Phase 3: Color correction panel with sliders, tint, and preset filters
- Color_Filter enum: None, Vintage, Noir, Cool_Tone, Warm_Tone, High_Contrast, Faded, Dramatic - editor_apply_filter_preset: maps each filter to adjustment values - editor_render_adjust_panel: right-side panel with: - Brightness/Contrast/Saturation/Tint sliders via rl.GuiSliderBar - Tint color swatches (6 preset tints) - 8 preset filter buttons - Apply and Reset buttons - editor_update_display: composites base image with tint preview into display_rt for real-time adjustment preview - 'Adjust' toggle button in toolbar (highlights when active) - Canvas width shrinks when adjustments panel is open - Color_Adjustments struct gains filter field - Panel_Editor_State gains show_adjust_panel field Build passes. 155/156 tests pass (1 pre-existing CLI/TUI failure).
This commit is contained in:
parent
49b383db2e
commit
4445574b43
@ -28,12 +28,24 @@ Brush_Stroke :: struct {
|
||||
tool: Editor_Tool,
|
||||
}
|
||||
|
||||
Color_Filter :: enum {
|
||||
None,
|
||||
Vintage,
|
||||
Noir,
|
||||
Cool_Tone,
|
||||
Warm_Tone,
|
||||
High_Contrast,
|
||||
Faded,
|
||||
Dramatic,
|
||||
}
|
||||
|
||||
Color_Adjustments :: struct {
|
||||
brightness: f32,
|
||||
contrast: f32,
|
||||
saturation: f32,
|
||||
tint_color: rl.Color,
|
||||
tint_amount: f32,
|
||||
filter: Color_Filter,
|
||||
}
|
||||
|
||||
Panel_Editor_State :: struct {
|
||||
@ -60,8 +72,56 @@ Panel_Editor_State :: struct {
|
||||
is_panning: bool,
|
||||
pan_start: rl.Vector2,
|
||||
pan_offset_start: rl.Vector2,
|
||||
show_color_panel: bool,
|
||||
needs_redraw: bool,
|
||||
show_color_panel: bool,
|
||||
show_adjust_panel: bool,
|
||||
needs_redraw: bool,
|
||||
}
|
||||
|
||||
editor_apply_filter_preset :: proc(adj: ^Color_Adjustments) {
|
||||
switch adj.filter {
|
||||
case .None:
|
||||
return
|
||||
case .Vintage:
|
||||
adj.brightness = -0.05
|
||||
adj.contrast = 0.1
|
||||
adj.saturation = -0.3
|
||||
adj.tint_color = rl.Color{255, 230, 180, 255}
|
||||
adj.tint_amount = 0.15
|
||||
case .Noir:
|
||||
adj.brightness = -0.1
|
||||
adj.contrast = 0.4
|
||||
adj.saturation = -1.0
|
||||
adj.tint_amount = 0
|
||||
case .Cool_Tone:
|
||||
adj.brightness = 0.0
|
||||
adj.contrast = 0.05
|
||||
adj.saturation = -0.1
|
||||
adj.tint_color = rl.Color{180, 200, 255, 255}
|
||||
adj.tint_amount = 0.12
|
||||
case .Warm_Tone:
|
||||
adj.brightness = 0.05
|
||||
adj.contrast = 0.05
|
||||
adj.saturation = -0.1
|
||||
adj.tint_color = rl.Color{255, 200, 150, 255}
|
||||
adj.tint_amount = 0.15
|
||||
case .High_Contrast:
|
||||
adj.brightness = 0.0
|
||||
adj.contrast = 0.5
|
||||
adj.saturation = 0.2
|
||||
adj.tint_amount = 0
|
||||
case .Faded:
|
||||
adj.brightness = 0.1
|
||||
adj.contrast = -0.2
|
||||
adj.saturation = -0.4
|
||||
adj.tint_color = rl.Color{240, 235, 220, 255}
|
||||
adj.tint_amount = 0.1
|
||||
case .Dramatic:
|
||||
adj.brightness = -0.15
|
||||
adj.contrast = 0.6
|
||||
adj.saturation = 0.15
|
||||
adj.tint_color = rl.Color{200, 180, 255, 255}
|
||||
adj.tint_amount = 0.08
|
||||
}
|
||||
}
|
||||
|
||||
editor_default_adjustments :: proc() -> Color_Adjustments {
|
||||
@ -71,6 +131,7 @@ editor_default_adjustments :: proc() -> Color_Adjustments {
|
||||
saturation = 0,
|
||||
tint_color = rl.WHITE,
|
||||
tint_amount = 0,
|
||||
filter = .None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,6 +202,7 @@ editor_open :: proc(app: ^GUI_App_State, panel_id: string) -> bool {
|
||||
app.editor.pan_start = rl.Vector2{}
|
||||
app.editor.pan_offset_start = rl.Vector2{}
|
||||
app.editor.show_color_panel = false
|
||||
app.editor.show_adjust_panel = false
|
||||
app.editor.needs_redraw = true
|
||||
|
||||
app.editor.current_stroke = Brush_Stroke{points = make([dynamic]rl.Vector2)}
|
||||
@ -464,6 +526,10 @@ editor_render :: proc(app: ^GUI_App_State) {
|
||||
|
||||
if ed.needs_redraw {
|
||||
editor_replay_strokes(ed)
|
||||
editor_update_display(ed)
|
||||
}
|
||||
if ed.adjustments_dirty {
|
||||
editor_update_display(ed)
|
||||
}
|
||||
|
||||
screen_w := rl.GetScreenWidth()
|
||||
@ -476,7 +542,9 @@ editor_render :: proc(app: ^GUI_App_State) {
|
||||
|
||||
canvas_x := sidebar_w_f32 + 4
|
||||
canvas_y := top_bar_h + toolbar_h + 4
|
||||
canvas_w := f32(screen_w) - sidebar_w_f32 - 8
|
||||
adj_panel_w: f32 = 0
|
||||
if ed.show_adjust_panel { adj_panel_w = 224 }
|
||||
canvas_w := f32(screen_w) - sidebar_w_f32 - 8 - adj_panel_w
|
||||
canvas_h := f32(screen_h) - top_bar_h - bottom_bar_h - toolbar_h - 8
|
||||
|
||||
ed.canvas_rect = rl.Rectangle{canvas_x, canvas_y, canvas_w, canvas_h}
|
||||
@ -496,11 +564,6 @@ editor_render :: proc(app: ^GUI_App_State) {
|
||||
draw_x := canvas_x + (canvas_w - draw_w) * 0.5 + vec2_x(ed.pan_offset) * fit_scale
|
||||
draw_y := canvas_y + (canvas_h - draw_h) * 0.5 + vec2_y(ed.pan_offset) * fit_scale
|
||||
|
||||
tint := rl.WHITE
|
||||
if ed.adjustments.tint_amount > 0 {
|
||||
tint = rl.Fade(ed.adjustments.tint_color, 1.0 - f32(ed.adjustments.tint_amount))
|
||||
}
|
||||
|
||||
img_rect := rl.Rectangle{0, 0, f32(ed.drawing_rt.texture.width), f32(ed.drawing_rt.texture.height)}
|
||||
dest_rect := rl.Rectangle{draw_x, draw_y, draw_w, draw_h}
|
||||
|
||||
@ -512,7 +575,7 @@ editor_render :: proc(app: ^GUI_App_State) {
|
||||
dest_rect,
|
||||
rl.Vector2{},
|
||||
0,
|
||||
tint,
|
||||
rl.WHITE,
|
||||
)
|
||||
|
||||
rl.DrawTexturePro(
|
||||
@ -534,6 +597,35 @@ editor_render :: proc(app: ^GUI_App_State) {
|
||||
}
|
||||
|
||||
editor_render_toolbar(app)
|
||||
|
||||
if ed.show_adjust_panel {
|
||||
editor_render_adjust_panel(app)
|
||||
}
|
||||
}
|
||||
|
||||
editor_update_display :: proc(ed: ^Panel_Editor_State) {
|
||||
if !ed.rt_valid || !ed.base_image_valid { return }
|
||||
|
||||
tex := rl.LoadTextureFromImage(ed.base_image)
|
||||
if tex.id == 0 { return }
|
||||
|
||||
rl.BeginTextureMode(ed.display_rt)
|
||||
rl.ClearBackground(rl.Color{0, 0, 0, 0})
|
||||
|
||||
tint := rl.WHITE
|
||||
adj := ed.adjustments
|
||||
if adj.tint_amount > 0 {
|
||||
tint = rl.Fade(adj.tint_color, 1.0 - adj.tint_amount)
|
||||
}
|
||||
|
||||
img_rect := rl.Rectangle{0, 0, f32(tex.width), f32(tex.height)}
|
||||
dest_rect := rl.Rectangle{0, 0, f32(ed.display_rt.texture.width), f32(ed.display_rt.texture.height)}
|
||||
|
||||
rl.DrawTexturePro(tex, img_rect, dest_rect, rl.Vector2{}, 0, tint)
|
||||
rl.EndTextureMode()
|
||||
|
||||
rl.UnloadTexture(tex)
|
||||
ed.adjustments_dirty = false
|
||||
}
|
||||
|
||||
editor_render_stroke_preview :: proc(ed: ^Panel_Editor_State, dest_rect: rl.Rectangle) {
|
||||
@ -652,6 +744,17 @@ editor_render_toolbar :: proc(app: ^GUI_App_State) {
|
||||
zoom_text := fmt.tprintf("Zoom: %.0f%%", ed.zoom * 100)
|
||||
rl.DrawText(to_cstr(zoom_text), c.int(zoom_x), c.int(start_y + 6), 11, rl.Color{160, 160, 180, 255})
|
||||
|
||||
adjust_x := zoom_x + 90
|
||||
adjust_bg := rl.Color{28, 28, 42, 255}
|
||||
if ed.show_adjust_panel { adjust_bg = rl.Color{99, 102, 241, 255} }
|
||||
rl.DrawRectangle(c.int(adjust_x), c.int(start_y), c.int(60), c.int(btn_h), adjust_bg)
|
||||
rl.DrawText("Adjust", c.int(adjust_x + 4), c.int(start_y + 6), 11, rl.WHITE)
|
||||
if rl.IsMouseButtonPressed(.LEFT) &&
|
||||
mx >= adjust_x && mx <= adjust_x + 60 &&
|
||||
my >= start_y && my <= start_y + btn_h {
|
||||
ed.show_adjust_panel = !ed.show_adjust_panel
|
||||
}
|
||||
|
||||
undo_x := tb_x + tb_w - 210
|
||||
undo_label := fmt.tprintf("Undo(%d)", len(ed.undo_stack))
|
||||
rl.DrawRectangle(c.int(undo_x), c.int(start_y), c.int(50), c.int(btn_h), rl.Color{28, 28, 42, 255})
|
||||
@ -733,3 +836,155 @@ editor_render_color_panel :: proc(ed: ^Panel_Editor_State, px, py: f32) {
|
||||
rl.DrawRectangle(c.int(px + 70), c.int(cur_y), c.int(30), c.int(16), ed.brush_color)
|
||||
rl.DrawRectangleLines(c.int(px + 70), c.int(cur_y), c.int(30), c.int(16), rl.Color{80, 80, 100, 255})
|
||||
}
|
||||
|
||||
editor_render_adjust_panel :: proc(app: ^GUI_App_State) {
|
||||
ed := &app.editor
|
||||
screen_w := rl.GetScreenWidth()
|
||||
screen_h := rl.GetScreenHeight()
|
||||
|
||||
panel_w: f32 = 220
|
||||
panel_h: f32 = f32(screen_h) - 38 - 44 - 8
|
||||
panel_x := f32(screen_w) - panel_w - 4
|
||||
panel_y: f32 = 38 + 40 + 4
|
||||
|
||||
rl.DrawRectangle(c.int(panel_x), c.int(panel_y), c.int(panel_w), c.int(panel_h), rl.Color{18, 18, 30, 245})
|
||||
rl.DrawRectangleLines(c.int(panel_x), c.int(panel_y), c.int(panel_w), c.int(panel_h), rl.Color{50, 50, 70, 255})
|
||||
|
||||
label_x := panel_x + 10
|
||||
slider_w := panel_w - 20
|
||||
slider_h: f32 = 14
|
||||
row_h: f32 = 36
|
||||
y := panel_y + 8
|
||||
|
||||
rl.DrawText("Color Adjustments", c.int(label_x), c.int(y), 12, rl.Color{200, 200, 220, 255})
|
||||
y += 22
|
||||
|
||||
adj := &ed.adjustments
|
||||
|
||||
brightness_val := adj.brightness
|
||||
rl.DrawText("Brightness", c.int(label_x), c.int(y), 10, rl.Color{160, 160, 180, 255})
|
||||
y += 14
|
||||
slider_rect := rl.Rectangle{label_x, y, slider_w, slider_h}
|
||||
rl.GuiSliderBar(slider_rect, "- ", " +", &brightness_val, -1.0, 1.0)
|
||||
if brightness_val != adj.brightness {
|
||||
adj.brightness = brightness_val
|
||||
ed.adjustments_dirty = true
|
||||
}
|
||||
y += row_h
|
||||
|
||||
contrast_val := adj.contrast
|
||||
rl.DrawText("Contrast", c.int(label_x), c.int(y), 10, rl.Color{160, 160, 180, 255})
|
||||
y += 14
|
||||
slider_rect = rl.Rectangle{label_x, y, slider_w, slider_h}
|
||||
rl.GuiSliderBar(slider_rect, "- ", " +", &contrast_val, -1.0, 1.0)
|
||||
if contrast_val != adj.contrast {
|
||||
adj.contrast = contrast_val
|
||||
ed.adjustments_dirty = true
|
||||
}
|
||||
y += row_h
|
||||
|
||||
sat_val := adj.saturation
|
||||
rl.DrawText("Saturation", c.int(label_x), c.int(y), 10, rl.Color{160, 160, 180, 255})
|
||||
y += 14
|
||||
slider_rect = rl.Rectangle{label_x, y, slider_w, slider_h}
|
||||
rl.GuiSliderBar(slider_rect, "- ", " +", &sat_val, -1.0, 1.0)
|
||||
if sat_val != adj.saturation {
|
||||
adj.saturation = sat_val
|
||||
ed.adjustments_dirty = true
|
||||
}
|
||||
y += row_h
|
||||
|
||||
tint_val := adj.tint_amount
|
||||
rl.DrawText("Tint Amount", c.int(label_x), c.int(y), 10, rl.Color{160, 160, 180, 255})
|
||||
y += 14
|
||||
slider_rect = rl.Rectangle{label_x, y, slider_w, slider_h}
|
||||
rl.GuiSliderBar(slider_rect, "0 ", " 1", &tint_val, 0.0, 1.0)
|
||||
if tint_val != adj.tint_amount {
|
||||
adj.tint_amount = tint_val
|
||||
ed.adjustments_dirty = true
|
||||
}
|
||||
y += row_h
|
||||
|
||||
rl.DrawText("Tint Color", c.int(label_x), c.int(y), 10, rl.Color{160, 160, 180, 255})
|
||||
y += 14
|
||||
tint_colors := []rl.Color{
|
||||
rl.WHITE, rl.Color{255, 230, 180, 255}, rl.Color{180, 200, 255, 255},
|
||||
rl.Color{255, 200, 150, 255}, rl.Color{200, 255, 200, 255}, rl.Color{255, 200, 200, 255},
|
||||
}
|
||||
tsw: f32 = 22
|
||||
tgap: f32 = 4
|
||||
for idx in 0..<len(tint_colors) {
|
||||
tc := tint_colors[idx]
|
||||
tx := label_x + f32(idx) * (tsw + tgap)
|
||||
rl.DrawRectangle(c.int(tx), c.int(y), c.int(tsw), c.int(tsw), tc)
|
||||
is_sel := adj.tint_color == tc
|
||||
if is_sel {
|
||||
rl.DrawRectangleLines(c.int(tx), c.int(y), c.int(tsw), c.int(tsw), rl.Color{99, 102, 241, 255})
|
||||
} else {
|
||||
rl.DrawRectangleLines(c.int(tx), c.int(y), c.int(tsw), c.int(tsw), rl.Color{60, 60, 80, 255})
|
||||
}
|
||||
mouse := rl.GetMousePosition()
|
||||
mx := vec2_x(mouse)
|
||||
my := vec2_y(mouse)
|
||||
if rl.IsMouseButtonPressed(.LEFT) &&
|
||||
mx >= tx && mx <= tx + tsw &&
|
||||
my >= y && my <= y + tsw {
|
||||
adj.tint_color = tc
|
||||
ed.adjustments_dirty = true
|
||||
}
|
||||
}
|
||||
y += tsw + 12
|
||||
|
||||
rl.DrawText("Presets", c.int(label_x), c.int(y), 10, rl.Color{160, 160, 180, 255})
|
||||
y += 14
|
||||
filter_names := []string{"None", "Vintage", "Noir", "Cool", "Warm", "HiCon", "Faded", "Drama"}
|
||||
filter_enums := []Color_Filter{.None, .Vintage, .Noir, .Cool_Tone, .Warm_Tone, .High_Contrast, .Faded, .Dramatic}
|
||||
fbtn_w: f32 = 48
|
||||
fbtn_h: f32 = 20
|
||||
fcols: f32 = 4
|
||||
for idx in 0..<len(filter_names) {
|
||||
col := f32(idx % int(fcols))
|
||||
row := f32(idx / int(fcols))
|
||||
fx := label_x + col * (fbtn_w + 4)
|
||||
fy := y + row * (fbtn_h + 4)
|
||||
is_sel := adj.filter == filter_enums[idx]
|
||||
bg := rl.Color{28, 28, 42, 255}
|
||||
if is_sel { bg = rl.Color{99, 102, 241, 255} }
|
||||
rl.DrawRectangle(c.int(fx), c.int(fy), c.int(fbtn_w), c.int(fbtn_h), bg)
|
||||
rl.DrawText(to_cstr(filter_names[idx]), c.int(fx + 3), c.int(fy + 4), 9, rl.WHITE)
|
||||
mouse := rl.GetMousePosition()
|
||||
mx := vec2_x(mouse)
|
||||
my := vec2_y(mouse)
|
||||
if rl.IsMouseButtonPressed(.LEFT) &&
|
||||
mx >= fx && mx <= fx + fbtn_w &&
|
||||
my >= fy && my <= fy + fbtn_h {
|
||||
adj.filter = filter_enums[idx]
|
||||
editor_apply_filter_preset(adj)
|
||||
ed.adjustments_dirty = true
|
||||
}
|
||||
}
|
||||
y += (f32(len(filter_names)) / fcols) * (fbtn_h + 4) + 12
|
||||
|
||||
btn_w: f32 = 92
|
||||
btn_h: f32 = 24
|
||||
rl.DrawRectangle(c.int(label_x), c.int(y), c.int(btn_w), c.int(btn_h), rl.Color{34, 139, 34, 255})
|
||||
rl.DrawText("Apply", c.int(label_x + 30), c.int(y + 5), 11, rl.WHITE)
|
||||
mouse := rl.GetMousePosition()
|
||||
mx := vec2_x(mouse)
|
||||
my := vec2_y(mouse)
|
||||
if rl.IsMouseButtonPressed(.LEFT) &&
|
||||
mx >= label_x && mx <= label_x + btn_w &&
|
||||
my >= y && my <= y + btn_h {
|
||||
push_status(&app.status_msg, &app.action_log, editor_commit(app))
|
||||
}
|
||||
|
||||
reset_x := label_x + btn_w + 6
|
||||
rl.DrawRectangle(c.int(reset_x), c.int(y), c.int(btn_w), c.int(btn_h), rl.Color{80, 80, 80, 255})
|
||||
rl.DrawText("Reset", c.int(reset_x + 28), c.int(y + 5), 11, rl.WHITE)
|
||||
if rl.IsMouseButtonPressed(.LEFT) &&
|
||||
mx >= reset_x && mx <= reset_x + btn_w &&
|
||||
my >= y && my <= y + btn_h {
|
||||
ed.adjustments = editor_default_adjustments()
|
||||
ed.adjustments_dirty = true
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user