Skip to content

Commit

Permalink
png: Add ability to internally strip 16 bit images to 8 bit images
Browse files Browse the repository at this point in the history
Signed-off-by: caleb <[email protected]>
  • Loading branch information
etemesi254 committed Oct 15, 2023
1 parent 5a60b9b commit cfd7f88
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 43 deletions.
160 changes: 117 additions & 43 deletions zune-png/src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,20 +161,21 @@ pub struct PngDecoder<T>
where
T: ZReaderTrait
{
pub(crate) stream: ZByteReader<T>,
pub(crate) options: DecoderOptions,
pub(crate) png_info: PngInfo,
pub(crate) palette: Vec<PLTEEntry>,
pub(crate) frames: Vec<SingleFrame>,
pub(crate) actl_info: Option<ActlChunk>,
pub(crate) previous_stride: Vec<u8>,
pub(crate) trns_bytes: [u16; 4],
pub(crate) seen_hdr: bool,
pub(crate) seen_ptle: bool,
pub(crate) seen_headers: bool,
pub(crate) seen_trns: bool,
pub(crate) seen_iend: bool,
pub(crate) current_frame: usize
pub(crate) stream: ZByteReader<T>,
pub(crate) options: DecoderOptions,
pub(crate) png_info: PngInfo,
pub(crate) palette: Vec<PLTEEntry>,
pub(crate) frames: Vec<SingleFrame>,
pub(crate) actl_info: Option<ActlChunk>,
pub(crate) previous_stride: Vec<u8>,
pub(crate) trns_bytes: [u16; 4],
pub(crate) seen_hdr: bool,
pub(crate) seen_ptle: bool,
pub(crate) seen_headers: bool,
pub(crate) seen_trns: bool,
pub(crate) seen_iend: bool,
pub(crate) current_frame: usize,
pub(crate) called_from_decode_into: bool
}

impl<T: ZReaderTrait> PngDecoder<T> {
Expand Down Expand Up @@ -205,20 +206,21 @@ impl<T: ZReaderTrait> PngDecoder<T> {
#[allow(unused_mut, clippy::redundant_field_names)]
pub fn new_with_options(data: T, options: DecoderOptions) -> PngDecoder<T> {
PngDecoder {
seen_hdr: false,
stream: ZByteReader::new(data),
options: options,
palette: Vec::new(),
png_info: PngInfo::default(),
actl_info: None,
previous_stride: vec![],
frames: vec![],
seen_ptle: false,
seen_trns: false,
seen_headers: false,
seen_iend: false,
trns_bytes: [0; 4],
current_frame: 0
seen_hdr: false,
stream: ZByteReader::new(data),
options: options,
palette: Vec::new(),
png_info: PngInfo::default(),
actl_info: None,
previous_stride: vec![],
frames: vec![],
seen_ptle: false,
seen_trns: false,
seen_headers: false,
seen_iend: false,
trns_bytes: [0; 4],
current_frame: 0,
called_from_decode_into: true
}
}

Expand Down Expand Up @@ -507,20 +509,29 @@ impl<T: ZReaderTrait> PngDecoder<T> {
}

let info = &self.png_info;
let bytes = if info.depth == 16 { 2 } else { 1 };
let bytes = if info.depth == 16 && !self.options.png_get_strip_to_8bit() { 2 } else { 1 };

let out_n = self.get_colorspace().unwrap().num_components();
let out_n = self.get_colorspace()?.num_components();

let new_len = info
.width
.checked_mul(info.height)
.unwrap()
.checked_mul(out_n)
.unwrap()
info.width
.checked_mul(info.height)?
.checked_mul(out_n)?
.checked_mul(bytes)
.unwrap();
}
fn inner_buffer_size(&self) -> Option<usize> {
if !self.seen_hdr {
return None;
}

Some(new_len)
let info = &self.png_info;
let bytes = if info.depth == 16 { 2 } else { 1 };

let out_n = self.get_colorspace()?.num_components();

info.width
.checked_mul(info.height)?
.checked_mul(out_n)?
.checked_mul(bytes)
}

/// Get png information which was extracted from the headers
Expand Down Expand Up @@ -559,6 +570,10 @@ impl<T: ZReaderTrait> PngDecoder<T> {
/// - `out`: The slice which we will write our values into.
/// If the slice length is smaller than [`output_buffer_size`](Self::output_buffer_size), it's an error
///
/// # Converting 16 bit to 8 bit images
/// When indicated by [`DecoderOptions::png_set_strip_to_8bit`](zune_core::options::DecoderOptions::png_get_strip_to_8bit)
/// the library will implicitly convert 16 bit to 8 bit by discarding the lower 8 bits
///
/// # Endianness
///
/// - In case the image is a 16 bit PNG, endianness of the samples may be retrieved
Expand All @@ -567,11 +582,40 @@ impl<T: ZReaderTrait> PngDecoder<T> {
/// - PNG uses Big Endian while most machines today are Little Endian (x86 and mainstream Arm),
/// hence if the configured endianness is little endian the library will implicitly convert
/// samples to little endian
///
pub fn decode_into(&mut self, out: &mut [u8]) -> Result<(), PngDecodeErrors> {
// decode headers
if !self.seen_headers || !self.seen_iend {
self.decode_headers()?;
}
// in case we are to decode from 16 bit to 8 bit, allocate separate and decode
if self.called_from_decode_into
&& self.png_info.depth == 16
&& self.options.png_get_strip_to_8bit()
{
let image_len = self.output_buffer_size().unwrap();

if out.len() < image_len {
return Err(PngDecodeErrors::TooSmallOutput(image_len, out.len()));
}
// allocate new size
let mut temp_alloc = vec![0; self.inner_buffer_size().unwrap()];
self.decode_into_inner(&mut temp_alloc)?;

let out = &mut out[..image_len];
// then convert it to 8 bit by taking top bit
for (input, output) in temp_alloc.chunks_exact(2).zip(out) {
*output = input[0];
}
return Ok(());
}
return self.decode_into_inner(out);
}
fn decode_into_inner(&mut self, out: &mut [u8]) -> Result<(), PngDecodeErrors> {
// decode headers
if !self.seen_headers || !self.seen_iend {
self.decode_headers()?;
}

trace!("Input Colorspace: {:?} ", self.png_info.color);
trace!("Output Colorspace: {:?} ", self.get_colorspace().unwrap());
Expand All @@ -586,7 +630,7 @@ impl<T: ZReaderTrait> PngDecoder<T> {

let png_info = self.png_info.clone();

let image_len = self.output_buffer_size().unwrap();
let image_len = self.inner_buffer_size().unwrap();

if out.len() < image_len {
return Err(PngDecodeErrors::TooSmallOutput(image_len, out.len()));
Expand Down Expand Up @@ -619,10 +663,16 @@ impl<T: ZReaderTrait> PngDecoder<T> {
Ok(())
}

/// Decode data returning it into `Vec<u8>`, endianness of
/// returned bytes in case of image being 16 bits is given
/// Decode data returning it into `Vec<u8>`.
///
/// Endianness of
/// returned bytes in case of image being 16 bits and the decoder
/// not converting 16 bit images to 8 bit images is given by
/// [`byte_endian()`](Self::byte_endian) method
///
/// # Converting 16 bit to 8 bit images
/// When indicated by [`DecoderOptions::png_set_strip_to_8bit`](zune_core::options::DecoderOptions::png_get_strip_to_8bit)
/// the library will implicitly convert 16 bit to 8 bit by discarding the lower 8 bits
///
/// returns: `Result<Vec<u8, Global>, PngErrors>`
///
Expand All @@ -631,11 +681,23 @@ impl<T: ZReaderTrait> PngDecoder<T> {
self.decode_headers()?;
}

self.called_from_decode_into = false;

// allocate
let new_len = self.output_buffer_size().unwrap();
let mut out: Vec<u8> = vec![0; new_len];
let t = self.inner_buffer_size().unwrap();
let mut out: Vec<u8> = vec![0; t];
//decode
self.decode_into(&mut out)?;
// then convert to 8 bit in place
let mut i = 0;
let mut j = 0;
while j < out.len() {
out[i] = out[j];
i += 1;
j += 2;
}
out.truncate(new_len);

Ok(out)
}
Expand Down Expand Up @@ -716,12 +778,19 @@ impl<T: ZReaderTrait> PngDecoder<T> {
}

/// Decode PNG encoded images and return the vector of raw pixels but for 16-bit images
/// represent them in a `Vec<u16>`
/// represent them in a `Vec<u16>` if [`DecoderOptions::png_set_strip_to_8bit`](zune_core::options::DecoderOptions::png_get_strip_to_8bit)
/// returns false
///
///
/// This returns an enum type [`DecodingResult`](zune_core::result::DecodingResult) which
/// one can de-sugar to extract actual values.
///
/// # Converting 16 bit to 8 bit images
/// When indicated by [`DecoderOptions::png_set_strip_to_8bit`](zune_core::options::DecoderOptions::png_get_strip_to_8bit)
/// the library will implicitly convert 16 bit to 8 bit by discarding the lower 8 bits
///
/// If such is specified, this routine will always return [`DecodingResult::U8`](zune_core::result::DecodingResult::U8)
///
/// # Example
///
/// ```no_run
Expand Down Expand Up @@ -755,6 +824,11 @@ impl<T: ZReaderTrait> PngDecoder<T> {
if !self.seen_headers || !self.seen_iend {
self.decode_headers()?;
}
// in case we are to strip 16 bit to 8 bit, use decode_raw which does that for us
if self.options.png_get_strip_to_8bit() && self.png_info.depth == 16 {
let bytes = self.decode_raw()?;
return Ok(DecodingResult::U8(bytes))
}
// configure that the decoder converts samples to native endian
if is_le()
{
Expand Down
23 changes: 23 additions & 0 deletions zune-png/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@
//! zune_png="0.2"
//! ```
//!
//! #### Decode to 8-bit(1 byte) per pixel always
//!
//! PNG supports both 8-bit and 16 bit images, but people mainly expect the
//! images to be in 8 bit, the library can implicitly convert to 8 bit images when
//! requested in case one doesn't want to handle it at the cost
//! of an extra allocation
//!
//! The below example shows how to do that
//!
//!```no_run
//! use zune_core::options::DecoderOptions;
//! use zune_png::PngDecoder;
//! // tell the png decoder to always strip 16 bit images to 8 bits
//! let options = DecoderOptions::default().png_set_strip_to_8bit(true);
//! let mut decoder = PngDecoder::new_with_options(&[],options);
//!
//! let pixels = decoder.decode_raw();
//! ```
//!
//! Above, we set the [`DecoderOptions::png_set_strip_to_8bit`](zune_core::options::DecoderOptions::png_get_strip_to_8bit)
//! to be true in order to indicate to the decoder that it should strip 16 bit images to 8 bit.
//!
//!
//! #### Decode to raw bytes.
//!
//! This is a simple decode operation which returns raw
Expand Down
Loading

0 comments on commit cfd7f88

Please sign in to comment.