1use otf_pixels_core::{
31 Codec, DecodeCapability, Decoder, Format, ImageDescriptor, Limits, Orientation, PixelFormat,
32 PixelsError, Region, Result, Source, TileMut,
33};
34
35use crate::ifd::{ByteOrder, Directory, directory_chain, parse_header, probe as probe_header};
36use crate::image::{Compression, Layout, Photometric, TiffImage};
37use otf_pixels_core::exif::Exif;
38use otf_pixels_core::{Density, ResolutionUnit, SourceInfo};
39
40#[derive(Debug)]
42pub struct TiffDecoder {
43 data: Vec<u8>,
44 image: TiffImage,
45 pages: u32,
47 row: u32,
48 cached: Option<(usize, Vec<u8>)>,
54 exif: Option<Vec<u8>>,
56 xmp: Option<Vec<u8>>,
58 info: SourceInfo,
60}
61
62pub(crate) const fn is_metadata_tag(tag: u16) -> bool {
66 matches!(
67 tag,
68 269 | 270
69 | 271
70 | 272
71 | 274
72 | 282
73 | 283
74 | 285
75 | 296
76 | 305
77 | 306
78 | 315
79 | 316
80 | 318
81 | 319
82 | 33_432
83 )
84}
85
86impl TiffDecoder {
87 pub fn new<S: Source>(source: S, limits: Limits) -> Result<Self> {
96 Self::with_page(source, limits, 0)
97 }
98
99 pub fn with_page<S: Source>(mut source: S, limits: Limits, page: u32) -> Result<Self> {
111 let mut data = Vec::new();
114 let mut buffer = vec![0_u8; 256 * 1024];
115 loop {
116 let read = source.read(&mut buffer)?;
117 if read == 0 {
118 break;
119 }
120 let Some(chunk) = buffer.get(..read) else {
121 break;
122 };
123 data.extend_from_slice(chunk);
124 }
125
126 let (order, first_ifd) = parse_header(&data)?;
127 let chain = directory_chain(&data, order, first_ifd);
128 let pages = u32::try_from(chain.len()).unwrap_or(u32::MAX).max(1);
131 let offset = match page {
132 0 => first_ifd,
133 _ => chain.get(page as usize).copied().ok_or_else(|| {
134 PixelsError::invalid_argument(
135 "page",
136 format!("page {page} was asked for; the file has {pages} (0 is the first)"),
137 )
138 })?,
139 };
140 let directory = Directory::parse(&data, order, offset)?;
141 let image = TiffImage::from_directory(&directory, order, &limits)?;
142
143 let tree = Exif::parse_directory(&data, Some(offset));
145 let xmp = tree
146 .as_ref()
147 .and_then(|t| t.get(700))
148 .map(|e| e.data.clone());
149 let mut info = SourceInfo::default();
150 if let Some(t) = &tree {
151 info.density = match (t.rational(282), t.integer(296).unwrap_or(2)) {
152 (Some(x), unit @ (2 | 3)) if x > 0.0 => Some(Density {
153 x,
154 y: t.rational(283).filter(|&y| y > 0.0).unwrap_or(x),
155 unit: if unit == 3 {
156 ResolutionUnit::Centimetre
157 } else {
158 ResolutionUnit::Inch
159 },
160 }),
161 _ => None,
162 };
163 }
164 let exif = tree
165 .map(|t| t.retain(is_metadata_tag))
166 .filter(|t| !t.ifd0.is_empty())
167 .map(|t| t.to_bytes());
168 info.bits_per_sample = u8::try_from(image.bits_per_sample).ok();
169 info.palette = image.photometric == Photometric::Palette;
170 info.compression = Some(match image.compression {
171 Compression::None => "none",
172 Compression::Lzw => "lzw",
173 Compression::Deflate => "deflate",
174 Compression::PackBits => "packbits",
175 });
176
177 Ok(Self {
178 data,
179 image,
180 pages,
181 row: 0,
182 cached: None,
183 exif,
184 xmp,
185 info,
186 })
187 }
188
189 #[must_use]
191 pub const fn image(&self) -> &TiffImage {
192 &self.image
193 }
194
195 #[must_use]
197 pub const fn byte_order(&self) -> ByteOrder {
198 self.image.order
199 }
200
201 fn ensure_chunk(&mut self, index: usize) -> Result<()> {
203 if matches!(&self.cached, Some((cached, _)) if *cached == index) {
204 return Ok(());
205 }
206 let decoded = self.image.read_chunk(&self.data, index)?;
207 self.cached = Some((index, decoded));
208 Ok(())
209 }
210
211 fn blit_chunk(
213 &mut self,
214 column: u32,
215 chunk_row: u32,
216 region: Region,
217 out: &mut TileMut<'_>,
218 ) -> Result<()> {
219 let across = self.image.chunks_across();
220 let index = (chunk_row * across + column) as usize;
221 if index >= self.image.offsets.len() {
222 return Err(PixelsError::malformed(
223 "tiff",
224 format!(
225 "chunk {index} is beyond the {} declared",
226 self.image.offsets.len()
227 ),
228 ));
229 }
230
231 self.ensure_chunk(index)?;
232 let Self { image, cached, .. } = self;
238 let Some((_, data)) = cached.as_ref() else {
239 return Err(PixelsError::graph("tiff chunk vanished after decoding"));
240 };
241
242 let area = image.chunk_region(column, chunk_row);
243 let (stored_width, _) = image.chunk_stored_size();
244 let stored_row_bytes = image.chunk_row_bytes();
245
246 let left = area.x.max(region.x);
248 let top = area.y.max(region.y);
249 let right = (area.x + area.width).min(region.x + region.width);
250 let bottom = (area.y + area.height).min(region.y + region.height);
251 if right <= left || bottom <= top {
252 return Ok(());
253 }
254
255 let mut expanded =
256 vec![0_u8; (right - left) as usize * image.descriptor.pixel.bytes_per_pixel()];
257 for y in top..bottom {
258 let within = (y - area.y) as usize;
259 let start = within * stored_row_bytes;
260 let Some(stored) = data.get(start..start + stored_row_bytes) else {
261 continue;
262 };
263 expand_row(
264 image,
265 stored,
266 (left - area.x) as usize,
267 (right - left) as usize,
268 stored_width as usize,
269 &mut expanded,
270 );
271 let Some(target) = out.row_mut(y) else {
272 continue;
273 };
274 let bpp = image.descriptor.pixel.bytes_per_pixel();
275 let at = (left - region.x) as usize * bpp;
276 let Some(slot) = target.get_mut(at..at + expanded.len()) else {
277 continue;
278 };
279 slot.copy_from_slice(&expanded);
280 }
281 Ok(())
282 }
283
284 fn read_region_into(&mut self, region: Region, out: &mut TileMut<'_>) -> Result<()> {
286 if region.x + region.width > self.image.descriptor.width
287 || region.y + region.height > self.image.descriptor.height
288 {
289 return Err(PixelsError::invalid_argument(
290 "region",
291 format!(
292 "{region} is outside a {}x{} image",
293 self.image.descriptor.width, self.image.descriptor.height
294 ),
295 ));
296 }
297
298 let (first_column, last_column, first_row, last_row) = self.chunks_covering(region);
299 for chunk_row in first_row..=last_row {
300 for column in first_column..=last_column {
301 self.blit_chunk(column, chunk_row, region, out)?;
302 }
303 }
304 Ok(())
305 }
306
307 fn chunks_covering(&self, region: Region) -> (u32, u32, u32, u32) {
313 match self.image.layout {
314 Layout::Strips { rows_per_strip } => {
315 let first = region.y / rows_per_strip;
316 let last = (region.y + region.height.saturating_sub(1)) / rows_per_strip;
317 (0, 0, first, last.min(self.image.chunks_down() - 1))
318 }
319 Layout::Tiles { width, height } => {
320 let first_column = region.x / width;
321 let last_column = (region.x + region.width.saturating_sub(1)) / width;
322 let first_row = region.y / height;
323 let last_row = (region.y + region.height.saturating_sub(1)) / height;
324 (
325 first_column,
326 last_column.min(self.image.chunks_across() - 1),
327 first_row,
328 last_row.min(self.image.chunks_down() - 1),
329 )
330 }
331 }
332 }
333}
334
335fn expand_row(
337 image: &TiffImage,
338 stored: &[u8],
339 from: usize,
340 count: usize,
341 stored_width: usize,
342 out: &mut [u8],
343) {
344 let bits = image.bits_per_sample as usize;
345 let channels = image.samples_per_pixel as usize;
346 let format = image.descriptor.pixel;
347 let bpp = format.bytes_per_pixel();
348 let maximum = if bits >= 16 {
349 65535_u32
350 } else {
351 (1_u32 << bits) - 1
352 };
353
354 for index in 0..count {
355 let x = from + index;
356 if x >= stored_width {
357 break;
358 }
359 let Some(target) = out.get_mut(index * bpp..(index + 1) * bpp) else {
360 break;
361 };
362
363 let mut samples = [0_u32; 4];
365 for (channel, slot) in samples.iter_mut().enumerate().take(channels.min(4)) {
366 *slot = read_sample(stored, x * channels + channel, bits, image.order);
367 }
368
369 match image.photometric {
370 Photometric::Palette => {
371 let entries = 1_usize << bits;
375 let index = samples[0] as usize;
376 for channel in 0..3 {
377 let value = image
378 .color_map
379 .get(channel * entries + index)
380 .copied()
381 .unwrap_or(0);
382 if let Some(slot) = target.get_mut(channel) {
383 *slot = (value >> 8) as u8;
386 }
387 }
388 }
389 _ => {
390 for (channel, &sample) in samples.iter().enumerate().take(channels.min(4)) {
391 let mut value = sample;
392 let is_colour = channel < 3;
396 if image.photometric == Photometric::WhiteIsZero && is_colour {
397 value = maximum.saturating_sub(value);
398 }
399 write_sample(target, channel, value, bits, maximum, format);
400 }
401 }
402 }
403 }
404}
405
406fn read_sample(data: &[u8], index: usize, bits: usize, order: ByteOrder) -> u32 {
408 match bits {
409 16 => u32::from(order.u16(data, index * 2)),
410 8 => u32::from(data.get(index).copied().unwrap_or(0)),
411 1 | 2 | 4 => {
412 let per_byte = 8 / bits;
415 let byte = data.get(index / per_byte).copied().unwrap_or(0);
416 let shift = 8 - bits * (index % per_byte + 1);
417 u32::from((byte >> shift) & ((1_u16 << bits) - 1) as u8)
418 }
419 _ => 0,
420 }
421}
422
423fn write_sample(
425 target: &mut [u8],
426 channel: usize,
427 value: u32,
428 bits: usize,
429 maximum: u32,
430 format: PixelFormat,
431) {
432 let widened = if bits >= 8 {
433 value
434 } else {
435 (value * 255 + maximum / 2) / maximum.max(1)
438 };
439 match format.sample_kind() {
440 otf_pixels_core::SampleKind::U16 => {
441 let scaled = widened as u16;
442 for (offset, byte) in scaled.to_ne_bytes().iter().enumerate() {
443 if let Some(slot) = target.get_mut(channel * 2 + offset) {
444 *slot = *byte;
445 }
446 }
447 }
448 _ => {
449 if let Some(slot) = target.get_mut(channel) {
450 *slot = widened.min(255) as u8;
451 }
452 }
453 }
454}
455
456impl Decoder for TiffDecoder {
457 fn descriptor(&self) -> ImageDescriptor {
458 self.image.descriptor
459 }
460
461 fn pages(&self) -> u32 {
462 self.pages
463 }
464
465 fn orientation(&self) -> Orientation {
466 self.image.orientation
467 }
468
469 fn icc_profile(&self) -> Option<&[u8]> {
470 self.image.icc.as_deref()
471 }
472
473 fn exif(&self) -> Option<&[u8]> {
477 self.exif.as_deref()
478 }
479
480 fn xmp(&self) -> Option<&[u8]> {
482 self.xmp.as_deref()
483 }
484
485 fn source_info(&self) -> SourceInfo {
487 self.info.clone()
488 }
489
490 fn capability(&self) -> DecodeCapability {
491 if self.image.layout.is_random_access() {
494 DecodeCapability::Regions
495 } else {
496 DecodeCapability::Sequential
497 }
498 }
499
500 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
501 if self.row >= self.image.descriptor.height {
502 return Err(PixelsError::invalid_argument(
503 "out",
504 format!(
505 "all {} rows have already been read",
506 self.image.descriptor.height
507 ),
508 ));
509 }
510 let row_bytes = self.image.descriptor.row_bytes();
511 if out.len() != row_bytes {
512 return Err(PixelsError::invalid_argument(
513 "out",
514 format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
515 ));
516 }
517
518 let region = Region::new(0, self.row, self.image.descriptor.width, 1);
523 let pixel = self.image.descriptor.pixel;
524 let mut tile = TileMut::new(region, pixel, row_bytes, out)?;
525 self.read_region_into(region, &mut tile)?;
526 self.row += 1;
527 Ok(())
528 }
529
530 fn read_region(&mut self, region: Region, out: &mut TileMut<'_>) -> Result<()> {
531 if !self.image.layout.is_random_access() {
532 return Err(PixelsError::unsupported(
533 "this TIFF is stored in strips; region decode requires tiles",
534 ));
535 }
536 self.read_region_into(region, out)
537 }
538}
539
540#[must_use]
544pub fn probe(prefix: &[u8]) -> bool {
545 probe_header(prefix)
546}
547
548#[derive(Debug, Clone, Copy, Default)]
550pub struct TiffCodec;
551
552impl Codec for TiffCodec {
553 fn format(&self) -> Format {
554 Format::Tiff
555 }
556
557 fn magic_len(&self) -> usize {
558 8
559 }
560
561 fn probe(&self, prefix: &[u8]) -> bool {
562 probe(prefix)
563 }
564}