Skip to main content

df_ocr_switcher/input/
tiff.rs

1//! Decodifica TIFF multi-pagina → `Vec<RgbImage>`.
2//!
3//! Usa il crate `tiff 0.10` che supporta I/O diretto senza dipendere da
4//! libtiff di sistema. Gestisce Gray8, RGB8, RGBA8; scala U16 a U8.
5
6use std::io::BufReader;
7use std::path::Path;
8
9use image::{DynamicImage, RgbImage};
10use tiff::ColorType as TiffColorType;
11use tiff::decoder::{Decoder, DecodingResult};
12
13use crate::error::{Error, Result};
14
15/// Carica tutte le pagine di un TIFF multi-pagina come `Vec<RgbImage>`.
16/// Restituisce almeno una pagina o `Err` se il file non è leggibile.
17pub fn load_tiff_pages(path: impl AsRef<Path>) -> Result<Vec<RgbImage>> {
18    let path = path.as_ref();
19    let file  = BufReader::new(std::fs::File::open(path)?);
20    let mut dec = Decoder::new(file)
21        .map_err(|e| Error::Tiff(format!("init decoder: {e}")))?;
22
23    let mut pages = Vec::new();
24    loop {
25        let page = decode_current(&mut dec, pages.len())?;
26        pages.push(page);
27        if dec.more_images() {
28            dec.next_image()
29                .map_err(|e| Error::Tiff(format!("next_image pag.{}: {e}", pages.len())))?;
30        } else {
31            break;
32        }
33    }
34    Ok(pages)
35}
36
37// ─── Helper ──────────────────────────────────────────────────────────────────
38
39fn decode_current<R: std::io::Read + std::io::Seek>(
40    dec: &mut Decoder<R>,
41    page_idx: usize,
42) -> Result<RgbImage> {
43    let (w, h)  = dec.dimensions()
44        .map_err(|e| Error::Tiff(format!("dimensions pag.{page_idx}: {e}")))?;
45    let color   = dec.colortype()
46        .map_err(|e| Error::Tiff(format!("colortype pag.{page_idx}: {e}")))?;
47    let buf     = dec.read_image()
48        .map_err(|e| Error::Tiff(format!("read_image pag.{page_idx}: {e}")))?;
49
50    let dyn_img: DynamicImage = match (color, buf) {
51        (TiffColorType::Gray(8), DecodingResult::U8(b)) =>
52            DynamicImage::ImageLuma8(
53                image::ImageBuffer::from_raw(w, h, b)
54                    .ok_or_else(|| tiff_err(page_idx, "Luma8 from_raw"))?),
55
56        (TiffColorType::RGB(8), DecodingResult::U8(b)) =>
57            DynamicImage::ImageRgb8(
58                image::ImageBuffer::from_raw(w, h, b)
59                    .ok_or_else(|| tiff_err(page_idx, "RGB8 from_raw"))?),
60
61        (TiffColorType::RGBA(8), DecodingResult::U8(b)) =>
62            DynamicImage::ImageRgba8(
63                image::ImageBuffer::from_raw(w, h, b)
64                    .ok_or_else(|| tiff_err(page_idx, "RGBA8 from_raw"))?),
65
66        // TIFF 16-bit (scanners medicali, mappe): scala a 8-bit
67        (TiffColorType::Gray(16), DecodingResult::U16(b)) => {
68            let b8: Vec<u8> = b.iter().map(|&v| (v >> 8) as u8).collect();
69            DynamicImage::ImageLuma8(
70                image::ImageBuffer::from_raw(w, h, b8)
71                    .ok_or_else(|| tiff_err(page_idx, "Gray16→8 from_raw"))?)
72        }
73
74        (TiffColorType::RGB(16), DecodingResult::U16(b)) => {
75            let b8: Vec<u8> = b.iter().map(|&v| (v >> 8) as u8).collect();
76            DynamicImage::ImageRgb8(
77                image::ImageBuffer::from_raw(w, h, b8)
78                    .ok_or_else(|| tiff_err(page_idx, "RGB16→8 from_raw"))?)
79        }
80
81        (ct, _) => return Err(Error::Tiff(
82            format!("pag.{page_idx}: tipo colore non gestito: {ct:?}"))),
83    };
84
85    Ok(dyn_img.to_rgb8())
86}
87
88fn tiff_err(page: usize, msg: &str) -> Error {
89    Error::Tiff(format!("pag.{page}: {msg} fallito"))
90}