mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
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:
parent
a395fbf063
commit
442937c13f
20 changed files with 423 additions and 265 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
|
@ -1 +1,2 @@
|
|||
pub mod camera_data;
|
||||
pub mod identify;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod convert_to_rgb;
|
||||
pub mod gamma_correction;
|
||||
pub mod record_histogram;
|
||||
pub mod transform;
|
||||
|
|
29
libraries/raw-rs/src/postprocessing/record_histogram.rs
Normal file
29
libraries/raw-rs/src/postprocessing/record_histogram.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
15
libraries/raw-rs/src/preprocessing/scale_to_16bit.rs
Normal file
15
libraries/raw-rs/src/preprocessing/scale_to_16bit.rs
Normal 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
|
||||
}
|
||||
}
|
31
libraries/raw-rs/src/preprocessing/scale_white_balance.rs
Normal file
31
libraries/raw-rs/src/preprocessing/scale_white_balance.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
66
libraries/raw-rs/src/processing.rs
Normal file
66
libraries/raw-rs/src/processing.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue