diff --git a/src/camera/mod.rs b/src/camera/mod.rs index 7e25aa0..2daf81d 100644 --- a/src/camera/mod.rs +++ b/src/camera/mod.rs @@ -13,9 +13,10 @@ use ptp::{ CommandCode, DevicePropCode, FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiCustomSetting, FujiCustomSettingName, FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, - FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiShadowTone, FujiSharpness, - FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, - ObjectFormat, UsbMode, + FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiMonochromaticColorTemperature, + FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect, + FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, ObjectFormat, + UsbMode, }, structs::{DeviceInfo, ObjectInfo}, }; @@ -82,6 +83,8 @@ impl Camera { get_dynamic_range => FujiDynamicRange, get_dynamic_range_priority => FujiDynamicRangePriority, get_film_simulation => FujiFilmSimulation, + get_monochromatic_color_temperature => FujiMonochromaticColorTemperature, + get_monochromatic_color_tint => FujiMonochromaticColorTint, get_grain_effect => FujiGrainEffect, get_white_balance => FujiWhiteBalance, get_high_iso_nr => FujiHighISONR, @@ -107,6 +110,8 @@ impl Camera { set_dynamic_range(value: &FujiDynamicRange) => (), set_dynamic_range_priority(value: &FujiDynamicRangePriority) => (), set_film_simulation(value: &FujiFilmSimulation) => (), + set_monochromatic_color_temperature(value: &FujiMonochromaticColorTemperature) => (), + set_monochromatic_color_tint(value: &FujiMonochromaticColorTint) => (), set_grain_effect(value: &FujiGrainEffect) => (), set_white_balance(value: &FujiWhiteBalance) => (), set_high_iso_nr(value: &FujiHighISONR) => (), @@ -234,6 +239,7 @@ pub trait CameraImpl { } fn chunk_size(&self) -> usize { + // Conservative estimate. Could go up to 15.75 * 1024^2 on the X-T5 but only gained 200ms. 1024 * 1024 } @@ -324,6 +330,8 @@ pub trait CameraImpl { dynamic_range: FujiDynamicRange => DevicePropCode::FujiStillCustomSettingDynamicRange, dynamic_range_priority: FujiDynamicRangePriority => DevicePropCode::FujiStillCustomSettingDynamicRangePriority, film_simulation: FujiFilmSimulation => DevicePropCode::FujiStillCustomSettingFilmSimulation, + monochromatic_color_temperature: FujiMonochromaticColorTemperature => DevicePropCode::FujiStillCustomSettingMonochromaticColorTemperature, + monochromatic_color_tint: FujiMonochromaticColorTint => DevicePropCode::FujiStillCustomSettingMonochromaticColorTint, grain_effect: FujiGrainEffect => DevicePropCode::FujiStillCustomSettingGrainEffect, white_balance: FujiWhiteBalance => DevicePropCode::FujiStillCustomSettingWhiteBalance, high_iso_nr: FujiHighISONR => DevicePropCode::FujiStillCustomSettingHighISONR, diff --git a/src/camera/ptp/hex.rs b/src/camera/ptp/hex.rs index 9ebecec..df3054c 100644 --- a/src/camera/ptp/hex.rs +++ b/src/camera/ptp/hex.rs @@ -136,7 +136,7 @@ impl PtpDeserialize for ContainerCode { pub enum DevicePropCode { FujiUsbMode = 0xd16e, FujiRawConversionRun = 0xD183, - FujiRawConversionSettings = 0xD185, + FujiRawConversionProfile = 0xD185, FujiStillCustomSetting = 0xD18C, FujiStillCustomSettingName = 0xD18D, FujiStillCustomSettingImageSize = 0xD18E, @@ -144,8 +144,8 @@ pub enum DevicePropCode { FujiStillCustomSettingDynamicRange = 0xD190, FujiStillCustomSettingDynamicRangePriority = 0xD191, FujiStillCustomSettingFilmSimulation = 0xD192, - // TODO: 0xD193 All 0s - // TODO: 0xD194 All 0s + FujiStillCustomSettingMonochromaticColorTemperature = 0xD193, + FujiStillCustomSettingMonochromaticColorTint = 0xD194, FujiStillCustomSettingGrainEffect = 0xD195, FujiStillCustomSettingColorChromeEffect = 0xD196, FujiStillCustomSettingColorChromeFXBlue = 0xD197, @@ -1211,6 +1211,8 @@ macro_rules! fuji_i16 { }; } +fuji_i16!(FujiMonochromaticColorTemperature, -18.0, 18.0, 1.0, 10i16); +fuji_i16!(FujiMonochromaticColorTint, -18.0, 18.0, 1.0, 10i16); fuji_i16!(FujiWhiteBalanceShift, -9.0, 9.0, 1.0, 1i16); fuji_i16!(FujiWhiteBalanceTemperature, 2500.0, 10000.0, 10.0, 1i16); fuji_i16!(FujiHighlightTone, -2.0, 4.0, 0.5, 10i16); diff --git a/src/cli/common/film.rs b/src/cli/common/film.rs index 031df1f..90d60f0 100644 --- a/src/cli/common/film.rs +++ b/src/cli/common/film.rs @@ -3,16 +3,25 @@ use clap::Args; use crate::camera::ptp::hex::{ FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, - FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiShadowTone, FujiSharpness, - FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, + FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiMonochromaticColorTemperature, + FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect, + FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, }; #[derive(Args, Debug)] pub struct FilmSimulationOptions { - /// The Fujifilm film simulation to use + /// Fujifilm Film Simulation #[clap(long)] pub simulation: Option, + /// Monochromatic Color Temperature (only applicable to B&W film simulations) + #[clap(long)] + pub monochromatic_color_temperature: Option, + + /// Monochromatic Color Tint (only applicable to B&W film simulations) + #[clap(long)] + pub monochromatic_color_tint: Option, + /// The output image resolution #[clap(long)] pub size: Option, diff --git a/src/cli/simulation/mod.rs b/src/cli/simulation/mod.rs index 096934a..b26bb22 100644 --- a/src/cli/simulation/mod.rs +++ b/src/cli/simulation/mod.rs @@ -5,8 +5,9 @@ use crate::{ FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiCustomSetting, FujiCustomSettingName, FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiHighlightTone, FujiImageQuality, FujiImageSize, - FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect, FujiWhiteBalance, - FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, + FujiMonochromaticColorTemperature, FujiMonochromaticColorTint, FujiShadowTone, + FujiSharpness, FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, + FujiWhiteBalanceTemperature, }, usb, }; @@ -102,56 +103,94 @@ fn handle_list(json: bool, device_id: Option<&str>) -> anyhow::Result<()> { #[serde(rename_all = "camelCase")] pub struct FilmSimulationRepr { pub name: FujiCustomSettingName, - pub simulation: FujiFilmSimulation, pub size: FujiImageSize, pub quality: FujiImageQuality, + pub simulation: FujiFilmSimulation, + pub monochromatic_color_temperature: FujiMonochromaticColorTemperature, + pub monochromatic_color_tint: FujiMonochromaticColorTint, pub highlight: FujiHighlightTone, pub shadow: FujiShadowTone, pub color: FujiColor, pub sharpness: FujiSharpness, pub clarity: FujiClarity, + pub noise_reduction: FujiHighISONR, + pub grain: FujiGrainEffect, + pub color_chrome_effect: FujiColorChromeEffect, + pub color_chrome_fx_blue: FujiColorChromeFXBlue, + pub smooth_skin_effect: FujiSmoothSkinEffect, pub white_balance: FujiWhiteBalance, pub white_balance_shift_red: FujiWhiteBalanceShift, pub white_balance_shift_blue: FujiWhiteBalanceShift, pub white_balance_temperature: FujiWhiteBalanceTemperature, pub dynamic_range: FujiDynamicRange, pub dynamic_range_priority: FujiDynamicRangePriority, - pub noise_reduction: FujiHighISONR, - pub grain: FujiGrainEffect, - pub color_chrome_effect: FujiColorChromeEffect, - pub color_chrome_fx_blue: FujiColorChromeFXBlue, - pub smooth_skin_effect: FujiSmoothSkinEffect, } impl fmt::Display for FilmSimulationRepr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "Name: {}", self.name)?; - writeln!(f, "Simulation: {}", self.simulation)?; writeln!(f, "Size: {}", self.size)?; writeln!(f, "Quality: {}", self.quality)?; - writeln!(f, "Highlights: {}", self.highlight)?; - writeln!(f, "Shadows: {}", self.shadow)?; + + writeln!(f, "Simulation: {}", self.simulation)?; + + match self.simulation { + FujiFilmSimulation::Monochrome + | FujiFilmSimulation::MonochromeYe + | FujiFilmSimulation::MonochromeR + | FujiFilmSimulation::MonochromeG + | FujiFilmSimulation::AcrosSTD + | FujiFilmSimulation::AcrosYe + | FujiFilmSimulation::AcrosR + | FujiFilmSimulation::AcrosG => { + writeln!( + f, + "Monochromatic Color Temperature: {}", + self.monochromatic_color_temperature + )?; + writeln!( + f, + "Monochromatic Color Tint: {}", + self.monochromatic_color_tint + )?; + } + _ => {} + }; + + if self.dynamic_range_priority == FujiDynamicRangePriority::Off { + writeln!(f, "Highlights: {}", self.highlight)?; + writeln!(f, "Shadows: {}", self.shadow)?; + } + writeln!(f, "Color: {}", self.color)?; writeln!(f, "Sharpness: {}", self.sharpness)?; writeln!(f, "Clarity: {}", self.clarity)?; + writeln!(f, "Noise Reduction: {}", self.noise_reduction)?; + writeln!(f, "Grain: {}", self.grain)?; + writeln!(f, "Color Chrome Effect: {}", self.color_chrome_effect)?; + writeln!(f, "Color Chrome FX Blue: {}", self.color_chrome_fx_blue)?; + writeln!(f, "Smooth Skin Effect: {}", self.smooth_skin_effect)?; + writeln!(f, "White Balance: {}", self.white_balance)?; writeln!( f, "White Balance Shift (R/B): {} / {}", self.white_balance_shift_red, self.white_balance_shift_blue )?; - writeln!( - f, - "White Balance Temperature: {}K", - self.white_balance_temperature - )?; - writeln!(f, "Dynamic Range: {}", self.dynamic_range)?; - writeln!(f, "Dynamic Range Priority: {}", self.dynamic_range_priority)?; - writeln!(f, "Noise Reduction: {}", self.noise_reduction)?; - writeln!(f, "Grain: {}", self.grain)?; - writeln!(f, "Color Chrome Effect: {}", self.color_chrome_effect)?; - writeln!(f, "Color Chrome FX Blue: {}", self.color_chrome_fx_blue)?; - writeln!(f, "Smooth Skin Effect: {}", self.smooth_skin_effect) + + if self.white_balance == FujiWhiteBalance::Temperature { + writeln!( + f, + "White Balance Temperature: {}K", + self.white_balance_temperature + )?; + } + + if self.dynamic_range_priority == FujiDynamicRangePriority::Off { + writeln!(f, "Dynamic Range: {}", self.dynamic_range)?; + } + + writeln!(f, "Dynamic Range Priority: {}", self.dynamic_range_priority) } } @@ -161,25 +200,27 @@ fn handle_get(json: bool, device_id: Option<&str>, slot: FujiCustomSetting) -> a let repr = FilmSimulationRepr { name: camera.get_custom_setting_name()?, - simulation: camera.get_film_simulation()?, size: camera.get_image_size()?, quality: camera.get_image_quality()?, + simulation: camera.get_film_simulation()?, + monochromatic_color_temperature: camera.get_monochromatic_color_temperature()?, + monochromatic_color_tint: camera.get_monochromatic_color_tint()?, highlight: camera.get_highlight_tone()?, shadow: camera.get_shadow_tone()?, color: camera.get_color()?, sharpness: camera.get_sharpness()?, clarity: camera.get_clarity()?, + noise_reduction: camera.get_high_iso_nr()?, + grain: camera.get_grain_effect()?, + color_chrome_effect: camera.get_color_chrome_effect()?, + color_chrome_fx_blue: camera.get_color_chrome_fx_blue()?, + smooth_skin_effect: camera.get_smooth_skin_effect()?, white_balance: camera.get_white_balance()?, white_balance_shift_red: camera.get_white_balance_shift_red()?, white_balance_shift_blue: camera.get_white_balance_shift_blue()?, white_balance_temperature: camera.get_white_balance_temperature()?, dynamic_range: camera.get_dynamic_range()?, dynamic_range_priority: camera.get_dynamic_range_priority()?, - noise_reduction: camera.get_high_iso_nr()?, - grain: camera.get_grain_effect()?, - color_chrome_effect: camera.get_color_chrome_effect()?, - color_chrome_fx_blue: camera.get_color_chrome_fx_blue()?, - smooth_skin_effect: camera.get_smooth_skin_effect()?, }; if json { @@ -219,6 +260,48 @@ fn handle_set( camera.set_film_simulation(simulation)?; } + if options.monochromatic_color_temperature.is_some() + || options.monochromatic_color_tint.is_some() + { + let simulation = if let Some(simulation) = &options.simulation { + simulation + } else { + &camera.get_film_simulation()? + }; + + let is_bnw = match *simulation { + FujiFilmSimulation::Monochrome + | FujiFilmSimulation::MonochromeYe + | FujiFilmSimulation::MonochromeR + | FujiFilmSimulation::MonochromeG + | FujiFilmSimulation::AcrosSTD + | FujiFilmSimulation::AcrosYe + | FujiFilmSimulation::AcrosR + | FujiFilmSimulation::AcrosG => true, + _ => false, + }; + + if let Some(monochromatic_color_temperature) = &options.monochromatic_color_temperature { + if is_bnw { + camera.set_monochromatic_color_temperature(monochromatic_color_temperature)?; + } else { + warn!( + "A B&W film simulation is not selected, refusing to set monochromatic color temperature" + ); + } + } + + if let Some(monochromatic_color_tint) = &options.monochromatic_color_tint { + if is_bnw { + camera.set_monochromatic_color_tint(monochromatic_color_tint)?; + } else { + warn!( + "A B&W film simulation is not selected, refusing to set monochromatic color tint" + ); + } + } + } + if let Some(color) = &options.color { camera.set_color(color)?; } @@ -291,8 +374,10 @@ fn handle_set( &camera.get_dynamic_range_priority()? }; + let is_drp_off = *dynamic_range_priority == FujiDynamicRangePriority::Off; + if let Some(dynamic_range) = &options.dynamic_range { - if *dynamic_range_priority == FujiDynamicRangePriority::Off { + if is_drp_off { camera.set_dynamic_range(dynamic_range)?; } else { warn!("Dynamic Range Priority is enabled, refusing to set dynamic range"); @@ -300,7 +385,7 @@ fn handle_set( } if let Some(highlights) = &options.highlight { - if *dynamic_range_priority == FujiDynamicRangePriority::Off { + if is_drp_off { camera.set_highlight_tone(highlights)?; } else { warn!("Dynamic Range Priority is enabled, refusing to set highlight tone"); @@ -308,7 +393,7 @@ fn handle_set( } if let Some(shadows) = &options.shadow { - if *dynamic_range_priority == FujiDynamicRangePriority::Off { + if is_drp_off { camera.set_shadow_tone(shadows)?; } else { warn!("Dynamic Range Priority is enabled, refusing to set shadow tone");