From cf22417c1af4ed4bc4946dae7c68b0b04cda63e6 Mon Sep 17 00:00:00 2001 From: vangel76 Date: Mon, 12 Oct 2026 22:58:31 +0200 Subject: [PATCH] PSD: share the duplicated decode, placed-transform and writer helpers (#1133) One decode_plane for raw/RLE/ZIP (decode_channel and composite), placed::eight and quad_from for the SoLd/PlLd transforms, the writer's big-endian and UTF-16 helpers from descriptor.rs, one delimiter const in engine_data.rs, and the dead binding in write.rs. No behaviour change; adds ZIP and RLE composite tests. Co-Authored-By: Claude Sonnet 5.5 --- crates/psd/src/descriptor.rs | 19 +++-- crates/psd/src/engine_data.rs | 7 +- crates/psd/src/lib.rs | 129 ++++++++++++++++------------------ crates/psd/src/placed.rs | 12 +++- crates/psd/src/tests.rs | 83 ++++++++++++++++++++++ crates/psd/src/write.rs | 123 +++++++++++++++----------------- 6 files changed, 231 insertions(+), 142 deletions(-) diff --git a/crates/psd/src/descriptor.rs b/crates/psd/src/descriptor.rs index b28c3479..3695badc 100644 --- a/crates/psd/src/descriptor.rs +++ b/crates/psd/src/descriptor.rs @@ -248,7 +248,11 @@ fn read_value(r: &mut Reader, ty: &[u8; 4], depth: usize) -> Result { // ---------------------------------------------------------------- writing -fn put_u32(out: &mut Vec, v: u32) { +pub(crate) fn put_u16(out: &mut Vec, v: u16) { + out.extend_from_slice(&v.to_be_bytes()); +} + +pub(crate) fn put_u32(out: &mut Vec, v: u32) { out.extend_from_slice(&v.to_be_bytes()); } @@ -261,14 +265,21 @@ fn put_id(out: &mut Vec, id: &str) { out.extend_from_slice(id.as_bytes()); } -pub(crate) fn put_unicode(out: &mut Vec, s: &str) { - let v: Vec = s.encode_utf16().chain(std::iter::once(0)).collect(); +/// A length-prefixed UTF-16BE string (the length counts code units, including the NUL when +/// `terminated`). +pub(crate) fn put_utf16(out: &mut Vec, s: &str, terminated: bool) { + let v: Vec = s.encode_utf16().chain(terminated.then_some(0)).collect(); put_u32(out, v.len() as u32); for c in v { - out.extend_from_slice(&c.to_be_bytes()); + put_u16(out, c); } } +/// A descriptor Unicode string: NUL-terminated. +pub(crate) fn put_unicode(out: &mut Vec, s: &str) { + put_utf16(out, s, true); +} + /// Serialise a descriptor (without the version field). pub fn write_descriptor(out: &mut Vec, d: &Descriptor) { put_unicode(out, &d.class_name); diff --git a/crates/psd/src/engine_data.rs b/crates/psd/src/engine_data.rs index b0a177d0..7ab29bfa 100644 --- a/crates/psd/src/engine_data.rs +++ b/crates/psd/src/engine_data.rs @@ -2,6 +2,9 @@ //! tree of dictionaries (`<< /Key value >>`), arrays (`[ ]`), numbers, names, booleans and //! strings (`(þÿ` + UTF-16BE with backslash escapes `)`). Read best-effort. +/// Bytes that end a bare token or a `/Name` (besides whitespace). +const DELIMITERS: &[u8] = b"[]<>/("; + #[derive(Clone, Debug, PartialEq)] pub enum EValue { Dict(Vec<(String, EValue)>), @@ -121,7 +124,7 @@ impl P<'_> { b'(' => Some(EValue::Str(self.string())), _ => { let start = self.i; - while self.i < self.b.len() && !self.b[self.i].is_ascii_whitespace() && !b"[]<>/(".contains(&self.b[self.i]) { + while self.i < self.b.len() && !self.b[self.i].is_ascii_whitespace() && !DELIMITERS.contains(&self.b[self.i]) { self.i += 1; } let tok = std::str::from_utf8(&self.b[start..self.i]).unwrap_or(""); @@ -140,7 +143,7 @@ impl P<'_> { fn name(&mut self) -> String { self.i += 1; // '/' let start = self.i; - while self.i < self.b.len() && !self.b[self.i].is_ascii_whitespace() && !b"[]<>/(".contains(&self.b[self.i]) { + while self.i < self.b.len() && !self.b[self.i].is_ascii_whitespace() && !DELIMITERS.contains(&self.b[self.i]) { self.i += 1; } String::from_utf8_lossy(&self.b[start..self.i]).into_owned() diff --git a/crates/psd/src/lib.rs b/crates/psd/src/lib.rs index 1c0fd3d4..3375f269 100644 --- a/crates/psd/src/lib.rs +++ b/crates/psd/src/lib.rs @@ -665,27 +665,12 @@ impl Psd { let _dver = r.u32()?; let d = descriptor::read_descriptor(&mut r)?; let uuid = d.text("Idnt").unwrap_or_default().to_string(); - let nums = |k: &str| -> Option> { - match d.get(k)? { - DValue::List(l) => Some( - l.iter() - .filter_map(|v| match v { - DValue::Double(x) | DValue::UnitFloat(_, x) => Some(*x), - DValue::Integer(i) => Some(*i as f64), - _ => None, - }) - .collect(), - ), - DValue::UnitFloats(_, v) => Some(v.clone()), - _ => None, - } - }; - let t = nums("Trnf").filter(|t| t.len() == 8).unwrap_or_else(|| vec![0.0; 8]); + let t = placed::eight(&d, "Trnf").unwrap_or([0.0; 8]); // The non-affine transform when present (perspective), else the transform. - let t = nums("nonAffineTransform").filter(|t| t.len() == 8).unwrap_or(t); + let t = placed::eight(&d, "nonAffineTransform").unwrap_or(t); let size = d.obj("Sz ").and_then(|s| Some([s.num("Wdth")?, s.num("Hght")?])); let warp = d.obj("warp").and_then(placed::Warp::read); - l.smart_object = Some(SmartObject { uuid, quad: [[t[0], t[1]], [t[2], t[3]], [t[4], t[5]], [t[6], t[7]]], size, warp }); + l.smart_object = Some(SmartObject { uuid, quad: placed::quad_from(t), size, warp }); } b"PlLd" if l.smart_object.is_none() => { let _id = r.tag()?; @@ -700,7 +685,7 @@ impl Psd { for v in &mut t { *v = r.f64()?; } - l.smart_object = Some(SmartObject { uuid, quad: [[t[0], t[1]], [t[2], t[3]], [t[4], t[5]], [t[6], t[7]]], size: None, warp: None }); + l.smart_object = Some(SmartObject { uuid, quad: placed::quad_from(t), size: None, warp: None }); } b"vmsk" | b"vsms" => { let _ver = r.u32()?; @@ -794,32 +779,60 @@ impl Psd { let mut r = Reader::at(&self.data[..end], offset); let comp = r.u16()?; decode_pixels(w, h)?; + let mut planes = self.open_planes(comp, &mut r, 1, 1, w, h)?; + if let PlaneStream::Zip(v) = &mut planes { + v.resize(self.row_bytes(w) * h, 0); + } + let raw = self.decode_plane(comp, &planes, &mut r, 0, w, h)?; + Ok(self.samples(&raw, w, h)) + } + + /// Prepare the pixel data at `r` (just after the compression field) of `total` planes of + /// `w`×`h` samples, of which the first `want` are decoded: the RLE row sizes of every plane, + /// or the inflated ZIP data, which holds all planes one after another. + fn open_planes(&self, comp: u16, r: &mut Reader, total: usize, want: usize, w: usize, h: usize) -> Result { let rowb = self.row_bytes(w); - let raw = match comp { - 0 => r.bytes(rowb * h)?.to_vec(), + Ok(match comp { + 0 => PlaneStream::Raw, 1 => { - let mut counts = Vec::with_capacity(h.min(r.remaining() / 2)); - for _ in 0..h { + let n = total.saturating_mul(h); + let mut counts = Vec::with_capacity(n.min(r.remaining() / 2)); + for _ in 0..n { counts.push(if self.psb { r.u32()? as usize } else { r.u16()? as usize }); } - let mut out = Vec::with_capacity(rowb * h); - for c in counts { - unpack_bits(r.bytes(c)?, rowb, &mut out)?; - } - out + PlaneStream::Rle(counts) } 2 | 3 => { let rest = r.bytes(r.remaining())?; - let mut v = miniz_oxide::inflate::decompress_to_vec_zlib_with_limit(rest, rowb * h).map_err(|e| Error::Invalid(format!("zip: {e:?}")))?; - v.resize(rowb * h, 0); - if comp == 3 { - self.unpredict(&mut v, w, h); - } - v + let v = miniz_oxide::inflate::decompress_to_vec_zlib_with_limit(rest, want * rowb * h).map_err(|e| Error::Invalid(format!("zip: {e:?}")))?; + PlaneStream::Zip(v) } c => return Err(Error::Unsupported(format!("compression {c}"))), - }; - Ok(self.samples(&raw, w, h)) + }) + } + + /// The raw bytes (`row_bytes(w)` × `h`, ZIP prediction undone) of plane `c`; raw and RLE + /// planes are read from `r` in order. + fn decode_plane(&self, comp: u16, planes: &PlaneStream, r: &mut Reader, c: usize, w: usize, h: usize) -> Result> { + let rowb = self.row_bytes(w); + Ok(match planes { + PlaneStream::Raw => r.bytes(rowb * h)?.to_vec(), + PlaneStream::Rle(counts) => { + let mut out = Vec::with_capacity(rowb * h); + for row in 0..h { + let n = *counts.get(c * h + row).ok_or(Error::Truncated)?; + unpack_bits(r.bytes(n)?, rowb, &mut out)?; + } + out + } + PlaneStream::Zip(v) => { + let mut plane = v.get(c * rowb * h..(c + 1) * rowb * h).ok_or(Error::Truncated)?.to_vec(); + if comp == 3 { + self.unpredict(&mut plane, w, h); + } + plane + } + }) } fn row_bytes(&self, w: usize) -> usize { @@ -911,39 +924,10 @@ impl Psd { let nch = self.channels as usize; let cc = self.mode.color_channels(); let want = (cc + usize::from(self.merged_alpha)).min(nch); - let rowb = self.row_bytes(w); let mut planes: Vec>> = vec![None; nch]; - match comp { - 0 => { - for p in planes.iter_mut().take(want) { - *p = Some(self.samples(r.bytes(rowb * h)?, w, h)); - } - } - 1 => { - let mut counts = Vec::with_capacity((nch * h).min(r.remaining() / 2)); - for _ in 0..nch * h { - counts.push(if self.psb { r.u32()? as usize } else { r.u16()? as usize }); - } - for (c, p) in planes.iter_mut().enumerate().take(want) { - let mut out = Vec::with_capacity(rowb * h); - for row in 0..h { - unpack_bits(r.bytes(counts[c * h + row])?, rowb, &mut out)?; - } - *p = Some(self.samples(&out, w, h)); - } - } - 2 | 3 => { - let rest = r.bytes(r.remaining())?; - let v = miniz_oxide::inflate::decompress_to_vec_zlib_with_limit(rest, want * rowb * h).map_err(|e| Error::Invalid(format!("zip: {e:?}")))?; - for (c, p) in planes.iter_mut().enumerate().take(want) { - let mut plane = v.get(c * rowb * h..(c + 1) * rowb * h).ok_or(Error::Truncated)?.to_vec(); - if comp == 3 { - self.unpredict(&mut plane, w, h); - } - *p = Some(self.samples(&plane, w, h)); - } - } - c => return Err(Error::Unsupported(format!("compression {c}"))), + let stream = self.open_planes(comp, &mut r, nch, want, w, h)?; + for (c, p) in planes.iter_mut().enumerate().take(want) { + *p = Some(self.samples(&self.decode_plane(comp, &stream, &mut r, c, w, h)?, w, h)); } let alpha = if self.merged_alpha && nch > cc { planes[cc].take() } else { None }; let mut data = Vec::with_capacity(w * h); @@ -1039,6 +1023,15 @@ impl Psd { } } +/// The pixel data of a run of planes, as prepared by `Psd::open_planes`. +enum PlaneStream { + Raw, + /// RLE row byte counts of every plane, plane after plane. + Rle(Vec), + /// The inflated ZIP data of all planes. + Zip(Vec), +} + /// The next additional-information block at `*pos` (signature `8BIM`/`8B64`, key, length): /// returns (key, data start, data end). Skips up to 3 padding bytes before a signature. fn next_block(data: &[u8], pos: &mut usize, end: usize, psb: bool) -> Option<([u8; 4], usize, usize)> { diff --git a/crates/psd/src/placed.rs b/crates/psd/src/placed.rs index 156a8e27..af37b63b 100644 --- a/crates/psd/src/placed.rs +++ b/crates/psd/src/placed.rs @@ -35,7 +35,7 @@ pub struct Warp { pub mesh: Vec<[f64; 2]>, } -fn floats(v: Option<&DValue>) -> Vec { +pub(crate) fn floats(v: Option<&DValue>) -> Vec { match v { Some(DValue::UnitFloats(_, v)) => v.clone(), Some(DValue::List(l)) => l @@ -50,6 +50,16 @@ fn floats(v: Option<&DValue>) -> Vec { } } +/// The eight numbers of a transform entry (`Trnf`, `nonAffineTransform`), when it has exactly eight. +pub(crate) fn eight(d: &Descriptor, key: &str) -> Option<[f64; 8]> { + floats(d.get(key)).try_into().ok() +} + +/// The corner quad (top-left, top-right, bottom-right, bottom-left) from x0 y0 … x3 y3. +pub(crate) fn quad_from(t: [f64; 8]) -> [[f64; 2]; 4] { + [[t[0], t[1]], [t[2], t[3]], [t[4], t[5]], [t[6], t[7]]] +} + impl Warp { /// Read a `warp` descriptor; `None` for no warp. pub fn read(d: &Descriptor) -> Option { diff --git a/crates/psd/src/tests.rs b/crates/psd/src/tests.rs index 235163b6..5871c1a1 100644 --- a/crates/psd/src/tests.rs +++ b/crates/psd/src/tests.rs @@ -252,3 +252,86 @@ fn huge_layer_bounds_error_instead_of_allocating() { let p = Psd::parse(bytes).unwrap(); assert!(matches!(p.layer_pixels(0, false), Err(Error::Unsupported(_)))); } + +/// A ZIP plane body for the decoder: the plane delta-coded per row when `predict`. +fn zip_body(raw: &[u8], w: usize, depth: u16, predict: bool) -> Vec { + if !predict { + return raw.to_vec(); + } + let mut out = raw.to_vec(); + let bps = usize::from(depth / 8); + for row in out.chunks_exact_mut(w * bps) { + if depth == 16 { + let mut prev = 0u16; + for c in row.as_chunks_mut::<2>().0 { + let x = u16::from_be_bytes([c[0], c[1]]); + c.copy_from_slice(&x.wrapping_sub(prev).to_be_bytes()); + prev = x; + } + } else { + for i in (1..row.len()).rev() { + row[i] = row[i].wrapping_sub(row[i - 1]); + } + } + } + out +} + +/// The same document with every channel and the merged image re-encoded as ZIP (`comp` 2) or +/// ZIP with prediction (3), appended to the file data. +fn rezip(p: &Psd, comp: u16) -> Psd { + let mut z = p.clone(); + let mut data = p.data.to_vec(); + let (depth, predict) = (p.depth, comp == 3); + let deflate = |body: &[u8]| { + let mut v = comp.to_be_bytes().to_vec(); + v.extend(miniz_oxide::deflate::compress_to_vec_zlib(body, 6)); + v + }; + for (l, zl) in p.layers.iter().zip(&mut z.layers) { + for (c, zc) in l.channels.iter().zip(&mut zl.channels) { + let w = if c.id == -2 { l.mask.map_or(0, |m| m.rect.width() as usize) } else { l.rect.width() as usize }; + let raw = &p.data[c.offset + 2..c.offset + c.len]; + zc.offset = data.len(); + data.extend(deflate(&zip_body(raw, w, depth, predict))); + zc.len = data.len() - zc.offset; + } + } + // The merged image: one ZIP stream holding all planes. + let (w, h) = (p.width as usize, p.height as usize); + let plane = w * h * usize::from(depth / 8); + let mut all = vec![]; + for c in 0..usize::from(p.channels) { + let at = p.merged_offset + 2 + c * plane; + all.extend(zip_body(&p.data[at..at + plane], w, depth, predict)); + } + z.merged_offset = data.len(); + data.extend(deflate(&all)); + z.data = data.into(); + z +} + +#[test] +fn zip_channels_decode_like_raw_ones() { + for depth in [8, 16] { + let raw = Psd::parse(write(&doc_with_layers(depth, ColorMode::Rgb, false))).unwrap(); + for comp in [2, 3] { + let zip = rezip(&raw, comp); + assert_eq!(zip.composite().unwrap().data, raw.composite().unwrap().data, "composite {depth} bit, compression {comp}"); + for (i, canvas) in [(0, true), (0, false), (1, false), (2, false), (2, true)] { + let (want, got) = (raw.layer_pixels(i, canvas).unwrap(), zip.layer_pixels(i, canvas).unwrap()); + assert_eq!(got.data, want.data, "layer {i} canvas {canvas}, {depth} bit, compression {comp}"); + } + } + } +} + +#[test] +fn rle_composite_matches_the_raw_one() { + for depth in [8, 16] { + let raw = Psd::parse(write(&doc_with_layers(depth, ColorMode::Rgb, false))).unwrap(); + let rle = Psd::parse(write(&doc_with_layers(depth, ColorMode::Rgb, true))).unwrap(); + assert_eq!(rle.composite().unwrap().data, raw.composite().unwrap().data, "{depth} bit"); + assert_eq!(rle.layer_pixels(2, true).unwrap().data, raw.layer_pixels(2, true).unwrap().data, "{depth} bit"); + } +} diff --git a/crates/psd/src/write.rs b/crates/psd/src/write.rs index cb3b2bf0..a92e76dc 100644 --- a/crates/psd/src/write.rs +++ b/crates/psd/src/write.rs @@ -3,7 +3,7 @@ //! additional-information blocks (helpers below build text, effects, fill and adjustment //! blocks). Used to generate test fixtures; not a full exporter. -use crate::descriptor::{self, DValue, Descriptor}; +use crate::descriptor::{self, DValue, Descriptor, put_u16, put_u32, put_utf16}; use crate::{ColorMode, Rect, Section, engine_data, pack_bits}; /// A layer mask for [`WLayer`]. @@ -114,13 +114,6 @@ impl WDoc { } } -fn u16b(out: &mut Vec, v: u16) { - out.extend_from_slice(&v.to_be_bytes()); -} -fn u32b(out: &mut Vec, v: u32) { - out.extend_from_slice(&v.to_be_bytes()); -} - /// Colour channels for a straight RGB pixel in the document mode. fn mode_channels(mode: ColorMode, p: [f32; 4]) -> Vec { match mode { @@ -139,7 +132,7 @@ fn encode_samples(v: &[f32], depth: u16) -> Vec { for &x in v { let x = x.clamp(0.0, 1.0); if depth == 16 { - u16b(&mut out, (x * 65535.0).round() as u16); + put_u16(&mut out, (x * 65535.0).round() as u16); } else { out.push((x * 255.0).round() as u8); } @@ -153,14 +146,14 @@ fn channel_data(v: &[f32], w: usize, h: usize, depth: u16, rle: bool) -> Vec let mut out = vec![]; let rowb = raw.len() / h.max(1); if !rle || w == 0 || h == 0 { - u16b(&mut out, 0); + put_u16(&mut out, 0); out.extend_from_slice(&raw); return out; } - u16b(&mut out, 1); + put_u16(&mut out, 1); let rows: Vec> = raw.chunks(rowb.max(1)).map(pack_bits).collect(); for r in &rows { - u16b(&mut out, r.len() as u16); + put_u16(&mut out, r.len() as u16); } for r in rows { out.extend_from_slice(&r); @@ -180,7 +173,7 @@ fn block(out: &mut Vec, key: &[u8; 4], data: &[u8]) { out.extend_from_slice(b"8BIM"); out.extend_from_slice(key); let padded = data.len().div_ceil(2) * 2; - u32b(out, padded as u32); + put_u32(out, padded as u32); out.extend_from_slice(data); out.resize(out.len() + padded - data.len(), 0); } @@ -208,10 +201,10 @@ fn layer_info(doc: &WDoc) -> Vec { for v in [l.rect.top, l.rect.left, l.rect.bottom, l.rect.right] { out.extend_from_slice(&v.to_be_bytes()); } - u16b(&mut out, chans.len() as u16); + put_u16(&mut out, chans.len() as u16); for (id, d) in &chans { out.extend_from_slice(&id.to_be_bytes()); - u32b(&mut out, d.len() as u32); + put_u32(&mut out, d.len() as u32); } out.extend_from_slice(b"8BIM"); out.extend_from_slice(&l.blend); @@ -226,7 +219,7 @@ fn layer_info(doc: &WDoc) -> Vec { let mut extra = vec![]; match &l.mask { Some(m) => { - u32b(&mut extra, 20); + put_u32(&mut extra, 20); for v in [m.rect.top, m.rect.left, m.rect.bottom, m.rect.right] { extra.extend_from_slice(&v.to_be_bytes()); } @@ -234,9 +227,9 @@ fn layer_info(doc: &WDoc) -> Vec { extra.push(if m.disabled { 2 } else { 0 }); extra.extend_from_slice(&[0, 0]); } - None => u32b(&mut extra, 0), + None => put_u32(&mut extra, 0), } - u32b(&mut extra, 0); // blending ranges + put_u32(&mut extra, 0); // blending ranges pascal4(&mut extra, &l.name); let luni = unicode_plain(&l.name); block(&mut extra, b"luni", &luni); @@ -244,7 +237,7 @@ fn layer_info(doc: &WDoc) -> Vec { Section::Layer => {} s => { let mut d = vec![]; - u32b( + put_u32( &mut d, match s { Section::OpenFolder => 1, @@ -260,7 +253,7 @@ fn layer_info(doc: &WDoc) -> Vec { for (k, d) in &l.blocks { block(&mut extra, k, d); } - u32b(&mut out, extra.len() as u32); + put_u32(&mut out, extra.len() as u32); out.extend_from_slice(&extra); chan_data.push(chans); } @@ -275,13 +268,10 @@ fn layer_info(doc: &WDoc) -> Vec { out } +/// A Unicode string without the terminating NUL (the layer-name `luni` form). fn unicode_plain(s: &str) -> Vec { - let v: Vec = s.encode_utf16().collect(); let mut out = vec![]; - u32b(&mut out, v.len() as u32); - for c in v { - u16b(&mut out, c); - } + put_utf16(&mut out, s, false); out } @@ -313,58 +303,58 @@ pub fn write(doc: &WDoc) -> Vec { let cc = doc.mode.color_channels(); let mut out = vec![]; out.extend_from_slice(b"8BPS"); - u16b(&mut out, 1); + put_u16(&mut out, 1); out.extend_from_slice(&[0; 6]); - u16b(&mut out, (cc + 1) as u16); - u32b(&mut out, doc.height); - u32b(&mut out, doc.width); - u16b(&mut out, doc.depth); - u16b(&mut out, doc.mode.to_u16()); - u32b(&mut out, 0); // colour mode data - u32b(&mut out, 0); // image resources + put_u16(&mut out, (cc + 1) as u16); + put_u32(&mut out, doc.height); + put_u32(&mut out, doc.width); + put_u16(&mut out, doc.depth); + put_u16(&mut out, doc.mode.to_u16()); + put_u32(&mut out, 0); // colour mode data + put_u32(&mut out, 0); // image resources // Layer and mask information. let mut lm = vec![]; if !doc.layers.is_empty() { let li = layer_info(doc); if doc.depth == 16 { - u32b(&mut lm, 0); - u32b(&mut lm, 0); // global layer mask info + put_u32(&mut lm, 0); + put_u32(&mut lm, 0); // global layer mask info let mut padded = li; while !padded.len().is_multiple_of(4) { padded.push(0); } lm.extend_from_slice(b"8BIM"); lm.extend_from_slice(b"Lr16"); - u32b(&mut lm, padded.len() as u32); + put_u32(&mut lm, padded.len() as u32); lm.extend_from_slice(&padded); } else { - u32b(&mut lm, li.len() as u32); + put_u32(&mut lm, li.len() as u32); lm.extend_from_slice(&li); - u32b(&mut lm, 0); + put_u32(&mut lm, 0); } if !doc.linked.is_empty() { let d = linked_block(&doc.linked); lm.extend_from_slice(b"8BIM"); lm.extend_from_slice(b"lnk2"); - u32b(&mut lm, d.len() as u32); + put_u32(&mut lm, d.len() as u32); lm.extend_from_slice(&d); } } - u32b(&mut out, lm.len() as u32); + put_u32(&mut out, lm.len() as u32); out.extend_from_slice(&lm); // Merged image. let comp = doc.composite.clone().unwrap_or_else(|| flatten(doc)); - let (w, h) = (doc.width as usize, doc.height as usize); + let w = doc.width as usize; let planes: Vec> = comp.iter().map(|p| mode_channels(doc.mode, *p)).collect(); let mut chans: Vec> = (0..cc).map(|c| planes.iter().map(|v| v[c]).collect()).collect(); chans.push(comp.iter().map(|p| p[3]).collect()); if doc.rle { - u16b(&mut out, 1); + put_u16(&mut out, 1); let rows: Vec>> = chans.iter().map(|c| encode_samples(c, doc.depth).chunks((w * if doc.depth == 16 { 2 } else { 1 }).max(1)).map(pack_bits).collect()).collect(); for c in &rows { for r in c { - u16b(&mut out, r.len() as u16); + put_u16(&mut out, r.len() as u16); } } for c in rows { @@ -373,12 +363,11 @@ pub fn write(doc: &WDoc) -> Vec { } } } else { - u16b(&mut out, 0); + put_u16(&mut out, 0); for c in &chans { out.extend_from_slice(&encode_samples(c, doc.depth)); } } - let _ = h; out } @@ -388,7 +377,7 @@ fn linked_block(files: &[(String, String, [u8; 4], Vec)]) -> Vec { for (uuid, name, ty, data) in files { let mut rec = vec![]; rec.extend_from_slice(b"liFD"); - u32b(&mut rec, 7); + put_u32(&mut rec, 7); rec.push(uuid.len() as u8); rec.extend_from_slice(uuid.as_bytes()); rec.extend_from_slice(&unicode_plain(name)); @@ -397,7 +386,7 @@ fn linked_block(files: &[(String, String, [u8; 4], Vec)]) -> Vec { rec.extend_from_slice(&(data.len() as u64).to_be_bytes()); rec.push(0); // no file-open descriptor rec.extend_from_slice(data); - u32b(&mut rec, 0); // child document id (empty unicode string) + put_u32(&mut rec, 0); // child document id (empty unicode string) rec.extend_from_slice(&0f64.to_be_bytes()); // asset modification time rec.push(0); // asset locked state out.extend_from_slice(&(rec.len() as u64).to_be_bytes()); @@ -462,8 +451,8 @@ pub fn smart_object_block_warped(uuid: &str, quad: [[f64; 2]; 4], size: [f64; 2] d = d.with("warp", DValue::Descriptor(w)); } let mut out = b"soLD".to_vec(); - u32b(&mut out, 4); - u32b(&mut out, 16); + put_u32(&mut out, 4); + put_u32(&mut out, 16); descriptor::write_descriptor(&mut out, &d); (*b"SoLd", out) } @@ -471,12 +460,12 @@ pub fn smart_object_block_warped(uuid: &str, quad: [[f64; 2]; 4], size: [f64; 2] /// `TySh`: a type layer with one style run; the text origin sits at `origin`. pub fn text_block(text: &str, origin: [f64; 2], font: &str, size: f64, color: [f64; 3], justification: u8) -> ([u8; 4], Vec) { let mut d = vec![]; - u16b(&mut d, 1); + put_u16(&mut d, 1); for v in [1.0, 0.0, 0.0, 1.0, origin[0], origin[1]] { d.extend_from_slice(&f64::to_be_bytes(v)); } - u16b(&mut d, 50); - u32b(&mut d, 16); + put_u16(&mut d, 50); + put_u32(&mut d, 16); let desc = Descriptor::new("TxLr") .with("Txt ", DValue::Text(text.replace('\n', "\r"))) .with("textGridding", DValue::Enum("textGridding".into(), "None".into())) @@ -484,11 +473,11 @@ pub fn text_block(text: &str, origin: [f64; 2], font: &str, size: f64, color: [f .with("EngineData", DValue::Raw(engine_data::minimal(text, font, size, color, justification))); descriptor::write_descriptor(&mut d, &desc); // Warp: version, descriptor version, an empty warp descriptor. - u16b(&mut d, 1); - u32b(&mut d, 16); + put_u16(&mut d, 1); + put_u32(&mut d, 16); descriptor::write_descriptor(&mut d, &Descriptor::new("warp").with("warpStyle", DValue::Enum("warpStyle".into(), "warpNone".into()))); for _ in 0..4 { - u32b(&mut d, 0); + put_u32(&mut d, 0); } (*b"TySh", d) } @@ -496,8 +485,8 @@ pub fn text_block(text: &str, origin: [f64; 2], font: &str, size: f64, color: [f /// `lfx2`: layer effects from an effects descriptor (`null` class with `DrSh`, `FrFX`…). pub fn effects_block(desc: &Descriptor) -> ([u8; 4], Vec) { let mut d = vec![]; - u32b(&mut d, 0); - u32b(&mut d, 16); + put_u32(&mut d, 0); + put_u32(&mut d, 16); descriptor::write_descriptor(&mut d, desc); (*b"lfx2", d) } @@ -505,7 +494,7 @@ pub fn effects_block(desc: &Descriptor) -> ([u8; 4], Vec) { /// `SoCo`: solid colour fill. pub fn solid_color_block(rgb: [f64; 3]) -> ([u8; 4], Vec) { let mut d = vec![]; - u32b(&mut d, 16); + put_u32(&mut d, 16); descriptor::write_descriptor(&mut d, &Descriptor::new("null").with("Clr ", DValue::Descriptor(Descriptor::rgb(rgb)))); (*b"SoCo", d) } @@ -513,7 +502,7 @@ pub fn solid_color_block(rgb: [f64; 3]) -> ([u8; 4], Vec) { /// `hue2`: Hue/Saturation (master). pub fn hue_sat_block(hue: i16, sat: i16, light: i16) -> ([u8; 4], Vec) { let mut d = vec![]; - u16b(&mut d, 2); + put_u16(&mut d, 2); d.extend_from_slice(&[0, 0]); for v in [0i16, 0, 0, hue, sat, light] { d.extend_from_slice(&v.to_be_bytes()); @@ -526,7 +515,7 @@ pub fn hue_sat_block(hue: i16, sat: i16, light: i16) -> ([u8; 4], Vec) { /// `levl`: Levels (composite record). pub fn levels_block(in_black: i16, in_white: i16, out_black: i16, out_white: i16, gamma: f64) -> ([u8; 4], Vec) { let mut d = vec![]; - u16b(&mut d, 2); + put_u16(&mut d, 2); for v in [in_black, in_white, out_black, out_white, (gamma * 100.0).round() as i16] { d.extend_from_slice(&v.to_be_bytes()); } @@ -553,7 +542,7 @@ pub fn brightness_block(brightness: i16, contrast: i16) -> ([u8; 4], Vec) { pub fn exposure_block(exposure: f32, offset: f32, gamma: f32) -> ([u8; 4], Vec) { let mut d = vec![]; - u16b(&mut d, 1); + put_u16(&mut d, 1); for v in [exposure, offset, gamma] { d.extend_from_slice(&v.to_be_bytes()); } @@ -563,18 +552,18 @@ pub fn exposure_block(exposure: f32, offset: f32, gamma: f32) -> ([u8; 4], Vec], width: u32, height: u32, inverted: bool) -> ([u8; 4], Vec) { let mut d = vec![]; - u32b(&mut d, 3); - u32b(&mut d, inverted as u32); + put_u32(&mut d, 3); + put_u32(&mut d, inverted as u32); // Path fill rule record. - u16b(&mut d, 6); + put_u16(&mut d, 6); d.resize(d.len() + 24, 0); let fx = |v: f64| ((v * (1 << 24) as f64).round() as i32).to_be_bytes(); for sp in subpaths { - u16b(&mut d, 0); - u16b(&mut d, sp.len() as u16); + put_u16(&mut d, 0); + put_u16(&mut d, sp.len() as u16); d.resize(d.len() + 22, 0); for k in sp { - u16b(&mut d, 1); + put_u16(&mut d, 1); for p in k { d.extend_from_slice(&fx(p[1] / height as f64)); d.extend_from_slice(&fx(p[0] / width as f64));