| 1 | //! object representing a single color in the sRGB colorspace with channel values from 0-255 |
| 2 | |
| 3 | const std = @import("std"); |
| 4 | const extras = @import("extras"); |
| 5 | const Self = @This(); |
| 6 | const color = @import("./mod.zig"); |
| 7 | const _x = @import("./_x.zig"); |
| 8 | const vec4 = @Vector(4, f32); |
| 9 | |
| 10 | r: u8, // red value |
| 11 | g: u8, // green value |
| 12 | b: u8, // blue value |
| 13 | a: u8, // alpha value |
| 14 | |
| 15 | pub 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 | |
| 24 | pub fn initRGB(r: u8, g: u8, b: u8) Self { |
| 25 | return initRGBA(r, g, b, 255); |
| 26 | } |
| 27 | |
| 28 | pub 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 | |
| 37 | pub 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 | |
| 47 | pub 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 | |
| 51 | pub 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 | |
| 60 | const M = _x.mixin(@This(), u8, .r, .g, .b); |
| 61 | pub const to_vec = M.to_vec; |
| 62 | pub const from_vec = M.from_vec; |
| 63 | pub const to_array = M.to_array; |
| 64 | |
| 65 | // https://www.w3.org/TR/WCAG/#dfn-relative-luminance |
| 66 | // https://webstore.iec.ch/publication/6169 |
| 67 | pub 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 | |
| 85 | pub fn to_float(x: Self) vec4 { |
| 86 | return x.to_vec() / @as(vec4, @splat(255.0)); |
| 87 | } |
| 88 | |
| 89 | pub 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 |
| 100 | pub 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 |
| 110 | pub 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 |
| 128 | pub 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 | |
| 145 | pub 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 |
| 158 | pub 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 | } |