Merge pull request #206 from fintelia/simplify-jpeg
Simplify and avoid allocation in jpeg decompression
This commit is contained in:
2 files changed
+27
-105
No files matched your search
+26
-5
@@ -1,5 +1,5 @@
|
|||||||
use super::ifd::{Directory, Value};
|
use super::ifd::{Directory, Value};
|
||||||
use super::stream::{ByteOrder, DeflateReader, JpegReader, LZWReader, PackBitsReader};
|
use super::stream::{ByteOrder, DeflateReader, LZWReader, PackBitsReader};
|
||||||
use super::tag_reader::TagReader;
|
use super::tag_reader::TagReader;
|
||||||
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
|
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
|
||||||
use super::{stream::SmartReader, ChunkType};
|
use super::{stream::SmartReader, ChunkType};
|
||||||
@@ -356,7 +356,7 @@ impl Image {
|
|||||||
photometric_interpretation: PhotometricInterpretation,
|
photometric_interpretation: PhotometricInterpretation,
|
||||||
compression_method: CompressionMethod,
|
compression_method: CompressionMethod,
|
||||||
compressed_length: u64,
|
compressed_length: u64,
|
||||||
jpeg_tables: Option<Arc<Vec<u8>>>,
|
jpeg_tables: Option<&[u8]>,
|
||||||
) -> 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),
|
||||||
@@ -374,7 +374,29 @@ impl Image {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?;
|
// Construct new jpeg_reader wrapping a SmartReader.
|
||||||
|
//
|
||||||
|
// JPEG compression in TIFF allows saving quantization and/or huffman tables in one
|
||||||
|
// central location. These `jpeg_tables` are simply prepended to the remaining jpeg image data.
|
||||||
|
// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
|
||||||
|
// which is also at the beginning of the remaining JPEG image data and would
|
||||||
|
// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
|
||||||
|
// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
|
||||||
|
// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
|
||||||
|
// this has to be removed as well (last two bytes of `jpeg_tables`).
|
||||||
|
let jpeg_reader = match jpeg_tables {
|
||||||
|
Some(jpeg_tables) => {
|
||||||
|
let mut reader = reader.take(compressed_length);
|
||||||
|
reader.read_exact(&mut [0; 2])?;
|
||||||
|
|
||||||
|
Box::new(
|
||||||
|
Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2])
|
||||||
|
.chain(reader.take(compressed_length)),
|
||||||
|
) as Box<dyn Read>
|
||||||
|
}
|
||||||
|
None => Box::new(reader.take(compressed_length)),
|
||||||
|
};
|
||||||
|
|
||||||
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
||||||
|
|
||||||
match photometric_interpretation {
|
match photometric_interpretation {
|
||||||
@@ -582,13 +604,12 @@ impl Image {
|
|||||||
|
|
||||||
let padding_right = chunk_dims.0 - data_dims.0;
|
let padding_right = chunk_dims.0 - data_dims.0;
|
||||||
|
|
||||||
let jpeg_tables = self.jpeg_tables.clone();
|
|
||||||
let mut reader = Self::create_reader(
|
let mut reader = Self::create_reader(
|
||||||
reader,
|
reader,
|
||||||
photometric_interpretation,
|
photometric_interpretation,
|
||||||
compression_method,
|
compression_method,
|
||||||
*compressed_bytes,
|
*compressed_bytes,
|
||||||
jpeg_tables,
|
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if output_width == data_dims.0 as usize && padding_right == 0 {
|
if output_width == data_dims.0 as usize && padding_right == 0 {
|
||||||
|
|||||||
+1
-100
@@ -1,8 +1,7 @@
|
|||||||
//! All IO functionality needed for TIFF decoding
|
//! All IO functionality needed for TIFF decoding
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Take};
|
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// Byte order of the TIFF file.
|
/// Byte order of the TIFF file.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -183,104 +182,6 @@ impl<R: Read> Read for LZWReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7))
|
|
||||||
///
|
|
||||||
|
|
||||||
pub(crate) struct JpegReader {
|
|
||||||
jpeg_tables: Option<Arc<Vec<u8>>>,
|
|
||||||
|
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
|
||||||
|
|
||||||
offset: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JpegReader {
|
|
||||||
/// Constructs new JpegReader wrapping a SmartReader.
|
|
||||||
/// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one
|
|
||||||
/// central location, the constructor accepts this data as `jpeg_tables` here containing either
|
|
||||||
/// or both.
|
|
||||||
/// These `jpeg_tables` are simply prepended to the remaining jpeg image data.
|
|
||||||
/// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
|
|
||||||
/// which is also at the beginning of the remaining JPEG image data and would
|
|
||||||
/// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
|
|
||||||
/// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
|
|
||||||
/// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
|
|
||||||
/// this has to be removed as well (last two bytes of `jpeg_tables`).
|
|
||||||
pub fn new<R: Read>(
|
|
||||||
mut reader: R,
|
|
||||||
length: u64,
|
|
||||||
jpeg_tables: Option<Arc<Vec<u8>>>,
|
|
||||||
) -> io::Result<JpegReader> {
|
|
||||||
// Read jpeg image data
|
|
||||||
let mut segment = vec![0; length as usize];
|
|
||||||
|
|
||||||
reader.read_exact(&mut segment[..])?;
|
|
||||||
|
|
||||||
match jpeg_tables {
|
|
||||||
Some(jpeg_tables) => {
|
|
||||||
assert!(
|
|
||||||
jpeg_tables.len() >= 2,
|
|
||||||
"jpeg_tables, if given, must be at least 2 bytes long. Got {:?}",
|
|
||||||
jpeg_tables
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
length >= 2,
|
|
||||||
"if jpeg_tables is given, length must be at least 2 bytes long, got {}",
|
|
||||||
length
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut buffer = io::Cursor::new(segment);
|
|
||||||
// Skip the first two bytes (marker bytes)
|
|
||||||
buffer.seek(SeekFrom::Start(2))?;
|
|
||||||
|
|
||||||
Ok(JpegReader {
|
|
||||||
buffer,
|
|
||||||
jpeg_tables: Some(jpeg_tables),
|
|
||||||
offset: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
None => Ok(JpegReader {
|
|
||||||
buffer: io::Cursor::new(segment),
|
|
||||||
jpeg_tables: None,
|
|
||||||
offset: 0,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for JpegReader {
|
|
||||||
// #[inline]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
let mut start = 0;
|
|
||||||
|
|
||||||
if let Some(jpeg_tables) = &self.jpeg_tables {
|
|
||||||
if jpeg_tables.len() - 2 > self.offset {
|
|
||||||
// Read (rest of) jpeg_tables to buf (without the last two bytes)
|
|
||||||
let size_remaining = jpeg_tables.len() - self.offset - 2;
|
|
||||||
let to_copy = size_remaining.min(buf.len());
|
|
||||||
|
|
||||||
buf[start..start + to_copy]
|
|
||||||
.copy_from_slice(&jpeg_tables[self.offset..self.offset + to_copy]);
|
|
||||||
|
|
||||||
self.offset += to_copy;
|
|
||||||
|
|
||||||
if to_copy == buf.len() {
|
|
||||||
return Ok(to_copy);
|
|
||||||
}
|
|
||||||
|
|
||||||
start += to_copy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let read = self.buffer.read(&mut buf[start..])?;
|
|
||||||
self.offset += read;
|
|
||||||
|
|
||||||
Ok(read + start)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// ## PackBits Reader
|
/// ## PackBits Reader
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in new issue
Block a user