Skip to main content

ag_psd/
writer.rs

1/*
2File: crates/ag-psd/src/writer.rs
3
4Purpose:
5низкоуровневая запись байтов в буфер PSD (структуры/курсор записи, примитивы записи чисел и строк).
6
7Source compatibility:
8- порт upstream-файла `test/ag-psd/src/psdWriter.ts` (разбиение 1:1).
9
10Main responsibilities:
11- зеркалировать соответствующий upstream-модуль при портировании;
12- держать публичный контракт этого участка в одном месте.
13*/
14
15// PORT STATUS: primitives ported; document orchestration ported.
16
17use crate::additional_info::{write_additional_info, WriteCtx};
18use crate::helpers::{
19    clamp, from_blend_mode, has_alpha, offset_for_channel, write_data_rle,
20    write_data_zip_without_prediction, Bounds as ChannelBounds, ChannelData, ColorSpace,
21    LayerChannelData, LayerMaskFlags, MaskParams, RAW_IMAGE_DATA,
22};
23use crate::image_resources::{has_image_resource, write_image_resource, RESOURCE_IDS};
24use crate::psd::{
25    BlendMode, ChannelId, Color, ColorMode, Compression, GlobalLayerMaskInfo, Layer,
26    LayerAdditionalInfo, LayerMaskData, PatternInfo, PixelData, Psd, SectionDividerType,
27    WriteOptions,
28};
29
30/// Порт TS-интерфейса `PsdWriter`.
31///
32/// В upstream-е используется заранее аллоцированный `ArrayBuffer` фиксированного
33/// размера + `DataView` + курсор `offset`, буфер растёт удвоением (`resizeBuffer`).
34/// Здесь `buffer: Vec<u8>` хранит «вместимость» (capacity), заполненную нулями,
35/// а реально записано `offset` байт. Это байт-в-байт повторяет upstream:
36/// `getWriterBuffer` отдаёт срез `[0, offset)`, а `ensureSize`/`resizeBuffer`
37/// воспроизводят логику удвоения. `tempBuffer` относится к оркестрации документа
38/// (буфер для RLE) и здесь не используется примитивами, но поле сохранено для
39/// зеркальности.
40#[derive(Debug, Clone)]
41pub struct PsdWriter {
42    /// Аналог `ArrayBuffer` фиксированной вместимости (заполнен нулями до конца).
43    pub buffer: Vec<u8>,
44    /// Курсор записи (число реально записанных байт).
45    pub offset: usize,
46    /// Временный буфер для RLE-сжатия (используется оркестрацией документа).
47    pub temp_buffer: Option<Vec<u8>>,
48}
49
50/// Порт `createWriter(size = 4096)`.
51pub fn create_writer(size: usize) -> PsdWriter {
52    PsdWriter {
53        buffer: vec![0u8; size],
54        offset: 0,
55        temp_buffer: None,
56    }
57}
58
59/// Порт `createWriter()` с дефолтным размером 4096.
60pub fn create_writer_default() -> PsdWriter {
61    create_writer(4096)
62}
63
64/// Порт `getWriterBuffer(writer)` — `buffer.slice(0, offset)` (копия).
65pub fn get_writer_buffer(writer: &PsdWriter) -> Vec<u8> {
66    writer.buffer[..writer.offset].to_vec()
67}
68
69/// Порт `getWriterBufferNoCopy(writer)` — `Uint8Array(buffer, 0, offset)` (без копии).
70pub fn get_writer_buffer_no_copy(writer: &PsdWriter) -> &[u8] {
71    &writer.buffer[..writer.offset]
72}
73
74// ===========================================================================
75// Buffer growth (resizeBuffer / ensureSize / addSize)
76// ===========================================================================
77
78/// Порт `resizeBuffer(writer, size)`.
79fn resize_buffer(writer: &mut PsdWriter, size: usize) {
80    let mut new_length = writer.buffer.len();
81
82    // do { newLength *= 2; } while (size > newLength);
83    loop {
84        new_length *= 2;
85        if size <= new_length {
86            break;
87        }
88    }
89
90    writer.buffer.resize(new_length, 0);
91}
92
93/// Порт `ensureSize(writer, size)`.
94fn ensure_size(writer: &mut PsdWriter, size: usize) {
95    if size > writer.buffer.len() {
96        resize_buffer(writer, size);
97    }
98}
99
100/// Порт `addSize(writer, size)` — возвращает прежний `offset`, продвигает курсор.
101fn add_size(writer: &mut PsdWriter, size: usize) -> usize {
102    let offset = writer.offset;
103    writer.offset += size;
104    ensure_size(writer, writer.offset);
105    offset
106}
107
108// ===========================================================================
109// Big-endian / little-endian helpers (mirror DataView.setXxx)
110//
111// Endianness: в upstream все `view.setInt16/Uint16/Int32/Uint32/Float32/Float64`
112// вызываются с `littleEndian = false`, т.е. PSD — big-endian. Исключения — явные
113// `*LE`-варианты (`setUint16(..., true)`, `setInt32(..., true)`).
114// ===========================================================================
115
116#[inline]
117fn set_bytes_be(writer: &mut PsdWriter, offset: usize, bytes: &[u8]) {
118    writer.buffer[offset..offset + bytes.len()].copy_from_slice(bytes);
119}
120
121// ===========================================================================
122// Scalar writers
123// ===========================================================================
124
125/// Порт `writeUint8`.
126pub fn write_uint8(writer: &mut PsdWriter, value: u8) {
127    let offset = add_size(writer, 1);
128    writer.buffer[offset] = value;
129}
130
131/// Порт `writeInt16` (big-endian).
132pub fn write_int16(writer: &mut PsdWriter, value: i16) {
133    let offset = add_size(writer, 2);
134    set_bytes_be(writer, offset, &value.to_be_bytes());
135}
136
137/// Порт `writeUint16` (big-endian).
138pub fn write_uint16(writer: &mut PsdWriter, value: u16) {
139    let offset = add_size(writer, 2);
140    set_bytes_be(writer, offset, &value.to_be_bytes());
141}
142
143/// Порт `writeUint16LE` (little-endian).
144pub fn write_uint16_le(writer: &mut PsdWriter, value: u16) {
145    let offset = add_size(writer, 2);
146    set_bytes_be(writer, offset, &value.to_le_bytes());
147}
148
149/// Порт `writeInt32` (big-endian).
150pub fn write_int32(writer: &mut PsdWriter, value: i32) {
151    let offset = add_size(writer, 4);
152    set_bytes_be(writer, offset, &value.to_be_bytes());
153}
154
155/// Порт `writeInt32LE` (little-endian).
156pub fn write_int32_le(writer: &mut PsdWriter, value: i32) {
157    let offset = add_size(writer, 4);
158    set_bytes_be(writer, offset, &value.to_le_bytes());
159}
160
161/// Порт `writeUint32` (big-endian).
162pub fn write_uint32(writer: &mut PsdWriter, value: u32) {
163    let offset = add_size(writer, 4);
164    set_bytes_be(writer, offset, &value.to_be_bytes());
165}
166
167/// Порт `writeFloat32` (big-endian).
168pub fn write_float32(writer: &mut PsdWriter, value: f32) {
169    let offset = add_size(writer, 4);
170    set_bytes_be(writer, offset, &value.to_be_bytes());
171}
172
173/// Порт `writeFloat64` (big-endian).
174pub fn write_float64(writer: &mut PsdWriter, value: f64) {
175    let offset = add_size(writer, 8);
176    set_bytes_be(writer, offset, &value.to_be_bytes());
177}
178
179/// Порт `writeFixedPoint32` — 32-битное число с фиксированной точкой 16.16.
180pub fn write_fixed_point32(writer: &mut PsdWriter, value: f64) {
181    write_int32(writer, (value * (1i64 << 16) as f64) as i32);
182}
183
184/// Порт `writeFixedPointPath32` — 32-битное число с фиксированной точкой 8.24.
185pub fn write_fixed_point_path32(writer: &mut PsdWriter, value: f64) {
186    write_int32(writer, (value * (1i64 << 24) as f64) as i32);
187}
188
189/// Порт `writeBytes`. В Rust пустой срез эквивалентен `undefined`/пустому буферу.
190pub fn write_bytes(writer: &mut PsdWriter, buffer: Option<&[u8]>) {
191    if let Some(buffer) = buffer {
192        ensure_size(writer, writer.offset + buffer.len());
193        let offset = writer.offset;
194        writer.buffer[offset..offset + buffer.len()].copy_from_slice(buffer);
195        writer.offset += buffer.len();
196    }
197}
198
199/// Порт `writeZeros(writer, count)`.
200pub fn write_zeros(writer: &mut PsdWriter, count: usize) {
201    for _ in 0..count {
202        write_uint8(writer, 0);
203    }
204}
205
206/// Порт `writeSignature(writer, signature)` — ровно 4 ASCII-символа.
207pub fn write_signature(writer: &mut PsdWriter, signature: &str) {
208    if signature.len() != 4 {
209        panic!("Invalid signature: '{}'", signature);
210    }
211
212    for b in signature.bytes() {
213        write_uint8(writer, b);
214    }
215}
216
217/// Порт `writeAsciiString(writer, text)` — по одному char-code на байт.
218///
219/// Upstream использует `charCodeAt` (UTF-16 code unit); для не-ASCII строк
220/// результат усекается до младшего байта. Здесь зеркалируем через коды
221/// символов (`char as u32 as u8`), что совпадает для ASCII.
222pub fn write_ascii_string(writer: &mut PsdWriter, text: &str) {
223    for ch in text.chars() {
224        write_uint8(writer, (ch as u32) as u8);
225    }
226}
227
228/// Порт `writePascalString(writer, text, padTo)`.
229pub fn write_pascal_string(writer: &mut PsdWriter, text: &str, pad_to: usize) {
230    let chars: Vec<char> = text.chars().collect();
231    let mut length = chars.len();
232    if length > 255 {
233        panic!("String too long");
234    }
235
236    write_uint8(writer, length as u8);
237
238    for &ch in &chars {
239        let code = ch as u32;
240        // code < 128 ? code : '?'
241        write_uint8(writer, if code < 128 { code as u8 } else { b'?' });
242    }
243
244    // while (++length % padTo) writeUint8(0)
245    length += 1;
246    while length % pad_to != 0 {
247        write_uint8(writer, 0);
248        length += 1;
249    }
250}
251
252/// Порт `writeUnicodeStringWithoutLength` — UTF-16 BE code units, без префикса длины.
253pub fn write_unicode_string_without_length(writer: &mut PsdWriter, text: &str) {
254    for unit in text.encode_utf16() {
255        write_uint16(writer, unit);
256    }
257}
258
259/// Порт `writeUnicodeStringWithoutLengthLE` — UTF-16 LE code units, без префикса длины.
260pub fn write_unicode_string_without_length_le(writer: &mut PsdWriter, text: &str) {
261    for unit in text.encode_utf16() {
262        write_uint16_le(writer, unit);
263    }
264}
265
266/// Порт `writeUnicodeString` — префикс длины (в UTF-16 code units) + строка BE.
267pub fn write_unicode_string(writer: &mut PsdWriter, text: &str) {
268    let len = text.encode_utf16().count();
269    write_uint32(writer, len as u32);
270    write_unicode_string_without_length(writer, text);
271}
272
273/// Порт `writeUnicodeStringWithPadding` — длина+1, строка BE, завершающий 0.
274pub fn write_unicode_string_with_padding(writer: &mut PsdWriter, text: &str) {
275    let len = text.encode_utf16().count();
276    write_uint32(writer, (len + 1) as u32);
277
278    for unit in text.encode_utf16() {
279        write_uint16(writer, unit);
280    }
281
282    write_uint16(writer, 0);
283}
284
285// ===========================================================================
286// Section helper
287// ===========================================================================
288
289/// Порт `writeSection(writer, round, func, writeTotalLength = false, large = false)`.
290///
291/// Пишет длину-префикс (4 байта BE; при `large` — два 4-байтовых слова),
292/// выполняет `func`, затем добавляет паддинг до кратности `round` и
293/// бэкпатчит длину секции.
294pub fn write_section<F: FnOnce(&mut PsdWriter)>(
295    writer: &mut PsdWriter,
296    round: usize,
297    func: F,
298    write_total_length: bool,
299    large: bool,
300) {
301    if large {
302        write_uint32(writer, 0);
303    }
304    let offset = writer.offset;
305    write_uint32(writer, 0);
306
307    func(writer);
308
309    let mut length = writer.offset - offset - 4;
310    let mut len = length;
311
312    while len % round != 0 {
313        write_uint8(writer, 0);
314        len += 1;
315    }
316
317    if write_total_length {
318        length = len;
319    }
320
321    // writer.view.setUint32(offset, length, false)
322    set_bytes_be(writer, offset, &(length as u32).to_be_bytes());
323}
324
325// ===========================================================================
326// Color / Pattern generic helpers
327// ===========================================================================
328
329/// Порт `writeColor(writer, color)`.
330pub fn write_color(writer: &mut PsdWriter, color: Option<&Color>) {
331    match color {
332        None => {
333            write_uint16(writer, ColorSpace::Rgb as u16);
334            write_zeros(writer, 8);
335        }
336        Some(Color::Rgba(c)) => {
337            // 'r' in color
338            write_uint16(writer, ColorSpace::Rgb as u16);
339            write_uint16(writer, (c.r * 257.0).round() as u16);
340            write_uint16(writer, (c.g * 257.0).round() as u16);
341            write_uint16(writer, (c.b * 257.0).round() as u16);
342            write_uint16(writer, 0);
343        }
344        Some(Color::Rgb(c)) => {
345            // 'r' in color
346            write_uint16(writer, ColorSpace::Rgb as u16);
347            write_uint16(writer, (c.r * 257.0).round() as u16);
348            write_uint16(writer, (c.g * 257.0).round() as u16);
349            write_uint16(writer, (c.b * 257.0).round() as u16);
350            write_uint16(writer, 0);
351        }
352        Some(Color::Frgb(c)) => {
353            // 'fr' in color
354            write_uint16(writer, ColorSpace::Rgb as u16);
355            write_uint16(writer, (c.fr * 255.0 * 257.0).round() as u16);
356            write_uint16(writer, (c.fg * 255.0 * 257.0).round() as u16);
357            write_uint16(writer, (c.fb * 255.0 * 257.0).round() as u16);
358            write_uint16(writer, 0);
359        }
360        Some(Color::Lab(c)) => {
361            // 'l' in color
362            write_uint16(writer, ColorSpace::Lab as u16);
363            write_int16(writer, (c.l * 10000.0).round() as i16);
364            write_int16(
365                writer,
366                (if c.a < 0.0 { c.a * 12800.0 } else { c.a * 12700.0 }).round() as i16,
367            );
368            write_int16(
369                writer,
370                (if c.b < 0.0 { c.b * 12800.0 } else { c.b * 12700.0 }).round() as i16,
371            );
372            write_uint16(writer, 0);
373        }
374        Some(Color::Hsb(c)) => {
375            // 'h' in color
376            write_uint16(writer, ColorSpace::Hsb as u16);
377            write_uint16(writer, (c.h * 0xffff as f64).round() as u16);
378            write_uint16(writer, (c.s * 0xffff as f64).round() as u16);
379            write_uint16(writer, (c.b * 0xffff as f64).round() as u16);
380            write_uint16(writer, 0);
381        }
382        Some(Color::Cmyk(c)) => {
383            // 'c' in color
384            write_uint16(writer, ColorSpace::Cmyk as u16);
385            write_uint16(writer, (c.c * 257.0).round() as u16);
386            write_uint16(writer, (c.m * 257.0).round() as u16);
387            write_uint16(writer, (c.y * 257.0).round() as u16);
388            write_uint16(writer, (c.k * 257.0).round() as u16);
389        }
390        Some(Color::Grayscale(c)) => {
391            // else
392            write_uint16(writer, ColorSpace::Grayscale as u16);
393            write_uint16(writer, (c.k * 10000.0 / 255.0).round() as u16);
394            write_zeros(writer, 6);
395        }
396    }
397}
398
399/// Порт `writePattern(writer, pattern)`.
400///
401/// Оперирует только `PatternInfo` и примитивом `write_data_rle`, без знания о
402/// форме Psd/Layer, поэтому остаётся в слое примитивов.
403pub fn write_pattern(writer: &mut PsdWriter, pattern: &PatternInfo) {
404    let width = pattern.bounds.w as u32;
405    let height = pattern.bounds.h as u32;
406    let pixel_data = PixelData {
407        width,
408        height,
409        data: pattern.data.clone(),
410    };
411
412    write_uint32(writer, 0); // length, fixed up below
413    let patts_offset = writer.offset;
414
415    write_uint32(writer, 1); // version
416    write_uint32(writer, ColorMode::Rgb as u32); // color mode - rgb only
417
418    write_int16(writer, pattern.x as i16);
419    write_int16(writer, pattern.y as i16);
420
421    write_unicode_string(writer, &format!("{}\0", pattern.name)); // name
422    write_pascal_string(writer, &pattern.id, 1); // id
423
424    // virtual memory array list
425    write_uint32(writer, 3); // version
426    write_uint32(writer, 0); // length, fixed up below
427    let vl_offset = writer.offset;
428
429    let top = pattern.bounds.y as u32;
430    let left = pattern.bounds.x as u32;
431    let bottom = top + height;
432    let right = left + width;
433
434    write_uint32(writer, top);
435    write_uint32(writer, left);
436    write_uint32(writer, bottom);
437    write_uint32(writer, right);
438
439    write_uint32(writer, 24); // channels count
440
441    // channels: RGB at indices 0,1,2 and alpha at index 25
442    for i in 0..(24 + 2) {
443        let offset: i32 = if i < 3 {
444            i
445        } else if i == 25 {
446            3
447        } else {
448            -1
449        };
450
451        if offset < 0 {
452            write_uint32(writer, 0); // has
453            continue;
454        }
455
456        // worst-case RLE size for a single channel
457        let mut buffer = vec![
458            0u8;
459            (width * height + 2 * height + 2 * width + 16) as usize
460        ];
461        let data = write_data_rle(&mut buffer, &pixel_data, &[offset as usize], false)
462            .expect("write_data_rle returned None for pattern channel");
463
464        write_uint32(writer, 1); // has
465        write_uint32(writer, (data.len() + 4 + 16 + 2 + 1) as u32); // length
466        write_uint32(writer, 8); // pixelDepth
467        write_uint32(writer, top);
468        write_uint32(writer, left);
469        write_uint32(writer, bottom);
470        write_uint32(writer, right);
471        write_uint16(writer, 8); // pixelDepth2
472        write_uint8(writer, 1); // compressionMode - rle
473        write_bytes(writer, Some(&data));
474    }
475
476    let vl_length = writer.offset - vl_offset;
477    let mut patts_length = writer.offset - patts_offset;
478
479    while patts_length % 4 != 0 {
480        write_zeros(writer, 1);
481        patts_length += 1;
482    }
483
484    set_bytes_be(writer, vl_offset - 4, &(vl_length as u32).to_be_bytes());
485    set_bytes_be(writer, patts_offset - 4, &(patts_length as u32).to_be_bytes());
486}
487
488// ===========================================================================
489// Document orchestration (port of psdWriter.ts writePsd & friends)
490// ===========================================================================
491
492/// Порт `getLargestLayerSize(layers)`.
493fn get_largest_layer_size(layers: Option<&[Layer]>) -> usize {
494    let mut max = 0usize;
495    let layers = match layers {
496        Some(l) => l,
497        None => return 0,
498    };
499    for layer in layers {
500        if layer.canvas.is_some() || layer.image_data.is_some() {
501            let (width, height) = get_layer_dimensions(layer.canvas.as_ref(), layer.image_data.as_ref());
502            let (w, h) = (width as usize, height as usize);
503            max = max.max(2 * h + 2 * w * h);
504        }
505        if let Some(children) = &layer.children {
506            max = max.max(get_largest_layer_size(Some(children)));
507        }
508    }
509    max
510}
511
512/// Порт `getLayerDimentions({ canvas, imageData })`.
513/// imageData берёт приоритет над canvas (как в upstream).
514fn get_layer_dimensions(canvas: Option<&PixelData>, image_data: Option<&PixelData>) -> (u32, u32) {
515    if let Some(d) = image_data {
516        (d.width, d.height)
517    } else if let Some(c) = canvas {
518        (c.width, c.height)
519    } else {
520        (0, 0)
521    }
522}
523
524/// Порт `verifyBitCount(target)`. В байтовой модели PixelData всегда 8-битный
525/// (нет Uint16Array/Uint32Array), поэтому проверка тривиально проходит. Оставлено
526/// для зеркальности (no-op рекурсия).
527fn verify_bit_count(_target_children: Option<&[Layer]>) {
528    // PixelData is always RGBA8 in this port; nothing to verify.
529}
530
531/// Публичная точка входа: построить байты `.psd` из документа.
532/// Порт связки `writePsd` (psd.ts) + `writePsd(writer, ...)` (psdWriter.ts).
533pub fn write_psd(psd: &Psd, options: &WriteOptions) -> Vec<u8> {
534    let mut writer = create_writer_default();
535    write_psd_to_writer(&mut writer, psd, options);
536    get_writer_buffer(&writer)
537}
538
539/// Порт `writePsd(writer, psd, options)`.
540pub fn write_psd_to_writer(writer: &mut PsdWriter, psd: &Psd, options: &WriteOptions) {
541    if !(psd.width > 0.0 && psd.height > 0.0) {
542        panic!("Invalid document size");
543    }
544
545    let psb = options.psb == Some(true);
546
547    if (psd.width > 30000.0 || psd.height > 30000.0) && !psb {
548        panic!("Document size is too large (max is 30000x30000, use PSB format instead)");
549    }
550
551    let bits_per_channel = psd.bits_per_channel.unwrap_or(8.0);
552    if bits_per_channel != 8.0 {
553        panic!("bitsPerChannel other than 8 are not supported for writing");
554    }
555
556    verify_bit_count(psd.children.as_deref());
557
558    // imageResources: { ...psd.imageResources }. generateThumbnail would set
559    // imageResources.thumbnail, but thumbnail generation requires canvas
560    // scaling (createThumbnail) which is a browser-canvas concern not available
561    // here; see report.
562    let image_resources = psd.image_resources.clone().unwrap_or_default();
563
564    // imageData (composite). Our model stores both image_data and canvas as
565    // PixelData; image_data takes priority, falling back to canvas.
566    let image_data: Option<&PixelData> = psd.image_data.as_ref().or(psd.canvas.as_ref());
567
568    if let Some(id) = image_data {
569        if psd.width as u32 != id.width || psd.height as u32 != id.height {
570            panic!("Document canvas must have the same size as document");
571        }
572    }
573
574    let global_alpha = image_data.map(has_alpha).unwrap_or(false);
575
576    let max_buffer_size = get_largest_layer_size(psd.children.as_deref()).max(
577        4 * 2 * (psd.width as usize) * (psd.height as usize) + 2 * (psd.height as usize),
578    );
579    writer.temp_buffer = Some(vec![0u8; max_buffer_size]);
580
581    // header
582    write_signature(writer, "8BPS");
583    write_uint16(writer, if psb { 2 } else { 1 }); // version
584    write_zeros(writer, 6);
585    write_uint16(writer, if global_alpha { 4 } else { 3 }); // channels
586    write_uint32(writer, psd.height as u32);
587    write_uint32(writer, psd.width as u32);
588    write_uint16(writer, bits_per_channel as u16);
589    write_uint16(writer, ColorMode::Rgb as u16); // we only support saving RGB
590
591    // color mode data
592    let palette = psd.palette.clone();
593    write_section(
594        writer,
595        1,
596        |w| {
597            if let Some(palette) = &palette {
598                for i in 0..256 {
599                    w_palette_byte(w, palette.get(i).map(|c| c.r));
600                }
601                for i in 0..256 {
602                    w_palette_byte(w, palette.get(i).map(|c| c.g));
603                }
604                for i in 0..256 {
605                    w_palette_byte(w, palette.get(i).map(|c| c.b));
606                }
607            }
608        },
609        false,
610        false,
611    );
612
613    // layers (flattened with section dividers)
614    //
615    // upstream unconditionally does `if (!layers.length) layers.push({})`, which
616    // materialises a single empty placeholder layer even for documents that have
617    // no layers section at all (e.g. a background-only PSD where `psd.children`
618    // is absent). That makes read(write(x)) gain a spurious child for such files.
619    // We only add the placeholder when the document actually carries a layers
620    // section (`children` present), so a background-only / no-layer document
621    // round-trips to the same (zero) child count it was read with. Documents with
622    // an explicit (possibly empty) children list still get the placeholder, as
623    // upstream requires for a valid layer section.
624    let has_layer_section = psd.children.is_some();
625    let mut layers: Vec<Layer> = Vec::new();
626    add_children(&mut layers, psd.children.as_deref());
627    if layers.is_empty() && has_layer_section {
628        layers.push(Layer::default());
629    }
630
631    // image resources
632    //
633    // upstream additionally sets imageResources.layersGroup /
634    // layerGroupsEnabledId here (resource ids 1026/1072). Those are
635    // InternalImageResources-only and are NOT modeled on the public
636    // ImageResources struct (the reader skips them), so they are not written.
637    // See report for this dependency gap.
638    write_section(
639        writer,
640        1,
641        |w| {
642            for &id in RESOURCE_IDS {
643                let count = has_image_resource(id, &image_resources);
644                for i in 0..count {
645                    write_signature(w, "8BIM");
646                    write_uint16(w, id);
647                    write_pascal_string(w, "", 2);
648                    write_section(
649                        w,
650                        2,
651                        |w| {
652                            // write_image_resource returns ReadResult<()>; errors
653                            // here mean a malformed resource model — surface as panic
654                            // to mirror upstream throwing.
655                            write_image_resource(id, w, &image_resources, i)
656                                .expect("write_image_resource failed");
657                        },
658                        false,
659                        false,
660                    );
661                }
662            }
663        },
664        false,
665        false,
666    );
667
668    // layer and mask info
669    write_section(
670        writer,
671        2,
672        |w| {
673            write_layer_info(w, &layers, psd, global_alpha, options, psb);
674            write_global_layer_mask_info(w, psd.global_layer_mask_info.as_ref());
675            // document-level additional layer info
676            let mut ctx = WriteCtx::new(options, psb);
677            write_additional_info(w, &psd.additional_info, &mut ctx);
678        },
679        false,
680        psb,
681    );
682
683    // image data (composite)
684    let channels: Vec<usize> = if global_alpha {
685        vec![0, 1, 2, 3]
686    } else {
687        vec![0, 1, 2]
688    };
689    let width = image_data.map(|d| d.width).unwrap_or(psd.width as u32);
690    let height = image_data.map(|d| d.height).unwrap_or(psd.height as u32);
691    let mut data = PixelData {
692        width,
693        height,
694        data: vec![0u8; (width as usize) * (height as usize) * 4],
695    };
696
697    write_uint16(writer, Compression::RleCompressed as u16);
698
699    if let Some(id) = image_data {
700        data.data[..id.data.len()].copy_from_slice(&id.data);
701
702        // add weird white matte
703        if global_alpha {
704            let size = (data.width as usize) * (data.height as usize) * 4;
705            let p = &mut data.data;
706            let mut i = 0;
707            while i < size {
708                let pa = p[i + 3];
709                if pa != 0 && pa != 255 {
710                    let a = pa as f64 / 255.0;
711                    let ra = 255.0 * (1.0 - a);
712                    p[i] = (p[i] as f64 * a + ra) as u8;
713                    p[i + 1] = (p[i + 1] as f64 * a + ra) as u8;
714                    p[i + 2] = (p[i + 2] as f64 * a + ra) as u8;
715                }
716                i += 4;
717            }
718        }
719    }
720
721    let mut temp = writer.temp_buffer.take().unwrap();
722    let rle = write_data_rle(&mut temp, &data, &channels, psb);
723    writer.temp_buffer = Some(temp);
724    write_bytes(writer, rle.as_deref());
725}
726
727/// Записать один байт палитры (0 при отсутствии цвета).
728fn w_palette_byte(writer: &mut PsdWriter, value: Option<f64>) {
729    write_uint8(writer, value.unwrap_or(0.0) as u8);
730}
731
732/// Порт `writeLayerInfo(writer, layers, psd, globalAlpha, options)`.
733fn write_layer_info(
734    writer: &mut PsdWriter,
735    layers: &[Layer],
736    _psd: &Psd,
737    global_alpha: bool,
738    options: &WriteOptions,
739    psb: bool,
740) {
741    write_section(
742        writer,
743        4,
744        |w| {
745            write_int16(
746                w,
747                if global_alpha {
748                    -(layers.len() as i16)
749                } else {
750                    layers.len() as i16
751                },
752            );
753
754            // extract channels for every layer
755            let mut temp = w.temp_buffer.take().unwrap();
756            let mut layers_data: Vec<LayerChannelData> = layers
757                .iter()
758                .enumerate()
759                .map(|(i, l)| get_channels(&mut temp, l, i == 0, options, psb))
760                .collect();
761            w.temp_buffer = Some(temp);
762
763            // layer records
764            let mut ctx = WriteCtx::new(options, psb);
765            for layer_data in &layers_data {
766                let layer = &layer_data.layer;
767                write_int32(w, layer_data.top);
768                write_int32(w, layer_data.left);
769                write_int32(w, layer_data.bottom);
770                write_int32(w, layer_data.right);
771                write_uint16(w, layer_data.channels.len() as u16);
772
773                for c in &layer_data.channels {
774                    write_int16(w, c.channel_id as i16);
775                    if psb {
776                        write_uint32(w, 0);
777                    }
778                    write_uint32(w, c.length as u32);
779                }
780
781                write_signature(w, "8BIM");
782                let blend = layer.blend_mode.map(from_blend_mode).unwrap_or("norm");
783                write_signature(w, blend);
784                write_uint8(w, (clamp(layer.opacity.unwrap_or(1.0), 0.0, 1.0) * 255.0).round() as u8);
785                write_uint8(w, if layer.clipping == Some(true) { 1 } else { 0 });
786
787                let mut flags: u8 = 0x08;
788                if layer.transparency_protected == Some(true) {
789                    flags |= 0x01;
790                }
791                if layer.hidden == Some(true) {
792                    flags |= 0x02;
793                }
794                let info = &layer.additional_info;
795                let section_irrelevant = info.section_divider.as_ref().map_or(false, |sd| {
796                    sd.divider_type != SectionDividerType::Other
797                });
798                if info.vector_mask.is_some() || section_irrelevant || info.adjustment.is_some() {
799                    flags |= 0x10;
800                }
801                if layer.effects_open == Some(true) {
802                    flags |= 0x20;
803                }
804
805                write_uint8(w, flags);
806                write_uint8(w, 0); // filler
807
808                write_section(
809                    w,
810                    1,
811                    |w| {
812                        write_layer_mask_data(w, info, layer_data);
813                        write_layer_blending_ranges(w, info);
814                        let name = info.name.clone().unwrap_or_default();
815                        let name: String = name.chars().take(255).collect();
816                        write_pascal_string(w, &name, 4);
817                        write_additional_info(w, info, &mut ctx);
818                    },
819                    false,
820                    false,
821                );
822            }
823
824            // layer channel image data
825            for layer_data in &mut layers_data {
826                for channel in &layer_data.channels {
827                    write_uint16(w, channel.compression as u16);
828                    if let Some(buffer) = &channel.buffer {
829                        write_bytes(w, Some(buffer));
830                    }
831                }
832            }
833        },
834        true,
835        psb,
836    );
837}
838
839/// Порт `writeLayerMaskData(writer, { mask, realMask }, layerData)`.
840fn write_layer_mask_data(
841    writer: &mut PsdWriter,
842    info: &LayerAdditionalInfo,
843    layer_data: &LayerChannelData,
844) {
845    let mask = info.mask.as_ref();
846    let real_mask = info.real_mask.as_ref();
847    write_section(
848        writer,
849        1,
850        |w| {
851            if mask.is_none() && real_mask.is_none() {
852                return;
853            }
854
855            let mut params: u8 = 0;
856            let mut flags: u8 = 0;
857            let mut real_flags: u8 = 0;
858
859            if let Some(mask) = mask {
860                if mask.user_mask_density.is_some() {
861                    params |= MaskParams::UserMaskDensity as u8;
862                }
863                if mask.user_mask_feather.is_some() {
864                    params |= MaskParams::UserMaskFeather as u8;
865                }
866                if mask.vector_mask_density.is_some() {
867                    params |= MaskParams::VectorMaskDensity as u8;
868                }
869                if mask.vector_mask_feather.is_some() {
870                    params |= MaskParams::VectorMaskFeather as u8;
871                }
872
873                if mask.disabled == Some(true) {
874                    flags |= LayerMaskFlags::LayerMaskDisabled as u8;
875                }
876                if mask.position_relative_to_layer == Some(true) {
877                    flags |= LayerMaskFlags::PositionRelativeToLayer as u8;
878                }
879                if mask.from_vector_data == Some(true) {
880                    flags |= LayerMaskFlags::LayerMaskFromRenderingOtherData as u8;
881                }
882                if params != 0 {
883                    flags |= LayerMaskFlags::MaskHasParametersAppliedToIt as u8;
884                }
885            }
886
887            let m = layer_data.mask.unwrap_or_default();
888            write_int32(w, m.top);
889            write_int32(w, m.left);
890            write_int32(w, m.bottom);
891            write_int32(w, m.right);
892            write_uint8(w, mask.and_then(|m| m.default_color).unwrap_or(0.0) as u8);
893            write_uint8(w, flags);
894
895            if let Some(real_mask) = real_mask {
896                if real_mask.disabled == Some(true) {
897                    real_flags |= LayerMaskFlags::LayerMaskDisabled as u8;
898                }
899                if real_mask.position_relative_to_layer == Some(true) {
900                    real_flags |= LayerMaskFlags::PositionRelativeToLayer as u8;
901                }
902                if real_mask.from_vector_data == Some(true) {
903                    real_flags |= LayerMaskFlags::LayerMaskFromRenderingOtherData as u8;
904                }
905
906                let r = layer_data.real_mask.unwrap_or_default();
907                write_uint8(w, real_flags);
908                write_uint8(w, real_mask.default_color.unwrap_or(0.0) as u8);
909                write_int32(w, r.top);
910                write_int32(w, r.left);
911                write_int32(w, r.bottom);
912                write_int32(w, r.right);
913            }
914
915            if params != 0 {
916                if let Some(mask) = mask {
917                    write_uint8(w, params);
918                    if let Some(v) = mask.user_mask_density {
919                        write_uint8(w, (v * 0xff as f64).round() as u8);
920                    }
921                    if let Some(v) = mask.user_mask_feather {
922                        write_float64(w, v);
923                    }
924                    if let Some(v) = mask.vector_mask_density {
925                        write_uint8(w, (v * 0xff as f64).round() as u8);
926                    }
927                    if let Some(v) = mask.vector_mask_feather {
928                        write_float64(w, v);
929                    }
930                }
931            }
932
933            write_zeros(w, 2);
934        },
935        false,
936        false,
937    );
938}
939
940/// Порт `writerBlendingRange`.
941fn write_blending_range(writer: &mut PsdWriter, range: &[f64]) {
942    write_uint8(writer, range[0] as u8);
943    write_uint8(writer, range[1] as u8);
944    write_uint8(writer, range[2] as u8);
945    write_uint8(writer, range[3] as u8);
946}
947
948/// Порт `writeLayerBlendingRanges(writer, layer)`.
949fn write_layer_blending_ranges(writer: &mut PsdWriter, info: &LayerAdditionalInfo) {
950    let ranges = info.blending_ranges.clone();
951    write_section(
952        writer,
953        1,
954        |w| {
955            if let Some(ranges) = &ranges {
956                write_blending_range(w, &ranges.composite_gray_blend_source);
957                write_blending_range(w, &ranges.composite_graph_blend_destination_range);
958                for r in &ranges.ranges {
959                    write_blending_range(w, &r.source_range);
960                    write_blending_range(w, &r.dest_range);
961                }
962            }
963        },
964        false,
965        false,
966    );
967}
968
969/// Порт `writeGlobalLayerMaskInfo(writer, info)`.
970fn write_global_layer_mask_info(writer: &mut PsdWriter, info: Option<&GlobalLayerMaskInfo>) {
971    let info = info.cloned();
972    write_section(
973        writer,
974        1,
975        |w| {
976            if let Some(info) = &info {
977                write_uint16(w, info.overlay_color_space as u16);
978                write_uint16(w, info.color_space1 as u16);
979                write_uint16(w, info.color_space2 as u16);
980                write_uint16(w, info.color_space3 as u16);
981                write_uint16(w, info.color_space4 as u16);
982                write_uint16(w, (info.opacity * 0xff as f64) as u16);
983                write_uint8(w, info.kind as u8);
984                write_zeros(w, 3);
985            }
986        },
987        false,
988        false,
989    );
990}
991
992/// Порт `addChildren(layers, children)`.
993fn add_children(layers: &mut Vec<Layer>, children: Option<&[Layer]>) {
994    let children = match children {
995        Some(c) => c,
996        None => return,
997    };
998
999    for c in children {
1000        if c.children.is_some() && c.canvas.is_some() {
1001            panic!("Invalid layer, cannot have both 'canvas' and 'children' properties");
1002        }
1003        if c.children.is_some() && c.image_data.is_some() {
1004            panic!("Invalid layer, cannot have both 'imageData' and 'children' properties");
1005        }
1006
1007        if c.children.is_some() {
1008            // bounding section divider
1009            let mut open_layer = Layer::default();
1010            open_layer.additional_info.name = Some("</Layer group>".to_string());
1011            open_layer.additional_info.section_divider = Some(crate::psd::SectionDivider {
1012                divider_type: SectionDividerType::BoundingSectionDivider,
1013                key: None,
1014                sub_type: None,
1015            });
1016            layers.push(open_layer);
1017
1018            add_children(layers, c.children.as_deref());
1019
1020            // closing folder layer: copy of `c` with adjusted blend mode + divider
1021            let mut folder = c.clone();
1022            folder.children = None;
1023            if folder.blend_mode == Some(BlendMode::PassThrough) {
1024                folder.blend_mode = Some(BlendMode::Normal);
1025            }
1026            let key = c.blend_mode.map(from_blend_mode).unwrap_or("pass").to_string();
1027            folder.additional_info.section_divider = Some(crate::psd::SectionDivider {
1028                divider_type: if c.opened == Some(false) {
1029                    SectionDividerType::ClosedFolder
1030                } else {
1031                    SectionDividerType::OpenFolder
1032                },
1033                key: Some(key),
1034                sub_type: Some(0.0),
1035            });
1036            layers.push(folder);
1037        } else {
1038            layers.push(c.clone());
1039        }
1040    }
1041}
1042
1043/// Порт `getChannels(tempBuffer, layer, background, options)`.
1044fn get_channels(
1045    temp_buffer: &mut [u8],
1046    layer: &Layer,
1047    background: bool,
1048    options: &WriteOptions,
1049    psb: bool,
1050) -> LayerChannelData {
1051    let mut layer_data = get_layer_channels(temp_buffer, layer, background, options, psb);
1052    if let Some(mask) = &layer.additional_info.mask {
1053        get_mask_channels(temp_buffer, &mut layer_data, mask, options, psb, false);
1054    }
1055    if let Some(real_mask) = &layer.additional_info.real_mask {
1056        get_mask_channels(temp_buffer, &mut layer_data, real_mask, options, psb, true);
1057    }
1058    layer_data
1059}
1060
1061/// Порт `getMaskChannels(...)`.
1062fn get_mask_channels(
1063    temp_buffer: &mut [u8],
1064    layer_data: &mut LayerChannelData,
1065    mask: &LayerMaskData,
1066    options: &WriteOptions,
1067    psb: bool,
1068    real_mask: bool,
1069) {
1070    let top = mask.top.unwrap_or(0.0) as i32;
1071    let left = mask.left.unwrap_or(0.0) as i32;
1072    let (width, height) = get_layer_dimensions(mask.canvas.as_ref(), mask.image_data.as_ref());
1073
1074    let image_data = mask.image_data.as_ref().or(mask.canvas.as_ref());
1075
1076    if let Some(id) = image_data {
1077        if id.width != width || id.height != height {
1078            panic!("Invalid imageData dimentions");
1079        }
1080    }
1081
1082    let right = left + width as i32;
1083    let bottom = top + height as i32;
1084
1085    let (buffer, compression): (Vec<u8>, Compression) = if image_data.is_none() {
1086        (Vec::new(), Compression::RleCompressed)
1087    } else if options.compress == Some(true) {
1088        (
1089            write_data_zip_without_prediction(image_data.unwrap(), &[0]).unwrap_or_default(),
1090            Compression::ZipWithoutPrediction,
1091        )
1092    } else {
1093        (
1094            write_data_rle(temp_buffer, image_data.unwrap(), &[0], psb).unwrap_or_default(),
1095            Compression::RleCompressed,
1096        )
1097    };
1098
1099    let length = 2 + buffer.len();
1100    layer_data.channels.push(ChannelData {
1101        channel_id: if real_mask {
1102            ChannelId::RealUserMask
1103        } else {
1104            ChannelId::UserMask
1105        },
1106        compression,
1107        buffer: Some(buffer),
1108        length,
1109    });
1110
1111    let bounds = ChannelBounds { top, left, right, bottom };
1112    if real_mask {
1113        layer_data.real_mask = Some(bounds);
1114    } else {
1115        layer_data.mask = Some(bounds);
1116    }
1117}
1118
1119/// Порт `cropImageData(data, left, top, width, height)`.
1120fn crop_image_data(data: &PixelData, left: usize, top: usize, width: usize, height: usize) -> PixelData {
1121    let mut dst = vec![0u8; width * height * 4];
1122    let src = &data.data;
1123    let dw = data.width as usize;
1124    for y in 0..height {
1125        for x in 0..width {
1126            let s = ((x + left) + (y + top) * dw) * 4;
1127            let d = (x + y * width) * 4;
1128            dst[d] = src[s];
1129            dst[d + 1] = src[s + 1];
1130            dst[d + 2] = src[s + 2];
1131            dst[d + 3] = src[s + 3];
1132        }
1133    }
1134    PixelData {
1135        width: width as u32,
1136        height: height as u32,
1137        data: dst,
1138    }
1139}
1140
1141/// Порт `getLayerChannels(tempBuffer, layer, background, options)`.
1142fn get_layer_channels(
1143    temp_buffer: &mut [u8],
1144    layer: &Layer,
1145    background: bool,
1146    options: &WriteOptions,
1147    psb: bool,
1148) -> LayerChannelData {
1149    let mut top = layer.top.unwrap_or(0.0) as i32;
1150    let mut left = layer.left.unwrap_or(0.0) as i32;
1151    #[allow(unused_assignments)]
1152    let mut right = layer.right.unwrap_or(0.0) as i32;
1153    #[allow(unused_assignments)]
1154    let mut bottom = layer.bottom.unwrap_or(0.0) as i32;
1155
1156    // default empty channel set (Transparency, Color0..2), all length=2.
1157    let default_channels = || {
1158        vec![
1159            ChannelData { channel_id: ChannelId::Transparency, compression: Compression::RawData, buffer: None, length: 2 },
1160            ChannelData { channel_id: ChannelId::Color0, compression: Compression::RawData, buffer: None, length: 2 },
1161            ChannelData { channel_id: ChannelId::Color1, compression: Compression::RawData, buffer: None, length: 2 },
1162            ChannelData { channel_id: ChannelId::Color2, compression: Compression::RawData, buffer: None, length: 2 },
1163        ]
1164    };
1165
1166    let (mut width, mut height) = get_layer_dimensions(layer.canvas.as_ref(), layer.image_data.as_ref());
1167
1168    if (layer.canvas.is_none() && layer.image_data.is_none()) || width == 0 || height == 0 {
1169        right = left;
1170        bottom = top;
1171        return LayerChannelData {
1172            layer: layer.clone(),
1173            channels: default_channels(),
1174            top,
1175            left,
1176            right,
1177            bottom,
1178            mask: None,
1179            real_mask: None,
1180        };
1181    }
1182
1183    right = left + width as i32;
1184    bottom = top + height as i32;
1185
1186    let mut data: PixelData = layer
1187        .image_data
1188        .clone()
1189        .or_else(|| layer.canvas.clone())
1190        .unwrap();
1191
1192    if options.trim_image_data == Some(true) {
1193        let trimmed = trim_data(&data);
1194        if trimmed.left != 0
1195            || trimmed.top != 0
1196            || trimmed.right != data.width as i32
1197            || trimmed.bottom != data.height as i32
1198        {
1199            left += trimmed.left;
1200            top += trimmed.top;
1201            right -= data.width as i32 - trimmed.right;
1202            bottom -= data.height as i32 - trimmed.bottom;
1203            width = (right - left) as u32;
1204            height = (bottom - top) as u32;
1205
1206            if width == 0 || height == 0 {
1207                return LayerChannelData {
1208                    layer: layer.clone(),
1209                    channels: default_channels(),
1210                    top,
1211                    left,
1212                    right,
1213                    bottom,
1214                    mask: None,
1215                    real_mask: None,
1216                };
1217            }
1218
1219            data = crop_image_data(
1220                &data,
1221                trimmed.left as usize,
1222                trimmed.top as usize,
1223                width as usize,
1224                height as usize,
1225            );
1226        }
1227    }
1228
1229    let mut channel_ids = vec![ChannelId::Color0, ChannelId::Color1, ChannelId::Color2];
1230
1231    if !background
1232        || options.no_background == Some(true)
1233        || layer.additional_info.mask.is_some()
1234        || has_alpha(&data)
1235    {
1236        channel_ids.insert(0, ChannelId::Transparency);
1237    }
1238
1239    let channels: Vec<ChannelData> = channel_ids
1240        .into_iter()
1241        .map(|channel_id| {
1242            let offset = offset_for_channel(channel_id, false) as usize;
1243            let (buffer, compression): (Vec<u8>, Compression) = if options.compress == Some(true) {
1244                (
1245                    write_data_zip_without_prediction(&data, &[offset]).unwrap_or_default(),
1246                    Compression::ZipWithoutPrediction,
1247                )
1248            } else {
1249                (
1250                    write_data_rle(temp_buffer, &data, &[offset], psb).unwrap_or_default(),
1251                    Compression::RleCompressed,
1252                )
1253            };
1254            let length = 2 + buffer.len();
1255            ChannelData { channel_id, compression, buffer: Some(buffer), length }
1256        })
1257        .collect();
1258
1259    let _ = RAW_IMAGE_DATA; // raw image data path not modeled (RAW_IMAGE_DATA == false)
1260
1261    LayerChannelData {
1262        layer: layer.clone(),
1263        channels,
1264        top,
1265        left,
1266        right,
1267        bottom,
1268        mask: None,
1269        real_mask: None,
1270    }
1271}
1272
1273/// Порт `isRowEmpty`.
1274fn is_row_empty(data: &PixelData, y: usize, left: usize, right: usize) -> bool {
1275    let width = data.width as usize;
1276    let start = (y * width + left) * 4 + 3;
1277    let end = start + (right - left) * 4;
1278    let mut i = start;
1279    while i < end {
1280        if data.data[i] != 0 {
1281            return false;
1282        }
1283        i += 4;
1284    }
1285    true
1286}
1287
1288/// Порт `isColEmpty`.
1289fn is_col_empty(data: &PixelData, x: usize, top: usize, bottom: usize) -> bool {
1290    let width = data.width as usize;
1291    let stride = width * 4;
1292    let start = top * stride + x * 4 + 3;
1293    let mut y = top;
1294    let mut i = start;
1295    while y < bottom {
1296        if data.data[i] != 0 {
1297            return false;
1298        }
1299        y += 1;
1300        i += stride;
1301    }
1302    true
1303}
1304
1305/// Порт `trimData(data)`. Возвращает обрезанные границы в координатах данных.
1306fn trim_data(data: &PixelData) -> ChannelBounds {
1307    let mut top = 0i32;
1308    let mut left = 0i32;
1309    let mut right = data.width as i32;
1310    let mut bottom = data.height as i32;
1311
1312    while top < bottom && is_row_empty(data, top as usize, left as usize, right as usize) {
1313        top += 1;
1314    }
1315    while bottom > top && is_row_empty(data, (bottom - 1) as usize, left as usize, right as usize) {
1316        bottom -= 1;
1317    }
1318    while left < right && is_col_empty(data, left as usize, top as usize, bottom as usize) {
1319        left += 1;
1320    }
1321    while right > left && is_col_empty(data, (right - 1) as usize, top as usize, bottom as usize) {
1322        right -= 1;
1323    }
1324
1325    ChannelBounds { top, left, right, bottom }
1326}
1327
1328#[cfg(test)]
1329mod tests {
1330    use super::*;
1331    use crate::psd::{Cmyk, Grayscale, Hsb, Lab, Rgb};
1332
1333    #[test]
1334    fn scalars_big_endian() {
1335        let mut w = create_writer_default();
1336        write_uint8(&mut w, 0x12);
1337        write_uint16(&mut w, 0x1234);
1338        write_int16(&mut w, -2);
1339        write_uint32(&mut w, 0x12345678);
1340        write_int32(&mut w, -1);
1341        let buf = get_writer_buffer(&w);
1342        assert_eq!(
1343            buf,
1344            vec![
1345                0x12, // u8
1346                0x12, 0x34, // u16 BE
1347                0xff, 0xfe, // i16 BE (-2)
1348                0x12, 0x34, 0x56, 0x78, // u32 BE
1349                0xff, 0xff, 0xff, 0xff, // i32 BE (-1)
1350            ]
1351        );
1352    }
1353
1354    #[test]
1355    fn le_variants() {
1356        let mut w = create_writer_default();
1357        write_uint16_le(&mut w, 0x1234);
1358        write_int32_le(&mut w, 0x12345678);
1359        assert_eq!(
1360            get_writer_buffer(&w),
1361            vec![0x34, 0x12, 0x78, 0x56, 0x34, 0x12]
1362        );
1363    }
1364
1365    #[test]
1366    fn floats_big_endian() {
1367        let mut w = create_writer_default();
1368        write_float32(&mut w, 1.0_f32);
1369        write_float64(&mut w, 1.0_f64);
1370        assert_eq!(
1371            get_writer_buffer(&w),
1372            vec![
1373                0x3f, 0x80, 0x00, 0x00, // f32 1.0 BE
1374                0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // f64 1.0 BE
1375            ]
1376        );
1377    }
1378
1379    #[test]
1380    fn signature_and_zeros() {
1381        let mut w = create_writer_default();
1382        write_signature(&mut w, "8BPS");
1383        write_zeros(&mut w, 3);
1384        assert_eq!(get_writer_buffer(&w), vec![b'8', b'B', b'P', b'S', 0, 0, 0]);
1385    }
1386
1387    #[test]
1388    fn pascal_string_padding() {
1389        // "ab" -> [len=2, 'a', 'b'] then pad: length becomes 3, 3%4!=0 -> write 0 (4),
1390        // 4%4==0 stop. Total bytes: 1 + 2 + 1 = 4.
1391        let mut w = create_writer_default();
1392        write_pascal_string(&mut w, "ab", 4);
1393        assert_eq!(get_writer_buffer(&w), vec![2, b'a', b'b', 0]);
1394    }
1395
1396    #[test]
1397    fn pascal_string_empty_pad2() {
1398        // "" -> [0], length 0 -> ++length=1, 1%2!=0 -> write 0 (2), 2%2==0 stop.
1399        let mut w = create_writer_default();
1400        write_pascal_string(&mut w, "", 2);
1401        assert_eq!(get_writer_buffer(&w), vec![0, 0]);
1402    }
1403
1404    #[test]
1405    fn pascal_string_non_ascii_becomes_question() {
1406        let mut w = create_writer_default();
1407        write_pascal_string(&mut w, "é", 1); // 1 char, code > 127 -> '?'
1408        // len=1, '?'; ++length=2, 2%1==0 stop.
1409        assert_eq!(get_writer_buffer(&w), vec![1, b'?']);
1410    }
1411
1412    #[test]
1413    fn unicode_string_layout() {
1414        let mut w = create_writer_default();
1415        write_unicode_string(&mut w, "AB");
1416        assert_eq!(
1417            get_writer_buffer(&w),
1418            vec![
1419                0x00, 0x00, 0x00, 0x02, // length = 2 (u32 BE)
1420                0x00, b'A', 0x00, b'B', // UTF-16 BE
1421            ]
1422        );
1423    }
1424
1425    #[test]
1426    fn unicode_string_with_padding_layout() {
1427        let mut w = create_writer_default();
1428        write_unicode_string_with_padding(&mut w, "A");
1429        assert_eq!(
1430            get_writer_buffer(&w),
1431            vec![
1432                0x00, 0x00, 0x00, 0x02, // length+1 = 2
1433                0x00, b'A', // UTF-16 BE
1434                0x00, 0x00, // terminating 0
1435            ]
1436        );
1437    }
1438
1439    #[test]
1440    fn section_backpatch_and_rounding() {
1441        // round=4, body = 3 bytes -> length 3, padded to 4. writeTotalLength=false
1442        // so the patched length is the un-padded value (3).
1443        let mut w = create_writer_default();
1444        write_section(
1445            &mut w,
1446            4,
1447            |w| {
1448                write_uint8(w, 0xaa);
1449                write_uint8(w, 0xbb);
1450                write_uint8(w, 0xcc);
1451            },
1452            false,
1453            false,
1454        );
1455        assert_eq!(
1456            get_writer_buffer(&w),
1457            vec![
1458                0x00, 0x00, 0x00, 0x03, // length = 3 (un-padded, writeTotalLength=false)
1459                0xaa, 0xbb, 0xcc, // body
1460                0x00, // padding to round=4
1461            ]
1462        );
1463    }
1464
1465    #[test]
1466    fn section_total_length() {
1467        // writeTotalLength=true -> patched length includes padding (4).
1468        let mut w = create_writer_default();
1469        write_section(
1470            &mut w,
1471            4,
1472            |w| {
1473                write_uint8(w, 0xaa);
1474                write_uint8(w, 0xbb);
1475                write_uint8(w, 0xcc);
1476            },
1477            true,
1478            false,
1479        );
1480        assert_eq!(
1481            get_writer_buffer(&w),
1482            vec![0x00, 0x00, 0x00, 0x04, 0xaa, 0xbb, 0xcc, 0x00]
1483        );
1484    }
1485
1486    #[test]
1487    fn section_large() {
1488        // large=true -> leading extra u32(0), then the length word. body=1 byte, round=2.
1489        let mut w = create_writer_default();
1490        write_section(&mut w, 2, |w| write_uint8(w, 0x7f), false, true);
1491        assert_eq!(
1492            get_writer_buffer(&w),
1493            vec![
1494                0x00, 0x00, 0x00, 0x00, // large leading u32
1495                0x00, 0x00, 0x00, 0x01, // length = 1 (un-padded)
1496                0x7f, // body
1497                0x00, // padding to round=2
1498            ]
1499        );
1500    }
1501
1502    #[test]
1503    fn buffer_growth_doubling() {
1504        // Start at size 4, write 10 bytes -> 4 -> 8 -> 16.
1505        let mut w = create_writer(4);
1506        for i in 0..10u8 {
1507            write_uint8(&mut w, i);
1508        }
1509        assert_eq!(w.offset, 10);
1510        assert_eq!(w.buffer.len(), 16);
1511        assert_eq!(
1512            get_writer_buffer(&w),
1513            vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1514        );
1515    }
1516
1517    #[test]
1518    fn write_bytes_grows_and_appends() {
1519        let mut w = create_writer(2);
1520        write_bytes(&mut w, Some(&[1, 2, 3, 4, 5]));
1521        write_bytes(&mut w, None);
1522        assert_eq!(get_writer_buffer(&w), vec![1, 2, 3, 4, 5]);
1523    }
1524
1525    #[test]
1526    fn color_none_is_rgb_zeros() {
1527        let mut w = create_writer_default();
1528        write_color(&mut w, None);
1529        assert_eq!(
1530            get_writer_buffer(&w),
1531            vec![0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0] // ColorSpace::Rgb (0) + 8 zeros
1532        );
1533    }
1534
1535    #[test]
1536    fn color_rgb() {
1537        let mut w = create_writer_default();
1538        let c = Color::Rgb(Rgb {
1539            r: 255.0,
1540            g: 0.0,
1541            b: 128.0,
1542        });
1543        write_color(&mut w, Some(&c));
1544        // 255*257 = 65535 = 0xffff, 0, 128*257 = 32896 = 0x8080, then 0
1545        assert_eq!(
1546            get_writer_buffer(&w),
1547            vec![
1548                0x00, 0x00, // ColorSpace::Rgb
1549                0xff, 0xff, // r
1550                0x00, 0x00, // g
1551                0x80, 0x80, // b
1552                0x00, 0x00, // pad
1553            ]
1554        );
1555    }
1556
1557    #[test]
1558    fn color_cmyk_lab_hsb_grayscale_color_space_codes() {
1559        let mut w = create_writer_default();
1560        write_color(&mut w, Some(&Color::Cmyk(Cmyk { c: 0.0, m: 0.0, y: 0.0, k: 0.0 })));
1561        write_color(&mut w, Some(&Color::Lab(Lab { l: 0.0, a: 0.0, b: 0.0 })));
1562        write_color(&mut w, Some(&Color::Hsb(Hsb { h: 0.0, s: 0.0, b: 0.0 })));
1563        write_color(&mut w, Some(&Color::Grayscale(Grayscale { k: 0.0 })));
1564        let buf = get_writer_buffer(&w);
1565        // First u16 of each block is the ColorSpace code.
1566        assert_eq!(&buf[0..2], &[0x00, 0x02]); // Cmyk = 2
1567        assert_eq!(&buf[10..12], &[0x00, 0x07]); // Lab = 7
1568        assert_eq!(&buf[20..22], &[0x00, 0x01]); // Hsb = 1
1569        assert_eq!(&buf[30..32], &[0x00, 0x08]); // Grayscale = 8
1570    }
1571
1572    // -----------------------------------------------------------------------
1573    // Round-trip tests: read a real fixture -> write_psd -> read again,
1574    // assert structural equality (the bar for this task).
1575    // -----------------------------------------------------------------------
1576
1577    use crate::psd::{BlendMode, Layer, Psd, ReadOptions};
1578    use crate::reader::read_psd;
1579
1580    fn read_fixture(rel: &str) -> Psd {
1581        // Read with use_image_data so layers carry `image_data` (byte planes)
1582        // for pixel comparison, instead of the (cloned) canvas.
1583        let path = format!(
1584            "{}/../../test/ag-psd/test/read/{}/src.psd",
1585            env!("CARGO_MANIFEST_DIR"),
1586            rel
1587        );
1588        let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("read {}: {}", path, e));
1589        let opts = ReadOptions { use_image_data: Some(true), ..Default::default() };
1590        read_psd(&bytes, &opts).unwrap_or_else(|e| panic!("read_psd {}: {:?}", rel, e))
1591    }
1592
1593    fn count_layers(layers: &[Layer]) -> usize {
1594        layers
1595            .iter()
1596            .map(|l| 1 + l.children.as_ref().map_or(0, |c| count_layers(c)))
1597            .sum()
1598    }
1599
1600    /// Flatten the tree depth-first into (name, top, left, bottom, right,
1601    /// opacity, blend_mode) tuples for structural comparison. Folders are
1602    /// included; group structure is preserved via recursion order.
1603    fn flatten(layers: &[Layer], out: &mut Vec<(String, i64, i64, i64, i64, u8, BlendMode)>) {
1604        for l in layers {
1605            out.push((
1606                l.additional_info.name.clone().unwrap_or_default(),
1607                l.top.unwrap_or(0.0) as i64,
1608                l.left.unwrap_or(0.0) as i64,
1609                l.bottom.unwrap_or(0.0) as i64,
1610                l.right.unwrap_or(0.0) as i64,
1611                (l.opacity.unwrap_or(1.0) * 255.0).round() as u8,
1612                l.blend_mode.unwrap_or(BlendMode::Normal),
1613            ));
1614            if let Some(children) = &l.children {
1615                flatten(children, out);
1616            }
1617        }
1618    }
1619
1620    fn round_trip(psd: &Psd) -> Psd {
1621        let bytes = write_psd(psd, &WriteOptions::default());
1622        let opts = ReadOptions { use_image_data: Some(true), ..Default::default() };
1623        read_psd(&bytes, &opts).expect("re-read written psd")
1624    }
1625
1626    fn assert_structural_eq(a: &Psd, b: &Psd) {
1627        assert_eq!(a.width, b.width, "width");
1628        assert_eq!(a.height, b.height, "height");
1629        assert_eq!(a.color_mode, b.color_mode, "color_mode");
1630
1631        let ca = a.children.as_deref().unwrap_or(&[]);
1632        let cb = b.children.as_deref().unwrap_or(&[]);
1633        assert_eq!(ca.len(), cb.len(), "top-level children count");
1634        assert_eq!(count_layers(ca), count_layers(cb), "total layer count");
1635
1636        let mut fa = Vec::new();
1637        let mut fb = Vec::new();
1638        flatten(ca, &mut fa);
1639        flatten(cb, &mut fb);
1640        assert_eq!(fa, fb, "per-layer name/bounds/opacity/blendMode");
1641    }
1642
1643    /// Total bytes of layer image data across the tree (for survival check).
1644    fn total_pixel_bytes(layers: &[Layer]) -> usize {
1645        layers
1646            .iter()
1647            .map(|l| {
1648                let own = l.image_data.as_ref().map_or(0, |d| d.data.len());
1649                own + l.children.as_ref().map_or(0, |c| total_pixel_bytes(c))
1650            })
1651            .sum()
1652    }
1653
1654    #[test]
1655    fn round_trip_layers_fixture() {
1656        let orig = read_fixture("layers");
1657        let again = round_trip(&orig);
1658        assert_structural_eq(&orig, &again);
1659
1660        // Layer pixel data must survive the round trip.
1661        let ca = orig.children.as_deref().unwrap_or(&[]);
1662        let cb = again.children.as_deref().unwrap_or(&[]);
1663        assert!(total_pixel_bytes(ca) > 0, "fixture should have layer pixels");
1664        assert_eq!(
1665            total_pixel_bytes(ca),
1666            total_pixel_bytes(cb),
1667            "layer pixel byte totals survive round trip"
1668        );
1669    }
1670
1671    #[test]
1672    fn round_trip_groups_fixture() {
1673        let orig = read_fixture("groups");
1674        let again = round_trip(&orig);
1675        assert_structural_eq(&orig, &again);
1676
1677        let ca = orig.children.as_deref().unwrap_or(&[]);
1678        let cb = again.children.as_deref().unwrap_or(&[]);
1679        assert_eq!(
1680            total_pixel_bytes(ca),
1681            total_pixel_bytes(cb),
1682            "layer pixel byte totals survive round trip"
1683        );
1684    }
1685
1686    #[test]
1687    fn round_trip_synthetic_two_solid_layers() {
1688        // Build a small Psd: 4x4 document, 2 solid-color layers each 4x4.
1689        fn solid(w: u32, h: u32, rgba: [u8; 4]) -> PixelData {
1690            let mut data = vec![0u8; (w * h * 4) as usize];
1691            for px in data.chunks_mut(4) {
1692                px.copy_from_slice(&rgba);
1693            }
1694            PixelData { width: w, height: h, data }
1695        }
1696
1697        let mut red = Layer::default();
1698        red.additional_info.name = Some("red".to_string());
1699        red.top = Some(0.0);
1700        red.left = Some(0.0);
1701        red.bottom = Some(4.0);
1702        red.right = Some(4.0);
1703        red.opacity = Some(1.0);
1704        red.blend_mode = Some(BlendMode::Normal);
1705        red.image_data = Some(solid(4, 4, [255, 0, 0, 255]));
1706
1707        let mut blue = Layer::default();
1708        blue.additional_info.name = Some("blue".to_string());
1709        blue.top = Some(0.0);
1710        blue.left = Some(0.0);
1711        blue.bottom = Some(4.0);
1712        blue.right = Some(4.0);
1713        blue.opacity = Some(0.5);
1714        blue.blend_mode = Some(BlendMode::Multiply);
1715        blue.image_data = Some(solid(4, 4, [0, 0, 255, 200]));
1716
1717        let psd = Psd {
1718            width: 4.0,
1719            height: 4.0,
1720            color_mode: Some(ColorMode::Rgb),
1721            bits_per_channel: Some(8.0),
1722            children: Some(vec![red, blue]),
1723            ..Default::default()
1724        };
1725
1726        let again = round_trip(&psd);
1727
1728        assert_eq!(again.width, 4.0);
1729        assert_eq!(again.height, 4.0);
1730        assert_eq!(again.color_mode, Some(ColorMode::Rgb));
1731
1732        let children = again.children.as_ref().expect("children");
1733        assert_eq!(children.len(), 2, "two layers survive");
1734
1735        let red_back = &children[0];
1736        assert_eq!(red_back.additional_info.name.as_deref(), Some("red"));
1737        assert_eq!(red_back.blend_mode, Some(BlendMode::Normal));
1738        assert_eq!(red_back.opacity.map(|o| (o * 255.0).round() as u8), Some(255));
1739        assert_eq!(red_back.bottom, Some(4.0));
1740        assert_eq!(red_back.right, Some(4.0));
1741        // first pixel should be red, fully opaque
1742        let rd = red_back.image_data.as_ref().expect("red image data");
1743        assert_eq!(&rd.data[0..4], &[255, 0, 0, 255]);
1744
1745        let blue_back = &children[1];
1746        assert_eq!(blue_back.additional_info.name.as_deref(), Some("blue"));
1747        assert_eq!(blue_back.blend_mode, Some(BlendMode::Multiply));
1748        assert_eq!(blue_back.opacity.map(|o| (o * 255.0).round() as u8), Some(128));
1749        let bd = blue_back.image_data.as_ref().expect("blue image data");
1750        assert_eq!(&bd.data[0..4], &[0, 0, 255, 200]);
1751    }
1752}