From 2ed8f48eb8443f21efc35876ff199fa89db435ef Mon Sep 17 00:00:00 2001 From: Neal Burger Date: Wed, 7 Oct 2026 08:43:13 +0200 Subject: [PATCH 1/2] Implement DNG black level delta V correction Added support for DNG black level delta V processing in raw image data. --- src-tauri/src/raw_processing.rs | 212 +++++++++++++++++++++++++++++++- 1 file changed, 210 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/raw_processing.rs b/src-tauri/src/raw_processing.rs index 09bb6d3cce..623bbf93fd 100644 --- a/src-tauri/src/raw_processing.rs +++ b/src-tauri/src/raw_processing.rs @@ -3,13 +3,15 @@ use crate::white_balance::WhiteBalance; use anyhow::{Result, anyhow}; use image::{DynamicImage, ImageBuffer, Rgba}; use rawler::{ - decoders::{Decoder, Orientation, RawDecodeParams}, + decoders::{Decoder, FormatHint, Orientation, RawDecodeParams, WellKnownIFD}, + formats::tiff::Value, imgop::{ develop::{DemosaicAlgorithm, Intermediate, ProcessingStep, RawDevelop}, xyz::Illuminant, }, - rawimage::{RawImage, RawPhotometricInterpretation}, + rawimage::{RawImage, RawImageData, RawPhotometricInterpretation}, rawsource::RawSource, + tags::DngTag, }; use std::sync::{ Arc, @@ -60,6 +62,129 @@ fn is_linear_raw_format(raw_image: &RawImage) -> bool { ) } +fn apply_black_level_delta_v_to_data( + data: &mut RawImageData, + width: usize, + height: usize, + cpp: usize, + delta_start_row: usize, + deltas: &[f32], +) -> bool { + let Some(row_width) = width.checked_mul(cpp) else { + return false; + }; + let Some(expected_len) = row_width.checked_mul(height) else { + return false; + }; + if row_width == 0 + || deltas.is_empty() + || deltas.iter().any(|delta| !delta.is_finite()) + { + return false; + } + let Some(delta_end_row) = delta_start_row.checked_add(deltas.len()) else { + return false; + }; + if deltas.len() != 1 && delta_end_row > height { + return false; + } + + match data { + RawImageData::Integer(pixels) if pixels.len() == expected_len => { + for (row, row_pixels) in pixels.chunks_exact_mut(row_width).enumerate() { + let delta = if deltas.len() == 1 { + deltas[0] + } else if (delta_start_row..delta_end_row).contains(&row) { + deltas[row - delta_start_row] + } else { + continue; + }; + for pixel in row_pixels { + *pixel = (*pixel as f32 - delta) + .round() + .clamp(0.0, u16::MAX as f32) as u16; + } + } + } + RawImageData::Float(pixels) if pixels.len() == expected_len => { + for (row, row_pixels) in pixels.chunks_exact_mut(row_width).enumerate() { + let delta = if deltas.len() == 1 { + deltas[0] + } else if (delta_start_row..delta_end_row).contains(&row) { + deltas[row - delta_start_row] + } else { + continue; + }; + for pixel in row_pixels { + *pixel = (*pixel - delta).max(0.0); + } + } + } + _ => return false, + } + + true +} + +fn apply_dng_black_level_delta_v(raw_image: &mut RawImage, decoder: &dyn Decoder) -> bool { + if decoder.format_hint() != FormatHint::DNG + || !matches!( + &raw_image.photometric, + RawPhotometricInterpretation::Cfa(_) + ) + { + return false; + } + + let Ok(Some(raw_ifd)) = decoder.ifd(WellKnownIFD::Raw) else { + return false; + }; + let Some(entry) = raw_ifd.get_entry(DngTag::BlackLevelDeltaV) else { + return false; + }; + let value = &entry.value; + if !matches!( + value, + Value::Rational(_) | Value::SRational(_) | Value::Float(_) | Value::Double(_) + ) { + return false; + } + + let mut deltas = Vec::with_capacity(value.count()); + for index in 0..value.count() { + let Ok(Some(delta)) = value.get_f32(index) else { + return false; + }; + deltas.push(delta); + } + + let delta_start_row = if deltas.len() == 1 || deltas.len() == raw_image.height { + 0 + } else { + let Some(active_area) = raw_ifd.get_entry(DngTag::ActiveArea) else { + return false; + }; + if active_area.value.count() < 4 { + return false; + } + let top = active_area.value.force_usize(0); + let bottom = active_area.value.force_usize(2); + if bottom.checked_sub(top) != Some(deltas.len()) { + return false; + } + top + }; + + apply_black_level_delta_v_to_data( + &mut raw_image.data, + raw_image.width, + raw_image.height, + raw_image.cpp, + delta_start_row, + &deltas, + ) +} + #[inline] fn srgb_to_linear(value: f32) -> f32 { if value <= 0.04045 { @@ -155,6 +280,10 @@ fn develop_internal( check_cancel()?; let mut raw_image: RawImage = decoder.raw_image(&source, &RawDecodeParams::default(), false)?; + if apply_dng_black_level_delta_v(&mut raw_image, decoder.as_ref()) { + log::debug!("Applied DNG BlackLevelDeltaV correction"); + } + let orientation = metadata_orientation(decoder.as_ref(), &source)?; let is_linear_format = is_linear_raw_format(&raw_image); @@ -345,3 +474,82 @@ pub fn get_fast_demosaic_scale_factor( } 1.0 } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn diagnose_attached_dng_raw_metadata() { + let bytes = std::fs::read("../20070819-IMG_2427.dng").unwrap(); + let source = RawSource::new_from_slice(&bytes); + let decoder = rawler::get_decoder(&source).unwrap(); + let raw_image = decoder + .raw_image(&source, &RawDecodeParams::default(), false) + .unwrap(); + + eprintln!( + "camera={:?}/{:?}, blacklevel={:?}, whitelevel={:?}, wb={:?}, matrix={:?}", + raw_image.make, + raw_image.model, + raw_image.blacklevel, + raw_image.whitelevel, + raw_image.wb_coeffs, + raw_image.color_matrix + ); + } + + #[test] + fn applies_per_row_black_level_deltas() { + let mut image = RawImageData::Integer(vec![1000, 2000, 500, 100]); + + assert!(apply_black_level_delta_v_to_data( + &mut image, + 2, + 2, + 1, + 0, + &[100.0, 200.0], + )); + let RawImageData::Integer(pixels) = image else { + panic!("Expected integer pixel data"); + }; + assert_eq!(pixels, [900, 1900, 300, 0]); + } + + #[test] + fn aligns_black_level_deltas_to_active_area_rows() { + let mut image = RawImageData::Integer(vec![1000, 1000, 1000]); + + assert!(apply_black_level_delta_v_to_data( + &mut image, + 1, + 3, + 1, + 1, + &[100.0, 200.0], + )); + let RawImageData::Integer(pixels) = image else { + panic!("Expected integer pixel data"); + }; + assert_eq!(pixels, [1000, 900, 800]); + } + + #[test] + fn ignores_black_level_delta_with_wrong_row_count() { + let mut image = RawImageData::Integer(vec![1000, 2000, 500, 100]); + + assert!(!apply_black_level_delta_v_to_data( + &mut image, + 2, + 2, + 1, + 0, + &[100.0, 200.0, 300.0], + )); + let RawImageData::Integer(pixels) = image else { + panic!("Expected integer pixel data"); + }; + assert_eq!(pixels, [1000, 2000, 500, 100]); + } +} From 06c15028924c1accc2ffe4db253e2b7f5876abef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Oct 2026 21:59:40 +0000 Subject: [PATCH 2/2] Apply remaining changes Co-authored-by: borgfriend <3630448+borgfriend@users.noreply.github.com> --- src-tauri/src/raw_processing.rs | 78 --------------------------------- 1 file changed, 78 deletions(-) diff --git a/src-tauri/src/raw_processing.rs b/src-tauri/src/raw_processing.rs index 623bbf93fd..7370d3d190 100644 --- a/src-tauri/src/raw_processing.rs +++ b/src-tauri/src/raw_processing.rs @@ -475,81 +475,3 @@ pub fn get_fast_demosaic_scale_factor( 1.0 } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn diagnose_attached_dng_raw_metadata() { - let bytes = std::fs::read("../20070819-IMG_2427.dng").unwrap(); - let source = RawSource::new_from_slice(&bytes); - let decoder = rawler::get_decoder(&source).unwrap(); - let raw_image = decoder - .raw_image(&source, &RawDecodeParams::default(), false) - .unwrap(); - - eprintln!( - "camera={:?}/{:?}, blacklevel={:?}, whitelevel={:?}, wb={:?}, matrix={:?}", - raw_image.make, - raw_image.model, - raw_image.blacklevel, - raw_image.whitelevel, - raw_image.wb_coeffs, - raw_image.color_matrix - ); - } - - #[test] - fn applies_per_row_black_level_deltas() { - let mut image = RawImageData::Integer(vec![1000, 2000, 500, 100]); - - assert!(apply_black_level_delta_v_to_data( - &mut image, - 2, - 2, - 1, - 0, - &[100.0, 200.0], - )); - let RawImageData::Integer(pixels) = image else { - panic!("Expected integer pixel data"); - }; - assert_eq!(pixels, [900, 1900, 300, 0]); - } - - #[test] - fn aligns_black_level_deltas_to_active_area_rows() { - let mut image = RawImageData::Integer(vec![1000, 1000, 1000]); - - assert!(apply_black_level_delta_v_to_data( - &mut image, - 1, - 3, - 1, - 1, - &[100.0, 200.0], - )); - let RawImageData::Integer(pixels) = image else { - panic!("Expected integer pixel data"); - }; - assert_eq!(pixels, [1000, 900, 800]); - } - - #[test] - fn ignores_black_level_delta_with_wrong_row_count() { - let mut image = RawImageData::Integer(vec![1000, 2000, 500, 100]); - - assert!(!apply_black_level_delta_v_to_data( - &mut image, - 2, - 2, - 1, - 0, - &[100.0, 200.0, 300.0], - )); - let RawImageData::Integer(pixels) = image else { - panic!("Expected integer pixel data"); - }; - assert_eq!(pixels, [1000, 2000, 500, 100]); - } -}