Small tweaks (#734)

- Fix docs for entropy filter (fixes #733)
- Improve docs for `--fix` (fixes #728)
- Add tests for alpha optimisation
- Following a recent change to the code here, I realised we don't
actually have any tests covering alpha optimisation. I've adjusted some
of the test images to include lines with both leading transparent and
all transparent pixels, and added dedicated tests for them.
- Change x86_64 macOS build to use the macos-15 runner
- There is a new macos-15-intel runner available but we don't actually
need an intel runner to build x86_64. (As support for x86_64 winds down,
we can probably drop these builds eventually - homebrew is the preferred
installation method for macOS anyway)
This commit is contained in:
andrews05 2025-10-04 22:46:47 +13:00 committed by GitHub
parent aebddf443a
commit e091767278
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 351 additions and 5 deletions

View file

@ -49,9 +49,9 @@ jobs:
- target: i686-pc-windows-msvc
os: windows-latest
- target: x86_64-apple-darwin
os: macos-13 # x86_64 runner
os: macos-15
- target: aarch64-apple-darwin
os: macos-15 # ARM64 runner
os: macos-15
env:
CARGO_BUILD_TARGET: ${{ matrix.target }}

View file

@ -229,9 +229,9 @@ PNG delta filters (apply the same filter to every line)
Heuristic strategies (try to find the best delta filter for each line)
5 => MinSum Minimum sum of absolute differences
6 => Entropy Highest Shannon entropy
6 => Entropy Smallest Shannon entropy
7 => Bigrams Lowest count of distinct bigrams
8 => BigEnt Highest Shannon entropy of bigrams
8 => BigEnt Smallest Shannon entropy of bigrams
9 => Brute Smallest compressed size (slow)
The default value depends on the optimization level preset.")
@ -309,7 +309,7 @@ compressed chunks (such as iCCP) will also be disabled. Note that the combinatio
.help("Disable checksum validation")
.long_help("\
Do not perform checksum validation of PNG chunks. This may allow some files with errors to \
be processed successfully.")
be processed successfully. The output will always have correct checksums.")
.long("fix")
.action(ArgAction::SetTrue),
)

346
tests/alpha.rs Normal file
View file

@ -0,0 +1,346 @@
use std::{
fs::remove_file,
path::{Path, PathBuf},
};
use oxipng::{internal_tests::*, *};
const GRAYSCALE_ALPHA: u8 = 4;
const RGBA: u8 = 6;
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
let options = oxipng::Options {
force: true,
optimize_alpha: true,
..Default::default()
};
(OutFile::from_path(input.with_extension("out.png")), options)
}
fn test_it_converts(
input: &str,
filter: FilterStrategy,
color_type_in: u8,
bit_depth_in: BitDepth,
color_type_out: u8,
bit_depth_out: BitDepth,
) {
let input = PathBuf::from(input);
let (output, mut opts) = get_opts(&input);
let png = PngData::new(&input, &opts).unwrap();
opts.filters = indexset! {filter};
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
Ok(_) => (),
Err(x) => panic!("{}", x),
};
let output = output.path().unwrap();
assert!(output.exists());
let png = match PngData::new(output, &opts) {
Ok(x) => x,
Err(x) => {
remove_file(output).ok();
panic!("{}", x)
}
};
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth as u8));
}
remove_file(output).ok();
}
#[test]
fn alpha_filter_0_for_rgba_16() {
test_it_converts(
"tests/files/filter_0_for_rgba_16.png",
FilterStrategy::NONE,
RGBA,
BitDepth::Sixteen,
RGBA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_1_for_rgba_16() {
test_it_converts(
"tests/files/filter_1_for_rgba_16.png",
FilterStrategy::SUB,
RGBA,
BitDepth::Sixteen,
RGBA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_2_for_rgba_16() {
test_it_converts(
"tests/files/filter_2_for_rgba_16.png",
FilterStrategy::UP,
RGBA,
BitDepth::Sixteen,
RGBA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_3_for_rgba_16() {
test_it_converts(
"tests/files/filter_3_for_rgba_16.png",
FilterStrategy::AVERAGE,
RGBA,
BitDepth::Sixteen,
RGBA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_4_for_rgba_16() {
test_it_converts(
"tests/files/filter_4_for_rgba_16.png",
FilterStrategy::PAETH,
RGBA,
BitDepth::Sixteen,
RGBA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_5_for_rgba_16() {
test_it_converts(
"tests/files/filter_5_for_rgba_16.png",
FilterStrategy::MinSum,
RGBA,
BitDepth::Sixteen,
RGBA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_0_for_rgba_8() {
test_it_converts(
"tests/files/filter_0_for_rgba_8.png",
FilterStrategy::NONE,
RGBA,
BitDepth::Eight,
RGBA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_1_for_rgba_8() {
test_it_converts(
"tests/files/filter_1_for_rgba_8.png",
FilterStrategy::SUB,
RGBA,
BitDepth::Eight,
RGBA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_2_for_rgba_8() {
test_it_converts(
"tests/files/filter_2_for_rgba_8.png",
FilterStrategy::UP,
RGBA,
BitDepth::Eight,
RGBA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_3_for_rgba_8() {
test_it_converts(
"tests/files/filter_3_for_rgba_8.png",
FilterStrategy::AVERAGE,
RGBA,
BitDepth::Eight,
RGBA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_4_for_rgba_8() {
test_it_converts(
"tests/files/filter_4_for_rgba_8.png",
FilterStrategy::PAETH,
RGBA,
BitDepth::Eight,
RGBA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_5_for_rgba_8() {
test_it_converts(
"tests/files/filter_5_for_rgba_8.png",
FilterStrategy::MinSum,
RGBA,
BitDepth::Eight,
RGBA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_0_for_grayscale_alpha_16() {
test_it_converts(
"tests/files/filter_0_for_grayscale_alpha_16.png",
FilterStrategy::NONE,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_1_for_grayscale_alpha_16() {
test_it_converts(
"tests/files/filter_1_for_grayscale_alpha_16.png",
FilterStrategy::SUB,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_2_for_grayscale_alpha_16() {
test_it_converts(
"tests/files/filter_2_for_grayscale_alpha_16.png",
FilterStrategy::UP,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_3_for_grayscale_alpha_16() {
test_it_converts(
"tests/files/filter_3_for_grayscale_alpha_16.png",
FilterStrategy::AVERAGE,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_4_for_grayscale_alpha_16() {
test_it_converts(
"tests/files/filter_4_for_grayscale_alpha_16.png",
FilterStrategy::PAETH,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_5_for_grayscale_alpha_16() {
test_it_converts(
"tests/files/filter_5_for_grayscale_alpha_16.png",
FilterStrategy::MinSum,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
GRAYSCALE_ALPHA,
BitDepth::Sixteen,
);
}
#[test]
fn alpha_filter_0_for_grayscale_alpha_8() {
test_it_converts(
"tests/files/filter_0_for_grayscale_alpha_8.png",
FilterStrategy::NONE,
GRAYSCALE_ALPHA,
BitDepth::Eight,
GRAYSCALE_ALPHA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_1_for_grayscale_alpha_8() {
test_it_converts(
"tests/files/filter_1_for_grayscale_alpha_8.png",
FilterStrategy::SUB,
GRAYSCALE_ALPHA,
BitDepth::Eight,
GRAYSCALE_ALPHA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_2_for_grayscale_alpha_8() {
test_it_converts(
"tests/files/filter_2_for_grayscale_alpha_8.png",
FilterStrategy::UP,
GRAYSCALE_ALPHA,
BitDepth::Eight,
GRAYSCALE_ALPHA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_3_for_grayscale_alpha_8() {
test_it_converts(
"tests/files/filter_3_for_grayscale_alpha_8.png",
FilterStrategy::AVERAGE,
GRAYSCALE_ALPHA,
BitDepth::Eight,
GRAYSCALE_ALPHA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_4_for_grayscale_alpha_8() {
test_it_converts(
"tests/files/filter_4_for_grayscale_alpha_8.png",
FilterStrategy::PAETH,
GRAYSCALE_ALPHA,
BitDepth::Eight,
GRAYSCALE_ALPHA,
BitDepth::Eight,
);
}
#[test]
fn alpha_filter_5_for_grayscale_alpha_8() {
test_it_converts(
"tests/files/filter_5_for_grayscale_alpha_8.png",
FilterStrategy::MinSum,
GRAYSCALE_ALPHA,
BitDepth::Eight,
GRAYSCALE_ALPHA,
BitDepth::Eight,
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 39 KiB