Skip to content

Format Rust code #279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 24 additions & 20 deletions benches/decoding_benchmark.rs
Original file line number Diff line number Diff line change
@@ -18,24 +18,28 @@ fn read_metadata(image: &[u8]) -> ImageInfo {

fn main() {
let mut c = Criterion::default().configure_from_args();
c.bench_function("decode a 512x512 JPEG", |b| b.iter(|| {
read_image(include_bytes!("tower.jpg"))
}));

c.bench_function("decode a 512x512 progressive JPEG", |b| b.iter(|| {
read_image(include_bytes!("tower_progressive.jpg"))
}));

c.bench_function("decode a 512x512 grayscale JPEG", |b| b.iter(|| {
read_image(include_bytes!("tower_grayscale.jpg"))
}));

c.bench_function("extract metadata from an image", |b| b.iter(|| {
read_metadata(include_bytes!("tower.jpg"))
}));

c.bench_function("decode a 3072x2048 RGB Lossless JPEG", |b| b.iter(|| {
read_image(include_bytes!("../tests/reftest/images/lossless/1/jpeg_lossless_sel1-rgb.jpg"))
}));
c.bench_function("decode a 512x512 JPEG", |b| {
b.iter(|| read_image(include_bytes!("tower.jpg")))
});

c.bench_function("decode a 512x512 progressive JPEG", |b| {
b.iter(|| read_image(include_bytes!("tower_progressive.jpg")))
});

c.bench_function("decode a 512x512 grayscale JPEG", |b| {
b.iter(|| read_image(include_bytes!("tower_grayscale.jpg")))
});

c.bench_function("extract metadata from an image", |b| {
b.iter(|| read_metadata(include_bytes!("tower.jpg")))
});

c.bench_function("decode a 3072x2048 RGB Lossless JPEG", |b| {
b.iter(|| {
read_image(include_bytes!(
"../tests/reftest/images/lossless/1/jpeg_lossless_sel1-rgb.jpg"
))
})
});
c.final_summary();
}
}
21 changes: 11 additions & 10 deletions examples/decode.rs
Original file line number Diff line number Diff line change
@@ -33,26 +33,27 @@ fn main() {
jpeg::PixelFormat::L16 => {
encoder.set_depth(png::BitDepth::Sixteen);
encoder.set_color(png::ColorType::Grayscale);
},
jpeg::PixelFormat::RGB24 => {
}
jpeg::PixelFormat::RGB24 => {
encoder.set_depth(png::BitDepth::Eight);
encoder.set_color(png::ColorType::RGB);
},
}
jpeg::PixelFormat::CMYK32 => {
data = cmyk_to_rgb(&mut data);
encoder.set_depth(png::BitDepth::Eight);
encoder.set_color(png::ColorType::RGB)
},
}
jpeg::PixelFormat::L8 => {
encoder.set_depth(png::BitDepth::Eight);
encoder.set_color(png::ColorType::Grayscale);
},
}
}

encoder.write_header()
.expect("writing png header failed")
.write_image_data(&data)
.expect("png encoding failed");

encoder
.write_header()
.expect("writing png header failed")
.write_image_data(&data)
.expect("png encoding failed");
}

fn cmyk_to_rgb(input: &[u8]) -> Vec<u8> {
37 changes: 22 additions & 15 deletions src/arch/wasm.rs
Original file line number Diff line number Diff line change
@@ -89,14 +89,14 @@ fn transpose8(data: &mut [v128; 8]) {
// A0 B0 A1 B1 ...
// dABCDll contains elements from the lower quarter (ll) of vectors A, B, C, D, interleaved -
// A0 B0 C0 D0 A1 B1 C1 D1 ...
let d01l = i16x8_shuffle::<0, 8, 1, 9, 2, 10, 3, 11>(data[0], data[1]);
let d23l = i16x8_shuffle::<0, 8, 1, 9, 2, 10, 3, 11>(data[2], data[3]);
let d45l = i16x8_shuffle::<0, 8, 1, 9, 2, 10, 3, 11>(data[4], data[5]);
let d67l = i16x8_shuffle::<0, 8, 1, 9, 2, 10, 3, 11>(data[6], data[7]);
let d01h = i16x8_shuffle::<4, 12, 5, 13, 6, 14, 7, 15>(data[0], data[1]);
let d23h = i16x8_shuffle::<4, 12, 5, 13, 6, 14, 7, 15>(data[2], data[3]);
let d45h = i16x8_shuffle::<4, 12, 5, 13, 6, 14, 7, 15>(data[4], data[5]);
let d67h = i16x8_shuffle::<4, 12, 5, 13, 6, 14, 7, 15>(data[6], data[7]);
let d01l = i16x8_shuffle::<0, 8, 1, 9, 2, 10, 3, 11>(data[0], data[1]);
let d23l = i16x8_shuffle::<0, 8, 1, 9, 2, 10, 3, 11>(data[2], data[3]);
let d45l = i16x8_shuffle::<0, 8, 1, 9, 2, 10, 3, 11>(data[4], data[5]);
let d67l = i16x8_shuffle::<0, 8, 1, 9, 2, 10, 3, 11>(data[6], data[7]);
let d01h = i16x8_shuffle::<4, 12, 5, 13, 6, 14, 7, 15>(data[0], data[1]);
let d23h = i16x8_shuffle::<4, 12, 5, 13, 6, 14, 7, 15>(data[2], data[3]);
let d45h = i16x8_shuffle::<4, 12, 5, 13, 6, 14, 7, 15>(data[4], data[5]);
let d67h = i16x8_shuffle::<4, 12, 5, 13, 6, 14, 7, 15>(data[6], data[7]);

// Operating on 32-bits will interleave *consecutive pairs* of 16-bit integers.
let d0123ll = i32x4_shuffle::<0, 4, 1, 5>(d01l, d23l);
@@ -176,10 +176,7 @@ pub fn dequantize_and_idct_block_8x8(
// `output_linestride * i + 7` < output.len(), so all accesses are in-bounds.
unsafe {
v128_store64_lane::<0>(
u8x16_narrow_i16x8(
i16x8_shr(data_with_offset, SHIFT + 3),
i16x8_splat(0),
),
u8x16_narrow_i16x8(i16x8_shr(data_with_offset, SHIFT + 3), i16x8_splat(0)),
output.as_mut_ptr().wrapping_add(output_linestride * i) as *mut _,
);
}
@@ -188,8 +185,12 @@ pub fn dequantize_and_idct_block_8x8(

#[cfg(target_arch = "wasm32")]
#[target_feature(enable = "simd128")]
pub fn color_convert_line_ycbcr(y_slice: &[u8], cb_slice: &[u8], cr_slice: &[u8], output: &mut [u8]) -> usize {

pub fn color_convert_line_ycbcr(
y_slice: &[u8],
cb_slice: &[u8],
cr_slice: &[u8],
output: &mut [u8],
) -> usize {
assert!(output.len() % 3 == 0);
let num = output.len() / 3;
assert!(num <= y_slice.len());
@@ -242,6 +243,7 @@ pub fn color_convert_line_ycbcr(y_slice: &[u8], cb_slice: &[u8], cr_slice: &[u8]

// Shuffle rrrrrrrrggggggggbbbbbbbb to rgbrgbrgb...

#[rustfmt::skip]
let rg_lanes = i8x16_shuffle::<0, 16,
1, 17,
2, 18,
@@ -251,13 +253,15 @@ pub fn color_convert_line_ycbcr(y_slice: &[u8], cb_slice: &[u8], cr_slice: &[u8]
6, 22,
7, 23>(r, g);

#[rustfmt::skip]
let rgb_low = i8x16_shuffle::<0, 1, 16, // r0, g0, b0
2, 3, 17, // r1, g1, b1
4, 5, 18, // r2, g2, b2
6, 7, 19, // r3, g3, b3
8, 9, 20, // r4, g4, b4
10>(rg_lanes, b); // r5

#[rustfmt::skip]
let rgb_hi = i8x16_shuffle::<11, 21, 12, // g5, b5, r6
13, 22, 14, // g6, b6, r7
15, 23, 0, // g7, b7, --
@@ -269,7 +273,10 @@ pub fn color_convert_line_ycbcr(y_slice: &[u8], cb_slice: &[u8], cr_slice: &[u8]
// `output.len() - 1`.
unsafe {
v128_store(output.as_mut_ptr().wrapping_add(24 * i) as *mut _, rgb_low);
v128_store64_lane::<0>(rgb_hi, output.as_mut_ptr().wrapping_add(24 * i + 16) as *mut _);
v128_store64_lane::<0>(
rgb_hi,
output.as_mut_ptr().wrapping_add(24 * i + 16) as *mut _,
);
}
}

2 changes: 1 addition & 1 deletion src/decoder.rs
Original file line number Diff line number Diff line change
@@ -790,7 +790,7 @@ impl<R: Read> Decoder<R> {
}
}

#[allow(clippy::type_complexity)]
#[allow(clippy::type_complexity)]
fn decode_scan(
&mut self,
frame: &FrameInfo,
58 changes: 38 additions & 20 deletions src/huffman.rs
Original file line number Diff line number Diff line change
@@ -3,10 +3,11 @@ use alloc::vec;
use alloc::vec::Vec;
use core::iter;
use std::io::Read;
use crate::read_u8;

use crate::error::{Error, Result};
use crate::marker::Marker;
use crate::parser::ScanInfo;
use crate::read_u8;

const LUT_BITS: u8 = 8;

@@ -38,11 +39,10 @@ impl HuffmanDecoder {
if size > 0 {
self.consume_bits(size);
Ok(value)
}
else {
} else {
let bits = self.peek_bits(16);

for i in LUT_BITS .. 16 {
for i in LUT_BITS..16 {
let code = (bits >> (15 - i)) as i32;

if code <= table.maxcode[i as usize] {
@@ -57,7 +57,11 @@ impl HuffmanDecoder {
}
}

pub fn decode_fast_ac<R: Read>(&mut self, reader: &mut R, table: &HuffmanTable) -> Result<Option<(i16, u8)>> {
pub fn decode_fast_ac<R: Read>(
&mut self,
reader: &mut R,
table: &HuffmanTable,
) -> Result<Option<(i16, u8)>> {
if let Some(ref ac_lut) = table.ac_lut {
if self.num_bits < LUT_BITS {
self.read_bits(reader)?;
@@ -144,8 +148,12 @@ impl HuffmanDecoder {
}

match next_byte {
0x00 => return Err(Error::Format("FF 00 found where marker was expected".to_owned())),
_ => self.marker = Some(Marker::from_u8(next_byte).unwrap()),
0x00 => {
return Err(Error::Format(
"FF 00 found where marker was expected".to_owned(),
))
}
_ => self.marker = Some(Marker::from_u8(next_byte).unwrap()),
}

continue;
@@ -198,7 +206,7 @@ impl HuffmanTable {
let mut maxcode = [-1i32; 16];
let mut j = 0;

for i in 0 .. 16 {
for i in 0..16 {
if bits[i] != 0 {
delta[i] = j as i32 - huffcode[j] as i32;
j += bits[i] as usize;
@@ -209,7 +217,11 @@ impl HuffmanTable {
// Build a lookup table for faster decoding.
let mut lut = [(0u8, 0u8); 1 << LUT_BITS];

for (i, &size) in huffsize.iter().enumerate().filter(|&(_, &size)| size <= LUT_BITS) {
for (i, &size) in huffsize
.iter()
.enumerate()
.filter(|&(_, &size)| size <= LUT_BITS)
{
let bits_remaining = LUT_BITS - size;
let start = (huffcode[i] << bits_remaining) as usize;

@@ -231,15 +243,17 @@ impl HuffmanTable {
let magnitude_category = value & 0x0f;

if magnitude_category > 0 && size + magnitude_category <= LUT_BITS {
let unextended_ac_value = (((i << size) & ((1 << LUT_BITS) - 1)) >> (LUT_BITS - magnitude_category)) as u16;
let unextended_ac_value = (((i << size) & ((1 << LUT_BITS) - 1))
>> (LUT_BITS - magnitude_category))
as u16;
let ac_value = extend(unextended_ac_value, magnitude_category);

table[i] = (ac_value, (run_length << 4) | (size + magnitude_category));
}
}

Some(table)
},
}
};

Ok(HuffmanTable {
@@ -255,12 +269,13 @@ impl HuffmanTable {
// Section C.2
fn derive_huffman_codes(bits: &[u8; 16]) -> Result<(Vec<u16>, Vec<u8>)> {
// Figure C.1
let huffsize = bits.iter()
.enumerate()
.fold(Vec::new(), |mut acc, (i, &value)| {
acc.extend(iter::repeat((i + 1) as u8).take(value as usize));
acc
});
let huffsize = bits
.iter()
.enumerate()
.fold(Vec::new(), |mut acc, (i, &value)| {
acc.extend(iter::repeat((i + 1) as u8).take(value as usize));
acc
});

// Figure C.2
let mut huffcode = vec![0u16; huffsize.len()];
@@ -292,9 +307,12 @@ fn derive_huffman_codes(bits: &[u8; 16]) -> Result<(Vec<u16>, Vec<u8>)> {
// 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(scan: &ScanInfo,
dc_huffman_tables: &mut[Option<HuffmanTable>],
ac_huffman_tables: &mut[Option<HuffmanTable>]) {
#[rustfmt::skip]
pub fn fill_default_mjpeg_tables(
scan: &ScanInfo,
dc_huffman_tables: &mut[Option<HuffmanTable>],
ac_huffman_tables: &mut[Option<HuffmanTable>],
) {
// Section K.3.3

if dc_huffman_tables[0].is_none() && scan.dc_table_indices.iter().any(|&i| i == 0) {
3 changes: 2 additions & 1 deletion src/idct.rs
Original file line number Diff line number Diff line change
@@ -560,7 +560,8 @@ fn dequantize_and_idct_block_1x1(
) {
debug_assert_eq!(coefficients.len(), 64);

let s0 = (Wrapping(coefficients[0] as i32 * quantization_table[0] as i32) + Wrapping(128 * 8)) / Wrapping(8);
let s0 = (Wrapping(coefficients[0] as i32 * quantization_table[0] as i32) + Wrapping(128 * 8))
/ Wrapping(8);
output[0] = stbi_clamp(s0);
}

4 changes: 2 additions & 2 deletions src/marker.rs
Original file line number Diff line number Diff line change
@@ -58,15 +58,15 @@ pub enum Marker {
impl Marker {
pub fn has_length(self) -> bool {
use self::Marker::*;
! matches!(self, RST(..) | SOI | EOI | TEM)
!matches!(self, RST(..) | SOI | EOI | TEM)
}

pub fn from_u8(n: u8) -> Option<Marker> {
use self::Marker::*;
match n {
0x00 => None, // Byte stuffing
0x01 => Some(TEM),
0x02 ..= 0xBF => Some(RES),
0x02..=0xBF => Some(RES),
0xC0 => Some(SOF(0)),
0xC1 => Some(SOF(1)),
0xC2 => Some(SOF(2)),
321 changes: 237 additions & 84 deletions src/parser.rs

Large diffs are not rendered by default.

208 changes: 125 additions & 83 deletions src/upsampler.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;

use crate::error::{Error, Result, UnsupportedFeature};
use crate::parser::Component;

pub struct Upsampler {
components: Vec<UpsamplerComponent>,
line_buffer_size: usize
line_buffer_size: usize,
}

struct UpsamplerComponent {
@@ -17,17 +18,33 @@ struct UpsamplerComponent {
}

impl Upsampler {
pub fn new(components: &[Component], output_width: u16, output_height: u16) -> Result<Upsampler> {
let h_max = components.iter().map(|c| c.horizontal_sampling_factor).max().unwrap();
let v_max = components.iter().map(|c| c.vertical_sampling_factor).max().unwrap();
pub fn new(
components: &[Component],
output_width: u16,
output_height: u16,
) -> Result<Upsampler> {
let h_max = components
.iter()
.map(|c| c.horizontal_sampling_factor)
.max()
.unwrap();
let v_max = components
.iter()
.map(|c| c.vertical_sampling_factor)
.max()
.unwrap();
let mut upsampler_components = Vec::with_capacity(components.len());

for component in components {
let upsampler = choose_upsampler((component.horizontal_sampling_factor,
component.vertical_sampling_factor),
(h_max, v_max),
output_width,
output_height)?;
let upsampler = choose_upsampler(
(
component.horizontal_sampling_factor,
component.vertical_sampling_factor,
),
(h_max, v_max),
output_width,
output_height,
)?;
upsampler_components.push(UpsamplerComponent {
upsampler,
width: component.size.width as usize,
@@ -36,28 +53,38 @@ impl Upsampler {
});
}

let buffer_size = components.iter().map(|c| c.size.width).max().unwrap() as usize * h_max as usize;
let buffer_size =
components.iter().map(|c| c.size.width).max().unwrap() as usize * h_max as usize;

Ok(Upsampler {
components: upsampler_components,
line_buffer_size: buffer_size
line_buffer_size: buffer_size,
})
}

pub fn upsample_and_interleave_row(&self, component_data: &[Vec<u8>], row: usize, output_width: usize, output: &mut [u8], color_convert: fn(&[Vec<u8>], &mut [u8])) {
pub fn upsample_and_interleave_row(
&self,
component_data: &[Vec<u8>],
row: usize,
output_width: usize,
output: &mut [u8],
color_convert: fn(&[Vec<u8>], &mut [u8]),
) {
let component_count = component_data.len();
let mut line_buffers = vec![vec![0u8; self.line_buffer_size]; component_count];

debug_assert_eq!(component_count, self.components.len());

for (i, component) in self.components.iter().enumerate() {
component.upsampler.upsample_row(&component_data[i],
component.width,
component.height,
component.row_stride,
row,
output_width,
&mut line_buffers[i]);
component.upsampler.upsample_row(
&component_data[i],
component.width,
component.height,
component.row_stride,
row,
output_width,
&mut line_buffers[i],
);
}
color_convert(&line_buffers, output);
}
@@ -70,13 +97,15 @@ struct UpsamplerH2V2;

struct UpsamplerGeneric {
horizontal_scaling_factor: u8,
vertical_scaling_factor: u8
vertical_scaling_factor: u8,
}

fn choose_upsampler(sampling_factors: (u8, u8),
max_sampling_factors: (u8, u8),
output_width: u16,
output_height: u16) -> Result<Box<dyn Upsample + Sync>> {
fn choose_upsampler(
sampling_factors: (u8, u8),
max_sampling_factors: (u8, u8),
output_width: u16,
output_height: u16,
) -> Result<Box<dyn Upsample + Sync>> {
let h1 = sampling_factors.0 == max_sampling_factors.0 || output_width == 1;
let v1 = sampling_factors.1 == max_sampling_factors.1 || output_height == 1;
let h2 = sampling_factors.0 * 2 == max_sampling_factors.0;
@@ -106,41 +135,47 @@ fn choose_upsampler(sampling_factors: (u8, u8),

#[allow(clippy::too_many_arguments)]
trait Upsample {
fn upsample_row(&self,
input: &[u8],
input_width: usize,
input_height: usize,
row_stride: usize,
row: usize,
output_width: usize,
output: &mut [u8]);
fn upsample_row(
&self,
input: &[u8],
input_width: usize,
input_height: usize,
row_stride: usize,
row: usize,
output_width: usize,
output: &mut [u8],
);
}

impl Upsample for UpsamplerH1V1 {
fn upsample_row(&self,
input: &[u8],
_input_width: usize,
_input_height: usize,
row_stride: usize,
row: usize,
output_width: usize,
output: &mut [u8]) {
let input = &input[row * row_stride ..];
fn upsample_row(
&self,
input: &[u8],
_input_width: usize,
_input_height: usize,
row_stride: usize,
row: usize,
output_width: usize,
output: &mut [u8],
) {
let input = &input[row * row_stride..];

output[..output_width].copy_from_slice(&input[..output_width]);
}
}

impl Upsample for UpsamplerH2V1 {
fn upsample_row(&self,
input: &[u8],
input_width: usize,
_input_height: usize,
row_stride: usize,
row: usize,
_output_width: usize,
output: &mut [u8]) {
let input = &input[row * row_stride ..];
fn upsample_row(
&self,
input: &[u8],
input_width: usize,
_input_height: usize,
row_stride: usize,
row: usize,
_output_width: usize,
output: &mut [u8],
) {
let input = &input[row * row_stride..];

if input_width == 1 {
output[0] = input[0];
@@ -151,33 +186,36 @@ impl Upsample for UpsamplerH2V1 {
output[0] = input[0];
output[1] = ((input[0] as u32 * 3 + input[1] as u32 + 2) >> 2) as u8;

for i in 1 .. input_width - 1 {
for i in 1..input_width - 1 {
let sample = 3 * input[i] as u32 + 2;
output[i * 2] = ((sample + input[i - 1] as u32) >> 2) as u8;
output[i * 2] = ((sample + input[i - 1] as u32) >> 2) as u8;
output[i * 2 + 1] = ((sample + input[i + 1] as u32) >> 2) as u8;
}

output[(input_width - 1) * 2] = ((input[input_width - 1] as u32 * 3 + input[input_width - 2] as u32 + 2) >> 2) as u8;
output[(input_width - 1) * 2] =
((input[input_width - 1] as u32 * 3 + input[input_width - 2] as u32 + 2) >> 2) as u8;
output[(input_width - 1) * 2 + 1] = input[input_width - 1];
}
}

impl Upsample for UpsamplerH1V2 {
fn upsample_row(&self,
input: &[u8],
_input_width: usize,
input_height: usize,
row_stride: usize,
row: usize,
output_width: usize,
output: &mut [u8]) {
fn upsample_row(
&self,
input: &[u8],
_input_width: usize,
input_height: usize,
row_stride: usize,
row: usize,
output_width: usize,
output: &mut [u8],
) {
let row_near = row as f32 / 2.0;
// If row_near's fractional is 0.0 we want row_far to be the previous row and if it's 0.5 we
// want it to be the next row.
let row_far = (row_near + row_near.fract() * 3.0 - 0.25).min((input_height - 1) as f32);

let input_near = &input[row_near as usize * row_stride ..];
let input_far = &input[row_far as usize * row_stride ..];
let input_near = &input[row_near as usize * row_stride..];
let input_far = &input[row_far as usize * row_stride..];

let output = &mut output[..output_width];
let input_near = &input_near[..output_width];
@@ -189,21 +227,23 @@ impl Upsample for UpsamplerH1V2 {
}

impl Upsample for UpsamplerH2V2 {
fn upsample_row(&self,
input: &[u8],
input_width: usize,
input_height: usize,
row_stride: usize,
row: usize,
_output_width: usize,
output: &mut [u8]) {
fn upsample_row(
&self,
input: &[u8],
input_width: usize,
input_height: usize,
row_stride: usize,
row: usize,
_output_width: usize,
output: &mut [u8],
) {
let row_near = row as f32 / 2.0;
// If row_near's fractional is 0.0 we want row_far to be the previous row and if it's 0.5 we
// want it to be the next row.
let row_far = (row_near + row_near.fract() * 3.0 - 0.25).min((input_height - 1) as f32);

let input_near = &input[row_near as usize * row_stride ..];
let input_far = &input[row_far as usize * row_stride ..];
let input_near = &input[row_near as usize * row_stride..];
let input_far = &input[row_far as usize * row_stride..];

if input_width == 1 {
let value = ((3 * input_near[0] as u32 + input_far[0] as u32 + 2) >> 2) as u8;
@@ -215,12 +255,12 @@ impl Upsample for UpsamplerH2V2 {
let mut t1 = 3 * input_near[0] as u32 + input_far[0] as u32;
output[0] = ((t1 + 2) >> 2) as u8;

for i in 1 .. input_width {
for i in 1..input_width {
let t0 = t1;
t1 = 3 * input_near[i] as u32 + input_far[i] as u32;

output[i * 2 - 1] = ((3 * t0 + t1 + 8) >> 4) as u8;
output[i * 2] = ((3 * t1 + t0 + 8) >> 4) as u8;
output[i * 2] = ((3 * t1 + t0 + 8) >> 4) as u8;
}

output[input_width * 2 - 1] = ((t1 + 2) >> 2) as u8;
@@ -229,14 +269,16 @@ impl Upsample for UpsamplerH2V2 {

impl Upsample for UpsamplerGeneric {
// Uses nearest neighbor sampling
fn upsample_row(&self,
input: &[u8],
input_width: usize,
_input_height: usize,
row_stride: usize,
row: usize,
_output_width: usize,
output: &mut [u8]) {
fn upsample_row(
&self,
input: &[u8],
input_width: usize,
_input_height: usize,
row_stride: usize,
row: usize,
_output_width: usize,
output: &mut [u8],
) {
let mut index = 0;
let start = (row / self.vertical_scaling_factor as usize) * row_stride;
let input = &input[start..(start + input_width)];
24 changes: 19 additions & 5 deletions src/worker/immediate.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use alloc::vec;
use alloc::vec::Vec;
use core::mem;

use super::{RowData, Worker};
use crate::alloc::sync::Arc;
use crate::decoder::MAX_COMPONENTS;
use crate::error::Result;
use crate::idct::dequantize_and_idct_block;
use crate::alloc::sync::Arc;
use crate::parser::Component;
use super::{RowData, Worker};

pub struct ImmediateWorker {
offsets: [usize; MAX_COMPONENTS],
@@ -31,7 +32,13 @@ impl ImmediateWorker {
assert!(self.results[data.index].is_empty());

self.offsets[data.index] = 0;
self.results[data.index].resize(data.component.block_size.width as usize * data.component.block_size.height as usize * data.component.dct_scale * data.component.dct_scale, 0u8);
self.results[data.index].resize(
data.component.block_size.width as usize
* data.component.block_size.height as usize
* data.component.dct_scale
* data.component.dct_scale,
0u8,
);
self.components[data.index] = Some(data.component);
self.quantization_tables[data.index] = Some(data.quantization_table);
}
@@ -41,7 +48,8 @@ impl ImmediateWorker {

let component = self.components[index].as_ref().unwrap();
let quantization_table = self.quantization_tables[index].as_ref().unwrap();
let block_count = component.block_size.width as usize * component.vertical_sampling_factor as usize;
let block_count =
component.block_size.width as usize * component.vertical_sampling_factor as usize;
let line_stride = component.block_size.width as usize * component.dct_scale;

assert_eq!(data.len(), block_count * 64);
@@ -53,7 +61,13 @@ impl ImmediateWorker {
let coefficients = data[i * 64..(i + 1) * 64].try_into().unwrap();
let output = &mut self.results[index][self.offsets[index] + y * line_stride + x..];

dequantize_and_idct_block(component.dct_scale, coefficients, quantization_table, line_stride, output);
dequantize_and_idct_block(
component.dct_scale,
coefficients,
quantization_table,
line_stride,
output,
);
}

self.offsets[index] += block_count * component.dct_scale * component.dct_scale;
19 changes: 12 additions & 7 deletions tests/lib.rs
Original file line number Diff line number Diff line change
@@ -2,18 +2,21 @@ extern crate jpeg_decoder as jpeg;
extern crate png;
extern crate walkdir;

use std::path::Path;
use std::fs::File;
use std::path::Path;

mod common;
mod crashtest;
mod reftest;

#[test]
#[cfg(all(target_family="wasm", target_os="unknown"))]
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
#[wasm_bindgen_test::wasm_bindgen_test]
fn included_file() {
const FILE: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/reftest/images/mozilla/jpg-progressive.jpg"));
const FILE: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/reftest/images/mozilla/jpg-progressive.jpg"
));

let mut data = FILE;
let mut decoder = jpeg::Decoder::new(&mut data);
@@ -33,7 +36,11 @@ fn included_file() {

#[test]
fn read_info() {
let path = Path::new("tests").join("reftest").join("images").join("mozilla").join("jpg-progressive.jpg");
let path = Path::new("tests")
.join("reftest")
.join("images")
.join("mozilla")
.join("jpg-progressive.jpg");

let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap());
let ref_data = decoder.decode().unwrap();
@@ -68,9 +75,7 @@ fn read_icc_profile() {
// Test if chunks are concatenated in the correct order
#[test]
fn read_icc_profile_random_order() {
let path = Path::new("tests")
.join("icc")
.join("icc_chunk_order.jpeg");
let path = Path::new("tests").join("icc").join("icc_chunk_order.jpeg");

let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap());
decoder.decode().unwrap();
7 changes: 6 additions & 1 deletion tests/rayon-0.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
//! Must be a separate test because it modifies the _global_ rayon pool.
use std::{fs::File, path::Path};

use jpeg_decoder::Decoder;

#[test]
fn decoding_in_global_pool() {
let path = Path::new("tests").join("reftest").join("images").join("mozilla").join("jpg-progressive.jpg");
let path = Path::new("tests")
.join("reftest")
.join("images")
.join("mozilla")
.join("jpg-progressive.jpg");

rayon::ThreadPoolBuilder::new()
.num_threads(1)
7 changes: 6 additions & 1 deletion tests/rayon-1.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
//! Must be a separate test because it modifies the _global_ rayon pool.
use std::{fs::File, path::Path};

use jpeg_decoder::Decoder;

#[test]
fn decoding_in_fetched_global_pool() {
let path = Path::new("tests").join("reftest").join("images").join("mozilla").join("jpg-progressive.jpg");
let path = Path::new("tests")
.join("reftest")
.join("images")
.join("mozilla")
.join("jpg-progressive.jpg");

rayon::ThreadPoolBuilder::new()
.num_threads(1)
5 changes: 3 additions & 2 deletions tests/rayon-2.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Must be a separate test because it modifies the _global_ rayon pool.
use std::{fs::File, path::Path};

use jpeg_decoder::Decoder;

#[test]
@@ -17,6 +18,6 @@ fn decoding_in_global_pool() {
let mut decoder = Decoder::new(File::open(&path).unwrap());
let _ = decoder.decode().unwrap();
});
}).collect();
})
.collect();
}

7 changes: 6 additions & 1 deletion tests/rayon.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use std::{fs::File, path::Path};

use jpeg_decoder::Decoder;

#[test]
fn decoding_in_limited_threadpool_does_not_deadlock() {
let path = Path::new("tests").join("reftest").join("images").join("mozilla").join("jpg-progressive.jpg");
let path = Path::new("tests")
.join("reftest")
.join("images")
.join("mozilla")
.join("jpg-progressive.jpg");

let pool = rayon::ThreadPoolBuilder::new()
.num_threads(1)
97 changes: 65 additions & 32 deletions tests/reftest/mod.rs
Original file line number Diff line number Diff line change
@@ -19,9 +19,14 @@ fn reftest() {
fn reftest_scaled() {
let base = &Path::new("tests").join("reftest").join("images");
reftest_scaled_file(&base.join("rgb.jpg"), 500, 333, &base.join("rgb.png"));
reftest_scaled_file(&base.join("rgb.jpg"), 250, 167, &base.join("rgb_250x167.png"));
reftest_scaled_file(&base.join("rgb.jpg"), 125, 84, &base.join("rgb_125x84.png"));
reftest_scaled_file(&base.join("rgb.jpg"), 63, 42, &base.join("rgb_63x42.png"));
reftest_scaled_file(
&base.join("rgb.jpg"),
250,
167,
&base.join("rgb_250x167.png"),
);
reftest_scaled_file(&base.join("rgb.jpg"), 125, 84, &base.join("rgb_125x84.png"));
reftest_scaled_file(&base.join("rgb.jpg"), 63, 42, &base.join("rgb_63x42.png"));
}

fn reftest_file(path: &Path) {
@@ -39,7 +44,9 @@ fn reftest_scaled_file(path: &Path, width: u16, height: u16, ref_path: &Path) {
}

fn reftest_decoder<T: std::io::Read>(mut decoder: jpeg::Decoder<T>, path: &Path, ref_path: &Path) {
let mut data = decoder.decode().expect(&format!("failed to decode file: {:?}", path));
let mut data = decoder
.decode()
.expect(&format!("failed to decode file: {:?}", path));
let info = decoder.info().unwrap();
let mut pixel_format = info.pixel_format;

@@ -55,66 +62,92 @@ fn reftest_decoder<T: std::io::Read>(mut decoder: jpeg::Decoder<T>, path: &Path,
// disable the default 8bit output of png v0.16.8 (fixed in master branch of png)
decoder.set_transformations(png::Transformations::EXPAND);
}

let (ref_info, mut ref_reader) = decoder.read_info().expect("png failed to read info");

assert_eq!(ref_info.width, info.width as u32);
assert_eq!(ref_info.height, info.height as u32);

let mut ref_data = vec![0; ref_info.buffer_size()];
ref_reader.next_frame(&mut ref_data).expect("png decode failed");
ref_reader
.next_frame(&mut ref_data)
.expect("png decode failed");
let mut ref_pixel_format = ref_info.color_type;

if ref_pixel_format == png::ColorType::RGBA {
if ref_pixel_format == png::ColorType::RGBA {
ref_data = rgba_to_rgb(&ref_data);
ref_pixel_format = png::ColorType::RGB;
}

let (refdata_16, data_u16) : (Vec<u16>, Vec<u16>) = match pixel_format {
let (refdata_16, data_u16): (Vec<u16>, Vec<u16>) = match pixel_format {
jpeg::PixelFormat::L8 => {
assert_eq!(ref_pixel_format, png::ColorType::Grayscale);
assert_eq!(ref_info.bit_depth, png::BitDepth::Eight);
(ref_data.iter().map(|x| *x as u16).collect(), data.iter().map(|x| *x as u16).collect())
},
(
ref_data.iter().map(|x| *x as u16).collect(),
data.iter().map(|x| *x as u16).collect(),
)
}
jpeg::PixelFormat::L16 => {
assert_eq!(ref_pixel_format, png::ColorType::Grayscale);
assert_eq!(ref_info.bit_depth, png::BitDepth::Sixteen);
(ref_data.chunks_exact(2).map(|a| u16::from_be_bytes([a[0],a[1]])).collect(),
data.chunks_exact(2).map(|a| u16::from_ne_bytes([a[0],a[1]])).collect())
},
(
ref_data
.chunks_exact(2)
.map(|a| u16::from_be_bytes([a[0], a[1]]))
.collect(),
data.chunks_exact(2)
.map(|a| u16::from_ne_bytes([a[0], a[1]]))
.collect(),
)
}
jpeg::PixelFormat::RGB24 => {
assert_eq!(ref_pixel_format, png::ColorType::RGB);
assert_eq!(ref_info.bit_depth, png::BitDepth::Eight);
(ref_data.iter().map(|x| *x as u16).collect(), data.iter().map(|x| *x as u16).collect())
},
(
ref_data.iter().map(|x| *x as u16).collect(),
data.iter().map(|x| *x as u16).collect(),
)
}
_ => panic!(),
};

assert_eq!(data_u16.len(), refdata_16.len());
let mut max_diff = 0;
let pixels: Vec<u8> = data_u16.iter().zip(refdata_16.iter()).map(|(&a, &b)| {
let diff = (a as isize - b as isize).abs();
max_diff = cmp::max(diff, max_diff);

if (info.coding_process != jpeg::CodingProcess::Lossless && diff <= 3) || diff == 0 {
// White for correct
0xFF
} else {
// "1100" in the RGBA channel with an error for an incorrect value
// This results in some number of C0 and FFs, which is much more
// readable (and distinguishable) than the previous difference-wise
// scaling but does not require reconstructing the actual RGBA pixel.
0xC0
}
}).collect();
let pixels: Vec<u8> = data_u16
.iter()
.zip(refdata_16.iter())
.map(|(&a, &b)| {
let diff = (a as isize - b as isize).abs();
max_diff = cmp::max(diff, max_diff);

if (info.coding_process != jpeg::CodingProcess::Lossless && diff <= 3) || diff == 0 {
// White for correct
0xFF
} else {
// "1100" in the RGBA channel with an error for an incorrect value
// This results in some number of C0 and FFs, which is much more
// readable (and distinguishable) than the previous difference-wise
// scaling but does not require reconstructing the actual RGBA pixel.
0xC0
}
})
.collect();

if pixels.iter().any(|&a| a < 255) {
let output_path = path.with_file_name(format!("{}-diff.png", path.file_stem().unwrap().to_str().unwrap()));
let output_path = path.with_file_name(format!(
"{}-diff.png",
path.file_stem().unwrap().to_str().unwrap()
));
let output = File::create(&output_path).unwrap();
let mut encoder = png::Encoder::new(output, info.width as u32, info.height as u32);
encoder.set_depth(png::BitDepth::Eight);
encoder.set_color(ref_pixel_format);
encoder.write_header().expect("png failed to write header").write_image_data(&pixels).expect("png failed to write data");
encoder
.write_header()
.expect("png failed to write header")
.write_image_data(&pixels)
.expect("png failed to write data");

panic!("decoding difference: {output_path:?}, maximum difference was {max_diff}");
}