oxipng/src/reduction/alpha.rs

233 lines
7.9 KiB
Rust

use evaluate::Evaluator;
use itertools::flatten;
use png::scan_lines::ScanLine;
use std::collections::HashSet;
use std::sync::Arc;
use colors::AlphaOptim;
use headers::IhdrData;
use png::PngImage;
use colors::ColorType;
use rayon::prelude::*;
pub(crate) fn try_alpha_reductions(png: Arc<PngImage>, alphas: &HashSet<AlphaOptim>, eval: &Evaluator) {
assert!(!alphas.is_empty());
let alphas = alphas.iter().collect::<Vec<_>>();
let alphas_iter = alphas.par_iter().with_max_len(1);
alphas_iter
.filter_map(|&alpha| filtered_alpha_channel(&png, *alpha))
.for_each(|image| eval.try_image(Arc::new(image), 0.99));
}
pub fn filtered_alpha_channel(png: &PngImage, optim: AlphaOptim) -> Option<PngImage> {
let (bpc, bpp) = match png.ihdr.color_type {
ColorType::RGBA | ColorType::GrayscaleAlpha => {
let cpp = png.channels_per_pixel();
let bpc = png.ihdr.bit_depth.as_u8() / 8;
(bpc as usize, (bpc * cpp) as usize)
}
_ => {
return None;
}
};
let raw_data = match optim {
AlphaOptim::NoOp => return None,
AlphaOptim::Black => reduced_alpha_to_black(png, bpc, bpp),
AlphaOptim::White => reduced_alpha_to_white(png, bpc, bpp),
AlphaOptim::Up => reduced_alpha_to_up(png, bpc, bpp),
AlphaOptim::Down => reduced_alpha_to_down(png, bpc, bpp),
AlphaOptim::Left => reduced_alpha_to_left(png, bpc, bpp),
AlphaOptim::Right => reduced_alpha_to_right(png, bpc, bpp),
};
Some(PngImage {
data: raw_data,
ihdr: png.ihdr,
palette: png.palette.clone(),
transparency_pixel: png.transparency_pixel.clone(),
aux_headers: png.aux_headers.clone(),
})
}
fn reduced_alpha_to_black(png: &PngImage, bpc: usize, bpp: usize) -> Vec<u8> {
let mut reduced = Vec::with_capacity(png.data.len());
for line in png.scan_lines() {
reduced.push(line.filter);
for pixel in line.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
for _ in 0..bpp {
reduced.push(0);
}
} else {
reduced.extend_from_slice(pixel);
}
}
}
reduced
}
fn reduced_alpha_to_white(png: &PngImage, bpc: usize, bpp: usize) -> Vec<u8> {
let mut reduced = Vec::with_capacity(png.data.len());
for line in png.scan_lines() {
reduced.push(line.filter);
for pixel in line.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
for _ in 0..(bpp - bpc) {
reduced.push(255);
}
for _ in 0..bpc {
reduced.push(0);
}
} else {
reduced.extend_from_slice(pixel);
}
}
}
reduced
}
fn reduced_alpha_to_up(png: &PngImage, bpc: usize, bpp: usize) -> Vec<u8> {
let mut lines = Vec::new();
let mut scan_lines = png.scan_lines().collect::<Vec<ScanLine>>();
scan_lines.reverse();
let mut last_line = Vec::new();
let mut current_line = Vec::with_capacity(last_line.len());
for line in scan_lines {
if line.data.len() != last_line.len() {
last_line = vec![0; line.data.len()];
}
current_line.push(line.filter);
for (pixel, last_pixel) in line.data.chunks(bpp).zip(last_line.chunks(bpp)) {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
current_line.extend_from_slice(&last_pixel[0..(bpp - bpc)]);
for _ in 0..bpc {
current_line.push(0);
}
} else {
current_line.extend_from_slice(pixel);
}
}
last_line = current_line.clone();
lines.push(current_line.clone());
current_line.clear();
}
flatten(lines.into_iter().rev()).collect()
}
fn reduced_alpha_to_down(png: &PngImage, bpc: usize, bpp: usize) -> Vec<u8> {
let mut reduced = Vec::with_capacity(png.data.len());
let mut last_line = Vec::new();
for line in png.scan_lines() {
if line.data.len() != last_line.len() {
last_line = vec![0; line.data.len()];
}
reduced.push(line.filter);
for (pixel, last_pixel) in line.data.chunks(bpp).zip(last_line.chunks(bpp)) {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
reduced.extend_from_slice(&last_pixel[0..(bpp - bpc)]);
for _ in 0..bpc {
reduced.push(0);
}
} else {
reduced.extend_from_slice(pixel);
}
}
last_line = reduced.clone();
}
reduced
}
fn reduced_alpha_to_left(png: &PngImage, bpc: usize, bpp: usize) -> Vec<u8> {
let mut reduced = Vec::with_capacity(png.data.len());
for line in png.scan_lines() {
let mut line_bytes = Vec::with_capacity(line.data.len());
let mut last_pixel = vec![0; bpp];
for pixel in line.data.chunks(bpp).rev() {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
line_bytes.extend_from_slice(&last_pixel[0..(bpp - bpc)]);
for _ in 0..bpc {
line_bytes.push(0);
}
} else {
line_bytes.extend_from_slice(pixel);
}
last_pixel = pixel.to_owned();
}
reduced.push(line.filter);
reduced.extend(flatten(line_bytes.chunks(bpp).rev()));
}
reduced
}
fn reduced_alpha_to_right(png: &PngImage, bpc: usize, bpp: usize) -> Vec<u8> {
let mut reduced = Vec::with_capacity(png.data.len());
for line in png.scan_lines() {
reduced.push(line.filter);
let mut last_pixel = vec![0; bpp];
for pixel in line.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).fold(0, |sum, i| sum | i) == 0 {
reduced.extend_from_slice(&last_pixel[0..(bpp - bpc)]);
for _ in 0..bpc {
reduced.push(0);
}
} else {
reduced.extend_from_slice(pixel);
}
last_pixel = pixel.to_owned();
}
}
reduced
}
#[must_use]
pub fn reduced_alpha_channel(png: &PngImage) -> Option<PngImage> {
let target_color_type = match png.ihdr.color_type {
ColorType::GrayscaleAlpha => ColorType::Grayscale,
ColorType::RGBA => ColorType::RGB,
_ => return None,
};
let byte_depth = png.ihdr.bit_depth.as_u8() >> 3;
let channels = png.channels_per_pixel();
let bpp = channels * byte_depth;
let bpp_mask = bpp - 1;
assert_eq!(0, bpp & bpp_mask);
let colored_bytes = bpp - byte_depth;
for line in png.scan_lines() {
for (i, &byte) in line.data.iter().enumerate() {
if i as u8 & bpp_mask >= colored_bytes && byte != 255 {
return None;
}
}
}
let mut raw_data = Vec::with_capacity(png.data.len());
for line in png.scan_lines() {
raw_data.push(line.filter);
for (i, &byte) in line.data.iter().enumerate() {
if i as u8 & bpp_mask >= colored_bytes {
continue;
} else {
raw_data.push(byte);
}
}
}
let mut aux_headers = png.aux_headers.clone();
// sBIT contains information about alpha channel's original depth,
// and alpha has just been removed
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
// Some programs save the sBIT header as RGB even if the image is RGBA.
aux_headers.insert(*b"sBIT", sbit_header.iter().cloned().take(3).collect());
}
Some(PngImage {
data: raw_data,
ihdr: IhdrData {
color_type: target_color_type,
..png.ihdr
},
aux_headers,
transparency_pixel: None,
palette: None,
})
}