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,
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<P: rusb::UsbContext> {
}
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<P: rusb::UsbContext> {
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,

View File

@@ -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);

View File

@@ -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<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
#[clap(long)]
pub size: Option<FujiImageSize>,

View File

@@ -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");