Skip to content

Commit daa0e3e

Browse files
authored
Merge pull request #206 from fintelia/simplify-jpeg
Simplify and avoid allocation in jpeg decompression
2 parents 473868a + 24d203c commit daa0e3e

File tree

2 files changed

+27
-105
lines changed

2 files changed

+27
-105
lines changed

src/decoder/image.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::ifd::{Directory, Value};
2-
use super::stream::{ByteOrder, DeflateReader, JpegReader, LZWReader, PackBitsReader};
2+
use super::stream::{ByteOrder, DeflateReader, LZWReader, PackBitsReader};
33
use super::tag_reader::TagReader;
44
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
55
use super::{stream::SmartReader, ChunkType};
@@ -356,7 +356,7 @@ impl Image {
356356
photometric_interpretation: PhotometricInterpretation,
357357
compression_method: CompressionMethod,
358358
compressed_length: u64,
359-
jpeg_tables: Option<Arc<Vec<u8>>>,
359+
jpeg_tables: Option<&[u8]>,
360360
) -> TiffResult<Box<dyn Read + 'r>> {
361361
Ok(match compression_method {
362362
CompressionMethod::None => Box::new(reader),
@@ -374,7 +374,29 @@ impl Image {
374374
));
375375
}
376376

377-
let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?;
377+
// Construct new jpeg_reader wrapping a SmartReader.
378+
//
379+
// JPEG compression in TIFF allows saving quantization and/or huffman tables in one
380+
// central location. These `jpeg_tables` are simply prepended to the remaining jpeg image data.
381+
// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
382+
// which is also at the beginning of the remaining JPEG image data and would
383+
// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
384+
// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
385+
// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
386+
// this has to be removed as well (last two bytes of `jpeg_tables`).
387+
let jpeg_reader = match jpeg_tables {
388+
Some(jpeg_tables) => {
389+
let mut reader = reader.take(compressed_length);
390+
reader.read_exact(&mut [0; 2])?;
391+
392+
Box::new(
393+
Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2])
394+
.chain(reader.take(compressed_length)),
395+
) as Box<dyn Read>
396+
}
397+
None => Box::new(reader.take(compressed_length)),
398+
};
399+
378400
let mut decoder = jpeg::Decoder::new(jpeg_reader);
379401

380402
match photometric_interpretation {
@@ -582,13 +604,12 @@ impl Image {
582604

583605
let padding_right = chunk_dims.0 - data_dims.0;
584606

585-
let jpeg_tables = self.jpeg_tables.clone();
586607
let mut reader = Self::create_reader(
587608
reader,
588609
photometric_interpretation,
589610
compression_method,
590611
*compressed_bytes,
591-
jpeg_tables,
612+
self.jpeg_tables.as_deref().map(|a| &**a),
592613
)?;
593614

594615
if output_width == data_dims.0 as usize && padding_right == 0 {

src/decoder/stream.rs

Lines changed: 1 addition & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
//! All IO functionality needed for TIFF decoding
22
33
use std::convert::TryFrom;
4-
use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Take};
5-
use std::sync::Arc;
4+
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
65

76
/// Byte order of the TIFF file.
87
#[derive(Clone, Copy, Debug)]
@@ -183,104 +182,6 @@ impl<R: Read> Read for LZWReader<R> {
183182
}
184183
}
185184

186-
///
187-
/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7))
188-
///
189-
190-
pub(crate) struct JpegReader {
191-
jpeg_tables: Option<Arc<Vec<u8>>>,
192-
193-
buffer: io::Cursor<Vec<u8>>,
194-
195-
offset: usize,
196-
}
197-
198-
impl JpegReader {
199-
/// Constructs new JpegReader wrapping a SmartReader.
200-
/// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one
201-
/// central location, the constructor accepts this data as `jpeg_tables` here containing either
202-
/// or both.
203-
/// These `jpeg_tables` are simply prepended to the remaining jpeg image data.
204-
/// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
205-
/// which is also at the beginning of the remaining JPEG image data and would
206-
/// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
207-
/// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
208-
/// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
209-
/// this has to be removed as well (last two bytes of `jpeg_tables`).
210-
pub fn new<R: Read>(
211-
mut reader: R,
212-
length: u64,
213-
jpeg_tables: Option<Arc<Vec<u8>>>,
214-
) -> io::Result<JpegReader> {
215-
// Read jpeg image data
216-
let mut segment = vec![0; length as usize];
217-
218-
reader.read_exact(&mut segment[..])?;
219-
220-
match jpeg_tables {
221-
Some(jpeg_tables) => {
222-
assert!(
223-
jpeg_tables.len() >= 2,
224-
"jpeg_tables, if given, must be at least 2 bytes long. Got {:?}",
225-
jpeg_tables
226-
);
227-
228-
assert!(
229-
length >= 2,
230-
"if jpeg_tables is given, length must be at least 2 bytes long, got {}",
231-
length
232-
);
233-
234-
let mut buffer = io::Cursor::new(segment);
235-
// Skip the first two bytes (marker bytes)
236-
buffer.seek(SeekFrom::Start(2))?;
237-
238-
Ok(JpegReader {
239-
buffer,
240-
jpeg_tables: Some(jpeg_tables),
241-
offset: 0,
242-
})
243-
}
244-
None => Ok(JpegReader {
245-
buffer: io::Cursor::new(segment),
246-
jpeg_tables: None,
247-
offset: 0,
248-
}),
249-
}
250-
}
251-
}
252-
253-
impl Read for JpegReader {
254-
// #[inline]
255-
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
256-
let mut start = 0;
257-
258-
if let Some(jpeg_tables) = &self.jpeg_tables {
259-
if jpeg_tables.len() - 2 > self.offset {
260-
// Read (rest of) jpeg_tables to buf (without the last two bytes)
261-
let size_remaining = jpeg_tables.len() - self.offset - 2;
262-
let to_copy = size_remaining.min(buf.len());
263-
264-
buf[start..start + to_copy]
265-
.copy_from_slice(&jpeg_tables[self.offset..self.offset + to_copy]);
266-
267-
self.offset += to_copy;
268-
269-
if to_copy == buf.len() {
270-
return Ok(to_copy);
271-
}
272-
273-
start += to_copy;
274-
}
275-
}
276-
277-
let read = self.buffer.read(&mut buf[start..])?;
278-
self.offset += read;
279-
280-
Ok(read + start)
281-
}
282-
}
283-
284185
///
285186
/// ## PackBits Reader
286187
///

0 commit comments

Comments
 (0)