feat: color space, lens optimizer

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2025-10-19 19:50:45 +01:00
parent 91d5d5b16b
commit fb4610bdaa
4 changed files with 217 additions and 74 deletions

View File

@@ -11,12 +11,12 @@ use ptp::{
Ptp, Ptp,
hex::{ hex::{
CommandCode, DevicePropCode, FujiClarity, FujiColor, FujiColorChromeEffect, CommandCode, DevicePropCode, FujiClarity, FujiColor, FujiColorChromeEffect,
FujiColorChromeFXBlue, FujiCustomSetting, FujiCustomSettingName, FujiDynamicRange, FujiColorChromeFXBlue, FujiColorSpace, FujiCustomSetting, FujiCustomSettingName,
FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect,
FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiMonochromaticColorTemperature, FujiHighISONR, FujiHighlightTone, FujiImageQuality, FujiImageSize,
FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect, FujiLensModulationOptimizer, FujiMonochromaticColorTemperature, FujiMonochromaticColorTint,
FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, ObjectFormat, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect, FujiWhiteBalance,
UsbMode, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, ObjectFormat, UsbMode,
}, },
structs::{DeviceInfo, ObjectInfo}, structs::{DeviceInfo, ObjectInfo},
}; };
@@ -99,6 +99,8 @@ impl Camera {
get_color_chrome_effect => FujiColorChromeEffect, get_color_chrome_effect => FujiColorChromeEffect,
get_color_chrome_fx_blue => FujiColorChromeFXBlue, get_color_chrome_fx_blue => FujiColorChromeFXBlue,
get_smooth_skin_effect => FujiSmoothSkinEffect, get_smooth_skin_effect => FujiSmoothSkinEffect,
get_lens_modulation_optimizer => FujiLensModulationOptimizer,
get_color_space => FujiColorSpace,
} }
camera_with_ptp! { camera_with_ptp! {
@@ -126,6 +128,8 @@ impl Camera {
set_color_chrome_effect(value: &FujiColorChromeEffect) => (), set_color_chrome_effect(value: &FujiColorChromeEffect) => (),
set_color_chrome_fx_blue(value: &FujiColorChromeFXBlue) => (), set_color_chrome_fx_blue(value: &FujiColorChromeFXBlue) => (),
set_smooth_skin_effect(value: &FujiSmoothSkinEffect) => (), set_smooth_skin_effect(value: &FujiSmoothSkinEffect) => (),
set_lens_modulation_optimizer(value: &FujiLensModulationOptimizer) => (),
set_color_space(value: &FujiColorSpace) => (),
} }
} }
@@ -323,28 +327,30 @@ pub trait CameraImpl<P: rusb::UsbContext> {
} }
camera_impl_custom_settings! { camera_impl_custom_settings! {
active_custom_setting: FujiCustomSetting => DevicePropCode::FujiStillCustomSetting, active_custom_setting: FujiCustomSetting => DevicePropCode::FujiCustomSetting,
custom_setting_name: FujiCustomSettingName => DevicePropCode::FujiStillCustomSettingName, custom_setting_name: FujiCustomSettingName => DevicePropCode::FujiCustomSettingName,
image_size: FujiImageSize => DevicePropCode::FujiStillCustomSettingImageSize, image_size: FujiImageSize => DevicePropCode::FujiCustomSettingImageSize,
image_quality: FujiImageQuality => DevicePropCode::FujiStillCustomSettingImageQuality, image_quality: FujiImageQuality => DevicePropCode::FujiCustomSettingImageQuality,
dynamic_range: FujiDynamicRange => DevicePropCode::FujiStillCustomSettingDynamicRange, dynamic_range: FujiDynamicRange => DevicePropCode::FujiCustomSettingDynamicRange,
dynamic_range_priority: FujiDynamicRangePriority => DevicePropCode::FujiStillCustomSettingDynamicRangePriority, dynamic_range_priority: FujiDynamicRangePriority => DevicePropCode::FujiCustomSettingDynamicRangePriority,
film_simulation: FujiFilmSimulation => DevicePropCode::FujiStillCustomSettingFilmSimulation, film_simulation: FujiFilmSimulation => DevicePropCode::FujiCustomSettingFilmSimulation,
monochromatic_color_temperature: FujiMonochromaticColorTemperature => DevicePropCode::FujiStillCustomSettingMonochromaticColorTemperature, monochromatic_color_temperature: FujiMonochromaticColorTemperature => DevicePropCode::FujiCustomSettingMonochromaticColorTemperature,
monochromatic_color_tint: FujiMonochromaticColorTint => DevicePropCode::FujiStillCustomSettingMonochromaticColorTint, monochromatic_color_tint: FujiMonochromaticColorTint => DevicePropCode::FujiCustomSettingMonochromaticColorTint,
grain_effect: FujiGrainEffect => DevicePropCode::FujiStillCustomSettingGrainEffect, grain_effect: FujiGrainEffect => DevicePropCode::FujiCustomSettingGrainEffect,
white_balance: FujiWhiteBalance => DevicePropCode::FujiStillCustomSettingWhiteBalance, white_balance: FujiWhiteBalance => DevicePropCode::FujiCustomSettingWhiteBalance,
high_iso_nr: FujiHighISONR => DevicePropCode::FujiStillCustomSettingHighISONR, high_iso_nr: FujiHighISONR => DevicePropCode::FujiCustomSettingHighISONR,
highlight_tone: FujiHighlightTone => DevicePropCode::FujiStillCustomSettingHighlightTone, highlight_tone: FujiHighlightTone => DevicePropCode::FujiCustomSettingHighlightTone,
shadow_tone: FujiShadowTone => DevicePropCode::FujiStillCustomSettingShadowTone, shadow_tone: FujiShadowTone => DevicePropCode::FujiCustomSettingShadowTone,
color: FujiColor => DevicePropCode::FujiStillCustomSettingColor, color: FujiColor => DevicePropCode::FujiCustomSettingColor,
sharpness: FujiSharpness => DevicePropCode::FujiStillCustomSettingSharpness, sharpness: FujiSharpness => DevicePropCode::FujiCustomSettingSharpness,
clarity: FujiClarity => DevicePropCode::FujiStillCustomSettingClarity, clarity: FujiClarity => DevicePropCode::FujiCustomSettingClarity,
white_balance_shift_red: FujiWhiteBalanceShift => DevicePropCode::FujiStillCustomSettingWhiteBalanceShiftRed, white_balance_shift_red: FujiWhiteBalanceShift => DevicePropCode::FujiCustomSettingWhiteBalanceShiftRed,
white_balance_shift_blue: FujiWhiteBalanceShift => DevicePropCode::FujiStillCustomSettingWhiteBalanceShiftBlue, white_balance_shift_blue: FujiWhiteBalanceShift => DevicePropCode::FujiCustomSettingWhiteBalanceShiftBlue,
white_balance_temperature: FujiWhiteBalanceTemperature => DevicePropCode::FujiStillCustomSettingWhiteBalanceTemperature, white_balance_temperature: FujiWhiteBalanceTemperature => DevicePropCode::FujiCustomSettingWhiteBalanceTemperature,
color_chrome_effect: FujiColorChromeEffect => DevicePropCode::FujiStillCustomSettingColorChromeEffect, color_chrome_effect: FujiColorChromeEffect => DevicePropCode::FujiCustomSettingColorChromeEffect,
color_chrome_fx_blue: FujiColorChromeFXBlue => DevicePropCode::FujiStillCustomSettingColorChromeFXBlue, color_chrome_fx_blue: FujiColorChromeFXBlue => DevicePropCode::FujiCustomSettingColorChromeFXBlue,
smooth_skin_effect: FujiSmoothSkinEffect => DevicePropCode::FujiStillCustomSettingSmoothSkinEffect, smooth_skin_effect: FujiSmoothSkinEffect => DevicePropCode::FujiCustomSettingSmoothSkinEffect,
lens_modulation_optimizer: FujiLensModulationOptimizer => DevicePropCode::FujiCustomSettingLensModulationOptimizer,
color_space: FujiColorSpace => DevicePropCode::FujiCustomSettingColorSpace,
} }
} }

View File

@@ -137,31 +137,31 @@ pub enum DevicePropCode {
FujiUsbMode = 0xd16e, FujiUsbMode = 0xd16e,
FujiRawConversionRun = 0xD183, FujiRawConversionRun = 0xD183,
FujiRawConversionProfile = 0xD185, FujiRawConversionProfile = 0xD185,
FujiStillCustomSetting = 0xD18C, FujiCustomSetting = 0xD18C,
FujiStillCustomSettingName = 0xD18D, FujiCustomSettingName = 0xD18D,
FujiStillCustomSettingImageSize = 0xD18E, FujiCustomSettingImageSize = 0xD18E,
FujiStillCustomSettingImageQuality = 0xD18F, FujiCustomSettingImageQuality = 0xD18F,
FujiStillCustomSettingDynamicRange = 0xD190, FujiCustomSettingDynamicRange = 0xD190,
FujiStillCustomSettingDynamicRangePriority = 0xD191, FujiCustomSettingDynamicRangePriority = 0xD191,
FujiStillCustomSettingFilmSimulation = 0xD192, FujiCustomSettingFilmSimulation = 0xD192,
FujiStillCustomSettingMonochromaticColorTemperature = 0xD193, FujiCustomSettingMonochromaticColorTemperature = 0xD193,
FujiStillCustomSettingMonochromaticColorTint = 0xD194, FujiCustomSettingMonochromaticColorTint = 0xD194,
FujiStillCustomSettingGrainEffect = 0xD195, FujiCustomSettingGrainEffect = 0xD195,
FujiStillCustomSettingColorChromeEffect = 0xD196, FujiCustomSettingColorChromeEffect = 0xD196,
FujiStillCustomSettingColorChromeFXBlue = 0xD197, FujiCustomSettingColorChromeFXBlue = 0xD197,
FujiStillCustomSettingSmoothSkinEffect = 0xD198, FujiCustomSettingSmoothSkinEffect = 0xD198,
FujiStillCustomSettingWhiteBalance = 0xD199, FujiCustomSettingWhiteBalance = 0xD199,
FujiStillCustomSettingWhiteBalanceShiftRed = 0xD19A, FujiCustomSettingWhiteBalanceShiftRed = 0xD19A,
FujiStillCustomSettingWhiteBalanceShiftBlue = 0xD19B, FujiCustomSettingWhiteBalanceShiftBlue = 0xD19B,
FujiStillCustomSettingWhiteBalanceTemperature = 0xD19C, FujiCustomSettingWhiteBalanceTemperature = 0xD19C,
FujiStillCustomSettingHighlightTone = 0xD19D, FujiCustomSettingHighlightTone = 0xD19D,
FujiStillCustomSettingShadowTone = 0xD19E, FujiCustomSettingShadowTone = 0xD19E,
FujiStillCustomSettingColor = 0xD19F, FujiCustomSettingColor = 0xD19F,
FujiStillCustomSettingSharpness = 0xD1A0, FujiCustomSettingSharpness = 0xD1A0,
FujiStillCustomSettingHighISONR = 0xD1A1, FujiCustomSettingHighISONR = 0xD1A1,
FujiStillCustomSettingClarity = 0xD1A2, FujiCustomSettingClarity = 0xD1A2,
// TODO: 0xD1A3 All 1s FujiCustomSettingLensModulationOptimizer = 0xD1A3,
// TODO: 0xD1A4 All 1s FujiCustomSettingColorSpace = 0xD1A4,
// TODO: 0xD1A5 All 7s // TODO: 0xD1A5 All 7s
FujiBatteryInfo2 = 0xD36B, FujiBatteryInfo2 = 0xD36B,
} }
@@ -1114,6 +1114,104 @@ impl FromStr for FujiHighISONR {
} }
} }
#[repr(u16)]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Serialize,
IntoPrimitive,
TryFromPrimitive,
PtpSerialize,
PtpDeserialize,
EnumIter,
)]
pub enum FujiLensModulationOptimizer {
Off = 0x2,
On = 0x1,
}
impl fmt::Display for FujiLensModulationOptimizer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Off => write!(f, "Off"),
Self::On => write!(f, "On"),
}
}
}
impl FromStr for FujiLensModulationOptimizer {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
let input = s.trim().to_lowercase();
match input.as_str() {
"off" | "false" => return Ok(Self::Off),
"on" | "true" => return Ok(Self::On),
_ => {}
}
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
if let Some(best) = suggest_closest(s, &choices) {
bail!("Unknown lens modulation optimizer '{s}'. Did you mean '{best}'?");
}
bail!("Unknown lens modulation optimizer '{s}'");
}
}
#[repr(u16)]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Serialize,
IntoPrimitive,
TryFromPrimitive,
PtpSerialize,
PtpDeserialize,
EnumIter,
)]
pub enum FujiColorSpace {
SRGB = 0x2,
AdobeRGB = 0x1,
}
impl fmt::Display for FujiColorSpace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SRGB => write!(f, "sRGB"),
Self::AdobeRGB => write!(f, "Adobe RGB"),
}
}
}
impl FromStr for FujiColorSpace {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
let input = s.trim().to_lowercase();
match input.as_str() {
"s" | "srgb" => return Ok(Self::SRGB),
"adobe" | "adobergb" => return Ok(Self::AdobeRGB),
_ => {}
}
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
if let Some(best) = suggest_closest(s, &choices) {
bail!("Unknown color space '{s}'. Did you mean '{best}'?");
}
bail!("Unknown color space '{s}'");
}
}
macro_rules! fuji_i16 { macro_rules! fuji_i16 {
($name:ident, $min:expr, $max:expr, $step:expr, $scale:literal) => { ($name:ident, $min:expr, $max:expr, $step:expr, $scale:literal) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PtpSerialize, PtpDeserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PtpSerialize, PtpDeserialize)]

View File

@@ -1,11 +1,11 @@
use clap::Args; use clap::Args;
use crate::camera::ptp::hex::{ use crate::camera::ptp::hex::{
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiDynamicRange, FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiColorSpace,
FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR,
FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiMonochromaticColorTemperature, FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiLensModulationOptimizer,
FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect, FujiMonochromaticColorTemperature, FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness,
FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
}; };
#[derive(Args, Debug)] #[derive(Args, Debug)]
@@ -93,4 +93,12 @@ pub struct FilmSimulationOptions {
/// Smooth Skin Effect /// Smooth Skin Effect
#[clap(long)] #[clap(long)]
pub smooth_skin_effect: Option<FujiSmoothSkinEffect>, pub smooth_skin_effect: Option<FujiSmoothSkinEffect>,
/// Lens Modulation Optimizer
#[clap(long)]
pub lens_modulation_optimizer: Option<FujiLensModulationOptimizer>,
/// Color Space
#[clap(long)]
pub color_space: Option<FujiColorSpace>,
} }

View File

@@ -2,12 +2,12 @@ use std::fmt;
use crate::{ use crate::{
camera::ptp::hex::{ camera::ptp::hex::{
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiCustomSetting, FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiColorSpace,
FujiCustomSettingName, FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiCustomSetting, FujiCustomSettingName, FujiDynamicRange, FujiDynamicRangePriority,
FujiGrainEffect, FujiHighISONR, FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiHighlightTone, FujiImageQuality,
FujiMonochromaticColorTemperature, FujiMonochromaticColorTint, FujiShadowTone, FujiImageSize, FujiLensModulationOptimizer, FujiMonochromaticColorTemperature,
FujiSharpness, FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect,
FujiWhiteBalanceTemperature, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
}, },
usb, usb,
}; };
@@ -16,7 +16,7 @@ use super::common::{
file::{Input, Output}, file::{Input, Output},
film::FilmSimulationOptions, film::FilmSimulationOptions,
}; };
use clap::Subcommand; use clap::{Args, Subcommand};
use log::warn; use log::warn;
use serde::Serialize; use serde::Serialize;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -40,9 +40,8 @@ pub enum SimulationCmd {
/// Simulation slot number /// Simulation slot number
slot: FujiCustomSetting, slot: FujiCustomSetting,
/// The name of the slot #[command(flatten)]
#[clap(long)] set_film_simulation_options: SetFilmSimulationOptions,
name: Option<FujiCustomSettingName>,
#[command(flatten)] #[command(flatten)]
film_simulation_options: FilmSimulationOptions, film_simulation_options: FilmSimulationOptions,
@@ -69,6 +68,13 @@ pub enum SimulationCmd {
}, },
} }
#[derive(Args, Debug)]
pub struct SetFilmSimulationOptions {
/// The name of the slot
#[clap(long)]
name: Option<FujiCustomSettingName>,
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CustomSettingRepr { pub struct CustomSettingRepr {
@@ -124,6 +130,8 @@ pub struct FilmSimulationRepr {
pub white_balance_temperature: FujiWhiteBalanceTemperature, pub white_balance_temperature: FujiWhiteBalanceTemperature,
pub dynamic_range: FujiDynamicRange, pub dynamic_range: FujiDynamicRange,
pub dynamic_range_priority: FujiDynamicRangePriority, pub dynamic_range_priority: FujiDynamicRangePriority,
pub lens_modulation_optimizer: FujiLensModulationOptimizer,
pub color_space: FujiColorSpace,
} }
impl fmt::Display for FilmSimulationRepr { impl fmt::Display for FilmSimulationRepr {
@@ -190,7 +198,14 @@ impl fmt::Display for FilmSimulationRepr {
writeln!(f, "Dynamic Range: {}", self.dynamic_range)?; writeln!(f, "Dynamic Range: {}", self.dynamic_range)?;
} }
writeln!(f, "Dynamic Range Priority: {}", self.dynamic_range_priority) writeln!(f, "Dynamic Range Priority: {}", self.dynamic_range_priority)?;
writeln!(
f,
"Lens Modulation Optimizer: {}",
self.lens_modulation_optimizer
)?;
writeln!(f, "Color Space: {}", self.color_space)
} }
} }
@@ -221,6 +236,8 @@ fn handle_get(json: bool, device_id: Option<&str>, slot: FujiCustomSetting) -> a
white_balance_temperature: camera.get_white_balance_temperature()?, white_balance_temperature: camera.get_white_balance_temperature()?,
dynamic_range: camera.get_dynamic_range()?, dynamic_range: camera.get_dynamic_range()?,
dynamic_range_priority: camera.get_dynamic_range_priority()?, dynamic_range_priority: camera.get_dynamic_range_priority()?,
lens_modulation_optimizer: camera.get_lens_modulation_optimizer()?,
color_space: camera.get_color_space()?,
}; };
if json { if json {
@@ -236,14 +253,14 @@ fn handle_get(json: bool, device_id: Option<&str>, slot: FujiCustomSetting) -> a
fn handle_set( fn handle_set(
device_id: Option<&str>, device_id: Option<&str>,
slot: FujiCustomSetting, slot: FujiCustomSetting,
name: Option<&FujiCustomSettingName>, set_options: &SetFilmSimulationOptions,
options: &FilmSimulationOptions, options: &FilmSimulationOptions,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut camera = usb::get_camera(device_id)?; let mut camera = usb::get_camera(device_id)?;
camera.set_active_custom_setting(&slot)?; camera.set_active_custom_setting(&slot)?;
// General // General
if let Some(name) = &name { if let Some(name) = &set_options.name {
camera.set_custom_setting_name(name)?; camera.set_custom_setting_name(name)?;
} }
@@ -401,6 +418,15 @@ fn handle_set(
} }
} }
// Extras
if let Some(lens_modulation_optimizer) = &options.lens_modulation_optimizer {
camera.set_lens_modulation_optimizer(lens_modulation_optimizer)?;
}
if let Some(color_space) = &options.color_space {
camera.set_color_space(color_space)?;
}
Ok(()) Ok(())
} }
@@ -426,9 +452,14 @@ pub fn handle(cmd: SimulationCmd, json: bool, device_id: Option<&str>) -> anyhow
SimulationCmd::Get { slot } => handle_get(json, device_id, slot), SimulationCmd::Get { slot } => handle_get(json, device_id, slot),
SimulationCmd::Set { SimulationCmd::Set {
slot, slot,
name, set_film_simulation_options,
film_simulation_options, film_simulation_options,
} => handle_set(device_id, slot, name.as_ref(), &film_simulation_options), } => handle_set(
device_id,
slot,
&set_film_simulation_options,
&film_simulation_options,
),
SimulationCmd::Export { slot, output_file } => handle_export(device_id, slot, &output_file), SimulationCmd::Export { slot, output_file } => handle_export(device_id, slot, &output_file),
SimulationCmd::Import { slot, input_file } => handle_import(device_id, slot, &input_file), SimulationCmd::Import { slot, input_file } => handle_import(device_id, slot, &input_file),
} }