Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memoization of RGBA palette when expanding palette indices into RGB8 or RGBA8 #462

Merged
merged 6 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benches/expand_paletted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ fn create_expand_palette_fn(info: &Info) -> TransformFn {

fn bench_create_fn(c: &mut Criterion, plte_size: usize, trns_size: usize) {
let mut group = c.benchmark_group("expand_paletted(ctor)");
group.sample_size(10000);
group.sample_size(1000);

let mut rng = rand::thread_rng();
let plte = get_random_bytes(&mut rng, plte_size as usize);
let plte = get_random_bytes(&mut rng, 3 * plte_size as usize);
let trns = get_random_bytes(&mut rng, trns_size as usize);
let info = create_info_from_plte_trns_bitdepth(&plte, Some(&trns), 8);
group.bench_with_input(
Expand Down
2 changes: 1 addition & 1 deletion src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ impl<R: Read> Reader<R> {
if self.transform_fn.is_none() {
self.transform_fn = Some(create_transform_fn(self.info(), self.transform)?);
}
self.transform_fn.unwrap()
self.transform_fn.as_deref().unwrap()
};
transform_fn(row, output_buffer, self.info());

Expand Down
233 changes: 23 additions & 210 deletions src/decoder/transform.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Transforming a decompressed, unfiltered row into the final output.

mod palette;

use crate::{BitDepth, ColorType, DecodingError, Info, Transformations};

use super::stream::FormatErrorInner;
Expand All @@ -10,7 +12,7 @@ use super::stream::FormatErrorInner;
///
/// TODO: If some precomputed state is needed (e.g. to make `expand_paletted...`
/// faster) then consider changing this into `Box<dyn Fn(...)>`.
pub type TransformFn = fn(&[u8], &mut [u8], &Info);
pub type TransformFn = Box<dyn Fn(&[u8], &mut [u8], &Info)>;

/// Returns a transformation function that should be applied to image rows based
/// on 1) decoded image metadata (`info`) and 2) the transformations requested
Expand Down Expand Up @@ -41,36 +43,36 @@ pub fn create_transform_fn(
.into(),
));
} else {
if trns {
Ok(expand_paletted_into_rgba8)
Ok(if trns {
palette::create_expansion_into_rgba8(info)
} else {
Ok(expand_paletted_into_rgb8)
}
palette::create_expansion_into_rgb8(info)
})
}
}
ColorType::Grayscale | ColorType::GrayscaleAlpha if bit_depth < 8 && expand => {
if trns {
Ok(expand_gray_u8_with_trns)
Ok(Box::new(if trns {
expand_gray_u8_with_trns
} else {
Ok(expand_gray_u8)
}
expand_gray_u8
}))
}
ColorType::Grayscale | ColorType::Rgb if expand && trns => {
if bit_depth == 8 {
Ok(expand_trns_line)
Ok(Box::new(if bit_depth == 8 {
expand_trns_line
} else if strip16 {
Ok(expand_trns_and_strip_line16)
expand_trns_and_strip_line16
} else {
assert_eq!(bit_depth, 16);
Ok(expand_trns_line16)
}
expand_trns_line16
}))
}
ColorType::Grayscale | ColorType::GrayscaleAlpha | ColorType::Rgb | ColorType::Rgba
if strip16 =>
{
Ok(transform_row_strip16)
Ok(Box::new(transform_row_strip16))
}
_ => Ok(copy_row),
_ => Ok(Box::new(copy_row)),
}
}

Expand Down Expand Up @@ -132,7 +134,7 @@ where
}
}

pub fn expand_trns_line(input: &[u8], output: &mut [u8], info: &Info) {
fn expand_trns_line(input: &[u8], output: &mut [u8], info: &Info) {
let channels = info.color_type.samples();
let trns = info.trns.as_deref();
for (input, output) in input
Expand All @@ -144,7 +146,7 @@ pub fn expand_trns_line(input: &[u8], output: &mut [u8], info: &Info) {
}
}

pub fn expand_trns_line16(input: &[u8], output: &mut [u8], info: &Info) {
fn expand_trns_line16(input: &[u8], output: &mut [u8], info: &Info) {
let channels = info.color_type.samples();
let trns = info.trns.as_deref();
for (input, output) in input
Expand All @@ -162,7 +164,7 @@ pub fn expand_trns_line16(input: &[u8], output: &mut [u8], info: &Info) {
}
}

pub fn expand_trns_and_strip_line16(input: &[u8], output: &mut [u8], info: &Info) {
fn expand_trns_and_strip_line16(input: &[u8], output: &mut [u8], info: &Info) {
let channels = info.color_type.samples();
let trns = info.trns.as_deref();
for (input, output) in input
Expand All @@ -176,60 +178,14 @@ pub fn expand_trns_and_strip_line16(input: &[u8], output: &mut [u8], info: &Info
}
}

pub fn expand_paletted_into_rgb8(row: &[u8], buffer: &mut [u8], info: &Info) {
let palette = info.palette.as_deref().expect("Caller should verify");
let black = [0, 0, 0];

unpack_bits(row, buffer, 3, info.bit_depth as u8, |i, chunk| {
let rgb = palette
.get(3 * i as usize..3 * i as usize + 3)
.unwrap_or(&black);
chunk[0] = rgb[0];
chunk[1] = rgb[1];
chunk[2] = rgb[2];
})
}

pub fn expand_paletted_into_rgba8(row: &[u8], buffer: &mut [u8], info: &Info) {
let palette = info.palette.as_deref().expect("Caller should verify");
let trns = info.trns.as_deref().unwrap_or(&[]);
let black = [0, 0, 0];

// > The tRNS chunk shall not contain more alpha values than there are palette
// entries, but a tRNS chunk may contain fewer values than there are palette
// entries. In this case, the alpha value for all remaining palette entries is
// assumed to be 255.
//
// It seems, accepted reading is to fully *ignore* an invalid tRNS as if it were
// completely empty / all pixels are non-transparent.
let trns = if trns.len() <= palette.len() / 3 {
trns
} else {
&[]
};

unpack_bits(row, buffer, 4, info.bit_depth as u8, |i, chunk| {
let (rgb, a) = (
palette
.get(3 * i as usize..3 * i as usize + 3)
.unwrap_or(&black),
*trns.get(i as usize).unwrap_or(&0xFF),
);
chunk[0] = rgb[0];
chunk[1] = rgb[1];
chunk[2] = rgb[2];
chunk[3] = a;
});
}

pub fn expand_gray_u8(row: &[u8], buffer: &mut [u8], info: &Info) {
fn expand_gray_u8(row: &[u8], buffer: &mut [u8], info: &Info) {
let scaling_factor = (255) / ((1u16 << info.bit_depth as u8) - 1) as u8;
unpack_bits(row, buffer, 1, info.bit_depth as u8, |val, chunk| {
chunk[0] = val * scaling_factor
});
}

pub fn expand_gray_u8_with_trns(row: &[u8], buffer: &mut [u8], info: &Info) {
fn expand_gray_u8_with_trns(row: &[u8], buffer: &mut [u8], info: &Info) {
let scaling_factor = (255) / ((1u16 << info.bit_depth as u8) - 1) as u8;
let trns = info.trns.as_deref();
unpack_bits(row, buffer, 2, info.bit_depth as u8, |pixel, chunk| {
Expand All @@ -245,146 +201,3 @@ pub fn expand_gray_u8_with_trns(row: &[u8], buffer: &mut [u8], info: &Info) {
chunk[0] = pixel * scaling_factor
});
}

#[cfg(test)]
mod test {
use crate::{BitDepth, ColorType, Info, Transformations};

fn expand_paletted(
src: &[u8],
src_bit_depth: u8,
palette: &[u8],
trns: Option<&[u8]>,
) -> Vec<u8> {
let info = Info {
color_type: ColorType::Indexed,
bit_depth: BitDepth::from_u8(src_bit_depth).unwrap(),
palette: Some(palette.into()),
trns: trns.map(Into::into),
..Info::default()
};
let output_bytes_per_input_sample = match trns {
None => 3,
Some(_) => 4,
};
let samples_count_per_byte = (8 / src_bit_depth) as usize;
let samples_count = src.len() * samples_count_per_byte;
let mut dst = vec![0; samples_count * output_bytes_per_input_sample];
let transform_fn = super::create_transform_fn(&info, Transformations::EXPAND).unwrap();
transform_fn(src, dst.as_mut_slice(), &info);
dst
}

#[test]
fn test_expand_paletted_rgba_8bit() {
let actual = expand_paletted(
&[0, 1, 2, 3], // src
8, // src_bit_depth
&[
// palette
0, 1, 2, // entry #0
4, 5, 6, // entry #1
8, 9, 10, // entry #2
12, 13, 14, // entry #3
],
Some(&[3, 7, 11, 15]), // trns
);
assert_eq!(actual, (0..16).collect::<Vec<u8>>());
}

#[test]
fn test_expand_paletted_rgb_8bit() {
let actual = expand_paletted(
&[0, 1, 2, 3], // src
8, // src_bit_depth
&[
// palette
0, 1, 2, // entry #0
3, 4, 5, // entry #1
6, 7, 8, // entry #2
9, 10, 11, // entry #3
],
None, // trns
);
assert_eq!(actual, (0..12).collect::<Vec<u8>>());
}

#[test]
fn test_expand_paletted_rgba_4bit() {
let actual = expand_paletted(
&[0x01, 0x23], // src
4, // src_bit_depth
&[
// palette
0, 1, 2, // entry #0
4, 5, 6, // entry #1
8, 9, 10, // entry #2
12, 13, 14, // entry #3
],
Some(&[3, 7, 11, 15]), // trns
);
assert_eq!(actual, (0..16).collect::<Vec<u8>>());
}

#[test]
fn test_expand_paletted_rgb_4bit() {
let actual = expand_paletted(
&[0x01, 0x23], // src
4, // src_bit_depth
&[
// palette
0, 1, 2, // entry #0
3, 4, 5, // entry #1
6, 7, 8, // entry #2
9, 10, 11, // entry #3
],
None, // trns
);
assert_eq!(actual, (0..12).collect::<Vec<u8>>());
}

#[test]
fn test_expand_paletted_rgba_8bit_more_trns_entries_than_palette_entries() {
let actual = expand_paletted(
&[0, 1, 2, 3], // src
8, // src_bit_depth
&[
// palette
0, 1, 2, // entry #0
4, 5, 6, // entry #1
8, 9, 10, // entry #2
12, 13, 14, // entry #3
],
Some(&[123; 5]), // trns
);

// Invalid (too-long) `trns` means that we'll use 0xFF / opaque alpha everywhere.
assert_eq!(
actual,
vec![0, 1, 2, 0xFF, 4, 5, 6, 0xFF, 8, 9, 10, 0xFF, 12, 13, 14, 0xFF],
);
}

#[test]
fn test_expand_paletted_rgba_8bit_less_trns_entries_than_palette_entries() {
let actual = expand_paletted(
&[0, 1, 2, 3], // src
8, // src_bit_depth
&[
// palette
0, 1, 2, // entry #0
4, 5, 6, // entry #1
8, 9, 10, // entry #2
12, 13, 14, // entry #3
],
Some(&[3, 7]), // trns
);

// Too-short `trns` is treated differently from too-long - only missing entries are
// replaced with 0XFF / opaque.
assert_eq!(
actual,
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0xFF, 12, 13, 14, 0xFF],
);
}
}
Loading
Loading