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
9 changes: 2 additions & 7 deletions src-tauri/src/android_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,12 @@ pub fn initialize_android(window: &tauri::WebviewWindow) {
.with_env(|env_22| {
let verifier_context =
unsafe { VerifierJObject::from_raw(env_22, raw_context) };
rustls_platform_verifier::android::init_with_env(
env_22,
verifier_context,
)
rustls_platform_verifier::android::init_with_env(env_22, verifier_context)
})
.into_outcome()
{
jni22::Outcome::Ok(()) => {
log::info!(
"Successfully initialized rustls-platform-verifier on Android."
);
log::info!("Successfully initialized rustls-platform-verifier on Android.");
}
jni22::Outcome::Err(error) => {
log::error!(
Expand Down
5 changes: 5 additions & 0 deletions src-tauri/src/cache_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ pub fn calculate_geometry_hash(adjustments: &serde_json::Value) -> u64 {

adjustments["orientationSteps"].as_u64().hash(&mut hasher);

// Negative conversion changes the decoded base, so it must key the base cache.
if let Some(nc) = adjustments.get("negativeConversion") {
nc.to_string().hash(&mut hasher);
}

for key in GEOMETRY_KEYS {
if let Some(val) = adjustments.get(key) {
key.hash(&mut hasher);
Expand Down
94 changes: 88 additions & 6 deletions src-tauri/src/file_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,22 @@ pub struct ImageFile {
path: String,
modified: u64,
is_edited: bool,
is_negative: bool,
rating: u8,
tags: Option<Vec<String>>,
exif: Option<HashMap<String, String>>,
is_virtual_copy: bool,
}

/// True when the sidecar has an enabled in-library negative conversion.
pub(crate) fn adjustments_is_negative(adjustments: &serde_json::Value) -> bool {
adjustments
.get("negativeConversion")
.and_then(|nc| nc.get("enabled"))
.and_then(|e| e.as_bool())
.unwrap_or(false)
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ImportSettings {
Expand Down Expand Up @@ -351,7 +361,7 @@ pub fn list_images_in_dir(path: String, app_handle: AppHandle) -> Result<Vec<Ima

let sidecar_path = path_buf.with_file_name(sidecar_filename);

let (is_edited, tags, rating) = {
let (is_edited, is_negative, tags, rating) = {
let mut metadata = crate::exif_processing::load_sidecar(&sidecar_path);

if enable_xmp_sync
Expand All @@ -369,13 +379,15 @@ pub fn list_images_in_dir(path: String, app_handle: AppHandle) -> Result<Vec<Ima
is_raw,
tm_override,
);
(edited, metadata.tags, metadata.rating)
let negative = adjustments_is_negative(&metadata.adjustments);
(edited, negative, metadata.tags, metadata.rating)
};

file_results.push(ImageFile {
path: virtual_path,
modified,
is_edited,
is_negative,
tags,
exif: None,
is_virtual_copy,
Expand Down Expand Up @@ -474,7 +486,7 @@ pub fn list_images_recursive(

let sidecar_path = path_buf.with_file_name(sidecar_filename);

let (is_edited, tags, rating) = {
let (is_edited, is_negative, tags, rating) = {
let mut metadata = crate::exif_processing::load_sidecar(&sidecar_path);

if enable_xmp_sync
Expand All @@ -492,13 +504,15 @@ pub fn list_images_recursive(
is_raw,
tm_override,
);
(edited, metadata.tags, metadata.rating)
let negative = adjustments_is_negative(&metadata.adjustments);
(edited, negative, metadata.tags, metadata.rating)
};

file_results.push(ImageFile {
path: virtual_path,
modified,
is_edited,
is_negative,
tags,
exif: None,
is_virtual_copy,
Expand Down Expand Up @@ -738,7 +752,7 @@ pub fn get_album_images(

let is_virtual_copy = virtual_path.contains("?vc=");

let (is_edited, tags, rating) = {
let (is_edited, is_negative, tags, rating) = {
let mut metadata = crate::exif_processing::load_sidecar(&sidecar_path);

if enable_xmp_sync
Expand All @@ -756,13 +770,15 @@ pub fn get_album_images(
is_raw,
tm_override,
);
(edited, metadata.tags, metadata.rating)
let negative = adjustments_is_negative(&metadata.adjustments);
(edited, negative, metadata.tags, metadata.rating)
};

Some(ImageFile {
path: virtual_path,
modified,
is_edited,
is_negative,
tags,
exif: None,
is_virtual_copy,
Expand Down Expand Up @@ -1604,6 +1620,59 @@ pub fn increment_thumbnail_progress(state: &AppState, app_handle: &AppHandle) {
}
}

/// Regenerate grid/filmstrip thumbnails for the given paths and push each to the
/// frontend via `thumbnail-generated`.
pub fn regenerate_thumbnails_for_paths(paths: &[String], app_handle: &AppHandle) {
let state = app_handle.state::<AppState>();

// Evict any pre-change cached base so the new one is decoded.
if let Ok(mut cache) = state.thumbnail_geometry_cache.lock() {
for p in paths {
cache.remove(p);
}
}

let settings = load_settings(app_handle.clone()).unwrap_or_default();
add_to_thumbnail_queue(&state, paths.len(), app_handle);

let thumb_cache_dir = match resolve_thumbnail_cache_dir(app_handle) {
Ok(dir) => dir,
Err(e) => {
log::warn!("Unable to initialize thumbnail cache directory: {}", e);
for path in paths {
emit_thumbnail_cache_setup_error(app_handle, path, &e);
}
for _ in 0..paths.len() {
increment_thumbnail_progress(&state, app_handle);
}
return;
}
};

let gpu_context = crate::gpu_processing::get_or_init_gpu_context(&state, app_handle).ok();

paths.par_iter().for_each(|path_str| {
let result = generate_single_thumbnail_and_cache(
path_str,
&thumb_cache_dir,
gpu_context.as_ref(),
None,
true,
app_handle,
&settings,
);

if let Some((thumbnail_data, rating, is_edited)) = result {
let _ = app_handle.emit(
"thumbnail-generated",
serde_json::json!({ "path": path_str, "data": thumbnail_data, "rating": rating, "is_edited": is_edited }),
);
}

increment_thumbnail_progress(&state, app_handle);
});
}

pub fn resolve_lens_params_in_adjustments(
adjustments: &mut Value,
exif_data: &Option<HashMap<String, String>>,
Expand Down Expand Up @@ -2072,7 +2141,20 @@ pub fn save_metadata_and_update_thumbnail(
);
}

// `negativeConversion` is owned by set_negative_conversion; a normal save must
// preserve the sidecar's own value rather than the frontend's.
let preserved_negative = metadata.adjustments.get("negativeConversion").cloned();
metadata.adjustments = final_adjustments;
if let Some(obj) = metadata.adjustments.as_object_mut() {
match preserved_negative {
Some(nc) => {
obj.insert("negativeConversion".to_string(), nc);
}
None => {
obj.remove("negativeConversion");
}
}
}

let json_string = serde_json::to_string_pretty(&metadata).map_err(|e| e.to_string())?;
std::fs::write(&sidecar_path, json_string).map_err(|e| e.to_string())?;
Expand Down
22 changes: 22 additions & 0 deletions src-tauri/src/image_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,34 @@ pub fn load_and_composite(
composite_patches_on_image(&base_image, adjustments)
}

/// Decode a base image and apply a stored negative conversion if the sidecar flags one.
pub fn load_base_image_from_bytes(
bytes: &[u8],
path_for_ext_check: &str,
use_fast_raw_dev: bool,
settings: &AppSettings,
cancel_token: Option<(Arc<AtomicUsize>, usize)>,
) -> Result<DynamicImage> {
let image = load_base_image_raw(
bytes,
path_for_ext_check,
use_fast_raw_dev,
settings,
cancel_token,
)?;
Ok(crate::negative_conversion::maybe_apply_negative(
image,
path_for_ext_check,
))
}

/// The raw decode, without applying a stored negative conversion.
pub fn load_base_image_raw(
bytes: &[u8],
path_for_ext_check: &str,
use_fast_raw_dev: bool,
settings: &AppSettings,
cancel_token: Option<(Arc<AtomicUsize>, usize)>,
) -> Result<DynamicImage> {
let highlight_compression = settings.raw_highlight_compression.unwrap_or(2.5);
let linear_mode = settings.linear_raw_mode.clone();
Expand Down
3 changes: 1 addition & 2 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2316,8 +2316,7 @@ pub fn run() {
lens_correction::get_lensfun_lenses_for_maker,
lens_correction::autodetect_lens,
lens_correction::get_lens_distortion_params,
negative_conversion::preview_negative_conversion,
negative_conversion::convert_negatives,
negative_conversion::set_negative_conversion,
])
.build(tauri::generate_context!())
.expect("error while building tauri application")
Expand Down
3 changes: 1 addition & 2 deletions src-tauri/src/lut_processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,7 @@ pub fn parse_lut_file(path_str: &str) -> anyhow::Result<Lut> {
.and_then(|s| s.to_str())
.unwrap_or("cube")
.to_lowercase();
let uri_bytes =
read_android_content_uri(path_str).map_err(|e| anyhow!("{}", e))?;
let uri_bytes = read_android_content_uri(path_str).map_err(|e| anyhow!("{}", e))?;
(ext, Some(uri_bytes))
}
#[cfg(not(target_os = "android"))]
Expand Down
Loading
Loading