Add initial support for modern JPEG compression
Some things still need to be done: - Check RGB images - Check small images - Check huffman-only jpeg tables - Check empty jpeg tables - Add tests
This commit is contained in:
1 parent
da9bdac8a2
commit
2cb57a4d04
5 files changed
+184
-1
No files matched your search
@@ -18,6 +18,7 @@ exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
weezl = "0.1.0"
|
weezl = "0.1.0"
|
||||||
|
jpeg = { package = "jpeg-decoder", version = "0.1.17", default-features = false }
|
||||||
|
|
||||||
[dependencies.miniz_oxide]
|
[dependencies.miniz_oxide]
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|||||||
+85
-1
@@ -9,7 +9,7 @@ use self::ifd::Directory;
|
|||||||
use tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type};
|
use tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type};
|
||||||
|
|
||||||
use self::stream::{
|
use self::stream::{
|
||||||
ByteOrder, DeflateReader, EndianReader, LZWReader, PackBitsReader, SmartReader,
|
ByteOrder, DeflateReader, EndianReader, LZWReader, PackBitsReader, SmartReader, JpegReader
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod ifd;
|
pub mod ifd;
|
||||||
@@ -710,6 +710,32 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_all_tags(&mut self) -> Vec<(Tag, ifd::Value)> {
|
||||||
|
let mut all_tags = Vec::new();
|
||||||
|
|
||||||
|
let dir = match &self.ifd {
|
||||||
|
Some(d) => d,
|
||||||
|
None => {
|
||||||
|
println!("No tags found");
|
||||||
|
return all_tags;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let keys = dir.keys().cloned().collect::<Vec<Tag>>();
|
||||||
|
// go through all tags
|
||||||
|
for key in keys {
|
||||||
|
let tag_value = match self.get_tag(key) {
|
||||||
|
Ok(val) => val,
|
||||||
|
_ => {
|
||||||
|
println!("Could not unwrap {:?}", key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
all_tags.push((key, tag_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
all_tags
|
||||||
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag and convert it to the desired type.
|
/// Tries to retrieve a tag and convert it to the desired type.
|
||||||
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
|
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
|
||||||
self.get_tag(tag)?.into_u32()
|
self.get_tag(tag)?.into_u32()
|
||||||
@@ -928,6 +954,56 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_jpeg_tiles(&mut self) -> TiffResult<DecodingResult> {
|
||||||
|
let tile_offsets = self.get_tag_u32_vec(Tag::TileOffsets)?;
|
||||||
|
let tile_bytes = self.get_tag_u32_vec(Tag::TileByteCounts)?;
|
||||||
|
let jpeg_tables: Vec<u8> = self.get_tag_u8_vec(Tag::JPEGTables)?;
|
||||||
|
|
||||||
|
let mut res_img = Vec::with_capacity(tile_offsets[0] as usize);
|
||||||
|
|
||||||
|
for (idx, offset) in tile_offsets.iter().enumerate() {
|
||||||
|
|
||||||
|
self.goto_offset(*offset)?;
|
||||||
|
let jpeg_reader = JpegReader::new(&mut self.reader, tile_bytes[idx], &jpeg_tables)?;
|
||||||
|
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
||||||
|
|
||||||
|
match decoder.decode() {
|
||||||
|
Err(e) => {
|
||||||
|
panic!("The ERror was: {}", e);
|
||||||
|
},
|
||||||
|
Ok(mut val) => res_img.append(&mut val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DecodingResult::U8(res_img))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_jpeg_strips(&mut self) -> TiffResult<DecodingResult> {
|
||||||
|
let strip_offsets = self.get_tag_u32_vec(Tag::StripOffsets)?;
|
||||||
|
let strip_bytes = self.get_tag_u32_vec(Tag::StripByteCounts)?;
|
||||||
|
let jpeg_tables: Vec<u8> = self.get_tag_u8_vec(Tag::JPEGTables)?;
|
||||||
|
|
||||||
|
let mut res_img = Vec::with_capacity(strip_offsets[0] as usize);
|
||||||
|
|
||||||
|
for (idx, offset) in strip_offsets.iter().enumerate() {
|
||||||
|
|
||||||
|
self.goto_offset(*offset)?;
|
||||||
|
let jpeg_reader = JpegReader::new(&mut self.reader, strip_bytes[idx], &jpeg_tables)?;
|
||||||
|
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
||||||
|
|
||||||
|
match decoder.decode() {
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Error when reading jpeg tiles: {}", e);
|
||||||
|
},
|
||||||
|
Ok(mut val) => res_img.append(&mut val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DecodingResult::U8(res_img))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> {
|
pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> {
|
||||||
self.initialize_strip_decoder()?;
|
self.initialize_strip_decoder()?;
|
||||||
|
|
||||||
@@ -1050,6 +1126,14 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
/// Decodes the entire image and return it as a Vector
|
/// Decodes the entire image and return it as a Vector
|
||||||
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
||||||
|
|
||||||
|
if self.compression_method == CompressionMethod::ModernJPEG {
|
||||||
|
match self.find_tag(Tag::TileOffsets) {
|
||||||
|
Ok(None) | Err(_) => return self.read_jpeg_strips(),
|
||||||
|
Ok(_) => return self.read_jpeg_tiles(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.initialize_strip_decoder()?;
|
self.initialize_strip_decoder()?;
|
||||||
let rows_per_strip =
|
let rows_per_strip =
|
||||||
usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?;
|
usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?;
|
||||||
|
|||||||
@@ -187,6 +187,14 @@ pub trait EndianReader: Read {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// # READERS
|
||||||
|
///
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## Deflate Reader
|
||||||
|
///
|
||||||
|
|
||||||
/// Reader that decompresses DEFLATE streams
|
/// Reader that decompresses DEFLATE streams
|
||||||
pub struct DeflateReader {
|
pub struct DeflateReader {
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
@@ -233,6 +241,10 @@ impl EndianReader for DeflateReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## LZW Reader
|
||||||
|
///
|
||||||
|
|
||||||
/// Reader that decompresses LZW streams
|
/// Reader that decompresses LZW streams
|
||||||
pub struct LZWReader {
|
pub struct LZWReader {
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
@@ -306,6 +318,76 @@ impl EndianReader for LZWReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7))
|
||||||
|
///
|
||||||
|
|
||||||
|
pub struct JpegReader {
|
||||||
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
|
byte_order: ByteOrder, // for jpeg image data always ...,
|
||||||
|
}
|
||||||
|
// Only tested with qtables in jpegtables, but it contain both qtables and huffman tables or either
|
||||||
|
impl JpegReader {
|
||||||
|
/// Constructs new JpegReader wrapping a SmartReader.
|
||||||
|
/// Because Jpeg compression in TIFF allows to save quantization and/or huffman tables in one
|
||||||
|
/// central loacation, the constructor accepts this data as `jpeg_tables` here containing either
|
||||||
|
/// both.
|
||||||
|
/// These `jpeg_tables` are simply prepended to the remaining jpeg image data.
|
||||||
|
/// Because these jpegtables 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 this 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>(
|
||||||
|
reader: &mut SmartReader<R>,
|
||||||
|
length: u32,
|
||||||
|
jpeg_tables: &Vec<u8>
|
||||||
|
) -> io::Result<JpegReader>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
let order = reader.byte_order;
|
||||||
|
|
||||||
|
// Read jpeg image data
|
||||||
|
let mut segment = vec![0; length as usize];
|
||||||
|
reader.read_exact(&mut segment[..])?;
|
||||||
|
|
||||||
|
let mut jpeg_data = jpeg_tables.clone();
|
||||||
|
jpeg_data.truncate(jpeg_data.len() - 2);
|
||||||
|
|
||||||
|
jpeg_data.extend_from_slice(&mut segment[2..]);
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
JpegReader {
|
||||||
|
buffer: io::Cursor::new(jpeg_data),
|
||||||
|
byte_order: order,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for JpegReader {
|
||||||
|
// #[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.buffer.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EndianReader for JpegReader {
|
||||||
|
#[inline(always)]
|
||||||
|
fn byte_order(&self) -> ByteOrder {
|
||||||
|
self.byte_order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## PackBits Reader
|
||||||
|
///
|
||||||
|
|
||||||
/// Reader that unpacks Apple's `PackBits` format
|
/// Reader that unpacks Apple's `PackBits` format
|
||||||
pub struct PackBitsReader {
|
pub struct PackBitsReader {
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
@@ -378,6 +460,10 @@ impl EndianReader for PackBitsReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## SmartReader Reader
|
||||||
|
///
|
||||||
|
|
||||||
/// Reader that is aware of the byte order.
|
/// Reader that is aware of the byte order.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SmartReader<R>
|
pub struct SmartReader<R>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
extern crate miniz_oxide;
|
extern crate miniz_oxide;
|
||||||
extern crate weezl;
|
extern crate weezl;
|
||||||
|
extern crate jpeg;
|
||||||
|
|
||||||
mod bytecast;
|
mod bytecast;
|
||||||
pub mod decoder;
|
pub mod decoder;
|
||||||
|
|||||||
+11
@@ -111,6 +111,13 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
|||||||
Threshholding = 263, // TODO add support
|
Threshholding = 263, // TODO add support
|
||||||
XResolution = 282,
|
XResolution = 282,
|
||||||
YResolution = 283,
|
YResolution = 283,
|
||||||
|
// JPEG
|
||||||
|
TileWidth = 322,
|
||||||
|
TileLength = 323,
|
||||||
|
TileOffsets = 324,
|
||||||
|
TileByteCounts = 325,
|
||||||
|
SampleFormat = 339,
|
||||||
|
JPEGTables = 347,
|
||||||
// Advanced tags
|
// Advanced tags
|
||||||
Predictor = 317,
|
Predictor = 317,
|
||||||
TileWidth = 322,
|
TileWidth = 322,
|
||||||
@@ -142,6 +149,8 @@ pub enum Type(u16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
|
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||||
|
/// for reference.
|
||||||
pub enum CompressionMethod(u16) {
|
pub enum CompressionMethod(u16) {
|
||||||
None = 1,
|
None = 1,
|
||||||
Huffman = 2,
|
Huffman = 2,
|
||||||
@@ -149,6 +158,8 @@ pub enum CompressionMethod(u16) {
|
|||||||
Fax4 = 4,
|
Fax4 = 4,
|
||||||
LZW = 5,
|
LZW = 5,
|
||||||
JPEG = 6,
|
JPEG = 6,
|
||||||
|
// "Extended JPEG" or "new JPEG" style
|
||||||
|
ModernJPEG = 7,
|
||||||
Deflate = 8,
|
Deflate = 8,
|
||||||
OldDeflate = 0x80B2,
|
OldDeflate = 0x80B2,
|
||||||
PackBits = 0x8005,
|
PackBits = 0x8005,
|
||||||
|
|||||||
Reference in new issue
Block a user