Skip to content

Commit 09713ab

Browse files
zachelnetDeepSeek V4
andcommitted
fix(renderer): preserve float precision through bilinear un-premultiply
sample_bilinear_rgba rounded premultiplied RGB to u8 before the un-premultiply step, losing sub-pixel precision and producing dark/ bright halos on semi-transparent sprite edges after rotation. Keep RGB in float space through interpolation + un-premultiply and only round/clamp once at the end. Co-authored-by: DeepSeek V4 <deepseek@v4.ai>
1 parent 063cf38 commit 09713ab

1 file changed

Lines changed: 16 additions & 10 deletions

File tree

crates/koharu-app/src/renderer.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,22 +1126,28 @@ fn sample_bilinear_rgba(src: &RgbaImage, x: f32, y: f32) -> Rgba<u8> {
11261126
let p11 = get_premul(src.get_pixel(x1 as u32, y1 as u32));
11271127

11281128
let lerp = |a: f32, b: f32, t: f32| a + (b - a) * t;
1129-
let mut out = [0u8; 4];
1130-
for (i, out_ch) in out.iter_mut().enumerate().take(3) {
1129+
1130+
// Interpolate premultiplied RGB in float space (don't round yet).
1131+
let mut rgb_premul = [0.0f32; 3];
1132+
for i in 0..3 {
11311133
let top = lerp(p00[i], p10[i], wx);
11321134
let bottom = lerp(p01[i], p11[i], wx);
1133-
*out_ch = lerp(top, bottom, wy).round().clamp(0.0, 255.0) as u8;
1135+
rgb_premul[i] = lerp(top, bottom, wy);
11341136
}
1135-
// Alpha is interpolated in straight-alpha space (premultiplied alpha = original alpha).
1137+
1138+
// Alpha is interpolated in straight-alpha space.
11361139
let top_a = lerp(p00[3], p10[3], wx);
11371140
let bottom_a = lerp(p01[3], p11[3], wx);
1138-
let alpha = lerp(top_a, bottom_a, wy).round().clamp(0.0, 255.0);
1139-
out[3] = alpha as u8;
1141+
let alpha = lerp(top_a, bottom_a, wy).clamp(0.0, 255.0);
1142+
1143+
let mut out = [0u8; 4];
1144+
let alpha_u8 = alpha.round() as u8;
1145+
out[3] = alpha_u8;
11401146

1141-
// Un-premultiply: divide R, G, B by alpha (unless fully transparent).
1142-
if alpha > 0.0 {
1143-
for out_ch in out.iter_mut().take(3) {
1144-
*out_ch = ((*out_ch as f32) * 255.0 / alpha).round().clamp(0.0, 255.0) as u8;
1147+
// Un-premultiply once, then round — preserves sub-pixel precision.
1148+
if alpha_u8 != 0 {
1149+
for i in 0..3 {
1150+
out[i] = (rgb_premul[i] * 255.0 / alpha).round().clamp(0.0, 255.0) as u8;
11451151
}
11461152
}
11471153
Rgba(out)

0 commit comments

Comments
 (0)