Implement deriving transfer function LUTs from processed test images.
This commit is contained in:
parent
10635b26ac
commit
7d5b68c639
62
Cargo.lock
generated
62
Cargo.lock
generated
|
@ -20,6 +20,12 @@ version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
|
checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.9.1"
|
version = "3.9.1"
|
||||||
|
@ -32,6 +38,23 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "3.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"indexmap",
|
||||||
|
"os_str_bytes",
|
||||||
|
"textwrap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorbox"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "git+https://github.com/cessen/colorbox?branch=master#45d5bf1b747d181fd5db39ba8e80f1f801cc18b2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deflate"
|
name = "deflate"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -101,6 +124,12 @@ version = "1.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
|
@ -110,6 +139,16 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inflate"
|
name = "inflate"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
@ -169,9 +208,17 @@ dependencies = [
|
||||||
name = "lut_extractor"
|
name = "lut_extractor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"colorbox",
|
||||||
"exr",
|
"exr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nanorand"
|
name = "nanorand"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -191,6 +238,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.0.10"
|
version = "1.0.10"
|
||||||
|
@ -261,6 +317,12 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "threadpool"
|
name = "threadpool"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
|
|
|
@ -3,7 +3,7 @@ name = "lut_extractor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
exr = "1.4.1"
|
exr = "1.4.1"
|
||||||
|
clap = { version = "3.1.8", default-features = false, features=["std"] }
|
||||||
|
colorbox = { git = "https://github.com/cessen/colorbox", branch = "master" }
|
||||||
|
|
159
src/main.rs
159
src/main.rs
|
@ -1,27 +1,174 @@
|
||||||
mod test_image;
|
mod test_image;
|
||||||
|
|
||||||
use test_image::{RES_X, RES_Y};
|
use std::{fs::File, io::BufWriter, path::Path};
|
||||||
|
|
||||||
|
use clap::{Arg, Command};
|
||||||
|
use colorbox::transfer_functions::srgb;
|
||||||
|
|
||||||
|
use test_image::{GRADIENT_LEN, RES_X, RES_Y};
|
||||||
|
|
||||||
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
const LUT_LEN: usize = 1 << 17;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let args = Command::new("LUT Extractor")
|
||||||
|
.version(VERSION)
|
||||||
|
.about("A small utility for extracting color LUTs from other software.")
|
||||||
|
.arg(
|
||||||
|
Arg::new("input")
|
||||||
|
.short('i')
|
||||||
|
.long("input")
|
||||||
|
.value_name("FILE")
|
||||||
|
.help("A processed test EXR file, to extract a LUT.")
|
||||||
|
.takes_value(true)
|
||||||
|
.required_unless_present_any(&["test_image"]),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("test_image")
|
||||||
|
.short('t')
|
||||||
|
.long("test_image")
|
||||||
|
.help("Generates the test EXR file."),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
if args.is_present("test_image") {
|
||||||
// Build the test image.
|
// Build the test image.
|
||||||
let pixels = test_image::build();
|
let pixels = test_image::build();
|
||||||
|
|
||||||
// Write the test image.
|
// Write the test image.
|
||||||
|
write_rgb_exr(
|
||||||
|
&format!("lut_extractor_{}x{}.exr", RES_X, RES_Y),
|
||||||
|
&pixels,
|
||||||
|
RES_X,
|
||||||
|
RES_Y,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
let input_path = args.value_of("input").unwrap();
|
||||||
|
|
||||||
|
let base_image = test_image::build();
|
||||||
|
let mut input_image = {
|
||||||
|
let (image, res_x, res_y) = read_rgb_exr(input_path).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res_x, RES_X,
|
||||||
|
"Input image doesn't have the correct resolution."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
res_y, RES_Y,
|
||||||
|
"Input image doesn't have the correct resolution."
|
||||||
|
);
|
||||||
|
image
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build the LUT.
|
||||||
|
let gray = &mut input_image[test_image::gray_idx(0)..test_image::gray_idx(GRADIENT_LEN)];
|
||||||
|
let mut prev = gray[0];
|
||||||
|
for rgb in gray.iter_mut() {
|
||||||
|
// Ensure montonicity.
|
||||||
|
if rgb[0] < prev[0] {
|
||||||
|
rgb[0] = prev[0];
|
||||||
|
}
|
||||||
|
if rgb[1] < prev[1] {
|
||||||
|
rgb[1] = prev[1];
|
||||||
|
}
|
||||||
|
if rgb[2] < prev[2] {
|
||||||
|
rgb[2] = prev[2];
|
||||||
|
}
|
||||||
|
prev = *rgb;
|
||||||
|
}
|
||||||
|
let mut gray_r = Vec::with_capacity(LUT_LEN);
|
||||||
|
let mut gray_g = Vec::with_capacity(LUT_LEN);
|
||||||
|
let mut gray_b = Vec::with_capacity(LUT_LEN);
|
||||||
|
for i in 0..LUT_LEN {
|
||||||
|
let t = i as f32 / (LUT_LEN - 1) as f32;
|
||||||
|
let rgb = lerp_slice(gray, t);
|
||||||
|
gray_r.push(rgb[0]);
|
||||||
|
gray_g.push(rgb[1]);
|
||||||
|
gray_b.push(rgb[2]);
|
||||||
|
// gray_r.push(srgb::to_linear(rgb[0]));
|
||||||
|
// gray_g.push(srgb::to_linear(rgb[1]));
|
||||||
|
// gray_b.push(srgb::to_linear(rgb[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the LUT.
|
||||||
|
colorbox::formats::cube::write_1d(
|
||||||
|
BufWriter::new(File::create("test.cube").unwrap()),
|
||||||
|
[(0.0, 1.0); 3],
|
||||||
|
[&gray_r, &gray_g, &gray_b],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lerp_slice(slice: &[[f32; 3]], t: f32) -> [f32; 3] {
|
||||||
|
assert!(!slice.is_empty());
|
||||||
|
|
||||||
|
let t2 = (slice.len() - 1) as f32 * t;
|
||||||
|
let t2i = t2 as usize;
|
||||||
|
|
||||||
|
if t2i == (slice.len() - 1) {
|
||||||
|
*slice.last().unwrap()
|
||||||
|
} else {
|
||||||
|
let alpha = t2.fract();
|
||||||
|
let inv_alpha = 1.0 - alpha;
|
||||||
|
let a = slice[t2i];
|
||||||
|
let b = slice[t2i + 1];
|
||||||
|
[
|
||||||
|
(a[0] * inv_alpha) + (b[0] * alpha),
|
||||||
|
(a[1] * inv_alpha) + (b[1] * alpha),
|
||||||
|
(a[2] * inv_alpha) + (b[2] * alpha),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_rgb_exr<P: AsRef<Path>>(path: P) -> exr::error::Result<(Vec<[f32; 3]>, usize, usize)> {
|
||||||
|
use exr::prelude::{ReadChannels, ReadLayers};
|
||||||
|
|
||||||
|
let image = exr::image::read::read()
|
||||||
|
.no_deep_data()
|
||||||
|
.largest_resolution_level()
|
||||||
|
.rgb_channels(
|
||||||
|
|res, _| (vec![[0.0f32; 3]; res.0 * res.1], res.0, res.1),
|
||||||
|
|(pixels, res_x, _res_y), co, (r, g, b): (f32, f32, f32)| {
|
||||||
|
pixels[co.1 * *res_x + co.0] = [r, g, b];
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.first_valid_layer()
|
||||||
|
.all_attributes()
|
||||||
|
.from_file(path)?;
|
||||||
|
|
||||||
|
Ok(image.layer_data.channel_data.pixels)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_rgb_exr<P: AsRef<Path>>(
|
||||||
|
path: P,
|
||||||
|
pixels: &[[f32; 3]],
|
||||||
|
res_x: usize,
|
||||||
|
res_y: usize,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
use exr::{
|
use exr::{
|
||||||
image::{Encoding, Image, Layer, SpecificChannels},
|
image::{Encoding, Image, Layer, SpecificChannels},
|
||||||
meta::header::LayerAttributes,
|
meta::header::LayerAttributes,
|
||||||
prelude::WritableImage,
|
prelude::WritableImage,
|
||||||
};
|
};
|
||||||
Image::from_layer(Layer::new(
|
|
||||||
(RES_X, RES_Y),
|
assert_eq!(pixels.len(), res_x * res_y);
|
||||||
|
|
||||||
|
match Image::from_layer(Layer::new(
|
||||||
|
(res_x, res_y),
|
||||||
LayerAttributes::named(""),
|
LayerAttributes::named(""),
|
||||||
Encoding::SMALL_LOSSLESS,
|
Encoding::SMALL_LOSSLESS,
|
||||||
SpecificChannels::rgb(|co: exr::math::Vec2<usize>| {
|
SpecificChannels::rgb(|co: exr::math::Vec2<usize>| {
|
||||||
let rgb = pixels[co.1 * RES_X + co.0];
|
let rgb = pixels[co.1 * res_y + co.0];
|
||||||
(rgb[0], rgb[1], rgb[2])
|
(rgb[0], rgb[1], rgb[2])
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
.write()
|
.write()
|
||||||
.to_file(format!("lut_extractor_{}x{}.exr", RES_X, RES_Y))
|
.to_file(path)
|
||||||
.unwrap();
|
{
|
||||||
|
// Only IO errors should be possible here.
|
||||||
|
Err(exr::error::Error::Io(e)) => Err(e),
|
||||||
|
Err(_) => panic!(),
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user