Add Predefined filter strategy
Use this to retain filters during evaluation rather than the complete filtered data
This commit is contained in:
parent
682b5d9ad0
commit
8c3758b9f4
4 changed files with 59 additions and 30 deletions
|
|
@ -23,11 +23,14 @@ use crate::{
|
|||
|
||||
pub(crate) struct Candidate {
|
||||
pub image: Arc<PngImage>,
|
||||
pub data: Vec<u8>,
|
||||
pub data_is_compressed: bool,
|
||||
pub idat_data: Option<Vec<u8>>,
|
||||
pub estimated_output_size: usize,
|
||||
/// The input filter, which is retained for printing and for APNG frames.
|
||||
pub filter: FilterStrategy,
|
||||
// For determining tie-breaker
|
||||
/// The filter returned by the filter function, which may be Predefined.
|
||||
/// Use this for the next round to avoid recomputing the filter.
|
||||
pub filter_used: FilterStrategy,
|
||||
/// For determining tie-breaker
|
||||
nth: usize,
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +39,7 @@ impl Candidate {
|
|||
(
|
||||
self.estimated_output_size,
|
||||
self.image.data.len(),
|
||||
self.filter,
|
||||
self.filter.clone(),
|
||||
// Prefer the later image added (e.g. baseline, which is always added last)
|
||||
usize::MAX - self.nth,
|
||||
)
|
||||
|
|
@ -143,21 +146,21 @@ impl Evaluator {
|
|||
// which are dangerous to do in side Rayon's loop.
|
||||
// Instead, only update (atomic) best size in real time,
|
||||
// and the best result later without need for locks.
|
||||
filters_iter.for_each(|&filter| {
|
||||
filters_iter.for_each(|filter| {
|
||||
if deadline.passed() {
|
||||
return;
|
||||
}
|
||||
let filtered = image.filter_image(filter, optimize_alpha);
|
||||
let (filtered, filter_used) = image.filter_image(filter.clone(), optimize_alpha);
|
||||
let idat_data = deflater.deflate(&filtered, best_candidate_size.get());
|
||||
if let Ok(idat_data) = idat_data {
|
||||
let estimated_output_size = image.estimated_output_size(&idat_data);
|
||||
// For the final round we need the IDAT data, otherwise the filtered data
|
||||
// We only need to retain the IDAT data in the final round
|
||||
let new = Candidate {
|
||||
image: image.clone(),
|
||||
data: if final_round { idat_data } else { filtered },
|
||||
data_is_compressed: final_round,
|
||||
idat_data: if final_round { Some(idat_data) } else { None },
|
||||
estimated_output_size,
|
||||
filter,
|
||||
filter: filter.clone(),
|
||||
filter_used,
|
||||
nth,
|
||||
};
|
||||
best_candidate_size.set_min(estimated_output_size);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{fmt, fmt::Display, mem::transmute};
|
||||
|
||||
/// Filtering strategy for use in [`Options`][crate::Options]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
|
||||
pub enum FilterStrategy {
|
||||
/// Same filter for all rows
|
||||
Basic(RowFilter),
|
||||
|
|
@ -15,6 +15,8 @@ pub enum FilterStrategy {
|
|||
BigEnt,
|
||||
/// Deflate compression
|
||||
Brute,
|
||||
/// Predefined filter for each row
|
||||
Predefined(Vec<RowFilter>),
|
||||
}
|
||||
|
||||
impl Display for FilterStrategy {
|
||||
|
|
@ -26,6 +28,7 @@ impl Display for FilterStrategy {
|
|||
Self::Bigrams => "Bigrams".fmt(f),
|
||||
Self::BigEnt => "BigEnt".fmt(f),
|
||||
Self::Brute => "Brute".fmt(f),
|
||||
Self::Predefined(_) => "Predefined".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
src/lib.rs
18
src/lib.rs
|
|
@ -170,7 +170,7 @@ impl RawImage {
|
|||
|
||||
let mut png = PngData {
|
||||
raw: result.image,
|
||||
idat_data: result.data,
|
||||
idat_data: result.idat_data.unwrap(),
|
||||
aux_chunks,
|
||||
frames: Vec::new(),
|
||||
};
|
||||
|
|
@ -359,7 +359,7 @@ fn optimize_png(
|
|||
};
|
||||
if let Some(result) = optimize_raw(raw.clone(), &opts, deadline.clone(), max_size) {
|
||||
png.raw = result.image;
|
||||
png.idat_data = result.data;
|
||||
png.idat_data = result.idat_data.unwrap();
|
||||
recompress_frames(png, &opts, deadline, result.filter)?;
|
||||
postprocess_chunks(&mut png.aux_chunks, &png.raw.ihdr, &raw.ihdr);
|
||||
}
|
||||
|
|
@ -472,7 +472,7 @@ fn optimize_raw(
|
|||
(eval_result?, eval_deflater)
|
||||
};
|
||||
|
||||
if result.data_is_compressed
|
||||
if result.idat_data.is_some()
|
||||
&& max_size.is_none_or(|max_size| result.estimated_output_size < max_size)
|
||||
{
|
||||
debug!("Found better result:");
|
||||
|
|
@ -499,7 +499,7 @@ fn perform_trials(
|
|||
|
||||
if eval_result.is_some() {
|
||||
// Some filters have already been evaluated, we don't need to try them again
|
||||
filters = filters.difference(&eval_filters).copied().collect();
|
||||
filters = filters.difference(&eval_filters).cloned().collect();
|
||||
}
|
||||
|
||||
if !filters.is_empty() {
|
||||
|
|
@ -523,14 +523,14 @@ fn perform_trials(
|
|||
// We should have a result here - fail if not (e.g. deadline passed)
|
||||
let mut result = eval_result?;
|
||||
|
||||
if !result.data_is_compressed {
|
||||
if result.idat_data.is_none() {
|
||||
// Compress with the main deflater
|
||||
debug!("Trying filter {} with {}", result.filter, opts.deflate);
|
||||
match opts.deflate.deflate(&result.data, max_size) {
|
||||
let (data, _) = image.filter_image(result.filter_used.clone(), opts.optimize_alpha);
|
||||
match opts.deflate.deflate(&data, max_size) {
|
||||
Ok(idat_data) => {
|
||||
result.estimated_output_size = result.image.estimated_output_size(&idat_data);
|
||||
result.data = idat_data;
|
||||
result.data_is_compressed = true;
|
||||
result.idat_data = Some(idat_data);
|
||||
trace!("{} bytes", result.estimated_output_size);
|
||||
}
|
||||
Err(PngError::DeflatedDataTooLong(bytes)) => {
|
||||
|
|
@ -644,7 +644,7 @@ fn recompress_frames(
|
|||
ihdr.width = frame.width;
|
||||
ihdr.height = frame.height;
|
||||
let image = PngImage::new(ihdr, &frame.data)?;
|
||||
let filtered = image.filter_image(filter, opts.optimize_alpha);
|
||||
let (filtered, _) = image.filter_image(filter.clone(), opts.optimize_alpha);
|
||||
let max_size = Some(frame.data.len() - 1);
|
||||
if let Ok(data) = opts.deflate.deflate(&filtered, max_size) {
|
||||
debug!(
|
||||
|
|
|
|||
|
|
@ -386,7 +386,11 @@ impl PngImage {
|
|||
|
||||
/// Apply the specified filter type to all rows in the image
|
||||
#[must_use]
|
||||
pub fn filter_image(&self, strategy: FilterStrategy, optimize_alpha: bool) -> Vec<u8> {
|
||||
pub fn filter_image(
|
||||
&self,
|
||||
strategy: FilterStrategy,
|
||||
optimize_alpha: bool,
|
||||
) -> (Vec<u8>, FilterStrategy) {
|
||||
let mut filtered = Vec::with_capacity(self.data.len());
|
||||
let bpp = self.bytes_per_channel() * self.channels_per_pixel();
|
||||
// If alpha optimization is enabled, determine how many bytes of alpha there are per pixel
|
||||
|
|
@ -399,31 +403,39 @@ impl PngImage {
|
|||
let mut prev_line = Vec::new();
|
||||
let mut prev_pass: Option<u8> = None;
|
||||
let mut f_buf = Vec::new();
|
||||
for line in self.scan_lines(false) {
|
||||
// For heuristic strategies, keep track of the actual filter used for each line
|
||||
let mut filters_used = Vec::new();
|
||||
for (i, line) in self.scan_lines(false).enumerate() {
|
||||
if prev_pass != line.pass || line.data.len() != prev_line.len() {
|
||||
prev_line = vec![0; line.data.len()];
|
||||
}
|
||||
// Alpha optimisation may alter the line data, so we need a mutable copy of it
|
||||
let mut line_data = line.data.to_vec();
|
||||
|
||||
if let FilterStrategy::Basic(filter) = strategy {
|
||||
if let FilterStrategy::Basic(mut filter) = strategy {
|
||||
// Standard filters
|
||||
let filter = if prev_pass == line.pass || filter <= RowFilter::Sub {
|
||||
filter
|
||||
} else {
|
||||
RowFilter::None
|
||||
};
|
||||
if prev_pass != line.pass && filter > RowFilter::Sub {
|
||||
filter = RowFilter::None;
|
||||
}
|
||||
filter.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes);
|
||||
filtered.extend_from_slice(&f_buf);
|
||||
prev_line = line_data;
|
||||
} else if let FilterStrategy::Predefined(lines) = &strategy {
|
||||
// Predefined filter for each line
|
||||
let filter = lines.get(i).unwrap_or(&RowFilter::None);
|
||||
filter.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes);
|
||||
filtered.extend_from_slice(&f_buf);
|
||||
prev_line = line_data;
|
||||
} else {
|
||||
// Heuristic filter selection strategies
|
||||
|
||||
let mut best_filter = RowFilter::None;
|
||||
if line_data.iter().all(|&x| x == 0) {
|
||||
// Assume None if the line is all zeros
|
||||
filtered.push(RowFilter::None as u8);
|
||||
filtered.push(best_filter as u8);
|
||||
filtered.extend_from_slice(&line_data);
|
||||
prev_line = line_data;
|
||||
filters_used.push(best_filter);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -450,6 +462,7 @@ impl PngImage {
|
|||
best_size = size;
|
||||
std::mem::swap(&mut best_line, &mut f_buf);
|
||||
best_line_raw.clone_from(&line_data);
|
||||
best_filter = *f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -473,6 +486,7 @@ impl PngImage {
|
|||
best_size = size;
|
||||
std::mem::swap(&mut best_line, &mut f_buf);
|
||||
best_line_raw.clone_from(&line_data);
|
||||
best_filter = *f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -492,6 +506,7 @@ impl PngImage {
|
|||
best_size = size;
|
||||
std::mem::swap(&mut best_line, &mut f_buf);
|
||||
best_line_raw.clone_from(&line_data);
|
||||
best_filter = *f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -512,6 +527,7 @@ impl PngImage {
|
|||
best_size = size;
|
||||
std::mem::swap(&mut best_line, &mut f_buf);
|
||||
best_line_raw.clone_from(&line_data);
|
||||
best_filter = *f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -537,6 +553,7 @@ impl PngImage {
|
|||
best_size = size;
|
||||
std::mem::swap(&mut best_line, &mut f_buf);
|
||||
best_line_raw.clone_from(&line_data);
|
||||
best_filter = *f;
|
||||
}
|
||||
}
|
||||
filtered.resize(line_start, 0);
|
||||
|
|
@ -545,11 +562,17 @@ impl PngImage {
|
|||
}
|
||||
filtered.extend_from_slice(&best_line);
|
||||
prev_line = best_line_raw;
|
||||
filters_used.push(best_filter);
|
||||
}
|
||||
|
||||
prev_pass = line.pass;
|
||||
}
|
||||
filtered
|
||||
|
||||
if filters_used.is_empty() {
|
||||
(filtered, strategy)
|
||||
} else {
|
||||
(filtered, FilterStrategy::Predefined(filters_used))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue