From a9cfeeb21969b8611e11b1c14d58817abd8cdc4e Mon Sep 17 00:00:00 2001 From: Elbert Ronnie <103196773+elbertronnie@users.noreply.github.com> Date: Sun, 11 Aug 2024 14:06:50 +0530 Subject: [PATCH] Raw-rs: Add preprocessing and demosaicing steps (#1796) * add subtract black step * add scale colors step * add raw to image step * implement linear demosiacing and fix errors in previous code * fix missing variable * make dependencies of tests optional * fix error in raw-rs tests * fix typo in "demosiacing" * use camera data from ADC and remove downloader * cargo fmt * use file_stem instead of file_name * remove old camera data * use equality instead of subtring to find model * store camera_to_xyz in decimal form * Code review --------- Co-authored-by: Keavon Chambers --- Cargo.lock | 64 ++++++++--- Cargo.toml | 1 + libraries/raw-rs/Cargo.toml | 18 +-- libraries/raw-rs/build-camera-data/Cargo.toml | 21 ++++ libraries/raw-rs/build-camera-data/src/lib.rs | 101 +++++++++++++++++ .../raw-rs/camera_data/Sony/DSLR-A100.toml | 1 + .../raw-rs/camera_data/Sony/DSLR-A200.toml | 1 + .../raw-rs/camera_data/Sony/DSLR-A300.toml | 1 + .../raw-rs/camera_data/Sony/DSLR-A330.toml | 1 + .../raw-rs/camera_data/Sony/DSLR-A350.toml | 1 + .../raw-rs/camera_data/Sony/DSLR-A550.toml | 1 + .../raw-rs/camera_data/Sony/DSLR-A700.toml | 1 + .../raw-rs/camera_data/Sony/DSLR-A850.toml | 1 + .../raw-rs/camera_data/Sony/DSLR-A900.toml | 1 + .../raw-rs/camera_data/Sony/ILCA-68.toml | 1 + .../raw-rs/camera_data/Sony/ILCA-99M2.toml | 1 + libraries/raw-rs/camera_data/Sony/ILCE-1.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-5100.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-6000.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-6100.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-6300.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-6400.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-6500.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-6600.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-6700.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-7CM2.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-7CR.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-7M2.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-7M3.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-7RM3.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-7RM4.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-7RM5.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-7SM2.toml | 1 + libraries/raw-rs/camera_data/Sony/ILCE-9.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-9M2.toml | 1 + .../raw-rs/camera_data/Sony/ILCE-9M3.toml | 1 + libraries/raw-rs/camera_data/Sony/NEX-3.toml | 1 + libraries/raw-rs/camera_data/Sony/NEX-3N.toml | 1 + libraries/raw-rs/camera_data/Sony/NEX-5R.toml | 1 + libraries/raw-rs/camera_data/Sony/NEX-6.toml | 1 + libraries/raw-rs/camera_data/Sony/NEX-7.toml | 1 + libraries/raw-rs/camera_data/Sony/ZV-1.toml | 1 + libraries/raw-rs/camera_data/Sony/ZV-1M2.toml | 1 + libraries/raw-rs/camera_data/Sony/ZV-E1.toml | 1 + libraries/raw-rs/camera_data/Sony/ZV-E10.toml | 1 + libraries/raw-rs/src/decoder/arw1.rs | 6 +- libraries/raw-rs/src/decoder/arw2.rs | 10 +- libraries/raw-rs/src/decoder/uncompressed.rs | 12 +- .../src/demosaicing/linear_demosaicing.rs | 77 +++++++++++++ libraries/raw-rs/src/demosaicing/mod.rs | 1 + libraries/raw-rs/src/lib.rs | 48 ++++++-- libraries/raw-rs/src/metadata/identify.rs | 60 ++++++++++ libraries/raw-rs/src/metadata/mod.rs | 1 + .../raw-rs/src/preprocessing/camera_data.rs | 26 +++++ libraries/raw-rs/src/preprocessing/mod.rs | 4 + .../raw-rs/src/preprocessing/raw_to_image.rs | 17 +++ .../raw-rs/src/preprocessing/scale_colors.rs | 46 ++++++++ .../src/preprocessing/subtract_black.rs | 30 +++++ libraries/raw-rs/src/tiff/mod.rs | 3 + libraries/raw-rs/src/tiff/tags.rs | 29 ++++- libraries/raw-rs/src/tiff/values.rs | 24 +++- libraries/raw-rs/tests/tests.rs | 105 +++++++++++++++--- 62 files changed, 688 insertions(+), 56 deletions(-) create mode 100644 libraries/raw-rs/build-camera-data/Cargo.toml create mode 100644 libraries/raw-rs/build-camera-data/src/lib.rs create mode 100644 libraries/raw-rs/camera_data/Sony/DSLR-A100.toml create mode 100644 libraries/raw-rs/camera_data/Sony/DSLR-A200.toml create mode 100644 libraries/raw-rs/camera_data/Sony/DSLR-A300.toml create mode 100644 libraries/raw-rs/camera_data/Sony/DSLR-A330.toml create mode 100644 libraries/raw-rs/camera_data/Sony/DSLR-A350.toml create mode 100644 libraries/raw-rs/camera_data/Sony/DSLR-A550.toml create mode 100644 libraries/raw-rs/camera_data/Sony/DSLR-A700.toml create mode 100644 libraries/raw-rs/camera_data/Sony/DSLR-A850.toml create mode 100644 libraries/raw-rs/camera_data/Sony/DSLR-A900.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCA-68.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCA-99M2.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-1.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-5100.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-6000.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-6100.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-6300.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-6400.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-6500.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-6600.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-6700.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-7CM2.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-7CR.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-7M2.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-7M3.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-7RM3.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-7RM4.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-7RM5.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-7SM2.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-9.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-9M2.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ILCE-9M3.toml create mode 100644 libraries/raw-rs/camera_data/Sony/NEX-3.toml create mode 100644 libraries/raw-rs/camera_data/Sony/NEX-3N.toml create mode 100644 libraries/raw-rs/camera_data/Sony/NEX-5R.toml create mode 100644 libraries/raw-rs/camera_data/Sony/NEX-6.toml create mode 100644 libraries/raw-rs/camera_data/Sony/NEX-7.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ZV-1.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ZV-1M2.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ZV-E1.toml create mode 100644 libraries/raw-rs/camera_data/Sony/ZV-E10.toml create mode 100644 libraries/raw-rs/src/demosaicing/linear_demosaicing.rs create mode 100644 libraries/raw-rs/src/demosaicing/mod.rs create mode 100644 libraries/raw-rs/src/metadata/identify.rs create mode 100644 libraries/raw-rs/src/metadata/mod.rs create mode 100644 libraries/raw-rs/src/preprocessing/camera_data.rs create mode 100644 libraries/raw-rs/src/preprocessing/mod.rs create mode 100644 libraries/raw-rs/src/preprocessing/raw_to_image.rs create mode 100644 libraries/raw-rs/src/preprocessing/scale_colors.rs create mode 100644 libraries/raw-rs/src/preprocessing/subtract_black.rs diff --git a/Cargo.lock b/Cargo.lock index 6de83f14..a6eed992 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -721,12 +721,28 @@ dependencies = [ "serde", ] +[[package]] +name = "build-camera-data" +version = "0.0.1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", + "toml 0.8.15", +] + [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + [[package]] name = "bytemuck" version = "1.16.1" @@ -1396,19 +1412,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" -[[package]] -name = "downloader" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05213e96f184578b5f70105d4d0a644a168e99e12d7bea0b200c15d67b5c182" -dependencies = [ - "futures", - "rand 0.8.5", - "reqwest 0.11.27", - "thiserror", - "tokio", -] - [[package]] name = "dtoa" version = "1.0.9" @@ -1592,6 +1595,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "fastnoise-lite" version = "1.1.1" @@ -4188,6 +4197,30 @@ dependencies = [ "ttf-parser 0.24.0", ] +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf 0.11.2", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "pango" version = "0.15.10" @@ -4784,9 +4817,12 @@ name = "raw-rs" version = "0.0.1" dependencies = [ "bitstream-io", - "downloader", + "build-camera-data", + "image 0.25.2", "libraw-rs", "num_enum 0.7.2", + "palette", + "reqwest 0.12.5", "tag-derive", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 45a72550..bee98f51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "libraries/bezier-rs", "libraries/raw-rs", "libraries/raw-rs/tag-derive", + "libraries/raw-rs/build-camera-data", "website/other/bezier-rs-demos/wasm", ] exclude = ["node-graph/gpu-compiler"] diff --git a/libraries/raw-rs/Cargo.toml b/libraries/raw-rs/Cargo.toml index c21ff780..a805ea71 100644 --- a/libraries/raw-rs/Cargo.toml +++ b/libraries/raw-rs/Cargo.toml @@ -13,20 +13,24 @@ repository = "https://github.com/GraphiteEditor/Graphite/tree/master/libraries/r documentation = "https://docs.rs/raw-rs" [features] -raw-rs-tests = ["libraw-rs"] +raw-rs-tests = ["dep:image", "dep:libraw-rs", "dep:palette", "dep:reqwest"] [dependencies] # Local dependencies tag-derive = { path = "tag-derive" } +build-camera-data = { path = "build-camera-data" } + +# Workspace dependencies +thiserror = { workspace = true } # Required dependencies bitstream-io = "2.3.0" num_enum = "0.7.2" -thiserror = "1.0" -# Optional dependencies -libraw-rs = { version = "0.0.4", optional = true } # Should be a dev dependency, but Cargo currently doesn't allow optional dev dependencies +# Optional workspace dependencies +image = { workspace = true, optional = true } +reqwest = { workspace = true, optional = true } -[dev-dependencies] -# Required dependencies -downloader = "0.2.7" +# Optional dependencies (should be dev dependencies, but Cargo currently doesn't allow optional dev dependencies) +libraw-rs = { version = "0.0.4", optional = true } +palette = { version = "0.7.6", optional = true } diff --git a/libraries/raw-rs/build-camera-data/Cargo.toml b/libraries/raw-rs/build-camera-data/Cargo.toml new file mode 100644 index 00000000..43ac2b4e --- /dev/null +++ b/libraries/raw-rs/build-camera-data/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "build-camera-data" +version = "0.0.1" +publish = false +edition = "2021" +authors = ["Graphite Authors "] +description = "Procedural macro to build the camera data into rust code" +license = "MIT OR Apache-2.0" +repository = "https://github.com/GraphiteEditor/Graphite/tree/master/libraries/raw-rs/build-camera-data" + +[lib] +proc-macro = true + +[dependencies] +# Workspace dependencies +quote = { workspace = true } +syn = { workspace = true } +proc-macro2 = { workspace = true } + +# Required dependencies +toml = "0.8.15" diff --git a/libraries/raw-rs/build-camera-data/src/lib.rs b/libraries/raw-rs/build-camera-data/src/lib.rs new file mode 100644 index 00000000..ae6a072f --- /dev/null +++ b/libraries/raw-rs/build-camera-data/src/lib.rs @@ -0,0 +1,101 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use toml::{Table, Value}; + +use std::fs; +use std::path::Path; + +enum CustomValue { + String(String), + Integer(i64), + Float(f64), + Boolean(bool), + Array(Vec), +} + +impl ToTokens for CustomValue { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self { + CustomValue::String(x) => x.to_tokens(tokens), + CustomValue::Integer(x) => { + let x: proc_macro2::TokenStream = format!("{:?}", x).parse().unwrap(); + x.to_tokens(tokens) + } + CustomValue::Float(x) => { + let x: proc_macro2::TokenStream = format!("{:?}", x).parse().unwrap(); + x.to_tokens(tokens) + } + CustomValue::Boolean(x) => x.to_tokens(tokens), + CustomValue::Array(x) => quote! { [ #( #x ),* ] }.to_tokens(tokens), + } + } +} + +impl From for CustomValue { + fn from(value: Value) -> Self { + match value { + Value::String(x) => CustomValue::String(x), + Value::Integer(x) => CustomValue::Integer(x), + Value::Float(x) => CustomValue::Float(x), + Value::Boolean(x) => CustomValue::Boolean(x), + Value::Array(x) => CustomValue::Array(x.into_iter().map(|x| x.into()).collect()), + _ => panic!("Unsuported data type"), + } + } +} + +#[proc_macro] +pub fn build_camera_data(_: TokenStream) -> TokenStream { + let mut camera_data: Vec<(String, Table)> = Vec::new(); + + let mut path = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).to_path_buf(); + path.push("camera_data"); + + fs::read_dir(path).unwrap().for_each(|entry| { + let company_name_path = entry.unwrap().path(); + if !company_name_path.is_dir() { + panic!("camera_data should only contain folders of company names") + } + + let company_name = company_name_path.file_name().unwrap().to_str().unwrap().to_string(); + + fs::read_dir(company_name_path).unwrap().for_each(|entry| { + let model_path = entry.unwrap().path(); + if !model_path.is_file() || model_path.extension().unwrap() != "toml" { + panic!("The folders within camera_data should only contain toml files") + } + + let name = company_name.clone() + " " + model_path.file_stem().unwrap().to_str().unwrap(); + + let mut values: Table = toml::from_str(&fs::read_to_string(model_path).unwrap()).unwrap(); + + if let Some(val) = values.get_mut("camera_to_xyz") { + *val = Value::Array(val.as_array().unwrap().iter().map(|x| Value::Integer((x.as_float().unwrap() * 10_000.) as i64)).collect()); + } + + camera_data.push((name, values)) + }); + }); + + let x: Vec<_> = camera_data + .iter() + .map(|(name, camera_data)| { + let keys: Vec<_> = camera_data.keys().map(|key| syn::Ident::new(key, proc_macro2::Span::call_site())).collect(); + let values: Vec = camera_data.values().cloned().map(|x| x.into()).collect(); + + quote! { + ( + #name, + CameraData { + #( #keys: #values, )* + ..CameraData::DEFAULT + } + ) + } + }) + .collect(); + + quote!([ #(#x),* ]).into() +} diff --git a/libraries/raw-rs/camera_data/Sony/DSLR-A100.toml b/libraries/raw-rs/camera_data/Sony/DSLR-A100.toml new file mode 100644 index 00000000..44addd12 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/DSLR-A100.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.9437, -0.2812, -0.0774, -0.8405, 1.6215, 0.2291, -0.0709, 0.0596, 0.7181] diff --git a/libraries/raw-rs/camera_data/Sony/DSLR-A200.toml b/libraries/raw-rs/camera_data/Sony/DSLR-A200.toml new file mode 100644 index 00000000..13b3e027 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/DSLR-A200.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.9847, -0.3091, -0.0929, -0.8485, 1.6346, 0.2225, -0.0714, 0.0595, 0.7103] diff --git a/libraries/raw-rs/camera_data/Sony/DSLR-A300.toml b/libraries/raw-rs/camera_data/Sony/DSLR-A300.toml new file mode 100644 index 00000000..13b3e027 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/DSLR-A300.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.9847, -0.3091, -0.0929, -0.8485, 1.6346, 0.2225, -0.0714, 0.0595, 0.7103] diff --git a/libraries/raw-rs/camera_data/Sony/DSLR-A330.toml b/libraries/raw-rs/camera_data/Sony/DSLR-A330.toml new file mode 100644 index 00000000..13b3e027 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/DSLR-A330.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.9847, -0.3091, -0.0929, -0.8485, 1.6346, 0.2225, -0.0714, 0.0595, 0.7103] diff --git a/libraries/raw-rs/camera_data/Sony/DSLR-A350.toml b/libraries/raw-rs/camera_data/Sony/DSLR-A350.toml new file mode 100644 index 00000000..6cee2f86 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/DSLR-A350.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6038, -0.1484, -0.0579, -0.9145, 1.6746, 0.2512, -0.0875, 0.0746, 0.7218] diff --git a/libraries/raw-rs/camera_data/Sony/DSLR-A550.toml b/libraries/raw-rs/camera_data/Sony/DSLR-A550.toml new file mode 100644 index 00000000..fe1efa5d --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/DSLR-A550.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.4950, -0.0580, -0.0103, -0.5228, 1.2542, 0.3029, -0.0709, 0.1435, 0.7371] diff --git a/libraries/raw-rs/camera_data/Sony/DSLR-A700.toml b/libraries/raw-rs/camera_data/Sony/DSLR-A700.toml new file mode 100644 index 00000000..dfc92e43 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/DSLR-A700.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.5775, -0.0805, -0.0359, -0.8573, 1.6294, 0.2391, -0.1943, 0.2342, 0.7249] diff --git a/libraries/raw-rs/camera_data/Sony/DSLR-A850.toml b/libraries/raw-rs/camera_data/Sony/DSLR-A850.toml new file mode 100644 index 00000000..c3871b88 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/DSLR-A850.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.5413, -0.1162, -0.0365, -0.5665, 1.3098, 0.2866, -0.0608, 0.1179, 0.8440] diff --git a/libraries/raw-rs/camera_data/Sony/DSLR-A900.toml b/libraries/raw-rs/camera_data/Sony/DSLR-A900.toml new file mode 100644 index 00000000..cf0c7586 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/DSLR-A900.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.5209, -0.1072, -0.0397, -0.8845, 1.6121, 0.2919, -0.1618, 0.1802, 0.8654] diff --git a/libraries/raw-rs/camera_data/Sony/ILCA-68.toml b/libraries/raw-rs/camera_data/Sony/ILCA-68.toml new file mode 100644 index 00000000..ec324ba0 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCA-68.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6435, -0.1903, -0.0536, -0.4722, 1.2449, 0.2550, -0.0663, 0.1363, 0.6517] diff --git a/libraries/raw-rs/camera_data/Sony/ILCA-99M2.toml b/libraries/raw-rs/camera_data/Sony/ILCA-99M2.toml new file mode 100644 index 00000000..f968630b --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCA-99M2.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6660, -0.1918, -0.0471, -0.4613, 1.2398, 0.2485, -0.0649, 0.1433, 0.6447] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-1.toml b/libraries/raw-rs/camera_data/Sony/ILCE-1.toml new file mode 100644 index 00000000..dd3e5561 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-1.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.8161, -0.2947, -0.0739, -0.4811, 1.2668, 0.2389, -0.0437, 0.1229, 0.6524] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-5100.toml b/libraries/raw-rs/camera_data/Sony/ILCE-5100.toml new file mode 100644 index 00000000..4e33271e --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-5100.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.5991, -0.1456, -0.0455, -0.4764, 1.2135, 0.2980, -0.0707, 0.1425, 0.6701] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-6000.toml b/libraries/raw-rs/camera_data/Sony/ILCE-6000.toml new file mode 100644 index 00000000..4e33271e --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-6000.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.5991, -0.1456, -0.0455, -0.4764, 1.2135, 0.2980, -0.0707, 0.1425, 0.6701] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-6100.toml b/libraries/raw-rs/camera_data/Sony/ILCE-6100.toml new file mode 100644 index 00000000..038a2cf9 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-6100.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.7657, -0.2847, -0.0607, -0.4083, 1.1966, 0.2389, -0.0684, 0.1418, 0.5844] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-6300.toml b/libraries/raw-rs/camera_data/Sony/ILCE-6300.toml new file mode 100644 index 00000000..dcad1c1d --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-6300.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.5973, -0.1695, -0.0419, -0.3826, 1.1797, 0.2293, -0.0639, 0.1398, 0.5789] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-6400.toml b/libraries/raw-rs/camera_data/Sony/ILCE-6400.toml new file mode 100644 index 00000000..038a2cf9 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-6400.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.7657, -0.2847, -0.0607, -0.4083, 1.1966, 0.2389, -0.0684, 0.1418, 0.5844] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-6500.toml b/libraries/raw-rs/camera_data/Sony/ILCE-6500.toml new file mode 100644 index 00000000..dcad1c1d --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-6500.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.5973, -0.1695, -0.0419, -0.3826, 1.1797, 0.2293, -0.0639, 0.1398, 0.5789] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-6600.toml b/libraries/raw-rs/camera_data/Sony/ILCE-6600.toml new file mode 100644 index 00000000..038a2cf9 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-6600.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.7657, -0.2847, -0.0607, -0.4083, 1.1966, 0.2389, -0.0684, 0.1418, 0.5844] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-6700.toml b/libraries/raw-rs/camera_data/Sony/ILCE-6700.toml new file mode 100644 index 00000000..a219444c --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-6700.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6972, -0.2408, -0.0600, -0.4330, 1.2101, 0.2515, -0.0388, 0.1277, 0.5847] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-7CM2.toml b/libraries/raw-rs/camera_data/Sony/ILCE-7CM2.toml new file mode 100644 index 00000000..6cf5a8ec --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-7CM2.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.7460, -0.2365, -0.0588, -0.5687, 1.3442, 0.2474, -0.0624, 0.1156, 0.6584] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-7CR.toml b/libraries/raw-rs/camera_data/Sony/ILCE-7CR.toml new file mode 100644 index 00000000..2fa4be8a --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-7CR.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.8200, -0.2976, -0.0719, -0.4296, 1.2053, 0.2532, -0.0429, 0.1282, 0.5774] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-7M2.toml b/libraries/raw-rs/camera_data/Sony/ILCE-7M2.toml new file mode 100644 index 00000000..b3fc49e2 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-7M2.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.5271, -0.0712, -0.0347, -0.6153, 1.3653, 0.2763, -0.1601, 0.2366, 0.7242] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-7M3.toml b/libraries/raw-rs/camera_data/Sony/ILCE-7M3.toml new file mode 100644 index 00000000..97edb941 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-7M3.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.7374, -0.2389, -0.0551, -0.5435, 1.3162, 0.2519, -0.1006, 0.1795, 0.6552] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-7RM3.toml b/libraries/raw-rs/camera_data/Sony/ILCE-7RM3.toml new file mode 100644 index 00000000..31a20de4 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-7RM3.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6640, -0.1847, -0.0503, -0.5238, 1.3010, 0.2474, -0.0993, 0.1673, 0.6527] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-7RM4.toml b/libraries/raw-rs/camera_data/Sony/ILCE-7RM4.toml new file mode 100644 index 00000000..eadef0e8 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-7RM4.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.7662, -0.2686, -0.0660, -0.5240, 1.2965, 0.2530, -0.0796, 0.1508, 0.6167] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-7RM5.toml b/libraries/raw-rs/camera_data/Sony/ILCE-7RM5.toml new file mode 100644 index 00000000..2fa4be8a --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-7RM5.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.8200, -0.2976, -0.0719, -0.4296, 1.2053, 0.2532, -0.0429, 0.1282, 0.5774] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-7SM2.toml b/libraries/raw-rs/camera_data/Sony/ILCE-7SM2.toml new file mode 100644 index 00000000..53276b94 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-7SM2.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.5838, -0.1430, -0.0246, -0.3497, 1.1477, 0.2297, -0.0748, 0.1885, 0.5778] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-9.toml b/libraries/raw-rs/camera_data/Sony/ILCE-9.toml new file mode 100644 index 00000000..c0e9b219 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-9.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6389, -0.1703, -0.0378, -0.4562, 1.2265, 0.2587, -0.0670, 0.1489, 0.6550] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-9M2.toml b/libraries/raw-rs/camera_data/Sony/ILCE-9M2.toml new file mode 100644 index 00000000..c0e9b219 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-9M2.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6389, -0.1703, -0.0378, -0.4562, 1.2265, 0.2587, -0.0670, 0.1489, 0.6550] diff --git a/libraries/raw-rs/camera_data/Sony/ILCE-9M3.toml b/libraries/raw-rs/camera_data/Sony/ILCE-9M3.toml new file mode 100644 index 00000000..ee78343a --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ILCE-9M3.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.9811, -0.3908, -0.0752, -0.3704, 1.1577, 0.2417, -0.0073, 0.0950, 0.5980] diff --git a/libraries/raw-rs/camera_data/Sony/NEX-3.toml b/libraries/raw-rs/camera_data/Sony/NEX-3.toml new file mode 100644 index 00000000..25f368d7 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/NEX-3.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6549, -0.1550, -0.0436, -0.4880, 1.2435, 0.2753, -0.0854, 0.1868, 0.6976] diff --git a/libraries/raw-rs/camera_data/Sony/NEX-3N.toml b/libraries/raw-rs/camera_data/Sony/NEX-3N.toml new file mode 100644 index 00000000..0b32fdcc --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/NEX-3N.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6129, -0.1545, -0.0418, -0.4930, 1.2490, 0.2743, -0.0977, 0.1693, 0.6615] diff --git a/libraries/raw-rs/camera_data/Sony/NEX-5R.toml b/libraries/raw-rs/camera_data/Sony/NEX-5R.toml new file mode 100644 index 00000000..0b32fdcc --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/NEX-5R.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6129, -0.1545, -0.0418, -0.4930, 1.2490, 0.2743, -0.0977, 0.1693, 0.6615] diff --git a/libraries/raw-rs/camera_data/Sony/NEX-6.toml b/libraries/raw-rs/camera_data/Sony/NEX-6.toml new file mode 100644 index 00000000..0b32fdcc --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/NEX-6.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6129, -0.1545, -0.0418, -0.4930, 1.2490, 0.2743, -0.0977, 0.1693, 0.6615] diff --git a/libraries/raw-rs/camera_data/Sony/NEX-7.toml b/libraries/raw-rs/camera_data/Sony/NEX-7.toml new file mode 100644 index 00000000..302c5ea4 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/NEX-7.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.5491, -0.1192, -0.0363, -0.4951, 1.2342, 0.2948, -0.0911, 0.1722, 0.7192] diff --git a/libraries/raw-rs/camera_data/Sony/ZV-1.toml b/libraries/raw-rs/camera_data/Sony/ZV-1.toml new file mode 100644 index 00000000..5a97b1cd --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ZV-1.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.8280, -0.2987, -0.0703, -0.3531, 1.1645, 0.2133, -0.0550, 0.1542, 0.5312] diff --git a/libraries/raw-rs/camera_data/Sony/ZV-1M2.toml b/libraries/raw-rs/camera_data/Sony/ZV-1M2.toml new file mode 100644 index 00000000..5a97b1cd --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ZV-1M2.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.8280, -0.2987, -0.0703, -0.3531, 1.1645, 0.2133, -0.0550, 0.1542, 0.5312] diff --git a/libraries/raw-rs/camera_data/Sony/ZV-E1.toml b/libraries/raw-rs/camera_data/Sony/ZV-E1.toml new file mode 100644 index 00000000..03047ce2 --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ZV-E1.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6912, -0.2127, -0.0469, -0.4470, 1.2175, 0.2587, -0.0398, 0.1478, 0.6492] diff --git a/libraries/raw-rs/camera_data/Sony/ZV-E10.toml b/libraries/raw-rs/camera_data/Sony/ZV-E10.toml new file mode 100644 index 00000000..87c345cc --- /dev/null +++ b/libraries/raw-rs/camera_data/Sony/ZV-E10.toml @@ -0,0 +1 @@ +camera_to_xyz = [0.6355, -0.2067, -0.0490, -0.3653, 1.1542, 0.2400, -0.0406, 0.1258, 0.5506] diff --git a/libraries/raw-rs/src/decoder/arw1.rs b/libraries/raw-rs/src/decoder/arw1.rs index ba999caf..cdc523d5 100644 --- a/libraries/raw-rs/src/decoder/arw1.rs +++ b/libraries/raw-rs/src/decoder/arw1.rs @@ -1,7 +1,7 @@ use crate::tiff::file::TiffRead; use crate::tiff::tags::SonyDataOffset; use crate::tiff::Ifd; -use crate::RawImage; +use crate::{RawImage, SubtractBlack}; use bitstream_io::{BitRead, BitReader, Endianness, BE}; use std::io::{Read, Seek}; @@ -22,6 +22,10 @@ pub fn decode_a100(ifd: Ifd, file: &mut TiffRead) -> RawImage data: image, width: image_width, height: image_height, + cfa_pattern: todo!(), + maximum: (1 << 12) - 1, + black: SubtractBlack::None, + camera_to_xyz: None, } } diff --git a/libraries/raw-rs/src/decoder/arw2.rs b/libraries/raw-rs/src/decoder/arw2.rs index 2ba7edbb..18029dbd 100644 --- a/libraries/raw-rs/src/decoder/arw2.rs +++ b/libraries/raw-rs/src/decoder/arw2.rs @@ -2,7 +2,7 @@ use crate::tiff::file::{Endian, TiffRead}; use crate::tiff::tags::{BitsPerSample, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, SonyToneCurve, StripByteCounts, StripOffsets, Tag}; use crate::tiff::values::CurveLookupTable; use crate::tiff::{Ifd, TiffError}; -use crate::RawImage; +use crate::{RawImage, SubtractBlack}; use std::io::{Read, Seek}; use tag_derive::Tag; @@ -30,7 +30,9 @@ pub fn decode(ifd: Ifd, file: &mut TiffRead) -> RawImage { let image_width: usize = ifd.image_width.try_into().unwrap(); let image_height: usize = ifd.image_height.try_into().unwrap(); - let _bits_per_sample: usize = ifd.bits_per_sample.into(); + let bits_per_sample: usize = ifd.bits_per_sample.into(); + assert!(bits_per_sample == 12); + let [cfa_pattern_width, cfa_pattern_height] = ifd.cfa_pattern_dim; assert!(cfa_pattern_width == 2 && cfa_pattern_height == 2); @@ -44,6 +46,10 @@ pub fn decode(ifd: Ifd, file: &mut TiffRead) -> RawImage { data: image, width: image_width, height: image_height, + cfa_pattern: ifd.cfa_pattern.try_into().unwrap(), + maximum: (1 << 14) - 1, + black: SubtractBlack::CfaGrid([512, 512, 512, 512]), // TODO: Find the correct way to do this + camera_to_xyz: None, } } diff --git a/libraries/raw-rs/src/decoder/uncompressed.rs b/libraries/raw-rs/src/decoder/uncompressed.rs index 8f396664..dc45204f 100644 --- a/libraries/raw-rs/src/decoder/uncompressed.rs +++ b/libraries/raw-rs/src/decoder/uncompressed.rs @@ -1,7 +1,8 @@ use crate::tiff::file::TiffRead; -use crate::tiff::tags::{BitsPerSample, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, RowsPerStrip, StripByteCounts, StripOffsets, Tag}; +use crate::tiff::tags::{BitsPerSample, BlackLevel, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, RowsPerStrip, StripByteCounts, StripOffsets, Tag}; use crate::tiff::{Ifd, TiffError}; -use crate::RawImage; +use crate::{RawImage, SubtractBlack}; + use std::io::{Read, Seek}; use tag_derive::Tag; @@ -13,6 +14,7 @@ struct ArwUncompressedIfd { rows_per_strip: RowsPerStrip, bits_per_sample: BitsPerSample, compression: Compression, + black_level: BlackLevel, cfa_pattern: CfaPattern, cfa_pattern_dim: CfaPatternDim, strip_offsets: StripOffsets, @@ -29,7 +31,7 @@ pub fn decode(ifd: Ifd, file: &mut TiffRead) -> RawImage { let image_width: usize = ifd.image_width.try_into().unwrap(); let image_height: usize = ifd.image_height.try_into().unwrap(); let rows_per_strip: usize = ifd.rows_per_strip.try_into().unwrap(); - let _bits_per_sample: usize = ifd.bits_per_sample.into(); + let bits_per_sample: usize = ifd.bits_per_sample.into(); let [cfa_pattern_width, cfa_pattern_height] = ifd.cfa_pattern_dim; assert!(cfa_pattern_width == 2 && cfa_pattern_height == 2); @@ -52,5 +54,9 @@ pub fn decode(ifd: Ifd, file: &mut TiffRead) -> RawImage { data: image, width: image_width, height: image_height, + cfa_pattern: ifd.cfa_pattern.try_into().unwrap(), + maximum: if bits_per_sample == 16 { u16::MAX } else { (1 << bits_per_sample) - 1 }, + black: SubtractBlack::CfaGrid(ifd.black_level), + camera_to_xyz: None, } } diff --git a/libraries/raw-rs/src/demosaicing/linear_demosaicing.rs b/libraries/raw-rs/src/demosaicing/linear_demosaicing.rs new file mode 100644 index 00000000..1fe7a2ce --- /dev/null +++ b/libraries/raw-rs/src/demosaicing/linear_demosaicing.rs @@ -0,0 +1,77 @@ +use crate::{Image, RawImage}; + +fn average(data: &[u16], indexes: impl Iterator) -> u16 { + let mut sum = 0; + let mut count = 0; + for index in indexes { + if index >= 0 && (index as usize) < data.len() { + sum += data[index as usize] as u32; + count += 1; + } + } + + (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!(), + } +} + +fn linear_demosaic_rggb(mut raw_image: RawImage) -> Image { + 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]; + + match (row % 2 == 0, col % 2 == 0) { + (true, true) => { + let indexes = cross_indexes.iter().map(|x| 3 * x + 1); + raw_image.data[3 * (pixel_index as usize) + 1] = average(&raw_image.data, indexes); + + let indexes = diagonal_indexes.iter().map(|x| 3 * x + 2); + raw_image.data[3 * (pixel_index as usize) + 2] = average(&raw_image.data, indexes); + } + (true, false) => { + let indexes = horizontal_indexes.iter().map(|x| 3 * x); + raw_image.data[3 * (pixel_index as usize)] = average(&raw_image.data, indexes); + + let indexes = vertical_indexes.iter().map(|x| 3 * x + 2); + raw_image.data[3 * (pixel_index as usize) + 2] = average(&raw_image.data, indexes); + } + (false, true) => { + let indexes = vertical_indexes.iter().map(|x| 3 * x); + raw_image.data[3 * (pixel_index as usize)] = average(&raw_image.data, indexes); + + let indexes = horizontal_indexes.iter().map(|x| 3 * x + 2); + raw_image.data[3 * (pixel_index as usize) + 2] = average(&raw_image.data, indexes); + } + (false, false) => { + let indexes = cross_indexes.iter().map(|x| 3 * x + 1); + raw_image.data[3 * (pixel_index as usize) + 1] = average(&raw_image.data, indexes); + + let indexes = diagonal_indexes.iter().map(|x| 3 * x); + raw_image.data[3 * (pixel_index as usize)] = average(&raw_image.data, indexes); + } + } + } + } + + Image { + channels: 3, + data: raw_image.data, + width: raw_image.width, + height: raw_image.height, + } +} diff --git a/libraries/raw-rs/src/demosaicing/mod.rs b/libraries/raw-rs/src/demosaicing/mod.rs new file mode 100644 index 00000000..6fe99b8e --- /dev/null +++ b/libraries/raw-rs/src/demosaicing/mod.rs @@ -0,0 +1 @@ +pub mod linear_demosaicing; diff --git a/libraries/raw-rs/src/lib.rs b/libraries/raw-rs/src/lib.rs index d068e052..81fd144a 100644 --- a/libraries/raw-rs/src/lib.rs +++ b/libraries/raw-rs/src/lib.rs @@ -1,18 +1,33 @@ pub mod decoder; +pub mod demosaicing; +pub mod metadata; +pub mod preprocessing; pub mod tiff; +use crate::preprocessing::camera_data::camera_to_xyz; + use tag_derive::Tag; use tiff::file::TiffRead; -use tiff::tags::{Compression, ImageLength, ImageWidth, Model, StripByteCounts, SubIfd, Tag}; +use tiff::tags::{Compression, ImageLength, ImageWidth, StripByteCounts, SubIfd, Tag}; use tiff::{Ifd, TiffError}; use std::io::{Read, Seek}; use thiserror::Error; +pub enum SubtractBlack { + None, + Value(u16), + CfaGrid([u16; 4]), +} + pub struct RawImage { pub data: Vec, pub width: usize, pub height: usize, + pub cfa_pattern: [u8; 4], + pub maximum: u16, + pub black: SubtractBlack, + pub camera_to_xyz: Option<[f64; 9]>, } pub struct Image { @@ -35,28 +50,41 @@ pub fn decode(reader: &mut R) -> Result let mut file = TiffRead::new(reader)?; let ifd = Ifd::new_first_ifd(&mut file)?; - // TODO: This is only for the tests to pass for now. Replace this with the correct implementation when the decoder is complete. - let model = ifd.get_value::(&mut file)?; + let camera_model = metadata::identify::identify_camera_model(&ifd, &mut file).unwrap(); - if model == "DSLR-A100" { - Ok(decoder::arw1::decode_a100(ifd, &mut file)) + let mut raw_image = if camera_model.model == "DSLR-A100" { + decoder::arw1::decode_a100(ifd, &mut file) } else { let sub_ifd = ifd.get_value::(&mut file)?; let arw_ifd = sub_ifd.get_value::(&mut file)?; if arw_ifd.compression == 1 { - Ok(decoder::uncompressed::decode(sub_ifd, &mut file)) + decoder::uncompressed::decode(sub_ifd, &mut file) } else if arw_ifd.strip_byte_counts[0] == arw_ifd.image_width * arw_ifd.image_height { - Ok(decoder::arw2::decode(sub_ifd, &mut file)) + decoder::arw2::decode(sub_ifd, &mut file) } else { // TODO: implement for arw 1. todo!() } - } + }; + + raw_image.camera_to_xyz = camera_to_xyz(&camera_model); + + Ok(raw_image) } -pub fn process_8bit(_image: RawImage) -> Image { - todo!() +pub fn process_8bit(raw_image: RawImage) -> Image { + let raw_image = crate::preprocessing::subtract_black::subtract_black(raw_image); + let raw_image = crate::preprocessing::raw_to_image::raw_to_image(raw_image); + let raw_image = crate::preprocessing::scale_colors::scale_colors(raw_image); + let image = crate::demosaicing::linear_demosaicing::linear_demosaic(raw_image); + + Image { + channels: image.channels, + data: image.data.iter().map(|x| (x >> 8) as u8).collect(), + width: image.width, + height: image.height, + } } pub fn process_16bit(_image: RawImage) -> Image { diff --git a/libraries/raw-rs/src/metadata/identify.rs b/libraries/raw-rs/src/metadata/identify.rs new file mode 100644 index 00000000..4a438be8 --- /dev/null +++ b/libraries/raw-rs/src/metadata/identify.rs @@ -0,0 +1,60 @@ +use crate::tiff::file::TiffRead; +use crate::tiff::tags::{Make, Model, Tag}; +use crate::tiff::{Ifd, TiffError}; + +use std::io::{Read, Seek}; +use tag_derive::Tag; + +const COMPANY_NAMES: [&str; 22] = [ + "AgfaPhoto", + "Canon", + "Casio", + "Epson", + "Fujifilm", + "Mamiya", + "Minolta", + "Motorola", + "Kodak", + "Konica", + "Leica", + "Nikon", + "Nokia", + "Olympus", + "Ricoh", + "Pentax", + "Phase One", + "Samsung", + "Sigma", + "Sinar", + "Sony", + "YI", +]; + +#[allow(dead_code)] +#[derive(Tag)] +struct CameraModelIfd { + make: Make, + model: Model, +} + +pub struct CameraModel { + pub make: String, + pub model: String, +} + +pub fn identify_camera_model(ifd: &Ifd, file: &mut TiffRead) -> Option { + let mut ifd = ifd.get_value::(file).unwrap(); + + ifd.make.make_ascii_lowercase(); + for company_name in COMPANY_NAMES { + let lowercase_company_name = company_name.to_ascii_lowercase(); + if ifd.make.contains(&lowercase_company_name) { + return Some(CameraModel { + make: company_name.to_string(), + model: ifd.model, + }); + } + } + + None +} diff --git a/libraries/raw-rs/src/metadata/mod.rs b/libraries/raw-rs/src/metadata/mod.rs new file mode 100644 index 00000000..5529e6b3 --- /dev/null +++ b/libraries/raw-rs/src/metadata/mod.rs @@ -0,0 +1 @@ +pub mod identify; diff --git a/libraries/raw-rs/src/preprocessing/camera_data.rs b/libraries/raw-rs/src/preprocessing/camera_data.rs new file mode 100644 index 00000000..fed952f2 --- /dev/null +++ b/libraries/raw-rs/src/preprocessing/camera_data.rs @@ -0,0 +1,26 @@ +use crate::metadata::identify::CameraModel; +use build_camera_data::build_camera_data; + +pub struct CameraData { + pub black: u16, + pub maximum: u16, + pub camera_to_xyz: [i16; 9], +} + +impl CameraData { + const DEFAULT: CameraData = CameraData { + black: 0, + maximum: 0, + camera_to_xyz: [0; 9], + }; +} + +const CAMERA_DATA: [(&str, CameraData); 40] = build_camera_data!(); + +pub fn camera_to_xyz(camera_model: &CameraModel) -> Option<[f64; 9]> { + let camera_name_needle = camera_model.make.to_owned() + " " + &camera_model.model; + 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.)) +} diff --git a/libraries/raw-rs/src/preprocessing/mod.rs b/libraries/raw-rs/src/preprocessing/mod.rs new file mode 100644 index 00000000..07858bc8 --- /dev/null +++ b/libraries/raw-rs/src/preprocessing/mod.rs @@ -0,0 +1,4 @@ +pub mod camera_data; +pub mod raw_to_image; +pub mod scale_colors; +pub mod subtract_black; diff --git a/libraries/raw-rs/src/preprocessing/raw_to_image.rs b/libraries/raw-rs/src/preprocessing/raw_to_image.rs new file mode 100644 index 00000000..af2422df --- /dev/null +++ b/libraries/raw-rs/src/preprocessing/raw_to_image.rs @@ -0,0 +1,17 @@ +use crate::RawImage; + +pub fn raw_to_image(mut raw_image: RawImage) -> RawImage { + let mut image = Vec::with_capacity(raw_image.width * raw_image.height * 3); + + for row in 0..raw_image.height { + for col in 0..raw_image.width { + let mut pixel = [0_u16; 3]; + let color_index = raw_image.cfa_pattern[2 * (row % 2) + (col % 2)]; + pixel[color_index as usize] = raw_image.data[row * raw_image.width + col]; + image.extend_from_slice(&pixel); + } + } + + raw_image.data = image; + raw_image +} diff --git a/libraries/raw-rs/src/preprocessing/scale_colors.rs b/libraries/raw-rs/src/preprocessing/scale_colors.rs new file mode 100644 index 00000000..cbcbb767 --- /dev/null +++ b/libraries/raw-rs/src/preprocessing/scale_colors.rs @@ -0,0 +1,46 @@ +use crate::RawImage; + +const XYZ_TO_RGB: [[f64; 3]; 3] = [[0.412453, 0.357580, 0.180423], [0.212671, 0.715160, 0.072169], [0.019334, 0.119193, 0.950227]]; + +pub fn scale_colors(mut raw_image: RawImage) -> RawImage { + if let Some(camera_to_xyz) = raw_image.camera_to_xyz { + 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 white_balance_multiplier = camera_to_rgb.map(|x| 1. / x.iter().sum::()); + + 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().fold(f64::INFINITY, |a, &b| a.min(b)) + } else { + white_balance_multiplier.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)) + }; + + 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.] + }; + + for i in 0..(raw_image.height * raw_image.width) { + for (c, multiplier) in final_multiplier.iter().enumerate() { + raw_image.data[3 * i + c] = ((raw_image.data[3 * i + c] as f64) * multiplier).min(u16::MAX as f64).max(0.) as u16; + } + } + } + + raw_image +} diff --git a/libraries/raw-rs/src/preprocessing/subtract_black.rs b/libraries/raw-rs/src/preprocessing/subtract_black.rs new file mode 100644 index 00000000..94f2bfde --- /dev/null +++ b/libraries/raw-rs/src/preprocessing/subtract_black.rs @@ -0,0 +1,30 @@ +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)]); + } + } + + raw_image.maximum -= black_level.iter().max().unwrap(); + + raw_image +} diff --git a/libraries/raw-rs/src/tiff/mod.rs b/libraries/raw-rs/src/tiff/mod.rs index c3284b24..7483ffb5 100644 --- a/libraries/raw-rs/src/tiff/mod.rs +++ b/libraries/raw-rs/src/tiff/mod.rs @@ -29,8 +29,11 @@ pub enum TagId { JpegOffset = 0x201, JpegLength = 0x202, SonyToneCurve = 0x7010, + BlackLevel = 0x7310, CfaPatternDim = 0x828d, CfaPattern = 0x828e, + ColorMatrix1 = 0xc621, + ColorMatrix2 = 0xc622, #[num_enum(catch_all)] Unknown(u16), diff --git a/libraries/raw-rs/src/tiff/tags.rs b/libraries/raw-rs/src/tiff/tags.rs index 1162e8df..e5677de1 100644 --- a/libraries/raw-rs/src/tiff/tags.rs +++ b/libraries/raw-rs/src/tiff/tags.rs @@ -1,4 +1,4 @@ -use super::types::{Array, ConstArray, TagType, TypeByte, TypeIfd, TypeLong, TypeNumber, TypeShort, TypeSonyToneCurve, TypeString}; +use super::types::{Array, ConstArray, TagType, TypeByte, TypeIfd, TypeLong, TypeNumber, TypeSRational, TypeShort, TypeSonyToneCurve, TypeString}; use super::{Ifd, TagId, TiffError, TiffRead}; use std::io::{Read, Seek}; @@ -24,10 +24,13 @@ pub struct StripByteCounts; pub struct SubIfd; pub struct JpegOffset; pub struct JpegLength; -pub struct CfaPatternDim; -pub struct CfaPattern; pub struct SonyDataOffset; pub struct SonyToneCurve; +pub struct BlackLevel; +pub struct CfaPatternDim; +pub struct CfaPattern; +pub struct ColorMatrix1; +pub struct ColorMatrix2; impl SimpleTag for ImageWidth { type Type = TypeNumber; @@ -141,6 +144,20 @@ impl SimpleTag for CfaPattern { const NAME: &'static str = "CFA Pattern"; } +impl SimpleTag for ColorMatrix1 { + type Type = Array; + + const ID: TagId = TagId::ColorMatrix1; + const NAME: &'static str = "Color Matrix 1"; +} + +impl SimpleTag for ColorMatrix2 { + type Type = Array; + + const ID: TagId = TagId::ColorMatrix2; + const NAME: &'static str = "Color Matrix 2"; +} + impl SimpleTag for SonyDataOffset { type Type = TypeLong; @@ -155,6 +172,12 @@ impl SimpleTag for SonyToneCurve { const NAME: &'static str = "Sony Tone Curve"; } +impl SimpleTag for BlackLevel { + const ID: TagId = TagId::BlackLevel; + type Type = ConstArray; + const NAME: &'static str = "Black Level"; +} + pub trait Tag { type Output; diff --git a/libraries/raw-rs/src/tiff/values.rs b/libraries/raw-rs/src/tiff/values.rs index a5b04d04..f107c5e8 100644 --- a/libraries/raw-rs/src/tiff/values.rs +++ b/libraries/raw-rs/src/tiff/values.rs @@ -1,8 +1,30 @@ -pub struct Rational { +pub trait ToFloat { + fn to_float(&self) -> f64; +} + +impl ToFloat for u32 { + fn to_float(&self) -> f64 { + *self as f64 + } +} + +impl ToFloat for i32 { + fn to_float(&self) -> f64 { + *self as f64 + } +} + +pub struct Rational { pub numerator: T, pub denominator: T, } +impl ToFloat for Rational { + fn to_float(&self) -> f64 { + self.numerator.to_float() / self.denominator.to_float() + } +} + pub struct CurveLookupTable { table: Vec, } diff --git a/libraries/raw-rs/tests/tests.rs b/libraries/raw-rs/tests/tests.rs index 30b5d2a9..681da198 100644 --- a/libraries/raw-rs/tests/tests.rs +++ b/libraries/raw-rs/tests/tests.rs @@ -1,13 +1,18 @@ +// Only compile this file if the feature "raw-rs-tests" is enabled #![cfg(feature = "raw-rs-tests")] -use std::collections::HashMap; -use std::fmt::Write; -use std::fs::{read_dir, File}; -use std::io::{Cursor, Read}; -use std::path::Path; use raw_rs::RawImage; -use downloader::{Download, Downloader}; +use image::codecs::png::{CompressionType, FilterType, PngEncoder}; +use image::{ColorType, ImageEncoder}; +use libraw::Processor; +use palette::{LinSrgb, Srgb}; +use std::collections::HashMap; +use std::fmt::Write; +use std::fs::{read_dir, File}; +use std::io::{BufWriter, Cursor, Read}; +use std::path::{Path, PathBuf}; +use std::time::Duration; const TEST_FILES: [&str; 3] = ["ILCE-7M3-ARW2.3.5-blossoms.arw", "ILCE-7RM4-ARW2.3.5-kestrel.arw", "ILCE-6000-ARW2.3.1-windsock.arw"]; const BASE_URL: &str = "https://static.graphite.rs/test-data/libraries/raw-rs/"; @@ -30,7 +35,7 @@ fn test_images_match_with_libraw() { print!("{} => ", path.display()); - let _raw_image = match test_raw_data(&content) { + let raw_image = match test_raw_data(&content) { Err(err_msg) => { failed_tests += 1; return println!("{}", err_msg); @@ -47,6 +52,15 @@ fn test_images_match_with_libraw() { // }; println!("Passed"); + + // TODO: Remove this later + let mut image = raw_rs::process_8bit(raw_image); + store_image(&path, "raw_rs", &mut image.data, image.width, image.height); + + let processor = Processor::new(); + let libraw_image = processor.process_8bit(&content).unwrap(); + let mut data = Vec::from_iter(libraw_image.iter().copied()); + store_image(&path, "libraw_rs", &mut data[..], libraw_image.width() as usize, libraw_image.height() as usize); }); if failed_tests != 0 { @@ -54,24 +68,47 @@ fn test_images_match_with_libraw() { } } +fn store_image(path: &Path, suffix: &str, data: &mut [u8], width: usize, height: usize) { + if suffix == "raw_rs" { + for pixel in data.chunks_mut(3) { + let lin_srgb: LinSrgb = LinSrgb::new(pixel[0], pixel[1], pixel[2]).into_format(); + let output: Srgb = Srgb::from_linear(lin_srgb); + pixel[0] = output.red; + pixel[1] = output.green; + pixel[2] = output.blue; + } + } + + let mut output_path = PathBuf::new(); + if let Some(parent) = path.parent() { + output_path.push(parent); + } + output_path.push("output"); + if let Some(filename) = path.file_stem() { + let new_filename = format!("{}_{}.{}", filename.to_string_lossy(), suffix, "png"); + output_path.push(new_filename); + } + output_path.set_extension("png"); + + let file = BufWriter::new(File::create(output_path).unwrap()); + let png_encoder = PngEncoder::new_with_quality(file, CompressionType::Best, FilterType::Adaptive); + png_encoder.write_image(data, width as u32, height as u32, ColorType::Rgb8.into()).unwrap(); +} + fn download_images() { let mut path = Path::new(BASE_PATH).to_owned(); - let mut downloads: Vec = Vec::new(); + let client = reqwest::blocking::Client::builder().timeout(Duration::from_secs(60 * 5)).build().unwrap(); for filename in TEST_FILES { path.push(filename); if !path.exists() { let url = BASE_URL.to_owned() + filename; - downloads.push(Download::new(&url).file_name(Path::new(filename))); + let mut response = client.get(url).send().unwrap(); + let mut file = File::create(BASE_PATH.to_owned() + filename).unwrap(); + std::io::copy(&mut response, &mut file).unwrap(); } path.pop(); } - - let mut downloader = Downloader::builder().download_folder(Path::new(BASE_PATH)).build().unwrap(); - - for download_summary in downloader.download(&downloads).unwrap() { - download_summary.unwrap(); - } } fn test_raw_data(content: &[u8]) -> Result { @@ -269,3 +306,41 @@ fn _test_final_image(content: &[u8], raw_image: RawImage) -> Result<(), String> Ok(()) } + +// #[test] +fn extract_data_from_dng_images() { + read_dir(BASE_PATH) + .unwrap() + .map(|dir_entry| dir_entry.unwrap().path()) + .filter(|path| path.is_file() && path.file_name().map(|file_name| file_name != ".gitkeep").unwrap_or(false)) + .for_each(|path| { + extract_data_from_dng_image(&path); + }); +} + +fn extract_data_from_dng_image(path: &Path) { + use raw_rs::tiff::file::TiffRead; + use raw_rs::tiff::tags::{ColorMatrix2, Make, Model}; + use raw_rs::tiff::values::ToFloat; + use raw_rs::tiff::Ifd; + use std::io::{BufReader, Write}; + + let reader = BufReader::new(File::open(path).unwrap()); + let mut file = TiffRead::new(reader).unwrap(); + let ifd = Ifd::new_first_ifd(&mut file).unwrap(); + + let make = ifd.get_value::(&mut file).unwrap(); + let model = ifd.get_value::(&mut file).unwrap(); + let matrix = ifd.get_value::(&mut file).unwrap(); + + if model == "MODEL-NAME" { + println!("{}", path.display()); + return; + } + + let output_folder = path.parent().unwrap().join(make); + std::fs::create_dir_all(&output_folder).unwrap(); + let mut output_file = File::create(output_folder.join(model + ".toml")).unwrap(); + let matrix: Vec<_> = matrix.iter().map(|x| x.to_float()).collect(); + writeln!(output_file, "camera_to_xyz = {:.4?}", matrix).unwrap(); +}