Faster depth reduction
This commit is contained in:
parent
e3d82db96b
commit
825ae6a9b2
1 changed files with 52 additions and 48 deletions
|
|
@ -1,28 +1,6 @@
|
||||||
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;
|
||||||
use bitvec::prelude::*;
|
|
||||||
|
|
||||||
const ONE_BIT_PERMUTATIONS: [u8; 2] = [0b0000_0000, 0b1111_1111];
|
|
||||||
const TWO_BIT_PERMUTATIONS: [u8; 4] = [0b0000_0000, 0b0101_0101, 0b1010_1010, 0b1111_1111];
|
|
||||||
const FOUR_BIT_PERMUTATIONS: [u8; 16] = [
|
|
||||||
0b0000_0000,
|
|
||||||
0b0001_0001,
|
|
||||||
0b0010_0010,
|
|
||||||
0b0011_0011,
|
|
||||||
0b0100_0100,
|
|
||||||
0b0101_0101,
|
|
||||||
0b0110_0110,
|
|
||||||
0b0111_0111,
|
|
||||||
0b1000_1000,
|
|
||||||
0b1001_1001,
|
|
||||||
0b1010_1010,
|
|
||||||
0b1011_1011,
|
|
||||||
0b1100_1100,
|
|
||||||
0b1101_1101,
|
|
||||||
0b1110_1110,
|
|
||||||
0b1111_1111,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Attempt to reduce the bit depth of the image
|
/// Attempt to reduce the bit depth of the image
|
||||||
/// Returns true if the bit depth was reduced, false otherwise
|
/// Returns true if the bit depth was reduced, false otherwise
|
||||||
|
|
@ -58,9 +36,12 @@ pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage>
|
||||||
pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> {
|
pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> {
|
||||||
assert!((1..8).contains(&minimum_bits));
|
assert!((1..8).contains(&minimum_bits));
|
||||||
let bit_depth: usize = png.ihdr.bit_depth.as_u8() as usize;
|
let bit_depth: usize = png.ihdr.bit_depth.as_u8() as usize;
|
||||||
if minimum_bits >= bit_depth {
|
if minimum_bits >= bit_depth || bit_depth > 8 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
// Calculate the current number of pixels per byte
|
||||||
|
let ppb = 8 / bit_depth;
|
||||||
|
|
||||||
if png.ihdr.color_type == ColorType::Indexed {
|
if png.ihdr.color_type == ColorType::Indexed {
|
||||||
for line in png.scan_lines(false) {
|
for line in png.scan_lines(false) {
|
||||||
let line_max = line
|
let line_max = line
|
||||||
|
|
@ -91,37 +72,60 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for &byte in &png.data {
|
// Checking for grayscale depth reduction is quite different than for indexed
|
||||||
while minimum_bits < bit_depth {
|
// Note: In rare cases, padding bits in the data may cause this to incorrectly return None
|
||||||
let permutations: &[u8] = if minimum_bits == 1 {
|
let mut mask = (1 << minimum_bits) - 1;
|
||||||
&ONE_BIT_PERMUTATIONS
|
let mut divisions = 1..(bit_depth / minimum_bits);
|
||||||
} else if minimum_bits == 2 {
|
for &b in &png.data {
|
||||||
&TWO_BIT_PERMUTATIONS
|
if b == 0 || b == 255 {
|
||||||
} else if minimum_bits == 4 {
|
continue;
|
||||||
&FOUR_BIT_PERMUTATIONS
|
}
|
||||||
} else {
|
'try_depth: loop {
|
||||||
return None;
|
let mut byte = b;
|
||||||
};
|
// Loop over each pixel in the byte
|
||||||
if permutations.iter().any(|perm| *perm == byte) {
|
for _ in 0..ppb {
|
||||||
break;
|
// Align the first pixel division with the mask
|
||||||
|
byte = byte.rotate_left(minimum_bits as u32);
|
||||||
|
// Each potential division of this pixel must be identical to successfully reduce
|
||||||
|
let compare = byte & mask;
|
||||||
|
for _ in divisions.clone() {
|
||||||
|
// Align the next division with the mask
|
||||||
|
byte = byte.rotate_left(minimum_bits as u32);
|
||||||
|
if byte & mask != compare {
|
||||||
|
// This depth is not possible, try the next one up
|
||||||
|
minimum_bits <<= 1;
|
||||||
|
if minimum_bits == bit_depth {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
mask = (1 << minimum_bits) - 1;
|
||||||
|
divisions = 1..(bit_depth / minimum_bits);
|
||||||
|
continue 'try_depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
minimum_bits <<= 1;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut reduced = BitVec::<u8, Msb0>::with_capacity(png.data.len() * 8);
|
let mut reduced = Vec::with_capacity(png.data.len());
|
||||||
|
let mask = (1 << minimum_bits) - 1;
|
||||||
for line in png.scan_lines(false) {
|
for line in png.scan_lines(false) {
|
||||||
let bit_vec = line.data.view_bits::<Msb0>();
|
// Loop over the data in chunks that will produce 1 byte of output
|
||||||
for (i, bit) in bit_vec.iter().by_vals().enumerate() {
|
for chunk in line.data.chunks(bit_depth / minimum_bits) {
|
||||||
let bit_index = bit_depth - (i % bit_depth);
|
let mut new_byte = 0;
|
||||||
if bit_index <= minimum_bits {
|
let mut shift = 8;
|
||||||
reduced.push(bit);
|
for &(mut byte) in chunk {
|
||||||
|
// Loop over each pixel in the byte
|
||||||
|
for _ in 0..ppb {
|
||||||
|
// Align the current pixel with the mask
|
||||||
|
byte = byte.rotate_left(bit_depth as u32);
|
||||||
|
shift -= minimum_bits;
|
||||||
|
// Take the low bits of the pixel and shift them into the output byte
|
||||||
|
new_byte |= (byte & mask) << shift;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
reduced.push(new_byte);
|
||||||
// Pad end of line to get 8 bits per byte
|
|
||||||
while reduced.len() % 8 != 0 {
|
|
||||||
reduced.push(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,7 +152,7 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: reduced.as_raw_slice().to_vec(),
|
data: reduced,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
bit_depth: BitDepth::from_u8(minimum_bits as u8),
|
bit_depth: BitDepth::from_u8(minimum_bits as u8),
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue