Alpha reductions
This commit is contained in:
parent
e5e51cdaa8
commit
52a22e9e03
4 changed files with 50 additions and 46 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue