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

HDR Decoder: float by default #1599

Closed
Closed
Show file tree
Hide file tree
Changes from 7 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
97 changes: 47 additions & 50 deletions src/codecs/hdr/decoder.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
use num_traits::identities::Zero;
use scoped_threadpool::Pool;
#[cfg(test)]
use std::borrow::Cow;
use std::convert::TryFrom;
use std::io::{self, Cursor, Read, Seek, BufRead};
use std::iter::Iterator;
use std::marker::PhantomData;
use std::{error, fmt, mem};
use std::num::{ParseFloatError, ParseIntError};
use std::path::Path;
use crate::Primitive;
use crate::{Primitive, Pixel};
use crate::traits::PixelWithColorType;

use crate::color::{ColorType, Rgb};
use crate::error::{DecodingError, ImageError, ImageFormatHint, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind};
use crate::image::{self, ImageDecoder, ImageDecoderExt, ImageFormat, Progress};

#[cfg(test)]
use std::borrow::Cow;
/// Errors that can occur during decoding and parsing of a HDR image
#[derive(Debug, Clone, PartialEq, Eq)]
enum DecoderError {
Expand Down Expand Up @@ -143,14 +143,23 @@ impl<R: BufRead> HdrAdapter<R> {
}

/// Read the actual data of the image, and store it in Self::data.
fn read_image_data(&mut self, buf: &mut [u8]) -> ImageResult<()> {
assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
fn read_image_data_f32(&mut self, image_bytes: &mut [u8]) -> ImageResult<()> {
assert_eq!(u64::try_from(image_bytes.len()), Ok(self.total_bytes()));
let bytes_per_pixel = Rgb::<f32>::COLOR_TYPE.bytes_per_pixel() as usize;

match self.inner.take() {
Some(decoder) => {
let img: Vec<Rgb<u8>> = decoder.read_image_ldr()?;
for (i, Rgb(data)) in img.into_iter().enumerate() {
buf[(i*3)..][..3].copy_from_slice(&data);
}


decoder.read_image_transform(
|index, pix| {
let rgb_f32 = pix.to_hdr();
let pixel_bytes: &[u8] = bytemuck::cast_slice(rgb_f32.channels());

image_bytes[index * bytes_per_pixel .. (index + 1) * bytes_per_pixel]
.copy_from_slice(pixel_bytes);
},
)?;

Ok(())
}
Expand All @@ -167,6 +176,7 @@ impl<R> Read for HdrReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}

fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
if self.0.position() == 0 && buf.is_empty() {
mem::swap(buf, self.0.get_mut());
Expand All @@ -185,15 +195,15 @@ impl<'a, R: 'a + BufRead> ImageDecoder<'a> for HdrAdapter<R> {
}

fn color_type(&self) -> ColorType {
ColorType::Rgb8
ColorType::Rgb32F
}

fn into_reader(self) -> ImageResult<Self::Reader> {
Ok(HdrReader(Cursor::new(image::decoder_to_vec(self)?), PhantomData))
}

fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
self.read_image_data(buf)
self.read_image_data_f32(buf)
}
}

Expand All @@ -207,8 +217,9 @@ impl<'a, R: 'a + BufRead + Seek> ImageDecoderExt<'a> for HdrAdapter<R> {
buf: &mut [u8],
progress_callback: F,
) -> ImageResult<()> {
// TODO if hdr is read scan line by scan line, why does this read everything at once?
image::load_rect(x, y, width, height, buf, progress_callback, self, |_, _| unreachable!(),
|s, buf| s.read_image_data(buf))
|s, buf| s.read_image_data_f32(buf))
}
}

Expand All @@ -219,7 +230,7 @@ const SIGNATURE_LENGTH: usize = 10;
/// An Radiance HDR decoder
#[derive(Debug)]
pub struct HdrDecoder<R> {
r: R,
reader: R,
width: u32,
height: u32,
meta: HdrMetadata,
Expand Down Expand Up @@ -387,7 +398,7 @@ impl<R: BufRead> HdrDecoder<R> {
}

Ok(HdrDecoder {
r: reader,
reader: reader,

width,
height,
Expand All @@ -414,70 +425,56 @@ impl<R: BufRead> HdrDecoder<R> {
let pixel_count = self.width as usize * self.height as usize;
let mut ret = vec![Default::default(); pixel_count];
for chunk in ret.chunks_mut(self.width as usize) {
read_scanline(&mut self.r, chunk)?;
read_scanline(&mut self.reader, chunk)?;
}
Ok(ret)
}

/// Consumes decoder and returns a vector of transformed pixels
pub fn read_image_transform<T: Send, F: Send + Sync + Fn(Rgbe8Pixel) -> T>(
/// The callback gets a flattened index as the first argument.
pub fn read_image_transform<F: Send + Sync + FnMut(usize, Rgbe8Pixel)>(
mut self,
f: F,
output_slice: &mut [T],
mut store_rgbe_pixel: F
) -> ImageResult<()> {
assert_eq!(
output_slice.len(),
self.width as usize * self.height as usize
);

// Don't read anything if image is empty
if self.width == 0 || self.height == 0 {
return Ok(());
}

let chunks_iter = output_slice.chunks_mut(self.width as usize);
let mut pool = Pool::new(8); //

(pool.scoped(|scope| {
for chunk in chunks_iter {
let mut buf = vec![Default::default(); self.width as usize];
read_scanline(&mut self.r, &mut buf[..])?;
let f = &f;
scope.execute(move || {
johannesvollmer marked this conversation as resolved.
Show resolved Hide resolved
for (dst, &pix) in chunk.iter_mut().zip(buf.iter()) {
*dst = f(pix);
}
});
let mut rgbe_pixel_line_tmp_buffer = vec![Default::default(); self.width as usize];

for y_index in 0 .. self.height as usize {
johannesvollmer marked this conversation as resolved.
Show resolved Hide resolved
read_scanline(&mut self.reader, rgbe_pixel_line_tmp_buffer.as_mut())?;

for (x_index, &rgbe_pixel) in rgbe_pixel_line_tmp_buffer.iter().enumerate() {
store_rgbe_pixel(y_index * self.width as usize + x_index, rgbe_pixel);
}
Ok(())
}) as Result<(), ImageError>)?;
}

Ok(())
}

/// Consumes decoder and returns a vector of Rgb<u8> pixels.
/// scale = 1, gamma = 2.2
pub fn read_image_ldr(self) -> ImageResult<Vec<Rgb<u8>>> {
let mut ret = vec![Rgb([0, 0, 0]); self.width as usize * self.height as usize];
self.read_image_transform(|pix| pix.to_ldr(), &mut ret[..])?;
self.read_image_transform(|index, pix| ret[index] = pix.to_ldr())?;
Ok(ret)
}

/// Consumes decoder and returns a vector of Rgb<f32> pixels.
///
pub fn read_image_hdr(self) -> ImageResult<Vec<Rgb<f32>>> {
let mut ret = vec![Rgb([0.0, 0.0, 0.0]); self.width as usize * self.height as usize];
self.read_image_transform(|pix| pix.to_hdr(), &mut ret[..])?;
self.read_image_transform(|index, pix| ret[index] = pix.to_hdr())?;
Ok(ret)
}
}

impl<R: Read> IntoIterator for HdrDecoder<R> {
impl<R: BufRead> IntoIterator for HdrDecoder<R> {
type Item = ImageResult<Rgbe8Pixel>;
type IntoIter = HdrImageDecoderIterator<R>;

fn into_iter(self) -> Self::IntoIter {
HdrImageDecoderIterator {
r: self.r,
r: self.reader,
scanline_cnt: self.height as usize,
buf: vec![Default::default(); self.width as usize],
col: 0,
Expand All @@ -489,7 +486,7 @@ impl<R: Read> IntoIterator for HdrDecoder<R> {
}

/// Scanline buffered pixel by pixel iterator
pub struct HdrImageDecoderIterator<R: Read> {
pub struct HdrImageDecoderIterator<R: BufRead> {
r: R,
scanline_cnt: usize,
buf: Vec<Rgbe8Pixel>, // scanline buffer
Expand All @@ -499,7 +496,7 @@ pub struct HdrImageDecoderIterator<R: Read> {
error_encountered: bool,
}

impl<R: Read> HdrImageDecoderIterator<R> {
impl<R: BufRead> HdrImageDecoderIterator<R> {
// Advances counter to the next pixel
#[inline]
fn advance(&mut self) {
Expand All @@ -512,7 +509,7 @@ impl<R: Read> HdrImageDecoderIterator<R> {
}
}

impl<R: Read> Iterator for HdrImageDecoderIterator<R> {
impl<R: BufRead> Iterator for HdrImageDecoderIterator<R> {
type Item = ImageResult<Rgbe8Pixel>;

fn next(&mut self) -> Option<Self::Item> {
Expand Down Expand Up @@ -563,7 +560,7 @@ impl<R: Read> Iterator for HdrImageDecoderIterator<R> {
}
}

impl<R: Read> ExactSizeIterator for HdrImageDecoderIterator<R> {}
impl<R: BufRead> ExactSizeIterator for HdrImageDecoderIterator<R> {}

// Precondition: buf.len() > 0
fn read_scanline<R: Read>(r: &mut R, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> {
Expand Down
64 changes: 64 additions & 0 deletions tests/roundtrip_images.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::path::PathBuf;
use tiff::encoder::ImageEncoder;
use image::{DynamicImage, ImageFormat, ImageResult, ImageError};
use std::io::Cursor;
use image::io::Reader;

const BASE_PATH: [&str; 2] = [".", "tests"];

fn process_images(process_image: impl Fn(PathBuf)) {
let base: PathBuf = BASE_PATH.iter().collect();
let extensions = &["tga", "tiff", "png", "gif", "bmp", "ico", "jpg", "hdr", "pbm", "webp", "exr"];
for extension in extensions {
let mut path = base.clone();
path.push("**");
path.push("*.".to_string() + extension);
let pattern = &*format!("{}", path.display());
for path in glob::glob(pattern).unwrap().filter_map(Result::ok) {
process_image(path)
}
}
}

#[test]
fn roundtrip() {
process_images(|path| {
println!("round trip test at path {:?}", path);

let format_and_image: ImageResult<(ImageFormat, DynamicImage)> = Reader::open(&path)
.map_err(|err| err.into())
.and_then(|reader|{
let reader = reader.with_guessed_format()?;
let format = reader.format();
let content = reader.decode()?;

// the image has been decoded, so the format is guaranteed to be detected
Ok((format.unwrap(), content))
});

if let Ok((format, decoded)) = format_and_image {
if format.can_write() {
let mut byte_buffer = Vec::new();
match decoded.write_to(&mut Cursor::new(&mut byte_buffer), format) {
Ok(_) => {
let re_decoded = image::load(Cursor::new(byte_buffer.as_slice()), format)
.expect("roundtrip re-decoding failed");

assert_eq!(decoded, re_decoded); // TODO tolerance for float images
}

Err(ImageError::Unsupported(error)) =>
println!("reading was successful, but the encoder does not support this image ({:?})", error),

Err(_) => panic!("cannot write image even though format says so"),
}
}
else {
println!("Skipping read-only format {:?}", format);
}
}
else {
println!("Skipping invalid image at path {:?}", &path);
}
})
}