Don't include filter byte in PngImage.data

This commit is contained in:
Andrew 2022-12-15 10:31:35 +13:00
parent 886dedd487
commit 7122214523
7 changed files with 46 additions and 128 deletions

View file

@ -41,40 +41,7 @@ impl Display for Interlacing {
pub fn interlace_image(png: &PngImage) -> PngImage {
let mut passes: Vec<BitVec<u8, Msb0>> = vec![BitVec::new(); 7];
let bits_per_pixel = png.ihdr.bpp();
for (index, line) in png.scan_lines().enumerate() {
match index % 8 {
// Add filter bytes to passes that will be in the output image
0 => {
passes[0].extend_from_raw_slice(&[0]);
if png.ihdr.width >= 5 {
passes[1].extend_from_raw_slice(&[0]);
}
if png.ihdr.width >= 3 {
passes[3].extend_from_raw_slice(&[0]);
}
if png.ihdr.width >= 2 {
passes[5].extend_from_raw_slice(&[0]);
}
}
4 => {
passes[2].extend_from_raw_slice(&[0]);
if png.ihdr.width >= 3 {
passes[3].extend_from_raw_slice(&[0]);
}
if png.ihdr.width >= 2 {
passes[5].extend_from_raw_slice(&[0]);
}
}
2 | 6 => {
passes[4].extend_from_raw_slice(&[0]);
if png.ihdr.width >= 2 {
passes[5].extend_from_raw_slice(&[0]);
}
}
_ => {
passes[6].extend_from_raw_slice(&[0]);
}
}
for (index, line) in png.scan_lines(false).enumerate() {
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
@ -148,15 +115,14 @@ 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 = 8 + bits_per_pixel as usize * png.ihdr.width as usize;
// Initialize each output line with a starting filter byte of 0
// as well as some blank data
let bits_per_line = bits_per_pixel as usize * 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];
let mut current_pass = 1;
let mut pass_constants = interlaced_constants(current_pass);
let mut current_y: usize = pass_constants.y_shift as usize;
for line in png.scan_lines() {
for line in png.scan_lines(false) {
let bit_vec = line.data.view_bits::<Msb0>();
let bits_in_line = ((png.ihdr.width - u32::from(pass_constants.x_shift)
+ u32::from(pass_constants.x_step)
@ -170,8 +136,8 @@ fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
}
let current_x: usize = pass_constants.x_shift as usize
+ (i / bits_per_pixel as usize) * pass_constants.x_step as usize;
// Copy this bit into the output line, offset by 8 because of filter byte
let index = 8 + (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize;
// Copy this bit into the output line
let index = (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize;
lines[current_y].set(index, bit);
}
// Calculate the next line and move to next pass if necessary
@ -197,19 +163,18 @@ 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 = 1 + bytes_per_pixel as usize * png.ihdr.width as usize;
// Initialize each output line with a starting filter byte of 0
// as well as some blank data
let bytes_per_line = bytes_per_pixel as usize * 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;
let mut pass_constants = interlaced_constants(current_pass);
let mut current_y: usize = pass_constants.y_shift as usize;
for line in png.scan_lines() {
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;
// Copy this byte into the output line, offset by 1 because of filter byte
let index = 1 + (i % bytes_per_pixel as usize) + current_x * bytes_per_pixel as usize;
// Copy this byte into the output line
let index = (i % bytes_per_pixel as usize) + current_x * bytes_per_pixel as usize;
lines[current_y][index] = *byte;
}
// Calculate the next line and move to next pass if necessary

View file

@ -18,7 +18,7 @@ use std::sync::Arc;
pub(crate) mod scan_lines;
use self::scan_lines::{ScanLines, ScanLinesMut};
use self::scan_lines::ScanLines;
/// Compression level to use for the Brute filter strategy
const BRUTE_LEVEL: i32 = 1; // 1 is fastest, 2-4 are not useful, 5 is slower but more effective
@ -29,7 +29,7 @@ const BRUTE_LINES: usize = 4; // Values over 8 are generally not useful
pub struct PngImage {
/// The headers stored in the IHDR chunk
pub ihdr: IhdrData,
/// The uncompressed, optionally filtered data from the IDAT chunk
/// The uncompressed, unfiltered data from the IDAT chunk
pub data: Vec<u8>,
/// The palette containing colors used in an Indexed image
/// Contains 3 bytes per color (R+G+B), up to 768
@ -280,14 +280,8 @@ impl PngImage {
/// Return an iterator over the scanlines of the image
#[inline]
pub fn scan_lines(&self) -> ScanLines<'_> {
ScanLines::new(self)
}
/// Return an iterator over the scanlines of the image
#[inline]
pub fn scan_lines_mut(&mut self) -> ScanLinesMut<'_> {
ScanLinesMut::new(self)
pub fn scan_lines(&self, has_filter: bool) -> ScanLines<'_> {
ScanLines::new(self, has_filter)
}
/// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
@ -297,7 +291,7 @@ impl PngImage {
let mut last_line: Vec<u8> = Vec::new();
let mut last_pass = None;
let mut unfiltered_buf = Vec::new();
for line in self.scan_lines() {
for line in self.scan_lines(true) {
if last_pass != line.pass {
last_line.clear();
last_pass = line.pass;
@ -305,7 +299,6 @@ impl PngImage {
last_line.resize(line.data.len(), 0);
let filter = RowFilter::try_from(line.filter).map_err(|_| PngError::InvalidData)?;
filter.unfilter_line(bpp, line.data, &last_line, &mut unfiltered_buf)?;
unfiltered.push(0);
unfiltered.extend_from_slice(&unfiltered_buf);
std::mem::swap(&mut last_line, &mut unfiltered_buf);
unfiltered_buf.clear();
@ -328,7 +321,7 @@ impl PngImage {
let mut prev_line = Vec::new();
let mut prev_pass: Option<u8> = None;
let mut f_buf = Vec::new();
for line in self.scan_lines() {
for line in self.scan_lines(false) {
if prev_pass != line.pass || line.data.len() != prev_line.len() {
prev_line = vec![0; line.data.len()];
}

View file

@ -7,13 +7,16 @@ pub struct ScanLines<'a> {
iter: ScanLineRanges,
/// A reference to the PNG image being iterated upon
raw_data: &'a [u8],
/// Whether the raw data contains filter bytes
has_filter: bool,
}
impl<'a> ScanLines<'a> {
pub fn new(png: &'a PngImage) -> Self {
pub fn new(png: &'a PngImage, has_filter: bool) -> Self {
Self {
iter: ScanLineRanges::new(png),
iter: ScanLineRanges::new(png, has_filter),
raw_data: &png.data,
has_filter,
}
}
}
@ -25,43 +28,16 @@ impl<'a> Iterator for ScanLines<'a> {
self.iter.next().map(|(len, pass)| {
let (data, rest) = self.raw_data.split_at(len);
self.raw_data = rest;
let (&filter, data) = data.split_first().unwrap();
let (&filter, data) = if self.has_filter {
data.split_first().unwrap()
} else {
(&0, data)
};
ScanLine { filter, data, pass }
})
}
}
#[derive(Debug)]
/// An iterator over the scan lines of a PNG image
pub struct ScanLinesMut<'a> {
iter: ScanLineRanges,
/// A reference to the PNG image being iterated upon
raw_data: Option<&'a mut [u8]>,
}
impl<'a> ScanLinesMut<'a> {
pub fn new(png: &'a mut PngImage) -> Self {
Self {
iter: ScanLineRanges::new(png),
raw_data: Some(&mut png.data),
}
}
}
impl<'a> Iterator for ScanLinesMut<'a> {
type Item = ScanLineMut<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(len, pass)| {
let tmp = self.raw_data.take().unwrap();
let (data, rest) = tmp.split_at_mut(len);
self.raw_data = Some(rest);
let (&mut filter, data) = data.split_first_mut().unwrap();
ScanLineMut { filter, data, pass }
})
}
}
#[derive(Debug, Clone)]
/// An iterator over the scan line locations of a PNG image
struct ScanLineRanges {
@ -71,10 +47,11 @@ struct ScanLineRanges {
width: u32,
height: u32,
left: usize,
has_filter: bool,
}
impl ScanLineRanges {
pub fn new(png: &PngImage) -> Self {
pub fn new(png: &PngImage, has_filter: bool) -> Self {
Self {
bits_per_pixel: png.ihdr.bit_depth.as_u8() * png.channels_per_pixel(),
width: png.ihdr.width,
@ -85,6 +62,7 @@ impl ScanLineRanges {
} else {
None
},
has_filter,
}
}
}
@ -166,8 +144,10 @@ impl Iterator for ScanLineRanges {
(self.width, None)
};
let bits_per_line = pixels_per_line * u32::from(self.bits_per_pixel);
let bytes_per_line = ((bits_per_line + 7) / 8) as usize;
let len = bytes_per_line + 1;
let mut len = ((bits_per_line + 7) / 8) as usize;
if self.has_filter {
len += 1;
}
self.left = self.left.checked_sub(len)?;
Some((len, current_pass))
}
@ -183,14 +163,3 @@ pub struct ScanLine<'a> {
/// The current pass if the image is interlaced
pub pass: Option<u8>,
}
#[derive(Debug)]
/// A scan line in a PNG image
pub struct ScanLineMut<'a> {
/// The filter type used to encode the current scan line (0-4)
pub filter: u8,
/// The byte data for the current scan line, encoded with the filter specified in the `filter` field
pub data: &'a mut [u8],
/// The current pass if the image is interlaced
pub pass: Option<u8>,
}

View file

@ -16,8 +16,7 @@ pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
};
let mut reduced = Vec::with_capacity(png.data.len());
for line in png.scan_lines() {
reduced.push(line.filter);
for line in png.scan_lines(false) {
for pixel in line.data.chunks(bpp) {
if pixel.iter().skip(bpp - bpc).all(|b| *b == 0) {
reduced.resize(reduced.len() + bpp, 0);
@ -54,7 +53,7 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
let mut has_transparency = false;
let mut used_colors = vec![false; 256];
for line in png.scan_lines() {
for line in png.scan_lines(false) {
for pixel in line.data.chunks(bpp) {
if optimize_alpha && pixel.iter().skip(colored_bytes).all(|b| *b == 0) {
// Fully transparent, we may be able to reduce with tRNS
@ -83,8 +82,7 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
};
let mut raw_data = Vec::with_capacity(png.data.len());
for line in png.scan_lines() {
raw_data.push(line.filter);
for line in png.scan_lines(false) {
for pixel in line.data.chunks(bpp) {
match transparency_pixel {
Some(ref trns) if pixel.iter().skip(colored_bytes).all(|b| *b == 0) => {

View file

@ -38,13 +38,11 @@ pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage>
// Reduce from 16 to 8 bits per channel per pixel
let mut reduced = Vec::with_capacity(
(png.ihdr.width * png.ihdr.height * u32::from(png.channels_per_pixel()) + png.ihdr.height)
as usize,
(png.ihdr.width * png.ihdr.height * u32::from(png.channels_per_pixel())) as usize,
);
let mut high_byte = 0;
for line in png.scan_lines() {
reduced.push(line.filter);
for line in png.scan_lines(false) {
for (i, &byte) in line.data.iter().enumerate() {
if i % 2 == 0 {
// High byte
@ -79,7 +77,7 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
if minimum_bits >= bit_depth {
return None;
}
for line in png.scan_lines() {
for line in png.scan_lines(false) {
if png.ihdr.color_type == ColorType::Indexed {
let line_max = line
.data
@ -129,8 +127,7 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
}
let mut reduced = BitVec::<u8, Msb0>::with_capacity(png.data.len() * 8);
for line in png.scan_lines() {
reduced.extend_from_raw_slice(&[line.filter]);
for line in png.scan_lines(false) {
let bit_vec = line.data.view_bits::<Msb0>();
for (i, bit) in bit_vec.iter().by_vals().enumerate() {
let bit_index = bit_depth - (i % bit_depth);

View file

@ -19,8 +19,7 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &PngImage) -> Option<PngImage> {
return None;
}
let colored_bytes = bpp - byte_depth;
for line in png.scan_lines() {
reduced.push(line.filter);
for line in png.scan_lines(false) {
let mut low_bytes = Vec::with_capacity(4);
let mut high_bytes = Vec::with_capacity(4);
let mut trans_bytes = Vec::with_capacity(byte_depth as usize);
@ -117,8 +116,7 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
.as_ref()
.filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6)
.map(|t| RGB8::new(t[1], t[3], t[5]));
for line in png.scan_lines() {
raw_data.push(line.filter);
for line in png.scan_lines(false) {
let ok = if png.ihdr.color_type == ColorType::RGB {
reduce_scanline_to_palette(
line.data.as_rgb().iter().cloned().map(|px| {
@ -221,8 +219,7 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
let byte_depth: u8 = png.ihdr.bit_depth.as_u8() >> 3;
let bpp: usize = 3 * byte_depth as usize;
let mut cur_pixel = Vec::with_capacity(bpp);
for line in png.scan_lines() {
reduced.push(line.filter);
for line in png.scan_lines(false) {
for (i, byte) in line.data.iter().enumerate() {
cur_pixel.push(*byte);
if i % bpp == bpp - 1 {

View file

@ -34,7 +34,7 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
let palette = png.palette.as_ref()?;
// Find palette entries that are never used
for line in png.scan_lines() {
for line in png.scan_lines(false) {
match png.ihdr.bit_depth {
BitDepth::Eight => {
for &byte in line.data {
@ -120,8 +120,7 @@ fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Opti
let mut raw_data = Vec::with_capacity(png.data.len());
// Reassign data bytes to new indices
for line in png.scan_lines() {
raw_data.push(line.filter);
for line in png.scan_lines(false) {
for byte in line.data {
raw_data.push(byte_map[*byte as usize]);
}