2026-07-30 19:48:03 +01:00
|
|
|
//! ----------------------------------------------------
|
|
|
|
|
//! ----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
const std = @import("std");
|
|
|
|
|
const Vec = @import("vec.zig").Vec;
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub const Color = extern struct {
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// FIELDS
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
r: f32 = 0,
|
|
|
|
|
g: f32 = 0,
|
|
|
|
|
b: f32 = 0,
|
|
|
|
|
a: f32 = 1,
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// CONSTRUCTORS
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn rgb(r: f32, g: f32, b: f32) Self {
|
|
|
|
|
return .{ .r = r, .g = g, .b = b, .a = 1 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn rgba(r: f32, g: f32, b: f32, a: f32) Self {
|
|
|
|
|
return .{ .r = r, .g = g, .b = b, .a = a };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
2026-08-06 00:20:25 +01:00
|
|
|
pub inline fn fromHex(value: u32) Self {
|
|
|
|
|
const r = @as(f32, @as(u8, @truncate(value >> 24))) / 255.0;
|
|
|
|
|
const g = @as(f32, @as(u8, @truncate(value >> 16))) / 255.0;
|
|
|
|
|
const b = @as(f32, @as(u8, @truncate(value >> 8))) / 255.0;
|
|
|
|
|
|
2026-07-30 19:48:03 +01:00
|
|
|
const a = if (value > 0xFFFFFF)
|
2026-08-06 00:20:25 +01:00
|
|
|
@as(f32, @as(u8, @truncate(value))) / 255.0
|
2026-07-30 19:48:03 +01:00
|
|
|
else
|
|
|
|
|
1.0;
|
2026-08-06 00:20:25 +01:00
|
|
|
|
|
|
|
|
return .{
|
|
|
|
|
.r = r,
|
|
|
|
|
.g = g,
|
|
|
|
|
.b = b,
|
|
|
|
|
.a = a,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn toHex(self: Self) u32 {
|
|
|
|
|
const r: u32 = @intFromFloat(std.math.clamp(self.r, 0.0, 1.0) * 255.0);
|
|
|
|
|
const g: u32 = @intFromFloat(std.math.clamp(self.g, 0.0, 1.0) * 255.0);
|
|
|
|
|
const b: u32 = @intFromFloat(std.math.clamp(self.b, 0.0, 1.0) * 255.0);
|
|
|
|
|
const a: u32 = @intFromFloat(std.math.clamp(self.a, 0.0, 1.0) * 255.0);
|
|
|
|
|
|
|
|
|
|
return (r << 24) |
|
|
|
|
|
(g << 16) |
|
|
|
|
|
(b << 8) |
|
|
|
|
|
a;
|
2026-07-30 19:48:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn fromVec4(v: Vec(f32, 4)) Self {
|
|
|
|
|
return .{ .r = v.value[0], .g = v.value[1], .b = v.value[2], .a = v.value[3] };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn toVec4(self: Self) Vec(f32, 4) {
|
|
|
|
|
return Vec(f32, 4).new(.{ self.r, self.g, self.b, self.a });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// ARITHMETIC
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn add(self: Self, other: Self) Self {
|
|
|
|
|
return .{
|
|
|
|
|
.r = self.r + other.r,
|
|
|
|
|
.g = self.g + other.g,
|
|
|
|
|
.b = self.b + other.b,
|
|
|
|
|
.a = self.a + other.a,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn sub(self: Self, other: Self) Self {
|
|
|
|
|
return .{
|
|
|
|
|
.r = self.r - other.r,
|
|
|
|
|
.g = self.g - other.g,
|
|
|
|
|
.b = self.b - other.b,
|
|
|
|
|
.a = self.a - other.a,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn mul(self: Self, other: Self) Self {
|
|
|
|
|
return .{
|
|
|
|
|
.r = self.r * other.r,
|
|
|
|
|
.g = self.g * other.g,
|
|
|
|
|
.b = self.b * other.b,
|
|
|
|
|
.a = self.a * other.a,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn div(self: Self, other: Self) Self {
|
|
|
|
|
return .{
|
|
|
|
|
.r = self.r / other.r,
|
|
|
|
|
.g = self.g / other.g,
|
|
|
|
|
.b = self.b / other.b,
|
|
|
|
|
.a = self.a / other.a,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn scale(self: Self, scalar: f32) Self {
|
|
|
|
|
return .{
|
|
|
|
|
.r = self.r * scalar,
|
|
|
|
|
.g = self.g * scalar,
|
|
|
|
|
.b = self.b * scalar,
|
|
|
|
|
.a = self.a * scalar,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// COLOR OPERATIONS
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn lerp(self: Self, other: Self, t: f32) Self {
|
|
|
|
|
return .{
|
|
|
|
|
.r = self.r + (other.r - self.r) * t,
|
|
|
|
|
.g = self.g + (other.g - self.g) * t,
|
|
|
|
|
.b = self.b + (other.b - self.b) * t,
|
|
|
|
|
.a = self.a + (other.a - self.a) * t,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn blend(self: Self, other: Self) Self {
|
|
|
|
|
const out_a = other.a + self.a * (1.0 - other.a);
|
|
|
|
|
if (out_a == 0) return .{ .r = 0, .g = 0, .b = 0, .a = 0 };
|
|
|
|
|
return .{
|
|
|
|
|
.r = (other.r * other.a + self.r * self.a * (1.0 - other.a)) / out_a,
|
|
|
|
|
.g = (other.g * other.a + self.g * self.a * (1.0 - other.a)) / out_a,
|
|
|
|
|
.b = (other.b * other.a + self.b * self.a * (1.0 - other.a)) / out_a,
|
|
|
|
|
.a = out_a,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn premultiply(self: Self) Self {
|
|
|
|
|
return .{
|
|
|
|
|
.r = self.r * self.a,
|
|
|
|
|
.g = self.g * self.a,
|
|
|
|
|
.b = self.b * self.a,
|
|
|
|
|
.a = self.a,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn luminance(self: Self) f32 {
|
|
|
|
|
return self.r * 0.2126 + self.g * 0.7152 + self.b * 0.0722;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn toLinear(self: Self) Self {
|
|
|
|
|
const srgb_to_linear = struct {
|
|
|
|
|
inline fn map(c: f32) f32 {
|
|
|
|
|
if (c <= 0.04045) return c / 12.92;
|
|
|
|
|
return std.math.pow(f32, (c + 0.055) / 1.055, 2.4);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return .{
|
|
|
|
|
.r = srgb_to_linear.map(self.r),
|
|
|
|
|
.g = srgb_to_linear.map(self.g),
|
|
|
|
|
.b = srgb_to_linear.map(self.b),
|
|
|
|
|
.a = self.a,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn toSRGB(self: Self) Self {
|
|
|
|
|
const linear_to_srgb = struct {
|
|
|
|
|
inline fn map(c: f32) f32 {
|
|
|
|
|
if (c <= 0.0031308) return c * 12.92;
|
|
|
|
|
return 1.055 * std.math.pow(f32, c, 1.0 / 2.4) - 0.055;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return .{
|
|
|
|
|
.r = linear_to_srgb.map(self.r),
|
|
|
|
|
.g = linear_to_srgb.map(self.g),
|
|
|
|
|
.b = linear_to_srgb.map(self.b),
|
|
|
|
|
.a = self.a,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn toHSV(self: Self) struct { h: f32, s: f32, v: f32 } {
|
|
|
|
|
const max = @max(self.r, @max(self.g, self.b));
|
|
|
|
|
const min = @min(self.r, @min(self.g, self.b));
|
|
|
|
|
const delta = max - min;
|
|
|
|
|
|
|
|
|
|
const v = max;
|
|
|
|
|
const s = if (max == 0) 0 else delta / max;
|
|
|
|
|
|
|
|
|
|
var h: f32 = 0;
|
|
|
|
|
if (delta != 0) {
|
|
|
|
|
if (max == self.r) {
|
|
|
|
|
h = 60.0 * @mod((self.g - self.b) / delta, 6.0);
|
|
|
|
|
} else if (max == self.g) {
|
|
|
|
|
h = 60.0 * ((self.b - self.r) / delta + 2.0);
|
|
|
|
|
} else {
|
|
|
|
|
h = 60.0 * ((self.r - self.g) / delta + 4.0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return .{ .h = h, .s = s, .v = v };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub inline fn fromHSV(h: f32, s: f32, v: f32) Self {
|
|
|
|
|
const c = v * s;
|
|
|
|
|
const hp = h / 60.0;
|
|
|
|
|
const x = c * (1.0 - @abs(@mod(hp, 2.0) - 1.0));
|
|
|
|
|
const m = v - c;
|
|
|
|
|
|
|
|
|
|
var r: f32 = 0;
|
|
|
|
|
var g: f32 = 0;
|
|
|
|
|
var b: f32 = 0;
|
|
|
|
|
|
|
|
|
|
if (hp < 1) {
|
|
|
|
|
r = c;
|
|
|
|
|
g = x;
|
|
|
|
|
} else if (hp < 2) {
|
|
|
|
|
r = x;
|
|
|
|
|
g = c;
|
|
|
|
|
} else if (hp < 3) {
|
|
|
|
|
g = c;
|
|
|
|
|
b = x;
|
|
|
|
|
} else if (hp < 4) {
|
|
|
|
|
g = x;
|
|
|
|
|
b = c;
|
|
|
|
|
} else if (hp < 5) {
|
|
|
|
|
r = x;
|
|
|
|
|
b = c;
|
|
|
|
|
} else {
|
|
|
|
|
r = c;
|
|
|
|
|
b = x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return .{ .r = r + m, .g = g + m, .b = b + m, .a = 1 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// CONSTANTS
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
pub const white = Self{ .r = 1, .g = 1, .b = 1, .a = 1 };
|
|
|
|
|
pub const black = Self{ .r = 0, .g = 0, .b = 0, .a = 1 };
|
|
|
|
|
pub const transparent = Self{ .r = 0, .g = 0, .b = 0, .a = 0 };
|
|
|
|
|
|
|
|
|
|
pub const red = Self{ .r = 1, .g = 0, .b = 0, .a = 1 };
|
|
|
|
|
pub const green = Self{ .r = 0, .g = 1, .b = 0, .a = 1 };
|
|
|
|
|
pub const blue = Self{ .r = 0, .g = 0, .b = 1, .a = 1 };
|
|
|
|
|
|
|
|
|
|
pub const yellow = Self{ .r = 1, .g = 1, .b = 0, .a = 1 };
|
|
|
|
|
pub const cyan = Self{ .r = 0, .g = 1, .b = 1, .a = 1 };
|
|
|
|
|
pub const magenta = Self{ .r = 1, .g = 0, .b = 1, .a = 1 };
|
|
|
|
|
|
|
|
|
|
pub const orange = Self{ .r = 1, .g = 0.55, .b = 0, .a = 1 };
|
|
|
|
|
pub const purple = Self{ .r = 0.5, .g = 0, .b = 0.5, .a = 1 };
|
|
|
|
|
pub const pink = Self{ .r = 1, .g = 0.75, .b = 0.8, .a = 1 };
|
|
|
|
|
pub const brown = Self{ .r = 0.6, .g = 0.3, .b = 0.1, .a = 1 };
|
|
|
|
|
pub const gray = Self{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 1 };
|
|
|
|
|
};
|