use super::icc_profile;
use glam::Vec3;
use image::{ExtendedColorType, ImageEncoder, codecs::png::PngEncoder};
use indicatrix::{
color::{ColorSpace, ToneMap},
renderer::tonemap::tonemap_to_rgba as tonemap_xyz_buffer,
};
use std::{io::BufWriter, path::Path, thread};
pub(super) fn tonemap_to_rgba(
width: u32,
height: u32,
total_samples: u32,
accum: &[Vec3],
) -> Vec<u8> {
debug_assert_eq!(
accum.len(),
(width as usize) * (height as usize),
"accum must hold exactly width*height pixels"
);
let inv_samples = 1.0 / total_samples as f32;
tonemap_xyz_buffer(accum, inv_samples)
}
pub(super) fn tonemap_wide_gamut(
width: u32,
height: u32,
total_samples: u32,
accum: &[Vec3],
color_space: ColorSpace,
) -> Vec<u8> {
debug_assert_eq!(
accum.len(),
(width as usize) * (height as usize),
"accum must hold exactly width*height pixels"
);
let inv_samples = 1.0 / total_samples as f32;
let mut out = vec![0u8; accum.len() * 4];
if accum.is_empty() {
return out;
}
let num_threads = thread::available_parallelism().map_or(8, std::num::NonZero::get);
let chunk_len = accum.len().div_ceil(num_threads).max(1);
thread::scope(|s| {
let color_chunks = accum.chunks(chunk_len);
let byte_chunks = out.chunks_mut(chunk_len * 4);
for (colors, dst) in color_chunks.zip(byte_chunks) {
s.spawn(move || {
for (i, xyz) in colors.iter().enumerate() {
let rgba = color_space
.encode(*xyz * inv_samples, ToneMap::AcesFilmic { exposure: 1.0 });
dst[i * 4..i * 4 + 4].copy_from_slice(&rgba);
}
});
}
});
out
}
pub(super) fn save_png(
path: &Path,
width: u32,
height: u32,
rgba: &[u8],
color_space: ColorSpace,
) -> Result<(), String> {
if color_space == ColorSpace::Srgb {
return image::RgbaImage::from_raw(width, height, rgba.to_vec()).map_or_else(
|| Err("Internal error: pixel buffer size did not match dimensions.".to_string()),
|img| img.save(path).map_err(|e| e.to_string()),
);
}
let file = std::fs::File::create(path).map_err(|e| e.to_string())?;
let mut encoder = PngEncoder::new(BufWriter::new(file));
encoder
.set_icc_profile(icc_profile::build(color_space))
.map_err(|e| e.to_string())?;
encoder
.write_image(rgba, width, height, ExtendedColorType::Rgba8)
.map_err(|e| e.to_string())
}