NYXMath/src/color.zig

301 lines
9.7 KiB
Zig
Raw Normal View History

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 };
}
/// ----------------------------------------------------
/// ----------------------------------------------------
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)
@as(f32, @as(u8, @truncate(value))) / 255.0
2026-07-30 19:48:03 +01:00
else
1.0;
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 };
};