1//! object representing a single color in the sRGB colorspace with channel values from 0-255
2
3const std = @import("std");
4const extras = @import("extras");
5const Self = @This();
6const color = @import("./mod.zig");
7const _x = @import("./_x.zig");
8const vec4 = @Vector(4, f32);
9
10r: u8, // red value
11g: u8, // green value
12b: u8, // blue value
13a: u8, // alpha value
14
15pub fn initRGBA(r: u8, g: u8, b: u8, a: u8) Self {
16 return Self{
17 .r = r,
18 .g = g,
19 .b = b,
20 .a = a,
21 };
22}
23
24pub fn initRGB(r: u8, g: u8, b: u8) Self {
25 return initRGBA(r, g, b, 255);
26}
27
28pub fn parseHexConst(comptime input: *const [7:0]u8) Self {
29 comptime std.debug.assert(input[0] == '#');
30 return comptime initRGB(
31 extras.parseDigits(u8, input[1..3], 16) catch unreachable,
32 extras.parseDigits(u8, input[3..5], 16) catch unreachable,
33 extras.parseDigits(u8, input[5..7], 16) catch unreachable,
34 );
35}
36
37pub fn parseHex(input: []const u8) Self {
38 std.debug.assert(input.len == 7);
39 std.debug.assert(input[0] == '#');
40 return initRGB(
41 extras.parseDigits(u8, input[1..3], 16) catch unreachable,
42 extras.parseDigits(u8, input[3..5], 16) catch unreachable,
43 extras.parseDigits(u8, input[5..7], 16) catch unreachable,
44 );
45}
46
47pub fn eql(x: Self, y: Self) bool {
48 return x.r == y.r and x.g == y.g and x.b == y.b and x.a == y.a;
49}
50
51pub fn nprint(x: Self, writer: anytype) !void {
52 if (x.a < 255) {
53 @branchHint(.cold);
54 try writer.print("#{x:0>2}{x:0>2}{x:0>2}{x:0>2}", .{ x.r, x.g, x.b, x.a });
55 return;
56 }
57 try writer.print("#{x:0>2}{x:0>2}{x:0>2}", .{ x.r, x.g, x.b });
58}
59
60const M = _x.mixin(@This(), u8, .r, .g, .b);
61pub const to_vec = M.to_vec;
62pub const from_vec = M.from_vec;
63pub const to_array = M.to_array;
64
65// https://www.w3.org/TR/WCAG/#dfn-relative-luminance
66// https://webstore.iec.ch/publication/6169
67pub fn to_linear_rgb(x: Self) color.LinearRgb {
68 const lut = comptime blk: {
69 @setEvalBranchQuota(20_000);
70 var res: [256]f32 = undefined;
71 for (0..256) |i| {
72 const c = @as(f32, @floatFromInt(i)) / 255.0;
73 res[i] = if (c <= 0.04045) c / 12.92 else std.math.pow(f32, (c + 0.055) / 1.055, 2.4);
74 }
75 break :blk res;
76 };
77 return color.LinearRgb.initRGBA(
78 lut[x.r],
79 lut[x.g],
80 lut[x.b],
81 lut[x.a],
82 );
83}
84
85pub fn to_float(x: Self) vec4 {
86 return x.to_vec() / @as(vec4, @splat(255.0));
87}
88
89pub fn from_float(y: vec4) Self {
90 const r, const g, const b, const a = y * @as(vec4, @splat(255.0));
91 return .{
92 .r = @intFromFloat(r),
93 .g = @intFromFloat(g),
94 .b = @intFromFloat(b),
95 .a = @intFromFloat(a),
96 };
97}
98
99// https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
100pub fn to_cmyk(x: Self) color.CMYK {
101 const r, const g, const b, const a = x.to_float();
102 const k = 1 - @max(r, g, b);
103 const c = (1 - r - k) / (1 - k);
104 const m = (1 - g - k) / (1 - k);
105 const y = (1 - b - k) / (1 - k);
106 return color.CMYK.initCMYKA(c, m, y, k, a);
107}
108
109// https://www.rapidtables.com/convert/color/rgb-to-hsl.html
110pub fn to_hsl(x: Self) color.HSL {
111 const r, const g, const b, const a = x.to_float();
112 const cmax = @max(r, g, b);
113 const cmin = @min(r, g, b);
114 const delta = cmax - cmin;
115 const h = blk: {
116 if (delta == 0) break :blk 0;
117 if (cmax == r) break :blk @rem(((g - b) / delta), 6.0) / 6.0;
118 if (cmax == g) break :blk (((b - r) / delta) + 2) / 6.0;
119 if (cmax == b) break :blk (((r - g) / delta) + 4) / 6.0;
120 unreachable;
121 };
122 const l = (cmax + cmin) / 2;
123 const s = if (delta == 0) 0 else delta / (1 - @abs(2 * l - 1));
124 return color.HSL.initHSLA(h, s, l, a);
125}
126
127// https://www.rapidtables.com/convert/color/rgb-to-hsv.html
128pub fn to_hsv(x: Self) color.HSV {
129 const r, const g, const b, const a = x.to_float();
130 const cmax = @max(r, g, b);
131 const cmin = @min(r, g, b);
132 const delta = cmax - cmin;
133 const h = blk: {
134 if (delta == 0) break :blk 0;
135 if (cmax == r) break :blk (@rem(((g - b) / delta), 6.0) / 6.0);
136 if (cmax == g) break :blk ((((b - r) / delta) + 2) / 6.0);
137 if (cmax == b) break :blk ((((r - g) / delta) + 4) / 6.0);
138 unreachable;
139 };
140 const s = if (cmax == 0) 0 else delta / cmax;
141 const v = cmax;
142 return color.HSV.initHSVA(h, s, v, a);
143}
144
145pub fn to_ycbcr(x: Self) color.YCbCr {
146 // zig fmt: off
147 const r, const g, const b, const a = x.to_vec();
148 return color.YCbCr.from_vec(.{
149 ( 0.299 * r + 0.587 * g + 0.114 * b) + 0,
150 (-0.1687 * r + -0.3313 * g + 0.5 * b) + 128,
151 ( 0.5 * r + -0.4187 * g + -0.0813 * b) + 128,
152 a,
153 });
154 // zig fmt: on
155}
156
157// https://web.archive.org/web/20180423091842/http://www.equasys.de/colorconversion.html
158pub fn to_yuv(x: Self) color.YUV {
159 // zig fmt: off
160 const r, const g, const b, const a = x.to_vec() / @as(vec4, @splat(255));
161 const y = r * 0.299 + g * 0.587 + b * 0.114;
162 const u = r * -0.147 + g * -0.289 + b * 0.436;
163 const v = r * 0.615 + g * -0.515 + b * -0.100;
164 return color.YUV.initYUVA(y, u, v, a);
165 // zig fmt: on
166}