Simplify reductions by not using scan lines

This commit is contained in:
Andrew 2022-12-15 10:51:34 +13:00
parent 7122214523
commit bacb7b8b74
4 changed files with 141 additions and 176 deletions

View file

@ -16,15 +16,13 @@ pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
}; };
let mut reduced = Vec::with_capacity(png.data.len()); let mut reduced = Vec::with_capacity(png.data.len());
for line in png.scan_lines(false) { for pixel in png.data.chunks(bpp) {
for pixel in line.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).all(|b| *b == 0) { if pixel.iter().skip(bpp - bpc).all(|b| *b == 0) {
reduced.resize(reduced.len() + bpp, 0); reduced.resize(reduced.len() + bpp, 0);
} else { } else {
reduced.extend_from_slice(pixel); reduced.extend_from_slice(pixel);
} }
} }
}
Some(PngImage { Some(PngImage {
data: reduced, data: reduced,
@ -53,8 +51,7 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
let mut has_transparency = false; let mut has_transparency = false;
let mut used_colors = vec![false; 256]; let mut used_colors = vec![false; 256];
for line in png.scan_lines(false) { for pixel in png.data.chunks(bpp) {
for pixel in line.data.chunks(bpp) {
if optimize_alpha && pixel.iter().skip(colored_bytes).all(|b| *b == 0) { if optimize_alpha && pixel.iter().skip(colored_bytes).all(|b| *b == 0) {
// Fully transparent, we may be able to reduce with tRNS // Fully transparent, we may be able to reduce with tRNS
has_transparency = true; has_transparency = true;
@ -66,7 +63,6 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
used_colors[pixel[0] as usize] = true; used_colors[pixel[0] as usize] = true;
} }
} }
}
let transparency_pixel = if has_transparency { let transparency_pixel = if has_transparency {
// If no unused color was found we will have to fail here // If no unused color was found we will have to fail here
@ -82,8 +78,7 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
}; };
let mut raw_data = Vec::with_capacity(png.data.len()); let mut raw_data = Vec::with_capacity(png.data.len());
for line in png.scan_lines(false) { for pixel in png.data.chunks(bpp) {
for pixel in line.data.chunks(bpp) {
match transparency_pixel { match transparency_pixel {
Some(ref trns) if pixel.iter().skip(colored_bytes).all(|b| *b == 0) => { Some(ref trns) if pixel.iter().skip(colored_bytes).all(|b| *b == 0) => {
raw_data.resize(raw_data.len() + colored_bytes, trns[1]); raw_data.resize(raw_data.len() + colored_bytes, trns[1]);
@ -91,7 +86,6 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
_ => raw_data.extend_from_slice(&pixel[0..colored_bytes]), _ => raw_data.extend_from_slice(&pixel[0..colored_bytes]),
}; };
} }
}
let mut aux_headers = png.aux_headers.clone(); let mut aux_headers = png.aux_headers.clone();
// sBIT contains information about alpha channel's original depth, // sBIT contains information about alpha channel's original depth,

View file

@ -37,29 +37,13 @@ pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage>
} }
// Reduce from 16 to 8 bits per channel per pixel // Reduce from 16 to 8 bits per channel per pixel
let mut reduced = Vec::with_capacity( if png.data.chunks(2).any(|pair| pair[0] != pair[1]) {
(png.ihdr.width * png.ihdr.height * u32::from(png.channels_per_pixel())) as usize, // Can't reduce
);
let mut high_byte = 0;
for line in png.scan_lines(false) {
for (i, &byte) in line.data.iter().enumerate() {
if i % 2 == 0 {
// High byte
high_byte = byte;
} else {
// Low byte
if high_byte != byte {
// Can't reduce, exit early
return None; return None;
} }
reduced.push(byte);
}
}
}
Some(PngImage { Some(PngImage {
data: reduced, data: png.data.iter().step_by(2).cloned().collect(),
ihdr: IhdrData { ihdr: IhdrData {
bit_depth: BitDepth::Eight, bit_depth: BitDepth::Eight,
..png.ihdr ..png.ihdr
@ -77,8 +61,8 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
if minimum_bits >= bit_depth { if minimum_bits >= bit_depth {
return None; return None;
} }
for line in png.scan_lines(false) {
if png.ihdr.color_type == ColorType::Indexed { if png.ihdr.color_type == ColorType::Indexed {
for line in png.scan_lines(false) {
let line_max = line let line_max = line
.data .data
.iter() .iter()
@ -105,8 +89,9 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
return None; return None;
} }
} }
}
} else { } else {
for &byte in line.data { for &byte in &png.data {
while minimum_bits < bit_depth { while minimum_bits < bit_depth {
let permutations: &[u8] = if minimum_bits == 1 { let permutations: &[u8] = if minimum_bits == 1 {
&ONE_BIT_PERMUTATIONS &ONE_BIT_PERMUTATIONS
@ -124,7 +109,6 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
} }
} }
} }
}
let mut reduced = BitVec::<u8, Msb0>::with_capacity(png.data.len() * 8); let mut reduced = BitVec::<u8, Msb0>::with_capacity(png.data.len() * 8);
for line in png.scan_lines(false) { for line in png.scan_lines(false) {

View file

@ -19,11 +19,10 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option<PngImage> {
return None; return None;
} }
let colored_bytes = bpp - byte_depth; let colored_bytes = bpp - byte_depth;
for line in png.scan_lines(false) {
let mut low_bytes = Vec::with_capacity(4); let mut low_bytes = Vec::with_capacity(4);
let mut high_bytes = Vec::with_capacity(4); let mut high_bytes = Vec::with_capacity(4);
let mut trans_bytes = Vec::with_capacity(byte_depth as usize); let mut trans_bytes = Vec::with_capacity(byte_depth as usize);
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in png.data.iter().enumerate() {
if i as u8 & bpp_mask < colored_bytes { if i as u8 & bpp_mask < colored_bytes {
if byte_depth == 1 || i % 2 == 1 { if byte_depth == 1 || i % 2 == 1 {
low_bytes.push(*byte); low_bytes.push(*byte);
@ -51,7 +50,6 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option<PngImage> {
trans_bytes.clear(); trans_bytes.clear();
} }
} }
}
let mut aux_headers = png.aux_headers.clone(); let mut aux_headers = png.aux_headers.clone();
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") { if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
@ -116,10 +114,9 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
.as_ref() .as_ref()
.filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6) .filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6)
.map(|t| RGB8::new(t[1], t[3], t[5])); .map(|t| RGB8::new(t[1], t[3], t[5]));
for line in png.scan_lines(false) {
let ok = if png.ihdr.color_type == ColorType::RGB { let ok = if png.ihdr.color_type == ColorType::RGB {
reduce_scanline_to_palette( reduce_scanline_to_palette(
line.data.as_rgb().iter().cloned().map(|px| { png.data.as_rgb().iter().cloned().map(|px| {
px.alpha(if Some(px) != transparency_pixel { px.alpha(if Some(px) != transparency_pixel {
255 255
} else { } else {
@ -131,7 +128,7 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
) )
} else if png.ihdr.color_type == ColorType::GrayscaleAlpha { } else if png.ihdr.color_type == ColorType::GrayscaleAlpha {
reduce_scanline_to_palette( reduce_scanline_to_palette(
line.data.as_gray_alpha().iter().cloned().map(|px| RGBA { png.data.as_gray_alpha().iter().cloned().map(|px| RGBA {
r: px.0, r: px.0,
g: px.0, g: px.0,
b: px.0, b: px.0,
@ -143,7 +140,7 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
} else { } else {
debug_assert_eq!(png.ihdr.color_type, ColorType::RGBA); debug_assert_eq!(png.ihdr.color_type, ColorType::RGBA);
reduce_scanline_to_palette( reduce_scanline_to_palette(
line.data.as_rgba().iter().cloned(), png.data.as_rgba().iter().cloned(),
&mut palette, &mut palette,
&mut raw_data, &mut raw_data,
) )
@ -151,7 +148,6 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
if !ok { if !ok {
return None; return None;
} }
}
let num_transparent = palette let num_transparent = palette
.iter() .iter()
@ -219,8 +215,7 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
let byte_depth: u8 = png.ihdr.bit_depth.as_u8() >> 3; let byte_depth: u8 = png.ihdr.bit_depth.as_u8() >> 3;
let bpp: usize = 3 * byte_depth as usize; let bpp: usize = 3 * byte_depth as usize;
let mut cur_pixel = Vec::with_capacity(bpp); let mut cur_pixel = Vec::with_capacity(bpp);
for line in png.scan_lines(false) { for (i, byte) in png.data.iter().enumerate() {
for (i, byte) in line.data.iter().enumerate() {
cur_pixel.push(*byte); cur_pixel.push(*byte);
if i % bpp == bpp - 1 { if i % bpp == bpp - 1 {
if bpp == 3 { if bpp == 3 {
@ -245,7 +240,6 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
cur_pixel.clear(); cur_pixel.clear();
} }
} }
}
let transparency_pixel = if let Some(ref trns) = png.transparency_pixel { let transparency_pixel = if let Some(ref trns) = png.transparency_pixel {
if trns.len() != 6 || trns[0..2] != trns[2..4] || trns[2..4] != trns[4..6] { if trns.len() != 6 || trns[0..2] != trns[2..4] || trns[2..4] != trns[4..6] {

View file

@ -34,21 +34,20 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
let palette = png.palette.as_ref()?; let palette = png.palette.as_ref()?;
// Find palette entries that are never used // Find palette entries that are never used
for line in png.scan_lines(false) {
match png.ihdr.bit_depth { match png.ihdr.bit_depth {
BitDepth::Eight => { BitDepth::Eight => {
for &byte in line.data { for &byte in &png.data {
used[byte as usize] = true; used[byte as usize] = true;
} }
} }
BitDepth::Four => { BitDepth::Four => {
for &byte in line.data { for &byte in &png.data {
used[(byte & 0x0F) as usize] = true; used[(byte & 0x0F) as usize] = true;
used[(byte >> 4) as usize] = true; used[(byte >> 4) as usize] = true;
} }
} }
BitDepth::Two => { BitDepth::Two => {
for &byte in line.data { for &byte in &png.data {
used[(byte & 0x03) as usize] = true; used[(byte & 0x03) as usize] = true;
used[((byte >> 2) & 0x03) as usize] = true; used[((byte >> 2) & 0x03) as usize] = true;
used[((byte >> 4) & 0x03) as usize] = true; used[((byte >> 4) & 0x03) as usize] = true;
@ -57,7 +56,6 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
} }
_ => unreachable!(), _ => unreachable!(),
} }
}
let mut used_enumerated: Vec<(usize, &bool)> = used.iter().enumerate().collect(); let mut used_enumerated: Vec<(usize, &bool)> = used.iter().enumerate().collect();
used_enumerated.sort_by(|a, b| { used_enumerated.sort_by(|a, b| {
@ -117,14 +115,9 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
#[must_use] #[must_use]
fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Option<PngImage> { fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Option<PngImage> {
let byte_map = palette_map_to_byte_map(png, palette_map)?; let byte_map = palette_map_to_byte_map(png, palette_map)?;
let mut raw_data = Vec::with_capacity(png.data.len());
// Reassign data bytes to new indices // Reassign data bytes to new indices
for line in png.scan_lines(false) { let raw_data = png.data.iter().map(|b| byte_map[*b as usize]).collect();
for byte in line.data {
raw_data.push(byte_map[*byte as usize]);
}
}
let mut aux_headers = png.aux_headers.clone(); let mut aux_headers = png.aux_headers.clone();
if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") { if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") {