1use gamut_core::{
4 Bilevel, Cmyk8, Dimensions, EncodeImage, Error, Gray8, ImageRef, Indexed8, Result, Rgb8, Rgba8,
5};
6
7use crate::compression::{Compression, ccitt, lzw, packbits, predictor};
8use crate::ifd::{PhotometricInterpretation, Predictor};
9use crate::palette::Palette8;
10use crate::{tags, writer};
11use gamut_ifd::{ByteOrder, Ifd, Value, Variant};
12
13struct SampleLayout {
15 spp: usize,
16 bits_per_sample: u16,
17 stored_row_bytes: usize,
18 photometric: PhotometricInterpretation,
19}
20
21#[derive(Debug, Clone)]
28pub struct TiffEncoder {
29 order: ByteOrder,
30 compression: Compression,
31 predictor: Predictor,
32 tiling: Option<(u32, u32)>,
33 big_tiff: bool,
34}
35
36impl Default for TiffEncoder {
37 fn default() -> Self {
38 Self {
39 order: ByteOrder::LittleEndian,
40 compression: Compression::None,
41 predictor: Predictor::None,
42 tiling: None,
43 big_tiff: false,
44 }
45 }
46}
47
48impl TiffEncoder {
49 #[must_use]
51 pub fn new() -> Self {
52 Self::default()
53 }
54
55 #[must_use]
57 pub fn with_byte_order(mut self, order: ByteOrder) -> Self {
58 self.order = order;
59 self
60 }
61
62 #[must_use]
64 pub fn with_compression(mut self, compression: Compression) -> Self {
65 self.compression = compression;
66 self
67 }
68
69 #[must_use]
73 pub fn with_predictor(mut self, predictor: Predictor) -> Self {
74 self.predictor = predictor;
75 self
76 }
77
78 #[must_use]
84 pub fn with_tiling(mut self, tile_width: u32, tile_height: u32) -> Self {
85 self.tiling = Some((tile_width, tile_height));
86 self
87 }
88
89 #[must_use]
97 pub fn with_big_tiff(mut self, big_tiff: bool) -> Self {
98 self.big_tiff = big_tiff;
99 self
100 }
101
102 fn variant(&self) -> Variant {
104 if self.big_tiff {
105 Variant::Big
106 } else {
107 Variant::Classic
108 }
109 }
110
111 pub fn encode_palette8(
119 &self,
120 indices: ImageRef<'_, Indexed8>,
121 palette: &Palette8,
122 out: &mut Vec<u8>,
123 ) -> Result<usize> {
124 let w = indices.width() as usize;
125 let colormap = palette.to_tiff_colormap();
126 self.encode_packed(
127 indices.as_samples(),
128 indices.dimensions(),
129 &SampleLayout {
130 spp: 1,
131 bits_per_sample: 8,
132 stored_row_bytes: w,
133 photometric: PhotometricInterpretation::Palette,
134 },
135 &[(tags::COLOR_MAP, Value::Short(colormap))],
136 out,
137 )
138 }
139
140 fn encode_8bit(
141 &self,
142 pixels: &[u8],
143 dims: Dimensions,
144 spp: usize,
145 photometric: PhotometricInterpretation,
146 out: &mut Vec<u8>,
147 ) -> Result<usize> {
148 let row_bytes = dims.width as usize * spp;
151 debug_assert_eq!(pixels.len(), row_bytes * dims.height as usize);
152 self.encode_packed(
153 pixels,
154 dims,
155 &SampleLayout {
156 spp,
157 bits_per_sample: 8,
158 stored_row_bytes: row_bytes,
159 photometric,
160 },
161 &[],
162 out,
163 )
164 }
165
166 fn encode_packed(
169 &self,
170 packed: &[u8],
171 dims: Dimensions,
172 layout: &SampleLayout,
173 extra_fields: &[(u16, Value)],
174 out: &mut Vec<u8>,
175 ) -> Result<usize> {
176 if let Some((tw, tl)) = self.tiling {
177 return self.encode_tiled(packed, dims, layout, extra_fields, tw, tl, out);
178 }
179 let (ifd, strips) = self.build_strip_image(packed, dims, layout, extra_fields)?;
180 let bytes = writer::write_image(self.order, self.variant(), &ifd, &strips);
181 out.extend_from_slice(&bytes);
182 Ok(bytes.len())
183 }
184
185 fn build_strip_image(
188 &self,
189 packed: &[u8],
190 dims: Dimensions,
191 layout: &SampleLayout,
192 extra_fields: &[(u16, Value)],
193 ) -> Result<(Ifd, Vec<Vec<u8>>)> {
194 let h = dims.height as usize;
195 let stored_row_bytes = layout.stored_row_bytes;
196
197 let predicting = self.predictor == Predictor::HorizontalDifferencing;
199 if predicting && layout.bits_per_sample != 8 {
200 return Err(Error::Unsupported("TIFF: predictor requires 8-bit samples"));
201 }
202 let predicted = predicting.then(|| {
203 let mut buf = packed.to_vec();
204 predictor::forward(&mut buf, stored_row_bytes, layout.spp);
205 buf
206 });
207 let packed: &[u8] = predicted.as_deref().unwrap_or(packed);
208
209 let rows_per_strip = (8192 / stored_row_bytes.max(1)).clamp(1, h);
211 let mut strips: Vec<Vec<u8>> = Vec::new();
212 let mut row = 0;
213 while row < h {
214 let rows = rows_per_strip.min(h - row);
215 let start = row * stored_row_bytes;
216 let raw = &packed[start..start + rows * stored_row_bytes];
217 strips.push(self.compress_strip(raw, dims, layout)?);
218 row += rows;
219 }
220
221 let mut ifd = Ifd::new();
222 ifd.set(tags::IMAGE_WIDTH, dim_value(dims.width));
223 ifd.set(tags::IMAGE_LENGTH, dim_value(dims.height));
224 ifd.set(
225 tags::BITS_PER_SAMPLE,
226 Value::Short(vec![layout.bits_per_sample; layout.spp]),
227 );
228 ifd.set(
229 tags::COMPRESSION,
230 Value::Short(vec![self.compression.code()]),
231 );
232 ifd.set(
233 tags::PHOTOMETRIC_INTERPRETATION,
234 Value::Short(vec![layout.photometric.code()]),
235 );
236 ifd.set(
237 tags::SAMPLES_PER_PIXEL,
238 Value::Short(vec![layout.spp as u16]),
239 );
240 ifd.set(tags::ROWS_PER_STRIP, dim_value(rows_per_strip as u32));
241 ifd.set(tags::X_RESOLUTION, Value::Rational(vec![(72, 1)]));
242 ifd.set(tags::Y_RESOLUTION, Value::Rational(vec![(72, 1)]));
243 ifd.set(tags::RESOLUTION_UNIT, Value::Short(vec![2])); if predicting {
245 ifd.set(tags::PREDICTOR, Value::Short(vec![2]));
246 }
247 for (tag, value) in extra_fields {
248 ifd.set(*tag, value.clone());
249 }
250 Ok((ifd, strips))
251 }
252
253 pub fn encode_pages_rgb8(
261 &self,
262 pages: &[ImageRef<'_, Rgb8>],
263 out: &mut Vec<u8>,
264 ) -> Result<usize> {
265 if pages.is_empty() {
266 return Err(Error::InvalidInput("TIFF: no pages to encode"));
267 }
268 let total = pages.len() as u16;
269 let mut images: Vec<(Ifd, Vec<Vec<u8>>)> = Vec::with_capacity(pages.len());
270 for (i, page) in pages.iter().enumerate() {
271 let row_bytes = page.width() as usize * 3;
272 let extra = [
273 (tags::NEW_SUBFILE_TYPE, Value::Long(vec![2])), (tags::PAGE_NUMBER, Value::Short(vec![i as u16, total])),
275 ];
276 images.push(self.build_strip_image(
277 page.as_samples(),
278 page.dimensions(),
279 &SampleLayout {
280 spp: 3,
281 bits_per_sample: 8,
282 stored_row_bytes: row_bytes,
283 photometric: PhotometricInterpretation::Rgb,
284 },
285 &extra,
286 )?);
287 }
288 let bytes = writer::write_multipage(self.order, self.variant(), &images);
289 out.extend_from_slice(&bytes);
290 Ok(bytes.len())
291 }
292
293 fn compress_strip(
295 &self,
296 raw: &[u8],
297 dims: Dimensions,
298 layout: &SampleLayout,
299 ) -> Result<Vec<u8>> {
300 let row_bytes = layout.stored_row_bytes;
301 match self.compression {
302 Compression::CcittRle => {
303 if layout.bits_per_sample != 1 {
304 return Err(Error::Unsupported(
305 "TIFF: Modified Huffman requires a bilevel image",
306 ));
307 }
308 ccitt::mh_encode_strip(raw, row_bytes, dims.width as usize)
309 }
310 Compression::CcittGroup4Fax => {
311 if layout.bits_per_sample != 1 {
312 return Err(Error::Unsupported(
313 "TIFF: Group 4 fax requires a bilevel image",
314 ));
315 }
316 let rows = raw.len() / row_bytes;
317 ccitt::g4_encode_strip(raw, row_bytes, rows, dims.width as usize)
318 }
319 _ => self.compress_bytes(raw, row_bytes),
320 }
321 }
322
323 fn compress_bytes(&self, raw: &[u8], row_bytes: usize) -> Result<Vec<u8>> {
325 match self.compression {
326 Compression::None => Ok(raw.to_vec()),
327 Compression::PackBits => {
328 let mut out = Vec::new();
329 for row in raw.chunks(row_bytes) {
330 packbits::encode_row(row, &mut out);
331 }
332 Ok(out)
333 }
334 Compression::Lzw => Ok(lzw::encode(raw)),
335 _ => Err(Error::Unsupported(
336 "TIFF: unsupported compression for encoding",
337 )),
338 }
339 }
340
341 #[allow(clippy::too_many_arguments)]
343 fn encode_tiled(
344 &self,
345 packed: &[u8],
346 dims: Dimensions,
347 layout: &SampleLayout,
348 extra_fields: &[(u16, Value)],
349 tile_w: u32,
350 tile_h: u32,
351 out: &mut Vec<u8>,
352 ) -> Result<usize> {
353 if layout.bits_per_sample != 8 {
354 return Err(Error::Unsupported(
355 "TIFF: tiling supported only for 8-bit images so far",
356 ));
357 }
358 if self.predictor != Predictor::None {
359 return Err(Error::Unsupported(
360 "TIFF: predictor with tiling not supported yet",
361 ));
362 }
363 let (tw, th) = (tile_w as usize, tile_h as usize);
364 if tw == 0 || th == 0 || tw % 16 != 0 || th % 16 != 0 {
365 return Err(Error::InvalidInput(
366 "TIFF: tile dimensions must be positive multiples of 16",
367 ));
368 }
369 let (w, h, spp) = (dims.width as usize, dims.height as usize, layout.spp);
370 let stored_row_bytes = layout.stored_row_bytes;
371 let tile_row_bytes = tw * spp;
372 let tiles_across = w.div_ceil(tw);
373 let tiles_down = h.div_ceil(th);
374
375 let mut tiles: Vec<Vec<u8>> = Vec::with_capacity(tiles_across * tiles_down);
376 for ty in 0..tiles_down {
377 for tx in 0..tiles_across {
378 let mut tile = vec![0u8; th * tile_row_bytes];
379 for r in 0..th {
380 let src_row = ty * th + r;
381 if src_row >= h {
382 break;
383 }
384 let copy_cols = tw.min(w - tx * tw);
385 let src = (src_row * stored_row_bytes) + (tx * tw) * spp;
386 let dst = r * tile_row_bytes;
387 tile[dst..dst + copy_cols * spp]
388 .copy_from_slice(&packed[src..src + copy_cols * spp]);
389 }
390 tiles.push(self.compress_bytes(&tile, tile_row_bytes)?);
391 }
392 }
393
394 let mut ifd = Ifd::new();
395 ifd.set(tags::IMAGE_WIDTH, dim_value(dims.width));
396 ifd.set(tags::IMAGE_LENGTH, dim_value(dims.height));
397 ifd.set(
398 tags::BITS_PER_SAMPLE,
399 Value::Short(vec![layout.bits_per_sample; spp]),
400 );
401 ifd.set(
402 tags::COMPRESSION,
403 Value::Short(vec![self.compression.code()]),
404 );
405 ifd.set(
406 tags::PHOTOMETRIC_INTERPRETATION,
407 Value::Short(vec![layout.photometric.code()]),
408 );
409 ifd.set(tags::SAMPLES_PER_PIXEL, Value::Short(vec![spp as u16]));
410 ifd.set(tags::TILE_WIDTH, dim_value(tile_w));
411 ifd.set(tags::TILE_LENGTH, dim_value(tile_h));
412 ifd.set(tags::X_RESOLUTION, Value::Rational(vec![(72, 1)]));
413 ifd.set(tags::Y_RESOLUTION, Value::Rational(vec![(72, 1)]));
414 ifd.set(tags::RESOLUTION_UNIT, Value::Short(vec![2])); for (tag, value) in extra_fields {
416 ifd.set(*tag, value.clone());
417 }
418
419 let bytes = writer::write_image_tiled(self.order, self.variant(), &ifd, &tiles);
420 out.extend_from_slice(&bytes);
421 Ok(bytes.len())
422 }
423}
424
425impl EncodeImage<Gray8> for TiffEncoder {
426 fn encode_image(&self, image: ImageRef<'_, Gray8>, out: &mut Vec<u8>) -> Result<usize> {
427 self.encode_8bit(
428 image.as_samples(),
429 image.dimensions(),
430 1,
431 PhotometricInterpretation::BlackIsZero,
432 out,
433 )
434 }
435}
436
437impl EncodeImage<Rgb8> for TiffEncoder {
438 fn encode_image(&self, image: ImageRef<'_, Rgb8>, out: &mut Vec<u8>) -> Result<usize> {
439 self.encode_8bit(
440 image.as_samples(),
441 image.dimensions(),
442 3,
443 PhotometricInterpretation::Rgb,
444 out,
445 )
446 }
447}
448
449impl EncodeImage<Cmyk8> for TiffEncoder {
450 fn encode_image(&self, image: ImageRef<'_, Cmyk8>, out: &mut Vec<u8>) -> Result<usize> {
452 self.encode_8bit(
453 image.as_samples(),
454 image.dimensions(),
455 4,
456 PhotometricInterpretation::Cmyk,
457 out,
458 )
459 }
460}
461
462impl EncodeImage<Rgba8> for TiffEncoder {
463 fn encode_image(&self, image: ImageRef<'_, Rgba8>, out: &mut Vec<u8>) -> Result<usize> {
465 let row_bytes = image.width() as usize * 4;
466 self.encode_packed(
467 image.as_samples(),
468 image.dimensions(),
469 &SampleLayout {
470 spp: 4,
471 bits_per_sample: 8,
472 stored_row_bytes: row_bytes,
473 photometric: PhotometricInterpretation::Rgb,
474 },
475 &[(tags::EXTRA_SAMPLES, Value::Short(vec![2]))],
476 out,
477 )
478 }
479}
480
481impl EncodeImage<Bilevel> for TiffEncoder {
482 fn encode_image(&self, image: ImageRef<'_, Bilevel>, out: &mut Vec<u8>) -> Result<usize> {
484 let (w, h) = (image.width() as usize, image.height() as usize);
485 let pixels = image.as_samples();
486 let stored_row_bytes = w.div_ceil(8);
487 let mut packed = vec![0u8; stored_row_bytes * h];
488 for y in 0..h {
489 let row = &pixels[y * w..(y + 1) * w];
490 let dst = &mut packed[y * stored_row_bytes..(y + 1) * stored_row_bytes];
491 for (x, &p) in row.iter().enumerate() {
492 if p != 0 {
493 dst[x / 8] |= 0x80 >> (x % 8);
494 }
495 }
496 }
497 self.encode_packed(
498 &packed,
499 image.dimensions(),
500 &SampleLayout {
501 spp: 1,
502 bits_per_sample: 1,
503 stored_row_bytes,
504 photometric: PhotometricInterpretation::BlackIsZero,
505 },
506 &[],
507 out,
508 )
509 }
510}
511
512fn dim_value(n: u32) -> Value {
514 if n <= u32::from(u16::MAX) {
515 Value::Short(vec![n as u16])
516 } else {
517 Value::Long(vec![n])
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn image_ref_rejects_mismatched_buffer() {
527 let dims = Dimensions {
530 width: 2,
531 height: 2,
532 };
533 assert!(ImageRef::<Rgb8>::new(&[0; 11], dims).is_err());
534 assert!(ImageRef::<Gray8>::new(&[0; 3], dims).is_err());
535 assert!(ImageRef::<Bilevel>::new(&[0; 3], dims).is_err());
536 assert!(
537 ImageRef::<Rgb8>::new(
538 &[],
539 Dimensions {
540 width: 0,
541 height: 1
542 }
543 )
544 .is_err()
545 );
546 }
547
548 #[test]
549 fn writes_a_well_formed_header() {
550 let enc = TiffEncoder::new();
551 let mut out = Vec::new();
552 let n = enc
553 .encode_image(
554 ImageRef::<Rgb8>::new(
555 &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
556 Dimensions {
557 width: 2,
558 height: 2,
559 },
560 )
561 .unwrap(),
562 &mut out,
563 )
564 .expect("encode");
565 assert_eq!(n, out.len());
566 assert_eq!(&out[0..2], b"II");
567 assert_eq!(out[2], 42);
569 }
570
571 #[test]
572 fn with_big_tiff_emits_bigtiff_header() {
573 let mut out = Vec::new();
574 TiffEncoder::new()
575 .with_big_tiff(true)
576 .encode_image(
577 ImageRef::<Rgb8>::new(
578 &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
579 Dimensions {
580 width: 2,
581 height: 2,
582 },
583 )
584 .unwrap(),
585 &mut out,
586 )
587 .expect("encode");
588 let (order, variant, first) = gamut_ifd::read_header(&out).expect("header");
590 assert_eq!(order, ByteOrder::LittleEndian);
591 assert_eq!(variant, Variant::Big);
592 assert_eq!(out[2], 0x2b);
593 assert!(first >= 16);
594 }
595}