fix(io): preserve selected real PSD masks (#130)

This commit is contained in:
MikeeI
2026-10-05 01:24:54 -04:00
committed by GitHub
parent ac22c5ae1e
commit f9f1efea6e
2 changed files with 22 additions and 4 deletions
+10 -4
View File
@@ -67,6 +67,12 @@ fn doc_mode(m: PsdMode) -> Option<ColorMode> {
})
}
// Real-mask metadata without a -3 channel still selects the synthetic -2 mask.
fn selected_real_mask(rec: &LayerRecord) -> Option<photocraft_psd::RealMask> {
rec.channel(CHANNEL_REAL_USER_MASK)?;
rec.layer_mask()?.real
}
impl Ctx<'_> {
fn warn(&mut self, s: impl Into<String>) {
self.warnings.push(s.into());
@@ -108,8 +114,8 @@ impl Ctx<'_> {
fn record_mask(&mut self, rec: &LayerRecord, name: &str) -> Option<LayerMask> {
let m = rec.layer_mask()?;
let (id, rect, default, flags) = match (rec.channel(CHANNEL_REAL_USER_MASK), m.real) {
(Some(_), Some(real)) => (CHANNEL_REAL_USER_MASK, real.rect, real.background, real.flags),
let (id, rect, default, flags) = match selected_real_mask(rec) {
Some(real) => (CHANNEL_REAL_USER_MASK, real.rect, real.background, real.flags),
_ => {
rec.channel(CHANNEL_USER_MASK)?;
(CHANNEL_USER_MASK, m.rect, m.default_color, m.flags)
@@ -272,7 +278,7 @@ impl Ctx<'_> {
// Mask flag bit 3: the user mask was rendered from vector data. For
// shape/text/smart layers the cached pixels already include that
// coverage, so applying it again would double-mask.
if rendered && rec.layer_mask().is_some_and(|m| m.flags & 8 != 0) {
if rendered && selected_real_mask(rec).is_none() && rec.layer_mask().is_some_and(|m| m.flags & 8 != 0) {
l.mask = None;
}
if !matches!(l.content, LayerContent::Shape(_)) {
@@ -292,7 +298,7 @@ impl Ctx<'_> {
vm.density = p.vector_density.map_or(1.0, |d| f32::from(d) / 255.0);
vm.feather = p.vector_feather.map_or(0.0, |f| f as f32);
}
if m.flags & 8 != 0 && m.real.is_none() {
if m.flags & 8 != 0 && selected_real_mask(rec).is_none() {
l.mask = None;
}
}
+12
View File
@@ -446,6 +446,18 @@ fn vector_rendered_mask_not_doubled_on_shapes() {
let l = d.layers.last().unwrap();
assert_eq!(l.content.kind_name(), "Shape");
assert!(l.mask.is_none());
let record = f.layers_mut().last_mut().unwrap();
let photocraft_psd::MaskData::Mask(mask) = &mut record.mask else { panic!("fixture has a mask") };
mask.real = Some(photocraft_psd::RealMask { flags: 0, background: 255, rect: photocraft_psd::Rect { top: 0, left: 0, bottom: 1, right: 1 } });
record.channels.push(photocraft_psd::ChannelData { id: -3, compression: Some(Compression::Raw), data: vec![64] });
let (imported, _) = psd_to_document(&f);
assert_eq!(imported.layers.last().unwrap().content.kind_name(), "Shape");
let mask = imported.layers.last().unwrap().mask.as_ref().expect("selected real mask must survive synthetic cleanup");
assert!((mask.surface.sample_channel(0, 0, 0) - 64.0 / 255.0).abs() < 1e-6);
let exported = export(&imported, "real-mask.psd", &Default::default()).unwrap();
let reopened = import("real-mask.psd", &exported.bytes).unwrap().document;
assert_eq!(reopened.layers.last().unwrap().mask.as_ref().unwrap().surface.pixel(0, 0), mask.surface.pixel(0, 0));
}
#[test]