From f4cf980a375aaae75687991bb0a235bacb0ee6e8 Mon Sep 17 00:00:00 2001 From: MikeeI Date: Mon, 5 Oct 2026 07:00:57 +0200 Subject: [PATCH] fix(io): detect merged PSD alpha at the stored sample precision (#111) --- crates/io/src/psd_export.rs | 7 ++++++- crates/io/tests/composite.rs | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/io/src/psd_export.rs b/crates/io/src/psd_export.rs index 072bd252..7cc4b792 100644 --- a/crates/io/src/psd_export.rs +++ b/crates/io/src/psd_export.rs @@ -502,7 +502,12 @@ fn merged_planes(doc: &Document, fmt: &PixelFormat, cmyk: bool, matte: bool) -> let w = canvas.width() as usize; let space = photocraft_compose::cmyk_space(doc); let _ = photocraft_compose::render_bands(doc, canvas, 0, |band| -> Result<(), ()> { - has_alpha |= band.px.iter().any(|p| q255(p[3]) < 255 || (sample == SampleType::F32 && p[3] < 1.0)); + // Retain alpha whenever it differs from opaque at the stored precision. + has_alpha |= band.px.iter().any(|p| match sample { + SampleType::U8 => q255(p[3]) < 255, + SampleType::U16 => (p[3].clamp(0.0, 1.0) * 65535.0 + 0.5) as u16 != u16::MAX, + SampleType::F32 => p[3] != 1.0, + }); translucent |= band.px.iter().any(|p| p[3] < 1.0); let start = (band.rect.y0 - canvas.y0) as usize * w; // Converted and encoded on all cores, then copied into each plane. diff --git a/crates/io/tests/composite.rs b/crates/io/tests/composite.rs index d5bb5e41..fbb9e5ab 100644 --- a/crates/io/tests/composite.rs +++ b/crates/io/tests/composite.rs @@ -171,4 +171,16 @@ fn merged_alpha_written_when_transparent() { assert!(f.merged_has_alpha()); let merged = merged_composite(&f).unwrap(); assert_eq!(merged[3][3], 0.0); + + // U8 rounding must not erase transparency that is representable in the exported U16 plane. + let mut near = photocraft_doc::Document::new("near opaque", photocraft_geom::Size::new(1, 1), ColorMode::Rgb, SampleType::U16); + let mut layer = photocraft_doc::Layer::raster("pixel", near.pixel_format()); + layer.surface_mut().unwrap().fill_rect(near.bounds(), &[1.0, 0.0, 0.0, 65500.0 / 65535.0]); + near.layers.push(layer); + let out = export(&near, "near.psd", &ExportOptions::default()).unwrap(); + let file = PsdFile::from_bytes(&out.bytes).unwrap(); + assert_eq!(file.header.channels, 4); + assert!(file.merged_has_alpha()); + let samples = file.decode_merged().unwrap(); + assert_eq!(u16::from_be_bytes([samples[6], samples[7]]), 65500); }