257 lines
7.4 KiB
Rust
257 lines
7.4 KiB
Rust
use clap::Parser;
|
|
use libraw_sys::*;
|
|
use std::ffi::CString;
|
|
use std::os::raw::c_int;
|
|
use std::path::Path;
|
|
|
|
/// Photo Editor - A Rust-based photo editor with RAW image processing capabilities using libraw
|
|
#[derive(Parser, Debug)]
|
|
#[command(author, version, about, long_about = None)]
|
|
struct Args {
|
|
/// Input raw image file to process
|
|
#[arg(short, long)]
|
|
input: String,
|
|
|
|
/// Output processed image file
|
|
#[arg(short, long)]
|
|
output: String,
|
|
|
|
/// Exposure adjustment (-2.0 to 2.0)
|
|
#[arg(long, default_value = "0.0")]
|
|
exposure: f32,
|
|
|
|
/// Contrast adjustment (-100 to 100)
|
|
#[arg(long, default_value = "0")]
|
|
contrast: i32,
|
|
|
|
/// Highlights adjustment (-100 to 100)
|
|
#[arg(long, default_value = "0")]
|
|
highlights: i32,
|
|
|
|
/// Shadows adjustment (-100 to 100)
|
|
#[arg(long, default_value = "0")]
|
|
shadows: i32,
|
|
|
|
/// Whites adjustment (-100 to 100)
|
|
#[arg(long, default_value = "0")]
|
|
whites: i32,
|
|
|
|
/// Blacks adjustment (-100 to 100)
|
|
#[arg(long, default_value = "0")]
|
|
blacks: i32,
|
|
|
|
/// Clarity adjustment (-100 to 100)
|
|
#[arg(long, default_value = "0")]
|
|
clarity: i32,
|
|
|
|
/// Vibrance adjustment (-100 to 100)
|
|
#[arg(long, default_value = "0")]
|
|
vibrance: i32,
|
|
|
|
/// Saturation adjustment (-100 to 100)
|
|
#[arg(long, default_value = "0")]
|
|
saturation: i32,
|
|
|
|
/// Temperature adjustment (-100 to 100)
|
|
#[arg(long, default_value = "0")]
|
|
temperature: i32,
|
|
|
|
/// Tint adjustment (-100 to 100)
|
|
#[arg(long, default_value = "0")]
|
|
tint: i32,
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let args = Args::parse();
|
|
|
|
println!("Photo Editor v0.1");
|
|
println!("==================");
|
|
println!("Input file: {}", args.input);
|
|
println!("Output file: {}", args.output);
|
|
println!("Exposure: {}", args.exposure);
|
|
println!("Contrast: {}", args.contrast);
|
|
println!("Highlights: {}", args.highlights);
|
|
println!("Shadows: {}", args.shadows);
|
|
println!("Whites: {}", args.whites);
|
|
println!("Blacks: {}", args.blacks);
|
|
println!("Clarity: {}", args.clarity);
|
|
println!("Vibrance: {}", args.vibrance);
|
|
println!("Saturation: {}", args.saturation);
|
|
println!("Temperature: {}", args.temperature);
|
|
println!("Tint: {}", args.tint);
|
|
|
|
// Check if input file exists
|
|
if !Path::new(&args.input).exists() {
|
|
eprintln!("Error: Input file '{}' does not exist.", args.input);
|
|
return Err("Input file not found".into());
|
|
}
|
|
|
|
println!("\n[INFO] Attempting RAW image processing with libraw integration...");
|
|
|
|
// Initialize libraw
|
|
let lr = unsafe { libraw_init(LIBRAW_OPTIONS_NONE) };
|
|
if lr.is_null() {
|
|
eprintln!("Error: Failed to initialize libraw");
|
|
return Err("Failed to initialize libraw".into());
|
|
}
|
|
|
|
// Open the RAW file
|
|
let input_cstr = CString::new(args.input.clone()).unwrap();
|
|
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) };
|
|
return Err("Failed to open file".into());
|
|
}
|
|
|
|
println!("[INFO] File opened successfully");
|
|
|
|
// 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) };
|
|
return Err("Failed to unpack RAW data".into());
|
|
}
|
|
|
|
println!("[INFO] RAW data unpacked successfully");
|
|
|
|
// Adjust image parameters based on user input
|
|
apply_image_parameters(lr, &args);
|
|
|
|
println!("[INFO] Image parameters applied");
|
|
|
|
// 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) };
|
|
return Err("Failed to process image".into());
|
|
}
|
|
|
|
println!("[INFO] Image processed successfully");
|
|
|
|
// Determine output format from extension
|
|
let ext = Path::new(&args.output)
|
|
.extension()
|
|
.and_then(|s| s.to_str())
|
|
.unwrap_or("jpg")
|
|
.to_lowercase();
|
|
|
|
let ret = match ext.as_str() {
|
|
"jpg" | "jpeg" => {
|
|
// For JPEG output, we'll write directly to file
|
|
let output_cstr = CString::new(args.output.clone()).unwrap();
|
|
unsafe { libraw_dcraw_ppm_tiff_writer(lr, output_cstr.as_ptr()) }
|
|
}
|
|
_ => {
|
|
// For other formats, attempt to convert
|
|
let output_cstr = CString::new(args.output.clone()).unwrap();
|
|
unsafe { libraw_dcraw_ppm_tiff_writer(lr, output_cstr.as_ptr()) }
|
|
}
|
|
};
|
|
|
|
if ret != LIBRAW_SUCCESS {
|
|
eprintln!("Error: Failed to write output file. Error code: {}", ret);
|
|
unsafe { libraw_close(lr) };
|
|
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: {}",
|
|
args.output
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn apply_image_parameters(lr: *mut libraw_data_t, args: &Args) {
|
|
unsafe {
|
|
// Set various processing parameters based on user input
|
|
|
|
// Apply exposure adjustment
|
|
if args.exposure != 0.0 {
|
|
libraw_set_bright(lr, args.exposure);
|
|
}
|
|
|
|
// 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 highlights adjustment
|
|
if args.highlights != 0 {
|
|
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 {
|
|
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
|
|
}
|
|
|
|
// Set output color space (sRGB in this case)
|
|
libraw_set_output_color(lr, 1); // 1 = sRGB
|
|
|
|
// Set output bits per sample
|
|
libraw_set_output_bps(lr, 8);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_args_parsing() {
|
|
let args = Args::parse_from([
|
|
"photo_editor",
|
|
"--input",
|
|
"test.raw",
|
|
"--output",
|
|
"output.jpg",
|
|
]);
|
|
assert_eq!(args.input, "test.raw");
|
|
assert_eq!(args.output, "output.jpg");
|
|
}
|
|
|
|
#[test]
|
|
fn test_args_with_positive_values() {
|
|
let args = Args::parse_from([
|
|
"photo_editor",
|
|
"--input",
|
|
"input.raw",
|
|
"--output",
|
|
"output.jpg",
|
|
"--exposure",
|
|
"0.5",
|
|
"--contrast",
|
|
"20",
|
|
"--highlights",
|
|
"10",
|
|
]);
|
|
assert_eq!(args.input, "input.raw");
|
|
assert_eq!(args.output, "output.jpg");
|
|
assert_eq!(args.exposure, 0.5);
|
|
assert_eq!(args.contrast, 20);
|
|
assert_eq!(args.highlights, 10);
|
|
}
|
|
}
|