Refactor ColorType

Move transparency and palette data into the ColorType
This commit is contained in:
Andrew 2023-04-20 08:03:48 +12:00 committed by Josh Holmer
parent 36af4198ed
commit 91723507b4
9 changed files with 193 additions and 181 deletions

View file

@ -1,14 +1,15 @@
use rgb::{RGB16, RGBA8};
use std::fmt;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone)]
/// The color type used to represent this image
pub enum ColorType {
/// Grayscale, with one color channel
Grayscale,
Grayscale { transparent: Option<u16> },
/// RGB, with three color channels
RGB,
RGB { transparent: Option<RGB16> },
/// Indexed, with one byte per pixel representing one of up to 256 colors in the image
Indexed,
Indexed { palette: Vec<RGBA8> },
/// Grayscale + Alpha, with two color channels
GrayscaleAlpha,
/// RGBA, with four color channels
@ -22,9 +23,9 @@ impl fmt::Display for ColorType {
f,
"{}",
match *self {
ColorType::Grayscale => "Grayscale",
ColorType::RGB => "RGB",
ColorType::Indexed => "Indexed",
ColorType::Grayscale { .. } => "Grayscale",
ColorType::RGB { .. } => "RGB",
ColorType::Indexed { .. } => "Indexed",
ColorType::GrayscaleAlpha => "Grayscale + Alpha",
ColorType::RGBA => "RGB + Alpha",
}
@ -35,22 +36,22 @@ impl fmt::Display for ColorType {
impl ColorType {
/// Get the code used by the PNG specification to denote this color type
#[inline]
pub fn png_header_code(self) -> u8 {
pub fn png_header_code(&self) -> u8 {
match self {
ColorType::Grayscale => 0,
ColorType::RGB => 2,
ColorType::Indexed => 3,
ColorType::Grayscale { .. } => 0,
ColorType::RGB { .. } => 2,
ColorType::Indexed { .. } => 3,
ColorType::GrayscaleAlpha => 4,
ColorType::RGBA => 6,
}
}
#[inline]
pub fn channels_per_pixel(self) -> u8 {
pub fn channels_per_pixel(&self) -> u8 {
match self {
ColorType::Grayscale | ColorType::Indexed => 1,
ColorType::Grayscale { .. } | ColorType::Indexed { .. } => 1,
ColorType::GrayscaleAlpha => 2,
ColorType::RGB => 3,
ColorType::RGB { .. } => 3,
ColorType::RGBA => 4,
}
}

View file

@ -4,10 +4,11 @@ use crate::error::PngError;
use crate::interlace::Interlacing;
use crate::PngResult;
use indexmap::IndexSet;
use rgb::{RGB16, RGBA8};
use std::io;
use std::io::{Cursor, Read};
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
/// Headers from the IHDR chunk of the image
pub struct IhdrData {
/// The width of the image in pixels
@ -143,15 +144,31 @@ pub fn parse_next_header<'a>(
Ok(Some(RawHeader { name, data }))
}
pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
pub fn parse_ihdr_header(
byte_data: &[u8],
palette_data: Option<Vec<u8>>,
trns_data: Option<Vec<u8>>,
) -> PngResult<IhdrData> {
// This eliminates bounds checks for the rest of the function
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
let mut rdr = Cursor::new(&byte_data[0..8]);
Ok(IhdrData {
color_type: match byte_data[9] {
0 => ColorType::Grayscale,
2 => ColorType::RGB,
3 => ColorType::Indexed,
0 => ColorType::Grayscale {
transparent: trns_data
.filter(|t| t.len() >= 2)
.map(|t| u16::from_be_bytes([t[0], t[1]])),
},
2 => ColorType::RGB {
transparent: trns_data.filter(|t| t.len() >= 6).map(|t| RGB16 {
r: u16::from_be_bytes([t[0], t[1]]),
g: u16::from_be_bytes([t[2], t[3]]),
b: u16::from_be_bytes([t[4], t[5]]),
}),
},
3 => ColorType::Indexed {
palette: palette_to_rgba(palette_data, trns_data).unwrap_or(vec![]),
},
4 => ColorType::GrayscaleAlpha,
6 => ColorType::RGBA,
_ => return Err(PngError::new("Unexpected color type in header")),
@ -172,6 +189,25 @@ pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
})
}
/// Construct an RGBA palette from the raw palette and transparency data
fn palette_to_rgba(
palette_data: Option<Vec<u8>>,
trns_data: Option<Vec<u8>>,
) -> Result<Vec<RGBA8>, PngError> {
let palette_data = palette_data.ok_or_else(|| PngError::new("no palette in indexed image"))?;
let mut palette: Vec<_> = palette_data
.chunks(3)
.map(|color| RGBA8::new(color[0], color[1], color[2], 255))
.collect();
if let Some(trns_data) = trns_data {
for (color, trns) in palette.iter_mut().zip(trns_data) {
color.a = trns;
}
}
Ok(palette)
}
#[inline]
fn read_be_u32<T: AsRef<[u8]>>(rdr: &mut Cursor<T>) -> Result<u32, io::Error> {
let mut int_buf = [0; 4];

View file

@ -87,12 +87,11 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
PngImage {
data: output,
ihdr: IhdrData {
color_type: png.ihdr.color_type.clone(),
interlaced: Interlacing::Adam7,
..png.ihdr
},
aux_headers: png.aux_headers.clone(),
palette: png.palette.clone(),
transparency_pixel: png.transparency_pixel.clone(),
}
}
@ -103,12 +102,11 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
_ => deinterlace_bits(png),
},
ihdr: IhdrData {
color_type: png.ihdr.color_type.clone(),
interlaced: Interlacing::None,
..png.ihdr
},
aux_headers: png.aux_headers.clone(),
palette: png.palette.clone(),
transparency_pixel: png.transparency_pixel.clone(),
}
}
@ -143,7 +141,7 @@ fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
// Calculate the next line and move to next pass if necessary
current_y += pass_constants.y_step as usize;
if current_y >= png.ihdr.height as usize {
if !increment_pass(&mut current_pass, png.ihdr) {
if !increment_pass(&mut current_pass, &png.ihdr) {
break;
}
pass_constants = interlaced_constants(current_pass);
@ -180,7 +178,7 @@ fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
// Calculate the next line and move to next pass if necessary
current_y += pass_constants.y_step as usize;
if current_y >= png.ihdr.height as usize {
if !increment_pass(&mut current_pass, png.ihdr) {
if !increment_pass(&mut current_pass, &png.ihdr) {
break;
}
pass_constants = interlaced_constants(current_pass);
@ -190,7 +188,7 @@ fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
lines.concat()
}
fn increment_pass(current_pass: &mut u8, ihdr: IhdrData) -> bool {
fn increment_pass(current_pass: &mut u8, ihdr: &IhdrData) -> bool {
if *current_pass == 7 {
return false;
}

View file

@ -25,7 +25,7 @@ extern crate rayon;
mod rayon;
use crate::atomicmin::AtomicMin;
use crate::colors::BitDepth;
use crate::colors::{BitDepth, ColorType};
use crate::deflate::{crc32, inflate};
use crate::evaluate::Evaluator;
use crate::png::PngData;
@ -814,7 +814,7 @@ impl Deadline {
/// Display the format of the image data
fn report_format(prefix: &str, png: &PngImage) {
if let Some(ref palette) = png.palette {
if let ColorType::Indexed { palette } = &png.ihdr.color_type {
debug!(
"{}{} bits/pixel, {} colors in palette ({})",
prefix,

View file

@ -8,7 +8,6 @@ use bitvec::bitarr;
use indexmap::IndexMap;
use libdeflater::{CompressionLvl, Compressor};
use rgb::ComponentSlice;
use rgb::RGBA8;
use rustc_hash::FxHashMap;
use std::fs::File;
use std::io::{BufReader, Read, Write};
@ -31,11 +30,6 @@ pub struct PngImage {
pub ihdr: IhdrData,
/// The uncompressed, unfiltered data from the IDAT chunk
pub data: Vec<u8>,
/// The palette containing colors used in an Indexed image
/// Contains 3 bytes per color (R+G+B), up to 768
pub palette: Option<Vec<RGBA8>>,
/// The pixel value that should be rendered as transparent
pub transparency_pixel: Option<Vec<u8>>,
/// All non-critical headers from the PNG are stored here
pub aux_headers: IndexMap<[u8; 4], Vec<u8>>,
}
@ -51,8 +45,6 @@ pub struct PngData {
pub filtered: Vec<u8>,
}
type PaletteWithTrns = (Option<Vec<RGBA8>>, Option<Vec<u8>>);
impl PngData {
/// Create a new `PngData` struct by opening a file
#[inline]
@ -116,7 +108,11 @@ impl PngData {
Some(ihdr) => ihdr,
None => return Err(PngError::ChunkMissing("IHDR")),
};
let ihdr_header = parse_ihdr_header(&ihdr)?;
let ihdr_header = parse_ihdr_header(
&ihdr,
aux_headers.remove(b"PLTE"),
aux_headers.remove(b"tRNS"),
)?;
let raw_data = deflate::inflate(idat_headers.as_ref(), ihdr_header.raw_data_size())?;
// Reject files with incorrect width/height or truncated data
@ -124,17 +120,9 @@ impl PngData {
return Err(PngError::TruncatedData);
}
let (palette, transparency_pixel) = Self::palette_to_rgba(
ihdr_header.color_type,
aux_headers.remove(b"PLTE"),
aux_headers.remove(b"tRNS"),
)?;
let mut raw = PngImage {
ihdr: ihdr_header,
data: raw_data,
palette,
transparency_pixel,
aux_headers,
};
let unfiltered = raw.unfilter_image()?;
@ -146,31 +134,6 @@ impl PngData {
})
}
/// Handle transparency header
fn palette_to_rgba(
color_type: ColorType,
palette_data: Option<Vec<u8>>,
trns_data: Option<Vec<u8>>,
) -> Result<PaletteWithTrns, PngError> {
if color_type == ColorType::Indexed {
let palette_data =
palette_data.ok_or_else(|| PngError::new("no palette in indexed image"))?;
let mut palette: Vec<_> = palette_data
.chunks(3)
.map(|color| RGBA8::new(color[0], color[1], color[2], 255))
.collect();
if let Some(trns_data) = trns_data {
for (color, trns) in palette.iter_mut().zip(trns_data) {
color.a = trns;
}
}
Ok((Some(palette), None))
} else {
Ok((None, trns_data))
}
}
/// Format the `PngData` struct into a valid PNG bytestream
pub fn output(&self) -> Vec<u8> {
// PNG header
@ -198,40 +161,49 @@ impl PngData {
{
write_png_block(key, header, &mut output);
}
// Palette
if let Some(ref palette) = self.raw.palette {
let mut palette_data = Vec::with_capacity(palette.len() * 3);
let mut max_palette_size = 1 << (self.raw.ihdr.bit_depth.as_u8() as usize);
// Ensure bKGD color doesn't get truncated from palette
if let Some(&idx) = self.raw.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
max_palette_size = max_palette_size.max(idx as usize + 1);
// Palette and transparency
match &self.raw.ihdr.color_type {
ColorType::Indexed { palette } => {
let mut palette_data = Vec::with_capacity(palette.len() * 3);
let mut max_palette_size = 1 << (self.raw.ihdr.bit_depth.as_u8() as usize);
// Ensure bKGD color doesn't get truncated from palette
if let Some(&idx) = self.raw.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
max_palette_size = max_palette_size.max(idx as usize + 1);
}
for px in palette.iter().take(max_palette_size) {
palette_data.extend_from_slice(px.rgb().as_slice());
}
write_png_block(b"PLTE", &palette_data, &mut output);
let num_transparent = palette.iter().take(max_palette_size).enumerate().fold(
0,
|prev, (index, px)| {
if px.a == 255 {
prev
} else {
index + 1
}
},
);
if num_transparent > 0 {
let trns_data: Vec<_> =
palette[0..num_transparent].iter().map(|px| px.a).collect();
write_png_block(b"tRNS", &trns_data, &mut output);
}
}
for px in palette.iter().take(max_palette_size) {
palette_data.extend_from_slice(px.rgb().as_slice());
ColorType::Grayscale {
transparent: Some(trns),
} => {
// Transparency pixel - 2 byte u16
write_png_block(b"tRNS", &trns.to_be_bytes(), &mut output);
}
write_png_block(b"PLTE", &palette_data, &mut output);
let num_transparent =
palette
.iter()
.take(max_palette_size)
.enumerate()
.fold(
0,
|prev, (index, px)| {
if px.a == 255 {
prev
} else {
index + 1
}
},
);
if num_transparent > 0 {
let trns_data: Vec<_> = palette[0..num_transparent].iter().map(|px| px.a).collect();
ColorType::RGB {
transparent: Some(trns),
} => {
// Transparency pixel - 6 byte RGB16
let trns_data: Vec<_> = trns.iter().flat_map(|c| c.to_be_bytes()).collect();
write_png_block(b"tRNS", &trns_data, &mut output);
}
} else if let Some(ref transparency_pixel) = self.raw.transparency_pixel {
// Transparency pixel
write_png_block(b"tRNS", transparency_pixel, &mut output);
_ => {}
}
// Special ancillary headers that need to come after PLTE but before IDAT
for (key, header) in self

View file

@ -1,3 +1,5 @@
use rgb::RGB16;
use crate::colors::{BitDepth, ColorType};
use crate::headers::IhdrData;
use crate::png::PngImage;
@ -26,20 +28,19 @@ pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
Some(PngImage {
data: reduced,
ihdr: png.ihdr,
palette: png.palette.clone(),
transparency_pixel: png.transparency_pixel.clone(),
ihdr: png.ihdr.clone(),
aux_headers: png.aux_headers.clone(),
})
}
#[must_use]
pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
let target_color_type = match png.ihdr.color_type {
ColorType::GrayscaleAlpha => ColorType::Grayscale,
ColorType::RGBA => ColorType::RGB,
_ => return None,
};
if !matches!(
png.ihdr.color_type,
ColorType::GrayscaleAlpha | ColorType::RGBA
) {
return None;
}
let byte_depth = (png.ihdr.bit_depth.as_u8() >> 3) as usize;
let channels = png.channels_per_pixel() as usize;
let bpp = channels * byte_depth;
@ -66,13 +67,7 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
let transparency_pixel = if has_transparency {
// If no unused color was found we will have to fail here
// Otherwise, proceed to construct the tRNS chunk
let unused_color = used_colors.iter().position(|b| !*b)? as u8;
Some(match png.ihdr.bit_depth {
BitDepth::Sixteen => vec![unused_color; colored_bytes],
// 8-bit is still stored as 16-bit, with the high byte set to 0
_ => [0, unused_color].repeat(colored_bytes),
})
Some(used_colors.iter().position(|b| !*b)? as u8)
} else {
None
};
@ -80,13 +75,25 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
let mut raw_data = Vec::with_capacity(png.data.len());
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]);
Some(trns) if pixel.iter().skip(colored_bytes).all(|b| *b == 0) => {
raw_data.resize(raw_data.len() + colored_bytes, trns);
}
_ => raw_data.extend_from_slice(&pixel[0..colored_bytes]),
};
}
// Construct the color type with appropriate transparency data
let transparent = transparency_pixel.map(|trns| match png.ihdr.bit_depth {
BitDepth::Sixteen => (trns as u16) << 8 | trns as u16,
_ => trns as u16,
});
let target_color_type = match png.ihdr.color_type {
ColorType::GrayscaleAlpha => ColorType::Grayscale { transparent },
_ => ColorType::RGB {
transparent: transparent.map(|t| RGB16::new(t, t, t)),
},
};
let mut aux_headers = png.aux_headers.clone();
// sBIT contains information about alpha channel's original depth,
// and alpha has just been removed
@ -102,7 +109,5 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
..png.ihdr
},
aux_headers,
transparency_pixel,
palette: None,
})
}

View file

@ -2,16 +2,16 @@ use crate::colors::{BitDepth, ColorType};
use crate::headers::IhdrData;
use crate::png::PngImage;
/// Attempt to reduce the bit depth of the image
/// Returns true if the bit depth was reduced, false otherwise
/// Attempt to reduce the bit depth of the image, returning the reduced image if successful
#[must_use]
pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage> {
if png.ihdr.bit_depth != BitDepth::Sixteen {
if png.ihdr.color_type == ColorType::Indexed || png.ihdr.color_type == ColorType::Grayscale
{
return reduce_bit_depth_8_or_less(png, minimum_bits);
}
return None;
return match png.ihdr.color_type {
ColorType::Indexed { .. } | ColorType::Grayscale { .. } => {
reduce_bit_depth_8_or_less(png, minimum_bits)
}
_ => None,
};
}
// Reduce from 16 to 8 bits per channel per pixel
@ -23,11 +23,10 @@ pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage>
Some(PngImage {
data: png.data.iter().step_by(2).cloned().collect(),
ihdr: IhdrData {
color_type: png.ihdr.color_type.clone(),
bit_depth: BitDepth::Eight,
..png.ihdr
},
palette: None,
transparency_pixel: png.transparency_pixel.clone(),
aux_headers: png.aux_headers.clone(),
})
}
@ -42,7 +41,7 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
// Calculate the current number of pixels per byte
let ppb = 8 / bit_depth;
if png.ihdr.color_type == ColorType::Indexed {
if let ColorType::Indexed { .. } = png.ihdr.color_type {
for line in png.scan_lines(false) {
let line_max = line
.data
@ -129,12 +128,11 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
}
// If the image is grayscale we also need to reduce the transparency pixel
let mut transparency_pixel = png
.transparency_pixel
.clone()
.filter(|t| png.ihdr.color_type == ColorType::Grayscale && t.len() >= 2);
if let Some(trans) = transparency_pixel {
let reduced_trans = trans[1] >> (bit_depth - minimum_bits);
let color_type = if let ColorType::Grayscale {
transparent: Some(trans),
} = png.ihdr.color_type
{
let reduced_trans = (trans & 0xFF) >> (bit_depth - minimum_bits);
// Verify the reduction is valid by restoring back to original bit depth
let mut check = reduced_trans;
let mut bits = minimum_bits;
@ -142,22 +140,25 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
check = check << bits | check;
bits <<= 1;
}
if trans[0] == 0 && trans[1] == check {
transparency_pixel = Some(vec![0, reduced_trans]);
} else {
// The transparency doesn't fit the new bit depth and is therefore unused - set it to None
transparency_pixel = None;
// If the transparency doesn't fit the new bit depth it is therefore unused - set it to None
ColorType::Grayscale {
transparent: if trans == check {
Some(reduced_trans)
} else {
None
},
}
}
} else {
png.ihdr.color_type.clone()
};
Some(PngImage {
data: reduced,
ihdr: IhdrData {
color_type,
bit_depth: BitDepth::from_u8(minimum_bits as u8),
..png.ihdr
},
aux_headers: png.aux_headers.clone(),
palette: png.palette.clone(),
transparency_pixel,
})
}

View file

@ -2,7 +2,7 @@ use crate::colors::{BitDepth, ColorType};
use crate::headers::IhdrData;
use crate::png::PngImage;
use indexmap::IndexMap;
use rgb::{FromSlice, RGB8, RGBA, RGBA8};
use rgb::{ComponentMap, FromSlice, RGBA, RGBA8};
use rustc_hash::FxHasher;
use std::hash::{BuildHasherDefault, Hash};
@ -41,12 +41,9 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
let mut raw_data = Vec::with_capacity(png.data.len());
let mut palette = FxIndexMap::default();
palette.reserve(257);
let transparency_pixel = png
.transparency_pixel
.as_ref()
.filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6)
.map(|t| RGB8::new(t[1], t[3], t[5]));
let ok = if png.ihdr.color_type == ColorType::RGB {
let ok = if let ColorType::RGB { transparent } = png.ihdr.color_type {
// Convert the RGB16 transparency to RGB8
let transparency_pixel = transparent.map(|t| t.map(|c| c as u8));
reduce_scanline_to_palette(
png.data.as_rgb().iter().cloned().map(|px| {
px.alpha(if Some(px) != transparency_pixel {
@ -132,12 +129,12 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
Some(PngImage {
data: raw_data,
ihdr: IhdrData {
color_type: ColorType::Indexed,
color_type: ColorType::Indexed {
palette: palette_vec,
},
..png.ihdr
},
aux_headers,
transparency_pixel: None,
palette: Some(palette_vec),
})
}
@ -158,16 +155,6 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
reduced.extend_from_slice(&pixel[last_color..]);
}
let transparency_pixel = if let Some(ref trns) = png.transparency_pixel {
if trns.len() != 6 || trns[0..2] != trns[2..4] || trns[2..4] != trns[4..6] {
None
} else {
Some(trns[0..2].to_owned())
}
} else {
png.transparency_pixel.clone()
};
let mut aux_headers = png.aux_headers.clone();
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
if let Some(&byte) = sbit_header.first() {
@ -180,17 +167,22 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
}
}
let color_type = match png.ihdr.color_type {
ColorType::RGB { transparent } => ColorType::Grayscale {
// Copy the transparent component if it is also gray
transparent: transparent
.filter(|t| t.r == t.g && t.g == t.b)
.map(|t| t.r),
},
_ => ColorType::GrayscaleAlpha,
};
Some(PngImage {
data: reduced,
ihdr: IhdrData {
color_type: match png.ihdr.color_type {
ColorType::RGBA => ColorType::GrayscaleAlpha,
_ => ColorType::Grayscale,
},
color_type,
..png.ihdr
},
aux_headers,
palette: None,
transparency_pixel,
})
}

View file

@ -18,10 +18,10 @@ pub(crate) use crate::bit_depth::reduce_bit_depth;
/// Attempt to reduce the number of colors in the palette
/// Returns `None` if palette hasn't changed
pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
if png.ihdr.color_type != ColorType::Indexed {
let ColorType::Indexed { palette } = &png.ihdr.color_type else {
// Can't reduce if there is no palette
return None;
}
};
if png.ihdr.bit_depth == BitDepth::One {
// Gains from 1-bit images will be at most 1 byte
// Not worth the CPU time
@ -31,8 +31,6 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
let mut palette_map = [None; 256];
let mut used = [false; 256];
{
let palette = png.palette.as_ref()?;
// Find palette entries that are never used
match png.ihdr.bit_depth {
BitDepth::Eight => {
@ -109,11 +107,15 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
}
}
do_palette_reduction(png, &palette_map)
do_palette_reduction(png, palette, &palette_map)
}
#[must_use]
fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Option<PngImage> {
fn do_palette_reduction(
png: &PngImage,
palette: &[RGBA8],
palette_map: &[Option<u8>; 256],
) -> Option<PngImage> {
let byte_map = palette_map_to_byte_map(png, palette_map)?;
// Reassign data bytes to new indices
@ -131,12 +133,12 @@ fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Opti
Some(PngImage {
ihdr: IhdrData {
color_type: ColorType::Indexed,
color_type: ColorType::Indexed {
palette: reordered_palette(palette, palette_map),
},
..png.ihdr
},
data: raw_data,
transparency_pixel: None,
palette: Some(reordered_palette(png.palette.as_ref()?, palette_map)),
aux_headers,
})
}
@ -199,10 +201,15 @@ pub fn reduce_color_type(
// Go down one step at a time
// Maybe not the most efficient, but it's safe
if grayscale_reduction && matches!(reduced.ihdr.color_type, ColorType::RGBA | ColorType::RGB) {
if grayscale_reduction
&& matches!(
reduced.ihdr.color_type,
ColorType::RGBA | ColorType::RGB { .. }
)
{
if let Some(r) = reduce_rgb_to_grayscale(&reduced) {
reduced = Cow::Owned(r);
should_reduce_bit_depth = reduced.ihdr.color_type == ColorType::Grayscale;
should_reduce_bit_depth = reduced.ihdr.color_type != ColorType::GrayscaleAlpha;
}
}
@ -216,7 +223,7 @@ pub fn reduce_color_type(
if matches!(
reduced.ihdr.color_type,
ColorType::RGBA | ColorType::RGB | ColorType::GrayscaleAlpha
ColorType::RGBA | ColorType::RGB { .. } | ColorType::GrayscaleAlpha
) {
if let Some(r) = reduce_to_palette(&reduced) {
reduced = Cow::Owned(r);