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,13 +16,11 @@ 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);
}
} }
} }
@ -53,18 +51,16 @@ 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; } else if pixel.iter().skip(colored_bytes).any(|b| *b != 255) {
} else if pixel.iter().skip(colored_bytes).any(|b| *b != 255) { // Partially transparent, the image is not reducible
// Partially transparent, the image is not reducible return None;
return None; } else if optimize_alpha && pixel.iter().take(colored_bytes).all(|b| *b == pixel[0]) {
} else if optimize_alpha && pixel.iter().take(colored_bytes).all(|b| *b == pixel[0]) { // Opaque shade of gray, we can't use this color for tRNS
// Opaque shade of gray, we can't use this color for tRNS used_colors[pixel[0] as usize] = true;
used_colors[pixel[0] as usize] = true;
}
} }
} }
@ -82,15 +78,13 @@ 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]); }
} _ => 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();

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
); return None;
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;
}
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,23 +89,23 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
return None; return None;
} }
} }
} else { }
for &byte in line.data { } else {
while minimum_bits < bit_depth { for &byte in &png.data {
let permutations: &[u8] = if minimum_bits == 1 { while minimum_bits < bit_depth {
&ONE_BIT_PERMUTATIONS let permutations: &[u8] = if minimum_bits == 1 {
} else if minimum_bits == 2 { &ONE_BIT_PERMUTATIONS
&TWO_BIT_PERMUTATIONS } else if minimum_bits == 2 {
} else if minimum_bits == 4 { &TWO_BIT_PERMUTATIONS
&FOUR_BIT_PERMUTATIONS } else if minimum_bits == 4 {
} else { &FOUR_BIT_PERMUTATIONS
return None; } else {
}; return None;
if permutations.iter().any(|perm| *perm == byte) { };
break; if permutations.iter().any(|perm| *perm == byte) {
} break;
minimum_bits <<= 1;
} }
minimum_bits <<= 1;
} }
} }
} }

View file

@ -19,37 +19,35 @@ 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 png.data.iter().enumerate() {
for (i, byte) in line.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);
} else {
high_bytes.push(*byte);
}
} else { } else {
trans_bytes.push(*byte); high_bytes.push(*byte);
} }
} else {
trans_bytes.push(*byte);
}
if (i as u8 & bpp_mask) == bpp - 1 { if (i as u8 & bpp_mask) == bpp - 1 {
if low_bytes.iter().unique().count() > 1 { if low_bytes.iter().unique().count() > 1 {
return None;
}
if byte_depth == 2 {
if high_bytes.iter().unique().count() > 1 {
return None; return None;
} }
if byte_depth == 2 { reduced.push(high_bytes[0]);
if high_bytes.iter().unique().count() > 1 { high_bytes.clear();
return None;
}
reduced.push(high_bytes[0]);
high_bytes.clear();
}
reduced.push(low_bytes[0]);
low_bytes.clear();
reduced.extend_from_slice(&trans_bytes);
trans_bytes.clear();
} }
reduced.push(low_bytes[0]);
low_bytes.clear();
reduced.extend_from_slice(&trans_bytes);
trans_bytes.clear();
} }
} }
@ -116,41 +114,39 @@ 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( png.data.as_rgb().iter().cloned().map(|px| {
line.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 { 0
0 })
}) }),
}), &mut palette,
&mut palette, &mut raw_data,
&mut raw_data, )
) } else if png.ihdr.color_type == ColorType::GrayscaleAlpha {
} else if png.ihdr.color_type == ColorType::GrayscaleAlpha { reduce_scanline_to_palette(
reduce_scanline_to_palette( png.data.as_gray_alpha().iter().cloned().map(|px| RGBA {
line.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, a: px.1,
a: px.1, }),
}), &mut palette,
&mut palette, &mut raw_data,
&mut raw_data, )
) } 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( png.data.as_rgba().iter().cloned(),
line.data.as_rgba().iter().cloned(), &mut palette,
&mut palette, &mut raw_data,
&mut raw_data, )
) };
}; if !ok {
if !ok { return None;
return None;
}
} }
let num_transparent = palette let num_transparent = palette
@ -219,31 +215,29 @@ 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 { if cur_pixel.iter().unique().count() > 1 {
if cur_pixel.iter().unique().count() > 1 { return None;
return None;
}
reduced.push(cur_pixel[0]);
} else {
let pixel_bytes = cur_pixel
.iter()
.step_by(2)
.cloned()
.zip(cur_pixel.iter().skip(1).step_by(2).cloned())
.unique()
.collect::<Vec<(u8, u8)>>();
if pixel_bytes.len() > 1 {
return None;
}
reduced.push(pixel_bytes[0].0);
reduced.push(pixel_bytes[0].1);
} }
cur_pixel.clear(); reduced.push(cur_pixel[0]);
} else {
let pixel_bytes = cur_pixel
.iter()
.step_by(2)
.cloned()
.zip(cur_pixel.iter().skip(1).step_by(2).cloned())
.unique()
.collect::<Vec<(u8, u8)>>();
if pixel_bytes.len() > 1 {
return None;
}
reduced.push(pixel_bytes[0].0);
reduced.push(pixel_bytes[0].1);
} }
cur_pixel.clear();
} }
} }

View file

@ -34,29 +34,27 @@ 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 &png.data {
for &byte in line.data { used[byte as usize] = true;
used[byte as usize] = true;
}
} }
BitDepth::Four => {
for &byte in line.data {
used[(byte & 0x0F) as usize] = true;
used[(byte >> 4) as usize] = true;
}
}
BitDepth::Two => {
for &byte in line.data {
used[(byte & 0x03) as usize] = true;
used[((byte >> 2) & 0x03) as usize] = true;
used[((byte >> 4) & 0x03) as usize] = true;
used[(byte >> 6) as usize] = true;
}
}
_ => unreachable!(),
} }
BitDepth::Four => {
for &byte in &png.data {
used[(byte & 0x0F) as usize] = true;
used[(byte >> 4) as usize] = true;
}
}
BitDepth::Two => {
for &byte in &png.data {
used[(byte & 0x03) as usize] = true;
used[((byte >> 2) & 0x03) as usize] = true;
used[((byte >> 4) & 0x03) as usize] = true;
used[(byte >> 6) as usize] = true;
}
}
_ => unreachable!(),
} }
let mut used_enumerated: Vec<(usize, &bool)> = used.iter().enumerate().collect(); let mut used_enumerated: Vec<(usize, &bool)> = used.iter().enumerate().collect();
@ -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") {