From 91723507b4fa29d082d76869c8c5914431ad4bd0 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 20 Apr 2023 08:03:48 +1200 Subject: [PATCH] Refactor ColorType Move transparency and palette data into the ColorType --- src/colors.rs | 29 +++++----- src/headers.rs | 46 +++++++++++++-- src/interlace.rs | 12 ++-- src/lib.rs | 4 +- src/png/mod.rs | 116 ++++++++++++++----------------------- src/reduction/alpha.rs | 43 ++++++++------ src/reduction/bit_depth.rs | 49 ++++++++-------- src/reduction/color.rs | 44 ++++++-------- src/reduction/mod.rs | 31 ++++++---- 9 files changed, 193 insertions(+), 181 deletions(-) diff --git a/src/colors.rs b/src/colors.rs index e339092b..c422a8e5 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -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 }, /// RGB, with three color channels - RGB, + RGB { transparent: Option }, /// Indexed, with one byte per pixel representing one of up to 256 colors in the image - Indexed, + Indexed { palette: Vec }, /// 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, } } diff --git a/src/headers.rs b/src/headers.rs index 7fdeef1e..a116bb33 100644 --- a/src/headers.rs +++ b/src/headers.rs @@ -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 { +pub fn parse_ihdr_header( + byte_data: &[u8], + palette_data: Option>, + trns_data: Option>, +) -> PngResult { // 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 { }) } +/// Construct an RGBA palette from the raw palette and transparency data +fn palette_to_rgba( + palette_data: Option>, + trns_data: Option>, +) -> Result, 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>(rdr: &mut Cursor) -> Result { let mut int_buf = [0; 4]; diff --git a/src/interlace.rs b/src/interlace.rs index 8ac0fdc7..3446346a 100644 --- a/src/interlace.rs +++ b/src/interlace.rs @@ -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 { // 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 { // 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 { 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; } diff --git a/src/lib.rs b/src/lib.rs index 0f5b637f..fc6dda47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, diff --git a/src/png/mod.rs b/src/png/mod.rs index f3d712d0..fffe43e3 100644 --- a/src/png/mod.rs +++ b/src/png/mod.rs @@ -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, - /// The palette containing colors used in an Indexed image - /// Contains 3 bytes per color (R+G+B), up to 768 - pub palette: Option>, - /// The pixel value that should be rendered as transparent - pub transparency_pixel: Option>, /// All non-critical headers from the PNG are stored here pub aux_headers: IndexMap<[u8; 4], Vec>, } @@ -51,8 +45,6 @@ pub struct PngData { pub filtered: Vec, } -type PaletteWithTrns = (Option>, Option>); - 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>, - trns_data: Option>, - ) -> Result { - 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 { // 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 diff --git a/src/reduction/alpha.rs b/src/reduction/alpha.rs index cf8c22ad..7930c441 100644 --- a/src/reduction/alpha.rs +++ b/src/reduction/alpha.rs @@ -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 { 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 { - 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 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 { - 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 Option { 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 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, }) } diff --git a/src/reduction/color.rs b/src/reduction/color.rs index 5e7135f8..dfbcc665 100644 --- a/src/reduction/color.rs +++ b/src/reduction/color.rs @@ -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 { 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 { 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 { 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 { } } + 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, }) } diff --git a/src/reduction/mod.rs b/src/reduction/mod.rs index defd0eab..083212ca 100644 --- a/src/reduction/mod.rs +++ b/src/reduction/mod.rs @@ -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 { - 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 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 } } - do_palette_reduction(png, &palette_map) + do_palette_reduction(png, palette, &palette_map) } #[must_use] -fn do_palette_reduction(png: &PngImage, palette_map: &[Option; 256]) -> Option { +fn do_palette_reduction( + png: &PngImage, + palette: &[RGBA8], + palette_map: &[Option; 256], +) -> Option { 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; 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);