mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-14 19:26:51 -04:00
PSD export: pad regenerated fill blocks to 4 bytes, as Photoshop does (#2759)
Photoshop pads every layer-level tagged block to a multiple of 4 inside the length field; all 567 Photoshop-authored fixtures in the corpora do. The exporter padded regenerated blocks to even only, so a Pattern Fill's PtFl came out 2 bytes short of the boundary for half of all pattern names and left the layer record's extra data unaligned. The pad is applied where the fill blocks are regenerated, not in the generic loop: preserved blocks must stay byte-identical (psd_structure, roundtrip) and shmd is length-structured (comps_artboards). Co-authored-by: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
ab76c242a1
commit
e2aa19341f
2 files changed
+59
-1
No files matched your search
@@ -504,7 +504,12 @@ impl Ex {
|
||||
let keep = raw.iter().any(|(k, d)| matches!(k, b"SoCo" | b"GdFl" | b"PtFl") && blocks::parse_fill(k, d).as_ref() == Some(f));
|
||||
if !keep {
|
||||
raw.retain(|(k, _)| !matches!(k, b"SoCo" | b"GdFl" | b"PtFl"));
|
||||
regenerated.push(blocks::write_fill(f));
|
||||
// Photoshop pads layer-level blocks to a multiple of 4 inside the length (every
|
||||
// one in its own files is). A fill descriptor of 2 mod 4 bytes, as a pattern
|
||||
// name of the wrong length gives, left the record's extra data unaligned.
|
||||
let (key, mut data) = blocks::write_fill(f);
|
||||
data.resize(data.len().next_multiple_of(4), 0);
|
||||
regenerated.push((key, data));
|
||||
}
|
||||
};
|
||||
match &l.content {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//! Every layer-level tagged block of an exported PSD/PSB is a multiple of 4 bytes long.
|
||||
//!
|
||||
//! Photoshop pads layer-level blocks to 4 inside the length field; all of its own files do (567
|
||||
//! fixtures checked), and a regenerated block that stopped at an even but unaligned size left the
|
||||
//! layer record's extra data unaligned: a Pattern Fill's `PtFl` came out 2 bytes short of the
|
||||
//! boundary for half of all pattern names.
|
||||
|
||||
use photocraft_color::{ColorMode, PixelFormat, SampleType};
|
||||
use photocraft_doc::{Document, Fill, Layer, LayerContent, Pattern};
|
||||
use photocraft_geom::{Rect, Size};
|
||||
use photocraft_io::*;
|
||||
use photocraft_psd::PsdFile;
|
||||
use photocraft_raster::Surface;
|
||||
|
||||
fn pattern(name: &str) -> Pattern {
|
||||
let mut s = Surface::new(PixelFormat::RGBA8);
|
||||
s.fill_rect(Rect::new(0, 0, 8, 8), &[0.8, 0.8, 0.8, 1.0]);
|
||||
s.fill_rect(Rect::new(0, 0, 4, 4), &[0.2, 0.4, 0.9, 1.0]);
|
||||
Pattern { id: "059289fb-0000-4000-8000-000000000001".into(), name: name.into(), width: 8, height: 8, surface: s }
|
||||
}
|
||||
|
||||
/// One Pattern Fill layer per name; the name's length moves the descriptor's size by two bytes
|
||||
/// per character, so consecutive lengths cover every residue modulo 4.
|
||||
fn doc(depth: SampleType, names: &[&str]) -> Document {
|
||||
let mut d = Document::new("p", Size::new(40, 24), ColorMode::Rgb, depth);
|
||||
for name in names {
|
||||
let p = pattern(name);
|
||||
let fill = Fill::Pattern { name: (*name).into(), scale: 1.0, id: p.id.clone(), angle: 0.0, link: true, phase: (0.0, 0.0) };
|
||||
d.patterns.push(p);
|
||||
d.layers.push(Layer::new(format!("Fill {name}"), LayerContent::Fill(fill)));
|
||||
}
|
||||
d
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_blocks_are_four_byte_aligned() {
|
||||
let names = ["A", "Ab", "Abc", "Abcd", "Dots", "Checker", "Wood grain"];
|
||||
for depth in [SampleType::U8, SampleType::U16, SampleType::F32] {
|
||||
for path in ["x.psd", "x.psb"] {
|
||||
let bytes = export(&doc(depth, &names), path, &ExportOptions::default()).expect("export").bytes;
|
||||
let file = PsdFile::from_bytes(&bytes).expect("reparse");
|
||||
let mut fills = 0;
|
||||
for layer in file.layers() {
|
||||
for b in &layer.blocks {
|
||||
let key = String::from_utf8_lossy(&b.key).into_owned();
|
||||
assert_eq!(b.data.len() % 4, 0, "{depth:?} {path}: layer {:?} block {key} is {} bytes", layer.name(), b.data.len());
|
||||
}
|
||||
fills += usize::from(layer.block(b"PtFl").is_some());
|
||||
}
|
||||
assert_eq!(fills, names.len(), "{depth:?} {path}: every pattern fill keeps its PtFl block");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user