const std = @import("std"); const fmt = std.fmt; pub const RGB = struct { r: u8, g: u8, b: u8, pub fn init(comptime string: []const u8) !RGB { std.debug.assert(string.len == 6 or string.len == 7); comptime var index: usize = 0; if (string[0] == '#') index += 1; return .{ .r = try fmt.parseInt(u8, string[index .. index + 2], 16), .g = try fmt.parseInt(u8, string[index + 2 .. index + 4], 16), .b = try fmt.parseInt(u8, string[index + 4 .. index + 6], 16), }; } }; pub const RGBA = struct { r: f32, g: f32, b: f32, /// Alpha channel between 0.0 - 1.0 a: f32 = 1.0, /// Create from string at comptime pub fn init(comptime string: []const u8) !RGBA { std.debug.assert(string.len == 8 or string.len == 9); comptime var index: usize = 0; if (string[0] == '#') index += 1; const r = try fmt.parseInt(u8, string[index .. index + 2], 16); const g = try fmt.parseInt(u8, string[index + 2 .. index + 4], 16); const b = try fmt.parseInt(u8, string[index + 4 .. index + 6], 16); const a = try fmt.parseInt(u8, string[index + 6 .. index + 8], 16); return .{ .r = @as(f32, @floatFromInt(r)) / 0xff, .g = @as(f32, @floatFromInt(g)) / 0xff, .b = @as(f32, @floatFromInt(b)) / 0xff, .a = @as(f32, @floatFromInt(a)) / 0xff, }; } pub fn eql(a: *const RGBA, b: *const RGBA) bool { return a.r == b.r and a.g == b.g and a.b == b.b and a.a == b.a; } pub fn blend(base: *const RGBA, add: *const RGBA) RGBA { var r: RGBA = undefined; r.a = 1 - (1 - add.a) * (1 - base.a); if (r.a < std.math.floatEps(f32)) { r.r = 0; r.g = 0; r.b = 0; return r; } r.r = (add.r * add.a / r.a + base.r * base.a * (1 - add.a)) / r.a; r.g = (add.g * add.a / r.a + base.g * base.a * (1 - add.a)) / r.a; r.b = (add.b * add.a / r.a + base.b * base.a * (1 - add.a)) / r.a; return r; } }; test "RGB init from string" { const red = try RGB.init("#ff0000"); try std.testing.expectEqual(0xff, red.r); try std.testing.expectEqual(0x00, red.g); try std.testing.expectEqual(0x00, red.b); const green = try RGB.init("00ff00"); try std.testing.expectEqual(0x00, green.r); try std.testing.expectEqual(0xff, green.g); try std.testing.expectEqual(0x00, green.b); const blue = try RGB.init("#0000ff"); try std.testing.expectEqual(0x00, blue.r); try std.testing.expectEqual(0x00, blue.g); try std.testing.expectEqual(0xff, blue.b); try std.testing.expectError(fmt.ParseIntError.InvalidCharacter, RGB.init("xyzxyz")); } test "RGBA blend" { const red = RGBA{ .r = 1.0, .g = 0, .b = 0, .a = 0.0 }; const blue = RGBA{ .r = 0, .g = 0, .b = 1.0, .a = 0.0 }; const result = red.blend(&blue); std.debug.print("\n{any}\n", .{result}); const reeb = RGBA{ .r = 1.0, .g = 0, .b = 0, .a = 1.0 }; const blueb = RGBA{ .r = 0, .g = 0, .b = 1.0, .a = 0.0 }; const resultt = reeb.blend(&blueb); std.debug.print("{any}\n", .{resultt}); const roob = RGBA{ .r = 1.0, .g = 0, .b = 0, .a = 0.5 }; const bloob = RGBA{ .r = 0, .g = 0, .b = 1.0, .a = 0.5 }; const resulttt = roob.blend(&bloob); std.debug.print("{any}\n", .{resulttt}); }