Vendor rawler 0.7.2 unmodified
The crate as crates.io publishes it, minus .cargo-ok, its Cargo.lock and data/testdata (13 MB of sample files only its own tests read). Not yet routed through [patch.crates-io]; the next commit is the patch.
This commit is contained in:
+729
@@ -0,0 +1,729 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Result;
|
||||
use crate::cfa::PlaneColor;
|
||||
use crate::imgop::raw::{correct_blacklevel, correct_blacklevel_cfa};
|
||||
use crate::imgop::{convert_from_f32_scaled_u16, convert_to_f32_unscaled};
|
||||
use crate::pixarray::SubPixel;
|
||||
use crate::{
|
||||
CFA,
|
||||
decoders::*,
|
||||
formats::tiff::{Rational, Value},
|
||||
imgop::{
|
||||
Dim2, Point, Rect,
|
||||
raw::{ColorMatrix, DevelopParams},
|
||||
xyz::{FlatColorMatrix, Illuminant},
|
||||
},
|
||||
pixarray::PixU16,
|
||||
tags::TiffTag,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct WhiteLevel(pub Vec<u32>);
|
||||
|
||||
impl Default for WhiteLevel {
|
||||
fn default() -> Self {
|
||||
Self(vec![u16::MAX as u32; 1])
|
||||
}
|
||||
}
|
||||
|
||||
impl WhiteLevel {
|
||||
pub fn new(level: impl Into<Vec<u32>>) -> Self {
|
||||
Self(level.into())
|
||||
}
|
||||
|
||||
pub fn new_bits(bits: u32, cpp: usize) -> Self {
|
||||
if bits > 32 {
|
||||
panic!("Whitelevel can only be calculated for max. 32 bits, but {} bits given", bits);
|
||||
}
|
||||
let level: u32 = ((1_u64 << bits) - 1) as u32;
|
||||
Self(vec![level; cpp])
|
||||
}
|
||||
|
||||
pub fn as_vec(&self) -> Vec<f32> {
|
||||
self.0.iter().cloned().map(|x| x as f32).collect_vec()
|
||||
}
|
||||
|
||||
pub fn as_bayer_array(&self) -> [f32; 4] {
|
||||
if self.0.len() == 4 {
|
||||
[self.0[0] as f32, self.0[1] as f32, self.0[2] as f32, self.0[3] as f32]
|
||||
} else {
|
||||
[self.0[0] as f32, self.0[0] as f32, self.0[0] as f32, self.0[0] as f32]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BlackLevel {
|
||||
pub levels: Vec<Rational>,
|
||||
pub cpp: usize,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
}
|
||||
|
||||
impl Default for BlackLevel {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
levels: [Rational::from(0_u32)].into(),
|
||||
width: 1,
|
||||
height: 1,
|
||||
cpp: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BlackLevel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let levels: Vec<f32> = self.levels.iter().map(|x| x.as_f32()).collect();
|
||||
f.write_fmt(format_args!("RepeatDim: {}:{}, cpp: {}, {:?}", self.height, self.width, self.cpp, levels))
|
||||
}
|
||||
}
|
||||
|
||||
impl BlackLevel {
|
||||
pub fn new<T>(levels: &[T], width: usize, height: usize, cpp: usize) -> Self
|
||||
where
|
||||
T: Copy + Into<Rational>,
|
||||
{
|
||||
assert_eq!(levels.len(), width * height * cpp);
|
||||
Self {
|
||||
levels: levels.iter().map(|x| (*x).into()).collect(),
|
||||
width,
|
||||
height,
|
||||
cpp,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero(width: usize, height: usize, cpp: usize) -> Self {
|
||||
if width == 0 || height == 0 || cpp == 0 {
|
||||
panic!(
|
||||
"Blacklevel::zero() must not be called with zero value arguments: width: {}, height: {}, cpp: {}",
|
||||
width, height, cpp
|
||||
);
|
||||
}
|
||||
Self {
|
||||
levels: vec![0_u16; cpp * width * height].iter().map(|x| Rational::from(*x)).collect(),
|
||||
width,
|
||||
height,
|
||||
cpp,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec(&self) -> Vec<f32> {
|
||||
self.levels.iter().map(Rational::as_f32).collect_vec()
|
||||
}
|
||||
|
||||
pub fn as_bayer_array(&self) -> [f32; 4] {
|
||||
if self.levels.len() == 4 {
|
||||
[
|
||||
self.levels[0].as_f32(),
|
||||
self.levels[1].as_f32(),
|
||||
self.levels[2].as_f32(),
|
||||
self.levels[3].as_f32(),
|
||||
]
|
||||
} else {
|
||||
[
|
||||
self.levels[0].as_f32(),
|
||||
self.levels[0].as_f32(),
|
||||
self.levels[0].as_f32(),
|
||||
self.levels[0].as_f32(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_count(&self) -> usize {
|
||||
self.cpp * self.width * self.height
|
||||
}
|
||||
|
||||
// TODO: write test
|
||||
pub fn shift(&self, x: usize, y: usize) -> Self {
|
||||
if self.sample_count() == 1 {
|
||||
self.clone()
|
||||
} else {
|
||||
let mut trans = self.clone();
|
||||
let (w, h, cpp) = (trans.width, trans.height, trans.cpp);
|
||||
for yn in 0..h {
|
||||
for xn in 0..w {
|
||||
let ys = (yn + y) % h;
|
||||
let xs = (xn + x) % w;
|
||||
trans.levels[yn * w * cpp + xn * cpp..yn * w * cpp + xn * cpp + cpp]
|
||||
.copy_from_slice(&self.levels[ys * w * cpp + xs * cpp..ys * w * cpp + xs * cpp + cpp]);
|
||||
}
|
||||
}
|
||||
trans
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RawPhotometricInterpretation {
|
||||
BlackIsZero,
|
||||
// Defined by DNG
|
||||
Cfa(CFAConfig),
|
||||
LinearRaw,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CFAConfig {
|
||||
pub cfa: CFA,
|
||||
pub colors: PlaneColor,
|
||||
}
|
||||
|
||||
impl CFAConfig {
|
||||
pub fn new(cfa: &CFA, colors: &PlaneColor) -> Self {
|
||||
Self {
|
||||
cfa: cfa.clone(),
|
||||
colors: colors.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_camera(cam: &Camera) -> Self {
|
||||
Self {
|
||||
cfa: cam.cfa.clone(),
|
||||
colors: cam.plane_color.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All the data needed to process this raw image, including the image data itself as well
|
||||
/// as all the needed metadata
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RawImage {
|
||||
/// Camera definition
|
||||
pub camera: Camera,
|
||||
/// camera make as encoded in the file
|
||||
pub make: String,
|
||||
/// camera model as encoded in the file
|
||||
pub model: String,
|
||||
/// make cleaned up to be consistent and short
|
||||
pub clean_make: String,
|
||||
/// model cleaned up to be consistent and short
|
||||
pub clean_model: String,
|
||||
/// width of the full image
|
||||
pub width: usize,
|
||||
/// height of the full image
|
||||
pub height: usize,
|
||||
/// number of components per pixel (1 for bayer, 3 for RGB images)
|
||||
pub cpp: usize,
|
||||
/// Bits per pixel
|
||||
pub bps: usize,
|
||||
/// whitebalance coefficients encoded in the file in RGBE order
|
||||
pub wb_coeffs: [f32; 4],
|
||||
/// image whitelevels in RGBE order
|
||||
pub whitelevel: WhiteLevel,
|
||||
/// image blacklevels in RGBE order
|
||||
pub blacklevel: BlackLevel,
|
||||
/// matrix to convert XYZ to camera RGBE
|
||||
pub xyz_to_cam: [[f32; 3]; 4], // TODO: deprecated, use color_matrix
|
||||
/// Photometric interpretation
|
||||
pub photometric: RawPhotometricInterpretation,
|
||||
/// how much to crop the image to get all the usable (non-black) area
|
||||
pub active_area: Option<Rect>,
|
||||
/// how much to crop the image to get all the recommended area
|
||||
pub crop_area: Option<Rect>,
|
||||
|
||||
/// Areas of the sensor that is masked to prevent it from receiving light. Used to calculate
|
||||
/// black levels and noise.
|
||||
pub blackareas: Vec<Rect>,
|
||||
|
||||
/// orientation of the image as indicated by the image metadata
|
||||
pub orientation: Orientation,
|
||||
/// image data itself, has `width`\*`height`\*`cpp` elements
|
||||
pub data: RawImageData,
|
||||
|
||||
pub color_matrix: HashMap<Illuminant, FlatColorMatrix>,
|
||||
|
||||
pub dng_tags: HashMap<u16, Value>,
|
||||
}
|
||||
|
||||
/// The actual image data, after decoding
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RawImageData {
|
||||
/// The most usual u16 output of almost all formats
|
||||
Integer(Vec<u16>),
|
||||
/// Some formats are directly encoded as f32, most notably some DNGs
|
||||
Float(Vec<f32>),
|
||||
}
|
||||
|
||||
impl RawImageData {
|
||||
pub fn as_f32<'a>(&'a self) -> Cow<'a, Vec<f32>> {
|
||||
match self {
|
||||
Self::Integer(data) => Cow::Owned(convert_to_f32_unscaled(data)),
|
||||
Self::Float(data) => Cow::Borrowed(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn force_integer(&mut self) {
|
||||
if let Self::Float(data) = self {
|
||||
*self = Self::Integer(convert_from_f32_scaled_u16(data, 0, u16::MAX));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RawImage {
|
||||
pub fn calc_black_levels<T>(cfa: &CFA, blackareas: &[Rect], width: usize, _height: usize, image: &[T]) -> Option<BlackLevel>
|
||||
where
|
||||
T: SubPixel,
|
||||
{
|
||||
let x = cfa.width * cfa.height;
|
||||
if x == 0 {
|
||||
return None;
|
||||
}
|
||||
assert!(!image.is_empty());
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Sample {
|
||||
avg: f32,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
if !blackareas.is_empty() {
|
||||
let mut samples = vec![Sample { avg: 0.0, count: 0 }; x];
|
||||
|
||||
for area in blackareas {
|
||||
for row in area.p.y..area.p.y + area.d.h {
|
||||
for col in area.p.x..area.p.x + area.d.w {
|
||||
//let color = cfa.color_at(row, col);
|
||||
let color = (row % cfa.height) * cfa.width + (col % cfa.width);
|
||||
samples[color].avg += image[row * width + col].as_f32();
|
||||
samples[color].count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let blacklevels: Vec<f32> = samples.into_iter().map(|s| s.avg / s.count as f32).collect();
|
||||
|
||||
debug!("Calculated blacklevels: {:?}", blacklevels);
|
||||
// TODO: support other then RGGB levels
|
||||
assert_eq!(cfa.width * cfa.height, 4);
|
||||
Some(BlackLevel::new(&[blacklevels[0], blacklevels[1], blacklevels[2], blacklevels[3]], 2, 2, 1))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn new(
|
||||
cam: Camera,
|
||||
image: PixU16,
|
||||
cpp: usize,
|
||||
wb_coeffs: [f32; 4],
|
||||
photometric: RawPhotometricInterpretation,
|
||||
blacklevel: Option<BlackLevel>,
|
||||
whitelevel: Option<WhiteLevel>,
|
||||
dummy: bool,
|
||||
) -> RawImage {
|
||||
assert_eq!(image.width % cpp, 0);
|
||||
assert_eq!(dummy, !image.is_initialized());
|
||||
let sample_width = image.width;
|
||||
let pixel_width = image.width / cpp;
|
||||
|
||||
let mut blackareas: Vec<Rect> = Vec::new();
|
||||
|
||||
let active_area = cam.active_area.map(|area| Rect::new_with_borders(Dim2::new(pixel_width, image.height), &area));
|
||||
|
||||
let blackarea_base = active_area.unwrap_or_else(|| Rect::new(Point::zero(), Dim2::new(sample_width, image.height)));
|
||||
|
||||
// For now, we only use masked areas when cpp is 1. For color images (RGB)
|
||||
// like Canon SRAW, we ignore it (it isn't provided anyway).
|
||||
if cpp == 1 {
|
||||
// Build black areas
|
||||
// First value (.0) is start and (.1) is length!
|
||||
if let Some(ah) = cam.blackareah {
|
||||
blackareas.push(Rect::new_with_points(
|
||||
Point::new(blackarea_base.p.x, ah.0),
|
||||
Point::new(blackarea_base.p.x + blackarea_base.d.w, ah.0 + ah.1),
|
||||
));
|
||||
}
|
||||
if let Some(av) = cam.blackareav {
|
||||
blackareas.push(Rect::new_with_points(
|
||||
Point::new(av.0, blackarea_base.p.y),
|
||||
Point::new(av.0 + av.1, blackarea_base.p.y + blackarea_base.d.h),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let blacklevel = cam
|
||||
.make_blacklevel(cpp)
|
||||
.or_else(|| if cam.find_hint("invalid_blacklevel") { None } else { blacklevel })
|
||||
.or_else(|| {
|
||||
if dummy {
|
||||
Some(BlackLevel::default())
|
||||
} else {
|
||||
Self::calc_black_levels::<u16>(&cam.cfa, &blackareas, image.width, image.height, image.pixels())
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| BlackLevel::zero(1, 1, cpp));
|
||||
|
||||
let whitelevel = cam
|
||||
.make_whitelevel(cpp)
|
||||
.or(whitelevel)
|
||||
.unwrap_or_else(|| panic!("Need whitelevel in config: {}", cam.clean_model));
|
||||
|
||||
let crop_area = cam.crop_area.map(|area| Rect::new_with_borders(Dim2::new(pixel_width, image.height), &area));
|
||||
|
||||
RawImage {
|
||||
camera: cam.clone(),
|
||||
make: cam.make.clone(),
|
||||
model: cam.model.clone(),
|
||||
clean_make: cam.clean_make.clone(),
|
||||
clean_model: cam.clean_model.clone(),
|
||||
width: image.width / cpp,
|
||||
height: image.height,
|
||||
cpp,
|
||||
bps: cam.real_bps,
|
||||
wb_coeffs,
|
||||
data: RawImageData::Integer(image.into_inner()),
|
||||
blacklevel,
|
||||
whitelevel,
|
||||
xyz_to_cam: cam.xyz_to_cam,
|
||||
photometric,
|
||||
active_area,
|
||||
crop_area,
|
||||
blackareas,
|
||||
orientation: Orientation::Normal, //cam.orientation, // TODO fixme
|
||||
color_matrix: cam.color_matrix,
|
||||
dng_tags: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn new_with_data(
|
||||
cam: Camera,
|
||||
image: RawImageData,
|
||||
sample_width: usize,
|
||||
height: usize,
|
||||
cpp: usize,
|
||||
wb_coeffs: [f32; 4],
|
||||
photometric: RawPhotometricInterpretation,
|
||||
blacklevel: Option<BlackLevel>,
|
||||
whitelevel: Option<WhiteLevel>,
|
||||
dummy: bool,
|
||||
) -> RawImage {
|
||||
//assert_eq!(image.width % cpp, 0);
|
||||
//assert_eq!(dummy, !image.is_initialized());
|
||||
let pixel_width = sample_width / cpp;
|
||||
|
||||
let mut blackareas: Vec<Rect> = Vec::new();
|
||||
|
||||
let active_area = cam.active_area.map(|area| Rect::new_with_borders(Dim2::new(pixel_width, height), &area));
|
||||
|
||||
let blackarea_base = active_area.unwrap_or_else(|| Rect::new(Point::zero(), Dim2::new(sample_width, height)));
|
||||
|
||||
// For now, we only use masked areas when cpp is 1. For color images (RGB)
|
||||
// like Canon SRAW, we ignore it (it isn't provided anyway).
|
||||
if cpp == 1 {
|
||||
// Build black areas
|
||||
// First value (.0) is start and (.1) is length!
|
||||
if let Some(ah) = cam.blackareah {
|
||||
blackareas.push(Rect::new_with_points(
|
||||
Point::new(blackarea_base.p.x, ah.0),
|
||||
Point::new(blackarea_base.p.x + blackarea_base.d.w, ah.0 + ah.1),
|
||||
));
|
||||
}
|
||||
if let Some(av) = cam.blackareav {
|
||||
blackareas.push(Rect::new_with_points(
|
||||
Point::new(av.0, blackarea_base.p.y),
|
||||
Point::new(av.0 + av.1, blackarea_base.p.y + blackarea_base.d.h),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let blacklevel = cam
|
||||
.make_blacklevel(cpp)
|
||||
.or_else(|| if cam.find_hint("invalid_blacklevel") { None } else { blacklevel })
|
||||
.or_else(|| {
|
||||
if dummy {
|
||||
Some(BlackLevel::default())
|
||||
} else {
|
||||
match &image {
|
||||
RawImageData::Integer(pix) => Self::calc_black_levels(&cam.cfa, &blackareas, sample_width, height, pix),
|
||||
RawImageData::Float(pix) => Self::calc_black_levels(&cam.cfa, &blackareas, sample_width, height, pix),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| BlackLevel::zero(1, 1, cpp));
|
||||
|
||||
let whitelevel = cam
|
||||
.make_whitelevel(cpp)
|
||||
.or(whitelevel)
|
||||
.unwrap_or_else(|| panic!("Need whitelvel in config: {}", cam.clean_model));
|
||||
|
||||
let crop_area = cam.crop_area.map(|area| Rect::new_with_borders(Dim2::new(pixel_width, height), &area));
|
||||
|
||||
RawImage {
|
||||
camera: cam.clone(),
|
||||
make: cam.make.clone(),
|
||||
model: cam.model.clone(),
|
||||
clean_make: cam.clean_make.clone(),
|
||||
clean_model: cam.clean_model.clone(),
|
||||
width: pixel_width,
|
||||
height: height,
|
||||
cpp,
|
||||
bps: cam.real_bps,
|
||||
wb_coeffs,
|
||||
data: image,
|
||||
blacklevel,
|
||||
whitelevel,
|
||||
xyz_to_cam: cam.xyz_to_cam,
|
||||
photometric,
|
||||
active_area,
|
||||
crop_area,
|
||||
blackareas,
|
||||
orientation: Orientation::Normal, //cam.orientation, // TODO fixme
|
||||
color_matrix: cam.color_matrix,
|
||||
dng_tags: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dim(&self) -> Dim2 {
|
||||
Dim2::new(self.width, self.height)
|
||||
}
|
||||
|
||||
pub fn pixels_u16(&self) -> &[u16] {
|
||||
if let RawImageData::Integer(data) = &self.data {
|
||||
data
|
||||
} else {
|
||||
panic!("Data ist not u16");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pixels_u16_mut(&mut self) -> &mut [u16] {
|
||||
if let RawImageData::Integer(data) = &mut self.data {
|
||||
data
|
||||
} else {
|
||||
panic!("Data ist not u16");
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply blacklevel and whitelevel scaling, replacing raw image data
|
||||
/// with floating point values in range 0.0 .. 1.0.
|
||||
/// Internal blacklevel and whitelevel is reset to 0.0 and 1.0 to match image data.
|
||||
pub fn apply_scaling(&mut self) -> crate::Result<()> {
|
||||
let mut pixels = self.data.as_f32();
|
||||
match &self.photometric {
|
||||
RawPhotometricInterpretation::BlackIsZero => todo!(),
|
||||
RawPhotometricInterpretation::Cfa(_) => {
|
||||
correct_blacklevel_cfa(
|
||||
pixels.to_mut(),
|
||||
self.width,
|
||||
self.height,
|
||||
&self.blacklevel.as_bayer_array(),
|
||||
&self.whitelevel.as_bayer_array(),
|
||||
);
|
||||
self.data = RawImageData::Float(pixels.into_owned());
|
||||
}
|
||||
RawPhotometricInterpretation::LinearRaw => {
|
||||
correct_blacklevel(pixels.to_mut(), &self.blacklevel.as_vec(), &self.whitelevel.as_vec());
|
||||
self.data = RawImageData::Float(pixels.into_owned());
|
||||
}
|
||||
}
|
||||
self.blacklevel.levels.iter_mut().for_each(|x| *x = Rational::new(0, 1));
|
||||
self.whitelevel.0.iter_mut().for_each(|x| *x = 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn develop_params(&self) -> Result<DevelopParams> {
|
||||
let mut xyz2cam: [[f32; 3]; 4] = [[0.0; 3]; 4];
|
||||
//let color_matrix = self.color_matrix.get(&Illuminant::D65).unwrap(); // TODO fixme
|
||||
let color_matrix = self
|
||||
.color_matrix
|
||||
.values()
|
||||
.next()
|
||||
.cloned()
|
||||
.unwrap_or(vec![1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]); // TODO: invalid
|
||||
assert_eq!(color_matrix.len() % 3, 0); // this is not so nice...
|
||||
let components = color_matrix.len() / 3;
|
||||
for i in 0..components {
|
||||
for j in 0..3 {
|
||||
xyz2cam[i][j] = color_matrix[i * 3 + j];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
let active_area = Rect::new(
|
||||
Point::new(self.crops[3], self.crops[0]),
|
||||
Dim2::new(self.width - self.crops[3] - self.crops[1], self.height - self.crops[0] - self.crops[2]),
|
||||
);
|
||||
*/
|
||||
debug!("RAW developing active area: {:?}", self.active_area);
|
||||
|
||||
let wb_coeff = if self.wb_coeffs[0].is_nan() { [1.0, 1.0, 1.0, 1.0] } else { self.wb_coeffs };
|
||||
|
||||
let params = DevelopParams {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
color_matrices: vec![ColorMatrix {
|
||||
illuminant: Illuminant::D65, // TODO: need CAT
|
||||
matrix: xyz2cam,
|
||||
}],
|
||||
whitelevel: self.whitelevel.clone(),
|
||||
blacklevel: self.blacklevel.clone(),
|
||||
//pattern,
|
||||
photometric: self.photometric.clone(),
|
||||
wb_coeff,
|
||||
cpp: self.cpp,
|
||||
active_area: self.active_area,
|
||||
crop_area: self.crop_area,
|
||||
//gamma: 2.4,
|
||||
};
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
/// Add a DNG tag override
|
||||
pub fn add_dng_tag<T: TiffTag, V: Into<Value>>(&mut self, tag: T, value: V) {
|
||||
let tag: u16 = tag.into();
|
||||
self.dng_tags.insert(tag, value.into());
|
||||
}
|
||||
|
||||
pub fn linearize(&self) -> Result<Self> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Outputs the inverted matrix that converts pixels in the camera colorspace into
|
||||
/// XYZ components.
|
||||
pub fn cam_to_xyz(&self) -> [[f32; 4]; 3] {
|
||||
self.pseudoinverse(self.xyz_to_cam)
|
||||
}
|
||||
|
||||
/// Outputs the inverted matrix that converts pixels in the camera colorspace into
|
||||
/// XYZ components normalized to be easily used to convert to Lab or a RGB output space
|
||||
pub fn cam_to_xyz_normalized(&self) -> [[f32; 4]; 3] {
|
||||
let mut xyz_to_cam = self.xyz_to_cam;
|
||||
// Normalize xyz_to_cam so that xyz_to_cam * (1,1,1) is (1,1,1,1)
|
||||
for i in 0..4 {
|
||||
let mut num = 0.0;
|
||||
for j in 0..3 {
|
||||
num += xyz_to_cam[i][j];
|
||||
}
|
||||
for j in 0..3 {
|
||||
xyz_to_cam[i][j] = if num == 0.0 { 0.0 } else { xyz_to_cam[i][j] / num };
|
||||
}
|
||||
}
|
||||
|
||||
self.pseudoinverse(xyz_to_cam)
|
||||
}
|
||||
|
||||
/// Not all cameras encode a whitebalance so in those cases just using a 6500K neutral one
|
||||
/// is a good compromise
|
||||
pub fn neutralwb(&self) -> [f32; 4] {
|
||||
let rgb_to_xyz = [
|
||||
// sRGB D65
|
||||
[0.412453, 0.357580, 0.180423],
|
||||
[0.212671, 0.715160, 0.072169],
|
||||
[0.019334, 0.119193, 0.950227],
|
||||
];
|
||||
|
||||
// Multiply RGB matrix
|
||||
let mut rgb_to_cam = [[0.0; 3]; 4];
|
||||
for i in 0..4 {
|
||||
for j in 0..3 {
|
||||
rgb_to_cam[i][j] = 0.0;
|
||||
for k in 0..3 {
|
||||
rgb_to_cam[i][j] += self.xyz_to_cam[i][k] * rgb_to_xyz[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut neutralwb = [0 as f32; 4];
|
||||
for i in 0..4 {
|
||||
let mut num = 0.0;
|
||||
for j in 0..3 {
|
||||
num += rgb_to_cam[i][j];
|
||||
}
|
||||
neutralwb[i] = 1.0 / num;
|
||||
}
|
||||
|
||||
[
|
||||
neutralwb[0] / neutralwb[1],
|
||||
neutralwb[1] / neutralwb[1],
|
||||
neutralwb[2] / neutralwb[1],
|
||||
neutralwb[3] / neutralwb[1],
|
||||
]
|
||||
}
|
||||
|
||||
fn pseudoinverse(&self, inm: [[f32; 3]; 4]) -> [[f32; 4]; 3] {
|
||||
let mut temp: [[f32; 6]; 3] = [[0.0; 6]; 3];
|
||||
|
||||
for i in 0..3 {
|
||||
for j in 0..6 {
|
||||
temp[i][j] = if j == i + 3 { 1.0 } else { 0.0 };
|
||||
}
|
||||
for j in 0..3 {
|
||||
for k in 0..4 {
|
||||
temp[i][j] += inm[k][i] * inm[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..3 {
|
||||
let mut num = temp[i][i];
|
||||
for j in 0..6 {
|
||||
temp[i][j] /= num;
|
||||
}
|
||||
for k in 0..3 {
|
||||
if k == i {
|
||||
continue;
|
||||
}
|
||||
num = temp[k][i];
|
||||
for j in 0..6 {
|
||||
temp[k][j] -= temp[i][j] * num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut out: [[f32; 4]; 3] = [[0.0; 4]; 3];
|
||||
|
||||
for i in 0..4 {
|
||||
for j in 0..3 {
|
||||
out[j][i] = 0.0;
|
||||
for k in 0..3 {
|
||||
out[j][i] += temp[j][k + 3] * inm[i][k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/// Returns the CFA pattern after the crop has been applied (and thus the pattern
|
||||
/// potentially shifted)
|
||||
pub fn cropped_cfa(&self) -> CFA {
|
||||
//self.cfa.shift(self.crops[3], self.crops[0])
|
||||
todo!()
|
||||
// Need to specify which crop, active or DefaultCrop
|
||||
}
|
||||
|
||||
/// Checks if the image is monochrome
|
||||
pub fn is_monochrome(&self) -> bool {
|
||||
self.photometric == RawPhotometricInterpretation::BlackIsZero
|
||||
//self.cpp == 1 && !self.cfa.is_valid()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn blacklevel_shift() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
let black = BlackLevel::new(&[1_u16, 2, 3, 4], 2, 2, 1).shift(1, 1);
|
||||
assert_eq!(black.levels, vec![4_u16, 3, 2, 1].into_iter().map(Rational::from).collect::<Vec<Rational>>());
|
||||
|
||||
let black = BlackLevel::new(&[1_u16, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4], 2, 2, 3).shift(3, 3);
|
||||
assert_eq!(
|
||||
black.levels,
|
||||
vec![4_u16, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1]
|
||||
.into_iter()
|
||||
.map(Rational::from)
|
||||
.collect::<Vec<Rational>>()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user