Don't include filter byte in PngImage.data
This commit is contained in:
parent
886dedd487
commit
7122214523
7 changed files with 46 additions and 128 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue