diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/png/src/utils.rs | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/png/src/utils.rs')
-rw-r--r-- | vendor/png/src/utils.rs | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/vendor/png/src/utils.rs b/vendor/png/src/utils.rs new file mode 100644 index 0000000..d43753b --- /dev/null +++ b/vendor/png/src/utils.rs @@ -0,0 +1,463 @@ +//! Utility functions +use std::iter::{repeat, StepBy}; +use std::ops::Range; + +#[inline(always)] +pub fn unpack_bits<F>(buf: &mut [u8], channels: usize, bit_depth: u8, func: F) +where + F: Fn(u8, &mut [u8]), +{ + // Return early if empty. This enables to subtract `channels` later without overflow. + if buf.len() < channels { + return; + } + + let bits = buf.len() / channels * bit_depth as usize; + let extra_bits = bits % 8; + let entries = bits / 8 + + match extra_bits { + 0 => 0, + _ => 1, + }; + let skip = match extra_bits { + 0 => 0, + n => (8 - n) / bit_depth as usize, + }; + let mask = ((1u16 << bit_depth) - 1) as u8; + let i = (0..entries) + .rev() // reverse iterator + .flat_map(|idx| + // this has to be reversed too + (0..8).step_by(bit_depth.into()) + .zip(repeat(idx))) + .skip(skip); + let j = (0..=buf.len() - channels).rev().step_by(channels); + for ((shift, i), j) in i.zip(j) { + let pixel = (buf[i] & (mask << shift)) >> shift; + func(pixel, &mut buf[j..(j + channels)]) + } +} + +pub fn expand_trns_line(input: &[u8], output: &mut [u8], trns: Option<&[u8]>, channels: usize) { + for (input, output) in input + .chunks_exact(channels) + .zip(output.chunks_exact_mut(channels + 1)) + { + output[..channels].copy_from_slice(input); + output[channels] = if Some(input) == trns { 0 } else { 0xFF }; + } +} + +pub fn expand_trns_line16(input: &[u8], output: &mut [u8], trns: Option<&[u8]>, channels: usize) { + for (input, output) in input + .chunks_exact(channels * 2) + .zip(output.chunks_exact_mut(channels * 2 + 2)) + { + output[..channels * 2].copy_from_slice(input); + if Some(input) == trns { + output[channels * 2] = 0; + output[channels * 2 + 1] = 0 + } else { + output[channels * 2] = 0xFF; + output[channels * 2 + 1] = 0xFF + }; + } +} + +pub fn expand_trns_and_strip_line16( + input: &[u8], + output: &mut [u8], + trns: Option<&[u8]>, + channels: usize, +) { + for (input, output) in input + .chunks_exact(channels * 2) + .zip(output.chunks_exact_mut(channels + 1)) + { + for i in 0..channels { + output[i] = input[i * 2]; + } + output[channels] = if Some(input) == trns { 0 } else { 0xFF }; + } +} + +/// This iterator iterates over the different passes of an image Adam7 encoded +/// PNG image +/// The pattern is: +/// 16462646 +/// 77777777 +/// 56565656 +/// 77777777 +/// 36463646 +/// 77777777 +/// 56565656 +/// 77777777 +/// +#[derive(Clone)] +pub(crate) struct Adam7Iterator { + line: u32, + lines: u32, + line_width: u32, + current_pass: u8, + width: u32, + height: u32, +} + +impl Adam7Iterator { + pub fn new(width: u32, height: u32) -> Adam7Iterator { + let mut this = Adam7Iterator { + line: 0, + lines: 0, + line_width: 0, + current_pass: 1, + width, + height, + }; + this.init_pass(); + this + } + + /// Calculates the bounds of the current pass + fn init_pass(&mut self) { + let w = f64::from(self.width); + let h = f64::from(self.height); + let (line_width, lines) = match self.current_pass { + 1 => (w / 8.0, h / 8.0), + 2 => ((w - 4.0) / 8.0, h / 8.0), + 3 => (w / 4.0, (h - 4.0) / 8.0), + 4 => ((w - 2.0) / 4.0, h / 4.0), + 5 => (w / 2.0, (h - 2.0) / 4.0), + 6 => ((w - 1.0) / 2.0, h / 2.0), + 7 => (w, (h - 1.0) / 2.0), + _ => unreachable!(), + }; + self.line_width = line_width.ceil() as u32; + self.lines = lines.ceil() as u32; + self.line = 0; + } + + /// The current pass#. + pub fn current_pass(&self) -> u8 { + self.current_pass + } +} + +/// Iterates over the (passes, lines, widths) +impl Iterator for Adam7Iterator { + type Item = (u8, u32, u32); + fn next(&mut self) -> Option<Self::Item> { + if self.line < self.lines && self.line_width > 0 { + let this_line = self.line; + self.line += 1; + Some((self.current_pass, this_line, self.line_width)) + } else if self.current_pass < 7 { + self.current_pass += 1; + self.init_pass(); + self.next() + } else { + None + } + } +} + +fn subbyte_pixels(scanline: &[u8], bits_pp: usize) -> impl Iterator<Item = u8> + '_ { + (0..scanline.len() * 8) + .step_by(bits_pp) + .map(move |bit_idx| { + let byte_idx = bit_idx / 8; + + // sub-byte samples start in the high-order bits + let rem = 8 - bit_idx % 8 - bits_pp; + + match bits_pp { + // evenly divides bytes + 1 => (scanline[byte_idx] >> rem) & 1, + 2 => (scanline[byte_idx] >> rem) & 3, + 4 => (scanline[byte_idx] >> rem) & 15, + _ => unreachable!(), + } + }) +} + +/// Given pass, image width, and line number, produce an iterator of bit positions of pixels to copy +/// from the input scanline to the image buffer. +fn expand_adam7_bits( + pass: u8, + width: usize, + line_no: usize, + bits_pp: usize, +) -> StepBy<Range<usize>> { + let (line_mul, line_off, samp_mul, samp_off) = match pass { + 1 => (8, 0, 8, 0), + 2 => (8, 0, 8, 4), + 3 => (8, 4, 4, 0), + 4 => (4, 0, 4, 2), + 5 => (4, 2, 2, 0), + 6 => (2, 0, 2, 1), + 7 => (2, 1, 1, 0), + _ => panic!("Adam7 pass out of range: {}", pass), + }; + + // the equivalent line number in progressive scan + let prog_line = line_mul * line_no + line_off; + // line width is rounded up to the next byte + let line_width = (width * bits_pp + 7) & !7; + let line_start = prog_line * line_width; + let start = line_start + (samp_off * bits_pp); + let stop = line_start + (width * bits_pp); + + (start..stop).step_by(bits_pp * samp_mul) +} + +/// Expands an Adam 7 pass +pub fn expand_pass( + img: &mut [u8], + width: u32, + scanline: &[u8], + pass: u8, + line_no: u32, + bits_pp: u8, +) { + let width = width as usize; + let line_no = line_no as usize; + let bits_pp = bits_pp as usize; + + // pass is out of range but don't blow up + if pass == 0 || pass > 7 { + return; + } + + let bit_indices = expand_adam7_bits(pass, width, line_no, bits_pp); + + if bits_pp < 8 { + for (pos, px) in bit_indices.zip(subbyte_pixels(scanline, bits_pp)) { + let rem = 8 - pos % 8 - bits_pp; + img[pos / 8] |= px << rem as u8; + } + } else { + let bytes_pp = bits_pp / 8; + + for (bitpos, px) in bit_indices.zip(scanline.chunks(bytes_pp)) { + for (offset, val) in px.iter().enumerate() { + img[bitpos / 8 + offset] = *val; + } + } + } +} + +#[test] +fn test_adam7() { + /* + 1646 + 7777 + 5656 + 7777 + */ + let it = Adam7Iterator::new(4, 4); + let passes: Vec<_> = it.collect(); + assert_eq!( + &*passes, + &[ + (1, 0, 1), + (4, 0, 1), + (5, 0, 2), + (6, 0, 2), + (6, 1, 2), + (7, 0, 4), + (7, 1, 4) + ] + ); +} + +#[test] +fn test_subbyte_pixels() { + let scanline = &[0b10101010, 0b10101010]; + + let pixels = subbyte_pixels(scanline, 1).collect::<Vec<_>>(); + assert_eq!(pixels.len(), 16); + assert_eq!(pixels, [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]); +} + +#[test] +fn test_expand_adam7_bits() { + let width = 32; + let bits_pp = 1; + + let expected = |offset: usize, step: usize, count: usize| { + (0..count) + .map(move |i| step * i + offset) + .collect::<Vec<_>>() + }; + + for line_no in 0..8 { + let start = 8 * line_no * width; + + assert_eq!( + expand_adam7_bits(1, width, line_no, bits_pp).collect::<Vec<_>>(), + expected(start, 8, 4) + ); + + let start = start + 4; + + assert_eq!( + expand_adam7_bits(2, width, line_no, bits_pp).collect::<Vec<_>>(), + expected(start, 8, 4) + ); + + let start = (8 * line_no + 4) as usize * width as usize; + + assert_eq!( + expand_adam7_bits(3, width, line_no, bits_pp).collect::<Vec<_>>(), + expected(start, 4, 8) + ); + } + + for line_no in 0..16 { + let start = 4 * line_no * width + 2; + + assert_eq!( + expand_adam7_bits(4, width, line_no, bits_pp).collect::<Vec<_>>(), + expected(start, 4, 8) + ); + + let start = (4 * line_no + 2) * width; + + assert_eq!( + expand_adam7_bits(5, width, line_no, bits_pp).collect::<Vec<_>>(), + expected(start, 2, 16) + ) + } + + for line_no in 0..32 { + let start = 2 * line_no * width + 1; + + assert_eq!( + expand_adam7_bits(6, width, line_no, bits_pp).collect::<Vec<_>>(), + expected(start, 2, 16), + "line_no: {}", + line_no + ); + + let start = (2 * line_no + 1) * width; + + assert_eq!( + expand_adam7_bits(7, width, line_no, bits_pp).collect::<Vec<_>>(), + expected(start, 1, 32) + ); + } +} + +#[test] +fn test_expand_pass_subbyte() { + let mut img = [0u8; 8]; + let width = 8; + let bits_pp = 1; + + expand_pass(&mut img, width, &[0b10000000], 1, 0, bits_pp); + assert_eq!(img, [0b10000000u8, 0, 0, 0, 0, 0, 0, 0]); + + expand_pass(&mut img, width, &[0b10000000], 2, 0, bits_pp); + assert_eq!(img, [0b10001000u8, 0, 0, 0, 0, 0, 0, 0]); + + expand_pass(&mut img, width, &[0b11000000], 3, 0, bits_pp); + assert_eq!(img, [0b10001000u8, 0, 0, 0, 0b10001000, 0, 0, 0]); + + expand_pass(&mut img, width, &[0b11000000], 4, 0, bits_pp); + assert_eq!(img, [0b10101010u8, 0, 0, 0, 0b10001000, 0, 0, 0]); + + expand_pass(&mut img, width, &[0b11000000], 4, 1, bits_pp); + assert_eq!(img, [0b10101010u8, 0, 0, 0, 0b10101010, 0, 0, 0]); + + expand_pass(&mut img, width, &[0b11110000], 5, 0, bits_pp); + assert_eq!(img, [0b10101010u8, 0, 0b10101010, 0, 0b10101010, 0, 0, 0]); + + expand_pass(&mut img, width, &[0b11110000], 5, 1, bits_pp); + assert_eq!( + img, + [0b10101010u8, 0, 0b10101010, 0, 0b10101010, 0, 0b10101010, 0] + ); + + expand_pass(&mut img, width, &[0b11110000], 6, 0, bits_pp); + assert_eq!( + img, + [0b11111111u8, 0, 0b10101010, 0, 0b10101010, 0, 0b10101010, 0] + ); + + expand_pass(&mut img, width, &[0b11110000], 6, 1, bits_pp); + assert_eq!( + img, + [0b11111111u8, 0, 0b11111111, 0, 0b10101010, 0, 0b10101010, 0] + ); + + expand_pass(&mut img, width, &[0b11110000], 6, 2, bits_pp); + assert_eq!( + img, + [0b11111111u8, 0, 0b11111111, 0, 0b11111111, 0, 0b10101010, 0] + ); + + expand_pass(&mut img, width, &[0b11110000], 6, 3, bits_pp); + assert_eq!( + [0b11111111u8, 0, 0b11111111, 0, 0b11111111, 0, 0b11111111, 0], + img + ); + + expand_pass(&mut img, width, &[0b11111111], 7, 0, bits_pp); + assert_eq!( + [ + 0b11111111u8, + 0b11111111, + 0b11111111, + 0, + 0b11111111, + 0, + 0b11111111, + 0 + ], + img + ); + + expand_pass(&mut img, width, &[0b11111111], 7, 1, bits_pp); + assert_eq!( + [ + 0b11111111u8, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0, + 0b11111111, + 0 + ], + img + ); + + expand_pass(&mut img, width, &[0b11111111], 7, 2, bits_pp); + assert_eq!( + [ + 0b11111111u8, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0 + ], + img + ); + + expand_pass(&mut img, width, &[0b11111111], 7, 3, bits_pp); + assert_eq!( + [ + 0b11111111u8, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111 + ], + img + ); +} |