Skip to content

Commit f4d6ce4

Browse files
authored
High-level compression options API (#503)
1 parent af38962 commit f4d6ce4

File tree

9 files changed

+345
-286
lines changed

9 files changed

+345
-286
lines changed

benches/unfilter.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,12 @@
99
1010
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
1111
use png::benchable_apis::unfilter;
12-
use png::FilterType;
12+
use png::Filter;
1313
use rand::Rng;
1414

1515
fn unfilter_all(c: &mut Criterion) {
1616
let bpps = [1, 2, 3, 4, 6, 8];
17-
let filters = [
18-
FilterType::Sub,
19-
FilterType::Up,
20-
FilterType::Avg,
21-
FilterType::Paeth,
22-
];
17+
let filters = [Filter::Sub, Filter::Up, Filter::Avg, Filter::Paeth];
2318
for &filter in filters.iter() {
2419
for &bpp in bpps.iter() {
2520
bench_unfilter(c, filter, bpp);
@@ -30,7 +25,7 @@ fn unfilter_all(c: &mut Criterion) {
3025
criterion_group!(benches, unfilter_all);
3126
criterion_main!(benches);
3227

33-
fn bench_unfilter(c: &mut Criterion, filter: FilterType, bpp: u8) {
28+
fn bench_unfilter(c: &mut Criterion, filter: Filter, bpp: u8) {
3429
let mut group = c.benchmark_group("unfilter");
3530

3631
fn get_random_bytes<R: Rng>(rng: &mut R, n: usize) -> Vec<u8> {

examples/corpus-bench.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,16 @@ fn run_encode(
4343
encoder.set_depth(bit_depth);
4444
encoder.set_compression(match args.speed {
4545
Speed::Fast => png::Compression::Fast,
46-
Speed::Default => png::Compression::Default,
47-
Speed::Best => png::Compression::Best,
46+
Speed::Default => png::Compression::Balanced,
47+
Speed::Best => png::Compression::High,
4848
});
4949
encoder.set_filter(match args.filter {
50-
Filter::None => png::FilterType::NoFilter,
51-
Filter::Sub => png::FilterType::Sub,
52-
Filter::Up => png::FilterType::Up,
53-
Filter::Average => png::FilterType::Avg,
54-
Filter::Paeth => png::FilterType::Paeth,
55-
Filter::Adaptive => png::FilterType::Paeth,
56-
});
57-
encoder.set_adaptive_filter(match args.filter {
58-
Filter::Adaptive => png::AdaptiveFilterType::Adaptive,
59-
_ => png::AdaptiveFilterType::NonAdaptive,
50+
Filter::None => png::Filter::NoFilter,
51+
Filter::Sub => png::Filter::Sub,
52+
Filter::Up => png::Filter::Up,
53+
Filter::Average => png::Filter::Avg,
54+
Filter::Paeth => png::Filter::Paeth,
55+
Filter::Adaptive => png::Filter::Adaptive,
6056
});
6157
let mut encoder = encoder.write_header().unwrap();
6258
encoder.write_image_data(image).unwrap();

fuzz/fuzz_targets/roundtrip.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![no_main]
22

33
use libfuzzer_sys::fuzz_target;
4-
use png::{FilterType, ColorType, BitDepth};
4+
use png::{Filter, ColorType, BitDepth};
55

66
fuzz_target!(|data: (u8, u8, u8, u8, u8, Vec<u8>, Vec<u8>)| {
77
if let Some((raw, encoded)) = encode_png(data.0, data.1, data.2, data.3, data.4, &data.5, &data.6) {
@@ -16,7 +16,7 @@ fn encode_png<'a>(width: u8, filter: u8, compression: u8, color_type: u8, raw_bi
1616
// Convert untyped bytes to the correct types and validate them:
1717
let width = width as u32;
1818
if width == 0 { return None };
19-
let filter = FilterType::from_u8(filter)?;
19+
let filter = filter_from_u8(filter);
2020
let bit_depth = BitDepth::from_u8(raw_bit_depth)?;
2121
let max_palette_length = 3 * u32::pow(2, raw_bit_depth as u32) as usize;
2222
let mut palette = raw_palette;
@@ -29,11 +29,9 @@ fn encode_png<'a>(width: u8, filter: u8, compression: u8, color_type: u8, raw_bi
2929
}
3030
// compression
3131
let compression = match compression {
32-
0 => png::Compression::Default,
33-
1 => png::Compression::Fast,
34-
2 => png::Compression::Best,
35-
3 => png::Compression::Huffman,
36-
4 => png::Compression::Rle,
32+
0 => png::DeflateCompression::NoCompression,
33+
level @ 1..=9 => png::DeflateCompression::Flate2(level),
34+
10 => png::DeflateCompression::FdeflateUltraFast,
3735
_ => return None,
3836
};
3937

@@ -52,7 +50,7 @@ fn encode_png<'a>(width: u8, filter: u8, compression: u8, color_type: u8, raw_bi
5250
encoder.set_depth(bit_depth);
5351
encoder.set_color(color_type);
5452
encoder.set_filter(filter);
55-
encoder.set_compression(compression);
53+
encoder.set_deflate_compression(compression);
5654
if let ColorType::Indexed = color_type {
5755
encoder.set_palette(palette)
5856
}
@@ -75,6 +73,18 @@ fn decode_png(data: &[u8]) -> (png::OutputInfo, Vec<u8>) {
7573
(info, img_data)
7674
}
7775

76+
/// Filter::from() doesn't cover the Filter::Adaptive variant, so we roll our own
77+
fn filter_from_u8(input: u8) -> Filter {
78+
match input {
79+
0 => Filter::NoFilter,
80+
1 => Filter::Sub,
81+
2 => Filter::Up,
82+
3 => Filter::Avg,
83+
4 => Filter::Paeth,
84+
_ => Filter::Adaptive,
85+
}
86+
}
87+
7888
// copied from the `png` codebase because it's pub(crate)
7989
fn raw_row_length_from_width(depth: BitDepth, color: ColorType, width: u32) -> usize {
8090
let samples = width as usize * color.samples();

src/benchable_apis.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
//! This module is gated behind the "benchmarks" feature.
33
44
use crate::common::BytesPerPixel;
5-
use crate::filter::FilterType;
5+
use crate::filter::{Filter, RowFilter};
66
use crate::{BitDepth, ColorType, Info};
77

88
/// Re-exporting `unfilter` to make it easier to benchmark, despite some items being only
99
/// `pub(crate)`: `fn unfilter`, `enum BytesPerPixel`.
10-
pub fn unfilter(filter: FilterType, tbpp: u8, previous: &[u8], current: &mut [u8]) {
10+
pub fn unfilter(filter: Filter, tbpp: u8, previous: &[u8], current: &mut [u8]) {
11+
let filter = RowFilter::from_method(filter).unwrap(); // RowFilter type is private
1112
let tbpp = BytesPerPixel::from_usize(tbpp as usize);
1213
crate::filter::unfilter(filter, tbpp, previous, current)
1314
}

src/common.rs

Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Common types shared between the encoder and decoder
22
use crate::text_metadata::{ITXtChunk, TEXtChunk, ZTXtChunk};
3+
#[allow(unused_imports)] // used by doc comments only
4+
use crate::Filter;
35
use crate::{chunk, encoder};
46
use io::Write;
57
use std::{borrow::Cow, convert::TryFrom, fmt, io};
@@ -313,33 +315,103 @@ impl AnimationControl {
313315
}
314316

315317
/// The type and strength of applied compression.
318+
///
319+
/// This is a simple, high-level interface that will automatically choose
320+
/// the appropriate DEFLATE compression mode and PNG filter.
321+
///
322+
/// If you need more control over the encoding paramters,
323+
/// you can set the [DeflateCompression] and [Filter] manually.
316324
#[derive(Debug, Clone, Copy)]
325+
#[non_exhaustive]
317326
pub enum Compression {
318-
/// Default level
319-
Default,
320-
/// Fast minimal compression
321-
Fast,
322-
/// Higher compression level
327+
/// No compression whatsoever. Fastest, but results in large files.
328+
NoCompression,
329+
/// Extremely fast but light compression.
330+
Fastest,
331+
/// Extremely fast compression with a decent compression ratio.
323332
///
324-
/// Best in this context isn't actually the highest possible level
325-
/// the encoder can do, but is meant to emulate the `Best` setting in the `Flate2`
326-
/// library.
327-
Best,
328-
#[deprecated(
329-
since = "0.17.6",
330-
note = "use one of the other compression levels instead, such as 'fast'"
331-
)]
332-
Huffman,
333-
#[deprecated(
334-
since = "0.17.6",
335-
note = "use one of the other compression levels instead, such as 'fast'"
336-
)]
337-
Rle,
333+
/// Significantly outperforms libpng and other popular encoders
334+
/// by using a [specialized DEFLATE implementation tuned for PNG](https://crates.io/crates/fdeflate),
335+
/// while still providing better compression ratio than the fastest modes of other encoders.
336+
Fast,
337+
/// Balances encoding speed and compression ratio
338+
Balanced,
339+
/// Spend more time to produce a slightly smaller file than with `Default`
340+
High,
338341
}
339342

340343
impl Default for Compression {
341344
fn default() -> Self {
342-
Self::Default
345+
Self::Balanced
346+
}
347+
}
348+
349+
/// Advanced compression settings with more customization options than [Compression].
350+
///
351+
/// Note that this setting only affects DEFLATE compression.
352+
/// Another setting that influences the compression ratio and lets you choose
353+
/// between encoding speed and compression ratio is the [Filter].
354+
///
355+
/// ### Stability guarantees
356+
///
357+
/// The implementation details of DEFLATE compression may evolve over time,
358+
/// even without a semver-breaking change to the version of `png` crate.
359+
///
360+
/// If a certain compression setting is superseded by other options,
361+
/// it may be marked deprecated and remapped to a different option.
362+
/// You will see a deprecation notice when compiling code relying on such options.
363+
#[non_exhaustive]
364+
#[derive(Debug, Clone, Copy)]
365+
pub enum DeflateCompression {
366+
/// Do not compress the data at all.
367+
///
368+
/// Useful for incompressible images,
369+
/// or when speed is paramount and you don't care about size at all.
370+
///
371+
/// This mode also disables filters, forcing [Filter::NoFilter].
372+
NoCompression,
373+
374+
/// Excellent for creating lightly compressed PNG images very quickly.
375+
///
376+
/// Uses the [fdeflate](https://crates.io/crates/fdeflate) crate under the hood
377+
/// to achieve speeds far exceeding what libpng is capable of
378+
/// while still providing a decent compression ratio.
379+
///
380+
/// Images encoded in this mode can also be decoded by the `png` crate slightly faster than usual.
381+
/// Other decoders (e.g. libpng) do not get a decoding speed boost from this mode.
382+
FdeflateUltraFast,
383+
384+
/// Uses [flate2](https://crates.io/crates/flate2) crate with the specified [compression level](flate2::Compression::new).
385+
///
386+
/// Flate2 has several backends that make different trade-offs.
387+
/// See the flate2 documentation for the available backends for more information.
388+
Flate2(u8),
389+
// Other variants can be added in the future
390+
}
391+
392+
impl Default for DeflateCompression {
393+
fn default() -> Self {
394+
Self::from_simple(Compression::Balanced)
395+
}
396+
}
397+
398+
impl DeflateCompression {
399+
pub(crate) fn from_simple(value: Compression) -> Self {
400+
match value {
401+
Compression::NoCompression => Self::NoCompression,
402+
Compression::Fastest => Self::FdeflateUltraFast,
403+
Compression::Fast => Self::FdeflateUltraFast,
404+
Compression::Balanced => Self::Flate2(flate2::Compression::default().level() as u8),
405+
Compression::High => Self::Flate2(flate2::Compression::best().level() as u8),
406+
}
407+
}
408+
409+
pub(crate) fn closest_flate2_level(&self) -> flate2::Compression {
410+
match self {
411+
DeflateCompression::NoCompression => flate2::Compression::none(),
412+
DeflateCompression::FdeflateUltraFast => flate2::Compression::new(1),
413+
DeflateCompression::Flate2(level) => flate2::Compression::new(u32::from(*level)),
414+
}
343415
}
344416
}
345417

src/decoder/unfiltering_buffer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::stream::{DecodingError, FormatErrorInner};
22
use crate::common::BytesPerPixel;
3-
use crate::filter::{unfilter, FilterType};
3+
use crate::filter::{unfilter, RowFilter};
44

55
// Buffer for temporarily holding decompressed, not-yet-`unfilter`-ed rows.
66
pub(crate) struct UnfilteringBuffer {
@@ -96,7 +96,7 @@ impl UnfilteringBuffer {
9696
debug_assert!(prev.is_empty() || prev.len() == (rowlen - 1));
9797

9898
// Get the filter type.
99-
let filter = FilterType::from_u8(row[0]).ok_or(DecodingError::Format(
99+
let filter = RowFilter::from_u8(row[0]).ok_or(DecodingError::Format(
100100
FormatErrorInner::UnknownFilterMethod(row[0]).into(),
101101
))?;
102102
let row = &mut row[1..rowlen];

0 commit comments

Comments
 (0)