Merge branch 'master' into docs/repology-packaging-status
This commit is contained in:
commit
e8716e6fd1
5 changed files with 86 additions and 38 deletions
12
.github/workflows/oxipng.yml
vendored
12
.github/workflows/oxipng.yml
vendored
|
|
@ -49,9 +49,9 @@ jobs:
|
|||
- target: i686-pc-windows-msvc
|
||||
os: windows-latest
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
os: macos-12
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
os: macos-14 # ARM64 runner
|
||||
|
||||
env:
|
||||
CARGO_BUILD_TARGET: ${{ matrix.target }}
|
||||
|
|
@ -122,19 +122,11 @@ jobs:
|
|||
reporter: github-check
|
||||
fail_on_error: true
|
||||
|
||||
# There aren't good user-mode ARM64 emulators we can use on x64 macOS hosts.
|
||||
# QEMU doesn't have any plans to add such support due to a lack of kernel
|
||||
# syscall stability guarantees: https://gitlab.com/qemu-project/qemu/-/issues/1682
|
||||
- name: Run tests
|
||||
if: matrix.target != 'aarch64-apple-darwin'
|
||||
run: |
|
||||
cargo nextest run --release --features sanity-checks
|
||||
cargo test --doc --release --features sanity-checks
|
||||
|
||||
- name: Build tests (ARM64 macOS only)
|
||||
if: matrix.target == 'aarch64-apple-darwin'
|
||||
run: cargo test --release --features sanity-checks --no-run
|
||||
|
||||
- name: Build benchmarks
|
||||
run: cargo bench --no-run
|
||||
|
||||
|
|
|
|||
|
|
@ -24,20 +24,26 @@ impl<'a> Iterator for ScanLines<'a> {
|
|||
type Item = ScanLine<'a>;
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|(len, pass, num_pixels)| {
|
||||
let (data, rest) = self.raw_data.split_at(len);
|
||||
self.raw_data = rest;
|
||||
let (&filter, data) = if self.has_filter {
|
||||
data.split_first().unwrap()
|
||||
} else {
|
||||
(&0, data)
|
||||
};
|
||||
ScanLine {
|
||||
filter,
|
||||
data,
|
||||
pass,
|
||||
num_pixels,
|
||||
}
|
||||
let (len, pass, num_pixels) = self.iter.next()?;
|
||||
debug_assert!(self.raw_data.len() >= len);
|
||||
debug_assert!(!self.has_filter || len > 1);
|
||||
// The data length should always be correct here but this check assures
|
||||
// the compiler that it doesn't need to account for a potential panic
|
||||
if self.raw_data.len() < len {
|
||||
return None;
|
||||
}
|
||||
let (data, rest) = self.raw_data.split_at(len);
|
||||
self.raw_data = rest;
|
||||
let (&filter, data) = if self.has_filter {
|
||||
data.split_first().unwrap()
|
||||
} else {
|
||||
(&0, data)
|
||||
};
|
||||
Some(ScanLine {
|
||||
filter,
|
||||
data,
|
||||
pass,
|
||||
num_pixels,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,8 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
|
|||
if png.ihdr.bit_depth != BitDepth::Eight {
|
||||
return None;
|
||||
}
|
||||
let palette = match &png.ihdr.color_type {
|
||||
ColorType::Indexed { palette } if palette.len() > 1 => palette,
|
||||
_ => return None,
|
||||
let ColorType::Indexed { palette } = &png.ihdr.color_type else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut used = [false; 256];
|
||||
|
|
@ -43,8 +42,9 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
|
|||
let data = if did_change {
|
||||
// Reassign data bytes to new indices
|
||||
png.data.iter().map(|b| byte_map[*b as usize]).collect()
|
||||
} else if condensed.len() < palette.len() {
|
||||
// Data is unchanged but palette will be truncated
|
||||
} else if condensed.len() != palette.len() {
|
||||
// Data is unchanged but palette is different size
|
||||
// Note the new palette could potentially be larger if the original had a missing entry
|
||||
png.data.clone()
|
||||
} else {
|
||||
// Nothing has changed
|
||||
|
|
@ -183,23 +183,36 @@ pub fn sorted_palette_battiato(png: &PngImage) -> Option<PngImage> {
|
|||
|
||||
// Find the most popular color on the image edges (the pixels neighboring the filter bytes)
|
||||
fn most_popular_edge_color(num_colors: usize, png: &PngImage) -> usize {
|
||||
let mut counts = vec![0; num_colors];
|
||||
let mut counts = [0u32; 256];
|
||||
for line in png.scan_lines(false) {
|
||||
counts[line.data[0] as usize] += 1;
|
||||
counts[line.data[line.data.len() - 1] as usize] += 1;
|
||||
if let &[first, .., last] = line.data {
|
||||
counts[first as usize] += 1;
|
||||
counts[last as usize] += 1;
|
||||
}
|
||||
}
|
||||
counts.iter().enumerate().max_by_key(|(_, &v)| v).unwrap().0
|
||||
counts
|
||||
.iter()
|
||||
.copied()
|
||||
.take(num_colors)
|
||||
.enumerate()
|
||||
.max_by_key(|&(_, v)| v)
|
||||
.unwrap_or_default()
|
||||
.0
|
||||
}
|
||||
|
||||
// Calculate co-occurences matrix
|
||||
fn co_occurrence_matrix(num_colors: usize, png: &PngImage) -> Vec<Vec<usize>> {
|
||||
let mut matrix = vec![vec![0; num_colors]; num_colors];
|
||||
fn co_occurrence_matrix(num_colors: usize, png: &PngImage) -> Vec<Vec<u32>> {
|
||||
let mut matrix = vec![vec![0u32; num_colors]; num_colors];
|
||||
let mut prev: Option<ScanLine> = None;
|
||||
let mut prev_val = None;
|
||||
for line in png.scan_lines(false) {
|
||||
for i in 0..line.data.len() {
|
||||
let val = line.data[i] as usize;
|
||||
if i > 0 {
|
||||
matrix[line.data[i - 1] as usize][val] += 1;
|
||||
if val > num_colors {
|
||||
continue;
|
||||
}
|
||||
if let Some(prev_val) = prev_val.replace(val) {
|
||||
matrix[prev_val][val] += 1;
|
||||
}
|
||||
if let Some(prev) = &prev {
|
||||
matrix[prev.data[i] as usize][val] += 1;
|
||||
|
|
@ -211,7 +224,7 @@ fn co_occurrence_matrix(num_colors: usize, png: &PngImage) -> Vec<Vec<usize>> {
|
|||
}
|
||||
|
||||
// Calculate edge list sorted by weight
|
||||
fn weighted_edges(matrix: &[Vec<usize>]) -> Vec<(usize, usize)> {
|
||||
fn weighted_edges(matrix: &[Vec<u32>]) -> Vec<(usize, usize)> {
|
||||
let mut edges = Vec::new();
|
||||
for i in 0..matrix.len() {
|
||||
for j in 0..i {
|
||||
|
|
|
|||
BIN
tests/files/palette_should_be_reduced_with_missing.png
Normal file
BIN
tests/files/palette_should_be_reduced_with_missing.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 B |
|
|
@ -1049,6 +1049,43 @@ fn palette_should_be_reduced_with_both() {
|
|||
remove_file(output).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn palette_should_be_reduced_with_missing() {
|
||||
let input = PathBuf::from("tests/files/palette_should_be_reduced_with_missing.png");
|
||||
let (output, opts) = get_opts(&input);
|
||||
|
||||
let png = PngData::new(&input, &opts).unwrap();
|
||||
|
||||
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||
if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
|
||||
assert_eq!(palette.len(), 2);
|
||||
}
|
||||
|
||||
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(), INDEXED);
|
||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Two);
|
||||
if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
|
||||
assert_eq!(palette.len(), 3);
|
||||
}
|
||||
|
||||
remove_file(output).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rgba_16_reduce_alpha() {
|
||||
test_it_converts(
|
||||
|
|
|
|||
Loading…
Reference in a new issue