Skip to content

Commit

Permalink
jpeg/decoder: Add ability to fill in default mjpeg tables in case an …
Browse files Browse the repository at this point in the history
…image doesn't have them present+ add a fix for horizontally sampled RGB to grayscale fixes

First one fixes: #136

Signed-off-by: caleb <[email protected]>
  • Loading branch information
etemesi254 committed Jul 26, 2023
1 parent 1809a35 commit 27d322c
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 7 deletions.
Binary file added test-images/jpeg/mjpeg_huffman.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions zune-jpeg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ default = ["x86", "neon", "std"]
log = "0.4.11" # logging facilities
zune-core = { path = "../zune-core", version = "0.2" }


[dev-dependencies]
zune-ppm = { path = "../zune-ppm" }
21 changes: 17 additions & 4 deletions zune-jpeg/src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ use alloc::string::ToString;
use alloc::vec::Vec;
use alloc::{format, vec};

use zune_core::bit_depth::BitDepth;
use zune_core::bytestream::{ZByteReader, ZReaderTrait};
use zune_core::colorspace::ColorSpace;
use zune_core::options::DecoderOptions;
use zune_core::options::{DecoderOptions, EncoderOptions};

use crate::color_convert::choose_ycbcr_to_rgb_convert_func;
use crate::components::{Components, SampleRatios};
Expand Down Expand Up @@ -146,7 +147,8 @@ pub struct JpegDecoder<T: ZReaderTrait>
// exif data, lifted from app2
pub(crate) exif_data: Option<Vec<u8>>,

pub(crate) icc_data: Vec<ICCChunk>
pub(crate) icc_data: Vec<ICCChunk>,
pub(crate) is_mjpeg: bool
}

impl<T> JpegDecoder<T>
Expand Down Expand Up @@ -189,7 +191,8 @@ where
headers_decoded: false,
seen_sof: false,
exif_data: None,
icc_data: vec![]
icc_data: vec![],
is_mjpeg: false
}
}
/// Decode a buffer already in memory
Expand Down Expand Up @@ -514,7 +517,7 @@ where
//APP(0) segment
Marker::APP(0) =>
{
let length = self.stream.get_u16_be_err()?;
let mut length = self.stream.get_u16_be_err()?;

if length < 2
{
Expand All @@ -523,6 +526,16 @@ where
)));
}
// skip for now
if length > 5 && self.stream.has(5)
{
let mut buffer = [0u8; 5];
self.stream.read_exact(&mut buffer).unwrap();
if &buffer == b"AVI1\0"
{
self.is_mjpeg = true;
}
length -= 5;
}
self.stream.skip((length - 2) as usize);

//parse_app(buf, m, &mut self.info)?;
Expand Down
19 changes: 19 additions & 0 deletions zune-jpeg/src/huffman.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/*
* Copyright (c) 2023.
*
* This software is free software;
*
* You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
*/

//! This file contains a single struct `HuffmanTable` that
//! stores Huffman tables needed during `BitStream` decoding.
#![allow(clippy::similar_names, clippy::module_name_repetitions)]
Expand Down Expand Up @@ -61,6 +69,17 @@ impl HuffmanTable
Ok(p)
}

/// Create a new huffman tables with values that aren't fixed
/// used by fill_mjpeg_tables
pub fn new_unfilled(
codes: &[u8; 17], values: &[u8], is_dc: bool, is_progressive: bool
) -> Result<HuffmanTable, DecodeErrors>
{
let mut buf = [0; 256];
buf[..values.len()].copy_from_slice(values);
HuffmanTable::new(codes, buf, is_dc, is_progressive)
}

/// Compute derived values for a Huffman table
///
/// This routine performs some validation checks on the table
Expand Down
13 changes: 13 additions & 0 deletions zune-jpeg/src/mcu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,19 @@ impl<T: ZReaderTrait> JpegDecoder<T>
mcu_width = ((self.info.width + 7) / 8) as usize;
mcu_height = ((self.info.height + 7) / 8) as usize;
}
if self.is_interleaved
&& self.input_colorspace.num_components() > 1
&& self.options.jpeg_get_out_colorspace().num_components() == 1
{
// For a specific set of images, e.g interleaved,
// when converting from YcbCr to grayscale, we need to
// take into account mcu height since the MCU decoding needs to take
// it into account for padding purposes and the post processor
// parses two rows per mcu width.
//
//TODO: Check if this test works over time
mcu_height /= self.h_max;
}

if self.input_colorspace.num_components() > self.components.len()
{
Expand Down
126 changes: 123 additions & 3 deletions zune-jpeg/src/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use zune_core::colorspace::ColorSpace;

use crate::components::SampleRatios;
use crate::errors::DecodeErrors;
use crate::huffman::HuffmanTable;
use crate::JpegDecoder;

/// Start of baseline DCT Huffman coding
Expand Down Expand Up @@ -292,9 +293,14 @@ pub(crate) fn setup_component_params<T: ZReaderTrait>(
));
}

// delete quantization tables, we'll extract them from the components when
// needed
img.qt_tables = [None, None, None, None];
if img.is_mjpeg
{
fill_default_mjpeg_tables(
img.is_progressive,
&mut img.dc_huffman_tables,
&mut img.ac_huffman_tables
);
}

Ok(())
}
Expand Down Expand Up @@ -328,3 +334,117 @@ pub fn calculate_padded_width(actual_width: usize, sub_sample: SampleRatios) ->
}
}
}

// https://www.loc.gov/preservation/digital/formats/fdd/fdd000063.shtml
// "Avery Lee, writing in the rec.video.desktop newsgroup in 2001, commented that "MJPEG, or at
// least the MJPEG in AVIs having the MJPG fourcc, is restricted JPEG with a fixed -- and
// *omitted* -- Huffman table. The JPEG must be YCbCr colorspace, it must be 4:2:2, and it must
// use basic Huffman encoding, not arithmetic or progressive.... You can indeed extract the
// MJPEG frames and decode them with a regular JPEG decoder, but you have to prepend the DHT
// segment to them, or else the decoder won't have any idea how to decompress the data.
// The exact table necessary is given in the OpenDML spec.""
pub fn fill_default_mjpeg_tables(
is_progressive: bool, dc_huffman_tables: &mut [Option<HuffmanTable>],
ac_huffman_tables: &mut [Option<HuffmanTable>]
)
{
// Section K.3.3
trace!("Filling with default mjpeg tables");

if dc_huffman_tables[0].is_none()
{
// Table K.3
dc_huffman_tables[0] = Some(
HuffmanTable::new_unfilled(
&[
0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
],
&[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B
],
true,
is_progressive
)
.unwrap()
);
}
if dc_huffman_tables[1].is_none()
{
// Table K.4
dc_huffman_tables[1] = Some(
HuffmanTable::new_unfilled(
&[
0x00, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00
],
&[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B
],
true,
is_progressive
)
.unwrap()
);
}
if ac_huffman_tables[0].is_none()
{
// Table K.5
ac_huffman_tables[0] = Some(
HuffmanTable::new_unfilled(
&[
0x00, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04,
0x00, 0x00, 0x01, 0x7D
],
&[
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13,
0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42,
0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A,
0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35,
0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67,
0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84,
0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3,
0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1,
0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4,
0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA
],
false,
is_progressive
)
.unwrap()
);
}
if ac_huffman_tables[1].is_none()
{
// Table K.6
ac_huffman_tables[1] = Some(
HuffmanTable::new_unfilled(
&[
0x00, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04,
0x00, 0x01, 0x02, 0x77
],
&[
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1,
0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24,
0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A,
0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96,
0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA,
0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9,
0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4,
0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA
],
false,
is_progressive
)
.unwrap()
);
}
}
5 changes: 5 additions & 0 deletions zune-tests/tests/jpeg.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,10 @@
"hash": 145881769686590241353562665580548083291,
"comment": "testing-bgr support",
"colorspace": "bgr"
},
{
"name": "mjpeg_huffman.jpg",
"hash": 309069272274389382301715239622093501038,
"comment": "Image needs to be filled with default huffman tables"
}
]

0 comments on commit 27d322c

Please sign in to comment.