Skip to content

Commit

Permalink
colorspace: renamed from color
Browse files Browse the repository at this point in the history
  • Loading branch information
arrufat committed Oct 2, 2024
1 parent 9d4ebcf commit 6f6c301
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 117 deletions.
8 changes: 4 additions & 4 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ pub fn build(b: *Build) void {
const optimize = b.standardOptimizeOption(.{});

// Export module.
_ = b.addModule("zignal", .{ .root_source_file = b.path("src/zignal.zig") });
_ = b.addModule("zignal", .{ .root_source_file = b.path("src/root.zig") });

// Build zignal.
const zignal = buildModule(b, "zignal", target, optimize);
const zignal = buildModule(b, "root", target, optimize);

// Emit docs.
const docs_step = b.step("docs", "Emit docs");
Expand All @@ -26,7 +26,7 @@ pub fn build(b: *Build) void {

const lib_check = b.addStaticLibrary(.{
.name = "zignal",
.root_source_file = b.path("src/zignal.zig"),
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
Expand All @@ -35,7 +35,7 @@ pub fn build(b: *Build) void {

const test_step = b.step("test", "Run library tests");
for ([_][]const u8{
"color",
"colorspace",
"image",
"geometry",
"matrix",
Expand Down
157 changes: 74 additions & 83 deletions src/color.zig → src/colorspace.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,99 +7,90 @@ const expectApproxEqRel = std.testing.expectApproxEqRel;
const lerp = std.math.lerp;
const pow = std.math.pow;

pub const Color = union(enum) {
Gray: u8,
Rgb: Rgb,
Rgba: Rgba,
Hsl: Hsl,
Hsv: Hsv,
Lab: Lab,

/// Returns true if and only if T can be treated as a color.
pub fn isColor(comptime T: type) bool {
return switch (T) {
u8, Rgb, Rgba, Hsv, Lab => true,
else => false,
};
}
/// Returns true if and only if T can be treated as a color.
pub fn isColor(comptime T: type) bool {
return switch (T) {
u8, Rgb, Rgba, Hsl, Hsv, Lab => true,
else => false,
};
}

/// Checks whether a type T can be used as an Rgb color, i.e., it has r, g, b fields of type u8.
fn isRgbCompatible(comptime T: type) bool {
return switch (T) {
Rgb, Rgba => true,
else => blk: {
comptime var checks: usize = 0;
for (std.meta.fields(T)) |field| {
if (std.mem.eql(u8, field.name, "r") and field.type == u8) {
checks += 1;
}
if (std.mem.eql(u8, field.name, "g") and field.type == u8) {
checks += 1;
}
if (std.mem.eql(u8, field.name, "b") and field.type == u8) {
checks += 1;
}
/// Checks whether a type T can be used as an Rgb color, i.e., it has r, g, b fields of type u8.
fn isRgbCompatible(comptime T: type) bool {
return switch (T) {
Rgb, Rgba => true,
else => blk: {
comptime var checks: usize = 0;
for (std.meta.fields(T)) |field| {
if (std.mem.eql(u8, field.name, "r") and field.type == u8) {
checks += 1;
}
break :blk checks == 3;
},
};
}
if (std.mem.eql(u8, field.name, "g") and field.type == u8) {
checks += 1;
}
if (std.mem.eql(u8, field.name, "b") and field.type == u8) {
checks += 1;
}
}
break :blk checks == 3;
},
};
}

/// Converts color into the T colorspace.
pub fn convert(comptime T: type, color: anytype) T {
comptime assert(isColor(T));
comptime assert(isColor(@TypeOf(color)));
return switch (T) {
u8 => switch (@TypeOf(color)) {
u8 => color,
inline else => color.toGray(),
},
Rgb => switch (@TypeOf(color)) {
Rgb => color,
u8 => .{ .r = color, .g = color, .b = color },
inline else => color.toRgb(),
},
Rgba => switch (@TypeOf(color)) {
Rgba => color,
u8 => .{ .r = color, .g = color, .b = color, .a = 255 },
inline else => color.toRgba(255),
},
Hsl => switch (@TypeOf(color)) {
Hsv => color,
u8 => .{ .h = 0, .s = 0, .l = @as(f32, @floatFromInt(color)) / 255 * 100 },
inline else => color.toHsl(),
},
Hsv => switch (@TypeOf(color)) {
Hsv => color,
u8 => .{ .h = 0, .s = 0, .v = @as(f32, @floatFromInt(color)) / 255 * 100 },
inline else => color.toHsv(),
},
Lab => switch (@TypeOf(color)) {
Lab => color,
u8 => .{ .l = @as(f32, @floatFromInt(color)) / 255 * 100, .a = 0, .b = 0 },
inline else => color.toLab(),
},
else => @compileError("Unsupported color " ++ @typeName(T)),
};
}
};
/// Converts color into the T colorspace.
pub fn convert(comptime T: type, color: anytype) T {
comptime assert(isColor(T));
comptime assert(isColor(@TypeOf(color)));
return switch (T) {
u8 => switch (@TypeOf(color)) {
u8 => color,
inline else => color.toGray(),
},
Rgb => switch (@TypeOf(color)) {
Rgb => color,
u8 => .{ .r = color, .g = color, .b = color },
inline else => color.toRgb(),
},
Rgba => switch (@TypeOf(color)) {
Rgba => color,
u8 => .{ .r = color, .g = color, .b = color, .a = 255 },
inline else => color.toRgba(255),
},
Hsl => switch (@TypeOf(color)) {
Hsv => color,
u8 => .{ .h = 0, .s = 0, .l = @as(f32, @floatFromInt(color)) / 255 * 100 },
inline else => color.toHsl(),
},
Hsv => switch (@TypeOf(color)) {
Hsv => color,
u8 => .{ .h = 0, .s = 0, .v = @as(f32, @floatFromInt(color)) / 255 * 100 },
inline else => color.toHsv(),
},
Lab => switch (@TypeOf(color)) {
Lab => color,
u8 => .{ .l = @as(f32, @floatFromInt(color)) / 255 * 100, .a = 0, .b = 0 },
inline else => color.toLab(),
},
else => @compileError("Unsupported color " ++ @typeName(T)),
};
}

test "Color.isRgbCompatible" {
try comptime expectEqual(Color.isRgbCompatible(Rgb), true);
try comptime expectEqual(Color.isRgbCompatible(Rgba), true);
try comptime expectEqual(Color.isRgbCompatible(Hsv), false);
try comptime expectEqual(Color.isRgbCompatible(Lab), false);
test "isRgbCompatible" {
try comptime expectEqual(isRgbCompatible(Rgb), true);
try comptime expectEqual(isRgbCompatible(Rgba), true);
try comptime expectEqual(isRgbCompatible(Hsv), false);
try comptime expectEqual(isRgbCompatible(Lab), false);
}

test "Color.convert" {
try expectEqual(Color.convert(u8, Rgb{ .r = 128, .g = 128, .b = 128 }), 128);
try expectEqual(Color.convert(u8, Lab{ .l = 50, .a = 0, .b = 0 }), 128);
try expectEqual(Color.convert(u8, Hsv{ .h = 0, .s = 100, .v = 50 }), 128);
test "convert" {
try expectEqual(convert(u8, Rgb{ .r = 128, .g = 128, .b = 128 }), 128);
try expectEqual(convert(u8, Lab{ .l = 50, .a = 0, .b = 0 }), 128);
try expectEqual(convert(u8, Hsv{ .h = 0, .s = 100, .v = 50 }), 128);
}

/// Alpha-blends c2 into c1.
inline fn alphaBlend(comptime T: type, c1: *T, c2: Rgba) void {
if (comptime !Color.isRgbCompatible(T)) {
if (comptime !isRgbCompatible(T)) {
@compileError(@typeName(T) ++ " is not Rgb compatible");
}
if (c2.a == 0) {
Expand Down
44 changes: 22 additions & 22 deletions src/draw.zig
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const std = @import("std");
const assert = std.debug.assert;
const as = @import("meta.zig").as;
const Color = @import("color.zig").Color;
const Rgba = @import("color.zig").Rgba;
const colorspace = @import("colorspace.zig");
const Rgba = @import("colorspace.zig").Rgba;
const Image = @import("image.zig").Image;
const Point2d = @import("point.zig").Point2d(f32);
const Rectangle = @import("geometry.zig").Rectangle(f32);
Expand All @@ -11,7 +11,7 @@ const Rectangle = @import("geometry.zig").Rectangle(f32);
/// Wu's line algorithm to perform antialiasing along the diagonal. Moreover, if color is of
/// Rgba type, it alpha-blends it on top of the image.
pub fn drawLine(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d, width: usize, color: anytype) void {
comptime assert(Color.isColor(@TypeOf(color)));
comptime assert(colorspace.isColor(@TypeOf(color)));
if (width == 0) return;
// To avoid casting all the time, perform all operations using the underlying type of p1 and p2.
const Float = @TypeOf(p1.x);
Expand All @@ -22,7 +22,7 @@ pub fn drawLine(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d, wid
const rows: Float = @floatFromInt(image.rows);
const cols: Float = @floatFromInt(image.cols);
const half_width: Float = @floatFromInt(width / 2);
var c2 = Color.convert(Rgba, color);
var c2 = colorspace.convert(Rgba, color);

if (x1 == x2) {
if (y1 > y2) std.mem.swap(Float, &y1, &y2);
Expand All @@ -35,9 +35,9 @@ pub fn drawLine(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d, wid
const px = x1 + i;
if (px >= 0 and px < cols) {
const pos = as(usize, y) * image.cols + as(usize, px);
var c1 = Color.convert(Rgba, image.data[pos]);
var c1 = colorspace.convert(Rgba, image.data[pos]);
c1.blend(c2);
image.data[pos] = Color.convert(T, c1);
image.data[pos] = colorspace.convert(T, c1);
}
}
}
Expand All @@ -52,9 +52,9 @@ pub fn drawLine(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d, wid
const py = y1 + i;
if (py >= 0 and py < rows) {
const pos = as(usize, py) * image.cols + as(usize, x);
var c1 = Color.convert(Rgba, image.data[pos]);
var c1 = colorspace.convert(Rgba, image.data[pos]);
c1.blend(c2);
image.data[pos] = Color.convert(T, c1);
image.data[pos] = colorspace.convert(T, c1);
}
}
}
Expand All @@ -80,14 +80,14 @@ pub fn drawLine(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d, wid
const py = @max(0, y + j);
const pos = as(usize, py) * image.cols + as(usize, x);
if (py >= 0 and py < rows) {
var c1: Rgba = Color.convert(Rgba, image.data[pos]);
var c1: Rgba = colorspace.convert(Rgba, image.data[pos]);
if (j == -half_width or j == half_width) {
c2.a = @intFromFloat((1 - (dy - y)) * max_alpha);
c1.blend(c2);
image.data[pos] = Color.convert(T, c1);
image.data[pos] = colorspace.convert(T, c1);
} else {
c1.blend(c2);
image.data[pos] = Color.convert(T, c1);
image.data[pos] = colorspace.convert(T, c1);
}
}
}
Expand All @@ -98,14 +98,14 @@ pub fn drawLine(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d, wid
const py = @max(0, y + 1 + j);
if (py >= 0 and py < rows) {
const pos = as(usize, py) * image.cols + as(usize, x);
var c1: Rgba = Color.convert(Rgba, image.data[pos]);
var c1: Rgba = colorspace.convert(Rgba, image.data[pos]);
if (j == -half_width or j == half_width) {
c2.a = @intFromFloat((dy - y) * max_alpha);
c1.blend(c2);
image.data[pos] = Color.convert(T, c1);
image.data[pos] = colorspace.convert(T, c1);
} else {
c1.blend(c2);
image.data[pos] = Color.convert(T, c1);
image.data[pos] = colorspace.convert(T, c1);
}
}
}
Expand All @@ -127,14 +127,14 @@ pub fn drawLine(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d, wid
const px = @max(0, x + j);
const pos = as(usize, y) * image.cols + as(usize, px);
if (px >= 0 and px < cols) {
var c1: Rgba = Color.convert(Rgba, image.data[pos]);
var c1: Rgba = colorspace.convert(Rgba, image.data[pos]);
if (j == -half_width or j == half_width) {
c2.a = @intFromFloat((1 - (dx - x)) * max_alpha);
c1.blend(c2);
image.data[pos] = Color.convert(T, c1);
image.data[pos] = colorspace.convert(T, c1);
} else {
c1.blend(c2);
image.data[pos] = Color.convert(T, c1);
image.data[pos] = colorspace.convert(T, c1);
}
}
}
Expand All @@ -146,13 +146,13 @@ pub fn drawLine(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d, wid
const px = @max(0, x + 1 + j);
const pos = as(usize, y) * image.cols + as(usize, px);
if (px >= 0 and px < cols) {
var c1: Rgba = Color.convert(Rgba, image.data[pos]);
var c1: Rgba = colorspace.convert(Rgba, image.data[pos]);
if (j == -half_width or j == half_width) {
c1.blend(c2);
image.data[pos] = Color.convert(T, c1);
image.data[pos] = colorspace.convert(T, c1);
} else {
c1.blend(c2);
image.data[pos] = Color.convert(T, c1);
image.data[pos] = colorspace.convert(T, c1);
}
}
}
Expand Down Expand Up @@ -200,7 +200,7 @@ pub fn drawLineFast(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d,
}

pub fn drawRectangle(comptime T: type, image: Image(T), rect: Rectangle, width: usize, color: anytype) void {
comptime assert(Color.isColor(@TypeOf(color)));
comptime assert(colorspace.isColor(@TypeOf(color)));
const points: []const Point2d = &.{
.{ .x = rect.l, .y = rect.t },
.{ .x = rect.r, .y = rect.t },
Expand All @@ -225,7 +225,7 @@ pub fn drawCross(comptime T: type, image: Image(T), center: Point2d, size: usize

/// Draws the given polygon defined as an array of points.
pub fn drawPolygon(comptime T: type, image: Image(T), polygon: []const Point2d, width: usize, color: anytype) void {
comptime assert(Color.isColor(@TypeOf(color)));
comptime assert(colorspace.isColor(@TypeOf(color)));
if (width == 0) return;
for (0..polygon.len) |i| {
drawLine(T, image, polygon[i], polygon[@mod(i + 1, polygon.len)], width, color);
Expand Down
2 changes: 1 addition & 1 deletion src/image.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const std = @import("std");
const assert = std.debug.assert;
const expectEqual = std.testing.expectEqual;
const Allocator = std.mem.Allocator;
const Rgba = @import("color.zig").Rgba;
const Rgba = @import("colorspace.zig").Rgba;
const as = @import("meta.zig").as;
const isScalar = @import("meta.zig").isScalar;
const isStruct = @import("meta.zig").isStruct;
Expand Down
13 changes: 6 additions & 7 deletions src/zignal.zig → src/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

const std = @import("std");

const color = @import("color.zig");
pub const Color = color.Color;
pub const Rgb = color.Rgb;
pub const Rgba = color.Rgba;
pub const Hsl = color.Hsl;
pub const Hsv = color.Hsv;
pub const Lab = color.Lab;
pub const colorspace = @import("colorspace.zig");
pub const Rgb = colorspace.Rgb;
pub const Rgba = colorspace.Rgba;
pub const Hsl = colorspace.Hsl;
pub const Hsv = colorspace.Hsv;
pub const Lab = colorspace.Lab;

pub const Point2d = @import("point.zig").Point2d;
pub const Point3d = @import("point.zig").Point3d;
Expand Down

0 comments on commit 6f6c301

Please sign in to comment.