Minor speedup of to_indexed

This commit is contained in:
Andrew 2023-05-11 08:23:56 +12:00
parent 5e052e5f62
commit 98cfcd25bc

View file

@ -1,40 +1,34 @@
use crate::colors::{BitDepth, ColorType}; use crate::colors::{BitDepth, ColorType};
use crate::headers::IhdrData; use crate::headers::IhdrData;
use crate::png::PngImage; use crate::png::PngImage;
use indexmap::IndexMap; use indexmap::IndexSet;
use rgb::alt::Gray; use rgb::alt::Gray;
use rgb::{ComponentMap, ComponentSlice, FromSlice, RGB, RGBA, RGBA8}; use rgb::{ComponentMap, ComponentSlice, FromSlice, RGB, RGBA, RGBA8};
use rustc_hash::FxHasher; use rustc_hash::FxHasher;
use std::hash::{BuildHasherDefault, Hash}; use std::hash::{BuildHasherDefault, Hash};
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; type FxIndexSet<V> = IndexSet<V, BuildHasherDefault<FxHasher>>;
/// Maximum size difference between indexed and channels to consider a candidate for evaluation /// Maximum size difference between indexed and channels to consider a candidate for evaluation
pub const INDEXED_MAX_DIFF: usize = 20000; pub const INDEXED_MAX_DIFF: usize = 20000;
fn reduce_scanline_to_palette<T>( fn build_palette<T>(
iter: impl IntoIterator<Item = T>, iter: impl IntoIterator<Item = T>,
palette: &mut FxIndexMap<T, u8>,
reduced: &mut Vec<u8>, reduced: &mut Vec<u8>,
) -> bool ) -> Option<FxIndexSet<T>>
where where
T: Eq + Hash, T: Eq + Hash,
{ {
let mut palette = FxIndexSet::default();
palette.reserve(257);
for pixel in iter { for pixel in iter {
let idx = if let Some(&idx) = palette.get(&pixel) { let (idx, _) = palette.insert_full(pixel);
idx if idx == 256 {
} else { return None;
let len = palette.len(); }
if len == 256 { reduced.push(idx as u8);
return false;
}
let idx = len as u8;
palette.insert(pixel, idx);
idx
};
reduced.push(idx);
} }
true Some(palette)
} }
#[must_use] #[must_use]
@ -46,57 +40,49 @@ pub fn reduced_to_indexed(png: &PngImage) -> Option<PngImage> {
return None; return None;
} }
let mut raw_data = Vec::with_capacity(png.data.len()); let mut raw_data = Vec::with_capacity(png.data.len() / png.channels_per_pixel());
let mut palette = FxIndexMap::default(); let mut palette: Vec<_> = match png.ihdr.color_type {
palette.reserve(257); ColorType::Grayscale { transparent_shade } => {
let ok = if let ColorType::Grayscale { transparent_shade } = png.ihdr.color_type { let pmap = build_palette(png.data.as_gray().iter().cloned(), &mut raw_data)?;
// Convert the Gray16 transparency to Gray8 // Convert the Gray16 transparency to Gray8
let transparency_pixel = transparent_shade.map(|t| Gray::from(t as u8)); let transparency_pixel = transparent_shade.map(|t| Gray::from(t as u8));
reduce_scanline_to_palette( pmap.into_iter()
png.data.as_gray().iter().cloned().map(|px| { .map(|px| {
RGB::from(px).alpha(if Some(px) != transparency_pixel { RGB::from(px).alpha(if Some(px) != transparency_pixel {
255 255
} else { } else {
0 0
})
}) })
}), .collect()
&mut palette, }
&mut raw_data, ColorType::RGB { transparent_color } => {
) let pmap = build_palette(png.data.as_rgb().iter().cloned(), &mut raw_data)?;
} else if let ColorType::RGB { transparent_color } = png.ihdr.color_type { // Convert the RGB16 transparency to RGB8
// Convert the RGB16 transparency to RGB8 let transparency_pixel = transparent_color.map(|t| t.map(|c| c as u8));
let transparency_pixel = transparent_color.map(|t| t.map(|c| c as u8)); pmap.into_iter()
reduce_scanline_to_palette( .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 { 0
0 })
}) })
}), .collect()
&mut palette, }
&mut raw_data, ColorType::GrayscaleAlpha => {
) let pmap = build_palette(png.data.as_gray_alpha().iter().cloned(), &mut raw_data)?;
} else if png.ihdr.color_type == ColorType::GrayscaleAlpha { pmap.into_iter().map(RGBA::from).collect()
reduce_scanline_to_palette( }
png.data.as_gray_alpha().iter().cloned().map(RGBA::from), ColorType::RGBA => {
&mut palette, let pmap = build_palette(png.data.as_rgba().iter().cloned(), &mut raw_data)?;
&mut raw_data, pmap.into_iter().collect()
) }
} else { _ => return None,
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 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) = aux_headers.remove(b"bKGD") {
let bg = if png.ihdr.color_type.is_rgb() && bkgd_header.len() == 6 { let bg = if png.ihdr.color_type.is_rgb() && bkgd_header.len() == 6 {
// In bKGD 16-bit values are used even for 8-bit images // In bKGD 16-bit values are used even for 8-bit images
Some(RGBA8::new( Some(RGBA8::new(
@ -116,16 +102,15 @@ pub fn reduced_to_indexed(png: &PngImage) -> Option<PngImage> {
None None
}; };
if let Some(bg) = bg { if let Some(bg) = bg {
let entry = if let Some(&entry) = palette.get(&bg) { let idx = palette.iter().position(|&px| px == bg).or_else(|| {
entry if palette.len() < 256 {
} else if palette.len() < 256 { palette.push(bg);
let entry = palette.len() as u8; Some(palette.len() - 1)
palette.insert(bg, entry); } else {
entry None // No space in palette to store the bg as an index
} else { }
return None; // No space in palette to store the bg as an index })?;
}; aux_headers.insert(*b"bKGD", vec![idx as u8]);
aux_headers.insert(*b"bKGD", vec![entry]);
} }
} }
@ -134,17 +119,10 @@ pub fn reduced_to_indexed(png: &PngImage) -> Option<PngImage> {
aux_headers.insert(*b"sBIT", sbit_header.iter().cloned().take(3).collect()); aux_headers.insert(*b"sBIT", sbit_header.iter().cloned().take(3).collect());
} }
let mut palette_vec = vec![RGBA8::new(0, 0, 0, 0); palette.len()];
for (color, idx) in palette {
palette_vec[idx as usize] = color;
}
Some(PngImage { Some(PngImage {
data: raw_data, data: raw_data,
ihdr: IhdrData { ihdr: IhdrData {
color_type: ColorType::Indexed { color_type: ColorType::Indexed { palette },
palette: palette_vec,
},
..png.ihdr ..png.ihdr
}, },
aux_headers, aux_headers,