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());
for line in png.scan_lines(false) {
for pixel in line.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).all(|b| *b == 0) {
reduced.resize(reduced.len() + bpp, 0);
} else {
reduced.extend_from_slice(pixel);
}
for pixel in png.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).all(|b| *b == 0) {
reduced.resize(reduced.len() + bpp, 0);
} else {
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 used_colors = vec![false; 256];
for line in png.scan_lines(false) {
for pixel in line.data.chunks(bpp) {
if optimize_alpha && pixel.iter().skip(colored_bytes).all(|b| *b == 0) {
// Fully transparent, we may be able to reduce with tRNS
has_transparency = true;
} else if pixel.iter().skip(colored_bytes).any(|b| *b != 255) {
// Partially transparent, the image is not reducible
return None;
} 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
used_colors[pixel[0] as usize] = true;
}
for pixel in png.data.chunks(bpp) {
if optimize_alpha && pixel.iter().skip(colored_bytes).all(|b| *b == 0) {
// Fully transparent, we may be able to reduce with tRNS
has_transparency = true;
} else if pixel.iter().skip(colored_bytes).any(|b| *b != 255) {
// Partially transparent, the image is not reducible
return None;
} 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
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());
for line in png.scan_lines(false) {
for pixel in line.data.chunks(bpp) {
match transparency_pixel {
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.extend_from_slice(&pixel[0..colored_bytes]),
};
}
for pixel in png.data.chunks(bpp) {
match transparency_pixel {
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.extend_from_slice(&pixel[0..colored_bytes]),
};
}
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
let mut reduced = Vec::with_capacity(
(png.ihdr.width * png.ihdr.height * u32::from(png.channels_per_pixel())) as usize,
);
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);
}
}
if png.data.chunks(2).any(|pair| pair[0] != pair[1]) {
// Can't reduce
return None;
}
Some(PngImage {
data: reduced,
data: png.data.iter().step_by(2).cloned().collect(),
ihdr: IhdrData {
bit_depth: BitDepth::Eight,
..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 {
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
.data
.iter()
@ -105,23 +89,23 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
return None;
}
}
} else {
for &byte in line.data {
while minimum_bits < bit_depth {
let permutations: &[u8] = if minimum_bits == 1 {
&ONE_BIT_PERMUTATIONS
} else if minimum_bits == 2 {
&TWO_BIT_PERMUTATIONS
} else if minimum_bits == 4 {
&FOUR_BIT_PERMUTATIONS
} else {
return None;
};
if permutations.iter().any(|perm| *perm == byte) {
break;
}
minimum_bits <<= 1;
}
} else {
for &byte in &png.data {
while minimum_bits < bit_depth {
let permutations: &[u8] = if minimum_bits == 1 {
&ONE_BIT_PERMUTATIONS
} else if minimum_bits == 2 {
&TWO_BIT_PERMUTATIONS
} else if minimum_bits == 4 {
&FOUR_BIT_PERMUTATIONS
} else {
return None;
};
if permutations.iter().any(|perm| *perm == byte) {
break;
}
minimum_bits <<= 1;
}
}
}

View file

@ -19,37 +19,35 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option<PngImage> {
return None;
}
let colored_bytes = bpp - byte_depth;
for line in png.scan_lines(false) {
let mut low_bytes = Vec::with_capacity(4);
let mut high_bytes = Vec::with_capacity(4);
let mut trans_bytes = Vec::with_capacity(byte_depth as usize);
for (i, byte) in line.data.iter().enumerate() {
if i as u8 & bpp_mask < colored_bytes {
if byte_depth == 1 || i % 2 == 1 {
low_bytes.push(*byte);
} else {
high_bytes.push(*byte);
}
let mut low_bytes = Vec::with_capacity(4);
let mut high_bytes = Vec::with_capacity(4);
let mut trans_bytes = Vec::with_capacity(byte_depth as usize);
for (i, byte) in png.data.iter().enumerate() {
if i as u8 & bpp_mask < colored_bytes {
if byte_depth == 1 || i % 2 == 1 {
low_bytes.push(*byte);
} else {
trans_bytes.push(*byte);
high_bytes.push(*byte);
}
} else {
trans_bytes.push(*byte);
}
if (i as u8 & bpp_mask) == bpp - 1 {
if low_bytes.iter().unique().count() > 1 {
if (i as u8 & bpp_mask) == bpp - 1 {
if low_bytes.iter().unique().count() > 1 {
return None;
}
if byte_depth == 2 {
if high_bytes.iter().unique().count() > 1 {
return None;
}
if byte_depth == 2 {
if high_bytes.iter().unique().count() > 1 {
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(high_bytes[0]);
high_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()
.filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6)
.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 {
reduce_scanline_to_palette(
line.data.as_rgb().iter().cloned().map(|px| {
px.alpha(if Some(px) != transparency_pixel {
255
} else {
0
})
}),
&mut palette,
&mut raw_data,
)
} else if png.ihdr.color_type == ColorType::GrayscaleAlpha {
reduce_scanline_to_palette(
line.data.as_gray_alpha().iter().cloned().map(|px| RGBA {
r: px.0,
g: px.0,
b: px.0,
a: px.1,
}),
&mut palette,
&mut raw_data,
)
} else {
debug_assert_eq!(png.ihdr.color_type, ColorType::RGBA);
reduce_scanline_to_palette(
line.data.as_rgba().iter().cloned(),
&mut palette,
&mut raw_data,
)
};
if !ok {
return None;
}
let ok = if png.ihdr.color_type == ColorType::RGB {
reduce_scanline_to_palette(
png.data.as_rgb().iter().cloned().map(|px| {
px.alpha(if Some(px) != transparency_pixel {
255
} else {
0
})
}),
&mut palette,
&mut raw_data,
)
} else if png.ihdr.color_type == ColorType::GrayscaleAlpha {
reduce_scanline_to_palette(
png.data.as_gray_alpha().iter().cloned().map(|px| RGBA {
r: px.0,
g: px.0,
b: px.0,
a: px.1,
}),
&mut palette,
&mut raw_data,
)
} else {
debug_assert_eq!(png.ihdr.color_type, ColorType::RGBA);
reduce_scanline_to_palette(
png.data.as_rgba().iter().cloned(),
&mut palette,
&mut raw_data,
)
};
if !ok {
return None;
}
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 bpp: usize = 3 * byte_depth as usize;
let mut cur_pixel = Vec::with_capacity(bpp);
for line in png.scan_lines(false) {
for (i, byte) in line.data.iter().enumerate() {
cur_pixel.push(*byte);
if i % bpp == bpp - 1 {
if bpp == 3 {
if cur_pixel.iter().unique().count() > 1 {
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);
for (i, byte) in png.data.iter().enumerate() {
cur_pixel.push(*byte);
if i % bpp == bpp - 1 {
if bpp == 3 {
if cur_pixel.iter().unique().count() > 1 {
return None;
}
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()?;
// Find palette entries that are never used
for line in png.scan_lines(false) {
match png.ihdr.bit_depth {
BitDepth::Eight => {
for &byte in line.data {
used[byte as usize] = true;
}
match png.ihdr.bit_depth {
BitDepth::Eight => {
for &byte in &png.data {
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();
@ -117,14 +115,9 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
#[must_use]
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 mut raw_data = Vec::with_capacity(png.data.len());
// Reassign data bytes to new indices
for line in png.scan_lines(false) {
for byte in line.data {
raw_data.push(byte_map[*byte as usize]);
}
}
let raw_data = png.data.iter().map(|b| byte_map[*b as usize]).collect();
let mut aux_headers = png.aux_headers.clone();
if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") {