initial commit

This commit is contained in:
2026-07-26 20:47:19 +02:00
commit fff3a5e734
37 changed files with 10781 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
//! Colour conversion.
//!
//! Config declares colours as the familiar 0xRRGGBBAA with straight alpha.
//! The two sinks want something different:
//!
//! * `river_window_v1.set_borders` takes one full-range u32 per channel —
//! river divides each by maxInt(u32) — with premultiplied alpha.
//! * wl_shm ARGB8888 wants premultiplied 8-bit channels packed into a u32.
const std = @import("std");
pub const Rgba = u32;
pub const Channels = struct {
r: u32,
g: u32,
b: u32,
a: u32,
};
fn premul8(c: u8, a: u8) u8 {
// Round to nearest rather than truncating, so 0xff at full alpha stays
// 0xff instead of drifting down.
return @intCast((@as(u32, c) * @as(u32, a) + 127) / 255);
}
/// Expand an 8-bit channel to the full u32 range: 0xff maps exactly to
/// 0xffffffff, which is what river treats as 1.0.
fn expand(c: u8) u32 {
return @as(u32, c) * 0x01010101;
}
fn split(rgba: Rgba) [4]u8 {
return .{
@intCast((rgba >> 24) & 0xff),
@intCast((rgba >> 16) & 0xff),
@intCast((rgba >> 8) & 0xff),
@intCast(rgba & 0xff),
};
}
/// Premultiplied, full-range channels for `set_borders`.
pub fn toChannels(rgba: Rgba) Channels {
const c = split(rgba);
const a = c[3];
return .{
.r = expand(premul8(c[0], a)),
.g = expand(premul8(c[1], a)),
.b = expand(premul8(c[2], a)),
.a = expand(a),
};
}
/// Premultiplied ARGB8888 as a native-endian u32, for wl_shm buffers.
pub fn toArgb8888(rgba: Rgba) u32 {
const c = split(rgba);
const a = c[3];
return (@as(u32, a) << 24) |
(@as(u32, premul8(c[0], a)) << 16) |
(@as(u32, premul8(c[1], a)) << 8) |
@as(u32, premul8(c[2], a));
}
test "opaque white survives both conversions intact" {
const ch = toChannels(0xffffffff);
try std.testing.expectEqual(@as(u32, 0xffffffff), ch.r);
try std.testing.expectEqual(@as(u32, 0xffffffff), ch.a);
try std.testing.expectEqual(@as(u32, 0xffffffff), toArgb8888(0xffffffff));
}
test "fully transparent premultiplies to zero" {
const ch = toChannels(0xffffff00);
try std.testing.expectEqual(@as(u32, 0), ch.r);
try std.testing.expectEqual(@as(u32, 0), ch.a);
try std.testing.expectEqual(@as(u32, 0), toArgb8888(0xffffff00));
}
test "opaque colour keeps its channels in argb order" {
// 0xRRGGBBAA -> 0xAARRGGBB
try std.testing.expectEqual(@as(u32, 0xff5294e2), toArgb8888(0x5294e2ff));
}
test "half alpha premultiplies channels but not alpha" {
const ch = toChannels(0xff000080);
try std.testing.expectEqual(@as(u32, 0x80808080), ch.a);
// 0xff * 0x80 / 0xff == 0x80
try std.testing.expectEqual(@as(u32, 0x80808080), ch.r);
try std.testing.expectEqual(@as(u32, 0), ch.g);
}