From 52a22e9e039beed3351deb1b178cbc55fc1ebc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kornel=20Lesin=CC=81ski?= Date: Tue, 4 Dec 2018 16:49:01 +0000 Subject: [PATCH] Alpha reductions --- src/png/mod.rs | 24 +++++++++++++++--------- src/reduction/alpha.rs | 33 +++++++++++++++++++++++---------- src/reduction/color.rs | 23 +---------------------- src/reduction/mod.rs | 16 +++++++++++----- 4 files changed, 50 insertions(+), 46 deletions(-) diff --git a/src/png/mod.rs b/src/png/mod.rs index 6ae59eae..02da016c 100644 --- a/src/png/mod.rs +++ b/src/png/mod.rs @@ -13,6 +13,7 @@ use itertools::flatten; use rayon::prelude::*; use reduction::bit_depth::*; use reduction::color::*; +use reduction::alpha::*; use rgb::ComponentSlice; use rgb::RGBA8; use std::collections::{HashMap, HashSet}; @@ -377,7 +378,8 @@ impl PngData { if self.ihdr_data.color_type == ColorType::RGBA { if reduce_rgba_to_grayscale_alpha(self) { changed = true; - } else if reduce_rgba_to_rgb(self) { + } else if let Some(reduced) = reduced_alpha_channel(self) { + self.apply_reduction(reduced); changed = true; } else if let Some(reduced) = reduced_color_to_palette(self) { self.apply_reduction(reduced); @@ -386,11 +388,12 @@ impl PngData { } } - if self.ihdr_data.color_type == ColorType::GrayscaleAlpha - && reduce_grayscale_alpha_to_grayscale(self) - { - changed = true; - should_reduce_bit_depth = true; + if self.ihdr_data.color_type == ColorType::GrayscaleAlpha { + if let Some(reduced) = reduced_alpha_channel(self) { + self.apply_reduction(reduced); + changed = true; + should_reduce_bit_depth = true; + } } if self.ihdr_data.color_type == ColorType::RGB { @@ -413,12 +416,15 @@ impl PngData { changed } - pub(crate) fn apply_reduction(&mut self, ReducedPng {color_type, raw_data, palette, aux_headers}: ReducedPng) { + pub(crate) fn apply_reduction(&mut self, ReducedPng {color_type, raw_data, palette, transparency_pixel, aux_headers}: ReducedPng) { self.ihdr_data.color_type = color_type; self.raw_data = raw_data; - if let Some(palette) = palette { + if palette.is_some() { self.transparency_pixel = None; - self.palette = Some(palette); + self.palette = palette; + } + if transparency_pixel.is_some() { + self.transparency_pixel = transparency_pixel; } self.idat_data.clear(); // this field is out of date and needs to be replaced diff --git a/src/reduction/alpha.rs b/src/reduction/alpha.rs index 0380c697..68cb207f 100644 --- a/src/reduction/alpha.rs +++ b/src/reduction/alpha.rs @@ -1,7 +1,16 @@ +use reduction::ReducedPng; use png::PngData; +use colors::ColorType; +use std::collections::HashMap; -pub fn reduce_alpha_channel(png: &mut PngData, channels: u8) -> Option> { +pub fn reduced_alpha_channel(png: &PngData) -> Option { + let target_color_type = match png.ihdr_data.color_type { + ColorType::GrayscaleAlpha => ColorType::Grayscale, + ColorType::RGBA => ColorType::RGB, + _ => return None, + }; let byte_depth = png.ihdr_data.bit_depth.as_u8() >> 3; + let channels = png.channels_per_pixel(); let bpp = channels * byte_depth; let bpp_mask = bpp - 1; assert_eq!(0, bpp & bpp_mask); @@ -14,27 +23,31 @@ pub fn reduce_alpha_channel(png: &mut PngData, channels: u8) -> Option> } } - let mut reduced = Vec::with_capacity(png.raw_data.len()); + let mut raw_data = Vec::with_capacity(png.raw_data.len()); for line in png.scan_lines() { - reduced.push(line.filter); + raw_data.push(line.filter); for (i, &byte) in line.data.iter().enumerate() { if i as u8 & bpp_mask >= colored_bytes { continue; } else { - reduced.push(byte); + raw_data.push(byte); } } } + let mut aux_headers = HashMap::new(); // sBIT contains information about alpha channel's original depth, // and alpha has just been removed - if let Some(sbit_header) = png.aux_headers.get_mut(b"sBIT") { + if let Some(sbit_header) = png.aux_headers.get(b"sBIT") { // Some programs save the sBIT header as RGB even if the image is RGBA. - // Only remove the alpha channel if it's actually there. - if sbit_header.len() == 4 { - sbit_header.pop(); - } + aux_headers.insert(*b"sBIT", Some(sbit_header.iter().cloned().take(3).collect())); } - Some(reduced) + Some(ReducedPng { + raw_data, + color_type: target_color_type, + aux_headers, + transparency_pixel: None, + palette: None, + }) } diff --git a/src/reduction/color.rs b/src/reduction/color.rs index 4fc2d082..3654df6f 100644 --- a/src/reduction/color.rs +++ b/src/reduction/color.rs @@ -6,18 +6,6 @@ use rgb::{FromSlice, RGB8, RGBA8}; use std::collections::HashMap; use std::hash::Hash; -use super::alpha::reduce_alpha_channel; - -pub fn reduce_rgba_to_rgb(png: &mut PngData) -> bool { - if let Some(reduced) = reduce_alpha_channel(png, 4) { - png.raw_data = reduced; - png.ihdr_data.color_type = ColorType::RGB; - true - } else { - false - } -} - pub fn reduce_rgba_to_grayscale_alpha(png: &mut PngData) -> bool { let mut reduced = Vec::with_capacity(png.raw_data.len()); let byte_depth = png.ihdr_data.bit_depth.as_u8() >> 3; @@ -185,6 +173,7 @@ pub fn reduced_color_to_palette(png: &mut PngData) -> Option { color_type: ColorType::Indexed, aux_headers, raw_data, + transparency_pixel: None, palette: Some(palette_vec), }) } @@ -242,13 +231,3 @@ pub fn reduce_rgb_to_grayscale(png: &mut PngData) -> bool { png.ihdr_data.color_type = ColorType::Grayscale; true } - -pub fn reduce_grayscale_alpha_to_grayscale(png: &mut PngData) -> bool { - if let Some(reduced) = reduce_alpha_channel(png, 2) { - png.raw_data = reduced; - png.ihdr_data.color_type = ColorType::Grayscale; - true - } else { - false - } -} diff --git a/src/reduction/mod.rs b/src/reduction/mod.rs index 9064a810..508588c4 100644 --- a/src/reduction/mod.rs +++ b/src/reduction/mod.rs @@ -4,19 +4,24 @@ use std::collections::hash_map::Entry::*; use png::PngData; use rgb::RGBA8; -mod alpha; +pub mod alpha; pub mod bit_depth; pub mod color; +/// Fields to replace in PngData to apply the reduction pub struct ReducedPng { - pub raw_data: Vec, - pub palette: Option>, - pub aux_headers: HashMap<[u8; 4], Option>>, pub color_type: ColorType, + pub raw_data: Vec, + /// replace if Some + pub palette: Option>, + /// replace if Some + pub transparency_pixel: Option>, + /// replace if Some, delete if None + pub aux_headers: HashMap<[u8; 4], Option>>, } /// Attempt to reduce the number of colors in the palette -/// Returns true if the palette was reduced, false otherwise +/// Returns `None` if palette hasn't changed pub fn reduced_palette(png: &PngData) -> Option { if png.ihdr_data.color_type != ColorType::Indexed { // Can't reduce if there is no palette @@ -101,6 +106,7 @@ fn do_palette_reduction(png: &PngData, palette_map: &[Option; 256]) -> Optio Some(ReducedPng { color_type: ColorType::Indexed, raw_data, + transparency_pixel: None, palette: Some(reordered_palette(png.palette.as_ref()?, palette_map)), aux_headers, })