Refactor ColorType
Move transparency and palette data into the ColorType
This commit is contained in:
parent
36af4198ed
commit
91723507b4
9 changed files with 193 additions and 181 deletions
|
|
@ -1,14 +1,15 @@
|
||||||
|
use rgb::{RGB16, RGBA8};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
/// The color type used to represent this image
|
/// The color type used to represent this image
|
||||||
pub enum ColorType {
|
pub enum ColorType {
|
||||||
/// Grayscale, with one color channel
|
/// Grayscale, with one color channel
|
||||||
Grayscale,
|
Grayscale { transparent: Option<u16> },
|
||||||
/// RGB, with three color channels
|
/// 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, 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
|
/// Grayscale + Alpha, with two color channels
|
||||||
GrayscaleAlpha,
|
GrayscaleAlpha,
|
||||||
/// RGBA, with four color channels
|
/// RGBA, with four color channels
|
||||||
|
|
@ -22,9 +23,9 @@ impl fmt::Display for ColorType {
|
||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
match *self {
|
match *self {
|
||||||
ColorType::Grayscale => "Grayscale",
|
ColorType::Grayscale { .. } => "Grayscale",
|
||||||
ColorType::RGB => "RGB",
|
ColorType::RGB { .. } => "RGB",
|
||||||
ColorType::Indexed => "Indexed",
|
ColorType::Indexed { .. } => "Indexed",
|
||||||
ColorType::GrayscaleAlpha => "Grayscale + Alpha",
|
ColorType::GrayscaleAlpha => "Grayscale + Alpha",
|
||||||
ColorType::RGBA => "RGB + Alpha",
|
ColorType::RGBA => "RGB + Alpha",
|
||||||
}
|
}
|
||||||
|
|
@ -35,22 +36,22 @@ impl fmt::Display for ColorType {
|
||||||
impl ColorType {
|
impl ColorType {
|
||||||
/// Get the code used by the PNG specification to denote this color type
|
/// Get the code used by the PNG specification to denote this color type
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn png_header_code(self) -> u8 {
|
pub fn png_header_code(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
ColorType::Grayscale => 0,
|
ColorType::Grayscale { .. } => 0,
|
||||||
ColorType::RGB => 2,
|
ColorType::RGB { .. } => 2,
|
||||||
ColorType::Indexed => 3,
|
ColorType::Indexed { .. } => 3,
|
||||||
ColorType::GrayscaleAlpha => 4,
|
ColorType::GrayscaleAlpha => 4,
|
||||||
ColorType::RGBA => 6,
|
ColorType::RGBA => 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn channels_per_pixel(self) -> u8 {
|
pub fn channels_per_pixel(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
ColorType::Grayscale | ColorType::Indexed => 1,
|
ColorType::Grayscale { .. } | ColorType::Indexed { .. } => 1,
|
||||||
ColorType::GrayscaleAlpha => 2,
|
ColorType::GrayscaleAlpha => 2,
|
||||||
ColorType::RGB => 3,
|
ColorType::RGB { .. } => 3,
|
||||||
ColorType::RGBA => 4,
|
ColorType::RGBA => 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ use crate::error::PngError;
|
||||||
use crate::interlace::Interlacing;
|
use crate::interlace::Interlacing;
|
||||||
use crate::PngResult;
|
use crate::PngResult;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
use rgb::{RGB16, RGBA8};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
/// Headers from the IHDR chunk of the image
|
/// Headers from the IHDR chunk of the image
|
||||||
pub struct IhdrData {
|
pub struct IhdrData {
|
||||||
/// The width of the image in pixels
|
/// The width of the image in pixels
|
||||||
|
|
@ -143,15 +144,31 @@ pub fn parse_next_header<'a>(
|
||||||
Ok(Some(RawHeader { name, data }))
|
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
|
// This eliminates bounds checks for the rest of the function
|
||||||
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
|
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
|
||||||
let mut rdr = Cursor::new(&byte_data[0..8]);
|
let mut rdr = Cursor::new(&byte_data[0..8]);
|
||||||
Ok(IhdrData {
|
Ok(IhdrData {
|
||||||
color_type: match byte_data[9] {
|
color_type: match byte_data[9] {
|
||||||
0 => ColorType::Grayscale,
|
0 => ColorType::Grayscale {
|
||||||
2 => ColorType::RGB,
|
transparent: trns_data
|
||||||
3 => ColorType::Indexed,
|
.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,
|
4 => ColorType::GrayscaleAlpha,
|
||||||
6 => ColorType::RGBA,
|
6 => ColorType::RGBA,
|
||||||
_ => return Err(PngError::new("Unexpected color type in header")),
|
_ => 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]
|
#[inline]
|
||||||
fn read_be_u32<T: AsRef<[u8]>>(rdr: &mut Cursor<T>) -> Result<u32, io::Error> {
|
fn read_be_u32<T: AsRef<[u8]>>(rdr: &mut Cursor<T>) -> Result<u32, io::Error> {
|
||||||
let mut int_buf = [0; 4];
|
let mut int_buf = [0; 4];
|
||||||
|
|
|
||||||
|
|
@ -87,12 +87,11 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
|
||||||
PngImage {
|
PngImage {
|
||||||
data: output,
|
data: output,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
|
color_type: png.ihdr.color_type.clone(),
|
||||||
interlaced: Interlacing::Adam7,
|
interlaced: Interlacing::Adam7,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers: png.aux_headers.clone(),
|
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),
|
_ => deinterlace_bits(png),
|
||||||
},
|
},
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
|
color_type: png.ihdr.color_type.clone(),
|
||||||
interlaced: Interlacing::None,
|
interlaced: Interlacing::None,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers: png.aux_headers.clone(),
|
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
|
// Calculate the next line and move to next pass if necessary
|
||||||
current_y += pass_constants.y_step as usize;
|
current_y += pass_constants.y_step as usize;
|
||||||
if current_y >= png.ihdr.height 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;
|
break;
|
||||||
}
|
}
|
||||||
pass_constants = interlaced_constants(current_pass);
|
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
|
// Calculate the next line and move to next pass if necessary
|
||||||
current_y += pass_constants.y_step as usize;
|
current_y += pass_constants.y_step as usize;
|
||||||
if current_y >= png.ihdr.height 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;
|
break;
|
||||||
}
|
}
|
||||||
pass_constants = interlaced_constants(current_pass);
|
pass_constants = interlaced_constants(current_pass);
|
||||||
|
|
@ -190,7 +188,7 @@ fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
|
||||||
lines.concat()
|
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 {
|
if *current_pass == 7 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ extern crate rayon;
|
||||||
mod rayon;
|
mod rayon;
|
||||||
|
|
||||||
use crate::atomicmin::AtomicMin;
|
use crate::atomicmin::AtomicMin;
|
||||||
use crate::colors::BitDepth;
|
use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::deflate::{crc32, inflate};
|
use crate::deflate::{crc32, inflate};
|
||||||
use crate::evaluate::Evaluator;
|
use crate::evaluate::Evaluator;
|
||||||
use crate::png::PngData;
|
use crate::png::PngData;
|
||||||
|
|
@ -814,7 +814,7 @@ impl Deadline {
|
||||||
|
|
||||||
/// Display the format of the image data
|
/// Display the format of the image data
|
||||||
fn report_format(prefix: &str, png: &PngImage) {
|
fn report_format(prefix: &str, png: &PngImage) {
|
||||||
if let Some(ref palette) = png.palette {
|
if let ColorType::Indexed { palette } = &png.ihdr.color_type {
|
||||||
debug!(
|
debug!(
|
||||||
"{}{} bits/pixel, {} colors in palette ({})",
|
"{}{} bits/pixel, {} colors in palette ({})",
|
||||||
prefix,
|
prefix,
|
||||||
|
|
|
||||||
116
src/png/mod.rs
116
src/png/mod.rs
|
|
@ -8,7 +8,6 @@ use bitvec::bitarr;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use libdeflater::{CompressionLvl, Compressor};
|
use libdeflater::{CompressionLvl, Compressor};
|
||||||
use rgb::ComponentSlice;
|
use rgb::ComponentSlice;
|
||||||
use rgb::RGBA8;
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, Read, Write};
|
use std::io::{BufReader, Read, Write};
|
||||||
|
|
@ -31,11 +30,6 @@ pub struct PngImage {
|
||||||
pub ihdr: IhdrData,
|
pub ihdr: IhdrData,
|
||||||
/// The uncompressed, unfiltered data from the IDAT chunk
|
/// The uncompressed, unfiltered data from the IDAT chunk
|
||||||
pub data: Vec<u8>,
|
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
|
/// All non-critical headers from the PNG are stored here
|
||||||
pub aux_headers: IndexMap<[u8; 4], Vec<u8>>,
|
pub aux_headers: IndexMap<[u8; 4], Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
@ -51,8 +45,6 @@ pub struct PngData {
|
||||||
pub filtered: Vec<u8>,
|
pub filtered: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaletteWithTrns = (Option<Vec<RGBA8>>, Option<Vec<u8>>);
|
|
||||||
|
|
||||||
impl PngData {
|
impl PngData {
|
||||||
/// Create a new `PngData` struct by opening a file
|
/// Create a new `PngData` struct by opening a file
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -116,7 +108,11 @@ impl PngData {
|
||||||
Some(ihdr) => ihdr,
|
Some(ihdr) => ihdr,
|
||||||
None => return Err(PngError::ChunkMissing("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())?;
|
let raw_data = deflate::inflate(idat_headers.as_ref(), ihdr_header.raw_data_size())?;
|
||||||
|
|
||||||
// Reject files with incorrect width/height or truncated data
|
// Reject files with incorrect width/height or truncated data
|
||||||
|
|
@ -124,17 +120,9 @@ impl PngData {
|
||||||
return Err(PngError::TruncatedData);
|
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 {
|
let mut raw = PngImage {
|
||||||
ihdr: ihdr_header,
|
ihdr: ihdr_header,
|
||||||
data: raw_data,
|
data: raw_data,
|
||||||
palette,
|
|
||||||
transparency_pixel,
|
|
||||||
aux_headers,
|
aux_headers,
|
||||||
};
|
};
|
||||||
let unfiltered = raw.unfilter_image()?;
|
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
|
/// Format the `PngData` struct into a valid PNG bytestream
|
||||||
pub fn output(&self) -> Vec<u8> {
|
pub fn output(&self) -> Vec<u8> {
|
||||||
// PNG header
|
// PNG header
|
||||||
|
|
@ -198,40 +161,49 @@ impl PngData {
|
||||||
{
|
{
|
||||||
write_png_block(key, header, &mut output);
|
write_png_block(key, header, &mut output);
|
||||||
}
|
}
|
||||||
// Palette
|
// Palette and transparency
|
||||||
if let Some(ref palette) = self.raw.palette {
|
match &self.raw.ihdr.color_type {
|
||||||
let mut palette_data = Vec::with_capacity(palette.len() * 3);
|
ColorType::Indexed { palette } => {
|
||||||
let mut max_palette_size = 1 << (self.raw.ihdr.bit_depth.as_u8() as usize);
|
let mut palette_data = Vec::with_capacity(palette.len() * 3);
|
||||||
// Ensure bKGD color doesn't get truncated from palette
|
let mut max_palette_size = 1 << (self.raw.ihdr.bit_depth.as_u8() as usize);
|
||||||
if let Some(&idx) = self.raw.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
|
// Ensure bKGD color doesn't get truncated from palette
|
||||||
max_palette_size = max_palette_size.max(idx as usize + 1);
|
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) {
|
ColorType::Grayscale {
|
||||||
palette_data.extend_from_slice(px.rgb().as_slice());
|
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);
|
ColorType::RGB {
|
||||||
let num_transparent =
|
transparent: Some(trns),
|
||||||
palette
|
} => {
|
||||||
.iter()
|
// Transparency pixel - 6 byte RGB16
|
||||||
.take(max_palette_size)
|
let trns_data: Vec<_> = trns.iter().flat_map(|c| c.to_be_bytes()).collect();
|
||||||
.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);
|
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
|
// Special ancillary headers that need to come after PLTE but before IDAT
|
||||||
for (key, header) in self
|
for (key, header) in self
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use rgb::RGB16;
|
||||||
|
|
||||||
use crate::colors::{BitDepth, ColorType};
|
use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
|
|
@ -26,20 +28,19 @@ pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: reduced,
|
data: reduced,
|
||||||
ihdr: png.ihdr,
|
ihdr: png.ihdr.clone(),
|
||||||
palette: png.palette.clone(),
|
|
||||||
transparency_pixel: png.transparency_pixel.clone(),
|
|
||||||
aux_headers: png.aux_headers.clone(),
|
aux_headers: png.aux_headers.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
|
pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
|
||||||
let target_color_type = match png.ihdr.color_type {
|
if !matches!(
|
||||||
ColorType::GrayscaleAlpha => ColorType::Grayscale,
|
png.ihdr.color_type,
|
||||||
ColorType::RGBA => ColorType::RGB,
|
ColorType::GrayscaleAlpha | ColorType::RGBA
|
||||||
_ => return None,
|
) {
|
||||||
};
|
return None;
|
||||||
|
}
|
||||||
let byte_depth = (png.ihdr.bit_depth.as_u8() >> 3) as usize;
|
let byte_depth = (png.ihdr.bit_depth.as_u8() >> 3) as usize;
|
||||||
let channels = png.channels_per_pixel() as usize;
|
let channels = png.channels_per_pixel() as usize;
|
||||||
let bpp = channels * byte_depth;
|
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 {
|
let transparency_pixel = if has_transparency {
|
||||||
// If no unused color was found we will have to fail here
|
// If no unused color was found we will have to fail here
|
||||||
// Otherwise, proceed to construct the tRNS chunk
|
Some(used_colors.iter().position(|b| !*b)? as u8)
|
||||||
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),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
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());
|
let mut raw_data = Vec::with_capacity(png.data.len());
|
||||||
for pixel in png.data.chunks(bpp) {
|
for pixel in png.data.chunks(bpp) {
|
||||||
match transparency_pixel {
|
match transparency_pixel {
|
||||||
Some(ref trns) if pixel.iter().skip(colored_bytes).all(|b| *b == 0) => {
|
Some(trns) if pixel.iter().skip(colored_bytes).all(|b| *b == 0) => {
|
||||||
raw_data.resize(raw_data.len() + colored_bytes, trns[1]);
|
raw_data.resize(raw_data.len() + colored_bytes, trns);
|
||||||
}
|
}
|
||||||
_ => raw_data.extend_from_slice(&pixel[0..colored_bytes]),
|
_ => 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();
|
let mut aux_headers = png.aux_headers.clone();
|
||||||
// sBIT contains information about alpha channel's original depth,
|
// sBIT contains information about alpha channel's original depth,
|
||||||
// and alpha has just been removed
|
// and alpha has just been removed
|
||||||
|
|
@ -102,7 +109,5 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers,
|
aux_headers,
|
||||||
transparency_pixel,
|
|
||||||
palette: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,16 @@ use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
|
|
||||||
/// Attempt to reduce the bit depth of the image
|
/// Attempt to reduce the bit depth of the image, returning the reduced image if successful
|
||||||
/// Returns true if the bit depth was reduced, false otherwise
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage> {
|
pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage> {
|
||||||
if png.ihdr.bit_depth != BitDepth::Sixteen {
|
if png.ihdr.bit_depth != BitDepth::Sixteen {
|
||||||
if png.ihdr.color_type == ColorType::Indexed || png.ihdr.color_type == ColorType::Grayscale
|
return match png.ihdr.color_type {
|
||||||
{
|
ColorType::Indexed { .. } | ColorType::Grayscale { .. } => {
|
||||||
return reduce_bit_depth_8_or_less(png, minimum_bits);
|
reduce_bit_depth_8_or_less(png, minimum_bits)
|
||||||
}
|
}
|
||||||
return None;
|
_ => None,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reduce from 16 to 8 bits per channel per pixel
|
// 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 {
|
Some(PngImage {
|
||||||
data: png.data.iter().step_by(2).cloned().collect(),
|
data: png.data.iter().step_by(2).cloned().collect(),
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
|
color_type: png.ihdr.color_type.clone(),
|
||||||
bit_depth: BitDepth::Eight,
|
bit_depth: BitDepth::Eight,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
palette: None,
|
|
||||||
transparency_pixel: png.transparency_pixel.clone(),
|
|
||||||
aux_headers: png.aux_headers.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
|
// Calculate the current number of pixels per byte
|
||||||
let ppb = 8 / bit_depth;
|
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) {
|
for line in png.scan_lines(false) {
|
||||||
let line_max = line
|
let line_max = line
|
||||||
.data
|
.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
|
// If the image is grayscale we also need to reduce the transparency pixel
|
||||||
let mut transparency_pixel = png
|
let color_type = if let ColorType::Grayscale {
|
||||||
.transparency_pixel
|
transparent: Some(trans),
|
||||||
.clone()
|
} = png.ihdr.color_type
|
||||||
.filter(|t| png.ihdr.color_type == ColorType::Grayscale && t.len() >= 2);
|
{
|
||||||
if let Some(trans) = transparency_pixel {
|
let reduced_trans = (trans & 0xFF) >> (bit_depth - minimum_bits);
|
||||||
let reduced_trans = trans[1] >> (bit_depth - minimum_bits);
|
|
||||||
// Verify the reduction is valid by restoring back to original bit depth
|
// Verify the reduction is valid by restoring back to original bit depth
|
||||||
let mut check = reduced_trans;
|
let mut check = reduced_trans;
|
||||||
let mut bits = minimum_bits;
|
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;
|
check = check << bits | check;
|
||||||
bits <<= 1;
|
bits <<= 1;
|
||||||
}
|
}
|
||||||
if trans[0] == 0 && trans[1] == check {
|
// If the transparency doesn't fit the new bit depth it is therefore unused - set it to None
|
||||||
transparency_pixel = Some(vec![0, reduced_trans]);
|
ColorType::Grayscale {
|
||||||
} else {
|
transparent: if trans == check {
|
||||||
// The transparency doesn't fit the new bit depth and is therefore unused - set it to None
|
Some(reduced_trans)
|
||||||
transparency_pixel = None;
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
png.ihdr.color_type.clone()
|
||||||
|
};
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: reduced,
|
data: reduced,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
|
color_type,
|
||||||
bit_depth: BitDepth::from_u8(minimum_bits as u8),
|
bit_depth: BitDepth::from_u8(minimum_bits as u8),
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers: png.aux_headers.clone(),
|
aux_headers: png.aux_headers.clone(),
|
||||||
palette: png.palette.clone(),
|
|
||||||
transparency_pixel,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use rgb::{FromSlice, RGB8, RGBA, RGBA8};
|
use rgb::{ComponentMap, FromSlice, RGBA, RGBA8};
|
||||||
use rustc_hash::FxHasher;
|
use rustc_hash::FxHasher;
|
||||||
use std::hash::{BuildHasherDefault, Hash};
|
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 raw_data = Vec::with_capacity(png.data.len());
|
||||||
let mut palette = FxIndexMap::default();
|
let mut palette = FxIndexMap::default();
|
||||||
palette.reserve(257);
|
palette.reserve(257);
|
||||||
let transparency_pixel = png
|
let ok = if let ColorType::RGB { transparent } = png.ihdr.color_type {
|
||||||
.transparency_pixel
|
// Convert the RGB16 transparency to RGB8
|
||||||
.as_ref()
|
let transparency_pixel = transparent.map(|t| t.map(|c| c as u8));
|
||||||
.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 {
|
|
||||||
reduce_scanline_to_palette(
|
reduce_scanline_to_palette(
|
||||||
png.data.as_rgb().iter().cloned().map(|px| {
|
png.data.as_rgb().iter().cloned().map(|px| {
|
||||||
px.alpha(if Some(px) != transparency_pixel {
|
px.alpha(if Some(px) != transparency_pixel {
|
||||||
|
|
@ -132,12 +129,12 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: raw_data,
|
data: raw_data,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
color_type: ColorType::Indexed,
|
color_type: ColorType::Indexed {
|
||||||
|
palette: palette_vec,
|
||||||
|
},
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers,
|
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..]);
|
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();
|
let mut aux_headers = png.aux_headers.clone();
|
||||||
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
|
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
|
||||||
if let Some(&byte) = sbit_header.first() {
|
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 {
|
Some(PngImage {
|
||||||
data: reduced,
|
data: reduced,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
color_type: match png.ihdr.color_type {
|
color_type,
|
||||||
ColorType::RGBA => ColorType::GrayscaleAlpha,
|
|
||||||
_ => ColorType::Grayscale,
|
|
||||||
},
|
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers,
|
aux_headers,
|
||||||
palette: None,
|
|
||||||
transparency_pixel,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ pub(crate) use crate::bit_depth::reduce_bit_depth;
|
||||||
/// Attempt to reduce the number of colors in the palette
|
/// Attempt to reduce the number of colors in the palette
|
||||||
/// Returns `None` if palette hasn't changed
|
/// Returns `None` if palette hasn't changed
|
||||||
pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
|
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
|
// Can't reduce if there is no palette
|
||||||
return None;
|
return None;
|
||||||
}
|
};
|
||||||
if png.ihdr.bit_depth == BitDepth::One {
|
if png.ihdr.bit_depth == BitDepth::One {
|
||||||
// Gains from 1-bit images will be at most 1 byte
|
// Gains from 1-bit images will be at most 1 byte
|
||||||
// Not worth the CPU time
|
// 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 palette_map = [None; 256];
|
||||||
let mut used = [false; 256];
|
let mut used = [false; 256];
|
||||||
{
|
{
|
||||||
let palette = png.palette.as_ref()?;
|
|
||||||
|
|
||||||
// Find palette entries that are never used
|
// Find palette entries that are never used
|
||||||
match png.ihdr.bit_depth {
|
match png.ihdr.bit_depth {
|
||||||
BitDepth::Eight => {
|
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]
|
#[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)?;
|
let byte_map = palette_map_to_byte_map(png, palette_map)?;
|
||||||
|
|
||||||
// Reassign data bytes to new indices
|
// Reassign data bytes to new indices
|
||||||
|
|
@ -131,12 +133,12 @@ fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Opti
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
color_type: ColorType::Indexed,
|
color_type: ColorType::Indexed {
|
||||||
|
palette: reordered_palette(palette, palette_map),
|
||||||
|
},
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
data: raw_data,
|
data: raw_data,
|
||||||
transparency_pixel: None,
|
|
||||||
palette: Some(reordered_palette(png.palette.as_ref()?, palette_map)),
|
|
||||||
aux_headers,
|
aux_headers,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -199,10 +201,15 @@ pub fn reduce_color_type(
|
||||||
|
|
||||||
// Go down one step at a time
|
// Go down one step at a time
|
||||||
// Maybe not the most efficient, but it's safe
|
// 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) {
|
if let Some(r) = reduce_rgb_to_grayscale(&reduced) {
|
||||||
reduced = Cow::Owned(r);
|
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!(
|
if matches!(
|
||||||
reduced.ihdr.color_type,
|
reduced.ihdr.color_type,
|
||||||
ColorType::RGBA | ColorType::RGB | ColorType::GrayscaleAlpha
|
ColorType::RGBA | ColorType::RGB { .. } | ColorType::GrayscaleAlpha
|
||||||
) {
|
) {
|
||||||
if let Some(r) = reduce_to_palette(&reduced) {
|
if let Some(r) = reduce_to_palette(&reduced) {
|
||||||
reduced = Cow::Owned(r);
|
reduced = Cow::Owned(r);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue