[go: up one dir, main page]

fax 0.2.3

Decoder and Encoder for CCITT Group 3 and 4 bi-level image encodings used by fax machines TIFF and PDF.
Documentation
#![feature(slice_split_once)]

use fax::{encoder, BitReader, ByteReader};
use fax::{VecWriter, decoder, decoder::pels, BitWriter, Bits, Color};
use std::io::Write;
use std::fs::{self, File};
use std::path::Path;

#[test]
fn main() {
    let data_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../fax-test");

    let mut fails = vec![];

    for r in data_path.read_dir().unwrap() {
        let e = r.unwrap();
        let p = e.path();

        let base = data_path.join(p.file_stem().unwrap());
        let pbm = base.with_extension("pbm");
        let r = if p.extension().is_some_and(|e| e == "fax") {
            read_pbm(&pbm).test_fax(&p)
        } else if p.extension().is_some_and(|e| e == "tiff") {
            read_pbm(&pbm).test_tiff(&p)
        } else {
            continue;
        };  
        println!("{base:?} {r:?}");
        if r.is_err() {
            fails.push(p);
            break;
        }
    }

    if fails.len() > 0 {
        println!("failures: {fails:?}");
        panic!("");
    }
}

struct TestImage {
    width: u16,
    height: u16,
    data: Vec<u8>,
}
fn read_pbm(path: &Path) -> TestImage {
    let ref_data = std::fs::read(path).unwrap();
    let (header1, data) = ref_data.split_once(|&b| b == b'\n').unwrap();
    assert_eq!(header1, b"P4");
    let (header2, ref_image) = data.split_once(|&b| b == b'\n').unwrap();
    let header2 = std::str::from_utf8(header2).unwrap();
    let (w, h) = header2.split_once(" ").unwrap();
    let width: u16 = w.parse().unwrap();
    let h: u16 = h.parse().unwrap();

    TestImage { width, height: h, data: ref_image.to_vec() }
}
impl TestImage {
    fn test_fax(&self, fax_path: &Path) -> Result<(), ()> {
        let data = fs::read(fax_path).unwrap();
        self.test_stream(&data, false)
    }

    fn test_tiff(&self, path: &Path) -> Result<(), ()> {
        use tiff::{decoder::Decoder, tags::Tag};
        let data = std::fs::read(path).unwrap();
        let reader = std::io::Cursor::new(data.as_slice());
        let mut decoder = Decoder::new(reader).unwrap();
        let (w, h) = decoder.chunk_dimensions();
        let mut buf = vec![0; w as usize * h as usize];
        let strip_offset = decoder.get_tag(Tag::StripOffsets).unwrap().into_u32().unwrap() as usize;
        let strip_bytes = decoder.get_tag(Tag::StripByteCounts).unwrap().into_u32().unwrap() as usize;
        decoder.goto_offset_u64(strip_offset as _).unwrap();

        let white_is_1 = decoder.get_tag(Tag::PhotometricInterpretation).unwrap().into_u16().unwrap() != 0;

        let data = &data[strip_offset .. strip_offset + strip_bytes];
        self.test_stream(&data, white_is_1)
    }

    fn test_stream(&self, data: &[u8], white_is_1: bool) -> Result<(), ()> {
        let mut ref_lines = self.data.chunks_exact((self.width as usize + 7) / 8);

        let (black, white) = match white_is_1 {
            false => (Bits { data: 1, len: 1 }, Bits { data: 0, len: 1 }),
            true => (Bits { data: 0, len: 1 }, Bits { data: 1, len: 1 })
        };

        let mut height = 0;
        let mut errors = 0;
        let ok = decoder::decode_g4(data.iter().cloned(), self.width, None,  |transitions| {
            //println!("{}", transitions.len());
            let mut writer = VecWriter::new();
            for c in pels(transitions, self.width) {
                let bit = match c {
                    Color::Black => black,
                    Color::White => white
                };
                writer.write(bit);
            }
            writer.pad();
            let data = writer.finish();
            let ref_line = ref_lines.next().unwrap();
            if ref_line != data {
                println!("line {height} mismatch");
                errors += 1;
            }
            height += 1;
        }).is_some();

        if errors > 0 {
            println!("{} errors", errors);
            return Err(());
        }
        if !ok {
            println!("not ok");
            return Err(());
        }


        fn pixels(line: &[u8], white_is_1: bool) -> impl Iterator<Item=Color> + '_ {
            ByteReader::new(line.iter().cloned()).into_bits().map(move |b| if b ^ white_is_1 { Color::Black } else { Color::White })
        }
        let mut expected = ByteReader::new(data.iter().cloned());
        let mut encoder = encoder::Encoder::new(TestWriter { expected: &mut expected, offset: 0 });
        let ref_lines = self.data.chunks_exact((self.width as usize + 7) / 8);
        let mut fail = false;
        for (i, line) in ref_lines.enumerate() {
            //println!("Line {i}");
            //println!("{:08b} {:08b}", line[0], line[1]);
            if encoder.encode_line(pixels(line, white_is_1), self.width).is_err() {
                fail = i < height as _;
                break;
            }
        }
        
        dbg!(fax::maps::mode::decode(&mut expected));

        if fail {
            return Err(());
        }
        Ok(())
    }
}

struct TestWriter<'a, R> {
    offset: usize,
    expected: &'a mut ByteReader<R>,
}
impl<'a, R> BitWriter for TestWriter<'a, R> where R: Iterator<Item=u8> {
    type Error = (usize, u8);
    fn write(&mut self, bits: Bits) -> Result<(), Self::Error> {
        match self.expected.expect(bits) {
            Ok(()) => {
                self.expected.consume(bits.len);
            }
            Err(_) => {
                self.expected.print_peek();
                println!("    @{}+{} found {}", self.offset/8, self.offset%8, bits);
                return Err((self.offset / 8, (self.offset % 8) as u8));
            },
        }
        self.offset += bits.len as usize;
        Ok(())
    }
}