feat: monochromatic color tint/temp

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2025-10-19 19:31:23 +01:00
parent 76ab55acd1
commit 91d5d5b16b
4 changed files with 145 additions and 41 deletions

View File

@@ -13,9 +13,10 @@ use ptp::{
CommandCode, DevicePropCode, FujiClarity, FujiColor, FujiColorChromeEffect, CommandCode, DevicePropCode, FujiClarity, FujiColor, FujiColorChromeEffect,
FujiColorChromeFXBlue, FujiCustomSetting, FujiCustomSettingName, FujiDynamicRange, FujiColorChromeFXBlue, FujiCustomSetting, FujiCustomSettingName, FujiDynamicRange,
FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR,
FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiShadowTone, FujiSharpness, FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiMonochromaticColorTemperature,
FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect,
ObjectFormat, UsbMode, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, ObjectFormat,
UsbMode,
}, },
structs::{DeviceInfo, ObjectInfo}, structs::{DeviceInfo, ObjectInfo},
}; };
@@ -82,6 +83,8 @@ impl Camera {
get_dynamic_range => FujiDynamicRange, get_dynamic_range => FujiDynamicRange,
get_dynamic_range_priority => FujiDynamicRangePriority, get_dynamic_range_priority => FujiDynamicRangePriority,
get_film_simulation => FujiFilmSimulation, get_film_simulation => FujiFilmSimulation,
get_monochromatic_color_temperature => FujiMonochromaticColorTemperature,
get_monochromatic_color_tint => FujiMonochromaticColorTint,
get_grain_effect => FujiGrainEffect, get_grain_effect => FujiGrainEffect,
get_white_balance => FujiWhiteBalance, get_white_balance => FujiWhiteBalance,
get_high_iso_nr => FujiHighISONR, get_high_iso_nr => FujiHighISONR,
@@ -107,6 +110,8 @@ impl Camera {
set_dynamic_range(value: &FujiDynamicRange) => (), set_dynamic_range(value: &FujiDynamicRange) => (),
set_dynamic_range_priority(value: &FujiDynamicRangePriority) => (), set_dynamic_range_priority(value: &FujiDynamicRangePriority) => (),
set_film_simulation(value: &FujiFilmSimulation) => (), set_film_simulation(value: &FujiFilmSimulation) => (),
set_monochromatic_color_temperature(value: &FujiMonochromaticColorTemperature) => (),
set_monochromatic_color_tint(value: &FujiMonochromaticColorTint) => (),
set_grain_effect(value: &FujiGrainEffect) => (), set_grain_effect(value: &FujiGrainEffect) => (),
set_white_balance(value: &FujiWhiteBalance) => (), set_white_balance(value: &FujiWhiteBalance) => (),
set_high_iso_nr(value: &FujiHighISONR) => (), set_high_iso_nr(value: &FujiHighISONR) => (),
@@ -234,6 +239,7 @@ pub trait CameraImpl<P: rusb::UsbContext> {
} }
fn chunk_size(&self) -> usize { 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 1024 * 1024
} }
@@ -324,6 +330,8 @@ pub trait CameraImpl<P: rusb::UsbContext> {
dynamic_range: FujiDynamicRange => DevicePropCode::FujiStillCustomSettingDynamicRange, dynamic_range: FujiDynamicRange => DevicePropCode::FujiStillCustomSettingDynamicRange,
dynamic_range_priority: FujiDynamicRangePriority => DevicePropCode::FujiStillCustomSettingDynamicRangePriority, dynamic_range_priority: FujiDynamicRangePriority => DevicePropCode::FujiStillCustomSettingDynamicRangePriority,
film_simulation: FujiFilmSimulation => DevicePropCode::FujiStillCustomSettingFilmSimulation, film_simulation: FujiFilmSimulation => DevicePropCode::FujiStillCustomSettingFilmSimulation,
monochromatic_color_temperature: FujiMonochromaticColorTemperature => DevicePropCode::FujiStillCustomSettingMonochromaticColorTemperature,
monochromatic_color_tint: FujiMonochromaticColorTint => DevicePropCode::FujiStillCustomSettingMonochromaticColorTint,
grain_effect: FujiGrainEffect => DevicePropCode::FujiStillCustomSettingGrainEffect, grain_effect: FujiGrainEffect => DevicePropCode::FujiStillCustomSettingGrainEffect,
white_balance: FujiWhiteBalance => DevicePropCode::FujiStillCustomSettingWhiteBalance, white_balance: FujiWhiteBalance => DevicePropCode::FujiStillCustomSettingWhiteBalance,
high_iso_nr: FujiHighISONR => DevicePropCode::FujiStillCustomSettingHighISONR, high_iso_nr: FujiHighISONR => DevicePropCode::FujiStillCustomSettingHighISONR,

View File

@@ -136,7 +136,7 @@ impl PtpDeserialize for ContainerCode {
pub enum DevicePropCode { pub enum DevicePropCode {
FujiUsbMode = 0xd16e, FujiUsbMode = 0xd16e,
FujiRawConversionRun = 0xD183, FujiRawConversionRun = 0xD183,
FujiRawConversionSettings = 0xD185, FujiRawConversionProfile = 0xD185,
FujiStillCustomSetting = 0xD18C, FujiStillCustomSetting = 0xD18C,
FujiStillCustomSettingName = 0xD18D, FujiStillCustomSettingName = 0xD18D,
FujiStillCustomSettingImageSize = 0xD18E, FujiStillCustomSettingImageSize = 0xD18E,
@@ -144,8 +144,8 @@ pub enum DevicePropCode {
FujiStillCustomSettingDynamicRange = 0xD190, FujiStillCustomSettingDynamicRange = 0xD190,
FujiStillCustomSettingDynamicRangePriority = 0xD191, FujiStillCustomSettingDynamicRangePriority = 0xD191,
FujiStillCustomSettingFilmSimulation = 0xD192, FujiStillCustomSettingFilmSimulation = 0xD192,
// TODO: 0xD193 All 0s FujiStillCustomSettingMonochromaticColorTemperature = 0xD193,
// TODO: 0xD194 All 0s FujiStillCustomSettingMonochromaticColorTint = 0xD194,
FujiStillCustomSettingGrainEffect = 0xD195, FujiStillCustomSettingGrainEffect = 0xD195,
FujiStillCustomSettingColorChromeEffect = 0xD196, FujiStillCustomSettingColorChromeEffect = 0xD196,
FujiStillCustomSettingColorChromeFXBlue = 0xD197, 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!(FujiWhiteBalanceShift, -9.0, 9.0, 1.0, 1i16);
fuji_i16!(FujiWhiteBalanceTemperature, 2500.0, 10000.0, 10.0, 1i16); fuji_i16!(FujiWhiteBalanceTemperature, 2500.0, 10000.0, 10.0, 1i16);
fuji_i16!(FujiHighlightTone, -2.0, 4.0, 0.5, 10i16); fuji_i16!(FujiHighlightTone, -2.0, 4.0, 0.5, 10i16);

View File

@@ -3,16 +3,25 @@ use clap::Args;
use crate::camera::ptp::hex::{ use crate::camera::ptp::hex::{
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiDynamicRange, FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiDynamicRange,
FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR,
FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiShadowTone, FujiSharpness, FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiMonochromaticColorTemperature,
FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect,
FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
}; };
#[derive(Args, Debug)] #[derive(Args, Debug)]
pub struct FilmSimulationOptions { pub struct FilmSimulationOptions {
/// The Fujifilm film simulation to use /// Fujifilm Film Simulation
#[clap(long)] #[clap(long)]
pub simulation: Option<FujiFilmSimulation>, pub simulation: Option<FujiFilmSimulation>,
/// Monochromatic Color Temperature (only applicable to B&W film simulations)
#[clap(long)]
pub monochromatic_color_temperature: Option<FujiMonochromaticColorTemperature>,
/// Monochromatic Color Tint (only applicable to B&W film simulations)
#[clap(long)]
pub monochromatic_color_tint: Option<FujiMonochromaticColorTint>,
/// The output image resolution /// The output image resolution
#[clap(long)] #[clap(long)]
pub size: Option<FujiImageSize>, pub size: Option<FujiImageSize>,

View File

@@ -5,8 +5,9 @@ use crate::{
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiCustomSetting, FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiCustomSetting,
FujiCustomSettingName, FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiCustomSettingName, FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation,
FujiGrainEffect, FujiHighISONR, FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiGrainEffect, FujiHighISONR, FujiHighlightTone, FujiImageQuality, FujiImageSize,
FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect, FujiWhiteBalance, FujiMonochromaticColorTemperature, FujiMonochromaticColorTint, FujiShadowTone,
FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, FujiSharpness, FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift,
FujiWhiteBalanceTemperature,
}, },
usb, usb,
}; };
@@ -102,56 +103,94 @@ fn handle_list(json: bool, device_id: Option<&str>) -> anyhow::Result<()> {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct FilmSimulationRepr { pub struct FilmSimulationRepr {
pub name: FujiCustomSettingName, pub name: FujiCustomSettingName,
pub simulation: FujiFilmSimulation,
pub size: FujiImageSize, pub size: FujiImageSize,
pub quality: FujiImageQuality, pub quality: FujiImageQuality,
pub simulation: FujiFilmSimulation,
pub monochromatic_color_temperature: FujiMonochromaticColorTemperature,
pub monochromatic_color_tint: FujiMonochromaticColorTint,
pub highlight: FujiHighlightTone, pub highlight: FujiHighlightTone,
pub shadow: FujiShadowTone, pub shadow: FujiShadowTone,
pub color: FujiColor, pub color: FujiColor,
pub sharpness: FujiSharpness, pub sharpness: FujiSharpness,
pub clarity: FujiClarity, 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: FujiWhiteBalance,
pub white_balance_shift_red: FujiWhiteBalanceShift, pub white_balance_shift_red: FujiWhiteBalanceShift,
pub white_balance_shift_blue: FujiWhiteBalanceShift, pub white_balance_shift_blue: FujiWhiteBalanceShift,
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 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 { impl fmt::Display for FilmSimulationRepr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Name: {}", self.name)?; writeln!(f, "Name: {}", self.name)?;
writeln!(f, "Simulation: {}", self.simulation)?;
writeln!(f, "Size: {}", self.size)?; writeln!(f, "Size: {}", self.size)?;
writeln!(f, "Quality: {}", self.quality)?; 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, "Color: {}", self.color)?;
writeln!(f, "Sharpness: {}", self.sharpness)?; writeln!(f, "Sharpness: {}", self.sharpness)?;
writeln!(f, "Clarity: {}", self.clarity)?; 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: {}", self.white_balance)?;
writeln!( writeln!(
f, f,
"White Balance Shift (R/B): {} / {}", "White Balance Shift (R/B): {} / {}",
self.white_balance_shift_red, self.white_balance_shift_blue self.white_balance_shift_red, self.white_balance_shift_blue
)?; )?;
writeln!(
f, if self.white_balance == FujiWhiteBalance::Temperature {
"White Balance Temperature: {}K", writeln!(
self.white_balance_temperature f,
)?; "White Balance Temperature: {}K",
writeln!(f, "Dynamic Range: {}", self.dynamic_range)?; self.white_balance_temperature
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)?; if self.dynamic_range_priority == FujiDynamicRangePriority::Off {
writeln!(f, "Color Chrome FX Blue: {}", self.color_chrome_fx_blue)?; writeln!(f, "Dynamic Range: {}", self.dynamic_range)?;
writeln!(f, "Smooth Skin Effect: {}", self.smooth_skin_effect) }
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 { let repr = FilmSimulationRepr {
name: camera.get_custom_setting_name()?, name: camera.get_custom_setting_name()?,
simulation: camera.get_film_simulation()?,
size: camera.get_image_size()?, size: camera.get_image_size()?,
quality: camera.get_image_quality()?, 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()?, highlight: camera.get_highlight_tone()?,
shadow: camera.get_shadow_tone()?, shadow: camera.get_shadow_tone()?,
color: camera.get_color()?, color: camera.get_color()?,
sharpness: camera.get_sharpness()?, sharpness: camera.get_sharpness()?,
clarity: camera.get_clarity()?, 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: camera.get_white_balance()?,
white_balance_shift_red: camera.get_white_balance_shift_red()?, white_balance_shift_red: camera.get_white_balance_shift_red()?,
white_balance_shift_blue: camera.get_white_balance_shift_blue()?, white_balance_shift_blue: camera.get_white_balance_shift_blue()?,
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()?,
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 { if json {
@@ -219,6 +260,48 @@ fn handle_set(
camera.set_film_simulation(simulation)?; 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 { if let Some(color) = &options.color {
camera.set_color(color)?; camera.set_color(color)?;
} }
@@ -291,8 +374,10 @@ fn handle_set(
&camera.get_dynamic_range_priority()? &camera.get_dynamic_range_priority()?
}; };
let is_drp_off = *dynamic_range_priority == FujiDynamicRangePriority::Off;
if let Some(dynamic_range) = &options.dynamic_range { if let Some(dynamic_range) = &options.dynamic_range {
if *dynamic_range_priority == FujiDynamicRangePriority::Off { if is_drp_off {
camera.set_dynamic_range(dynamic_range)?; camera.set_dynamic_range(dynamic_range)?;
} else { } else {
warn!("Dynamic Range Priority is enabled, refusing to set dynamic range"); 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 let Some(highlights) = &options.highlight {
if *dynamic_range_priority == FujiDynamicRangePriority::Off { if is_drp_off {
camera.set_highlight_tone(highlights)?; camera.set_highlight_tone(highlights)?;
} else { } else {
warn!("Dynamic Range Priority is enabled, refusing to set highlight tone"); 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 let Some(shadows) = &options.shadow {
if *dynamic_range_priority == FujiDynamicRangePriority::Off { if is_drp_off {
camera.set_shadow_tone(shadows)?; camera.set_shadow_tone(shadows)?;
} else { } else {
warn!("Dynamic Range Priority is enabled, refusing to set shadow tone"); warn!("Dynamic Range Priority is enabled, refusing to set shadow tone");