Make more use of helper functions

This commit is contained in:
Andrew 2023-04-21 11:55:14 +12:00 committed by Josh Holmer
parent 0669478181
commit 873f0fefbe
9 changed files with 71 additions and 74 deletions

View file

@ -55,6 +55,16 @@ impl ColorType {
ColorType::RGBA => 4,
}
}
#[inline]
pub fn is_rgb(&self) -> bool {
matches!(self, ColorType::RGB { .. } | ColorType::RGBA)
}
#[inline]
pub fn has_alpha(&self) -> bool {
matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]

View file

@ -31,8 +31,8 @@ impl IhdrData {
/// Bits per pixel
#[must_use]
#[inline]
pub fn bpp(&self) -> u8 {
self.bit_depth.as_u8() * self.color_type.channels_per_pixel()
pub fn bpp(&self) -> usize {
(self.bit_depth.as_u8() * self.color_type.channels_per_pixel()) as usize
}
/// Byte length of IDAT that is correct for this IHDR
@ -42,8 +42,8 @@ impl IhdrData {
let h = self.height as usize;
let bpp = self.bpp();
fn bitmap_size(bpp: u8, w: usize, h: usize) -> usize {
(((w / 8) * bpp as usize) + ((w & 7) * bpp as usize + 7) / 8) * h
fn bitmap_size(bpp: usize, w: usize, h: usize) -> usize {
((w * bpp + 7) / 8) * h
}
if self.interlaced == Interlacing::None {

View file

@ -45,11 +45,11 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
let bit_vec = line.data.view_bits::<Msb0>();
for (i, bit) in bit_vec.iter().by_vals().enumerate() {
// Avoid moving padded 0's into new image
if i >= (png.ihdr.width * u32::from(bits_per_pixel)) as usize {
if i >= (png.ihdr.width as usize * bits_per_pixel) {
break;
}
// Copy pixels into interlaced passes
let pix_modulo = (i / bits_per_pixel as usize) % 8;
let pix_modulo = (i / bits_per_pixel) % 8;
match index % 8 {
0 => match pix_modulo {
0 => passes[0].push(bit),
@ -113,7 +113,7 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
/// Deinterlace by bits, for images with less than 8bpp
fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
let bits_per_pixel = png.ihdr.bpp();
let bits_per_line = bits_per_pixel as usize * png.ihdr.width as usize;
let bits_per_line = bits_per_pixel * png.ihdr.width as usize;
// Initialize each output line with blank data
let mut lines: Vec<BitVec<u8, Msb0>> =
vec![bitvec![u8, Msb0; 0; bits_per_line]; png.ihdr.height as usize];
@ -126,16 +126,16 @@ fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
+ u32::from(pass_constants.x_step)
- 1)
/ u32::from(pass_constants.x_step)) as usize
* bits_per_pixel as usize;
* bits_per_pixel;
for (i, bit) in bit_vec.iter().by_vals().enumerate() {
// Avoid moving padded 0's into new image
if i >= bits_in_line {
break;
}
let current_x: usize = pass_constants.x_shift as usize
+ (i / bits_per_pixel as usize) * pass_constants.x_step as usize;
+ (i / bits_per_pixel) * pass_constants.x_step as usize;
// Copy this bit into the output line
let index = (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize;
let index = (i % bits_per_pixel) + current_x * bits_per_pixel;
lines[current_y].set(index, bit);
}
// Calculate the next line and move to next pass if necessary
@ -161,7 +161,7 @@ fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
/// Deinterlace by bytes, for images with at least 8bpp
fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
let bytes_per_pixel = png.ihdr.bpp() / 8;
let bytes_per_line = bytes_per_pixel as usize * png.ihdr.width as usize;
let bytes_per_line = bytes_per_pixel * png.ihdr.width as usize;
// Initialize each output line with some blank data
let mut lines: Vec<Vec<u8>> = vec![vec![0; bytes_per_line]; png.ihdr.height as usize];
let mut current_pass = 1;
@ -170,9 +170,9 @@ fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
for line in png.scan_lines(false) {
for (i, byte) in line.data.iter().enumerate() {
let current_x: usize = pass_constants.x_shift as usize
+ (i / bytes_per_pixel as usize) * pass_constants.x_step as usize;
+ (i / bytes_per_pixel) * pass_constants.x_step as usize;
// Copy this byte into the output line
let index = (i % bytes_per_pixel as usize) + current_x * bytes_per_pixel as usize;
let index = (i % bytes_per_pixel) + current_x * bytes_per_pixel;
lines[current_y][index] = *byte;
}
// Calculate the next line and move to next pass if necessary

View file

@ -1,4 +1,4 @@
use crate::colors::ColorType;
use crate::colors::{BitDepth, ColorType};
use crate::deflate;
use crate::error::PngError;
use crate::filters::*;
@ -246,8 +246,18 @@ impl PngImage {
/// Return the number of channels in the image, based on color type
#[inline]
pub fn channels_per_pixel(&self) -> u8 {
self.ihdr.color_type.channels_per_pixel()
pub fn channels_per_pixel(&self) -> usize {
self.ihdr.color_type.channels_per_pixel() as usize
}
/// Return the number of bytes per channel in the image
#[inline]
pub fn bytes_per_channel(&self) -> usize {
match self.ihdr.bit_depth {
BitDepth::Sixteen => 2,
// Depths lower than 8 will round up to 1 byte
_ => 1,
}
}
/// Return an iterator over the scanlines of the image
@ -259,7 +269,7 @@ impl PngImage {
/// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
fn unfilter_image(&self) -> Result<Vec<u8>, PngError> {
let mut unfiltered = Vec::with_capacity(self.data.len());
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
let bpp = self.bytes_per_channel() * self.channels_per_pixel();
let mut last_line: Vec<u8> = Vec::new();
let mut last_pass = None;
let mut unfiltered_buf = Vec::new();
@ -281,13 +291,12 @@ impl PngImage {
/// Apply the specified filter type to all rows in the image
pub fn filter_image(&self, filter: RowFilter, optimize_alpha: bool) -> Vec<u8> {
let mut filtered = Vec::with_capacity(self.data.len());
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
let bpp = self.bytes_per_channel() * self.channels_per_pixel();
// If alpha optimization is enabled, determine how many bytes of alpha there are per pixel
let alpha_bytes = match self.ihdr.color_type {
ColorType::RGBA | ColorType::GrayscaleAlpha if optimize_alpha => {
(self.ihdr.bit_depth.as_u8() / 8) as usize
}
_ => 0,
let alpha_bytes = if optimize_alpha && self.ihdr.color_type.has_alpha() {
self.bytes_per_channel()
} else {
0
};
let mut prev_line = Vec::new();

View file

@ -43,7 +43,7 @@ impl<'a> Iterator for ScanLines<'a> {
struct ScanLineRanges {
/// Current pass number, and 0-indexed row within the pass
pass: Option<(u8, u32)>,
bits_per_pixel: u8,
bits_per_pixel: usize,
width: u32,
height: u32,
left: usize,
@ -53,7 +53,7 @@ struct ScanLineRanges {
impl ScanLineRanges {
pub fn new(png: &PngImage, has_filter: bool) -> Self {
Self {
bits_per_pixel: png.ihdr.bit_depth.as_u8() * png.channels_per_pixel(),
bits_per_pixel: png.ihdr.bpp(),
width: png.ihdr.width,
height: png.ihdr.height,
left: png.data.len(),
@ -143,8 +143,8 @@ impl Iterator for ScanLineRanges {
// Standard, non-interlaced PNG scanlines
(self.width, None)
};
let bits_per_line = pixels_per_line * u32::from(self.bits_per_pixel);
let mut len = ((bits_per_line + 7) / 8) as usize;
let bits_per_line = pixels_per_line as usize * self.bits_per_pixel;
let mut len = (bits_per_line + 7) / 8;
if self.has_filter {
len += 1;
}

View file

@ -6,20 +6,16 @@ use crate::png::PngImage;
/// Clean the alpha channel by setting the color of all fully transparent pixels to black
pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
let (bpc, bpp) = match png.ihdr.color_type {
ColorType::RGBA | ColorType::GrayscaleAlpha => {
let cpp = png.channels_per_pixel();
let bpc = png.ihdr.bit_depth.as_u8() / 8;
(bpc as usize, (bpc * cpp) as usize)
}
_ => {
return None;
}
};
if !png.ihdr.color_type.has_alpha() {
return None;
}
let byte_depth = png.bytes_per_channel();
let bpp = png.channels_per_pixel() * byte_depth;
let colored_bytes = bpp - byte_depth;
let mut reduced = Vec::with_capacity(png.data.len());
for pixel in png.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).all(|b| *b == 0) {
if pixel.iter().skip(colored_bytes).all(|b| *b == 0) {
reduced.resize(reduced.len() + bpp, 0);
} else {
reduced.extend_from_slice(pixel);
@ -35,15 +31,11 @@ pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
#[must_use]
pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
if !matches!(
png.ihdr.color_type,
ColorType::GrayscaleAlpha | ColorType::RGBA
) {
if !png.ihdr.color_type.has_alpha() {
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;
let byte_depth = png.bytes_per_channel();
let bpp = png.channels_per_pixel() * byte_depth;
let colored_bytes = bpp - byte_depth;
// If alpha optimisation is enabled, see if the image contains only fully opaque and fully transparent pixels.

View file

@ -6,12 +6,10 @@ use crate::png::PngImage;
#[must_use]
pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage> {
if png.ihdr.bit_depth != BitDepth::Sixteen {
return match png.ihdr.color_type {
ColorType::Indexed { .. } | ColorType::Grayscale { .. } => {
reduce_bit_depth_8_or_less(png, minimum_bits)
}
_ => None,
};
if png.channels_per_pixel() == 1 {
return reduce_bit_depth_8_or_less(png, minimum_bits);
}
return None;
}
// Reduce from 16 to 8 bits per channel per pixel

View file

@ -141,8 +141,8 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
#[must_use]
pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
let mut reduced = Vec::with_capacity(png.data.len());
let byte_depth = png.ihdr.bit_depth.as_u8() as usize >> 3;
let bpp = png.channels_per_pixel() as usize * byte_depth;
let byte_depth = png.bytes_per_channel();
let bpp = png.channels_per_pixel() * byte_depth;
let last_color = 2 * byte_depth;
for pixel in png.data.chunks(bpp) {
if byte_depth == 1 {

View file

@ -189,27 +189,20 @@ fn reordered_palette(palette: &[RGBA8], palette_map: &[Option<u8>; 256]) -> Vec<
new_palette
}
/// Attempt to reduce the color type of the image
/// Returns true if the color type was reduced, false otherwise
/// Attempt to reduce the color type of the image, returning the reduced image if successful
pub fn reduce_color_type(
png: &PngImage,
grayscale_reduction: bool,
optimize_alpha: bool,
) -> Option<PngImage> {
let mut should_reduce_bit_depth = false;
let was_single_channel = png.channels_per_pixel() == 1;
let mut reduced = Cow::Borrowed(png);
// 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 { .. }
)
{
// Go down one step at a time - maybe not the most efficient, but it's safe
// Attempt to reduce RGB to grayscale
if grayscale_reduction && reduced.ihdr.color_type.is_rgb() {
if let Some(r) = reduce_rgb_to_grayscale(&reduced) {
reduced = Cow::Owned(r);
should_reduce_bit_depth = reduced.ihdr.color_type != ColorType::GrayscaleAlpha;
}
}
@ -217,17 +210,13 @@ pub fn reduce_color_type(
if reduced.ihdr.color_type == ColorType::GrayscaleAlpha {
if let Some(r) = reduced_alpha_channel(&reduced, optimize_alpha) {
reduced = Cow::Owned(r);
should_reduce_bit_depth = true;
}
}
if matches!(
reduced.ihdr.color_type,
ColorType::RGBA | ColorType::RGB { .. } | ColorType::GrayscaleAlpha
) {
// Attempt to reduce to palette, if not already a single channel
if reduced.channels_per_pixel() != 1 {
if let Some(r) = reduce_to_palette(&reduced) {
reduced = Cow::Owned(r);
should_reduce_bit_depth = true;
// Make sure that palette gets sorted. Ideally, this should be done within reduce_to_palette.
if let Some(r) = reduced_palette(&reduced, optimize_alpha) {
@ -243,9 +232,8 @@ pub fn reduce_color_type(
}
}
if should_reduce_bit_depth {
// Some conversions will allow us to perform bit depth reduction that
// wasn't possible before
// Some conversions will allow us to perform bit depth reduction that wasn't possible before
if !was_single_channel && reduced.channels_per_pixel() == 1 {
if let Some(r) = reduce_bit_depth_8_or_less(&reduced, 1) {
reduced = Cow::Owned(r);
}