Simplify reductions by not using scan lines
This commit is contained in:
parent
7122214523
commit
bacb7b8b74
4 changed files with 141 additions and 176 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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") {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue