Skip to content

Commit 442937c

Browse files
authored
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
1 parent a395fbf commit 442937c

File tree

20 files changed

+411
-253
lines changed

20 files changed

+411
-253
lines changed

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libraries/raw-rs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ thiserror = { workspace = true }
2626
# Required dependencies
2727
bitstream-io = "2.3.0"
2828
num_enum = "0.7.2"
29+
fortuples = "0.9.1"
2930

3031
# Optional workspace dependencies
3132
image = { workspace = true, optional = true }

libraries/raw-rs/src/decoder/arw1.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ pub fn decode_a100<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage
2828
black: SubtractBlack::None,
2929
transform: Transform::Horizontal,
3030
camera_model: None,
31-
camera_white_balance_multiplier: None,
32-
white_balance_multiplier: None,
31+
camera_white_balance: None,
32+
white_balance: None,
3333
camera_to_rgb: None,
3434
rgb_to_camera: None,
3535
}

libraries/raw-rs/src/decoder/arw2.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
5252
black: SubtractBlack::CfaGrid([512, 512, 512, 512]), // TODO: Find the correct way to do this
5353
transform: Transform::Horizontal,
5454
camera_model: None,
55-
camera_white_balance_multiplier: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
56-
white_balance_multiplier: None,
55+
camera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
56+
white_balance: None,
5757
camera_to_rgb: None,
5858
rgb_to_camera: None,
5959
}

libraries/raw-rs/src/decoder/uncompressed.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ pub fn decode<R: Read + Seek>(ifd: Ifd, file: &mut TiffRead<R>) -> RawImage {
6060
black: SubtractBlack::CfaGrid(ifd.black_level),
6161
transform: Transform::Horizontal,
6262
camera_model: None,
63-
camera_white_balance_multiplier: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
64-
white_balance_multiplier: None,
63+
camera_white_balance: ifd.white_balance_levels.map(|arr| arr.map(|x| x as f64)),
64+
white_balance: None,
6565
camera_to_rgb: None,
6666
rgb_to_camera: None,
6767
}
Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{Image, RawImage};
1+
use crate::{Pixel, RawImage};
22

33
fn average(data: &[u16], indexes: impl Iterator<Item = i64>) -> u16 {
44
let mut sum = 0;
@@ -13,62 +13,69 @@ fn average(data: &[u16], indexes: impl Iterator<Item = i64>) -> u16 {
1313
(sum / count) as u16
1414
}
1515

16-
pub fn linear_demosaic(raw_image: RawImage) -> Image<u16> {
17-
match raw_image.cfa_pattern {
18-
[0, 1, 1, 2] => linear_demosaic_rggb(raw_image),
19-
_ => todo!(),
16+
impl RawImage {
17+
pub fn linear_demosaic_iter(&self) -> impl Iterator<Item = Pixel> + use<'_> {
18+
match self.cfa_pattern {
19+
[0, 1, 1, 2] => self.linear_demosaic_rggb_iter(),
20+
_ => todo!(),
21+
}
2022
}
21-
}
2223

23-
fn linear_demosaic_rggb(raw_image: RawImage) -> Image<u16> {
24-
let mut image = vec![0; raw_image.width * raw_image.height * 3];
25-
let width = raw_image.width as i64;
26-
let height = raw_image.height as i64;
24+
fn linear_demosaic_rggb_iter(&self) -> impl Iterator<Item = Pixel> + use<'_> {
25+
let width = self.width as i64;
26+
let height = self.height as i64;
2727

28-
for row in 0..height {
29-
let row_by_width = row * width;
28+
(0..height).flat_map(move |row| {
29+
let row_by_width = row * width;
3030

31-
for col in 0..width {
32-
let pixel_index = row_by_width + col;
31+
(0..width).map(move |column| {
32+
let pixel_index = row_by_width + column;
3333

34-
let vertical_indexes = [pixel_index + width, pixel_index - width];
35-
let horizontal_indexes = [pixel_index + 1, pixel_index - 1];
36-
let cross_indexes = [pixel_index + width, pixel_index - width, pixel_index + 1, pixel_index - 1];
37-
let diagonal_indexes = [pixel_index + width + 1, pixel_index - width + 1, pixel_index + width - 1, pixel_index - width - 1];
34+
let vertical_indexes = [pixel_index + width, pixel_index - width];
35+
let horizontal_indexes = [pixel_index + 1, pixel_index - 1];
36+
let cross_indexes = [pixel_index + width, pixel_index - width, pixel_index + 1, pixel_index - 1];
37+
let diagonal_indexes = [pixel_index + width + 1, pixel_index - width + 1, pixel_index + width - 1, pixel_index - width - 1];
3838

39-
let pixel_index = pixel_index as usize;
40-
match (row % 2 == 0, col % 2 == 0) {
41-
(true, true) => {
42-
image[3 * pixel_index] = raw_image.data[pixel_index];
43-
image[3 * pixel_index + 1] = average(&raw_image.data, cross_indexes.into_iter());
44-
image[3 * pixel_index + 2] = average(&raw_image.data, diagonal_indexes.into_iter());
45-
}
46-
(true, false) => {
47-
image[3 * pixel_index] = average(&raw_image.data, horizontal_indexes.into_iter());
48-
image[3 * pixel_index + 1] = raw_image.data[pixel_index];
49-
image[3 * pixel_index + 2] = average(&raw_image.data, vertical_indexes.into_iter());
50-
}
51-
(false, true) => {
52-
image[3 * pixel_index] = average(&raw_image.data, vertical_indexes.into_iter());
53-
image[3 * pixel_index + 1] = raw_image.data[pixel_index];
54-
image[3 * pixel_index + 2] = average(&raw_image.data, horizontal_indexes.into_iter());
39+
let pixel_index = pixel_index as usize;
40+
match (row % 2 == 0, column % 2 == 0) {
41+
(true, true) => Pixel {
42+
values: [
43+
self.data[pixel_index],
44+
average(&self.data, cross_indexes.into_iter()),
45+
average(&self.data, diagonal_indexes.into_iter()),
46+
],
47+
row: row as usize,
48+
column: column as usize,
49+
},
50+
(true, false) => Pixel {
51+
values: [
52+
average(&self.data, horizontal_indexes.into_iter()),
53+
self.data[pixel_index],
54+
average(&self.data, vertical_indexes.into_iter()),
55+
],
56+
row: row as usize,
57+
column: column as usize,
58+
},
59+
(false, true) => Pixel {
60+
values: [
61+
average(&self.data, vertical_indexes.into_iter()),
62+
self.data[pixel_index],
63+
average(&self.data, horizontal_indexes.into_iter()),
64+
],
65+
row: row as usize,
66+
column: column as usize,
67+
},
68+
(false, false) => Pixel {
69+
values: [
70+
average(&self.data, diagonal_indexes.into_iter()),
71+
average(&self.data, cross_indexes.into_iter()),
72+
self.data[pixel_index],
73+
],
74+
row: row as usize,
75+
column: column as usize,
76+
},
5577
}
56-
(false, false) => {
57-
image[3 * pixel_index] = average(&raw_image.data, diagonal_indexes.into_iter());
58-
image[3 * pixel_index + 1] = average(&raw_image.data, cross_indexes.into_iter());
59-
image[3 * pixel_index + 2] = raw_image.data[pixel_index];
60-
}
61-
}
62-
}
63-
}
64-
65-
Image {
66-
channels: 3,
67-
data: image,
68-
width: raw_image.width,
69-
height: raw_image.height,
70-
transform: raw_image.transform,
71-
rgb_to_camera: raw_image.rgb_to_camera,
72-
histogram: None,
78+
})
79+
})
7380
}
7481
}

libraries/raw-rs/src/lib.rs

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ pub mod demosaicing;
33
pub mod metadata;
44
pub mod postprocessing;
55
pub mod preprocessing;
6+
pub mod processing;
67
pub mod tiff;
78

89
use crate::metadata::identify::CameraModel;
910

11+
use processing::{Pixel, PixelTransform, RawPixel, RawPixelTransform};
1012
use tag_derive::Tag;
1113
use tiff::file::TiffRead;
1214
use tiff::tags::{Compression, ImageLength, ImageWidth, Orientation, StripByteCounts, SubIfd, Tag};
@@ -16,6 +18,9 @@ use tiff::{Ifd, TiffError};
1618
use std::io::{Read, Seek};
1719
use thiserror::Error;
1820

21+
pub const CHANNELS_IN_RGB: usize = 3;
22+
pub type Histogram = [[usize; 0x2000]; CHANNELS_IN_RGB];
23+
1924
pub enum SubtractBlack {
2025
None,
2126
Value(u16),
@@ -31,8 +36,8 @@ pub struct RawImage {
3136
pub maximum: u16,
3237
pub black: SubtractBlack,
3338
pub camera_model: Option<CameraModel>,
34-
pub camera_white_balance_multiplier: Option<[f64; 4]>,
35-
pub white_balance_multiplier: Option<[f64; 4]>,
39+
pub camera_white_balance: Option<[f64; 4]>,
40+
pub white_balance: Option<[f64; 4]>,
3641
pub camera_to_rgb: Option<[[f64; 3]; 3]>,
3742
pub rgb_to_camera: Option<[[f64; 3]; 3]>,
3843
}
@@ -45,8 +50,6 @@ pub struct Image<T> {
4550
/// See <https://github.com/GraphiteEditor/Graphite/pull/1923#discussion_r1725070342> for more information.
4651
pub channels: u8,
4752
pub transform: Transform,
48-
pub rgb_to_camera: Option<[[f64; 3]; 3]>,
49-
pub(crate) histogram: Option<[[usize; 0x2000]; 3]>,
5053
}
5154

5255
#[allow(dead_code)]
@@ -84,6 +87,8 @@ pub fn decode<R: Read + Seek>(reader: &mut R) -> Result<RawImage, DecoderError>
8487
raw_image.camera_model = Some(camera_model);
8588
raw_image.transform = transform;
8689

90+
raw_image.calculate_conversion_matrices();
91+
8792
Ok(raw_image)
8893
}
8994

@@ -96,19 +101,94 @@ pub fn process_8bit(raw_image: RawImage) -> Image<u8> {
96101
width: image.width,
97102
height: image.height,
98103
transform: image.transform,
99-
rgb_to_camera: image.rgb_to_camera,
100-
histogram: image.histogram,
101104
}
102105
}
103106

104107
pub fn process_16bit(raw_image: RawImage) -> Image<u16> {
105-
let raw_image = crate::preprocessing::camera_data::calculate_conversion_matrices(raw_image);
106-
let raw_image = crate::preprocessing::subtract_black::subtract_black(raw_image);
107-
let raw_image = crate::preprocessing::scale_colors::scale_colors(raw_image);
108-
let image = crate::demosaicing::linear_demosaicing::linear_demosaic(raw_image);
109-
let image = crate::postprocessing::convert_to_rgb::convert_to_rgb(image);
110-
let image = crate::postprocessing::transform::transform(image);
111-
crate::postprocessing::gamma_correction::gamma_correction(image)
108+
let subtract_black = raw_image.subtract_black_fn();
109+
let scale_white_balance = raw_image.scale_white_balance_fn();
110+
let scale_to_16bit = raw_image.scale_to_16bit_fn();
111+
let raw_image = raw_image.apply((subtract_black, scale_white_balance, scale_to_16bit));
112+
113+
let convert_to_rgb = raw_image.convert_to_rgb_fn();
114+
let mut record_histogram = raw_image.record_histogram_fn();
115+
let image = raw_image.demosaic_and_apply((convert_to_rgb, &mut record_histogram));
116+
117+
let gamma_correction = image.gamma_correction_fn(&record_histogram.histogram);
118+
if image.transform == Transform::Horizontal {
119+
image.apply(gamma_correction)
120+
} else {
121+
image.transform_and_apply(gamma_correction)
122+
}
123+
}
124+
125+
impl RawImage {
126+
pub fn apply(mut self, mut transform: impl RawPixelTransform) -> RawImage {
127+
for (index, value) in self.data.iter_mut().enumerate() {
128+
let pixel = RawPixel {
129+
value: *value,
130+
row: index / self.width,
131+
column: index % self.width,
132+
};
133+
*value = transform.apply(pixel);
134+
}
135+
136+
self
137+
}
138+
139+
pub fn demosaic_and_apply(self, mut transform: impl PixelTransform) -> Image<u16> {
140+
let mut image = vec![0; self.width * self.height * 3];
141+
for Pixel { values, row, column } in self.linear_demosaic_iter().map(|mut pixel| {
142+
pixel.values = transform.apply(pixel);
143+
pixel
144+
}) {
145+
let pixel_index = row * self.width + column;
146+
image[3 * pixel_index..3 * (pixel_index + 1)].copy_from_slice(&values);
147+
}
148+
149+
Image {
150+
channels: 3,
151+
data: image,
152+
width: self.width,
153+
height: self.height,
154+
transform: self.transform,
155+
}
156+
}
157+
}
158+
159+
impl Image<u16> {
160+
pub fn apply(mut self, mut transform: impl PixelTransform) -> Image<u16> {
161+
for (index, values) in self.data.chunks_exact_mut(3).enumerate() {
162+
let pixel = Pixel {
163+
values: values.try_into().unwrap(),
164+
row: index / self.width,
165+
column: index % self.width,
166+
};
167+
values.copy_from_slice(&transform.apply(pixel));
168+
}
169+
170+
self
171+
}
172+
173+
pub fn transform_and_apply(self, mut transform: impl PixelTransform) -> Image<u16> {
174+
let mut image = vec![0; self.width * self.height * 3];
175+
let (width, height, iter) = self.transform_iter();
176+
for Pixel { values, row, column } in iter.map(|mut pixel| {
177+
pixel.values = transform.apply(pixel);
178+
pixel
179+
}) {
180+
let pixel_index = row * width + column;
181+
image[3 * pixel_index..3 * (pixel_index + 1)].copy_from_slice(&values);
182+
}
183+
184+
Image {
185+
channels: 3,
186+
data: image,
187+
width,
188+
height,
189+
transform: Transform::Horizontal,
190+
}
191+
}
112192
}
113193

114194
#[derive(Error, Debug)]

0 commit comments

Comments
 (0)