Indexed to channels
This commit is contained in:
parent
21d6d955f6
commit
762e6ba788
2 changed files with 83 additions and 9 deletions
|
|
@ -2,7 +2,7 @@ use crate::colors::{BitDepth, ColorType};
|
|||
use crate::headers::IhdrData;
|
||||
use crate::png::PngImage;
|
||||
use indexmap::IndexMap;
|
||||
use rgb::{ComponentMap, FromSlice, RGBA, RGBA8};
|
||||
use rgb::{ComponentMap, ComponentSlice, FromSlice, RGBA, RGBA8};
|
||||
use rustc_hash::FxHasher;
|
||||
use std::hash::{BuildHasherDefault, Hash};
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ where
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
|
||||
pub fn reduced_to_indexed(png: &PngImage) -> Option<PngImage> {
|
||||
if png.ihdr.bit_depth != BitDepth::Eight || png.channels_per_pixel() == 1 {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -153,7 +153,7 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
|
||||
pub fn reduced_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
|
||||
if !png.ihdr.color_type.is_rgb() {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -204,3 +204,67 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
|
|||
aux_headers,
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempt to convert indexed to a different color type, returning the resulting image if successful
|
||||
#[must_use]
|
||||
pub fn indexed_to_channels(png: &PngImage) -> Option<PngImage> {
|
||||
if png.ihdr.bit_depth != BitDepth::Eight {
|
||||
return None;
|
||||
}
|
||||
let palette = match &png.ihdr.color_type {
|
||||
ColorType::Indexed { palette } => palette,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// Determine which channels are required
|
||||
let is_gray = palette.iter().all(|c| c.r == c.g && c.g == c.b);
|
||||
let has_alpha = palette.iter().any(|c| c.a != 255);
|
||||
let color_type = match (is_gray, has_alpha) {
|
||||
(false, true) => ColorType::RGBA,
|
||||
(false, false) => ColorType::RGB {
|
||||
transparent_color: None,
|
||||
},
|
||||
(true, true) => ColorType::GrayscaleAlpha,
|
||||
(true, false) => ColorType::Grayscale {
|
||||
transparent_shade: None,
|
||||
},
|
||||
};
|
||||
|
||||
// Don't proceed if output would be too much larger
|
||||
let out_size = color_type.channels_per_pixel() as usize * png.data.len();
|
||||
if out_size - png.data.len() > 10000 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Construct the new data
|
||||
let black = RGBA::new(0, 0, 0, 255);
|
||||
let ch_start = if is_gray { 2 } else { 0 };
|
||||
let ch_end = if has_alpha { 3 } else { 2 };
|
||||
let mut data = Vec::with_capacity(out_size);
|
||||
for b in &png.data {
|
||||
let color = palette.get(*b as usize).unwrap_or(&black);
|
||||
data.extend_from_slice(&color.as_slice()[ch_start..=ch_end]);
|
||||
}
|
||||
|
||||
// Update bKGD if it exists
|
||||
let mut aux_headers = png.aux_headers.clone();
|
||||
if let Some(idx) = aux_headers.remove(b"bKGD").and_then(|b| b.first().cloned()) {
|
||||
if let Some(color) = palette.get(idx as usize) {
|
||||
let bkgd = if is_gray {
|
||||
vec![0, color.r]
|
||||
} else {
|
||||
vec![0, color.r, 0, color.g, 0, color.b]
|
||||
};
|
||||
aux_headers.insert(*b"bKGD", bkgd);
|
||||
}
|
||||
}
|
||||
|
||||
Some(PngImage {
|
||||
ihdr: IhdrData {
|
||||
color_type,
|
||||
..png.ihdr
|
||||
},
|
||||
data,
|
||||
aux_headers,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ pub(crate) fn perform_reductions(
|
|||
// Attempt to reduce RGB to grayscale
|
||||
// This is just removal of bytes and does not need to be evaluated
|
||||
if opts.color_type_reduction && !deadline.passed() {
|
||||
if let Some(reduced) = reduce_rgb_to_grayscale(&png) {
|
||||
if let Some(reduced) = reduced_rgb_to_grayscale(&png) {
|
||||
png = Arc::new(reduced);
|
||||
reduction_occurred = true;
|
||||
}
|
||||
|
|
@ -74,9 +74,9 @@ pub(crate) fn perform_reductions(
|
|||
if opts.color_type_reduction && !deadline.passed() {
|
||||
if let Some(reduced) = reduced_alpha_channel(&png, opts.optimize_alpha) {
|
||||
png = Arc::new(reduced);
|
||||
// If the reduction requires a tRNS chunk, enter this into the evaluator
|
||||
// Otherwise it is just removal of bytes and should become the baseline
|
||||
if png.ihdr.color_type.has_trns() {
|
||||
// For small differences, if a tRNS chunk is required then enter this into the evaluator
|
||||
// Otherwise it is mostly just removal of bytes and should become the baseline
|
||||
if png.ihdr.color_type.has_trns() && baseline.data.len() - png.data.len() <= 1000 {
|
||||
eval.try_image(png.clone());
|
||||
evaluation_added = true;
|
||||
} else {
|
||||
|
|
@ -95,9 +95,19 @@ pub(crate) fn perform_reductions(
|
|||
}
|
||||
}
|
||||
|
||||
// Attempt to reduce to palette
|
||||
// Attempt to convert from indexed to channels
|
||||
// This may give a better result due to dropping the PLTE chunk
|
||||
if opts.color_type_reduction && !deadline.passed() {
|
||||
if let Some(reduced) = reduce_to_palette(&png) {
|
||||
if let Some(reduced) = indexed_to_channels(&png) {
|
||||
// This result should not be passed on to subsequent reductions
|
||||
eval.try_image(Arc::new(reduced));
|
||||
evaluation_added = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to reduce to indexed
|
||||
if opts.color_type_reduction && !deadline.passed() {
|
||||
if let Some(reduced) = reduced_to_indexed(&png) {
|
||||
png = Arc::new(reduced);
|
||||
// Make sure the palette gets sorted (but don't bother evaluating both results)
|
||||
if let Some(reduced) = sorted_palette(&png) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue