Alpha reductions

This commit is contained in:
Kornel Lesiński 2018-12-04 16:49:01 +00:00 committed by Kornel Lesiński
parent e5e51cdaa8
commit 52a22e9e03
4 changed files with 50 additions and 46 deletions

View file

@ -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

View file

@ -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<Vec<u8>> {
pub fn reduced_alpha_channel(png: &PngData) -> Option<ReducedPng> {
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<Vec<u8>>
}
}
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,
})
}

View file

@ -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<ReducedPng> {
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
}
}

View file

@ -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<u8>,
pub palette: Option<Vec<RGBA8>>,
pub aux_headers: HashMap<[u8; 4], Option<Vec<u8>>>,
pub color_type: ColorType,
pub raw_data: Vec<u8>,
/// replace if Some
pub palette: Option<Vec<RGBA8>>,
/// replace if Some
pub transparency_pixel: Option<Vec<u8>>,
/// replace if Some, delete if None
pub aux_headers: HashMap<[u8; 4], Option<Vec<u8>>>,
}
/// 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<ReducedPng> {
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<u8>; 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,
})