Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
bd7cbd3
initial
vsaase Jul 25, 2021
9984e28
fix explicit panic
vsaase Jul 25, 2021
e099592
no decoding error
vsaase Jul 25, 2021
7ea804f
diffing jpg and png example works
vsaase Jul 26, 2021
6823490
diffing jpeg and png works
vsaase Jul 26, 2021
fb2c126
lossless prediction decoding
vsaase Jul 27, 2021
4535363
use public png lib
vsaase Jul 27, 2021
1919855
tests pass
vsaase Jul 28, 2021
eeba223
generalize png output in 16bit case
vsaase Jul 28, 2021
881cb6f
isize to usize
vsaase Jul 28, 2021
4005520
comparison in hex editor seems fine
vsaase Jul 29, 2021
5958a08
use u16
vsaase Jul 29, 2021
1146cca
tests passing
vsaase Jul 29, 2021
56383b1
full precision calculations
vsaase Jul 29, 2021
527f935
all predictors working
vsaase Jul 30, 2021
3ac2364
tests pass for lossless RGB
vsaase Jul 30, 2021
ef52153
make coding process public
vsaase Jul 30, 2021
e9dc16e
cleanup
vsaase Jul 30, 2021
9b69bef
cleanup
vsaase Jul 30, 2021
53f7c05
decode returns big endian for 16bit jpeg
vsaase Jul 31, 2021
d28dad0
changelog and error on other precisions
vsaase Jul 31, 2021
fa2e735
lossless specific image computing
vsaase Jul 31, 2021
a4d33e6
use u16 only for lossless
vsaase Jul 31, 2021
f6c94fe
clean
vsaase Jul 31, 2021
ec505b6
changelog
vsaase Jul 31, 2021
e7f05cc
resolving comments to pull request
vsaase Jul 31, 2021
ac55952
change MRSV to 1.36
vsaase Aug 1, 2021
ce56298
change MRSV to 1.36
vsaase Aug 1, 2021
8223775
native endian output for 16bit decoding
vsaase Aug 2, 2021
6a032d6
also change to native endian in tests
vsaase Aug 2, 2021
9627436
add benchmark
vsaase Aug 2, 2021
770a5a2
performance regression
vsaase Aug 2, 2021
a904088
better to iterate columns in inner loop
vsaase Aug 2, 2021
d719b19
cleanup
vsaase Aug 2, 2021
6add112
fix comment
vsaase Aug 3, 2021
246d1b9
update workflow to 1.36.0
vsaase Aug 5, 2021
a094090
remove unused imports
vsaase Aug 19, 2021
4e18d8d
fix wrong vec initialization
vsaase Aug 19, 2021
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
6 changes: 3 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

strategy:
matrix:
rust: ["1.34.2", stable, beta, nightly]
rust: ["1.36.0", stable, beta, nightly]
features: ["", "rayon"]
command: [test, benchmark]

Expand All @@ -29,11 +29,11 @@ jobs:
- name: test
run: >
cargo test --tests --benches --no-default-features --features "$FEATURES"
if: ${{ matrix.command == 'test' && matrix.rust != '1.34.2' }}
if: ${{ matrix.command == 'test' && matrix.rust != '1.36.0' }}
env:
FEATURES: ${{ matrix.features }}
- name: benchmark
run: cargo bench --bench decoding_benchmark --no-default-features --features "$FEATURES" -- --warm-up-time 1 --measurement-time 1 --sample-size 25
if: ${{ matrix.command == 'benchmark' && matrix.rust != '1.34.2' }}
if: ${{ matrix.command == 'benchmark' && matrix.rust != '1.36.0' }}
env:
FEATURES: ${{ matrix.features }}
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

- Added Lossless JPEG support
- Minimum supported rust version changed to 1.36

## v0.1.22 (2021-01-27)

- Fix panic on jpeg without frames.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ fn main() {
```

## Requirements
This crate compiles only with rust >= 1.34.
This crate compiles only with rust >= 1.36.
4 changes: 4 additions & 0 deletions benches/decoding_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@ fn main() {
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/jpeg_lossless_sel1-rgb.jpg"))
}));
c.final_summary();
}
20 changes: 15 additions & 5 deletions examples/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,27 @@ fn main() {

let output_file = File::create(output_path).unwrap();
let mut encoder = png::Encoder::new(output_file, info.width as u32, info.height as u32);
encoder.set_depth(png::BitDepth::Eight);

match info.pixel_format {
jpeg::PixelFormat::L8 => encoder.set_color(png::ColorType::Grayscale),
jpeg::PixelFormat::RGB24 => encoder.set_color(png::ColorType::RGB),
jpeg::PixelFormat::L16 => {
encoder.set_depth(png::BitDepth::Sixteen);
encoder.set_color(png::ColorType::Grayscale);
},
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)
Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.34.2
1.36
110 changes: 71 additions & 39 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ use worker::{RowData, PlatformWorker, Worker};

pub const MAX_COMPONENTS: usize = 4;

mod lossless;
use self::lossless::compute_image_lossless;

static UNZIGZAG: [u8; 64] = [
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5,
Expand All @@ -31,6 +34,8 @@ static UNZIGZAG: [u8; 64] = [
pub enum PixelFormat {
/// Luminance (grayscale), 8 bits
L8,
/// Luminance (grayscale), 16 bits
L16,
/// RGB, 8 bits per channel
RGB24,
/// CMYK, 8 bits per channel
Expand All @@ -42,6 +47,7 @@ impl PixelFormat {
pub fn pixel_bytes(&self) -> usize {
match self {
PixelFormat::L8 => 1,
PixelFormat::L16 => 2,
PixelFormat::RGB24 => 3,
PixelFormat::CMYK32 => 4,
}
Expand All @@ -57,6 +63,8 @@ pub struct ImageInfo {
pub height: u16,
/// The pixel format of the image.
pub pixel_format: PixelFormat,
/// The coding process of the image.
pub coding_process: CodingProcess,
}

/// JPEG decoder
Expand Down Expand Up @@ -111,7 +119,11 @@ impl<R: Read> Decoder<R> {
match self.frame {
Some(ref frame) => {
let pixel_format = match frame.components.len() {
1 => PixelFormat::L8,
1 => match frame.precision {
8 => PixelFormat::L8,
16 => PixelFormat::L16,
_ => panic!()
},
3 => PixelFormat::RGB24,
4 => PixelFormat::CMYK32,
_ => panic!(),
Expand All @@ -121,6 +133,7 @@ impl<R: Read> Decoder<R> {
width: frame.output_size.width,
height: frame.output_size.height,
pixel_format: pixel_format,
coding_process: frame.coding_process,
})
},
None => None,
Expand Down Expand Up @@ -209,7 +222,8 @@ impl<R: Read> Decoder<R> {
let mut pending_marker = None;
let mut worker = None;
let mut scans_processed = 0;
let mut planes = vec![Vec::new(); self.frame.as_ref().map_or(0, |frame| frame.components.len())];
let mut planes = vec![Vec::<u8>::new(); self.frame.as_ref().map_or(0, |frame| frame.components.len())];
let mut planes_u16 = vec![Vec::<u16>::new(); self.frame.as_ref().map_or(0, |frame| frame.components.len())];

loop {
let marker = match pending_marker.take() {
Expand All @@ -234,13 +248,13 @@ impl<R: Read> Decoder<R> {
if frame.is_differential {
return Err(Error::Unsupported(UnsupportedFeature::Hierarchical));
}
if frame.coding_process == CodingProcess::Lossless {
return Err(Error::Unsupported(UnsupportedFeature::Lossless));
}
if frame.entropy_coding == EntropyCoding::Arithmetic {
return Err(Error::Unsupported(UnsupportedFeature::ArithmeticEntropyCoding));
}
if frame.precision != 8 {
if frame.precision != 8 && frame.coding_process != CodingProcess::Lossless {
return Err(Error::Unsupported(UnsupportedFeature::SamplePrecision(frame.precision)));
}
if frame.precision != 8 && frame.precision != 16 {
return Err(Error::Unsupported(UnsupportedFeature::SamplePrecision(frame.precision)));
}
if component_count != 1 && component_count != 3 && component_count != 4 {
Expand All @@ -257,6 +271,7 @@ impl<R: Read> Decoder<R> {
}

planes = vec![Vec::new(); component_count];
planes_u16 = vec![Vec::new(); component_count];
},

// Scan header
Expand All @@ -278,46 +293,57 @@ impl<R: Read> Decoder<R> {
}).collect();
}

// This was previously buggy, so let's explain the log here a bit. When a
// progressive frame is encoded then the coefficients (DC, AC) of each
// component (=color plane) can be split amongst scans. In particular it can
// happen or at least occurs in the wild that a scan contains coefficient 0 of
// all components. If now one but not all components had all other coefficients
// delivered in previous scans then such a scan contains all components but
// completes only some of them! (This is technically NOT permitted for all
// other coefficients as the standard dictates that scans with coefficients
// other than the 0th must only contain ONE component so we would either
// complete it or not. We may want to detect and error in case more component
// are part of a scan than allowed.) What a weird edge case.
//
// But this means we track precisely which components get completed here.
let mut finished = [false; MAX_COMPONENTS];

if scan.successive_approximation_low == 0 {
for (&i, component_finished) in scan.component_indices.iter().zip(&mut finished) {
if self.coefficients_finished[i] == !0 {
continue;
}
for j in scan.spectral_selection.clone() {
self.coefficients_finished[i] |= 1 << j;
}
if self.coefficients_finished[i] == !0 {
*component_finished = true;
}

if frame.coding_process == CodingProcess::Lossless {
let (marker, data) = self.decode_scan_lossless(&frame, &scan)?;

for (i, plane) in data.into_iter().enumerate().filter(|&(_, ref plane)| !plane.is_empty()) {
planes_u16[i] = plane;
}
pending_marker = marker;
}
else {
// This was previously buggy, so let's explain the log here a bit. When a
// progressive frame is encoded then the coefficients (DC, AC) of each
// component (=color plane) can be split amongst scans. In particular it can
// happen or at least occurs in the wild that a scan contains coefficient 0 of
// all components. If now one but not all components had all other coefficients
// delivered in previous scans then such a scan contains all components but
// completes only some of them! (This is technically NOT permitted for all
// other coefficients as the standard dictates that scans with coefficients
// other than the 0th must only contain ONE component so we would either
// complete it or not. We may want to detect and error in case more component
// are part of a scan than allowed.) What a weird edge case.
//
// But this means we track precisely which components get completed here.
let mut finished = [false; MAX_COMPONENTS];

if scan.successive_approximation_low == 0 {
for (&i, component_finished) in scan.component_indices.iter().zip(&mut finished) {
if self.coefficients_finished[i] == !0 {
continue;
}
for j in scan.spectral_selection.clone() {
self.coefficients_finished[i] |= 1 << j;
}
if self.coefficients_finished[i] == !0 {
*component_finished = true;
}
}
}

let (marker, data) = self.decode_scan(&frame, &scan, worker.as_mut().unwrap(), &finished)?;
let (marker, data) = self.decode_scan(&frame, &scan, worker.as_mut().unwrap(), &finished)?;

if let Some(data) = data {
for (i, plane) in data.into_iter().enumerate().filter(|&(_, ref plane)| !plane.is_empty()) {
if self.coefficients_finished[i] == !0 {
planes[i] = plane;
if let Some(data) = data {
for (i, plane) in data.into_iter().enumerate().filter(|&(_, ref plane)| !plane.is_empty()) {
if self.coefficients_finished[i] == !0 {
planes[i] = plane;
}
}
}
pending_marker = marker;
}

pending_marker = marker;
scans_processed += 1;
},

Expand Down Expand Up @@ -465,7 +491,12 @@ impl<R: Read> Decoder<R> {
}
}

compute_image(&frame.components, planes, frame.output_size, self.is_jfif, self.color_transform)
if frame.coding_process == CodingProcess::Lossless {
compute_image_lossless(&frame, planes_u16)
}
else{
compute_image(&frame.components, planes, frame.output_size, self.is_jfif, self.color_transform)
}
}

fn read_marker(&mut self) -> Result<Marker> {
Expand Down Expand Up @@ -964,6 +995,7 @@ fn compute_image(components: &[Component],
}
}


#[cfg(feature="rayon")]
fn compute_image_parallel(components: &[Component],
data: Vec<Vec<u8>>,
Expand Down
Loading