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

Add ImageEncoder implementation for Hdr (rebase of #2013) #2246

Merged
merged 5 commits into from
Jun 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
75 changes: 61 additions & 14 deletions src/codecs/hdr/encoder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::codecs::hdr::{rgbe8, Rgbe8Pixel, SIGNATURE};
use crate::color::Rgb;
use crate::error::ImageResult;
use crate::error::{EncodingError, ImageFormatHint, ImageResult};
use crate::{ExtendedColorType, ImageEncoder, ImageError, ImageFormat};
use std::cmp::Ordering;
use std::io::{Result, Write};

Expand All @@ -9,16 +10,59 @@ pub struct HdrEncoder<W: Write> {
w: W,
}

impl<W: Write> ImageEncoder for HdrEncoder<W> {
fn write_image(
self,
unaligned_bytes: &[u8],
width: u32,
height: u32,
color_type: ExtendedColorType,
) -> ImageResult<()> {
match color_type {
ExtendedColorType::Rgb32F => {
let bytes_per_pixel = color_type.bits_per_pixel() as usize / 8;
let rgbe_pixels = unaligned_bytes
.chunks_exact(bytes_per_pixel)
.map(|bytes| to_rgbe8(Rgb::<f32>(bytemuck::pod_read_unaligned(bytes))));

// the length will be checked inside encode_pixels
self.encode_pixels(rgbe_pixels, width as usize, height as usize)
}

_ => Err(ImageError::Encoding(EncodingError::new(
ImageFormatHint::Exact(ImageFormat::Hdr),
"hdr format currently only supports the `Rgb32F` color type".to_string(),
))),
}
}
}

impl<W: Write> HdrEncoder<W> {
/// Creates encoder
pub fn new(w: W) -> HdrEncoder<W> {
HdrEncoder { w }
}

/// Encodes the image ```data```
/// Encodes the image ```rgb```
/// that has dimensions ```width``` and ```height```
pub fn encode(mut self, data: &[Rgb<f32>], width: usize, height: usize) -> ImageResult<()> {
assert!(data.len() >= width * height);
pub fn encode(self, rgb: &[Rgb<f32>], width: usize, height: usize) -> ImageResult<()> {
self.encode_pixels(rgb.iter().map(|&rgb| to_rgbe8(rgb)), width, height)
}

/// Encodes the image ```flattened_rgbe_pixels```
/// that has dimensions ```width``` and ```height```.
/// The callback must return the color for the given flattened index of the pixel (row major).
fn encode_pixels(
mut self,
mut flattened_rgbe_pixels: impl ExactSizeIterator<Item = Rgbe8Pixel>,
width: usize,
height: usize,
) -> ImageResult<()> {
assert!(
flattened_rgbe_pixels.len() >= width * height,
"not enough pixels provided"
); // bonus: this might elide some bounds checks

let w = &mut self.w;
w.write_all(SIGNATURE)?;
w.write_all(b"\n")?;
Expand All @@ -27,8 +71,8 @@ impl<W: Write> HdrEncoder<W> {
w.write_all(format!("-Y {} +X {}\n", height, width).as_bytes())?;

if !(8..=32_768).contains(&width) {
for &pix in data {
write_rgbe8(w, to_rgbe8(pix))?;
for pixel in flattened_rgbe_pixels {
write_rgbe8(w, pixel)?;
}
} else {
// new RLE marker contains scanline width
Expand All @@ -39,20 +83,22 @@ impl<W: Write> HdrEncoder<W> {
let mut bufb = vec![0; width];
let mut bufe = vec![0; width];
let mut rle_buf = vec![0; width];
for scanline in data.chunks(width) {
for ((((r, g), b), e), &pix) in bufr
for _scanline_index in 0..height {
assert!(flattened_rgbe_pixels.len() >= width); // may reduce the bound checks

for ((((r, g), b), e), pixel) in bufr
.iter_mut()
.zip(bufg.iter_mut())
.zip(bufb.iter_mut())
.zip(bufe.iter_mut())
.zip(scanline.iter())
.zip(&mut flattened_rgbe_pixels)
{
let cp = to_rgbe8(pix);
*r = cp.c[0];
*g = cp.c[1];
*b = cp.c[2];
*e = cp.e;
*r = pixel.c[0];
*g = pixel.c[1];
*b = pixel.c[2];
*e = pixel.e;
}

write_rgbe8(w, marker)?; // New RLE encoding marker
rle_buf.clear();
rle_compress(&bufr[..], &mut rle_buf);
Expand All @@ -77,6 +123,7 @@ enum RunOrNot {
Run(u8, usize),
Norun(usize, usize),
}

use self::RunOrNot::{Norun, Run};

const RUN_MAX_LEN: usize = 127;
Expand Down
5 changes: 2 additions & 3 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ impl ImageFormat {
ImageFormat::Farbfeld => true,
ImageFormat::Avif => true,
ImageFormat::WebP => true,
ImageFormat::Hdr => false,
ImageFormat::Hdr => true,
ImageFormat::OpenExr => true,
ImageFormat::Dds => false,
ImageFormat::Qoi => true,
Expand Down Expand Up @@ -341,8 +341,8 @@ impl ImageFormat {
ImageFormat::WebP => cfg!(feature = "webp"),
ImageFormat::OpenExr => cfg!(feature = "exr"),
ImageFormat::Qoi => cfg!(feature = "qoi"),
ImageFormat::Hdr => cfg!(feature = "hdr"),
ImageFormat::Dds => false,
ImageFormat::Hdr => false,
}
}

Expand Down Expand Up @@ -1818,7 +1818,6 @@ mod tests {
cfg!(feature = "ff"),
ImageFormat::Farbfeld.writing_enabled()
);
assert!(!ImageFormat::Hdr.writing_enabled());
assert!(!ImageFormat::Dds.writing_enabled());
}
}
4 changes: 4 additions & 0 deletions src/io/free_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ pub(crate) fn write_buffer_impl<W: std::io::Write + Seek>(
ImageFormat::WebP => {
webp::WebPEncoder::new_lossless(buffered_write).write_image(buf, width, height, color)
}
#[cfg(feature = "hdr")]
ImageFormat::Hdr => {
hdr::HdrEncoder::new(buffered_write).write_image(buf, width, height, color)
}
_ => Err(ImageError::Unsupported(
UnsupportedError::from_format_and_kind(
ImageFormatHint::Unknown,
Expand Down
Loading