Compare commits
23 Commits
fix/issue-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1cc4d2e27 | ||
|
|
09e02f93c9 | ||
|
|
b34b6e4ffb | ||
|
|
70d925a46b | ||
|
|
ec60c2b598 | ||
|
|
e30dd662b5 | ||
|
|
9c80ae7f80 | ||
|
|
75629fbbdc | ||
|
|
761f16bbea | ||
|
|
35d5dc6b2d | ||
|
|
2d1a157f33 | ||
|
|
6470ed153d | ||
|
|
c883e4d26a | ||
|
|
b3c938cde1 | ||
|
|
b6a3bc3219 | ||
|
|
148e6cb0cb | ||
|
|
2a5930ca1e | ||
|
|
929b5dcba5 | ||
|
|
098e8163b9 | ||
|
|
6db7d6f56d | ||
|
|
bab2f17533 | ||
|
|
d838594eb2 | ||
|
|
c73466f5fa |
@ -2,6 +2,7 @@
|
||||
name = "photo_editor"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
libraw-sys = "0.1.1"
|
||||
@ -17,4 +18,3 @@ path = "src/main.rs"
|
||||
opt-level = "z" # Optimize for size
|
||||
lto = true # Enable link-time optimization
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
215
src/main.rs
215
src/main.rs
@ -55,6 +55,44 @@ fn sanitize_path(
|
||||
}
|
||||
}
|
||||
|
||||
/// Map libraw error codes to human-readable strings.
|
||||
fn libraw_error_string(code: c_int) -> &'static str {
|
||||
match code {
|
||||
0 => "Success",
|
||||
-1 => "General failure (LIBRAW_ERROR_UNSUCCESSFUL)",
|
||||
-2 => "Unsupported file format",
|
||||
-3 => "File open failed (LIBRAW_ERROR_FILE_OPEN_FAIL)",
|
||||
-4 => "File is not recognized as RAW",
|
||||
-5 => "Out of memory",
|
||||
-6 => "File read failed",
|
||||
-7 => "Internal error",
|
||||
-8 => "Unsupported operation",
|
||||
_ => "Unknown error code",
|
||||
}
|
||||
}
|
||||
|
||||
/// RAII guard for libraw memory cleanup.
|
||||
/// Automatically calls libraw_close() and libraw_free() on drop,
|
||||
/// eliminating the risk of leaked handles or double-free.
|
||||
struct LibRawGuard(*mut libraw_data_t);
|
||||
|
||||
impl Drop for LibRawGuard {
|
||||
fn drop(&mut self) {
|
||||
if !self.0.is_null() {
|
||||
unsafe {
|
||||
libraw_close(self.0);
|
||||
libraw_free(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LibRawGuard {
|
||||
fn ptr(&self) -> *mut libraw_data_t {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Photo Editor - A Rust-based photo editor with RAW image processing capabilities using libraw
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
@ -112,9 +150,51 @@ struct Args {
|
||||
tint: i32,
|
||||
}
|
||||
|
||||
/// Validate that all adjustment parameters are within their documented ranges.
|
||||
fn validate_params(args: &Args) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if args.exposure < -2.0 || args.exposure > 2.0 {
|
||||
return Err("--exposure must be between -2.0 and 2.0".into());
|
||||
}
|
||||
let range = -100i32..=100i32;
|
||||
if !range.contains(&args.contrast) {
|
||||
return Err("--contrast must be between -100 and 100".into());
|
||||
}
|
||||
if !range.contains(&args.highlights) {
|
||||
return Err("--highlights must be between -100 and 100".into());
|
||||
}
|
||||
if !range.contains(&args.shadows) {
|
||||
return Err("--shadows must be between -100 and 100".into());
|
||||
}
|
||||
if !range.contains(&args.whites) {
|
||||
return Err("--whites must be between -100 and 100".into());
|
||||
}
|
||||
if !range.contains(&args.blacks) {
|
||||
return Err("--blacks must be between -100 and 100".into());
|
||||
}
|
||||
if !range.contains(&args.clarity) {
|
||||
return Err("--clarity must be between -100 and 100".into());
|
||||
}
|
||||
if !range.contains(&args.vibrance) {
|
||||
return Err("--vibrance must be between -100 and 100".into());
|
||||
}
|
||||
if !range.contains(&args.saturation) {
|
||||
return Err("--saturation must be between -100 and 100".into());
|
||||
}
|
||||
if !range.contains(&args.temperature) {
|
||||
return Err("--temperature must be between -100 and 100".into());
|
||||
}
|
||||
if !range.contains(&args.tint) {
|
||||
return Err("--tint must be between -100 and 100".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
// Validate parameter ranges before processing
|
||||
validate_params(&args)?;
|
||||
|
||||
println!("Photo Editor v0.1");
|
||||
println!("==================");
|
||||
println!("Input file: {}", args.input);
|
||||
@ -137,6 +217,29 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
e
|
||||
})?;
|
||||
|
||||
// Validate RAW file format by extension
|
||||
let raw_exts = [
|
||||
"arw", "cr2", "cr3", "crw", "dcr", "dng", "erf", "fff",
|
||||
"iiq", "k25", "kdc", "mdc", "mef", "mos", "mrw", "nef",
|
||||
"orf", "pef", "ptx", "pxn", "raf", "raw", "rdc", "sr2",
|
||||
"srf", "srw", "x3f",
|
||||
];
|
||||
if let Some(ext) = input_path.extension().and_then(|s| s.to_str()) {
|
||||
let ext_lower = ext.to_lowercase();
|
||||
if !raw_exts.contains(&ext_lower.as_str()) {
|
||||
eprintln!(
|
||||
"Error: '{}' is not a supported RAW format (extension: .{})",
|
||||
input_path.display(),
|
||||
ext_lower
|
||||
);
|
||||
eprintln!("Supported formats: {}", raw_exts.join(", "));
|
||||
return Err("Unsupported file format".into());
|
||||
}
|
||||
} else {
|
||||
eprintln!("Error: Input file has no extension: {}", input_path.display());
|
||||
return Err("File has no extension".into());
|
||||
}
|
||||
|
||||
// Sanitize and resolve output path
|
||||
let output_path = sanitize_path(&args.output, false).map_err(|e| {
|
||||
eprintln!("Error: {}", e);
|
||||
@ -145,12 +248,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
println!("\n[INFO] Attempting RAW image processing with libraw integration...");
|
||||
|
||||
// Initialize libraw
|
||||
let lr = unsafe { libraw_init(LIBRAW_OPTIONS_NONE) };
|
||||
if lr.is_null() {
|
||||
// Initialize libraw with RAII guard for automatic cleanup
|
||||
let lr_raw = unsafe { libraw_init(LIBRAW_OPTIONS_NONE) };
|
||||
if lr_raw.is_null() {
|
||||
eprintln!("Error: Failed to initialize libraw");
|
||||
return Err("Failed to initialize libraw".into());
|
||||
}
|
||||
let lr_guard = LibRawGuard(lr_raw);
|
||||
let lr = lr_guard.ptr();
|
||||
|
||||
// Open the RAW file
|
||||
let input_cstr = CString::new(input_path.to_string_lossy().as_bytes())
|
||||
@ -158,8 +263,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let ret = unsafe { libraw_open_file(lr, input_cstr.as_ptr()) };
|
||||
|
||||
if ret != LIBRAW_SUCCESS {
|
||||
eprintln!("Error: Failed to open file. Error code: {}", ret);
|
||||
unsafe { libraw_close(lr) };
|
||||
eprintln!("Error: Failed to open file: {}", libraw_error_string(ret));
|
||||
return Err("Failed to open file".into());
|
||||
}
|
||||
|
||||
@ -168,8 +272,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Process the RAW data
|
||||
let ret = unsafe { libraw_unpack(lr) };
|
||||
if ret != LIBRAW_SUCCESS {
|
||||
eprintln!("Error: Failed to unpack RAW data. Error code: {}", ret);
|
||||
unsafe { libraw_close(lr) };
|
||||
eprintln!("Error: Failed to unpack RAW data: {}", libraw_error_string(ret));
|
||||
return Err("Failed to unpack RAW data".into());
|
||||
}
|
||||
|
||||
@ -183,8 +286,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Process the image
|
||||
let ret = unsafe { libraw_dcraw_process(lr) };
|
||||
if ret != LIBRAW_SUCCESS {
|
||||
eprintln!("Error: Failed to process image. Error code: {}", ret);
|
||||
unsafe { libraw_close(lr) };
|
||||
eprintln!("Error: Failed to process image: {}", libraw_error_string(ret));
|
||||
return Err("Failed to process image".into());
|
||||
}
|
||||
|
||||
@ -235,18 +337,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
};
|
||||
|
||||
if ret != LIBRAW_SUCCESS {
|
||||
eprintln!("Error: Failed to write output file. Error code: {}", ret);
|
||||
unsafe { libraw_close(lr) };
|
||||
eprintln!("Error: Failed to write output file: {}", libraw_error_string(ret));
|
||||
return Err("Failed to write output file".into());
|
||||
}
|
||||
|
||||
println!("[INFO] Processed image written to disk");
|
||||
|
||||
// Clean up
|
||||
unsafe {
|
||||
libraw_close(lr);
|
||||
}
|
||||
|
||||
println!("\n[SUCCESS] Photo editing completed successfully with libraw integration!");
|
||||
println!(
|
||||
"[NOTICE] The edited image has been saved as: {}",
|
||||
@ -257,43 +353,102 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
fn apply_image_parameters(lr: *mut libraw_data_t, args: &Args) {
|
||||
if lr.is_null() {
|
||||
eprintln!("Warning: libraw pointer is null, skipping parameter application");
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
// Set various processing parameters based on user input
|
||||
|
||||
// Apply exposure adjustment
|
||||
if args.exposure != 0.0 {
|
||||
libraw_set_bright(lr, args.exposure);
|
||||
if args.exposure != 0.0 && !lr.is_null() {
|
||||
// Convert EV stops to brightness multiplier: EV 1.0 → 2x, EV -1.0 → 0.5x
|
||||
let multiplier = 2.0f32.powf(args.exposure);
|
||||
libraw_set_bright(lr, multiplier);
|
||||
}
|
||||
|
||||
// Apply contrast (using highlight parameters for demonstration)
|
||||
if args.contrast != 0 {
|
||||
let value = args.contrast.min(100).max(-100) as c_int;
|
||||
libraw_set_highlight(lr, value);
|
||||
// Apply contrast (map to brightness multiplier for tonal range)
|
||||
if args.contrast != 0 && !lr.is_null() {
|
||||
let value = args.contrast.min(100).max(-100) as f32 / 100.0 * 100.0;
|
||||
libraw_set_bright(lr, value);
|
||||
}
|
||||
|
||||
// Apply highlights adjustment
|
||||
if args.highlights != 0 {
|
||||
if args.highlights != 0 && !lr.is_null() {
|
||||
let value = args.highlights.min(100).max(-100) as c_int;
|
||||
libraw_set_highlight(lr, value);
|
||||
}
|
||||
|
||||
// Apply clarity (using noise reduction parameters)
|
||||
if args.clarity != 0 {
|
||||
if args.clarity != 0 && !lr.is_null() {
|
||||
let value = args.clarity.min(100).max(-100) as c_int;
|
||||
libraw_set_fbdd_noiserd(lr, value);
|
||||
}
|
||||
|
||||
// Apply saturation
|
||||
if args.saturation != 0 {
|
||||
let value = 1.0 + args.saturation.min(100).max(-100) as f32 / 100.0;
|
||||
libraw_set_gamma(lr, 0, value); // Apply gamma adjustment to simulate saturation
|
||||
// Apply shadows adjustment (map to brightness for dark areas)
|
||||
if args.shadows != 0 && !lr.is_null() {
|
||||
let value = args.shadows.min(100).max(-100) as f32 / 100.0 * 200.0;
|
||||
libraw_set_bright(lr, value);
|
||||
}
|
||||
|
||||
// Apply whites adjustment (map to highlight recovery)
|
||||
if args.whites != 0 && !lr.is_null() {
|
||||
let value = args.whites.min(100).max(-100) as c_int;
|
||||
libraw_set_highlight(lr, value);
|
||||
}
|
||||
|
||||
// Apply blacks adjustment (map to brightness offset for dark point)
|
||||
if args.blacks != 0 && !lr.is_null() {
|
||||
let value = args.blacks.min(100).max(-100) as f32 / 100.0 * -100.0;
|
||||
libraw_set_bright(lr, value);
|
||||
}
|
||||
|
||||
// Apply vibrance adjustment (selective saturation, map to saturation multiplier)
|
||||
if args.vibrance != 0 && !lr.is_null() {
|
||||
let value = 1.0 + args.vibrance.min(100).max(-100) as f32 / 200.0;
|
||||
libraw_set_gamma(lr, 0, value);
|
||||
}
|
||||
|
||||
// Apply temperature adjustment (white balance via user_mul)
|
||||
if args.temperature != 0 && !lr.is_null() {
|
||||
let temp_mul = [
|
||||
256i16 as c_int,
|
||||
256i16 as c_int + args.temperature.min(100).max(-100) as c_int,
|
||||
256i16 as c_int,
|
||||
256i16 as c_int,
|
||||
];
|
||||
libraw_set_user_mul(lr, temp_mul.as_ptr());
|
||||
libraw_set_use_auto_wb(lr, 0);
|
||||
}
|
||||
|
||||
// Apply tint adjustment (green-magenta white balance axis)
|
||||
if args.tint != 0 && !lr.is_null() {
|
||||
let tint_mul = [
|
||||
256i16 as c_int + args.tint.min(100).max(-100) as c_int,
|
||||
256i16 as c_int,
|
||||
256i16 as c_int,
|
||||
256i16 as c_int - args.tint.min(100).max(-100) as c_int,
|
||||
];
|
||||
libraw_set_user_mul(lr, tint_mul.as_ptr());
|
||||
libraw_set_use_auto_wb(lr, 0);
|
||||
}
|
||||
|
||||
// Apply saturation (use saturation multiplier, not gamma)
|
||||
if args.saturation != 0 && !lr.is_null() {
|
||||
let mul = (256i32 + args.saturation.min(100).max(-100) as i32 * 2) as c_int;
|
||||
let sat_mul = [mul, mul, mul, mul];
|
||||
libraw_set_saturation_mul(lr, sat_mul.as_ptr());
|
||||
}
|
||||
|
||||
// Set output color space (sRGB in this case)
|
||||
libraw_set_output_color(lr, 1); // 1 = sRGB
|
||||
if !lr.is_null() {
|
||||
libraw_set_output_color(lr, 1); // 1 = sRGB
|
||||
}
|
||||
|
||||
// Set output bits per sample
|
||||
libraw_set_output_bps(lr, 8);
|
||||
if !lr.is_null() {
|
||||
libraw_set_output_bps(lr, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user