silkdot/src/color.zig

101 lines
3.4 KiB
Zig
Raw Permalink Normal View History

2024-04-07 14:03:38 -06:00
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,
};
}
2025-03-03 18:41:40 -07:00
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;
}
};
2024-04-07 14:03:38 -06:00
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});
}