From 80a0ee8e81d036aadf3da56c2a6ecb3750306dff Mon Sep 17 00:00:00 2001 From: Jakub Arnold Date: Sun, 28 Jan 2024 00:04:05 +0100 Subject: [PATCH] Colors are stupid, but now tints work Comfy's GPU tinting works in linear space, and while textures are being converted from sRGB to linear space, the colors passed to shaders are passed as in. Our shaders then do `a*b`, which means we're implicitly doing tinting in linear space. But blood canvas runs on the CPU and uses image data from the `image` crate, which apparently works in sRGB at least in the way we're using it. This means that the same tint color we get from `to_quad_draw(...)` can't just multiply, as that would be sRGB * linear. For simplicity we'll just convert it to sRGB and then do .linear_space_tint(...), which does a conversion to linear space, multiplication, and conversion back. We could do this differently, but this works for now. --- comfy-core/src/blood_canvas.rs | 13 ++++++++----- comfy-core/src/lib.rs | 31 +++++++++++++++++++++++++++++++ comfy/examples/blood_canvas.rs | 1 + 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/comfy-core/src/blood_canvas.rs b/comfy-core/src/blood_canvas.rs index 0e14316..93d8ed6 100644 --- a/comfy-core/src/blood_canvas.rs +++ b/comfy-core/src/blood_canvas.rs @@ -173,6 +173,8 @@ impl BloodCanvas { flip_x: bool, flip_y: bool, ) { + let tint = tint.to_srgb(); + let assets = ASSETS.borrow_mut(); let image_map = assets.texture_image_map.lock(); @@ -200,17 +202,17 @@ impl BloodCanvas { read_y = rect.offset.y + rect.size.y - y - 1; } - let px = image.get_pixel(read_x as u32, read_y as u32); + let src_px = image.get_pixel(read_x as u32, read_y as u32); - if px.0[3] > 0 { + if src_px.0[3] > 0 { let px_pos = position + vec2(x as f32, y as f32) / 16.0 - size_offset / 16.0; if tint.a < 1.0 { let existing = self.get_pixel(px_pos); - let tinted = - Into::::into(*px) * tint.alpha(1.0); + let tinted = Into::::into(*src_px) + .linear_space_tint(tint.alpha(1.0)); self.set_pixel( px_pos, @@ -219,7 +221,8 @@ impl BloodCanvas { } else { self.set_pixel( px_pos, - Into::::into(*px) * tint, + Into::::into(*src_px) + .linear_space_tint(tint), ); } } diff --git a/comfy-core/src/lib.rs b/comfy-core/src/lib.rs index a7c9597..8fd2af9 100644 --- a/comfy-core/src/lib.rs +++ b/comfy-core/src/lib.rs @@ -770,6 +770,37 @@ impl Color { Vec4::new(self.r, self.g, self.b, self.a) } + pub fn gamma_space_tint(self, tint: Color) -> Color { + let gamma_a = self.to_srgb(); + let gamma_b = tint.to_srgb(); + + let result = gamma_a * gamma_b; + + result.to_linear() + } + + pub fn linear_space_tint(self, tint: Color) -> Color { + let lin_a = self.to_linear(); + let lin_b = tint.to_linear(); + + let result = lin_a * lin_b; + + result.to_srgb() + } + + pub fn to_linear(self) -> Color { + Color::new(self.r.powf(2.2), self.g.powf(2.2), self.b.powf(2.2), self.a) + } + + pub fn to_srgb(self) -> Color { + Color::new( + self.r.powf(1.0 / 2.2), + self.g.powf(1.0 / 2.2), + self.b.powf(1.0 / 2.2), + self.a, + ) + } + pub fn to_array(self) -> [u8; 4] { [ (self.r * 255.0) as u8, diff --git a/comfy/examples/blood_canvas.rs b/comfy/examples/blood_canvas.rs index ccfe17e..4c46165 100644 --- a/comfy/examples/blood_canvas.rs +++ b/comfy/examples/blood_canvas.rs @@ -50,6 +50,7 @@ fn setup(c: &mut EngineContext) { Player, AnimatedSpriteBuilder::new() .z_index(10) + .color(DARKRED) .add_animation("idle", 0.1, true, AnimationSource::Atlas { name: "player".into(), offset: ivec2(0, 0),