Merge pull request #229 from stephenjudkins/sdj/fax4-support
CCITT group 4 (Fax4) decoding support
This commit is contained in:
6 files changed
+99
-3
No files matched your search
@@ -20,6 +20,8 @@ exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
|||||||
half = { version = "2.4.1" }
|
half = { version = "2.4.1" }
|
||||||
quick-error = "2.0.1"
|
quick-error = "2.0.1"
|
||||||
|
|
||||||
|
# Rename to avoid confusion with the feature name.
|
||||||
|
fax34 = { package = "fax", version = "0.2.4", optional = true }
|
||||||
flate2 = { version = "1.0.20", optional = true }
|
flate2 = { version = "1.0.20", optional = true }
|
||||||
weezl = { version = "0.1.10", optional = true }
|
weezl = { version = "0.1.10", optional = true }
|
||||||
zstd = { version = "0.13", optional = true }
|
zstd = { version = "0.13", optional = true }
|
||||||
@@ -35,6 +37,7 @@ default = ["deflate", "jpeg", "lzw"]
|
|||||||
|
|
||||||
# Compression algorithms
|
# Compression algorithms
|
||||||
deflate = ["dep:flate2"]
|
deflate = ["dep:flate2"]
|
||||||
|
fax = ["dep:fax34"]
|
||||||
jpeg = ["dep:zune-jpeg"]
|
jpeg = ["dep:zune-jpeg"]
|
||||||
lzw = ["dep:weezl"]
|
lzw = ["dep:weezl"]
|
||||||
zstd = ["dep:zstd"]
|
zstd = ["dep:zstd"]
|
||||||
|
|||||||
+11
-1
@@ -9,6 +9,7 @@ use crate::tags::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::io::{self, Cursor, Read, Seek};
|
use std::io::{self, Cursor, Read, Seek};
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -384,7 +385,9 @@ impl Image {
|
|||||||
reader: R,
|
reader: R,
|
||||||
compression_method: CompressionMethod,
|
compression_method: CompressionMethod,
|
||||||
compressed_length: u64,
|
compressed_length: u64,
|
||||||
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
|
// FIXME: these should be `expect` attributes or we choose another way of passing them.
|
||||||
|
#[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>,
|
||||||
|
#[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32),
|
||||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||||
Ok(match compression_method {
|
Ok(match compression_method {
|
||||||
CompressionMethod::None => Box::new(reader),
|
CompressionMethod::None => Box::new(reader),
|
||||||
@@ -452,6 +455,12 @@ impl Image {
|
|||||||
|
|
||||||
Box::new(Cursor::new(data))
|
Box::new(Cursor::new(data))
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new(
|
||||||
|
dimensions,
|
||||||
|
reader,
|
||||||
|
compressed_length,
|
||||||
|
)?),
|
||||||
method => {
|
method => {
|
||||||
return Err(TiffError::UnsupportedError(
|
return Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
||||||
@@ -658,6 +667,7 @@ impl Image {
|
|||||||
compression_method,
|
compression_method,
|
||||||
*compressed_bytes,
|
*compressed_bytes,
|
||||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||||
|
chunk_dims,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if output_row_stride == chunk_row_bytes {
|
if output_row_stride == chunk_row_bytes {
|
||||||
|
|||||||
@@ -547,7 +547,6 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read + Seek> Decoder<R> {
|
impl<R: Read + Seek> Decoder<R> {
|
||||||
/// Create a new decoder that decodes from the stream ```r```
|
|
||||||
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
|
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
|
||||||
let mut endianess = Vec::with_capacity(2);
|
let mut endianess = Vec::with_capacity(2);
|
||||||
(&mut r).take(2).read_to_end(&mut endianess)?;
|
(&mut r).take(2).read_to_end(&mut endianess)?;
|
||||||
|
|||||||
+79
-1
@@ -1,5 +1,4 @@
|
|||||||
//! All IO functionality needed for TIFF decoding
|
//! All IO functionality needed for TIFF decoding
|
||||||
|
|
||||||
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
||||||
|
|
||||||
/// Byte order of the TIFF file.
|
/// Byte order of the TIFF file.
|
||||||
@@ -276,6 +275,85 @@ impl<R: Read> Read for PackBitsReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
pub struct Group4Reader<R: Read> {
|
||||||
|
decoder: fax34::decoder::Group4Decoder<io::Bytes<io::BufReader<io::Take<R>>>>,
|
||||||
|
line_buf: io::Cursor<Vec<u8>>,
|
||||||
|
height: u16,
|
||||||
|
width: u16,
|
||||||
|
y: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
impl<R: Read> Group4Reader<R> {
|
||||||
|
pub fn new(
|
||||||
|
dimensions: (u32, u32),
|
||||||
|
reader: R,
|
||||||
|
compressed_length: u64,
|
||||||
|
) -> crate::TiffResult<Self> {
|
||||||
|
let width = u16::try_from(dimensions.0)?;
|
||||||
|
let height = u16::try_from(dimensions.1)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
decoder: fax34::decoder::Group4Decoder::new(
|
||||||
|
io::BufReader::new(reader.take(compressed_length)).bytes(),
|
||||||
|
width,
|
||||||
|
)?,
|
||||||
|
line_buf: io::Cursor::new(Vec::with_capacity(width.into())),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
impl<R: Read> Read for Group4Reader<R> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
// Either we have not read any line or we are at the end of a line.
|
||||||
|
if self.line_buf.position() as usize == self.line_buf.get_ref().len()
|
||||||
|
&& self.y < self.height
|
||||||
|
{
|
||||||
|
let next = self.decoder.advance().map_err(std::io::Error::other)?;
|
||||||
|
|
||||||
|
match next {
|
||||||
|
fax34::decoder::DecodeStatus::End => (),
|
||||||
|
fax34::decoder::DecodeStatus::Incomplete => {
|
||||||
|
self.y += 1;
|
||||||
|
|
||||||
|
// We known `transitions` yields exactly `self.width` items (per doc).
|
||||||
|
let transitions = fax34::decoder::pels(self.decoder.transition(), self.width);
|
||||||
|
|
||||||
|
let buffer = self.line_buf.get_mut();
|
||||||
|
buffer.resize(usize::from(self.width).div_ceil(8), 0u8);
|
||||||
|
|
||||||
|
let target = &mut buffer[..];
|
||||||
|
|
||||||
|
let mut bits = transitions.map(|c| match c {
|
||||||
|
fax34::Color::Black => true,
|
||||||
|
fax34::Color::White => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assemble bits in MSB as per our library representation for buffer.
|
||||||
|
for byte in target {
|
||||||
|
let mut val = 0;
|
||||||
|
|
||||||
|
for (idx, bit) in bits.by_ref().take(8).enumerate() {
|
||||||
|
val |= u8::from(bit) << (7 - idx % 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
*byte = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.line_buf.set_position(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.line_buf.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -672,3 +672,9 @@ fn bytes_gray_f32() {
|
|||||||
byte_order_u32,
|
byte_order_u32,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
fn test_fax4() {
|
||||||
|
test_image_sum_u8("fax4.tiff", ColorType::Gray(1), 7802706);
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in new issue
Block a user