1#![allow(dead_code)]
18
19use super::image_codec::RawDecodeResult;
20
21const TAG_IMAGE_WIDTH: u16 = 0x0100;
24const TAG_IMAGE_LENGTH: u16 = 0x0101;
25const TAG_BITS_PER_SAMPLE: u16 = 0x0102;
26const TAG_COMPRESSION: u16 = 0x0103;
27const TAG_PHOTOMETRIC: u16 = 0x0106;
28const TAG_STRIP_OFFSETS: u16 = 0x0111;
29const TAG_SAMPLES_PER_PIXEL: u16 = 0x0115;
30const TAG_ROWS_PER_STRIP: u16 = 0x0116;
31const TAG_STRIP_BYTE_COUNTS: u16 = 0x0117;
32const TAG_X_RESOLUTION: u16 = 0x011A;
33const TAG_Y_RESOLUTION: u16 = 0x011B;
34const TAG_RESOLUTION_UNIT: u16 = 0x0128;
35
36const TYPE_SHORT: u16 = 3;
39const TYPE_LONG: u16 = 4;
40const TYPE_RATIONAL: u16 = 5;
41
42#[derive(Debug, thiserror::Error)]
46pub enum TiffError {
47 #[error("Invalid TIFF: {0}")]
48 Invalid(String),
49 #[error("Unsupported TIFF feature: {0}")]
50 Unsupported(String),
51 #[error("Truncated data")]
52 Truncated,
53}
54
55pub fn tiff_encode_rgb(width: u32, height: u32, pixels: &[u8]) -> Result<Vec<u8>, TiffError> {
61 let expected = (width as usize) * (height as usize) * 3;
62 if pixels.len() != expected {
63 return Err(TiffError::Invalid(format!(
64 "pixel buffer length {} != expected {}",
65 pixels.len(),
66 expected
67 )));
68 }
69
70 const IFD_ENTRY_COUNT: u16 = 12;
77 const IFD_OFFSET: u32 = 8;
78 const IFD_SIZE: u32 = 2 + 12 * (IFD_ENTRY_COUNT as u32) + 4; const EXTRA_OFFSET: u32 = IFD_OFFSET + IFD_SIZE; const BPS_OFFSET: u32 = EXTRA_OFFSET; const XRES_OFFSET: u32 = EXTRA_OFFSET + 6; const YRES_OFFSET: u32 = XRES_OFFSET + 8; const PIXEL_OFFSET: u32 = YRES_OFFSET + 8; let pixel_byte_count: u32 = width * height * 3;
86 let total_size = (PIXEL_OFFSET as usize) + (pixel_byte_count as usize);
87
88 let mut buf: Vec<u8> = Vec::with_capacity(total_size);
89
90 buf.extend_from_slice(b"II"); buf.extend_from_slice(&42u16.to_le_bytes()); buf.extend_from_slice(&IFD_OFFSET.to_le_bytes()); buf.extend_from_slice(&IFD_ENTRY_COUNT.to_le_bytes());
97
98 let write_entry = |out: &mut Vec<u8>, tag: u16, typ: u16, count: u32, val: u32| {
100 out.extend_from_slice(&tag.to_le_bytes());
101 out.extend_from_slice(&typ.to_le_bytes());
102 out.extend_from_slice(&count.to_le_bytes());
103 out.extend_from_slice(&val.to_le_bytes());
104 };
105
106 write_entry(&mut buf, TAG_IMAGE_WIDTH, TYPE_LONG, 1, width);
108 write_entry(&mut buf, TAG_IMAGE_LENGTH, TYPE_LONG, 1, height);
109 write_entry(&mut buf, TAG_BITS_PER_SAMPLE, TYPE_SHORT, 3, BPS_OFFSET); write_entry(&mut buf, TAG_COMPRESSION, TYPE_SHORT, 1, 1); write_entry(&mut buf, TAG_PHOTOMETRIC, TYPE_SHORT, 1, 2); write_entry(&mut buf, TAG_STRIP_OFFSETS, TYPE_LONG, 1, PIXEL_OFFSET);
113 write_entry(&mut buf, TAG_SAMPLES_PER_PIXEL, TYPE_SHORT, 1, 3);
114 write_entry(&mut buf, TAG_ROWS_PER_STRIP, TYPE_LONG, 1, height);
115 write_entry(
116 &mut buf,
117 TAG_STRIP_BYTE_COUNTS,
118 TYPE_LONG,
119 1,
120 pixel_byte_count,
121 );
122 write_entry(&mut buf, TAG_X_RESOLUTION, TYPE_RATIONAL, 1, XRES_OFFSET);
123 write_entry(&mut buf, TAG_Y_RESOLUTION, TYPE_RATIONAL, 1, YRES_OFFSET);
124 write_entry(&mut buf, TAG_RESOLUTION_UNIT, TYPE_SHORT, 1, 2); buf.extend_from_slice(&0u32.to_le_bytes());
128
129 buf.extend_from_slice(&8u16.to_le_bytes());
132 buf.extend_from_slice(&8u16.to_le_bytes());
133 buf.extend_from_slice(&8u16.to_le_bytes());
134
135 buf.extend_from_slice(&72u32.to_le_bytes());
137 buf.extend_from_slice(&1u32.to_le_bytes());
138
139 buf.extend_from_slice(&72u32.to_le_bytes());
141 buf.extend_from_slice(&1u32.to_le_bytes());
142
143 buf.extend_from_slice(pixels);
145
146 debug_assert_eq!(buf.len(), total_size);
147 Ok(buf)
148}
149
150pub fn tiff_decode(bytes: &[u8]) -> Result<RawDecodeResult, TiffError> {
156 if bytes.len() < 8 {
157 return Err(TiffError::Truncated);
158 }
159
160 let little_endian = match &bytes[0..2] {
162 b"II" => true,
163 b"MM" => false,
164 _ => {
165 return Err(TiffError::Invalid(format!(
166 "unrecognised byte-order marker: {:02X} {:02X}",
167 bytes[0], bytes[1]
168 )))
169 }
170 };
171
172 let read_u16 = |off: usize| -> Result<u16, TiffError> {
173 bytes
174 .get(off..off + 2)
175 .and_then(|s| s.try_into().ok())
176 .map(|a: [u8; 2]| {
177 if little_endian {
178 u16::from_le_bytes(a)
179 } else {
180 u16::from_be_bytes(a)
181 }
182 })
183 .ok_or(TiffError::Truncated)
184 };
185
186 let read_u32 = |off: usize| -> Result<u32, TiffError> {
187 bytes
188 .get(off..off + 4)
189 .and_then(|s| s.try_into().ok())
190 .map(|a: [u8; 4]| {
191 if little_endian {
192 u32::from_le_bytes(a)
193 } else {
194 u32::from_be_bytes(a)
195 }
196 })
197 .ok_or(TiffError::Truncated)
198 };
199
200 let magic = read_u16(2)?;
202 if magic != 42 {
203 return Err(TiffError::Invalid(format!("bad TIFF magic: {}", magic)));
204 }
205
206 let ifd_offset = read_u32(4)? as usize;
207
208 let entry_count = read_u16(ifd_offset)? as usize;
210 let entries_start = ifd_offset + 2;
211 if bytes.len() < entries_start + entry_count * 12 {
212 return Err(TiffError::Truncated);
213 }
214
215 let read_entry = |n: usize| -> Result<(u16, u16, u32, u32), TiffError> {
217 let base = entries_start + n * 12;
218 let tag = read_u16(base)?;
219 let typ = read_u16(base + 2)?;
220 let count = read_u32(base + 4)?;
221 let val = read_u32(base + 8)?;
222 Ok((tag, typ, count, val))
223 };
224
225 let mut width: Option<u32> = None;
227 let mut height: Option<u32> = None;
228 let mut compression: Option<u32> = None;
229 let mut samples_per_pixel: Option<u32> = None;
230 let mut strip_offsets: Option<u32> = None;
231 let mut strip_byte_counts: Option<u32> = None;
232 let mut bits_per_sample_info: Option<(u16, u32, u32)> = None; for i in 0..entry_count {
235 let (tag, typ, count, val) = read_entry(i)?;
236 match tag {
237 TAG_IMAGE_WIDTH => width = Some(val),
238 TAG_IMAGE_LENGTH => height = Some(val),
239 TAG_BITS_PER_SAMPLE => bits_per_sample_info = Some((typ, count, val)),
240 TAG_COMPRESSION => compression = Some(val),
241 TAG_STRIP_OFFSETS => strip_offsets = Some(val),
242 TAG_SAMPLES_PER_PIXEL => samples_per_pixel = Some(val),
243 TAG_STRIP_BYTE_COUNTS => strip_byte_counts = Some(val),
244 _ => {}
245 }
246 }
247
248 let width = width.ok_or_else(|| TiffError::Invalid("missing ImageWidth".into()))? as usize;
249 let height = height.ok_or_else(|| TiffError::Invalid("missing ImageLength".into()))? as usize;
250 let compression =
251 compression.ok_or_else(|| TiffError::Invalid("missing Compression".into()))?;
252 let spp =
253 samples_per_pixel.ok_or_else(|| TiffError::Invalid("missing SamplesPerPixel".into()))?;
254 let strip_offset =
255 strip_offsets.ok_or_else(|| TiffError::Invalid("missing StripOffsets".into()))? as usize;
256 let strip_bytes = strip_byte_counts
257 .ok_or_else(|| TiffError::Invalid("missing StripByteCounts".into()))?
258 as usize;
259
260 if compression != 1 {
261 return Err(TiffError::Unsupported(format!(
262 "compression={} (only 1/no-compression supported)",
263 compression
264 )));
265 }
266 if spp != 3 {
267 return Err(TiffError::Unsupported(format!(
268 "SamplesPerPixel={} (only 3/RGB supported)",
269 spp
270 )));
271 }
272
273 if let Some((bps_type, bps_count, bps_val)) = bits_per_sample_info {
275 if bps_count != 3 {
276 return Err(TiffError::Unsupported(format!(
277 "BitsPerSample count={} (expected 3)",
278 bps_count
279 )));
280 }
281 if bps_type == TYPE_SHORT {
284 let off = bps_val as usize;
285 if bytes.len() < off + 6 {
286 return Err(TiffError::Truncated);
287 }
288 let b0 = read_u16(off)?;
289 let b1 = read_u16(off + 2)?;
290 let b2 = read_u16(off + 4)?;
291 if b0 != 8 || b1 != 8 || b2 != 8 {
292 return Err(TiffError::Unsupported(format!(
293 "BitsPerSample=[{},{},{}] (only 8,8,8 supported)",
294 b0, b1, b2
295 )));
296 }
297 }
298 }
299
300 let expected_bytes = width * height * 3;
302 if strip_bytes < expected_bytes {
303 return Err(TiffError::Invalid(format!(
304 "StripByteCounts={} < expected={}",
305 strip_bytes, expected_bytes
306 )));
307 }
308 if bytes.len() < strip_offset + expected_bytes {
309 return Err(TiffError::Truncated);
310 }
311
312 let pixels = bytes[strip_offset..strip_offset + expected_bytes].to_vec();
313
314 Ok(RawDecodeResult {
315 width,
316 height,
317 pixels,
318 })
319}
320
321#[cfg(test)]
324mod tests {
325 use super::*;
326
327 fn make_gradient(w: usize, h: usize) -> Vec<u8> {
329 let mut pixels = Vec::with_capacity(w * h * 3);
330 for y in 0..h {
331 for x in 0..w {
332 pixels.push(((x * 255) / w.max(1)) as u8); pixels.push(((y * 255) / h.max(1)) as u8); pixels.push(128u8); }
336 }
337 pixels
338 }
339
340 #[test]
341 fn test_tiff_header_le() {
342 let pixels = vec![200u8; 4 * 4 * 3];
343 let encoded = tiff_encode_rgb(4, 4, &pixels).expect("encode must succeed");
344 assert!(
345 encoded.starts_with(b"II\x2A\x00"),
346 "must start with II (LE) + magic 42"
347 );
348 }
349
350 #[test]
351 fn test_tiff_roundtrip_exact() {
352 let pixels: Vec<u8> = (0u8..=255u8).cycle().take(4 * 4 * 3).collect();
353 let encoded = tiff_encode_rgb(4, 4, &pixels).expect("encode");
354 let decoded = tiff_decode(&encoded).expect("decode");
355 assert_eq!(decoded.width, 4);
356 assert_eq!(decoded.height, 4);
357 assert_eq!(
358 decoded.pixels, pixels,
359 "round-tripped pixels must be identical"
360 );
361 }
362
363 #[test]
364 fn test_tiff_dimensions() {
365 let pixels = vec![0u8; 10 * 6 * 3];
366 let encoded = tiff_encode_rgb(10, 6, &pixels).expect("encode");
367 let decoded = tiff_decode(&encoded).expect("decode");
368 assert_eq!(decoded.width, 10);
369 assert_eq!(decoded.height, 6);
370 }
371
372 #[test]
373 fn test_tiff_decode_invalid_returns_error() {
374 let result = tiff_decode(b"not a tiff file at all 12345678");
375 assert!(result.is_err(), "invalid data must return an error");
376 }
377
378 #[test]
379 fn test_tiff_roundtrip_gradient() {
380 let src = make_gradient(8, 8);
381 let encoded = tiff_encode_rgb(8, 8, &src).expect("encode");
382 let decoded = tiff_decode(&encoded).expect("decode");
383 assert_eq!(decoded.width, 8);
384 assert_eq!(decoded.height, 8);
385 assert_eq!(decoded.pixels, src, "gradient round-trip must be exact");
386 }
387
388 #[test]
389 fn test_tiff_wrong_pixel_buffer_size_returns_error() {
390 let result = tiff_encode_rgb(4, 4, &[0u8; 10]);
392 assert!(result.is_err());
393 }
394
395 #[test]
396 fn test_tiff_truncated_returns_error() {
397 let pixels = vec![0u8; 4 * 4 * 3];
398 let encoded = tiff_encode_rgb(4, 4, &pixels).expect("encode");
399 let result = tiff_decode(&encoded[..6]);
401 assert!(result.is_err());
402 }
403}