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::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use rgb::{ComponentMap, FromSlice, RGBA, RGBA8};
|
use rgb::{ComponentMap, ComponentSlice, FromSlice, RGBA, RGBA8};
|
||||||
use rustc_hash::FxHasher;
|
use rustc_hash::FxHasher;
|
||||||
use std::hash::{BuildHasherDefault, Hash};
|
use std::hash::{BuildHasherDefault, Hash};
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[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 {
|
if png.ihdr.bit_depth != BitDepth::Eight || png.channels_per_pixel() == 1 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +153,7 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[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() {
|
if !png.ihdr.color_type.is_rgb() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -204,3 +204,67 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
|
||||||
aux_headers,
|
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
|
// Attempt to reduce RGB to grayscale
|
||||||
// This is just removal of bytes and does not need to be evaluated
|
// This is just removal of bytes and does not need to be evaluated
|
||||||
if opts.color_type_reduction && !deadline.passed() {
|
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);
|
png = Arc::new(reduced);
|
||||||
reduction_occurred = true;
|
reduction_occurred = true;
|
||||||
}
|
}
|
||||||
|
|
@ -74,9 +74,9 @@ pub(crate) fn perform_reductions(
|
||||||
if opts.color_type_reduction && !deadline.passed() {
|
if opts.color_type_reduction && !deadline.passed() {
|
||||||
if let Some(reduced) = reduced_alpha_channel(&png, opts.optimize_alpha) {
|
if let Some(reduced) = reduced_alpha_channel(&png, opts.optimize_alpha) {
|
||||||
png = Arc::new(reduced);
|
png = Arc::new(reduced);
|
||||||
// If the reduction requires a tRNS chunk, enter this into the evaluator
|
// For small differences, if a tRNS chunk is required then enter this into the evaluator
|
||||||
// Otherwise it is just removal of bytes and should become the baseline
|
// Otherwise it is mostly just removal of bytes and should become the baseline
|
||||||
if png.ihdr.color_type.has_trns() {
|
if png.ihdr.color_type.has_trns() && baseline.data.len() - png.data.len() <= 1000 {
|
||||||
eval.try_image(png.clone());
|
eval.try_image(png.clone());
|
||||||
evaluation_added = true;
|
evaluation_added = true;
|
||||||
} else {
|
} 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 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);
|
png = Arc::new(reduced);
|
||||||
// Make sure the palette gets sorted (but don't bother evaluating both results)
|
// Make sure the palette gets sorted (but don't bother evaluating both results)
|
||||||
if let Some(reduced) = sorted_palette(&png) {
|
if let Some(reduced) = sorted_palette(&png) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue