773 lines
21 KiB
Rust
773 lines
21 KiB
Rust
//! The develop settings schema. Every field has a neutral default; serde uses `#[serde(default)]` so
|
||
//! older/newer files load (unknown fields are ignored, missing fields take defaults).
|
||
|
||
use lightcraft_geom::{CropGeometry, Orientation, Point};
|
||
use serde::{Deserialize, Serialize};
|
||
|
||
pub const SCHEMA_VERSION: u32 = 1;
|
||
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct DevelopSettings {
|
||
pub version: u32,
|
||
pub profile: Profile,
|
||
pub treatment: Treatment,
|
||
pub wb: WhiteBalance,
|
||
pub light: Light,
|
||
pub curve: ToneCurve,
|
||
pub color: ColorAdj,
|
||
pub mixer: Mixer,
|
||
pub bw_mix: BwMix,
|
||
pub grading: ColorGrading,
|
||
pub effects: Effects,
|
||
pub vignette: Vignette,
|
||
pub grain: Grain,
|
||
pub detail: Detail,
|
||
pub optics: Optics,
|
||
pub geometry: Geometry,
|
||
pub crop: Crop,
|
||
pub orientation: Orientation,
|
||
pub masks: Vec<Mask>,
|
||
pub spots: Vec<Spot>,
|
||
pub red_eye: Vec<RedEye>,
|
||
pub lens_blur: LensBlur,
|
||
pub enhance: Enhance,
|
||
/// Section on/off toggles (the "eye" buttons on panel headers): section id → enabled.
|
||
pub disabled_sections: Vec<String>,
|
||
}
|
||
|
||
impl Default for DevelopSettings {
|
||
fn default() -> Self {
|
||
Self {
|
||
version: SCHEMA_VERSION,
|
||
profile: Profile::default(),
|
||
treatment: Treatment::Color,
|
||
wb: WhiteBalance::default(),
|
||
light: Light::default(),
|
||
curve: ToneCurve::default(),
|
||
color: ColorAdj::default(),
|
||
mixer: Mixer::default(),
|
||
bw_mix: BwMix::default(),
|
||
grading: ColorGrading::default(),
|
||
effects: Effects::default(),
|
||
vignette: Vignette::default(),
|
||
grain: Grain::default(),
|
||
detail: Detail::default(),
|
||
optics: Optics::default(),
|
||
geometry: Geometry::default(),
|
||
crop: Crop::default(),
|
||
orientation: Orientation::Normal,
|
||
masks: Vec::new(),
|
||
spots: Vec::new(),
|
||
red_eye: Vec::new(),
|
||
lens_blur: LensBlur::default(),
|
||
enhance: Enhance::default(),
|
||
disabled_sections: Vec::new(),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "lowercase")]
|
||
pub enum Treatment {
|
||
#[default]
|
||
Color,
|
||
Bw,
|
||
}
|
||
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Profile {
|
||
/// Profile id from the profile registry (e.g. `lc.color`, `lc.vivid`, `lc.mono`).
|
||
pub id: String,
|
||
/// Creative profile amount 0..200 (%).
|
||
pub amount: f64,
|
||
}
|
||
|
||
impl Default for Profile {
|
||
fn default() -> Self {
|
||
Self { id: "lc.color".into(), amount: 100.0 }
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub enum WbMode {
|
||
#[default]
|
||
AsShot,
|
||
Auto,
|
||
Daylight,
|
||
Cloudy,
|
||
Shade,
|
||
Tungsten,
|
||
Fluorescent,
|
||
Flash,
|
||
Custom,
|
||
}
|
||
|
||
impl WbMode {
|
||
pub const ALL: [WbMode; 9] = [
|
||
WbMode::AsShot,
|
||
WbMode::Auto,
|
||
WbMode::Daylight,
|
||
WbMode::Cloudy,
|
||
WbMode::Shade,
|
||
WbMode::Tungsten,
|
||
WbMode::Fluorescent,
|
||
WbMode::Flash,
|
||
WbMode::Custom,
|
||
];
|
||
pub fn label(self) -> &'static str {
|
||
match self {
|
||
WbMode::AsShot => "As Shot",
|
||
WbMode::Auto => "Auto",
|
||
WbMode::Daylight => "Daylight",
|
||
WbMode::Cloudy => "Cloudy",
|
||
WbMode::Shade => "Shade",
|
||
WbMode::Tungsten => "Tungsten",
|
||
WbMode::Fluorescent => "Fluorescent",
|
||
WbMode::Flash => "Flash",
|
||
WbMode::Custom => "Custom",
|
||
}
|
||
}
|
||
/// Preset temperature/tint for raw files (K, tint).
|
||
pub fn preset(self) -> Option<(f64, f64)> {
|
||
match self {
|
||
WbMode::Daylight => Some((5500.0, 10.0)),
|
||
WbMode::Cloudy => Some((6500.0, 10.0)),
|
||
WbMode::Shade => Some((7500.0, 10.0)),
|
||
WbMode::Tungsten => Some((2850.0, 0.0)),
|
||
WbMode::Fluorescent => Some((3800.0, 21.0)),
|
||
WbMode::Flash => Some((5500.0, 0.0)),
|
||
_ => None,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// White balance. For raw files `temp` is the scene illuminant in Kelvin; for rendered files
|
||
/// (JPEG etc.) the image is assumed D65-balanced and `temp`/`tint` are the same model with an
|
||
/// "as shot" of 6500 K / 0 (the UI shows a relative −100..100 scale for those, like Lightroom).
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct WhiteBalance {
|
||
pub mode: WbMode,
|
||
pub temp: f64,
|
||
pub tint: f64,
|
||
}
|
||
|
||
impl Default for WhiteBalance {
|
||
fn default() -> Self {
|
||
Self { mode: WbMode::AsShot, temp: 6500.0, tint: 0.0 }
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Light {
|
||
pub exposure: f64,
|
||
pub contrast: f64,
|
||
pub highlights: f64,
|
||
pub shadows: f64,
|
||
pub whites: f64,
|
||
pub blacks: f64,
|
||
}
|
||
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct ToneCurve {
|
||
pub highlights: f64,
|
||
pub lights: f64,
|
||
pub darks: f64,
|
||
pub shadows: f64,
|
||
/// Region split points (percent), Lightroom defaults 25/50/75.
|
||
pub split_shadows: f64,
|
||
pub split_mid: f64,
|
||
pub split_highlights: f64,
|
||
/// Point curves in 0..1 (identity = empty or [(0,0),(1,1)]).
|
||
pub master: Vec<Point>,
|
||
pub red: Vec<Point>,
|
||
pub green: Vec<Point>,
|
||
pub blue: Vec<Point>,
|
||
}
|
||
|
||
impl Default for ToneCurve {
|
||
fn default() -> Self {
|
||
Self {
|
||
highlights: 0.0,
|
||
lights: 0.0,
|
||
darks: 0.0,
|
||
shadows: 0.0,
|
||
split_shadows: 25.0,
|
||
split_mid: 50.0,
|
||
split_highlights: 75.0,
|
||
master: Vec::new(),
|
||
red: Vec::new(),
|
||
green: Vec::new(),
|
||
blue: Vec::new(),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl ToneCurve {
|
||
pub fn point_curve_is_identity(pts: &[Point]) -> bool {
|
||
pts.is_empty() || pts.iter().all(|p| (p.x - p.y).abs() < 1e-9)
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct ColorAdj {
|
||
pub vibrance: f64,
|
||
pub saturation: f64,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Hsl {
|
||
pub hue: f64,
|
||
pub sat: f64,
|
||
pub lum: f64,
|
||
}
|
||
|
||
/// The 8 colour-mixer bands in Lightroom order.
|
||
pub const MIXER_BANDS: [&str; 8] = ["red", "orange", "yellow", "green", "aqua", "blue", "purple", "magenta"];
|
||
/// Centre hue of each band in degrees (sRGB-ish HSV hue).
|
||
pub const MIXER_HUES: [f64; 8] = [0.0, 30.0, 60.0, 120.0, 180.0, 225.0, 270.0, 315.0];
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Mixer {
|
||
pub red: Hsl,
|
||
pub orange: Hsl,
|
||
pub yellow: Hsl,
|
||
pub green: Hsl,
|
||
pub aqua: Hsl,
|
||
pub blue: Hsl,
|
||
pub purple: Hsl,
|
||
pub magenta: Hsl,
|
||
}
|
||
|
||
impl Mixer {
|
||
pub fn bands(&self) -> [Hsl; 8] {
|
||
[self.red, self.orange, self.yellow, self.green, self.aqua, self.blue, self.purple, self.magenta]
|
||
}
|
||
pub fn is_neutral(&self) -> bool {
|
||
self.bands().iter().all(|b| *b == Hsl::default())
|
||
}
|
||
}
|
||
|
||
/// B&W mix (used when treatment = Bw): per-band luminance contribution −100..100.
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct BwMix {
|
||
pub red: f64,
|
||
pub orange: f64,
|
||
pub yellow: f64,
|
||
pub green: f64,
|
||
pub aqua: f64,
|
||
pub blue: f64,
|
||
pub purple: f64,
|
||
pub magenta: f64,
|
||
}
|
||
|
||
impl BwMix {
|
||
pub fn bands(&self) -> [f64; 8] {
|
||
[self.red, self.orange, self.yellow, self.green, self.aqua, self.blue, self.purple, self.magenta]
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Wheel {
|
||
/// Hue in degrees 0..360.
|
||
pub hue: f64,
|
||
/// Saturation 0..100.
|
||
pub sat: f64,
|
||
/// Luminance −100..100.
|
||
pub lum: f64,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct ColorGrading {
|
||
pub shadows: Wheel,
|
||
pub midtones: Wheel,
|
||
pub highlights: Wheel,
|
||
pub global: Wheel,
|
||
/// 0..100, default 50.
|
||
pub blending: f64,
|
||
/// −100..100.
|
||
pub balance: f64,
|
||
}
|
||
|
||
impl Default for ColorGrading {
|
||
fn default() -> Self {
|
||
Self {
|
||
shadows: Wheel::default(),
|
||
midtones: Wheel::default(),
|
||
highlights: Wheel::default(),
|
||
global: Wheel::default(),
|
||
blending: 50.0,
|
||
balance: 0.0,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl ColorGrading {
|
||
pub fn is_neutral(&self) -> bool {
|
||
[self.shadows, self.midtones, self.highlights, self.global].iter().all(|w| w.sat == 0.0 && w.lum == 0.0)
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Effects {
|
||
pub texture: f64,
|
||
pub clarity: f64,
|
||
pub dehaze: f64,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub enum VignetteStyle {
|
||
#[default]
|
||
HighlightPriority,
|
||
ColorPriority,
|
||
PaintOverlay,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Vignette {
|
||
pub amount: f64,
|
||
pub midpoint: f64,
|
||
pub roundness: f64,
|
||
pub feather: f64,
|
||
pub highlights: f64,
|
||
pub style: VignetteStyle,
|
||
}
|
||
|
||
impl Default for Vignette {
|
||
fn default() -> Self {
|
||
Self { amount: 0.0, midpoint: 50.0, roundness: 0.0, feather: 50.0, highlights: 0.0, style: VignetteStyle::HighlightPriority }
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Grain {
|
||
pub amount: f64,
|
||
pub size: f64,
|
||
pub roughness: f64,
|
||
/// Seed for the procedural grain (stable per photo).
|
||
pub seed: u32,
|
||
}
|
||
|
||
impl Default for Grain {
|
||
fn default() -> Self {
|
||
Self { amount: 0.0, size: 25.0, roughness: 50.0, seed: 0 }
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Detail {
|
||
pub sharpen_amount: f64,
|
||
pub sharpen_radius: f64,
|
||
pub sharpen_detail: f64,
|
||
pub sharpen_masking: f64,
|
||
pub nr_luminance: f64,
|
||
pub nr_detail: f64,
|
||
pub nr_contrast: f64,
|
||
pub nr_color: f64,
|
||
pub nr_color_detail: f64,
|
||
pub nr_color_smoothness: f64,
|
||
}
|
||
|
||
impl Default for Detail {
|
||
/// Lightroom's raw defaults are sharpening 40/1.0/25/0 and colour NR 25/50/50; we use neutral
|
||
/// values for rendered files and apply raw defaults at import time (`DevelopSettings::for_raw`).
|
||
fn default() -> Self {
|
||
Self {
|
||
sharpen_amount: 0.0,
|
||
sharpen_radius: 1.0,
|
||
sharpen_detail: 25.0,
|
||
sharpen_masking: 0.0,
|
||
nr_luminance: 0.0,
|
||
nr_detail: 50.0,
|
||
nr_contrast: 0.0,
|
||
nr_color: 0.0,
|
||
nr_color_detail: 50.0,
|
||
nr_color_smoothness: 50.0,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Optics {
|
||
pub remove_ca: bool,
|
||
pub lens_profile: bool,
|
||
pub profile_distortion: f64,
|
||
pub profile_vignetting: f64,
|
||
pub defringe_purple_amount: f64,
|
||
pub defringe_purple_hue_lo: f64,
|
||
pub defringe_purple_hue_hi: f64,
|
||
pub defringe_green_amount: f64,
|
||
pub defringe_green_hue_lo: f64,
|
||
pub defringe_green_hue_hi: f64,
|
||
pub distortion: f64,
|
||
pub vignetting: f64,
|
||
pub vignetting_midpoint: f64,
|
||
/// Manual lateral chromatic aberration (red/cyan, blue/yellow fringes), −100..100, added to the
|
||
/// automatic estimate when `remove_ca` is on.
|
||
pub ca_red: f64,
|
||
pub ca_blue: f64,
|
||
}
|
||
|
||
impl Default for Optics {
|
||
fn default() -> Self {
|
||
Self {
|
||
remove_ca: false,
|
||
lens_profile: false,
|
||
profile_distortion: 100.0,
|
||
profile_vignetting: 100.0,
|
||
defringe_purple_amount: 0.0,
|
||
defringe_purple_hue_lo: 30.0,
|
||
defringe_purple_hue_hi: 70.0,
|
||
defringe_green_amount: 0.0,
|
||
defringe_green_hue_lo: 40.0,
|
||
defringe_green_hue_hi: 60.0,
|
||
distortion: 0.0,
|
||
vignetting: 0.0,
|
||
vignetting_midpoint: 50.0,
|
||
ca_red: 0.0,
|
||
ca_blue: 0.0,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Lens corrections embedded in a DNG file (`OpcodeList3`: `WarpRectilinear`, `FixVignetteRadial`), converted
|
||
/// to the oriented, default-cropped image. This is camera/file data (stored on the photo record, not in the develop
|
||
/// settings); "Enable Profile Corrections" applies it, scaled by the profile distortion/vignetting amounts.
|
||
/// LightCraft never uses Adobe LCP lens profiles.
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct EmbeddedLens {
|
||
pub warp: Option<EmbeddedWarp>,
|
||
pub vignette: Option<EmbeddedVignette>,
|
||
}
|
||
|
||
/// DNG `WarpRectilinear`: per plane (R, G, B) `[kr0, kr1, kr2, kr3, kt0, kt1]`. For an output (corrected) point at
|
||
/// offset `d = (p − center) / radius`, the source point is `center + radius · (d·f(r²) + tangential(d))`.
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
pub struct EmbeddedWarp {
|
||
pub planes: [[f64; 6]; 3],
|
||
/// Optical centre, normalized to the oriented image (0..1).
|
||
pub center: Point,
|
||
/// Normalisation radius as a fraction of the oriented image's long edge.
|
||
pub radius: f64,
|
||
}
|
||
|
||
/// DNG `FixVignetteRadial`: gain `1 + k0 r² + k1 r⁴ + … + k4 r¹⁰` with `r = |p − center| / radius`.
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
pub struct EmbeddedVignette {
|
||
pub k: [f64; 5],
|
||
pub center: Point,
|
||
pub radius: f64,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub enum Upright {
|
||
#[default]
|
||
Off,
|
||
Auto,
|
||
Guided,
|
||
Level,
|
||
Vertical,
|
||
Full,
|
||
}
|
||
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Geometry {
|
||
pub upright: Upright,
|
||
/// Guided Upright guides (normalized image coords), up to 4 line segments.
|
||
pub guides: Vec<(Point, Point)>,
|
||
pub vertical: f64,
|
||
pub horizontal: f64,
|
||
pub rotate: f64,
|
||
pub aspect: f64,
|
||
pub scale: f64,
|
||
pub offset_x: f64,
|
||
pub offset_y: f64,
|
||
pub constrain_crop: bool,
|
||
}
|
||
|
||
impl Default for Geometry {
|
||
fn default() -> Self {
|
||
Self {
|
||
upright: Upright::Off,
|
||
guides: Vec::new(),
|
||
vertical: 0.0,
|
||
horizontal: 0.0,
|
||
rotate: 0.0,
|
||
aspect: 0.0,
|
||
scale: 100.0,
|
||
offset_x: 0.0,
|
||
offset_y: 0.0,
|
||
constrain_crop: false,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Geometry {
|
||
pub fn is_identity(&self) -> bool {
|
||
self.upright == Upright::Off
|
||
&& self.vertical == 0.0
|
||
&& self.horizontal == 0.0
|
||
&& self.rotate == 0.0
|
||
&& self.aspect == 0.0
|
||
&& self.scale == 100.0
|
||
&& self.offset_x == 0.0
|
||
&& self.offset_y == 0.0
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Crop {
|
||
pub geometry: CropGeometry,
|
||
/// Locked aspect (w, h); None = free / original.
|
||
pub aspect: Option<(u32, u32)>,
|
||
pub flip_h: bool,
|
||
pub flip_v: bool,
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Masks and local adjustments
|
||
|
||
/// The sliders available inside a mask (Lightroom's local adjustments).
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct LocalAdjustments {
|
||
pub temp: f64,
|
||
pub tint: f64,
|
||
pub exposure: f64,
|
||
pub contrast: f64,
|
||
pub highlights: f64,
|
||
pub shadows: f64,
|
||
pub whites: f64,
|
||
pub blacks: f64,
|
||
pub texture: f64,
|
||
pub clarity: f64,
|
||
pub dehaze: f64,
|
||
pub hue: f64,
|
||
pub saturation: f64,
|
||
pub sharpness: f64,
|
||
pub noise: f64,
|
||
pub moire: f64,
|
||
pub defringe: f64,
|
||
/// Colour overlay (hue degrees, saturation 0..100); sat 0 = none.
|
||
pub color_hue: f64,
|
||
pub color_sat: f64,
|
||
/// Local vignette/grain are not in Lightroom; we keep the struct extensible.
|
||
pub amount: f64,
|
||
}
|
||
|
||
impl Default for LocalAdjustments {
|
||
fn default() -> Self {
|
||
Self {
|
||
temp: 0.0,
|
||
tint: 0.0,
|
||
exposure: 0.0,
|
||
contrast: 0.0,
|
||
highlights: 0.0,
|
||
shadows: 0.0,
|
||
whites: 0.0,
|
||
blacks: 0.0,
|
||
texture: 0.0,
|
||
clarity: 0.0,
|
||
dehaze: 0.0,
|
||
hue: 0.0,
|
||
saturation: 0.0,
|
||
sharpness: 0.0,
|
||
noise: 0.0,
|
||
moire: 0.0,
|
||
defringe: 0.0,
|
||
color_hue: 0.0,
|
||
color_sat: 0.0,
|
||
amount: 100.0,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "lowercase")]
|
||
pub enum MaskOp {
|
||
#[default]
|
||
Add,
|
||
Subtract,
|
||
Intersect,
|
||
}
|
||
|
||
/// One brush dab sequence. Coordinates normalized to the uncropped oriented image.
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct BrushStroke {
|
||
pub points: Vec<Point>,
|
||
/// Radius as a fraction of the image's long edge.
|
||
pub size: f64,
|
||
/// 0..100.
|
||
pub feather: f64,
|
||
pub flow: f64,
|
||
pub density: f64,
|
||
pub erase: bool,
|
||
pub auto_mask: bool,
|
||
}
|
||
|
||
impl Default for BrushStroke {
|
||
fn default() -> Self {
|
||
Self { points: Vec::new(), size: 0.03, feather: 50.0, flow: 100.0, density: 100.0, erase: false, auto_mask: false }
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(tag = "kind", rename_all = "camelCase")]
|
||
pub enum MaskShape {
|
||
Brush {
|
||
strokes: Vec<BrushStroke>,
|
||
},
|
||
/// Linear gradient: full effect at `start`, fading to none at `end` (normalized coords).
|
||
Linear {
|
||
start: Point,
|
||
end: Point,
|
||
},
|
||
/// Radial gradient: ellipse centre, radii (normalized to the long edge), rotation (deg), feather 0..100.
|
||
Radial {
|
||
center: Point,
|
||
rx: f64,
|
||
ry: f64,
|
||
angle: f64,
|
||
feather: f64,
|
||
invert: bool,
|
||
},
|
||
/// Colour range: sampled OkLab colours with a refine amount 0..100.
|
||
ColorRange {
|
||
samples: Vec<[f64; 3]>,
|
||
refine: f64,
|
||
},
|
||
/// Luminance range (0..1 bounds with feathered falloff 0..1).
|
||
LuminanceRange {
|
||
lo: f64,
|
||
hi: f64,
|
||
lo_feather: f64,
|
||
hi_feather: f64,
|
||
},
|
||
DepthRange {
|
||
lo: f64,
|
||
hi: f64,
|
||
feather: f64,
|
||
},
|
||
/// AI / automatic selections, evaluated by a segmenter.
|
||
Subject,
|
||
Sky,
|
||
Background,
|
||
Object {
|
||
hint: Vec<Point>,
|
||
},
|
||
People {
|
||
person: u32,
|
||
parts: Vec<String>,
|
||
},
|
||
Landscape {
|
||
class: String,
|
||
},
|
||
}
|
||
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
pub struct MaskComponent {
|
||
#[serde(default)]
|
||
pub op: MaskOp,
|
||
#[serde(default)]
|
||
pub invert: bool,
|
||
pub shape: MaskShape,
|
||
}
|
||
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Mask {
|
||
pub id: u32,
|
||
pub name: String,
|
||
pub visible: bool,
|
||
pub invert: bool,
|
||
pub components: Vec<MaskComponent>,
|
||
pub adjust: LocalAdjustments,
|
||
}
|
||
|
||
impl Default for Mask {
|
||
fn default() -> Self {
|
||
Self { id: 0, name: "Mask 1".into(), visible: true, invert: false, components: Vec::new(), adjust: LocalAdjustments::default() }
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Heal / remove / red eye
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "lowercase")]
|
||
pub enum SpotMode {
|
||
#[default]
|
||
Remove,
|
||
Heal,
|
||
Clone,
|
||
}
|
||
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Spot {
|
||
pub mode: SpotMode,
|
||
/// Target: a single point (circle spot) or a brushed path. Normalized coords.
|
||
pub points: Vec<Point>,
|
||
/// Radius as a fraction of the long edge.
|
||
pub size: f64,
|
||
pub feather: f64,
|
||
pub opacity: f64,
|
||
/// Source offset (normalized) from the target; None = auto (content-aware remove).
|
||
pub source_offset: Option<Point>,
|
||
}
|
||
|
||
impl Default for Spot {
|
||
fn default() -> Self {
|
||
Self { mode: SpotMode::Remove, points: Vec::new(), size: 0.02, feather: 50.0, opacity: 100.0, source_offset: None }
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
pub struct RedEye {
|
||
pub center: Point,
|
||
pub rx: f64,
|
||
pub ry: f64,
|
||
pub pupil_size: f64,
|
||
pub darken: f64,
|
||
pub pet: bool,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct LensBlur {
|
||
pub enabled: bool,
|
||
pub amount: f64,
|
||
pub focal_distance: f64,
|
||
pub focal_range: f64,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||
#[serde(default)]
|
||
pub struct Enhance {
|
||
pub denoise: f64,
|
||
pub raw_details: bool,
|
||
pub super_resolution: bool,
|
||
}
|