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
31 changes: 31 additions & 0 deletions src-tauri/src/formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ pub fn is_raw_file<P: AsRef<Path>>(path: P) -> bool {
.any(|(raw_ext, _)| raw_ext.eq_ignore_ascii_case(ext))
}

/// Returns true when the bytes start with the signature of a standard (non-RAW) image
/// format. Used to catch files whose RAW extension does not match their contents, e.g.
/// JPEGs that cloud photo exports save under the original `.ARW`/`.RW2` filename.
pub fn has_non_raw_image_signature(bytes: &[u8]) -> bool {
const JPEG: &[u8] = &[0xFF, 0xD8, 0xFF];
const PNG: &[u8] = &[0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];

bytes.starts_with(JPEG) || bytes.starts_with(PNG)
}

pub fn is_supported_image_file<P: AsRef<Path>>(path: P) -> bool {
let path = path.as_ref();

Expand Down Expand Up @@ -116,3 +126,24 @@ pub fn is_supported_image_file<P: AsRef<Path>>(path: P) -> bool {
.iter()
.any(|non_raw_ext| non_raw_ext.eq_ignore_ascii_case(ext))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn detects_standard_image_signatures() {
assert!(has_non_raw_image_signature(&[0xFF, 0xD8, 0xFF, 0xE0, 0x00]));
assert!(has_non_raw_image_signature(&[
0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A
]));
}

#[test]
fn ignores_raw_signatures() {
// Little-endian TIFF header used by ARW, NEF, DNG, ...
assert!(!has_non_raw_image_signature(&[0x49, 0x49, 0x2A, 0x00]));
assert!(!has_non_raw_image_signature(b"FUJIFILMCCD-RAW"));
assert!(!has_non_raw_image_signature(&[]));
}
}
18 changes: 12 additions & 6 deletions src-tauri/src/image_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::app_settings::{AppSettings, load_settings};
use crate::app_state::{AppState, LoadedImage};
use crate::exif_processing;
use crate::file_management::{parse_virtual_path, read_file_mapped};
use crate::formats::is_raw_file;
use crate::formats::{has_non_raw_image_signature, is_raw_file};
use crate::image_processing::ImageMetadata;
use crate::image_processing::{
apply_orientation, apply_srgb_to_linear, remove_raw_artifacts_and_enhance,
Expand Down Expand Up @@ -104,10 +104,16 @@ pub fn load_base_image_from_bytes(
bytes,
);

if is_raw_file(path_for_ext_check)
&& !use_fast_raw_dev
&& settings.use_apple_raw9.unwrap_or(false)
{
let mut decode_as_raw = is_raw_file(path_for_ext_check);
if decode_as_raw && has_non_raw_image_signature(bytes) {
log::info!(
"'{}' has a RAW extension but contains a standard image, decoding it as such",
path_for_ext_check
);
decode_as_raw = false;
}

if decode_as_raw && !use_fast_raw_dev && settings.use_apple_raw9.unwrap_or(false) {
if let Some((tracker, generation)) = &cancel_token
&& tracker.load(Ordering::SeqCst) != *generation
{
Expand All @@ -128,7 +134,7 @@ pub fn load_base_image_from_bytes(
}
}

if is_raw_file(path_for_ext_check) {
if decode_as_raw {
match panic::catch_unwind(move || {
crate::raw_processing::develop_raw_image(
bytes,
Expand Down