use std::ops::{DerefMut, Index};
use std::path::Path;
use std::sync::{Arc, RwLock, RwLockWriteGuard};
#[cfg(feature = "openexr")]
use openexr::{FrameBuffer, Header, PixelType, ScanlineOutputFile};
use smallvec::SmallVec;
use crate::core::filter::Filter;
use crate::core::geometry::{
bnd2_intersect_bnd2i, pnt2_ceil, pnt2_floor, pnt2_inside_exclusivei, pnt2_max_pnt2i,
pnt2_min_pnt2i,
};
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
use crate::core::paramset::ParamSet;
use crate::core::pbrt::{clamp_t, gamma_correct};
use crate::core::pbrt::{Float, Spectrum};
use crate::core::spectrum::xyz_to_rgb;
const FILTER_TABLE_WIDTH: usize = 16;
#[derive(Debug, Clone)]
pub struct Pixel {
pub(crate) xyz: [Float; 3],
pub(crate) filter_weight_sum: Float,
splat_xyz: [Float; 3],
}
impl Default for Pixel {
fn default() -> Self {
Pixel {
xyz: [0.0 as Float; 3],
filter_weight_sum: 0.0 as Float,
splat_xyz: [Float::default(), Float::default(), Float::default()],
}
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct FilmTilePixel {
contrib_sum: Spectrum,
filter_weight_sum: Float,
}
pub struct FilmTile<'a> {
pub pixel_bounds: Bounds2i,
filter_radius: Vector2f,
inv_filter_radius: Vector2f,
filter_table: &'a [Float; FILTER_TABLE_WIDTH * FILTER_TABLE_WIDTH],
filter_table_size: usize,
pixels: Vec<FilmTilePixel>,
max_sample_luminance: Float,
}
impl<'a> FilmTile<'a> {
pub fn new(
pixel_bounds: Bounds2i,
filter_radius: Vector2f,
filter_table: &'a [Float; FILTER_TABLE_WIDTH * FILTER_TABLE_WIDTH],
filter_table_size: usize,
max_sample_luminance: Float,
) -> Self {
FilmTile {
pixel_bounds,
filter_radius,
inv_filter_radius: Vector2f {
x: 1.0 / filter_radius.x,
y: 1.0 / filter_radius.y,
},
filter_table,
filter_table_size,
pixels: vec![FilmTilePixel::default(); pixel_bounds.area() as usize],
max_sample_luminance,
}
}
pub fn add_sample(&mut self, p_film: Point2f, l: &mut Spectrum, sample_weight: Float) {
if l.y() > self.max_sample_luminance {
*l *= Spectrum::new(self.max_sample_luminance / l.y());
}
let p_film_discrete: Point2f = p_film - Vector2f { x: 0.5, y: 0.5 };
let p0f: Point2f = pnt2_ceil(p_film_discrete - self.filter_radius);
let mut p0: Point2i = Point2i {
x: p0f.x as i32,
y: p0f.y as i32,
};
let p1f: Point2f = pnt2_floor(p_film_discrete + self.filter_radius);
let mut p1: Point2i = Point2i {
x: p1f.x as i32 + 1,
y: p1f.y as i32 + 1,
};
p0 = pnt2_max_pnt2i(p0, self.pixel_bounds.p_min);
p1 = pnt2_min_pnt2i(p1, self.pixel_bounds.p_max);
let mut ifx: SmallVec<[usize; 16]> = SmallVec::with_capacity(p1.x as usize - p0.x as usize);
for x in p0.x..p1.x {
let fx: Float = ((x as Float - p_film_discrete.x)
* self.inv_filter_radius.x
* self.filter_table_size as Float)
.abs();
ifx.push(fx.floor().min(self.filter_table_size as Float - 1.0) as usize);
}
let mut ify: SmallVec<[usize; 16]> = SmallVec::with_capacity(p1.y as usize - p0.y as usize);
for y in p0.y..p1.y {
let fy: Float = ((y as Float - p_film_discrete.y)
* self.inv_filter_radius.y
* self.filter_table_size as Float)
.abs();
ify.push(fy.floor().min(self.filter_table_size as Float - 1.0) as usize);
}
for y in p0.y..p1.y {
for x in p0.x..p1.x {
let offset: usize =
ify[(y - p0.y) as usize] * self.filter_table_size + ifx[(x - p0.x) as usize];
let filter_weight: Float = self.filter_table[offset];
let idx = self.get_pixel_index(x, y);
let pixel = &mut self.pixels[idx];
pixel.contrib_sum +=
*l * Spectrum::new(sample_weight) * Spectrum::new(filter_weight);
pixel.filter_weight_sum += filter_weight;
}
}
}
fn get_pixel_index(&self, x: i32, y: i32) -> usize {
let width: i32 = self.pixel_bounds.p_max.x - self.pixel_bounds.p_min.x;
let pidx = (y - self.pixel_bounds.p_min.y) * width + (x - self.pixel_bounds.p_min.x);
pidx as usize
}
}
pub struct Film {
pub full_resolution: Point2i,
pub diagonal: Float,
pub filter: Box<Filter>,
pub filename: String,
pub cropped_pixel_bounds: Bounds2i,
pub pixels: RwLock<Vec<Pixel>>,
filter_table: [Float; FILTER_TABLE_WIDTH * FILTER_TABLE_WIDTH],
scale: Float,
max_sample_luminance: Float,
}
impl Film {
pub fn new(
resolution: Point2i,
crop_window: Bounds2f,
filter: Box<Filter>,
diagonal: Float,
filename: String,
scale: Float,
max_sample_luminance: Float,
) -> Self {
let cropped_pixel_bounds: Bounds2i = Bounds2i {
p_min: Point2i {
x: (resolution.x as Float * crop_window.p_min.x).ceil() as i32,
y: (resolution.y as Float * crop_window.p_min.y).ceil() as i32,
},
p_max: Point2i {
x: (resolution.x as Float * crop_window.p_max.x).ceil() as i32,
y: (resolution.y as Float * crop_window.p_max.y).ceil() as i32,
},
};
let mut filter_table: [Float; FILTER_TABLE_WIDTH * FILTER_TABLE_WIDTH] =
[0.0; FILTER_TABLE_WIDTH * FILTER_TABLE_WIDTH];
let mut offset: usize = 0;
let filter_radius: Vector2f = filter.get_radius();
for y in 0..FILTER_TABLE_WIDTH {
for x in 0..FILTER_TABLE_WIDTH {
let p: Point2f = Point2f {
x: (x as Float + 0.5) * filter_radius.x / FILTER_TABLE_WIDTH as Float,
y: (y as Float + 0.5) * filter_radius.y / FILTER_TABLE_WIDTH as Float,
};
filter_table[offset] = filter.evaluate(p);
offset += 1;
}
}
Film {
full_resolution: resolution,
diagonal: diagonal * 0.001,
filter,
filename,
cropped_pixel_bounds,
pixels: RwLock::new(vec![Pixel::default(); cropped_pixel_bounds.area() as usize]),
filter_table,
scale,
max_sample_luminance,
}
}
pub fn create(params: &ParamSet, filter: Box<Filter>, crop_window: &Bounds2f) -> Arc<Film> {
let filename: String = params.find_one_string("filename", String::new());
let xres: i32 = params.find_one_int("xresolution", 1280);
let yres: i32 = params.find_one_int("yresolution", 720);
let resolution: Point2i = Point2i { x: xres, y: yres };
let mut crop: Bounds2f = Bounds2f {
p_min: Point2f { x: 0.0, y: 0.0 },
p_max: Point2f { x: 1.0, y: 1.0 },
};
let cr: Vec<Float> = params.find_float("cropwindow");
if cr.len() == 4 {
crop.p_min.x = clamp_t(cr[0].min(cr[1]), 0.0, 1.0);
crop.p_max.x = clamp_t(cr[0].max(cr[1]), 0.0, 1.0);
crop.p_min.y = clamp_t(cr[2].min(cr[3]), 0.0, 1.0);
crop.p_max.y = clamp_t(cr[2].max(cr[3]), 0.0, 1.0);
} else if !cr.is_empty() {
panic!(
"{:?} values supplied for \"cropwindow\". Expected 4.",
cr.len()
);
} else {
crop = *crop_window;
}
let scale: Float = params.find_one_float("scale", 1.0);
let diagonal: Float = params.find_one_float("diagonal", 35.0);
let max_sample_luminance: Float =
params.find_one_float("maxsampleluminance", std::f32::INFINITY);
Arc::new(Film::new(
resolution,
crop,
filter,
diagonal,
filename,
scale,
max_sample_luminance,
))
}
pub fn get_cropped_pixel_bounds(&self) -> Bounds2i {
self.cropped_pixel_bounds
}
pub fn get_sample_bounds(&self) -> Bounds2i {
let f: Point2f = pnt2_floor(
Point2f {
x: self.cropped_pixel_bounds.p_min.x as Float,
y: self.cropped_pixel_bounds.p_min.y as Float,
} + Vector2f { x: 0.5, y: 0.5 }
- self.filter.get_radius(),
);
let c: Point2f = pnt2_ceil(
Point2f {
x: self.cropped_pixel_bounds.p_max.x as Float,
y: self.cropped_pixel_bounds.p_max.y as Float,
} - Vector2f { x: 0.5, y: 0.5 }
+ self.filter.get_radius(),
);
let float_bounds: Bounds2f = Bounds2f { p_min: f, p_max: c };
Bounds2i {
p_min: Point2i {
x: float_bounds.p_min.x as i32,
y: float_bounds.p_min.y as i32,
},
p_max: Point2i {
x: float_bounds.p_max.x as i32,
y: float_bounds.p_max.y as i32,
},
}
}
pub fn get_physical_extent(&self) -> Bounds2f {
let aspect: Float = self.full_resolution.y as Float / self.full_resolution.x as Float;
let x: Float = (self.diagonal * self.diagonal / (1.0 as Float + aspect * aspect)).sqrt();
let y: Float = aspect * x;
Bounds2f {
p_min: Point2f {
x: -x / 2.0 as Float,
y: -y / 2.0 as Float,
},
p_max: Point2f {
x: x / 2.0 as Float,
y: y / 2.0 as Float,
},
}
}
pub fn get_film_tile(&self, sample_bounds: &Bounds2i) -> FilmTile {
let half_pixel: Vector2f = Vector2f { x: 0.5, y: 0.5 };
let float_bounds: Bounds2f = Bounds2f {
p_min: Point2f {
x: sample_bounds.p_min.x as Float,
y: sample_bounds.p_min.y as Float,
},
p_max: Point2f {
x: sample_bounds.p_max.x as Float,
y: sample_bounds.p_max.y as Float,
},
};
let p_min: Point2f = float_bounds.p_min - half_pixel - self.filter.get_radius();
let p0: Point2i = Point2i {
x: p_min.x.ceil() as i32,
y: p_min.y.ceil() as i32,
};
let p_max: Point2f = float_bounds.p_max - half_pixel + self.filter.get_radius();
let p1: Point2i = Point2i {
x: p_max.x.floor() as i32,
y: p_max.y.floor() as i32,
} + Point2i { x: 1, y: 1 };
let tile_pixel_bounds: Bounds2i = bnd2_intersect_bnd2i(
&Bounds2i {
p_min: p0,
p_max: p1,
},
&self.cropped_pixel_bounds,
);
FilmTile::new(
tile_pixel_bounds,
self.filter.get_radius(),
&self.filter_table,
FILTER_TABLE_WIDTH,
self.max_sample_luminance,
)
}
pub fn merge_film_tile(&self, tile: &FilmTile) {
for pixel in &tile.pixel_bounds {
let idx = tile.get_pixel_index(pixel.x, pixel.y);
let tile_pixel = &tile.pixels[idx];
assert!(pnt2_inside_exclusivei(pixel, &self.cropped_pixel_bounds));
let width: i32 = self.cropped_pixel_bounds.p_max.x - self.cropped_pixel_bounds.p_min.x;
let offset: i32 = (pixel.x - self.cropped_pixel_bounds.p_min.x)
+ (pixel.y - self.cropped_pixel_bounds.p_min.y) * width;
let mut pixels_write = self.pixels.write().unwrap();
let merge_pixel = &mut pixels_write[offset as usize];
let mut xyz: [Float; 3] = [0.0; 3];
tile_pixel.contrib_sum.to_xyz(&mut xyz);
for (i, item) in xyz.iter().enumerate() {
merge_pixel.xyz[i] += item;
}
merge_pixel.filter_weight_sum += tile_pixel.filter_weight_sum;
}
}
pub fn set_image(&self, img: &[Spectrum]) {
let n_pixels: i32 = self.cropped_pixel_bounds.area();
let mut pixels_write = self.pixels.write().unwrap();
for i in 0..n_pixels as usize {
let merge_pixel = &mut pixels_write[i];
let mut xyz: [Float; 3] = [0.0; 3];
img[i].to_xyz(&mut xyz);
for (i, item) in xyz.iter().enumerate() {
merge_pixel.xyz[i] = *item;
}
merge_pixel.filter_weight_sum = 1.0 as Float;
merge_pixel.splat_xyz[0] = 0.0;
merge_pixel.splat_xyz[1] = 0.0;
merge_pixel.splat_xyz[2] = 0.0;
}
}
pub fn add_splat(&self, p: Point2f, v: &Spectrum) {
let mut v: Spectrum = *v;
if v.has_nans() {
println!(
"ERROR: Ignoring splatted spectrum with NaN values at ({:?}, {:?})",
p.x, p.y
);
return;
} else if v.y() < 0.0 as Float {
println!(
"ERROR: Ignoring splatted spectrum with negative luminance {:?} at ({:?}, {:?})",
v.y(),
p.x,
p.y
);
return;
} else if v.y().is_infinite() {
println!(
"ERROR: Ignoring splatted spectrum with infinite luminance at ({:?}, {:?})",
p.x, p.y
);
return;
}
let pi: Point2i = Point2i {
x: p.x as i32,
y: p.y as i32,
};
if !pnt2_inside_exclusivei(pi, &self.cropped_pixel_bounds) {
return;
}
if v.y() > self.max_sample_luminance {
v = v * self.max_sample_luminance / v.y();
}
let mut xyz: [Float; 3] = [Float::default(); 3];
v.to_xyz(&mut xyz);
let width: i32 = self.cropped_pixel_bounds.p_max.x - self.cropped_pixel_bounds.p_min.x;
let offset: i32 = (pi.x - self.cropped_pixel_bounds.p_min.x)
+ (pi.y - self.cropped_pixel_bounds.p_min.y) * width;
let mut pixels_write: RwLockWriteGuard<Vec<Pixel>> = self.pixels.write().unwrap();
let pixel_vec: &mut Vec<Pixel> = pixels_write.deref_mut();
let pixel: &mut Pixel = &mut pixel_vec[offset as usize];
let splat_xyz: &mut [Float; 3] = &mut pixel.splat_xyz;
splat_xyz[0] += xyz[0];
splat_xyz[1] += xyz[1];
splat_xyz[2] += xyz[2];
}
#[cfg(not(feature = "openexr"))]
pub fn write_image(&self, splat_scale: Float) {
let mut rgb: Vec<Float> =
vec![0.0 as Float; (3 * self.cropped_pixel_bounds.area()) as usize];
let mut offset;
for p in &self.cropped_pixel_bounds {
assert!(pnt2_inside_exclusivei(p, &self.cropped_pixel_bounds));
let width: i32 = self.cropped_pixel_bounds.p_max.x - self.cropped_pixel_bounds.p_min.x;
offset = ((p.x - self.cropped_pixel_bounds.p_min.x)
+ (p.y - self.cropped_pixel_bounds.p_min.y) * width) as usize;
let pixel: &Pixel = &self.pixels.read().unwrap()[offset];
let start: usize = 3 * offset;
let mut rgb_array: [Float; 3] = [0.0 as Float; 3];
xyz_to_rgb(&pixel.xyz, &mut rgb_array); rgb[start] = rgb_array[0];
rgb[start + 1] = rgb_array[1];
rgb[start + 2] = rgb_array[2];
let filter_weight_sum: Float = pixel.filter_weight_sum;
if filter_weight_sum != 0.0 as Float {
let inv_wt: Float = 1.0 as Float / filter_weight_sum;
rgb[start] = (rgb[start] * inv_wt).max(0.0 as Float);
rgb[start + 1] = (rgb[start + 1] * inv_wt).max(0.0 as Float);
rgb[start + 2] = (rgb[start + 2] * inv_wt).max(0.0 as Float);
}
let mut splat_rgb: [Float; 3] = [0.0 as Float; 3];
let pixel_splat_xyz: &[Float; 3] = &pixel.splat_xyz;
let splat_xyz: [Float; 3] = [
*pixel_splat_xyz.index(0),
*pixel_splat_xyz.index(1),
*pixel_splat_xyz.index(2),
];
xyz_to_rgb(&splat_xyz, &mut splat_rgb);
rgb[start] += splat_scale * splat_rgb[0];
rgb[start + 1] += splat_scale * splat_rgb[1];
rgb[start + 2] += splat_scale * splat_rgb[2];
rgb[start] *= self.scale;
rgb[start + 1] *= self.scale;
rgb[start + 2] *= self.scale;
}
let filename = "pbrt.png";
println!(
"Writing image {:?} with bounds {:?}",
filename, self.cropped_pixel_bounds
);
let mut buffer: Vec<u8> = vec![0.0 as u8; (3 * self.cropped_pixel_bounds.area()) as usize];
let width: u32 =
(self.cropped_pixel_bounds.p_max.x - self.cropped_pixel_bounds.p_min.x) as u32;
let height: u32 =
(self.cropped_pixel_bounds.p_max.y - self.cropped_pixel_bounds.p_min.y) as u32;
for y in 0..height {
for x in 0..width {
let index: usize = (3 * (y * width + x)) as usize;
buffer[index] = clamp_t(
255.0 as Float * gamma_correct(rgb[index]) + 0.5,
0.0 as Float,
255.0 as Float,
) as u8;
let index: usize = (3 * (y * width + x) + 1) as usize;
buffer[index] = clamp_t(
255.0 as Float * gamma_correct(rgb[index]) + 0.5,
0.0 as Float,
255.0 as Float,
) as u8;
let index: usize = (3 * (y * width + x) + 2) as usize;
buffer[index] = clamp_t(
255.0 as Float * gamma_correct(rgb[index]) + 0.5,
0.0 as Float,
255.0 as Float,
) as u8;
}
}
image::save_buffer(
Path::new("pbrt.png"),
&buffer,
width,
height,
image::ColorType::Rgb8,
)
.unwrap();
}
#[cfg(feature = "openexr")]
pub fn write_image(&self, splat_scale: Float) {
let mut rgb: Vec<Float> =
vec![0.0 as Float; (3 * self.cropped_pixel_bounds.area()) as usize];
let mut exr: Vec<(Float, Float, Float)> = vec![(0.0_f32, 0.0_f32, 0.0_f32); self.cropped_pixel_bounds.area() as usize];
let mut offset;
for p in &self.cropped_pixel_bounds {
assert!(pnt2_inside_exclusivei(p, &self.cropped_pixel_bounds));
let width: i32 = self.cropped_pixel_bounds.p_max.x - self.cropped_pixel_bounds.p_min.x;
offset = ((p.x - self.cropped_pixel_bounds.p_min.x)
+ (p.y - self.cropped_pixel_bounds.p_min.y) * width) as usize;
let pixel: &Pixel = &self.pixels.read().unwrap()[offset];
let start = 3 * offset;
let mut rgb_array: [Float; 3] = [0.0 as Float; 3];
xyz_to_rgb(&pixel.xyz, &mut rgb_array); rgb[start] = rgb_array[0];
rgb[start + 1] = rgb_array[1];
rgb[start + 2] = rgb_array[2];
let filter_weight_sum: Float = pixel.filter_weight_sum;
if filter_weight_sum != 0.0 as Float {
let inv_wt: Float = 1.0 as Float / filter_weight_sum;
rgb[start] = (rgb[start] * inv_wt).max(0.0 as Float);
rgb[start + 1] = (rgb[start + 1] * inv_wt).max(0.0 as Float);
rgb[start + 2] = (rgb[start + 2] * inv_wt).max(0.0 as Float);
}
let mut splat_rgb: [Float; 3] = [0.0 as Float; 3];
let pixel_splat_xyz: &[Float; 3] = &pixel.splat_xyz;
let splat_xyz: [Float; 3] = [
*pixel_splat_xyz.index(0),
*pixel_splat_xyz.index(1),
*pixel_splat_xyz.index(2),
];
xyz_to_rgb(&splat_xyz, &mut splat_rgb);
rgb[start] += splat_scale * splat_rgb[0];
rgb[start + 1] += splat_scale * splat_rgb[1];
rgb[start + 2] += splat_scale * splat_rgb[2];
rgb[start] *= self.scale;
rgb[start + 1] *= self.scale;
rgb[start + 2] *= self.scale;
exr[offset].0 = rgb[start];
exr[offset].1 = rgb[start + 1];
exr[offset].2 = rgb[start + 2];
}
let filename = "pbrt.png";
println!(
"Writing image {:?} with bounds {:?}",
filename, self.cropped_pixel_bounds
);
let mut buffer: Vec<u8> = vec![0.0 as u8; (3 * self.cropped_pixel_bounds.area()) as usize];
let width: u32 =
(self.cropped_pixel_bounds.p_max.x - self.cropped_pixel_bounds.p_min.x) as u32;
let height: u32 =
(self.cropped_pixel_bounds.p_max.y - self.cropped_pixel_bounds.p_min.y) as u32;
let filename = "pbrt_rust.exr";
println!(
"Writing image {:?} with bounds {:?}",
filename, self.cropped_pixel_bounds
);
let mut file = std::fs::File::create("pbrt_rust.exr").unwrap();
let mut output_file = ScanlineOutputFile::new(
&mut file,
Header::new()
.set_resolution(width, height)
.add_channel("R", PixelType::FLOAT)
.add_channel("G", PixelType::FLOAT)
.add_channel("B", PixelType::FLOAT),
)
.unwrap();
let mut fb = FrameBuffer::new(width as u32, height as u32);
fb.insert_channels(&["R", "G", "B"], &exr);
output_file.write_pixels(&fb).unwrap();
for y in 0..height {
for x in 0..width {
let index: usize = (3 * (y * width + x)) as usize;
buffer[index] = clamp_t(
255.0 as Float * gamma_correct(rgb[index]) + 0.5,
0.0 as Float,
255.0 as Float,
) as u8;
let index: usize = (3 * (y * width + x) + 1) as usize;
buffer[index] = clamp_t(
255.0 as Float * gamma_correct(rgb[index]) + 0.5,
0.0 as Float,
255.0 as Float,
) as u8;
let index: usize = (3 * (y * width + x) + 2) as usize;
buffer[index] = clamp_t(
255.0 as Float * gamma_correct(rgb[index]) + 0.5,
0.0 as Float,
255.0 as Float,
) as u8;
}
}
image::save_buffer(
&Path::new("pbrt.png"),
&buffer,
width,
height,
image::ColorType::Rgb8,
)
.unwrap();
}
}