diff --git a/Cargo.lock b/Cargo.lock index c7f1a5a239..61b1892e1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/libraries/raw-rs/Cargo.toml b/libraries/raw-rs/Cargo.toml index f7f3dac029..89c14721a6 100644 --- a/libraries/raw-rs/Cargo.toml +++ b/libraries/raw-rs/Cargo.toml @@ -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 } diff --git a/libraries/raw-rs/src/decoder/arw1.rs b/libraries/raw-rs/src/decoder/arw1.rs index 03ca47a279..ae0cf621b4 100644 --- a/libraries/raw-rs/src/decoder/arw1.rs +++ b/libraries/raw-rs/src/decoder/arw1.rs @@ -28,8 +28,8 @@ pub fn decode_a100(ifd: Ifd, file: &mut TiffRead) -> 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, } diff --git a/libraries/raw-rs/src/decoder/arw2.rs b/libraries/raw-rs/src/decoder/arw2.rs index 8ee393462b..a25707345c 100644 --- a/libraries/raw-rs/src/decoder/arw2.rs +++ b/libraries/raw-rs/src/decoder/arw2.rs @@ -52,8 +52,8 @@ pub fn decode(ifd: Ifd, file: &mut TiffRead) -> 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, } diff --git a/libraries/raw-rs/src/decoder/uncompressed.rs b/libraries/raw-rs/src/decoder/uncompressed.rs index e28427875d..049359b853 100644 --- a/libraries/raw-rs/src/decoder/uncompressed.rs +++ b/libraries/raw-rs/src/decoder/uncompressed.rs @@ -60,8 +60,8 @@ pub fn decode(ifd: Ifd, file: &mut TiffRead) -> 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, } diff --git a/libraries/raw-rs/src/demosaicing/linear_demosaicing.rs b/libraries/raw-rs/src/demosaicing/linear_demosaicing.rs index 4f60c37850..cd96b1b8bb 100644 --- a/libraries/raw-rs/src/demosaicing/linear_demosaicing.rs +++ b/libraries/raw-rs/src/demosaicing/linear_demosaicing.rs @@ -1,4 +1,4 @@ -use crate::{Image, RawImage}; +use crate::{Pixel, RawImage}; fn average(data: &[u16], indexes: impl Iterator) -> u16 { let mut sum = 0; @@ -13,62 +13,69 @@ fn average(data: &[u16], indexes: impl Iterator) -> u16 { (sum / count) as u16 } -pub fn linear_demosaic(raw_image: RawImage) -> Image { - match raw_image.cfa_pattern { - [0, 1, 1, 2] => linear_demosaic_rggb(raw_image), - _ => todo!(), +impl RawImage { + pub fn linear_demosaic_iter(&self) -> impl Iterator + use<'_> { + match self.cfa_pattern { + [0, 1, 1, 2] => self.linear_demosaic_rggb_iter(), + _ => todo!(), + } } -} -fn linear_demosaic_rggb(raw_image: RawImage) -> Image { - 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; + fn linear_demosaic_rggb_iter(&self) -> impl Iterator + use<'_> { + let width = self.width as i64; + let height = self.height as i64; - for row in 0..height { - let row_by_width = row * width; + (0..height).flat_map(move |row| { + let row_by_width = row * width; - for col in 0..width { - let pixel_index = row_by_width + col; + (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 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()); + 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, + }, } - (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]; - } - } - } - } - - 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, + }) + }) } } diff --git a/libraries/raw-rs/src/lib.rs b/libraries/raw-rs/src/lib.rs index 5d7fc7b8cc..cdada33904 100644 --- a/libraries/raw-rs/src/lib.rs +++ b/libraries/raw-rs/src/lib.rs @@ -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, - 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 { /// See 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(reader: &mut R) -> Result 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 { 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 { - 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 { + 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 { + pub fn apply(mut self, mut transform: impl PixelTransform) -> Image { + 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 { + 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)] diff --git a/libraries/raw-rs/src/preprocessing/camera_data.rs b/libraries/raw-rs/src/metadata/camera_data.rs similarity index 53% rename from libraries/raw-rs/src/preprocessing/camera_data.rs rename to libraries/raw-rs/src/metadata/camera_data.rs index da1d077278..f60e7f4e9c 100644 --- a/libraries/raw-rs/src/preprocessing/camera_data.rs +++ b/libraries/raw-rs/src/metadata/camera_data.rs @@ -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; - - 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 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]; +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 }; + + 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::()); - 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]) - }; + let white_balance_multiplier = camera_to_rgb.map(|x| 1. / x.iter().sum::()); + 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)); - 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); + 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]) + }; - raw_image + self.white_balance = Some(cfa_white_balance_multiplier); + self.camera_to_rgb = Some(camera_to_rgb); + self.rgb_to_camera = Some(rgb_to_camera); + } } #[allow(clippy::needless_range_loop)] diff --git a/libraries/raw-rs/src/metadata/mod.rs b/libraries/raw-rs/src/metadata/mod.rs index 5529e6b304..e66de3a721 100644 --- a/libraries/raw-rs/src/metadata/mod.rs +++ b/libraries/raw-rs/src/metadata/mod.rs @@ -1 +1,2 @@ +pub mod camera_data; pub mod identify; diff --git a/libraries/raw-rs/src/postprocessing/convert_to_rgb.rs b/libraries/raw-rs/src/postprocessing/convert_to_rgb.rs index 86b1b94206..196e808141 100644 --- a/libraries/raw-rs/src/postprocessing/convert_to_rgb.rs +++ b/libraries/raw-rs/src/postprocessing/convert_to_rgb.rs @@ -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) -> Image { - 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: . - 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 } diff --git a/libraries/raw-rs/src/postprocessing/gamma_correction.rs b/libraries/raw-rs/src/postprocessing/gamma_correction.rs index e3b98b2174..99110602c8 100644 --- a/libraries/raw-rs/src/postprocessing/gamma_correction.rs +++ b/libraries/raw-rs/src/postprocessing/gamma_correction.rs @@ -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) -> Image { - let Some(histogram) = image.histogram else { return image }; +impl Image { + 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); + 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]; + move |pixel: Pixel| pixel.values.map(|value| curve[value as usize]) } - image.histogram = None; - - image } /// `max_intensity` must be non-zero. diff --git a/libraries/raw-rs/src/postprocessing/mod.rs b/libraries/raw-rs/src/postprocessing/mod.rs index d2227226d3..226cbf7f9d 100644 --- a/libraries/raw-rs/src/postprocessing/mod.rs +++ b/libraries/raw-rs/src/postprocessing/mod.rs @@ -1,3 +1,4 @@ pub mod convert_to_rgb; pub mod gamma_correction; +pub mod record_histogram; pub mod transform; diff --git a/libraries/raw-rs/src/postprocessing/record_histogram.rs b/libraries/raw-rs/src/postprocessing/record_histogram.rs new file mode 100644 index 0000000000..d484b7bd5b --- /dev/null +++ b/libraries/raw-rs/src/postprocessing/record_histogram.rs @@ -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 + } +} diff --git a/libraries/raw-rs/src/postprocessing/transform.rs b/libraries/raw-rs/src/postprocessing/transform.rs index 45b24593f5..5ffe495712 100644 --- a/libraries/raw-rs/src/postprocessing/transform.rs +++ b/libraries/raw-rs/src/postprocessing/transform.rs @@ -1,45 +1,48 @@ -use crate::Image; -use crate::Transform; +use crate::{Image, Pixel, Transform}; -pub fn transform(mut image: Image) -> Image { - if image.transform.is_identity() { - return image; - } +impl Image { + pub fn transform_iter(&self) -> (usize, usize, impl Iterator + use<'_>) { + let (final_width, final_height) = if self.transform.will_swap_coordinates() { + (self.height, self.width) + } else { + (self.width, self.height) + }; - let channels = image.channels as usize; - let mut data = vec![0; channels * image.width * image.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 (final_width, final_height) = if image.transform.will_swap_coordinates() { - (image.height, image.width) - } else { - (image.width, image.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 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; + let channels = self.channels as usize; - for row in 0..final_height { - for col in 0..final_width { - let transformed_index = final_width * row + col; + ( + 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); - 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]); + Pixel { + values: pixel.try_into().unwrap(), + row, + column, + } + }); - initial_index = (initial_index as i64 + column_step) as usize; - } - initial_index = (initial_index as i64 + row_step) as usize; - } + index = (index.0 + row_step.0, index.1 + row_step.1); - image.data = data; - image.width = final_width; - image.height = final_height; - - image + temp + }), + ) + } } -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) } diff --git a/libraries/raw-rs/src/preprocessing/mod.rs b/libraries/raw-rs/src/preprocessing/mod.rs index c0cfbce14a..a600787272 100644 --- a/libraries/raw-rs/src/preprocessing/mod.rs +++ b/libraries/raw-rs/src/preprocessing/mod.rs @@ -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; diff --git a/libraries/raw-rs/src/preprocessing/scale_colors.rs b/libraries/raw-rs/src/preprocessing/scale_colors.rs deleted file mode 100644 index c839f95b4c..0000000000 --- a/libraries/raw-rs/src/preprocessing/scale_colors.rs +++ /dev/null @@ -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 -} diff --git a/libraries/raw-rs/src/preprocessing/scale_to_16bit.rs b/libraries/raw-rs/src/preprocessing/scale_to_16bit.rs new file mode 100644 index 0000000000..7f7ad87cdf --- /dev/null +++ b/libraries/raw-rs/src/preprocessing/scale_to_16bit.rs @@ -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 + } +} diff --git a/libraries/raw-rs/src/preprocessing/scale_white_balance.rs b/libraries/raw-rs/src/preprocessing/scale_white_balance.rs new file mode 100644 index 0000000000..f81660a7ce --- /dev/null +++ b/libraries/raw-rs/src/preprocessing/scale_white_balance.rs @@ -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 + } + } +} diff --git a/libraries/raw-rs/src/preprocessing/subtract_black.rs b/libraries/raw-rs/src/preprocessing/subtract_black.rs index 94f2bfde6c..b4e368eb3b 100644 --- a/libraries/raw-rs/src/preprocessing/subtract_black.rs +++ b/libraries/raw-rs/src/preprocessing/subtract_black.rs @@ -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 } diff --git a/libraries/raw-rs/src/processing.rs b/libraries/raw-rs/src/processing.rs new file mode 100644 index 0000000000..6ba4dba54d --- /dev/null +++ b/libraries/raw-rs/src/processing.rs @@ -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 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 [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 + } + } +}