Skip to main content

rawshift_image/tiff/
writer.rs

1//! TIFF/DNG file writer.
2//!
3//! This module provides low-level TIFF writing functionality for creating
4//! DNG files with embedded metadata.
5
6use binrw::{BinWrite, BinWriterExt, Endian};
7use std::io::{Seek, SeekFrom, Write};
8
9use crate::error::RawResult;
10use crate::tiff::TiffTag;
11use crate::tiff::types::{ByteOrder, TiffType};
12
13/// A single IFD entry to be written.
14#[derive(Debug, Clone)]
15pub struct IfdEntry {
16    /// Tag ID
17    pub tag: u16,
18    /// Data type
19    pub typ: TiffType,
20    /// Number of values
21    pub count: u32,
22    /// Raw value bytes (in file byte order)
23    pub value_bytes: Vec<u8>,
24}
25
26/// On-disk representation of an IFD entry (12 bytes).
27#[derive(Debug, Clone, BinWrite)]
28pub struct RawIfdEntry {
29    pub tag: u16,
30    pub typ: u16,
31    pub count: u32,
32    pub value_offset: [u8; 4],
33}
34
35impl IfdEntry {
36    /// Create a SHORT (u16) entry.
37    pub fn short(tag: TiffTag, value: u16) -> Self {
38        Self {
39            tag: tag.as_u16(),
40            typ: TiffType::Short,
41            count: 1,
42            value_bytes: value.to_le_bytes().to_vec(),
43        }
44    }
45
46    /// Create an array of SHORTs.
47    pub fn shorts(tag: TiffTag, values: &[u16]) -> Self {
48        let mut bytes = Vec::with_capacity(values.len() * 2);
49        for v in values {
50            bytes.extend_from_slice(&v.to_le_bytes());
51        }
52        Self {
53            tag: tag.as_u16(),
54            typ: TiffType::Short,
55            count: values.len() as u32,
56            value_bytes: bytes,
57        }
58    }
59
60    /// Create a LONG (u32) entry.
61    pub fn long(tag: TiffTag, value: u32) -> Self {
62        Self {
63            tag: tag.as_u16(),
64            typ: TiffType::Long,
65            count: 1,
66            value_bytes: value.to_le_bytes().to_vec(),
67        }
68    }
69
70    /// Create an array of LONGs.
71    pub fn longs(tag: TiffTag, values: &[u32]) -> Self {
72        let mut bytes = Vec::with_capacity(values.len() * 4);
73        for v in values {
74            bytes.extend_from_slice(&v.to_le_bytes());
75        }
76        Self {
77            tag: tag.as_u16(),
78            typ: TiffType::Long,
79            count: values.len() as u32,
80            value_bytes: bytes,
81        }
82    }
83
84    /// Create a RATIONAL (unsigned) entry.
85    pub fn rational(tag: TiffTag, num: u32, den: u32) -> Self {
86        let mut bytes = Vec::with_capacity(8);
87        bytes.extend_from_slice(&num.to_le_bytes());
88        bytes.extend_from_slice(&den.to_le_bytes());
89        Self {
90            tag: tag.as_u16(),
91            typ: TiffType::Rational,
92            count: 1,
93            value_bytes: bytes,
94        }
95    }
96
97    /// Create an array of RATIONALs.
98    pub fn rationals(tag: TiffTag, values: &[(u32, u32)]) -> Self {
99        let mut bytes = Vec::with_capacity(values.len() * 8);
100        for (num, den) in values {
101            bytes.extend_from_slice(&num.to_le_bytes());
102            bytes.extend_from_slice(&den.to_le_bytes());
103        }
104        Self {
105            tag: tag.as_u16(),
106            typ: TiffType::Rational,
107            count: values.len() as u32,
108            value_bytes: bytes,
109        }
110    }
111
112    /// Create an SRATIONAL (signed) entry.
113    pub fn srational(tag: TiffTag, num: i32, den: i32) -> Self {
114        let mut bytes = Vec::with_capacity(8);
115        bytes.extend_from_slice(&num.to_le_bytes());
116        bytes.extend_from_slice(&den.to_le_bytes());
117        Self {
118            tag: tag.as_u16(),
119            typ: TiffType::SRational,
120            count: 1,
121            value_bytes: bytes,
122        }
123    }
124
125    /// Create an array of SRATIONALs.
126    pub fn srationals(tag: TiffTag, values: &[(i32, i32)]) -> Self {
127        let mut bytes = Vec::with_capacity(values.len() * 8);
128        for (num, den) in values {
129            bytes.extend_from_slice(&num.to_le_bytes());
130            bytes.extend_from_slice(&den.to_le_bytes());
131        }
132        Self {
133            tag: tag.as_u16(),
134            typ: TiffType::SRational,
135            count: values.len() as u32,
136            value_bytes: bytes,
137        }
138    }
139
140    /// Create an ASCII string entry (null-terminated).
141    pub fn ascii(tag: TiffTag, s: &str) -> Self {
142        let mut bytes = s.as_bytes().to_vec();
143        bytes.push(0); // Null terminator
144        Self {
145            tag: tag.as_u16(),
146            typ: TiffType::Ascii,
147            count: bytes.len() as u32,
148            value_bytes: bytes,
149        }
150    }
151
152    /// Create a BYTE array entry.
153    pub fn bytes(tag: TiffTag, data: &[u8]) -> Self {
154        Self {
155            tag: tag.as_u16(),
156            typ: TiffType::Byte,
157            count: data.len() as u32,
158            value_bytes: data.to_vec(),
159        }
160    }
161
162    /// Create an UNDEFINED byte array entry.
163    pub fn undefined(tag: TiffTag, data: &[u8]) -> Self {
164        Self {
165            tag: tag.as_u16(),
166            typ: TiffType::Undefined,
167            count: data.len() as u32,
168            value_bytes: data.to_vec(),
169        }
170    }
171
172    /// Create a DOUBLE array entry.
173    pub fn doubles(tag: TiffTag, values: &[f64]) -> Self {
174        let mut bytes = Vec::with_capacity(values.len() * 8);
175        for v in values {
176            bytes.extend_from_slice(&v.to_le_bytes());
177        }
178        Self {
179            tag: tag.as_u16(),
180            typ: TiffType::Double,
181            count: values.len() as u32,
182            value_bytes: bytes,
183        }
184    }
185
186    /// Size of the value in bytes.
187    fn value_size(&self) -> usize {
188        self.value_bytes.len()
189    }
190
191    /// Whether the value fits inline in the 4-byte value/offset field.
192    fn fits_inline(&self) -> bool {
193        self.value_size() <= 4
194    }
195}
196
197/// Low-level TIFF file writer.
198pub struct TiffWriter<W> {
199    writer: W,
200    byte_order: ByteOrder,
201    position: u64,
202}
203
204impl<W: Write + Seek> TiffWriter<W> {
205    /// Create a new TIFF writer (does not write header yet).
206    pub fn new(writer: W, byte_order: ByteOrder) -> Self {
207        Self {
208            writer,
209            byte_order,
210            position: 0,
211        }
212    }
213
214    /// Convert internal ByteOrder to binrw Endian
215    fn endian(&self) -> Endian {
216        match self.byte_order {
217            ByteOrder::LittleEndian => Endian::Little,
218            ByteOrder::BigEndian => Endian::Big,
219        }
220    }
221
222    /// Write the TIFF header. Returns the offset where IFD0 pointer should go.
223    pub fn write_header(&mut self) -> RawResult<()> {
224        let endian = self.endian();
225
226        // Byte order marker
227        self.writer.write_type(&self.byte_order, endian)?;
228        self.position += 2;
229
230        // Magic number (42 for standard TIFF)
231        self.writer.write_type(&42u16, endian)?;
232        self.position += 2;
233
234        // IFD0 offset placeholder (will be updated later)
235        // For now, write 8 (immediately after header)
236        self.writer.write_type(&8u32, endian)?;
237        self.position += 4;
238
239        Ok(())
240    }
241
242    /// Write an IFD at the current position.
243    /// Returns the offset of the IFD.
244    /// `next_ifd_offset` is the offset to the next IFD (0 if none).
245    pub fn write_ifd(&mut self, entries: &mut [IfdEntry], next_ifd_offset: u32) -> RawResult<u64> {
246        let endian = self.endian();
247
248        // Sort entries by tag number (TIFF spec requirement)
249        entries.sort_by_key(|e| e.tag);
250
251        let ifd_start = self.position;
252        let entry_count = entries.len() as u16;
253
254        // Write entry count
255        self.writer.write_type(&entry_count, endian)?;
256        self.position += 2;
257
258        // Calculate where overflow data will start
259        // IFD: 2 (count) + 12*entries + 4 (next IFD pointer)
260        let overflow_start = ifd_start + 2 + (12 * entries.len() as u64) + 4;
261        let mut overflow_offset = overflow_start;
262
263        // First pass: calculate overflow offsets
264        let mut offsets: Vec<Option<u64>> = Vec::with_capacity(entries.len());
265        for entry in entries.iter() {
266            if entry.fits_inline() {
267                offsets.push(None);
268            } else {
269                offsets.push(Some(overflow_offset));
270                overflow_offset += entry.value_size() as u64;
271                // Pad to word boundary
272                if !overflow_offset.is_multiple_of(2) {
273                    overflow_offset += 1;
274                }
275            }
276        }
277
278        // Write entries
279        for (entry, offset) in entries.iter().zip(offsets.iter()) {
280            // field calculation
281            let val_field: [u8; 4] = if let Some(off) = offset {
282                // It is an offset. Write as u32 in correct endianness.
283                let off_u32 = *off as u32;
284                match endian {
285                    Endian::Little => off_u32.to_le_bytes(),
286                    Endian::Big => off_u32.to_be_bytes(),
287                }
288            } else {
289                // It is inline data.
290                // Data in `entry.value_bytes` is already LE (from `IfdEntry` methods).
291
292                // If we are writing LE file:
293                // We want `entry.value_bytes` padded to 4 bytes.
294                // `IfdEntry::short(5)` -> `[05, 00]`.
295                // Pad -> `[05, 00, 00, 00]`.
296
297                // If we are writing BE file:
298                // `IfdEntry` logic is flawed for BE currently.
299                // If we assume `entry.value_bytes` needs to be BE for BE file.
300                // But `IfdEntry` structs are hardcoded to `to_le_bytes`.
301                //
302                // Let's assume for this specific refactor step (preserving behavior) that we just write `value_bytes` padded.
303                // The previous code did:
304                // `inline[..len].copy_from_slice(&entry.value_bytes[..len]); self.write_bytes(&inline)?;`
305                // It wrote `value_bytes` directly.
306                // So we should replicate that.
307
308                let mut inline = [0u8; 4];
309                let len = entry.value_bytes.len().min(4);
310                inline[..len].copy_from_slice(&entry.value_bytes[..len]);
311                inline
312            };
313
314            let raw_entry = RawIfdEntry {
315                tag: entry.tag,
316                typ: entry.typ as u16,
317                count: entry.count,
318                value_offset: val_field,
319            };
320
321            self.writer.write_type(&raw_entry, endian)?;
322            self.position += 12;
323        }
324
325        // Write next IFD offset
326        self.writer.write_type(&next_ifd_offset, endian)?;
327        self.position += 4;
328
329        // Write overflow data
330        for (entry, offset) in entries.iter().zip(offsets.iter()) {
331            if offset.is_some() {
332                self.write_bytes(&entry.value_bytes)?;
333                // Pad to word boundary
334                if entry.value_bytes.len() % 2 != 0 {
335                    self.write_bytes(&[0])?;
336                }
337            }
338        }
339
340        Ok(ifd_start)
341    }
342
343    /// Write 16-bit RGB image data as a single strip.
344    /// Returns (strip_offset, strip_byte_count).
345    pub fn write_image_strip_rgb16(&mut self, data: &[u16]) -> RawResult<(u64, u64)> {
346        let offset = self.position;
347        let endian = self.endian();
348
349        // Write pixel data element by element
350        // Ideally block writing, but data is &[u16] and need swapping.
351        // We can create a buffer or just loop. Loop is fine for now (buffered writer usually wraps this).
352        for &pixel in data {
353            self.writer.write_type(&pixel, endian)?;
354        }
355        self.position += data.len() as u64 * 2;
356
357        let byte_count = data.len() as u64 * 2;
358        Ok((offset, byte_count))
359    }
360
361    /// Get the current write position.
362    pub fn position(&self) -> u64 {
363        self.position
364    }
365
366    /// Seek to a specific position.
367    pub fn seek_to(&mut self, offset: u64) -> RawResult<()> {
368        self.writer.seek(SeekFrom::Start(offset))?;
369        self.position = offset;
370        Ok(())
371    }
372
373    /// Update the IFD0 offset in the header (at byte 4).
374    pub fn update_ifd0_offset(&mut self, offset: u32) -> RawResult<()> {
375        let current = self.position;
376        self.seek_to(4)?;
377        let endian = self.endian();
378        self.writer.write_type(&offset, endian)?;
379        self.seek_to(current)?;
380        Ok(())
381    }
382
383    /// Finish writing and return the inner writer.
384    pub fn finish(self) -> W {
385        self.writer
386    }
387
388    // --- Helper methods ---
389
390    fn write_bytes(&mut self, data: &[u8]) -> RawResult<()> {
391        self.writer.write_all(data)?;
392        self.position += data.len() as u64;
393        Ok(())
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400    use std::io::Cursor;
401
402    #[test]
403    fn test_ifd_entry_short() {
404        let entry = IfdEntry::short(TiffTag::ImageWidth, 1024);
405        assert_eq!(entry.tag, 0x0100);
406        assert_eq!(entry.count, 1);
407        assert!(entry.fits_inline());
408    }
409
410    #[test]
411    fn test_ifd_entry_ascii() {
412        let entry = IfdEntry::ascii(TiffTag::Make, "SONY");
413        assert_eq!(entry.tag, 0x010F);
414        assert_eq!(entry.count, 5); // "SONY" + null
415        assert!(!entry.fits_inline()); // 5 bytes > 4
416    }
417
418    #[test]
419    fn test_tiff_writer_header() {
420        let mut buffer = Cursor::new(Vec::new());
421        let mut writer = TiffWriter::new(&mut buffer, ByteOrder::LittleEndian);
422        writer.write_header().unwrap();
423
424        let data = buffer.into_inner();
425        assert_eq!(&data[0..2], b"II"); // Little-endian marker
426        assert_eq!(&data[2..4], &[42, 0]); // Magic 42
427        assert_eq!(&data[4..8], &[8, 0, 0, 0]); // IFD0 at offset 8
428    }
429
430    #[test]
431    fn test_write_ifd() {
432        let mut buffer = Cursor::new(Vec::new());
433        let mut writer = TiffWriter::new(&mut buffer, ByteOrder::LittleEndian);
434        writer.write_header().unwrap();
435
436        let mut entries = vec![
437            IfdEntry::short(TiffTag::ImageWidth, 100),
438            IfdEntry::short(TiffTag::ImageLength, 50),
439        ];
440        let ifd_offset = writer.write_ifd(&mut entries, 0).unwrap();
441        assert_eq!(ifd_offset, 8);
442    }
443}