Skip to content

Commit b53f68c

Browse files
authored
Merge pull request #939 from mmadesignerunknown/main
Visual Improvements And New Features For Color Grading Panel
2 parents 5ec7ce2 + 3515eb9 commit b53f68c

8 files changed

Lines changed: 467 additions & 201 deletions

File tree

package-lock.json

Lines changed: 236 additions & 166 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@types/lodash.throttle": "^4.1.9",
4747
"@types/react": "^19.2.14",
4848
"@types/react-dom": "^19.2.3",
49+
"@types/react-image-crop": "^8.1.6",
4950
"@typescript-eslint/eslint-plugin": "^8.57.0",
5051
"@typescript-eslint/parser": "^8.57.0",
5152
"@vitejs/plugin-react": "^6.0.1",

src-tauri/src/image_processing.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,7 @@ pub struct GlobalAdjustments {
10851085
pub color_grading_shadows: ColorGradeSettings,
10861086
pub color_grading_midtones: ColorGradeSettings,
10871087
pub color_grading_highlights: ColorGradeSettings,
1088+
pub color_grading_global: ColorGradeSettings,
10881089
pub color_grading_blending: f32,
10891090
pub color_grading_balance: f32,
10901091
_pad2: f32,
@@ -1146,6 +1147,7 @@ pub struct MaskAdjustments {
11461147
pub color_grading_shadows: ColorGradeSettings,
11471148
pub color_grading_midtones: ColorGradeSettings,
11481149
pub color_grading_highlights: ColorGradeSettings,
1150+
pub color_grading_global: ColorGradeSettings,
11491151
pub color_grading_blending: f32,
11501152
pub color_grading_balance: f32,
11511153
_pad5: f32,
@@ -1625,6 +1627,11 @@ fn get_global_adjustments_from_json(
16251627
} else {
16261628
ColorGradeSettings::default()
16271629
},
1630+
color_grading_global: if is_visible("color") {
1631+
parse_color_grade_settings(&cg_obj["global"])
1632+
} else {
1633+
ColorGradeSettings::default()
1634+
},
16281635
color_grading_blending: if is_visible("color") {
16291636
cg_obj["blending"].as_f64().unwrap_or(50.0) as f32 / SCALES.color_grading_blending
16301637
} else {
@@ -1759,6 +1766,11 @@ fn get_mask_adjustments_from_json(adj: &serde_json::Value) -> MaskAdjustments {
17591766
} else {
17601767
ColorGradeSettings::default()
17611768
},
1769+
color_grading_global: if is_visible("color") {
1770+
parse_color_grade_settings(&cg_obj["global"])
1771+
} else {
1772+
ColorGradeSettings::default()
1773+
},
17621774
color_grading_blending: if is_visible("color") {
17631775
cg_obj["blending"].as_f64().unwrap_or(50.0) as f32 / SCALES.color_grading_blending
17641776
} else {

src-tauri/src/preset_converter.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ pub fn convert_xmp_to_preset(xmp_content: &str) -> Result<Preset, String> {
227227
let mut shadows_map = Map::new();
228228
let mut midtones_map = Map::new();
229229
let mut highlights_map = Map::new();
230+
let mut global_map = Map::new();
230231
if let Some(raw) = attrs.get("SplitToningShadowHue")
231232
&& let Some(num) = parse_num(raw)
232233
&& let Some(json_val) = num_to_json(num)
@@ -281,6 +282,24 @@ pub fn convert_xmp_to_preset(xmp_content: &str) -> Result<Preset, String> {
281282
{
282283
highlights_map.insert("luminance".to_string(), json_val);
283284
}
285+
if let Some(raw) = attrs.get("ColorGradeGlobalHue")
286+
&& let Some(num) = parse_num(raw)
287+
&& let Some(json_val) = num_to_json(num)
288+
{
289+
global_map.insert("hue".to_string(), json_val);
290+
}
291+
if let Some(raw) = attrs.get("ColorGradeGlobalSat")
292+
&& let Some(num) = parse_num(raw)
293+
&& let Some(json_val) = num_to_json(num)
294+
{
295+
global_map.insert("saturation".to_string(), json_val);
296+
}
297+
if let Some(raw) = attrs.get("ColorGradeGlobalLum")
298+
&& let Some(num) = parse_num(raw)
299+
&& let Some(json_val) = num_to_json(num)
300+
{
301+
global_map.insert("luminance".to_string(), json_val);
302+
}
284303
if let Some(raw) = attrs.get("SplitToningBalance")
285304
&& let Some(num) = parse_num(raw)
286305
&& let Some(json_val) = num_to_json(num)
@@ -296,6 +315,9 @@ pub fn convert_xmp_to_preset(xmp_content: &str) -> Result<Preset, String> {
296315
if !highlights_map.is_empty() {
297316
color_grading_map.insert("highlights".to_string(), Value::Object(highlights_map));
298317
}
318+
if !global_map.is_empty() {
319+
color_grading_map.insert("global".to_string(), Value::Object(global_map));
320+
}
299321
if !color_grading_map.is_empty() {
300322
adjustments.insert("colorGrading".to_string(), Value::Object(color_grading_map));
301323
}

src-tauri/src/shaders/shader.wgsl

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ struct GlobalAdjustments {
8585
color_grading_shadows: ColorGradeSettings,
8686
color_grading_midtones: ColorGradeSettings,
8787
color_grading_highlights: ColorGradeSettings,
88+
color_grading_global: ColorGradeSettings,
8889
color_grading_blending: f32,
8990
color_grading_balance: f32,
9091
_pad2: f32,
@@ -144,6 +145,7 @@ struct MaskAdjustments {
144145
color_grading_shadows: ColorGradeSettings,
145146
color_grading_midtones: ColorGradeSettings,
146147
color_grading_highlights: ColorGradeSettings,
148+
color_grading_global: ColorGradeSettings,
147149
color_grading_blending: f32,
148150
color_grading_balance: f32,
149151
_pad5: f32,
@@ -659,7 +661,7 @@ fn apply_hsl_panel(color: vec3<f32>, hsl_adjustments: array<HslColor, 8>, coords
659661
return final_color;
660662
}
661663

662-
fn apply_color_grading(color: vec3<f32>, shadows: ColorGradeSettings, midtones: ColorGradeSettings, highlights: ColorGradeSettings, blending: f32, balance: f32) -> vec3<f32> {
664+
fn apply_color_grading(color: vec3<f32>, shadows: ColorGradeSettings, midtones: ColorGradeSettings, highlights: ColorGradeSettings, global: ColorGradeSettings, blending: f32, balance: f32) -> vec3<f32> {
663665
let luma = get_luma(max(vec3(0.0), color));
664666
let base_shadow_crossover = 0.1;
665667
let base_highlight_crossover = 0.5;
@@ -671,19 +673,24 @@ fn apply_color_grading(color: vec3<f32>, shadows: ColorGradeSettings, midtones:
671673
let shadow_mask = 1.0 - smoothstep(final_shadow_crossover - feather, final_shadow_crossover + feather, luma);
672674
let highlight_mask = smoothstep(highlight_crossover - feather, highlight_crossover + feather, luma);
673675
let midtone_mask = max(0.0, 1.0 - shadow_mask - highlight_mask);
676+
let global_mask = 1.0;
674677
var graded_color = color;
675678
let shadow_sat_strength = 0.3;
676679
let shadow_lum_strength = 0.5;
677680
let midtone_sat_strength = 0.6;
678681
let midtone_lum_strength = 0.8;
679682
let highlight_sat_strength = 0.8;
680683
let highlight_lum_strength = 1.0;
684+
let global_sat_strength = 1.0;
685+
let global_lum_strength = 1.0;
681686
if (shadows.saturation > 0.001) { let tint_rgb = hsv_to_rgb(vec3<f32>(shadows.hue, 1.0, 1.0)); graded_color += (tint_rgb - 0.5) * shadows.saturation * shadow_mask * shadow_sat_strength; }
682687
graded_color += shadows.luminance * shadow_mask * shadow_lum_strength;
683688
if (midtones.saturation > 0.001) { let tint_rgb = hsv_to_rgb(vec3<f32>(midtones.hue, 1.0, 1.0)); graded_color += (tint_rgb - 0.5) * midtones.saturation * midtone_mask * midtone_sat_strength; }
684689
graded_color += midtones.luminance * midtone_mask * midtone_lum_strength;
685690
if (highlights.saturation > 0.001) { let tint_rgb = hsv_to_rgb(vec3<f32>(highlights.hue, 1.0, 1.0)); graded_color += (tint_rgb - 0.5) * highlights.saturation * highlight_mask * highlight_sat_strength; }
686691
graded_color += highlights.luminance * highlight_mask * highlight_lum_strength;
692+
if (global.saturation > 0.001) { let tint_rgb = hsv_to_rgb(vec3<f32>(global.hue, 1.0, 1.0)); graded_color += (tint_rgb - 0.5) * global.saturation * global_mask * global_sat_strength; }
693+
graded_color += global.luminance * global_mask * global_lum_strength;
687694
return graded_color;
688695
}
689696

@@ -1096,7 +1103,7 @@ fn apply_all_mask_adjustments(
10961103
processed_rgb = apply_tonal_adjustments(processed_rgb, tonal_blurred, is_raw, adj.contrast, adj.shadows, adj.whites, adj.blacks);
10971104

10981105
processed_rgb = apply_hsl_panel(processed_rgb, adj.hsl, coords_i);
1099-
processed_rgb = apply_color_grading(processed_rgb, adj.color_grading_shadows, adj.color_grading_midtones, adj.color_grading_highlights, adj.color_grading_blending, adj.color_grading_balance);
1106+
processed_rgb = apply_color_grading(processed_rgb, adj.color_grading_shadows, adj.color_grading_midtones, adj.color_grading_highlights, adj.color_grading_global, adj.color_grading_blending, adj.color_grading_balance);
11001107
processed_rgb = apply_creative_color(processed_rgb, adj.saturation, adj.vibrance);
11011108

11021109
return processed_rgb;
@@ -1347,6 +1354,9 @@ fn scale_mask_adjustments(adj: MaskAdjustments, influence: f32) -> MaskAdjustmen
13471354
scaled.color_grading_highlights.saturation *= influence;
13481355
scaled.color_grading_highlights.luminance *= influence;
13491356

1357+
scaled.color_grading_global.saturation *= influence;
1358+
scaled.color_grading_global.luminance *= influence;
1359+
13501360
for (var i = 0u; i < 8u; i = i + 1u) {
13511361
scaled.hsl[i].hue *= influence;
13521362
scaled.hsl[i].saturation *= influence;
@@ -1503,6 +1513,7 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
15031513
adjustments.global.color_grading_shadows,
15041514
adjustments.global.color_grading_midtones,
15051515
adjustments.global.color_grading_highlights,
1516+
adjustments.global.color_grading_global,
15061517
adjustments.global.color_grading_blending,
15071518
adjustments.global.color_grading_balance
15081519
);

src/components/adjustments/Color.tsx

Lines changed: 126 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from 'react';
2-
import { Pipette } from 'lucide-react';
2+
import { Pipette, Sliders } from 'lucide-react';
3+
import { motion, AnimatePresence } from 'framer-motion';
34
import Slider from '../ui/Slider';
45
import ColorWheel from '../ui/ColorWheel';
56
import { ColorAdjustment, ColorCalibration, HueSatLum, INITIAL_ADJUSTMENTS } from '../../utils/adjustments';
@@ -114,6 +115,8 @@ const ColorSwatch = ({ color, name, isActive, onClick }: ColorSwatchProps) => {
114115
};
115116

116117
const ColorGradingPanel = ({ adjustments, setAdjustments, onDragStateChange }: ColorPanelProps) => {
118+
const [activeTab, setActiveTab] = useState<'3way' | 'global'>('3way');
119+
const [isExpanded, setIsExpanded] = useState(false);
117120
const colorGrading = adjustments.colorGrading || INITIAL_ADJUSTMENTS.colorGrading;
118121

119122
const handleChange = (grading: ColorGrading, newValue: HueSatLum) => {
@@ -136,39 +139,130 @@ const ColorGradingPanel = ({ adjustments, setAdjustments, onDragStateChange }: C
136139
}));
137140
};
138141

142+
const tabs = [
143+
{
144+
id: '3way',
145+
icon: (
146+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
147+
<circle cx="12" cy="6" r="4.5" />
148+
<circle cx="5" cy="18" r="4.5" />
149+
<circle cx="19" cy="18" r="4.5" />
150+
</svg>
151+
),
152+
},
153+
{
154+
id: 'global',
155+
icon: <div className="w-3.5 h-3.5 rounded-full" style={{ background: 'linear-gradient(to top, #666, #fff)' }} />,
156+
},
157+
];
158+
139159
return (
140160
<div>
141-
<div className="flex justify-center mb-4">
142-
<div className="w-[calc(50%-0.5rem)]">
143-
<ColorWheel
144-
defaultValue={INITIAL_ADJUSTMENTS.colorGrading.midtones}
145-
label="Midtones"
146-
onChange={(val: HueSatLum) => handleChange(ColorGrading.Midtones, val)}
147-
value={colorGrading.midtones}
148-
onDragStateChange={onDragStateChange}
149-
/>
150-
</div>
161+
<div className="flex items-center justify-start gap-2 mb-4 mt-2">
162+
{tabs.map((tab) => {
163+
const isActive = activeTab === tab.id;
164+
return (
165+
<button
166+
key={tab.id}
167+
onClick={() => setActiveTab(tab.id as '3way' | 'global')}
168+
className={`w-7 h-7 rounded-full flex items-center justify-center transition-all focus:outline-none
169+
${
170+
isActive
171+
? 'ring-2 ring-offset-2 ring-offset-surface ring-accent text-text-primary'
172+
: 'bg-bg-secondary text-text-secondary hover:text-text-primary hover:bg-bg-secondary/80'
173+
}`}
174+
>
175+
{tab.icon}
176+
</button>
177+
);
178+
})}
179+
180+
<div className="w-px h-5 bg-text-secondary/20 mx-1" />
181+
182+
<button
183+
onClick={() => setIsExpanded(!isExpanded)}
184+
className={`w-7 h-7 rounded-full flex items-center justify-center transition-all focus:outline-none
185+
${
186+
isExpanded
187+
? 'bg-accent text-button-text'
188+
: 'bg-bg-secondary text-text-secondary hover:text-text-primary hover:bg-bg-secondary/80'
189+
}`}
190+
data-tooltip="Toggle Sliders"
191+
>
192+
<Sliders size={14} />
193+
</button>
151194
</div>
152-
<div className="flex justify-between mb-2 gap-4">
153-
<div className="w-full">
154-
<ColorWheel
155-
defaultValue={INITIAL_ADJUSTMENTS.colorGrading.shadows}
156-
label="Shadows"
157-
onChange={(val: HueSatLum) => handleChange(ColorGrading.Shadows, val)}
158-
value={colorGrading.shadows}
159-
onDragStateChange={onDragStateChange}
160-
/>
161-
</div>
162-
<div className="w-full">
163-
<ColorWheel
164-
defaultValue={INITIAL_ADJUSTMENTS.colorGrading.highlights}
165-
label="Highlights"
166-
onChange={(val: HueSatLum) => handleChange(ColorGrading.Highlights, val)}
167-
value={colorGrading.highlights}
168-
onDragStateChange={onDragStateChange}
169-
/>
170-
</div>
195+
196+
<div className="relative w-full mb-4">
197+
<AnimatePresence mode="wait">
198+
{activeTab === '3way' ? (
199+
<motion.div
200+
key="3way"
201+
initial={{ opacity: 0, x: -15 }}
202+
animate={{ opacity: 1, x: 0 }}
203+
exit={{ opacity: 0, x: -15 }}
204+
transition={{ duration: 0.2 }}
205+
className="w-full"
206+
>
207+
<div className="flex justify-center mb-4">
208+
<div className="w-[calc(50%-0.5rem)]">
209+
<ColorWheel
210+
defaultValue={INITIAL_ADJUSTMENTS.colorGrading.midtones}
211+
label="Midtones"
212+
onChange={(val: HueSatLum) => handleChange(ColorGrading.Midtones, val)}
213+
value={colorGrading.midtones}
214+
onDragStateChange={onDragStateChange}
215+
isExpanded={isExpanded}
216+
/>
217+
</div>
218+
</div>
219+
<div className="flex justify-between mb-2 gap-4">
220+
<div className="w-full flex-1 min-w-0">
221+
<ColorWheel
222+
defaultValue={INITIAL_ADJUSTMENTS.colorGrading.shadows}
223+
label="Shadows"
224+
onChange={(val: HueSatLum) => handleChange(ColorGrading.Shadows, val)}
225+
value={colorGrading.shadows}
226+
onDragStateChange={onDragStateChange}
227+
isExpanded={isExpanded}
228+
/>
229+
</div>
230+
<div className="w-full flex-1 min-w-0">
231+
<ColorWheel
232+
defaultValue={INITIAL_ADJUSTMENTS.colorGrading.highlights}
233+
label="Highlights"
234+
onChange={(val: HueSatLum) => handleChange(ColorGrading.Highlights, val)}
235+
value={colorGrading.highlights}
236+
onDragStateChange={onDragStateChange}
237+
isExpanded={isExpanded}
238+
/>
239+
</div>
240+
</div>
241+
</motion.div>
242+
) : (
243+
<motion.div
244+
key="global"
245+
initial={{ opacity: 0, x: 15 }}
246+
animate={{ opacity: 1, x: 0 }}
247+
exit={{ opacity: 0, x: 15 }}
248+
transition={{ duration: 0.2 }}
249+
className="w-full flex justify-center pb-2"
250+
>
251+
<div className="w-full max-w-70">
252+
<ColorWheel
253+
defaultValue={INITIAL_ADJUSTMENTS.colorGrading.global}
254+
label="Global"
255+
onChange={(val: HueSatLum) => handleChange(ColorGrading.Global, val)}
256+
value={colorGrading.global || INITIAL_ADJUSTMENTS.colorGrading.global}
257+
onDragStateChange={onDragStateChange}
258+
isExpanded={isExpanded}
259+
/>
260+
</div>
261+
</motion.div>
262+
)}
263+
</AnimatePresence>
171264
</div>
265+
172266
<div>
173267
<Slider
174268
defaultValue={50}
@@ -346,7 +440,7 @@ export default function ColorPanel({
346440
onChange={(e: any) => handleGlobalChange(ColorAdjustment.Temperature, e.target.value)}
347441
step={1}
348442
value={adjustments.temperature || 0}
349-
trackClassName='temperature-gradient-track'
443+
trackClassName="temperature-gradient-track"
350444
onDragStateChange={onDragStateChange}
351445
/>
352446
<Slider
@@ -356,7 +450,7 @@ export default function ColorPanel({
356450
onChange={(e: any) => handleGlobalChange(ColorAdjustment.Tint, e.target.value)}
357451
step={1}
358452
value={adjustments.tint || 0}
359-
trackClassName='tint-gradient-track'
453+
trackClassName="tint-gradient-track"
360454
onDragStateChange={onDragStateChange}
361455
/>
362456
</div>

0 commit comments

Comments
 (0)