Raw-rs: Refactor to run multiple steps in a single loop (#1972)

* Prevent extra allocation in convert to RGB step

* Run preprocessing steps in a single loop

* Create new API to call steps in pipeline

* Include transform and gamma correction step

* cargo fmt

* Split scale colors into two steps

* Code relocations

* cargo fmt

* Implement transform traits for all tuples

* Replace Captures trick with the new `use` keyword
This commit is contained in:
Elbert Ronnie 2024-10-26 00:22:35 +05:30 committed by GitHub
parent a395fbf063
commit 442937c13f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 423 additions and 265 deletions

12
Cargo.lock generated
View file

@ -1821,6 +1821,17 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fortuples"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87630a8087e9cac4b7edfb6ee5e250ddca9112b57b6b17d8f5107375a3a8eace"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "futf"
version = "0.1.5"
@ -4916,6 +4927,7 @@ version = "0.0.1"
dependencies = [
"bitstream-io",
"build-camera-data",
"fortuples",
"image 0.25.2",
"libraw-rs",
"num_enum 0.7.3",

View file

@ -26,6 +26,7 @@ thiserror = { workspace = true }
# Required dependencies
bitstream-io = "2.3.0"
num_enum = "0.7.2"
fortuples = "0.9.1"
# Optional workspace dependencies
image = { workspace = true, optional = true }

View file

@ -28,8 +28,8 @@ pub fn decode_a100<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage
black: SubtractBlack::None,
transform: Transform::Horizontal,
camera_model: None,
camera_white_balance_multiplier: None,
white_balance_multiplier: None,
camera_white_balance: None,
white_balance: None,
camera_to_rgb: None,
rgb_to_camera: None,
}

View file

@ -52,8 +52,8 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
black: SubtractBlack::CfaGrid([512, 512, 512, 512]), // TODO: Find the correct way to do this
transform: Transform::Horizontal,
camera_model: None,
camera_white_balance_multiplier: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
white_balance_multiplier: None,
camera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
white_balance: None,
camera_to_rgb: None,
rgb_to_camera: None,
}

View file

@ -60,8 +60,8 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
black: SubtractBlack::CfaGrid(ifd.black_level),
transform: Transform::Horizontal,
camera_model: None,
camera_white_balance_multiplier: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
white_balance_multiplier: None,
camera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
white_balance: None,
camera_to_rgb: None,
rgb_to_camera: None,
}

View file

@ -1,4 +1,4 @@
use crate::{Image, RawImage};
use crate::{Pixel, RawImage};
fn average(data: &[u16], indexes: impl Iterator<Item = i64>) -> u16 {
let mut sum = 0;
@ -13,62 +13,69 @@ fn average(data: &[u16], indexes: impl Iterator<Item = i64>) -> u16 {
(sum / count) as u16
}
pub fn linear_demosaic(raw_image: RawImage) -> Image<u16> {
match raw_image.cfa_pattern {
[0, 1, 1, 2] => linear_demosaic_rggb(raw_image),
_ => todo!(),
}
}
fn linear_demosaic_rggb(raw_image: RawImage) -> Image<u16> {
let mut image = vec![0; raw_image.width * raw_image.height * 3];
let width = raw_image.width as i64;
let height = raw_image.height as i64;
for row in 0..height {
let row_by_width = row * width;
for col in 0..width {
let pixel_index = row_by_width + col;
let vertical_indexes = [pixel_index + width, pixel_index - width];
let horizontal_indexes = [pixel_index + 1, pixel_index - 1];
let cross_indexes = [pixel_index + width, pixel_index - width, pixel_index + 1, pixel_index - 1];
let diagonal_indexes = [pixel_index + width + 1, pixel_index - width + 1, pixel_index + width - 1, pixel_index - width - 1];
let pixel_index = pixel_index as usize;
match (row % 2 == 0, col % 2 == 0) {
(true, true) => {
image[3 * pixel_index] = raw_image.data[pixel_index];
image[3 * pixel_index + 1] = average(&raw_image.data, cross_indexes.into_iter());
image[3 * pixel_index + 2] = average(&raw_image.data, diagonal_indexes.into_iter());
}
(true, false) => {
image[3 * pixel_index] = average(&raw_image.data, horizontal_indexes.into_iter());
image[3 * pixel_index + 1] = raw_image.data[pixel_index];
image[3 * pixel_index + 2] = average(&raw_image.data, vertical_indexes.into_iter());
}
(false, true) => {
image[3 * pixel_index] = average(&raw_image.data, vertical_indexes.into_iter());
image[3 * pixel_index + 1] = raw_image.data[pixel_index];
image[3 * pixel_index + 2] = average(&raw_image.data, horizontal_indexes.into_iter());
}
(false, false) => {
image[3 * pixel_index] = average(&raw_image.data, diagonal_indexes.into_iter());
image[3 * pixel_index + 1] = average(&raw_image.data, cross_indexes.into_iter());
image[3 * pixel_index + 2] = raw_image.data[pixel_index];
}
}
impl RawImage {
pub fn linear_demosaic_iter(&self) -> impl Iterator<Item = Pixel> + use<'_> {
match self.cfa_pattern {
[0, 1, 1, 2] => self.linear_demosaic_rggb_iter(),
_ => todo!(),
}
}
Image {
channels: 3,
data: image,
width: raw_image.width,
height: raw_image.height,
transform: raw_image.transform,
rgb_to_camera: raw_image.rgb_to_camera,
histogram: None,
fn linear_demosaic_rggb_iter(&self) -> impl Iterator<Item = Pixel> + use<'_> {
let width = self.width as i64;
let height = self.height as i64;
(0..height).flat_map(move |row| {
let row_by_width = row * width;
(0..width).map(move |column| {
let pixel_index = row_by_width + column;
let vertical_indexes = [pixel_index + width, pixel_index - width];
let horizontal_indexes = [pixel_index + 1, pixel_index - 1];
let cross_indexes = [pixel_index + width, pixel_index - width, pixel_index + 1, pixel_index - 1];
let diagonal_indexes = [pixel_index + width + 1, pixel_index - width + 1, pixel_index + width - 1, pixel_index - width - 1];
let pixel_index = pixel_index as usize;
match (row % 2 == 0, column % 2 == 0) {
(true, true) => Pixel {
values: [
self.data[pixel_index],
average(&self.data, cross_indexes.into_iter()),
average(&self.data, diagonal_indexes.into_iter()),
],
row: row as usize,
column: column as usize,
},
(true, false) => Pixel {
values: [
average(&self.data, horizontal_indexes.into_iter()),
self.data[pixel_index],
average(&self.data, vertical_indexes.into_iter()),
],
row: row as usize,
column: column as usize,
},
(false, true) => Pixel {
values: [
average(&self.data, vertical_indexes.into_iter()),
self.data[pixel_index],
average(&self.data, horizontal_indexes.into_iter()),
],
row: row as usize,
column: column as usize,
},
(false, false) => Pixel {
values: [
average(&self.data, diagonal_indexes.into_iter()),
average(&self.data, cross_indexes.into_iter()),
self.data[pixel_index],
],
row: row as usize,
column: column as usize,
},
}
})
})
}
}

View file

@ -3,10 +3,12 @@ pub mod demosaicing;
pub mod metadata;
pub mod postprocessing;
pub mod preprocessing;
pub mod processing;
pub mod tiff;
use crate::metadata::identify::CameraModel;
use processing::{Pixel, PixelTransform, RawPixel, RawPixelTransform};
use tag_derive::Tag;
use tiff::file::TiffRead;
use tiff::tags::{Compression, ImageLength, ImageWidth, Orientation, StripByteCounts, SubIfd, Tag};
@ -16,6 +18,9 @@ use tiff::{Ifd, TiffError};
use std::io::{Read, Seek};
use thiserror::Error;
pub const CHANNELS_IN_RGB: usize = 3;
pub type Histogram = [[usize; 0x2000]; CHANNELS_IN_RGB];
pub enum SubtractBlack {
None,
Value(u16),
@ -31,8 +36,8 @@ pub struct RawImage {
pub maximum: u16,
pub black: SubtractBlack,
pub camera_model: Option<CameraModel>,
pub camera_white_balance_multiplier: Option<[f64; 4]>,
pub white_balance_multiplier: Option<[f64; 4]>,
pub camera_white_balance: Option<[f64; 4]>,
pub white_balance: Option<[f64; 4]>,
pub camera_to_rgb: Option<[[f64; 3]; 3]>,
pub rgb_to_camera: Option<[[f64; 3]; 3]>,
}
@ -45,8 +50,6 @@ pub struct Image<T> {
/// See <https://github.com/GraphiteEditor/Graphite/pull/1923#discussion_r1725070342> for more information.
pub channels: u8,
pub transform: Transform,
pub rgb_to_camera: Option<[[f64; 3]; 3]>,
pub(crate) histogram: Option<[[usize; 0x2000]; 3]>,
}
#[allow(dead_code)]
@ -84,6 +87,8 @@ pub fn decode<R: Read + Seek>(reader: &mut R) -> Result<RawImage, DecoderError>
raw_image.camera_model = Some(camera_model);
raw_image.transform = transform;
raw_image.calculate_conversion_matrices();
Ok(raw_image)
}
@ -96,19 +101,94 @@ pub fn process_8bit(raw_image: RawImage) -> Image<u8> {
width: image.width,
height: image.height,
transform: image.transform,
rgb_to_camera: image.rgb_to_camera,
histogram: image.histogram,
}
}
pub fn process_16bit(raw_image: RawImage) -> Image<u16> {
let raw_image = crate::preprocessing::camera_data::calculate_conversion_matrices(raw_image);
let raw_image = crate::preprocessing::subtract_black::subtract_black(raw_image);
let raw_image = crate::preprocessing::scale_colors::scale_colors(raw_image);
let image = crate::demosaicing::linear_demosaicing::linear_demosaic(raw_image);
let image = crate::postprocessing::convert_to_rgb::convert_to_rgb(image);
let image = crate::postprocessing::transform::transform(image);
crate::postprocessing::gamma_correction::gamma_correction(image)
let subtract_black = raw_image.subtract_black_fn();
let scale_white_balance = raw_image.scale_white_balance_fn();
let scale_to_16bit = raw_image.scale_to_16bit_fn();
let raw_image = raw_image.apply((subtract_black, scale_white_balance, scale_to_16bit));
let convert_to_rgb = raw_image.convert_to_rgb_fn();
let mut record_histogram = raw_image.record_histogram_fn();
let image = raw_image.demosaic_and_apply((convert_to_rgb, &mut record_histogram));
let gamma_correction = image.gamma_correction_fn(&record_histogram.histogram);
if image.transform == Transform::Horizontal {
image.apply(gamma_correction)
} else {
image.transform_and_apply(gamma_correction)
}
}
impl RawImage {
pub fn apply(mut self, mut transform: impl RawPixelTransform) -> RawImage {
for (index, value) in self.data.iter_mut().enumerate() {
let pixel = RawPixel {
value: *value,
row: index / self.width,
column: index % self.width,
};
*value = transform.apply(pixel);
}
self
}
pub fn demosaic_and_apply(self, mut transform: impl PixelTransform) -> Image<u16> {
let mut image = vec![0; self.width * self.height * 3];
for Pixel { values, row, column } in self.linear_demosaic_iter().map(|mut pixel| {
pixel.values = transform.apply(pixel);
pixel
}) {
let pixel_index = row * self.width + column;
image[3 * pixel_index..3 * (pixel_index + 1)].copy_from_slice(&values);
}
Image {
channels: 3,
data: image,
width: self.width,
height: self.height,
transform: self.transform,
}
}
}
impl Image<u16> {
pub fn apply(mut self, mut transform: impl PixelTransform) -> Image<u16> {
for (index, values) in self.data.chunks_exact_mut(3).enumerate() {
let pixel = Pixel {
values: values.try_into().unwrap(),
row: index / self.width,
column: index % self.width,
};
values.copy_from_slice(&transform.apply(pixel));
}
self
}
pub fn transform_and_apply(self, mut transform: impl PixelTransform) -> Image<u16> {
let mut image = vec![0; self.width * self.height * 3];
let (width, height, iter) = self.transform_iter();
for Pixel { values, row, column } in iter.map(|mut pixel| {
pixel.values = transform.apply(pixel);
pixel
}) {
let pixel_index = row * width + column;
image[3 * pixel_index..3 * (pixel_index + 1)].copy_from_slice(&values);
}
Image {
channels: 3,
data: image,
width,
height,
transform: Transform::Horizontal,
}
}
}
#[derive(Error, Debug)]

View file

@ -24,42 +24,42 @@ const XYZ_TO_RGB: [[f64; 3]; 3] = [
[0.019334, 0.119193, 0.950227],
];
pub fn calculate_conversion_matrices(mut raw_image: RawImage) -> RawImage {
let Some(ref camera_model) = raw_image.camera_model else { return raw_image };
let camera_name_needle = camera_model.make.to_owned() + " " + &camera_model.model;
impl RawImage {
pub fn calculate_conversion_matrices(&mut self) {
let Some(ref camera_model) = self.camera_model else { return };
let camera_name_needle = camera_model.make.to_owned() + " " + &camera_model.model;
let camera_to_xyz = CAMERA_DATA
.iter()
.find(|(camera_name_haystack, _)| camera_name_needle == *camera_name_haystack)
.map(|(_, data)| data.camera_to_xyz.map(|x| (x as f64) / 10_000.));
let Some(camera_to_xyz) = camera_to_xyz else { return raw_image };
let camera_to_xyz = CAMERA_DATA
.iter()
.find(|(camera_name_haystack, _)| camera_name_needle == *camera_name_haystack)
.map(|(_, data)| data.camera_to_xyz.map(|x| (x as f64) / 10_000.));
let Some(camera_to_xyz) = camera_to_xyz else { return };
let mut camera_to_rgb = [[0.; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
camera_to_rgb[i][j] += camera_to_xyz[i * 3 + k] * XYZ_TO_RGB[k][j];
let mut camera_to_rgb = [[0.; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
camera_to_rgb[i][j] += camera_to_xyz[i * 3 + k] * XYZ_TO_RGB[k][j];
}
}
}
let white_balance_multiplier = camera_to_rgb.map(|x| 1. / x.iter().sum::<f64>());
for (index, row) in camera_to_rgb.iter_mut().enumerate() {
*row = row.map(|x| x * white_balance_multiplier[index]);
}
let rgb_to_camera = transpose(pseudoinverse(camera_to_rgb));
let cfa_white_balance_multiplier = if let Some(white_balance) = self.camera_white_balance {
white_balance
} else {
self.cfa_pattern.map(|index| white_balance_multiplier[index as usize])
};
self.white_balance = Some(cfa_white_balance_multiplier);
self.camera_to_rgb = Some(camera_to_rgb);
self.rgb_to_camera = Some(rgb_to_camera);
}
let white_balance_multiplier = camera_to_rgb.map(|x| 1. / x.iter().sum::<f64>());
for (index, row) in camera_to_rgb.iter_mut().enumerate() {
*row = row.map(|x| x * white_balance_multiplier[index]);
}
let rgb_to_camera = transpose(pseudoinverse(camera_to_rgb));
let cfa_white_balance_multiplier = if let Some(white_balance) = raw_image.camera_white_balance_multiplier {
white_balance
} else {
raw_image.cfa_pattern.map(|index| white_balance_multiplier[index as usize])
};
raw_image.white_balance_multiplier = Some(cfa_white_balance_multiplier);
raw_image.camera_to_rgb = Some(camera_to_rgb);
raw_image.rgb_to_camera = Some(rgb_to_camera);
raw_image
}
#[allow(clippy::needless_range_loop)]

View file

@ -1 +1,2 @@
pub mod camera_data;
pub mod identify;

View file

@ -1,40 +1,13 @@
use crate::Image;
use crate::{Pixel, RawImage, CHANNELS_IN_RGB};
const CHANNELS_IN_RGB: usize = 3;
impl RawImage {
pub fn convert_to_rgb_fn(&self) -> impl Fn(Pixel) -> [u16; CHANNELS_IN_RGB] {
let Some(rgb_to_camera) = self.rgb_to_camera else { todo!() };
pub fn convert_to_rgb(mut image: Image<u16>) -> Image<u16> {
let Some(rgb_to_camera) = image.rgb_to_camera else { return image };
// Rarely this might be 4 instead of 3 if an obscure Bayer filter is used, such as RGBE or CYGM, instead of the typical RGGB.
// See: <https://github.com/GraphiteEditor/Graphite/pull/1923#discussion_r1725070342>.
let channels = image.channels as usize;
let mut data = Vec::with_capacity(CHANNELS_IN_RGB * image.width * image.height);
let mut histogram = [[0; 0x2000]; CHANNELS_IN_RGB];
for i in 0..(image.height * image.width) {
let start = i * channels;
let end = start + channels;
let input_pixel = &mut image.data[start..end];
let mut output_pixel = [0.; CHANNELS_IN_RGB];
for (channel, &value) in input_pixel.iter().enumerate() {
output_pixel[0] += rgb_to_camera[0][channel] * value as f64;
output_pixel[1] += rgb_to_camera[1][channel] * value as f64;
output_pixel[2] += rgb_to_camera[2][channel] * value as f64;
}
for (output_pixel_channel, histogram_channel) in output_pixel.iter().zip(histogram.iter_mut()) {
let final_sum = (*output_pixel_channel as u16).clamp(0, u16::MAX);
histogram_channel[final_sum as usize >> CHANNELS_IN_RGB] += 1;
data.push(final_sum);
move |pixel: Pixel| {
std::array::from_fn(|i| i)
.map(|i| rgb_to_camera[i].iter().zip(pixel.values.iter()).map(|(&coeff, &value)| coeff * value as f64).sum())
.map(|x: f64| (x as u16).clamp(0, u16::MAX))
}
}
image.data = data;
image.histogram = Some(histogram);
image.channels = CHANNELS_IN_RGB as u8;
image
}

View file

@ -1,32 +1,27 @@
use crate::Image;
use crate::{Histogram, Image, Pixel, CHANNELS_IN_RGB};
use std::f64::consts::E;
pub fn gamma_correction(mut image: Image<u16>) -> Image<u16> {
let Some(histogram) = image.histogram else { return image };
impl Image<u16> {
pub fn gamma_correction_fn(&self, histogram: &Histogram) -> impl Fn(Pixel) -> [u16; CHANNELS_IN_RGB] {
let percentage = self.width * self.height;
let percentage = image.width * image.height;
let mut white = 0;
for channel_histogram in histogram {
let mut total = 0;
for i in (0x20..0x2000).rev() {
total += channel_histogram[i] as u64;
let mut white = 0;
for channel_histogram in histogram {
let mut total = 0;
for i in (0x20..0x2000).rev() {
total += channel_histogram[i] as u64;
if total * 100 > percentage as u64 {
white = white.max(i);
break;
if total * 100 > percentage as u64 {
white = white.max(i);
break;
}
}
}
let curve = generate_gamma_curve(0.45, 4.5, (white << 3) as f64);
move |pixel: Pixel| pixel.values.map(|value| curve[value as usize])
}
let curve = generate_gamma_curve(0.45, 4.5, (white << 3) as f64);
for value in image.data.iter_mut() {
*value = curve[*value as usize];
}
image.histogram = None;
image
}
/// `max_intensity` must be non-zero.

View file

@ -1,3 +1,4 @@
pub mod convert_to_rgb;
pub mod gamma_correction;
pub mod record_histogram;
pub mod transform;

View file

@ -0,0 +1,29 @@
use crate::{Histogram, Pixel, PixelTransform, RawImage, CHANNELS_IN_RGB};
impl RawImage {
pub fn record_histogram_fn(&self) -> RecordHistogram {
RecordHistogram::new()
}
}
pub struct RecordHistogram {
pub histogram: Histogram,
}
impl RecordHistogram {
fn new() -> RecordHistogram {
RecordHistogram {
histogram: [[0; 0x2000]; CHANNELS_IN_RGB],
}
}
}
impl PixelTransform for &mut RecordHistogram {
fn apply(&mut self, pixel: Pixel) -> [u16; CHANNELS_IN_RGB] {
self.histogram
.iter_mut()
.zip(pixel.values.iter())
.for_each(|(histogram, &value)| histogram[value as usize >> CHANNELS_IN_RGB] += 1);
pixel.values
}
}

View file

@ -1,45 +1,48 @@
use crate::Image;
use crate::Transform;
use crate::{Image, Pixel, Transform};
pub fn transform(mut image: Image<u16>) -> Image<u16> {
if image.transform.is_identity() {
return image;
impl Image<u16> {
pub fn transform_iter(&self) -> (usize, usize, impl Iterator<Item = Pixel> + use<'_>) {
let (final_width, final_height) = if self.transform.will_swap_coordinates() {
(self.height, self.width)
} else {
(self.width, self.height)
};
let index_0_0 = inverse_transform_index(self.transform, 0, 0, self.width, self.height);
let index_0_1 = inverse_transform_index(self.transform, 0, 1, self.width, self.height);
let index_1_0 = inverse_transform_index(self.transform, 1, 0, self.width, self.height);
let column_step = (index_0_1.0 - index_0_0.0, index_0_1.1 - index_0_0.1);
let row_step = (index_1_0.0 - index_0_0.0, index_1_0.1 - index_0_0.1);
let mut index = index_0_0;
let channels = self.channels as usize;
(
final_width,
final_height,
(0..final_height).flat_map(move |row| {
let temp = (0..final_width).map(move |column| {
let initial_index = (self.width as i64 * index.0 + index.1) as usize;
let pixel = &self.data[channels * initial_index..channels * (initial_index + 1)];
index = (index.0 + column_step.0, index.1 + column_step.1);
Pixel {
values: pixel.try_into().unwrap(),
row,
column,
}
});
index = (index.0 + row_step.0, index.1 + row_step.1);
temp
}),
)
}
let channels = image.channels as usize;
let mut data = vec![0; channels * image.width * image.height];
let (final_width, final_height) = if image.transform.will_swap_coordinates() {
(image.height, image.width)
} else {
(image.width, image.height)
};
let mut initial_index = inverse_transform_index(image.transform, 0, 0, image.width, image.height);
let column_step = inverse_transform_index(image.transform, 0, 1, image.width, image.height) as i64 - initial_index as i64;
let row_step = inverse_transform_index(image.transform, 1, 0, image.width, image.height) as i64 - inverse_transform_index(image.transform, 0, final_width, image.width, image.height) as i64;
for row in 0..final_height {
for col in 0..final_width {
let transformed_index = final_width * row + col;
let copy_from_range = channels * initial_index..channels * (initial_index + 1);
let copy_to_range = channels * transformed_index..channels * (transformed_index + 1);
data[copy_to_range].copy_from_slice(&image.data[copy_from_range]);
initial_index = (initial_index as i64 + column_step) as usize;
}
initial_index = (initial_index as i64 + row_step) as usize;
}
image.data = data;
image.width = final_width;
image.height = final_height;
image
}
pub fn inverse_transform_index(transform: Transform, mut row: usize, mut column: usize, width: usize, height: usize) -> usize {
pub fn inverse_transform_index(transform: Transform, mut row: usize, mut column: usize, width: usize, height: usize) -> (i64, i64) {
let value = match transform {
Transform::Horizontal => 0,
Transform::MirrorHorizontal => 1,
@ -63,5 +66,5 @@ pub fn inverse_transform_index(transform: Transform, mut row: usize, mut column:
column = width - 1 - column;
}
width * row + column
(row as i64, column as i64)
}

View file

@ -1,3 +1,3 @@
pub mod camera_data;
pub mod scale_colors;
pub mod scale_to_16bit;
pub mod scale_white_balance;
pub mod subtract_black;

View file

@ -1,37 +0,0 @@
use crate::RawImage;
pub fn scale_colors(mut raw_image: RawImage) -> RawImage {
let Some(mut white_balance_multiplier) = raw_image.white_balance_multiplier else {
return raw_image;
};
if white_balance_multiplier[1] == 0. {
white_balance_multiplier[1] = 1.;
}
// TODO: Move this at its correct location when highlights are implemented correctly.
let highlight = 0;
let normalize_white_balance = if highlight == 0 {
white_balance_multiplier.iter().copied().fold(f64::INFINITY, f64::min)
} else {
white_balance_multiplier.iter().copied().fold(f64::NEG_INFINITY, f64::max)
};
let final_multiplier = if normalize_white_balance > 0.00001 && raw_image.maximum > 0 {
let scale_to_16bit_multiplier = u16::MAX as f64 / raw_image.maximum as f64;
white_balance_multiplier.map(|x| x / normalize_white_balance * scale_to_16bit_multiplier)
} else {
[1., 1., 1., 1.]
};
for row in 0..raw_image.height {
for column in 0..raw_image.width {
let index = row * raw_image.width + column;
let cfa_index = 2 * (row % 2) + (column % 2);
raw_image.data[index] = ((raw_image.data[index] as f64) * final_multiplier[cfa_index]).min(u16::MAX as f64).max(0.) as u16;
}
}
raw_image
}

View file

@ -0,0 +1,15 @@
use crate::{RawImage, RawPixel, SubtractBlack};
impl RawImage {
pub fn scale_to_16bit_fn(&self) -> impl Fn(RawPixel) -> u16 {
let black_level = match self.black {
SubtractBlack::CfaGrid(x) => x,
_ => unreachable!(),
};
let maximum = self.maximum - black_level.iter().max().unwrap();
let scale_to_16bit_multiplier = if maximum > 0 { u16::MAX as f64 / maximum as f64 } else { 1. };
move |pixel: RawPixel| ((pixel.value as f64) * scale_to_16bit_multiplier).min(u16::MAX as f64).max(0.) as u16
}
}

View file

@ -0,0 +1,31 @@
use crate::{RawImage, RawPixel};
impl RawImage {
pub fn scale_white_balance_fn(&self) -> impl Fn(RawPixel) -> u16 {
let Some(mut white_balance) = self.white_balance else { todo!() };
if white_balance[1] == 0. {
white_balance[1] = 1.;
}
// TODO: Move this at its correct location when highlights are implemented correctly.
let highlight = 0;
let normalization_factor = if highlight == 0 {
white_balance.into_iter().fold(f64::INFINITY, f64::min)
} else {
white_balance.into_iter().fold(f64::NEG_INFINITY, f64::max)
};
let normalized_white_balance = if normalization_factor > 0.00001 {
white_balance.map(|x| x / normalization_factor)
} else {
[1., 1., 1., 1.]
};
move |pixel: RawPixel| {
let cfa_index = 2 * (pixel.row % 2) + (pixel.column % 2);
((pixel.value as f64) * normalized_white_balance[cfa_index]).min(u16::MAX as f64).max(0.) as u16
}
}
}

View file

@ -1,30 +1,11 @@
use crate::RawPixel;
use crate::{RawImage, SubtractBlack};
pub fn subtract_black(raw_image: RawImage) -> RawImage {
let mut raw_image = match raw_image.black {
SubtractBlack::None => raw_image,
SubtractBlack::Value(_) => todo!(),
SubtractBlack::CfaGrid(_) => subtract_black_cfa_grid(raw_image),
};
raw_image.black = SubtractBlack::None;
raw_image
}
pub fn subtract_black_cfa_grid(mut raw_image: RawImage) -> RawImage {
let width = raw_image.width;
let black_level = match raw_image.black {
SubtractBlack::CfaGrid(x) => x,
_ => unreachable!(),
};
for row in 0..raw_image.height {
for col in 0..width {
raw_image.data[row * width + col] = raw_image.data[row * width + col].saturating_sub(black_level[2 * (row % 2) + (col % 2)]);
impl RawImage {
pub fn subtract_black_fn(&self) -> impl Fn(RawPixel) -> u16 {
match self.black {
SubtractBlack::CfaGrid(black_levels) => move |pixel: RawPixel| pixel.value.saturating_sub(black_levels[2 * (pixel.row % 2) + (pixel.column % 2)]),
_ => todo!(),
}
}
raw_image.maximum -= black_level.iter().max().unwrap();
raw_image
}

View file

@ -0,0 +1,66 @@
use crate::CHANNELS_IN_RGB;
use fortuples::fortuples;
#[derive(Clone, Copy)]
pub struct RawPixel {
pub value: u16,
pub row: usize,
pub column: usize,
}
#[derive(Clone, Copy)]
pub struct Pixel {
pub values: [u16; CHANNELS_IN_RGB],
pub row: usize,
pub column: usize,
}
pub trait RawPixelTransform {
fn apply(&mut self, pixel: RawPixel) -> u16;
}
impl<T: Fn(RawPixel) -> u16> RawPixelTransform for T {
fn apply(&mut self, pixel: RawPixel) -> u16 {
self(pixel)
}
}
fortuples! {
#[tuples::min_size(1)]
#[tuples::max_size(8)]
impl RawPixelTransform for #Tuple
where
#(#Member: RawPixelTransform),*
{
fn apply(&mut self, mut pixel: RawPixel) -> u16 {
#(pixel.value = #self.apply(pixel);)*
pixel.value
}
}
}
pub trait PixelTransform {
fn apply(&mut self, pixel: Pixel) -> [u16; CHANNELS_IN_RGB];
}
impl<T: Fn(Pixel) -> [u16; CHANNELS_IN_RGB]> PixelTransform for T {
fn apply(&mut self, pixel: Pixel) -> [u16; CHANNELS_IN_RGB] {
self(pixel)
}
}
fortuples! {
#[tuples::min_size(1)]
#[tuples::max_size(8)]
impl PixelTransform for #Tuple
where
#(#Member: PixelTransform),*
{
fn apply(&mut self, mut pixel: Pixel) -> [u16; CHANNELS_IN_RGB] {
#(pixel.values = #self.apply(pixel);)*
pixel.values
}
}
}