| author | |
| committer | |
| log | 36f4989aee0406ab6ea7802fdac1fe8a45ea89d7 |
| tree | 914ab0bd00225af61041b9153c84f4cb42d6d29c |
| parent | 8c09516944357d85f4ffa3d7a1421f96f63ac420 |
7 files changed, 120 insertions(+), 11 deletions(-)
CMYK.zig+10| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | const std = @import("std"); |
| 4 | 4 | const Self = @This(); |
| 5 | const color = @import("./mod.zig"); | |
| 5 | 6 | |
| 6 | 7 | c: f32, // cyan |
| 7 | 8 | m: f32, // magenta |
| ... | ... | @@ -37,3 +38,12 @@ pub fn to_array(x: Self) [5]f32 { |
| 37 | 38 | x.a, |
| 38 | 39 | }; |
| 39 | 40 | } |
| 41 | ||
| 42 | // https://www.rapidtables.com/convert/color/cmyk-to-rgb.html | |
| 43 | pub fn to_srgb(x: Self) color.sRGB { | |
| 44 | const c, const m, const y, const k, const a = x.to_array(); | |
| 45 | const r = (1 - c) * (1 - k); | |
| 46 | const g = (1 - m) * (1 - k); | |
| 47 | const b = (1 - y) * (1 - k); | |
| 48 | return color.sRGB.from_float(.{ r, g, b, a }); | |
| 49 | } |
HSL.zig+22| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | const std = @import("std"); |
| 4 | 4 | const Self = @This(); |
| 5 | const color = @import("./mod.zig"); | |
| 5 | 6 | const _x = @import("./_x.zig"); |
| 6 | 7 | |
| 7 | 8 | h: f32, // hue |
| ... | ... | @@ -27,3 +28,24 @@ pub fn eql(x: Self, y: Self) bool { |
| 27 | 28 | } |
| 28 | 29 | |
| 29 | 30 | pub usingnamespace _x.mixin(@This(), f32, .h, .s, .l); |
| 31 | ||
| 32 | // https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB | |
| 33 | // https://www.rapidtables.com/convert/color/hsl-to-rgb.html | |
| 34 | pub fn to_srgb(t: Self) color.sRGB { | |
| 35 | var h, const s, const l, const a = t.to_array(); | |
| 36 | h *= 360.0; | |
| 37 | h /= 60.0; | |
| 38 | const c = (1.0 - @abs(2 * l - 1.0)) * s; | |
| 39 | const x = c * (1 - @abs(@rem(h, 2.0) - 1.0)); | |
| 40 | const r, const g, const b = blk: { | |
| 41 | if (h >= 0.0 and h < 1.0) break :blk .{ c, x, 0 }; | |
| 42 | if (h >= 1.0 and h < 2.0) break :blk .{ x, c, 0 }; | |
| 43 | if (h >= 2.0 and h < 3.0) break :blk .{ 0, c, x }; | |
| 44 | if (h >= 3.0 and h < 4.0) break :blk .{ 0, x, c }; | |
| 45 | if (h >= 4.0 and h < 5.0) break :blk .{ x, 0, c }; | |
| 46 | if (h >= 5.0 and h < 6.0) break :blk .{ c, 0, x }; | |
| 47 | unreachable; | |
| 48 | }; | |
| 49 | const m = l - c / 2.0; | |
| 50 | return color.sRGB.from_float(.{ r + m, g + m, b + m, a }); | |
| 51 | } |
HSV.zig+22| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | const std = @import("std"); |
| 4 | 4 | const Self = @This(); |
| 5 | const color = @import("./mod.zig"); | |
| 5 | 6 | const _x = @import("./_x.zig"); |
| 6 | 7 | |
| 7 | 8 | h: f32, // hue |
| ... | ... | @@ -27,3 +28,24 @@ pub fn eql(x: Self, y: Self) bool { |
| 27 | 28 | } |
| 28 | 29 | |
| 29 | 30 | pub usingnamespace _x.mixin(@This(), f32, .h, .s, .v); |
| 31 | ||
| 32 | // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB | |
| 33 | // https://www.rapidtables.com/convert/color/hsv-to-rgb.html | |
| 34 | pub fn to_srgb(t: Self) color.sRGB { | |
| 35 | var h, const s, const v, const a = t.to_array(); | |
| 36 | h *= 360.0; | |
| 37 | h /= 60.0; | |
| 38 | const c = v * s; | |
| 39 | const x = c * (1 - @abs(@rem(h, 2.0) - 1.0)); | |
| 40 | const r, const g, const b = blk: { | |
| 41 | if (h >= 0.0 and h < 1.0) break :blk .{ c, x, 0 }; | |
| 42 | if (h >= 1.0 and h < 2.0) break :blk .{ x, c, 0 }; | |
| 43 | if (h >= 2.0 and h < 3.0) break :blk .{ 0, c, x }; | |
| 44 | if (h >= 3.0 and h < 4.0) break :blk .{ 0, x, c }; | |
| 45 | if (h >= 4.0 and h < 5.0) break :blk .{ x, 0, c }; | |
| 46 | if (h >= 5.0 and h < 6.0) break :blk .{ c, 0, x }; | |
| 47 | unreachable; | |
| 48 | }; | |
| 49 | const m = v - c; | |
| 50 | return color.sRGB.from_float(.{ r + m, g + m, b + m, a }); | |
| 51 | } |
LinearRgb.zig+12| ... | ... | @@ -2,7 +2,9 @@ |
| 2 | 2 | |
| 3 | 3 | const std = @import("std"); |
| 4 | 4 | const Self = @This(); |
| 5 | const color = @import("./mod.zig"); | |
| 5 | 6 | const _x = @import("./_x.zig"); |
| 7 | const vec4 = @Vector(4, f32); | |
| 6 | 8 | |
| 7 | 9 | r: f32, // red |
| 8 | 10 | g: f32, // green |
| ... | ... | @@ -26,8 +28,18 @@ pub fn eql(x: Self, y: Self) bool { |
| 26 | 28 | return x.r == y.r and x.g == y.g and x.b == y.b and x.a == y.a; |
| 27 | 29 | } |
| 28 | 30 | |
| 31 | // https://www.w3.org/TR/WCAG/#dfn-relative-luminance | |
| 29 | 32 | pub fn relative_luminance(x: Self) f32 { |
| 30 | 33 | return (0.2126 * x.r) + (0.7152 * x.g) + (0.0722 * x.b); |
| 31 | 34 | } |
| 32 | 35 | |
| 33 | 36 | pub usingnamespace _x.mixin(@This(), f32, .r, .g, .b); |
| 37 | ||
| 38 | // https://gamedev.stackexchange.com/a/194038 | |
| 39 | pub fn to_srgb(x: Self) color.sRGB { | |
| 40 | const v: vec4 = x.to_array(); | |
| 41 | const cutoff = v < @as(vec4, @splat(0.0031308)); | |
| 42 | const higher = @as(vec4, @splat(1.055)) * std.math.pow(vec4, v, @splat(1.0 / 2.4)) - @as(vec4, @splat(0.055)); | |
| 43 | const lower = v * @as(vec4, @splat(12.92)); | |
| 44 | return color.sRGB.from_float(@select(f32, cutoff, higher, lower)); | |
| 45 | } |
YCbCr.zig+6-6| ... | ... | @@ -27,16 +27,16 @@ pub fn eql(x: Self, y: Self) bool { |
| 27 | 27 | return x.y == y.y and x.cb == y.cb and x.cr == y.cr and x.a == y.a; |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | pub usingnamespace _x.mixin(@This(), .y, .cb, .cr); | |
| 30 | pub usingnamespace _x.mixin(@This(), u8, .y, .cb, .cr); | |
| 31 | 31 | |
| 32 | 32 | pub fn to_srgb(x: Self) color.sRGB { |
| 33 | 33 | // zig fmt: off |
| 34 | const f = x.to_vec(); | |
| 34 | const r, const g, const b, const a = x.to_vec(); | |
| 35 | 35 | return color.sRGB.from_vec(.{ |
| 36 | @max(0, @min(255, f[0] + 1.402 * (f[2]-128))), | |
| 37 | @max(0, @min(255, f[0] - 0.34414 * (f[1]-128) - 0.71414 * (f[2]-128))), | |
| 38 | @max(0, @min(255, f[0] + 1.772 * (f[1]-128))), | |
| 39 | @max(0, @min(255, f[3])), | |
| 36 | @max(0, @min(255, r + 1.402 * (b-128))), | |
| 37 | @max(0, @min(255, r - 0.34414 * (g-128) - 0.71414 * (b-128))), | |
| 38 | @max(0, @min(255, r + 1.772 * (g-128))), | |
| 39 | @max(0, @min(255, a)), | |
| 40 | 40 | }); |
| 41 | 41 | // zig fmt: on |
| 42 | 42 | } |
sRGB.zig+20-5| ... | ... | @@ -75,6 +75,8 @@ pub fn format(x: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, |
| 75 | 75 | |
| 76 | 76 | pub usingnamespace _x.mixin(@This(), u8, .r, .g, .b); |
| 77 | 77 | |
| 78 | // https://www.w3.org/TR/WCAG/#dfn-relative-luminance | |
| 79 | // https://webstore.iec.ch/publication/6169 | |
| 78 | 80 | pub fn to_linear_rgb(x: Self) color.LinearRgb { |
| 79 | 81 | const lut = comptime blk: { |
| 80 | 82 | @setEvalBranchQuota(10_000); |
| ... | ... | @@ -102,6 +104,17 @@ pub fn to_float(x: Self) Float { |
| 102 | 104 | }; |
| 103 | 105 | } |
| 104 | 106 | |
| 107 | pub fn from_float(y: @Vector(4, f32)) Self { | |
| 108 | const r, const g, const b, const a = y * @as(@Vector(4, f32), @splat(255.0)); | |
| 109 | return .{ | |
| 110 | .r = @intFromFloat(r), | |
| 111 | .g = @intFromFloat(g), | |
| 112 | .b = @intFromFloat(b), | |
| 113 | .a = @intFromFloat(a), | |
| 114 | }; | |
| 115 | } | |
| 116 | ||
| 117 | // https://www.rapidtables.com/convert/color/rgb-to-cmyk.html | |
| 105 | 118 | pub fn to_cmyk(x: Self) color.CMYK { |
| 106 | 119 | const f = x.to_float(); |
| 107 | 120 | const k = 1 - @max(f.r, f.g, f.b); |
| ... | ... | @@ -112,6 +125,7 @@ pub fn to_cmyk(x: Self) color.CMYK { |
| 112 | 125 | return color.CMYK.initCMYKA(c, m, y, k, a); |
| 113 | 126 | } |
| 114 | 127 | |
| 128 | // https://www.rapidtables.com/convert/color/rgb-to-hsl.html | |
| 115 | 129 | pub fn to_hsl(x: Self) color.HSL { |
| 116 | 130 | const f = x.to_float(); |
| 117 | 131 | const cmax = @max(f.r, f.g, f.b); |
| ... | ... | @@ -130,6 +144,7 @@ pub fn to_hsl(x: Self) color.HSL { |
| 130 | 144 | return color.HSL.initHSLA(h, s, l, a); |
| 131 | 145 | } |
| 132 | 146 | |
| 147 | // https://www.rapidtables.com/convert/color/rgb-to-hsv.html | |
| 133 | 148 | pub fn to_hsv(x: Self) color.HSV { |
| 134 | 149 | const f = x.to_float(); |
| 135 | 150 | const cmax = @max(f.r, f.g, f.b); |
| ... | ... | @@ -150,12 +165,12 @@ pub fn to_hsv(x: Self) color.HSV { |
| 150 | 165 | |
| 151 | 166 | pub fn to_ycbcr(x: Self) color.YCbCr { |
| 152 | 167 | // zig fmt: off |
| 153 | const f = x.to_vec(); | |
| 168 | const r, const g, const b, const a = x.to_vec(); | |
| 154 | 169 | return color.YCbCr.from_vec(.{ |
| 155 | ( 0.299 * f.r + 0.587 * f.g + 0.114 * f.b) + 0, | |
| 156 | (-0.1687 * f.r + -0.3313 * f.g + 0.5 * f.b) + 128, | |
| 157 | ( 0.5 * f.r + -0.4187 * f.g + -0.0813 * f.b) + 128, | |
| 158 | f.a, | |
| 170 | ( 0.299 * r + 0.587 * g + 0.114 * b) + 0, | |
| 171 | (-0.1687 * r + -0.3313 * g + 0.5 * b) + 128, | |
| 172 | ( 0.5 * r + -0.4187 * g + -0.0813 * b) + 128, | |
| 173 | a, | |
| 159 | 174 | }); |
| 160 | 175 | // zig fmt: on |
| 161 | 176 | } |
test.zig+28| ... | ... | @@ -1,8 +1,23 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const color = @import("color"); |
| 3 | 3 | |
| 4 | fn expect(actual: anytype) Expect(@TypeOf(actual)) { | |
| 5 | return .{ .actual = actual }; | |
| 6 | } | |
| 7 | ||
| 8 | fn Expect(T: type) type { | |
| 9 | return struct { | |
| 10 | actual: T, | |
| 11 | ||
| 12 | fn toEqual(self: *const @This(), expected: T) !void { | |
| 13 | try std.testing.expectEqual(expected, self.actual); | |
| 14 | } | |
| 15 | }; | |
| 16 | } | |
| 17 | ||
| 4 | 18 | test { |
| 5 | 19 | // https://www.colorhexa.com/4fc3f7 |
| 20 | // https://encycolorpedia.com/4fc3f7 | |
| 6 | 21 | const o = color.sRGB.parseHexConst("#4FC3F7"); |
| 7 | 22 | { |
| 8 | 23 | const r, const g, const b, const a = o.to_array(); |
| ... | ... | @@ -10,15 +25,18 @@ test { |
| 10 | 25 | try std.testing.expect(g == 195); |
| 11 | 26 | try std.testing.expect(b == 247); |
| 12 | 27 | try std.testing.expect(a == 255); |
| 28 | try expect(o.to_array()).toEqual(.{ 79, 195, 247, 255 }); | |
| 13 | 29 | } |
| 14 | 30 | { |
| 15 | 31 | // 68% cyan, 21.1% magenta, 0% yellow and 3.1% black |
| 32 | // cyan: 68% (0.68), magenta: 21% (0.211), yellow: 0% (0.0), key: 3% (0.031) | |
| 16 | 33 | const c, const m, const y, const k, const a = o.to_cmyk().to_array(); |
| 17 | 34 | try std.testing.expectApproxEqAbs(0.68, c, 0.01); |
| 18 | 35 | try std.testing.expectApproxEqAbs(0.21, m, 0.01); |
| 19 | 36 | try std.testing.expectApproxEqAbs(0.00, y, 0.01); |
| 20 | 37 | try std.testing.expectApproxEqAbs(0.03, k, 0.01); |
| 21 | 38 | try std.testing.expect(a == 1.0); |
| 39 | try expect(o.to_cmyk().to_srgb().to_array()).toEqual(.{ 79, 195, 247, 255 }); | |
| 22 | 40 | } |
| 23 | 41 | { |
| 24 | 42 | // 198.6°, 91.3, 63.9 |
| ... | ... | @@ -27,6 +45,7 @@ test { |
| 27 | 45 | try std.testing.expectApproxEqAbs(0.913, s, 0.001); |
| 28 | 46 | try std.testing.expectApproxEqAbs(0.639, l, 0.001); |
| 29 | 47 | try std.testing.expect(a == 1.0); |
| 48 | try expect(o.to_hsl().to_srgb().to_array()).toEqual(.{ 79, 195, 247, 255 }); | |
| 30 | 49 | } |
| 31 | 50 | { |
| 32 | 51 | // 198.6°, 68, 96.9 |
| ... | ... | @@ -35,13 +54,22 @@ test { |
| 35 | 54 | try std.testing.expectApproxEqAbs(0.680, s, 0.001); |
| 36 | 55 | try std.testing.expectApproxEqAbs(0.969, v, 0.001); |
| 37 | 56 | try std.testing.expect(a == 1.0); |
| 57 | try expect(o.to_hsv().to_srgb().to_array()).toEqual(.{ 79, 195, 247, 255 }); | |
| 38 | 58 | } |
| 39 | 59 | { |
| 60 | // 0.07818742180518633 0.5457244613701866 0.9301108583754237 | |
| 40 | 61 | // 0.0781874218051863, 0.545724461370187, 0.930110858375424 |
| 41 | 62 | const r, const g, const b, const a = o.to_linear_rgb().to_array(); |
| 42 | 63 | try std.testing.expectApproxEqAbs(0.078, r, 0.001); |
| 43 | 64 | try std.testing.expectApproxEqAbs(0.545, g, 0.001); |
| 44 | 65 | try std.testing.expectApproxEqAbs(0.930, b, 0.001); |
| 45 | 66 | try std.testing.expect(a == 1.0); |
| 67 | // try expect(o.to_linear_rgb().to_srgb().to_array()).toEqual(.{ 79, 195, 247, 255 }); // https://github.com/ziglang/zig/issues/22354 | |
| 68 | try std.testing.expectApproxEqAbs(0.474, o.to_linear_rgb().relative_luminance(), 0.001); | |
| 69 | } | |
| 70 | { | |
| 71 | // Y: 158.789, Cb: 167.996, Cr: 73.384 | |
| 72 | try expect(o.to_ycbcr().to_array()).toEqual(.{ 166, 173, 65, 255 }); | |
| 73 | try expect(o.to_ycbcr().to_srgb().to_array()).toEqual(.{ 77, 195, 245, 255 }); | |
| 46 | 74 | } |
| 47 | 75 | } |