Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src-tauri/src/color_encoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::sync::OnceLock;

const SRGB_TO_LINEAR_CUTOFF: f32 = 0.04045;
const LINEAR_TO_SRGB_CUTOFF: f32 = 0.0031308;
const SRGB_ALPHA: f32 = 0.055;
const SRGB_SLOPE: f32 = 12.92;
const SRGB_GAMMA: f32 = 2.4;
const SRGB_INV_GAMMA: f32 = 1.0 / SRGB_GAMMA;

pub fn srgb_to_linear(value: f32) -> f32 {
debug_assert!(
value.is_finite(),
"srgb_to_linear received non-finite input"
);

if value <= SRGB_TO_LINEAR_CUTOFF {
value / SRGB_SLOPE
} else {
((value + SRGB_ALPHA) / (1.0 + SRGB_ALPHA)).powf(SRGB_GAMMA)
}
}

pub fn linear_to_srgb(value: f32) -> f32 {
debug_assert!(
value.is_finite(),
"linear_to_srgb received non-finite input"
);

let clamped: f32 = value.clamp(0.0, 1.0);
__encode(clamped)
}

pub fn linear_to_srgb_extended(value: f32) -> f32 {
debug_assert!(
value.is_finite(),
"linear_to_srgb_extended received non-finite input"
);

let floored: f32 = value.max(0.0);
__encode(floored)
}

pub fn srgb_to_linear_lut() -> &'static [f32; 256] {
static LUT: OnceLock<[f32; 256]> = OnceLock::new();
LUT.get_or_init(|| {
let mut lut: [f32; 256] = [0.0f32; 256];
for (i, entry) in lut.iter_mut().enumerate() {
let code: f32 = i as f32 / 255.0;
*entry = srgb_to_linear(code);
}
lut
})
}

fn __encode(value: f32) -> f32 {
debug_assert!(value >= 0.0, "__encode requires a non-negative input");

if value <= LINEAR_TO_SRGB_CUTOFF {
value * SRGB_SLOPE
} else {
(1.0 + SRGB_ALPHA) * value.powf(SRGB_INV_GAMMA) - SRGB_ALPHA
}
}
15 changes: 13 additions & 2 deletions src-tauri/src/culling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ fn calculate_exposure_metric(image: &GrayImage) -> f64 {
return 0.0;
}

let histogram_total: u32 = histogram.channels[0].iter().sum();
assert_eq!(
histogram_total,
image.width() * image.height(),
"Histogram total mismatch: expected {} pixels, found {}",
image.width() * image.height(),
histogram_total
);

let clip_threshold_dark = 5;
let clip_threshold_bright = 250;

Expand Down Expand Up @@ -135,8 +144,10 @@ fn analyze_image(

let file_bytes = std::fs::read(path).map_err(|e| e.to_string())?;

let img = image_loader::load_base_image_from_bytes(&file_bytes, path, true, settings, None)
.map_err(|e| e.to_string())?;
let img = image_loader::load_base_image_tagged(&file_bytes, path, true, settings, None)
.map_err(|e| e.to_string())?
.into_srgb()
.into_inner();

let (width, height) = img.dimensions();
let thumbnail = img.thumbnail(ANALYSIS_DIM, ANALYSIS_DIM);
Expand Down
11 changes: 5 additions & 6 deletions src-tauri/src/denoising.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use crate::app_settings::load_settings;
use crate::app_state::AppState;
use crate::file_management::parse_virtual_path;
use crate::formats::is_raw_file;
use crate::image_loader::load_base_image_from_bytes;
use crate::image_processing::apply_cpu_default_raw_processing;
use crate::image_loader::load_base_image_tagged;
use base64::{Engine as _, engine::general_purpose};
use image::{DynamicImage, GenericImageView, ImageFormat, Rgb, Rgb32FImage};
use rayon::prelude::*;
Expand Down Expand Up @@ -306,14 +304,15 @@ fn denoise_image(
return Err("File not found".to_string());
}

let is_raw = is_raw_file(&path_str);
let settings = load_settings(app_handle.clone()).unwrap_or_default();

let _ = app_handle.emit("denoise-progress", "Loading image...");

let file_bytes = fs::read(path).map_err(|e| e.to_string())?;
let dynamic_img = load_base_image_from_bytes(&file_bytes, &path_str, false, &settings, None)
.map_err(|e| e.to_string())?;
let dynamic_img = load_base_image_tagged(&file_bytes, &path_str, false, &settings, None)
.map_err(|e| e.to_string())?
.into_srgb()
.into_inner();

let rgb_img_for_denoiser = dynamic_img.to_rgb32f();

Expand Down
13 changes: 8 additions & 5 deletions src-tauri/src/file_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ use crate::gpu_processing;
use crate::image_loader;
use crate::image_processing::GpuContext;
use crate::image_processing::{
Crop, ImageMetadata, apply_coarse_rotation, apply_cpu_default_raw_processing, apply_crop,
apply_flip, apply_geometry_warp, apply_rotation, auto_results_to_json,
get_all_adjustments_from_json, perform_auto_analysis,
Crop, ImageMetadata, apply_coarse_rotation, apply_crop, apply_flip, apply_geometry_warp,
apply_rotation, auto_results_to_json, get_all_adjustments_from_json, perform_auto_analysis,
};
use crate::mask_generation::MaskDefinition;
use crate::preset_converter;
Expand Down Expand Up @@ -1744,7 +1743,7 @@ pub fn generate_thumbnail_data(
}
crate::image_processing::apply_cpu_agx_tonemap(&mut final_image);
} else if is_raw {
apply_cpu_default_raw_processing(&mut final_image);
final_image = crate::image_processing::apply_linear_to_srgb(final_image);
}
}

Expand Down Expand Up @@ -2772,7 +2771,11 @@ pub async fn apply_auto_adjustments_to_paths(
)
.map_err(|e| e.to_string())?;

let auto_results = perform_auto_analysis(&image);
let auto_results = perform_auto_analysis(
&image,
crate::image_processing::AutoMeteringMode::Percentile,
crate::formats::is_raw_file(&source_path_str),
);
let auto_adjustments_json = auto_results_to_json(&auto_results);

let mut existing_metadata = crate::exif_processing::load_sidecar(&sidecar_path);
Expand Down
14 changes: 3 additions & 11 deletions src-tauri/src/hdr_deghosting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ use crate::app_settings::AppSettings;
use crate::exif_processing::{read_exposure_time_secs, read_iso};
use crate::formats::is_raw_file;
use crate::image_loader::load_base_image_from_bytes;
use crate::image_processing::{
apply_cpu_default_raw_processing, apply_linear_to_srgb, apply_srgb_to_linear,
};
use crate::image_processing::{apply_linear_to_srgb, apply_srgb_to_linear};
use crate::panorama_stitching::{Feature, KeyPoint, Match};
use crate::panorama_utils::{processing, stitching};
use image::{DynamicImage, GenericImageView, Rgb32FImage};
Expand Down Expand Up @@ -110,7 +108,7 @@ pub fn align_hdr_frames(frames: &mut [HdrFrame], app_handle: &AppHandle) {
let reference_index = frames.len() / 2;
let detections: Vec<FrameDetection> = frames
.iter()
.map(|frame| detect_frame_features(&frame.1, &brief_pairs, is_raw_file(&frame.0)))
.map(|frame| detect_frame_features(&frame.1, &brief_pairs))
.collect();
for index in 0..frames.len() {
if index == reference_index {
Expand Down Expand Up @@ -145,14 +143,8 @@ pub fn align_hdr_frames(frames: &mut [HdrFrame], app_handle: &AppHandle) {
fn detect_frame_features(
image: &DynamicImage,
brief_pairs: &[(Point2<i32>, Point2<i32>)],
source_is_raw: bool,
) -> FrameDetection {
let mut detection_proxy = image.clone();
if source_is_raw {
apply_cpu_default_raw_processing(&mut detection_proxy);
} else {
detection_proxy = apply_linear_to_srgb(detection_proxy);
}
let detection_proxy = apply_linear_to_srgb(image.clone());
let gray_full = image::imageops::colorops::grayscale(&detection_proxy.to_rgb8());
let (width, height) = gray_full.dimensions();
let (small_width, small_height, scale_factor) =
Expand Down
40 changes: 24 additions & 16 deletions src-tauri/src/image_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use std::collections::HashMap;
use std::fs;
use std::panic;
use std::path::Path;
use std::sync::OnceLock;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
Expand All @@ -48,20 +47,29 @@ struct PatchMaskInfo {
sub_masks: Vec<SubMask>,
}

fn srgb_to_linear_lut() -> &'static [f32; 256] {
static LUT: OnceLock<[f32; 256]> = OnceLock::new();
LUT.get_or_init(|| {
let mut lut = [0.0f32; 256];
for (i, v) in lut.iter_mut().enumerate() {
let x = i as f32 / 255.0;
*v = if x <= 0.04045 {
x / 12.92
} else {
((x + 0.055) / 1.055).powf(2.4)
};
}
lut
})
pub fn load_base_image_tagged(
bytes: &[u8],
path_for_ext_check: &str,
use_fast_raw_dev: bool,
settings: &AppSettings,
cancel_token: Option<(Arc<AtomicUsize>, usize)>,
) -> Result<crate::tagged_image::TaggedImage> {
let is_raw: bool = is_raw_file(path_for_ext_check);
let image: DynamicImage = load_base_image_from_bytes(
bytes,
path_for_ext_check,
use_fast_raw_dev,
settings,
cancel_token,
)?;

let encoding: crate::tagged_image::Encoding = if is_raw {
crate::tagged_image::Encoding::Linear
} else {
crate::tagged_image::Encoding::Srgb
};

Ok(crate::tagged_image::TaggedImage::new(image, encoding))
}

pub fn load_and_composite(
Expand Down Expand Up @@ -553,7 +561,7 @@ pub fn composite_patches_on_image(
let decoded_patches = decoded_patches?;

let mut composited_image = base_image.clone();
let lut = srgb_to_linear_lut();
let lut = crate::color_encoding::srgb_to_linear_lut();

let get_color = |patch: &DecodedPatch, r: u8, g: u8, b: u8| -> (f32, f32, f32) {
if patch.is_srgb_encoded {
Expand Down
Loading
Loading