Revamp alpha optimization
This commit is contained in:
parent
54ee621e41
commit
d70b35053e
8 changed files with 192 additions and 145 deletions
|
|
@ -13,7 +13,7 @@ fn filters_16_bits_filter_0(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ fn filters_8_bits_filter_0(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ fn filters_4_bits_filter_0(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ fn filters_2_bits_filter_0(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ fn filters_16_bits_filter_1(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ fn filters_8_bits_filter_1(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ fn filters_4_bits_filter_1(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ fn filters_2_bits_filter_1(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ fn filters_16_bits_filter_2(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,7 +135,7 @@ fn filters_8_bits_filter_2(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +147,7 @@ fn filters_4_bits_filter_2(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,7 +159,7 @@ fn filters_2_bits_filter_2(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +171,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,7 +181,7 @@ fn filters_16_bits_filter_3(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,7 +191,7 @@ fn filters_8_bits_filter_3(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,7 +203,7 @@ fn filters_4_bits_filter_3(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +215,7 @@ fn filters_2_bits_filter_3(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,7 +227,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,7 +237,7 @@ fn filters_16_bits_filter_4(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,7 +247,7 @@ fn filters_8_bits_filter_4(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,7 +259,7 @@ fn filters_4_bits_filter_4(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,7 +271,7 @@ fn filters_2_bits_filter_4(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,7 +283,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,7 +293,7 @@ fn filters_16_bits_filter_5(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,7 +303,7 @@ fn filters_8_bits_filter_5(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,7 +315,7 @@ fn filters_4_bits_filter_5(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,7 +327,7 @@ fn filters_2_bits_filter_5(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,6 +339,6 @@ fn filters_1_bits_filter_5(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ fn filters_minsum(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ fn filters_entropy(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Entropy);
|
png.raw.filter_image(RowFilter::Entropy, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ fn filters_bigrams(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Bigrams);
|
png.raw.filter_image(RowFilter::Bigrams, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ fn filters_bigent(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::BigEnt);
|
png.raw.filter_image(RowFilter::BigEnt, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,6 +53,6 @@ fn filters_brute(b: &mut Bencher) {
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, false).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Brute);
|
png.raw.filter_image(RowFilter::Brute, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ pub(crate) struct Evaluator {
|
||||||
deadline: Arc<Deadline>,
|
deadline: Arc<Deadline>,
|
||||||
filters: IndexSet<RowFilter>,
|
filters: IndexSet<RowFilter>,
|
||||||
compression: u8,
|
compression: u8,
|
||||||
|
optimize_alpha: bool,
|
||||||
nth: AtomicUsize,
|
nth: AtomicUsize,
|
||||||
best_candidate_size: Arc<AtomicMin>,
|
best_candidate_size: Arc<AtomicMin>,
|
||||||
/// images are sent to the caller thread for evaluation
|
/// images are sent to the caller thread for evaluation
|
||||||
|
|
@ -55,13 +56,19 @@ pub(crate) struct Evaluator {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Evaluator {
|
impl Evaluator {
|
||||||
pub fn new(deadline: Arc<Deadline>, filters: IndexSet<RowFilter>, compression: u8) -> Self {
|
pub fn new(
|
||||||
|
deadline: Arc<Deadline>,
|
||||||
|
filters: IndexSet<RowFilter>,
|
||||||
|
compression: u8,
|
||||||
|
optimize_alpha: bool,
|
||||||
|
) -> Self {
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
let eval_channel = unbounded();
|
let eval_channel = unbounded();
|
||||||
Self {
|
Self {
|
||||||
deadline,
|
deadline,
|
||||||
filters,
|
filters,
|
||||||
compression,
|
compression,
|
||||||
|
optimize_alpha,
|
||||||
best_candidate_size: Arc::new(AtomicMin::new(None)),
|
best_candidate_size: Arc::new(AtomicMin::new(None)),
|
||||||
nth: AtomicUsize::new(0),
|
nth: AtomicUsize::new(0),
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
|
|
@ -106,6 +113,7 @@ impl Evaluator {
|
||||||
let deadline = self.deadline.clone();
|
let deadline = self.deadline.clone();
|
||||||
let filters = self.filters.clone();
|
let filters = self.filters.clone();
|
||||||
let compression = self.compression;
|
let compression = self.compression;
|
||||||
|
let optimize_alpha = self.optimize_alpha;
|
||||||
let best_candidate_size = self.best_candidate_size.clone();
|
let best_candidate_size = self.best_candidate_size.clone();
|
||||||
// sends it off asynchronously for compression,
|
// sends it off asynchronously for compression,
|
||||||
// but results will be collected via the message queue
|
// but results will be collected via the message queue
|
||||||
|
|
@ -122,7 +130,7 @@ impl Evaluator {
|
||||||
if deadline.passed() {
|
if deadline.passed() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let filtered = image.filter_image(filter);
|
let filtered = image.filter_image(filter, optimize_alpha);
|
||||||
if let Ok(idat_data) =
|
if let Ok(idat_data) =
|
||||||
deflate::deflate(&filtered, compression, &best_candidate_size)
|
deflate::deflate(&filtered, compression, &best_candidate_size)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
117
src/filters.rs
117
src/filters.rs
|
|
@ -56,9 +56,21 @@ impl RowFilter {
|
||||||
pub const STANDARD: [Self; 5] = [Self::None, Self::Sub, Self::Up, Self::Average, Self::Paeth];
|
pub const STANDARD: [Self; 5] = [Self::None, Self::Sub, Self::Up, Self::Average, Self::Paeth];
|
||||||
pub const SINGLE_LINE: [Self; 2] = [Self::None, Self::Sub];
|
pub const SINGLE_LINE: [Self; 2] = [Self::None, Self::Sub];
|
||||||
|
|
||||||
pub fn filter_line(self, bpp: usize, data: &[u8], last_line: &[u8], buf: &mut Vec<u8>) {
|
pub fn filter_line(
|
||||||
|
self,
|
||||||
|
bpp: usize,
|
||||||
|
data: &mut [u8],
|
||||||
|
prev_line: &[u8],
|
||||||
|
buf: &mut Vec<u8>,
|
||||||
|
alpha_bytes: usize,
|
||||||
|
) {
|
||||||
assert!(data.len() >= bpp);
|
assert!(data.len() >= bpp);
|
||||||
assert!(last_line.is_empty() || data.len() == last_line.len());
|
assert_eq!(data.len(), prev_line.len());
|
||||||
|
|
||||||
|
if alpha_bytes != 0 {
|
||||||
|
self.optimize_alpha(bpp, data, prev_line, bpp - alpha_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
buf.clear();
|
buf.clear();
|
||||||
buf.reserve(data.len() + 1);
|
buf.reserve(data.len() + 1);
|
||||||
buf.push(self as u8);
|
buf.push(self as u8);
|
||||||
|
|
@ -76,68 +88,101 @@ impl RowFilter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Self::Up => {
|
Self::Up => {
|
||||||
if last_line.is_empty() {
|
|
||||||
buf.extend_from_slice(data);
|
|
||||||
} else {
|
|
||||||
assert_eq!(data.len(), last_line.len());
|
|
||||||
buf.extend(
|
buf.extend(
|
||||||
data.iter()
|
data.iter()
|
||||||
.zip(last_line.iter())
|
.zip(prev_line.iter())
|
||||||
.map(|(cur, last)| cur.wrapping_sub(*last)),
|
.map(|(cur, last)| cur.wrapping_sub(*last)),
|
||||||
);
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Self::Average => {
|
Self::Average => {
|
||||||
for (i, byte) in data.iter().enumerate() {
|
for (i, byte) in data.iter().enumerate() {
|
||||||
if last_line.is_empty() {
|
|
||||||
buf.push(match i.checked_sub(bpp) {
|
|
||||||
Some(x) => byte.wrapping_sub(data[x] >> 1),
|
|
||||||
None => *byte,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
buf.push(match i.checked_sub(bpp) {
|
buf.push(match i.checked_sub(bpp) {
|
||||||
Some(x) => byte.wrapping_sub(
|
Some(x) => byte.wrapping_sub(
|
||||||
((u16::from(data[x]) + u16::from(last_line[i])) >> 1) as u8,
|
((u16::from(data[x]) + u16::from(prev_line[i])) >> 1) as u8,
|
||||||
),
|
),
|
||||||
None => byte.wrapping_sub(last_line[i] >> 1),
|
None => byte.wrapping_sub(prev_line[i] >> 1),
|
||||||
});
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Paeth => {
|
Self::Paeth => {
|
||||||
for (i, byte) in data.iter().enumerate() {
|
for (i, byte) in data.iter().enumerate() {
|
||||||
if last_line.is_empty() {
|
|
||||||
buf.push(match i.checked_sub(bpp) {
|
buf.push(match i.checked_sub(bpp) {
|
||||||
Some(x) => byte.wrapping_sub(data[x]),
|
Some(x) => {
|
||||||
None => *byte,
|
byte.wrapping_sub(paeth_predictor(data[x], prev_line[i], prev_line[x]))
|
||||||
|
}
|
||||||
|
None => byte.wrapping_sub(prev_line[i]),
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
buf.push(match i.checked_sub(bpp) {
|
|
||||||
Some(x) => byte.wrapping_sub(paeth_predictor(
|
|
||||||
data[x],
|
|
||||||
last_line[i],
|
|
||||||
last_line[x],
|
|
||||||
)),
|
|
||||||
None => byte.wrapping_sub(last_line[i]),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimize fully transparent pixels of a scanline such that they will be zeroed when filtered
|
||||||
|
fn optimize_alpha(self, bpp: usize, data: &mut [u8], prev_line: &[u8], color_bytes: usize) {
|
||||||
|
if self == Self::None {
|
||||||
|
// Assume transparent pixels already set to 0
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pixels: Vec<_> = data.chunks_mut(bpp).collect();
|
||||||
|
let prev_pixels: Vec<_> = prev_line.chunks(bpp).collect();
|
||||||
|
for i in 0..pixels.len() {
|
||||||
|
if pixels[i].iter().skip(color_bytes).all(|b| *b == 0) {
|
||||||
|
match self {
|
||||||
|
Self::Sub => {
|
||||||
|
for j in 0..color_bytes {
|
||||||
|
pixels[i][j] = match i {
|
||||||
|
0 => 0,
|
||||||
|
_ => pixels[i - 1][j],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Up => {
|
||||||
|
pixels[i][0..color_bytes].copy_from_slice(&prev_pixels[i][0..color_bytes]);
|
||||||
|
}
|
||||||
|
Self::Average => {
|
||||||
|
for j in 0..color_bytes {
|
||||||
|
pixels[i][j] = match i {
|
||||||
|
0 => prev_pixels[i][j] >> 1,
|
||||||
|
_ => {
|
||||||
|
((u16::from(pixels[i - 1][j]) + u16::from(prev_pixels[i][j]))
|
||||||
|
>> 1) as u8
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Paeth => {
|
||||||
|
if i == 0 {
|
||||||
|
pixels[i][0..color_bytes]
|
||||||
|
.copy_from_slice(&prev_pixels[i][0..color_bytes]);
|
||||||
|
} else {
|
||||||
|
for j in 0..color_bytes {
|
||||||
|
pixels[i][j] = paeth_predictor(
|
||||||
|
pixels[i - 1][j],
|
||||||
|
prev_pixels[i][j],
|
||||||
|
prev_pixels[i - 1][j],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn unfilter_line(
|
pub fn unfilter_line(
|
||||||
self,
|
self,
|
||||||
bpp: usize,
|
bpp: usize,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
last_line: &[u8],
|
prev_line: &[u8],
|
||||||
buf: &mut Vec<u8>,
|
buf: &mut Vec<u8>,
|
||||||
) -> Result<(), PngError> {
|
) -> Result<(), PngError> {
|
||||||
buf.clear();
|
buf.clear();
|
||||||
buf.reserve(data.len());
|
buf.reserve(data.len());
|
||||||
assert!(data.len() >= bpp);
|
assert!(data.len() >= bpp);
|
||||||
assert_eq!(data.len(), last_line.len());
|
assert_eq!(data.len(), prev_line.len());
|
||||||
match self {
|
match self {
|
||||||
Self::None => {
|
Self::None => {
|
||||||
buf.extend_from_slice(data);
|
buf.extend_from_slice(data);
|
||||||
|
|
@ -154,12 +199,12 @@ impl RowFilter {
|
||||||
Self::Up => {
|
Self::Up => {
|
||||||
buf.extend(
|
buf.extend(
|
||||||
data.iter()
|
data.iter()
|
||||||
.zip(last_line)
|
.zip(prev_line)
|
||||||
.map(|(&cur, &last)| cur.wrapping_add(last)),
|
.map(|(&cur, &last)| cur.wrapping_add(last)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Self::Average => {
|
Self::Average => {
|
||||||
for (i, (&cur, &last)) in data.iter().zip(last_line).enumerate() {
|
for (i, (&cur, &last)) in data.iter().zip(prev_line).enumerate() {
|
||||||
let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied());
|
let prev_byte = i.checked_sub(bpp).and_then(|x| buf.get(x).copied());
|
||||||
buf.push(match prev_byte {
|
buf.push(match prev_byte {
|
||||||
Some(b) => cur.wrapping_add(((u16::from(b) + u16::from(last)) >> 1) as u8),
|
Some(b) => cur.wrapping_add(((u16::from(b) + u16::from(last)) >> 1) as u8),
|
||||||
|
|
@ -168,11 +213,11 @@ impl RowFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Paeth => {
|
Self::Paeth => {
|
||||||
for (i, (&cur, &up)) in data.iter().zip(last_line).enumerate() {
|
for (i, (&cur, &up)) in data.iter().zip(prev_line).enumerate() {
|
||||||
buf.push(
|
buf.push(
|
||||||
match i
|
match i
|
||||||
.checked_sub(bpp)
|
.checked_sub(bpp)
|
||||||
.map(|x| (buf.get(x).copied(), last_line.get(x).copied()))
|
.map(|x| (buf.get(x).copied(), prev_line.get(x).copied()))
|
||||||
{
|
{
|
||||||
Some((Some(left), Some(left_up))) => {
|
Some((Some(left), Some(left_up))) => {
|
||||||
cur.wrapping_add(paeth_predictor(left, up, left_up))
|
cur.wrapping_add(paeth_predictor(left, up, left_up))
|
||||||
|
|
|
||||||
34
src/lib.rs
34
src/lib.rs
|
|
@ -491,13 +491,25 @@ fn optimize_png(
|
||||||
perform_strip(png, opts);
|
perform_strip(png, opts);
|
||||||
let stripped_png = png.clone();
|
let stripped_png = png.clone();
|
||||||
|
|
||||||
|
// If alpha optimization is enabled, first perform a black alpha reduction
|
||||||
|
// This can allow reductions from alpha to indexed which may not have been possible otherwise
|
||||||
|
if !opts.alphas.is_empty() {
|
||||||
|
if let Some(reduced) = filtered_alpha_channel(&png.raw, AlphaOptim::Black) {
|
||||||
|
png.raw = Arc::new(reduced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Must use normal (lazy) compression, as faster ones (greedy) are not representative
|
// Must use normal (lazy) compression, as faster ones (greedy) are not representative
|
||||||
// Alpha reductions can benefit from higher compression but otherwise it's not beneficial
|
|
||||||
let eval_compression = 5;
|
let eval_compression = 5;
|
||||||
// None and Bigrams work well together, especially for alpha reductions
|
// None and Bigrams work well together, especially for alpha reductions
|
||||||
let eval_filters = indexset! {RowFilter::None, RowFilter::Bigrams};
|
let eval_filters = indexset! {RowFilter::None, RowFilter::Bigrams};
|
||||||
// This will collect all versions of images and pick one that compresses best
|
// This will collect all versions of images and pick one that compresses best
|
||||||
let eval = Evaluator::new(deadline.clone(), eval_filters.clone(), eval_compression);
|
let eval = Evaluator::new(
|
||||||
|
deadline.clone(),
|
||||||
|
eval_filters.clone(),
|
||||||
|
eval_compression,
|
||||||
|
false,
|
||||||
|
);
|
||||||
perform_reductions(png.raw.clone(), opts, &deadline, &eval);
|
perform_reductions(png.raw.clone(), opts, &deadline, &eval);
|
||||||
let (reduction_occurred, mut eval_filter) = if let Some(result) = eval.get_best_candidate() {
|
let (reduction_occurred, mut eval_filter) = if let Some(result) = eval.get_best_candidate() {
|
||||||
*png = result.image;
|
*png = result.image;
|
||||||
|
|
@ -519,7 +531,8 @@ fn optimize_png(
|
||||||
|
|
||||||
if !filters.is_empty() {
|
if !filters.is_empty() {
|
||||||
debug!("Evaluating: {} filters", filters.len());
|
debug!("Evaluating: {} filters", filters.len());
|
||||||
let eval = Evaluator::new(deadline, filters, eval_compression);
|
let eval =
|
||||||
|
Evaluator::new(deadline, filters, eval_compression, !opts.alphas.is_empty());
|
||||||
if eval_filter.is_some() {
|
if eval_filter.is_some() {
|
||||||
eval.set_best_size(png.idat_data.len());
|
eval.set_best_size(png.idat_data.len());
|
||||||
}
|
}
|
||||||
|
|
@ -585,7 +598,7 @@ fn optimize_png(
|
||||||
if deadline.passed() {
|
if deadline.passed() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let filtered = &png.raw.filter_image(trial.filter);
|
let filtered = &png.raw.filter_image(trial.filter, !opts.alphas.is_empty());
|
||||||
perform_trial(filtered, opts, trial, &best_size)
|
perform_trial(filtered, opts, trial, &best_size)
|
||||||
});
|
});
|
||||||
best.reduce_with(|i, j| {
|
best.reduce_with(|i, j| {
|
||||||
|
|
@ -606,7 +619,7 @@ fn optimize_png(
|
||||||
opts.filter,
|
opts.filter,
|
||||||
png.idat_data.len()
|
png.idat_data.len()
|
||||||
);
|
);
|
||||||
} else if eval_filter.is_some() {
|
} else {
|
||||||
*png = stripped_png;
|
*png = stripped_png;
|
||||||
}
|
}
|
||||||
} else if png.idat_data.len() >= idat_original_size {
|
} else if png.idat_data.len() >= idat_original_size {
|
||||||
|
|
@ -726,13 +739,6 @@ fn perform_reductions(
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.color_type_reduction {
|
if opts.color_type_reduction {
|
||||||
// Perform a black alpha reduction before color type reductions
|
|
||||||
// This can allow reductions from alpha to indexed which may not have been possible otherwise
|
|
||||||
if !opts.alphas.is_empty() {
|
|
||||||
if let Some(reduced) = filtered_alpha_channel(&png, AlphaOptim::Black) {
|
|
||||||
png = Arc::new(reduced);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(reduced) = reduce_color_type(&png, opts.grayscale_reduction) {
|
if let Some(reduced) = reduce_color_type(&png, opts.grayscale_reduction) {
|
||||||
png = Arc::new(reduced);
|
png = Arc::new(reduced);
|
||||||
eval.try_image(png.clone());
|
eval.try_image(png.clone());
|
||||||
|
|
@ -744,10 +750,6 @@ fn perform_reductions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if try_alpha_reductions(png, &opts.alphas, eval) {
|
|
||||||
reduction_occurred = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(baseline) = baseline {
|
if let Some(baseline) = baseline {
|
||||||
if reduction_occurred {
|
if reduction_occurred {
|
||||||
eval.set_baseline(baseline);
|
eval.set_baseline(baseline);
|
||||||
|
|
|
||||||
|
|
@ -314,31 +314,43 @@ impl PngImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the specified filter type to all rows in the image
|
/// Apply the specified filter type to all rows in the image
|
||||||
pub fn filter_image(&self, filter: RowFilter) -> Vec<u8> {
|
pub fn filter_image(&self, filter: RowFilter, optimize_alpha: bool) -> Vec<u8> {
|
||||||
let mut filtered = Vec::with_capacity(self.data.len());
|
let mut filtered = Vec::with_capacity(self.data.len());
|
||||||
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
|
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
|
||||||
let mut last_line: &[u8] = &[];
|
// If alpha optimization is enabled, determine how many bytes of alpha there are per pixel
|
||||||
let mut last_pass: Option<u8> = None;
|
let alpha_bytes = match self.ihdr.color_type {
|
||||||
|
ColorType::RGBA | ColorType::GrayscaleAlpha if optimize_alpha => {
|
||||||
|
(self.ihdr.bit_depth.as_u8() / 8) as usize
|
||||||
|
}
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut prev_line = Vec::new();
|
||||||
|
let mut prev_pass: Option<u8> = None;
|
||||||
let mut f_buf = Vec::new();
|
let mut f_buf = Vec::new();
|
||||||
for line in self.scan_lines() {
|
for line in self.scan_lines() {
|
||||||
if last_pass != line.pass {
|
if prev_pass != line.pass || line.data.len() != prev_line.len() {
|
||||||
last_line = &[];
|
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 filter <= RowFilter::Paeth {
|
if filter <= RowFilter::Paeth {
|
||||||
// Standard filters
|
// Standard filters
|
||||||
let filter = if last_pass == line.pass || filter <= RowFilter::Sub {
|
let filter = if prev_pass == line.pass || filter <= RowFilter::Sub {
|
||||||
filter
|
filter
|
||||||
} else {
|
} else {
|
||||||
RowFilter::None
|
RowFilter::None
|
||||||
};
|
};
|
||||||
filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
filter.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes);
|
||||||
filtered.extend_from_slice(&f_buf);
|
filtered.extend_from_slice(&f_buf);
|
||||||
|
prev_line = line_data;
|
||||||
} else {
|
} else {
|
||||||
// Heuristic filter selection strategies
|
// Heuristic filter selection strategies
|
||||||
let mut best_line = Vec::new();
|
let mut best_line = Vec::new();
|
||||||
|
let mut best_line_raw = Vec::new();
|
||||||
// Avoid vertical filtering on first line of each interlacing pass
|
// Avoid vertical filtering on first line of each interlacing pass
|
||||||
let try_filters = if last_pass == line.pass {
|
let try_filters = if prev_pass == line.pass {
|
||||||
RowFilter::STANDARD.iter()
|
RowFilter::STANDARD.iter()
|
||||||
} else {
|
} else {
|
||||||
RowFilter::SINGLE_LINE.iter()
|
RowFilter::SINGLE_LINE.iter()
|
||||||
|
|
@ -348,8 +360,8 @@ impl PngImage {
|
||||||
// MSAD algorithm mentioned in libpng reference docs
|
// MSAD algorithm mentioned in libpng reference docs
|
||||||
// http://www.libpng.org/pub/png/book/chapter09.html
|
// http://www.libpng.org/pub/png/book/chapter09.html
|
||||||
let mut best_size = usize::MAX;
|
let mut best_size = usize::MAX;
|
||||||
for try_filter in try_filters {
|
for f in try_filters {
|
||||||
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
f.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes);
|
||||||
let size = f_buf.iter().fold(0, |acc, &x| {
|
let size = f_buf.iter().fold(0, |acc, &x| {
|
||||||
let signed = x as i8;
|
let signed = x as i8;
|
||||||
acc + signed.unsigned_abs() as usize
|
acc + signed.unsigned_abs() as usize
|
||||||
|
|
@ -357,6 +369,7 @@ impl PngImage {
|
||||||
if size < best_size {
|
if size < best_size {
|
||||||
best_size = size;
|
best_size = size;
|
||||||
std::mem::swap(&mut best_line, &mut f_buf);
|
std::mem::swap(&mut best_line, &mut f_buf);
|
||||||
|
best_line_raw = line_data.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -364,8 +377,8 @@ impl PngImage {
|
||||||
// Shannon entropy algorithm, from LodePNG
|
// Shannon entropy algorithm, from LodePNG
|
||||||
// https://github.com/lvandeve/lodepng
|
// https://github.com/lvandeve/lodepng
|
||||||
let mut best_size = i32::MIN;
|
let mut best_size = i32::MIN;
|
||||||
for try_filter in try_filters {
|
for f in try_filters {
|
||||||
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
f.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes);
|
||||||
let mut counts = vec![0; 0x100];
|
let mut counts = vec![0; 0x100];
|
||||||
for &i in f_buf.iter() {
|
for &i in f_buf.iter() {
|
||||||
counts[i as usize] += 1;
|
counts[i as usize] += 1;
|
||||||
|
|
@ -379,6 +392,7 @@ impl PngImage {
|
||||||
if size > best_size {
|
if size > best_size {
|
||||||
best_size = size;
|
best_size = size;
|
||||||
std::mem::swap(&mut best_line, &mut f_buf);
|
std::mem::swap(&mut best_line, &mut f_buf);
|
||||||
|
best_line_raw = line_data.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -386,8 +400,8 @@ impl PngImage {
|
||||||
// Count distinct bigrams, from pngwolf
|
// Count distinct bigrams, from pngwolf
|
||||||
// https://bjoern.hoehrmann.de/pngwolf/
|
// https://bjoern.hoehrmann.de/pngwolf/
|
||||||
let mut best_size = usize::MAX;
|
let mut best_size = usize::MAX;
|
||||||
for try_filter in try_filters {
|
for f in try_filters {
|
||||||
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
f.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes);
|
||||||
let mut set = bitarr![0; 0x10000];
|
let mut set = bitarr![0; 0x10000];
|
||||||
for pair in f_buf.windows(2) {
|
for pair in f_buf.windows(2) {
|
||||||
let bigram = (pair[0] as usize) << 8 | pair[1] as usize;
|
let bigram = (pair[0] as usize) << 8 | pair[1] as usize;
|
||||||
|
|
@ -397,6 +411,7 @@ impl PngImage {
|
||||||
if size < best_size {
|
if size < best_size {
|
||||||
best_size = size;
|
best_size = size;
|
||||||
std::mem::swap(&mut best_line, &mut f_buf);
|
std::mem::swap(&mut best_line, &mut f_buf);
|
||||||
|
best_line_raw = line_data.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -405,8 +420,8 @@ impl PngImage {
|
||||||
let mut best_size = i32::MIN;
|
let mut best_size = i32::MIN;
|
||||||
// FxHasher is the fastest rust hasher currently available for this purpose
|
// FxHasher is the fastest rust hasher currently available for this purpose
|
||||||
let mut counts = FxHashMap::<u16, u32>::default();
|
let mut counts = FxHashMap::<u16, u32>::default();
|
||||||
for try_filter in try_filters {
|
for f in try_filters {
|
||||||
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
f.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes);
|
||||||
counts.clear();
|
counts.clear();
|
||||||
for pair in f_buf.windows(2) {
|
for pair in f_buf.windows(2) {
|
||||||
let bigram = (pair[0] as u16) << 8 | pair[1] as u16;
|
let bigram = (pair[0] as u16) << 8 | pair[1] as u16;
|
||||||
|
|
@ -416,6 +431,7 @@ impl PngImage {
|
||||||
if size > best_size {
|
if size > best_size {
|
||||||
best_size = size;
|
best_size = size;
|
||||||
std::mem::swap(&mut best_line, &mut f_buf);
|
std::mem::swap(&mut best_line, &mut f_buf);
|
||||||
|
best_line_raw = line_data.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -431,8 +447,8 @@ impl PngImage {
|
||||||
let capacity = compressor.zlib_compress_bound(limit);
|
let capacity = compressor.zlib_compress_bound(limit);
|
||||||
let mut dest = vec![0; capacity];
|
let mut dest = vec![0; capacity];
|
||||||
|
|
||||||
for try_filter in try_filters {
|
for f in try_filters {
|
||||||
try_filter.filter_line(bpp, line.data, last_line, &mut f_buf);
|
f.filter_line(bpp, &mut line_data, &prev_line, &mut f_buf, alpha_bytes);
|
||||||
filtered[line_start..].copy_from_slice(&f_buf);
|
filtered[line_start..].copy_from_slice(&f_buf);
|
||||||
let size = compressor
|
let size = compressor
|
||||||
.zlib_compress(&filtered[filtered.len() - limit..], &mut dest)
|
.zlib_compress(&filtered[filtered.len() - limit..], &mut dest)
|
||||||
|
|
@ -440,6 +456,7 @@ impl PngImage {
|
||||||
if size < best_size {
|
if size < best_size {
|
||||||
best_size = size;
|
best_size = size;
|
||||||
std::mem::swap(&mut best_line, &mut f_buf);
|
std::mem::swap(&mut best_line, &mut f_buf);
|
||||||
|
best_line_raw = line_data.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filtered.resize(line_start, 0);
|
filtered.resize(line_start, 0);
|
||||||
|
|
@ -447,10 +464,10 @@ impl PngImage {
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
filtered.extend_from_slice(&best_line);
|
filtered.extend_from_slice(&best_line);
|
||||||
|
prev_line = best_line_raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_line = line.data;
|
prev_pass = line.pass;
|
||||||
last_pass = line.pass;
|
|
||||||
}
|
}
|
||||||
filtered
|
filtered
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,7 @@
|
||||||
use crate::colors::AlphaOptim;
|
use crate::colors::AlphaOptim;
|
||||||
use crate::colors::ColorType;
|
use crate::colors::ColorType;
|
||||||
use crate::evaluate::Evaluator;
|
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
#[cfg(not(feature = "parallel"))]
|
|
||||||
use crate::rayon::prelude::*;
|
|
||||||
use indexmap::IndexSet;
|
|
||||||
#[cfg(feature = "parallel")]
|
|
||||||
use rayon::prelude::*;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub(crate) fn try_alpha_reductions(
|
|
||||||
png: Arc<PngImage>,
|
|
||||||
alphas: &IndexSet<AlphaOptim>,
|
|
||||||
eval: &Evaluator,
|
|
||||||
) -> bool {
|
|
||||||
match png.ihdr.color_type {
|
|
||||||
ColorType::RGBA | ColorType::GrayscaleAlpha if !alphas.is_empty() => {
|
|
||||||
alphas
|
|
||||||
.par_iter()
|
|
||||||
.with_max_len(1)
|
|
||||||
.filter_map(|&alpha| filtered_alpha_channel(&png, alpha))
|
|
||||||
.for_each(|image| eval.try_image(Arc::new(image)));
|
|
||||||
true
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn filtered_alpha_channel(png: &PngImage, optim: AlphaOptim) -> Option<PngImage> {
|
pub fn filtered_alpha_channel(png: &PngImage, optim: AlphaOptim) -> Option<PngImage> {
|
||||||
let (bpc, bpp) = match png.ihdr.color_type {
|
let (bpc, bpp) = match png.ihdr.color_type {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::bit_depth::reduce_bit_depth_8_or_less;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
use crate::color::*;
|
use crate::color::*;
|
||||||
|
|
||||||
pub(crate) use crate::alpha::{filtered_alpha_channel, try_alpha_reductions};
|
pub(crate) use crate::alpha::filtered_alpha_channel;
|
||||||
pub(crate) use crate::bit_depth::reduce_bit_depth;
|
pub(crate) use crate::bit_depth::reduce_bit_depth;
|
||||||
|
|
||||||
/// Attempt to reduce the number of colors in the palette
|
/// Attempt to reduce the number of colors in the palette
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue