Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions src-tauri/src/image_processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1288,13 +1288,13 @@ pub struct GlobalAdjustments {
pub chromatic_aberration_blue_yellow: f32,
pub show_clipping: u32,
pub is_raw_image: u32,
_pad_ca1: f32,
pub skin_smoothing: f32,

pub has_lut: u32,
pub lut_intensity: f32,
pub tonemapper_mode: u32,
_pad_lut2: f32,
_pad_lut3: f32,
pub skin_texture: f32,
pub skin_smoothing_scale: f32,
_pad_lut4: f32,
_pad_lut5: f32,

Expand Down Expand Up @@ -1367,15 +1367,15 @@ pub struct MaskAdjustments {
pub sharpness_threshold: f32,

pub hue: f32,
_pad_cg1: f32,
_pad_cg2: f32,
pub skin_smoothing: f32,
pub skin_texture: f32,
pub color_grading_shadows: ColorGradeSettings,
pub color_grading_midtones: ColorGradeSettings,
pub color_grading_highlights: ColorGradeSettings,
pub color_grading_global: ColorGradeSettings,
pub color_grading_blending: f32,
pub color_grading_balance: f32,
_pad5: f32,
pub skin_smoothing_scale: f32,
_pad6: f32,

pub hsl: [HslColor; 8],
Expand Down Expand Up @@ -1424,6 +1424,9 @@ struct AdjustmentScales {
luma_noise_reduction: f32,
color_noise_reduction: f32,
clarity: f32,
skin_smoothing: f32,
skin_texture: f32,
skin_smoothing_scale: f32,
dehaze: f32,
structure: f32,
centré: f32,
Expand Down Expand Up @@ -1473,6 +1476,9 @@ const SCALES: AdjustmentScales = AdjustmentScales {
luma_noise_reduction: 100.0,
color_noise_reduction: 100.0,
clarity: 200.0,
skin_smoothing: 100.0,
skin_texture: 50.0,
skin_smoothing_scale: 100.0,
dehaze: 750.0,
structure: 200.0,
centré: 250.0,
Expand Down Expand Up @@ -2068,15 +2074,15 @@ fn get_global_adjustments_from_json(
0
},
is_raw_image: if is_raw { 1 } else { 0 },
_pad_ca1: 0.0,
skin_smoothing: get_val("details", "skinSmoothing", SCALES.skin_smoothing, None),

has_lut,
lut_intensity,

tonemapper_mode: tonemapper_override
.unwrap_or_else(|| if tone_mapper == "agx" { 1 } else { 0 }),
_pad_lut2: 0.0,
_pad_lut3: 0.0,
skin_texture: get_val("details", "skinTexture", SCALES.skin_texture, Some(50.0)),
skin_smoothing_scale: get_val("details", "skinSmoothingScale", SCALES.skin_smoothing_scale, None),
_pad_lut4: 0.0,
_pad_lut5: 0.0,

Expand Down Expand Up @@ -2231,8 +2237,13 @@ fn get_mask_adjustments_from_json(adj: &serde_json::Value) -> MaskAdjustments {
sharpness_threshold: get_val("details", "sharpnessThreshold", SCALES.sharpness_threshold),

hue: get_val("color", "hue", 1.0),
_pad_cg1: 0.0,
_pad_cg2: 0.0,
skin_smoothing: get_val("details", "skinSmoothing", SCALES.skin_smoothing),
// Neutral texture is 1.0 (raw value 50); missing/hidden must not read as 0.
skin_texture: if is_visible("details") {
adj["skinTexture"].as_f64().unwrap_or(50.0) as f32 / SCALES.skin_texture
} else {
1.0
},
color_grading_shadows: if is_visible("color") {
parse_color_grade_settings(&cg_obj["shadows"])
} else {
Expand Down Expand Up @@ -2263,7 +2274,7 @@ fn get_mask_adjustments_from_json(adj: &serde_json::Value) -> MaskAdjustments {
} else {
0.0
},
_pad5: 0.0,
skin_smoothing_scale: get_val("details", "skinSmoothingScale", SCALES.skin_smoothing_scale),
_pad6: 0.0,

hsl: if is_visible("color") {
Expand Down
66 changes: 60 additions & 6 deletions src-tauri/src/shaders/shader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ struct GlobalAdjustments {
chromatic_aberration_blue_yellow: f32,
show_clipping: u32,
is_raw_image: u32,
_pad_ca1: f32,
skin_smoothing: f32,

has_lut: u32,
lut_intensity: f32,
tonemapper_mode: u32,
_pad_lut2: f32,
_pad_lut3: f32,
skin_texture: f32,
skin_smoothing_scale: f32,
_pad_lut4: f32,
_pad_lut5: f32,

Expand Down Expand Up @@ -143,15 +143,15 @@ struct MaskAdjustments {
sharpness_threshold: f32,

hue: f32,
_pad_cg1: f32,
_pad_cg2: f32,
skin_smoothing: f32,
skin_texture: f32,
color_grading_shadows: ColorGradeSettings,
color_grading_midtones: ColorGradeSettings,
color_grading_highlights: ColorGradeSettings,
color_grading_global: ColorGradeSettings,
color_grading_blending: f32,
color_grading_balance: f32,
_pad5: f32,
skin_smoothing_scale: f32,
_pad6: f32,

hsl: array<HslColor, 8>,
Expand Down Expand Up @@ -718,6 +718,50 @@ fn apply_color_grading(color: vec3<f32>, shadows: ColorGradeSettings, midtones:
return graded_color;
}

// Frequency-separation skin smoothing: flatten the blotch band while re-adding
// the fine band (pores, hair) so texture survives - unlike a plain
// negative-clarity blur mix.
fn apply_skin_smoothing(
color_linear: vec3<f32>,
clarity_blurred_input: vec3<f32>,
structure_blurred_input: vec3<f32>,
tonal_blurred_input: vec3<f32>,
amount: f32,
texture_amount: f32,
scale_amount: f32,
is_raw: u32,
) -> vec3<f32> {
if (amount <= 0.001) {
return color_linear;
}
var clarity_blurred = clarity_blurred_input;
var structure_blurred = structure_blurred_input;
var tonal_blurred = tonal_blurred_input;
if (is_raw != 1u) {
clarity_blurred = srgb_to_linear(clarity_blurred);
structure_blurred = srgb_to_linear(structure_blurred);
tonal_blurred = srgb_to_linear(tonal_blurred);
}
// Separation point (the "radius" in classic frequency separation): blend the
// flatten base from the clarity blur (8px, micro-blotches) toward the
// structure blur (40px, large tonal patches) to match the shot's scale.
let base_blurred = mix(clarity_blurred, structure_blurred, clamp(scale_amount, 0.0, 1.0));
// Edge guard: blotches are low-amplitude relative to their surround, while
// structural edges (eyes, brows, hair boundaries) are high-amplitude. Fade
// the smoothing out as the normalized mid-band contrast rises.
let mid_delta = color_linear - base_blurred;
let surround_luma = max(get_luma(base_blurred), 0.02);
let rel_contrast = get_luma(abs(mid_delta)) / surround_luma;
let edge_protect = 1.0 - smoothstep(0.12, 0.4, rel_contrast);
let a = clamp(amount, 0.0, 1.0) * edge_protect;
// Texture band is everything finer than the tonal blur radius (~pore scale),
// re-injected under user control.
let tf = clamp(texture_amount, 0.0, 2.0);
let fine_detail = color_linear - tonal_blurred;
let smoothed_base = mix(color_linear, base_blurred, a);
return max(smoothed_base + fine_detail * a * tf, vec3<f32>(0.0));
}

fn apply_local_contrast(
processed_color_linear: vec3<f32>,
blurred_color_input_space: vec3<f32>,
Expand Down Expand Up @@ -1482,6 +1526,9 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
var t_clarity = adjustments.global.clarity;
var t_dehaze = adjustments.global.dehaze;
var t_structure = adjustments.global.structure;
var t_skin_smoothing = adjustments.global.skin_smoothing;
var t_skin_texture = adjustments.global.skin_texture - 1.0;
var t_skin_scale = adjustments.global.skin_smoothing_scale;
var t_glow = adjustments.global.glow_amount;
var t_halation = adjustments.global.halation_amount;
var t_flare = adjustments.global.flare_amount;
Expand Down Expand Up @@ -1520,6 +1567,9 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
t_clarity += m.clarity * influence;
t_dehaze += m.dehaze * influence;
t_structure += m.structure * influence;
t_skin_smoothing += m.skin_smoothing * influence;
t_skin_texture += (m.skin_texture - 1.0) * influence;
t_skin_scale += m.skin_smoothing_scale * influence;

t_glow += m.glow_amount * influence;
t_halation += m.halation_amount * influence;
Expand Down Expand Up @@ -1577,6 +1627,10 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
}
locally_contrasted_rgb += sharpness_delta;

locally_contrasted_rgb = apply_skin_smoothing(
locally_contrasted_rgb, clarity_blurred, structure_blurred, tonal_blurred,
t_skin_smoothing, 1.0 + t_skin_texture, t_skin_scale, is_raw
);
locally_contrasted_rgb = apply_local_contrast(locally_contrasted_rgb, clarity_blurred, t_clarity, is_raw, 1u, 0.0);
locally_contrasted_rgb = apply_local_contrast(locally_contrasted_rgb, structure_blurred, t_structure, is_raw, 1u, 0.0);
locally_contrasted_rgb = apply_centre_local_contrast(locally_contrasted_rgb, adjustments.global.centre, absolute_coord_i, clarity_blurred, is_raw);
Expand Down
28 changes: 28 additions & 0 deletions src/components/adjustments/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,34 @@ export default function DetailsPanel({
value={adjustments.clarity}
onDragStateChange={onDragStateChange}
/>
<Slider
label={t('adjustments.details.skinSmoothing')}
max={100}
min={0}
onChange={(e: any) => handleAdjustmentChange(DetailsAdjustment.SkinSmoothing, e.target.value)}
step={1}
value={adjustments.skinSmoothing}
onDragStateChange={onDragStateChange}
/>
<Slider
defaultValue={50}
label={t('adjustments.details.skinTexture')}
max={100}
min={0}
onChange={(e: any) => handleAdjustmentChange(DetailsAdjustment.SkinTexture, e.target.value)}
step={1}
value={adjustments.skinTexture}
onDragStateChange={onDragStateChange}
/>
<Slider
label={t('adjustments.details.skinSmoothingScale')}
max={100}
min={0}
onChange={(e: any) => handleAdjustmentChange(DetailsAdjustment.SkinSmoothingScale, e.target.value)}
step={1}
value={adjustments.skinSmoothingScale}
onDragStateChange={onDragStateChange}
/>
<Slider
label={t('adjustments.details.dehaze')}
max={100}
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"blueYellow": "Blue/Yellow",
"centre": "Centré",
"chromaticAberration": "Chromatic Aberration",
"clarity": "Clarity",
"clarity": "Clarity", "skinSmoothing": "Skin Smoothing", "skinTexture": "Skin Texture", "skinSmoothingScale": "Smoothing Scale",
"color": "Color",
"dehaze": "Dehaze",
"luminance": "Luminance",
Expand Down
18 changes: 18 additions & 0 deletions src/utils/adjustments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export enum ColorGrading {

export enum DetailsAdjustment {
Clarity = 'clarity',
SkinSmoothing = 'skinSmoothing',
SkinTexture = 'skinTexture',
SkinSmoothingScale = 'skinSmoothingScale',
Dehaze = 'dehaze',
Structure = 'structure',
Centré = 'centré',
Expand Down Expand Up @@ -154,6 +157,9 @@ export interface Adjustments {
brightness: number;
centré: number;
clarity: number;
skinSmoothing: number;
skinTexture: number;
skinSmoothingScale: number;
chromaticAberrationBlueYellow: number;
chromaticAberrationRedCyan: number;
colorCalibration: ColorCalibration;
Expand Down Expand Up @@ -295,6 +301,9 @@ export interface MaskAdjustments {
blacks: number;
brightness: number;
clarity: number;
skinSmoothing: number;
skinTexture: number;
skinSmoothingScale: number;
colorGrading: ColorGradingProps;
colorNoiseReduction: number;
contrast: number;
Expand Down Expand Up @@ -423,6 +432,9 @@ export const INITIAL_MASK_ADJUSTMENTS: MaskAdjustments = {
blacks: 0,
brightness: 0,
clarity: 0,
skinSmoothing: 0,
skinTexture: 50,
skinSmoothingScale: 0,
colorGrading: { ...INITIAL_COLOR_GRADING },
colorNoiseReduction: 0,
contrast: 0,
Expand Down Expand Up @@ -482,6 +494,9 @@ export const INITIAL_ADJUSTMENTS: Adjustments = {
brightness: 0,
centré: 0,
clarity: 0,
skinSmoothing: 0,
skinTexture: 50,
skinSmoothingScale: 0,
chromaticAberrationBlueYellow: 0,
chromaticAberrationRedCyan: 0,
colorCalibration: { ...INITIAL_COLOR_CALIBRATION },
Expand Down Expand Up @@ -734,6 +749,9 @@ export const ADJUSTMENT_GROUPS: Record<string, AdjustmentGroup[]> = {
label: 'modals.copyPaste.groups.clarityDehaze',
keys: [
DetailsAdjustment.Clarity,
DetailsAdjustment.SkinSmoothing,
DetailsAdjustment.SkinTexture,
DetailsAdjustment.SkinSmoothingScale,
DetailsAdjustment.Structure,
DetailsAdjustment.Dehaze,
DetailsAdjustment.Centré,
Expand Down