mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 17:19:50 +00:00
Implement a color curves image effect
Massive thanks to Material Maker for the custom widget code. The color curves effect is still WIP, I need to make the tangent points visible (not yet sure why they aren't now), add some curve presets, and implement it as a layer effect as well.
This commit is contained in:
parent
605bff7324
commit
048058bd35
|
@ -685,6 +685,10 @@ adjust_brightness_contrast={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": []
|
"events": []
|
||||||
}
|
}
|
||||||
|
color_curves={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": []
|
||||||
|
}
|
||||||
gradient={
|
gradient={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": []
|
"events": []
|
||||||
|
|
|
@ -64,6 +64,7 @@ enum EffectsMenu {
|
||||||
DESATURATION,
|
DESATURATION,
|
||||||
HSV,
|
HSV,
|
||||||
BRIGHTNESS_SATURATION,
|
BRIGHTNESS_SATURATION,
|
||||||
|
COLOR_CURVES,
|
||||||
PALETTIZE,
|
PALETTIZE,
|
||||||
PIXELIZE,
|
PIXELIZE,
|
||||||
POSTERIZE,
|
POSTERIZE,
|
||||||
|
@ -809,6 +810,7 @@ func _initialize_keychain() -> void:
|
||||||
&"drop_shadow": Keychain.InputAction.new("", "Effects menu", true),
|
&"drop_shadow": Keychain.InputAction.new("", "Effects menu", true),
|
||||||
&"adjust_hsv": Keychain.InputAction.new("", "Effects menu", true),
|
&"adjust_hsv": Keychain.InputAction.new("", "Effects menu", true),
|
||||||
&"adjust_brightness_contrast": Keychain.InputAction.new("", "Effects menu", true),
|
&"adjust_brightness_contrast": Keychain.InputAction.new("", "Effects menu", true),
|
||||||
|
&"color_curves": Keychain.InputAction.new("", "Effects menu", true),
|
||||||
&"gaussian_blur": Keychain.InputAction.new("", "Effects menu", true),
|
&"gaussian_blur": Keychain.InputAction.new("", "Effects menu", true),
|
||||||
&"gradient": Keychain.InputAction.new("", "Effects menu", true),
|
&"gradient": Keychain.InputAction.new("", "Effects menu", true),
|
||||||
&"gradient_map": Keychain.InputAction.new("", "Effects menu", true),
|
&"gradient_map": Keychain.InputAction.new("", "Effects menu", true),
|
||||||
|
|
67
src/Shaders/Effects/ColorCurves.gdshader
Normal file
67
src/Shaders/Effects/ColorCurves.gdshader
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
shader_type canvas_item;
|
||||||
|
|
||||||
|
// CurveTexture(s)
|
||||||
|
uniform sampler2D curve_rgb;
|
||||||
|
uniform sampler2D curve_red;
|
||||||
|
uniform sampler2D curve_green;
|
||||||
|
uniform sampler2D curve_blue;
|
||||||
|
uniform sampler2D curve_alpha;
|
||||||
|
uniform sampler2D curve_hue;
|
||||||
|
uniform sampler2D curve_sat;
|
||||||
|
uniform sampler2D curve_value;
|
||||||
|
uniform sampler2D selection : filter_nearest;
|
||||||
|
|
||||||
|
|
||||||
|
vec3 rgb2hsb(vec3 c) {
|
||||||
|
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
||||||
|
vec4 p = mix(vec4(c.bg, K.wz),
|
||||||
|
vec4(c.gb, K.xy),
|
||||||
|
step(c.b, c.g));
|
||||||
|
vec4 q = mix(vec4(p.xyw, c.r),
|
||||||
|
vec4(c.r, p.yzx),
|
||||||
|
step(p.x, c.r));
|
||||||
|
float d = q.x - min(q.w, q.y);
|
||||||
|
float e = 1.0e-10;
|
||||||
|
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)),
|
||||||
|
d / (q.x + e),
|
||||||
|
q.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 hsb2rgb(vec3 c)
|
||||||
|
{
|
||||||
|
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||||
|
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||||
|
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
vec4 original_color = texture(TEXTURE, UV);
|
||||||
|
vec4 selection_color = texture(selection, UV);
|
||||||
|
vec4 col;
|
||||||
|
float red_curve_color = texture(curve_red, vec2(COLOR.r, 0.0)).r;
|
||||||
|
float green_curve_color = texture(curve_green, vec2(COLOR.g, 0.0)).r;
|
||||||
|
float blue_curve_color = texture(curve_blue, vec2(COLOR.b, 0.0)).r;
|
||||||
|
float alpha_curve_color = texture(curve_alpha, vec2(COLOR.a, 0.0)).r;
|
||||||
|
col.r = red_curve_color;
|
||||||
|
col.g = green_curve_color;
|
||||||
|
col.b = blue_curve_color;
|
||||||
|
col.a = alpha_curve_color;
|
||||||
|
|
||||||
|
vec3 hsb = rgb2hsb(col.rgb);
|
||||||
|
float hue_curve_color = texture(curve_hue, vec2(hsb.r, 0.0)).r;
|
||||||
|
float sat_curve_color = texture(curve_sat, vec2(hsb.g, 0.0)).r;
|
||||||
|
float value_curve_color = texture(curve_value, vec2(hsb.b, 0.0)).r;
|
||||||
|
hsb.r = hue_curve_color;
|
||||||
|
hsb.g = sat_curve_color;
|
||||||
|
hsb.b = value_curve_color;
|
||||||
|
|
||||||
|
col.rgb = hsb2rgb(hsb);
|
||||||
|
|
||||||
|
float rgb_curve_color_r = texture(curve_rgb, vec2(col.r, 0.0)).r;
|
||||||
|
float rgb_curve_color_g = texture(curve_rgb, vec2(col.g, 0.0)).r;
|
||||||
|
float rgb_curve_color_b = texture(curve_rgb, vec2(col.b, 0.0)).r;
|
||||||
|
col.rgb = vec3(rgb_curve_color_r, rgb_curve_color_g, rgb_curve_color_b);
|
||||||
|
vec4 output = mix(original_color.rgba, col, selection_color.a);
|
||||||
|
COLOR = output;
|
||||||
|
}
|
50
src/UI/Dialogs/ImageEffects/ColorCurvesDialog.gd
Normal file
50
src/UI/Dialogs/ImageEffects/ColorCurvesDialog.gd
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
extends ImageEffect
|
||||||
|
|
||||||
|
enum Channel { RGB, RED, GREEN, BLUE, ALPHA, HUE, SATURATION, VALUE }
|
||||||
|
const SHADER := preload("res://src/Shaders/Effects/ColorCurves.gdshader")
|
||||||
|
|
||||||
|
var curves: Array[Curve]
|
||||||
|
@onready var channel_option_button := %ChannelOptionButton as OptionButton
|
||||||
|
@onready var curve_edit := $VBoxContainer/CurveEdit as CurveEdit
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
super._ready()
|
||||||
|
var sm := ShaderMaterial.new()
|
||||||
|
sm.shader = SHADER
|
||||||
|
preview.set_material(sm)
|
||||||
|
for i in channel_option_button.item_count:
|
||||||
|
var curve := Curve.new()
|
||||||
|
curve.add_point(Vector2.ZERO, 0, 1, Curve.TANGENT_LINEAR)
|
||||||
|
curve.add_point(Vector2.ONE, 1, 0, Curve.TANGENT_LINEAR)
|
||||||
|
curves.append(curve)
|
||||||
|
curve_edit.curve = curves[Channel.RGB]
|
||||||
|
|
||||||
|
|
||||||
|
func commit_action(cel: Image, project := Global.current_project) -> void:
|
||||||
|
var selection_tex: ImageTexture
|
||||||
|
if selection_checkbox.button_pressed and project.has_selection:
|
||||||
|
var selection := project.selection_map.return_cropped_copy(project.size)
|
||||||
|
selection_tex = ImageTexture.create_from_image(selection)
|
||||||
|
|
||||||
|
var params := {
|
||||||
|
"curve_rgb": CurveEdit.to_texture(curves[Channel.RGB]),
|
||||||
|
"curve_red": CurveEdit.to_texture(curves[Channel.RED]),
|
||||||
|
"curve_green": CurveEdit.to_texture(curves[Channel.GREEN]),
|
||||||
|
"curve_blue": CurveEdit.to_texture(curves[Channel.BLUE]),
|
||||||
|
"curve_alpha": CurveEdit.to_texture(curves[Channel.ALPHA]),
|
||||||
|
"curve_hue": CurveEdit.to_texture(curves[Channel.HUE]),
|
||||||
|
"curve_sat": CurveEdit.to_texture(curves[Channel.SATURATION]),
|
||||||
|
"curve_value": CurveEdit.to_texture(curves[Channel.VALUE]),
|
||||||
|
"selection": selection_tex
|
||||||
|
}
|
||||||
|
if !has_been_confirmed:
|
||||||
|
for param in params:
|
||||||
|
preview.material.set_shader_parameter(param, params[param])
|
||||||
|
else:
|
||||||
|
var gen := ShaderImageEffect.new()
|
||||||
|
gen.generate_image(cel, SHADER, params, project.size)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_channel_option_button_item_selected(index: int) -> void:
|
||||||
|
curve_edit.curve = curves[index]
|
60
src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn
Normal file
60
src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
[gd_scene load_steps=5 format=3 uid="uid://cthknpr74lawl"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bybqhhayl5ay5" path="res://src/UI/Dialogs/ImageEffects/ImageEffectParent.tscn" id="1_4g7xo"]
|
||||||
|
[ext_resource type="Script" path="res://src/UI/Dialogs/ImageEffects/ColorCurvesDialog.gd" id="2_xkivc"]
|
||||||
|
[ext_resource type="Script" path="res://src/UI/Nodes/CurveEditor/CurveEdit.gd" id="3_fp0lm"]
|
||||||
|
|
||||||
|
[sub_resource type="Curve" id="Curve_p6hdh"]
|
||||||
|
_data = [Vector2(0, 0), 0.0, 1.0, 0, 1, Vector2(1, 1), 1.0, 0.0, 1, 0]
|
||||||
|
point_count = 2
|
||||||
|
|
||||||
|
[node name="ColorCurvesDialog" instance=ExtResource("1_4g7xo")]
|
||||||
|
title = "Color Curves"
|
||||||
|
size = Vector2i(362, 440)
|
||||||
|
script = ExtResource("2_xkivc")
|
||||||
|
|
||||||
|
[node name="VBoxContainer" parent="." index="3"]
|
||||||
|
offset_bottom = 391.0
|
||||||
|
|
||||||
|
[node name="ShowAnimate" parent="VBoxContainer" index="0"]
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="ColorOptions" type="GridContainer" parent="VBoxContainer" index="3"]
|
||||||
|
layout_mode = 2
|
||||||
|
columns = 2
|
||||||
|
|
||||||
|
[node name="ChannelLabel" type="Label" parent="VBoxContainer/ColorOptions" index="0"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Channel:"
|
||||||
|
|
||||||
|
[node name="ChannelOptionButton" type="OptionButton" parent="VBoxContainer/ColorOptions" index="1"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
selected = 0
|
||||||
|
item_count = 8
|
||||||
|
popup/item_0/text = "RGB"
|
||||||
|
popup/item_1/text = "Red"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
popup/item_2/text = "Green"
|
||||||
|
popup/item_2/id = 2
|
||||||
|
popup/item_3/text = "Blue"
|
||||||
|
popup/item_3/id = 3
|
||||||
|
popup/item_4/text = "Alpha"
|
||||||
|
popup/item_4/id = 4
|
||||||
|
popup/item_5/text = "Hue"
|
||||||
|
popup/item_5/id = 5
|
||||||
|
popup/item_6/text = "Saturation"
|
||||||
|
popup/item_6/id = 6
|
||||||
|
popup/item_7/text = "Value"
|
||||||
|
popup/item_7/id = 7
|
||||||
|
|
||||||
|
[node name="CurveEdit" type="Control" parent="VBoxContainer" index="4"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
script = ExtResource("3_fp0lm")
|
||||||
|
curve = SubResource("Curve_p6hdh")
|
||||||
|
|
||||||
|
[connection signal="item_selected" from="VBoxContainer/ColorOptions/ChannelOptionButton" to="." method="_on_channel_option_button_item_selected"]
|
104
src/UI/Nodes/CurveEditor/CurveControlPoint.gd
Normal file
104
src/UI/Nodes/CurveEditor/CurveControlPoint.gd
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
# Code taken and modified from Material Maker, licensed under MIT
|
||||||
|
# gdlint: ignore=max-line-length
|
||||||
|
# https://github.com/RodZill4/material-maker/blob/master/material_maker/widgets/curve_edit/control_point.gd
|
||||||
|
class_name CurveEditControlPoint
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
signal moved(index: int)
|
||||||
|
signal removed(index: int)
|
||||||
|
|
||||||
|
const OFFSET := Vector2(3, 3)
|
||||||
|
|
||||||
|
var moving := false
|
||||||
|
|
||||||
|
var min_x: float
|
||||||
|
var max_x: float
|
||||||
|
var min_y: float
|
||||||
|
var max_y: float
|
||||||
|
var left_slope: CurveEditTangentPoint
|
||||||
|
var right_slope: CurveEditTangentPoint
|
||||||
|
|
||||||
|
@onready var parent := get_parent() as CurveEdit
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
custom_minimum_size = Vector2(8, 8)
|
||||||
|
left_slope = CurveEditTangentPoint.new()
|
||||||
|
right_slope = CurveEditTangentPoint.new()
|
||||||
|
gui_input.connect(_on_gui_input)
|
||||||
|
left_slope.gui_input.connect(_on_gui_input)
|
||||||
|
right_slope.gui_input.connect(_on_gui_input)
|
||||||
|
add_child(left_slope)
|
||||||
|
add_child(right_slope)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var color := Color.GRAY
|
||||||
|
var current_scene := get_tree().current_scene
|
||||||
|
if current_scene is Control:
|
||||||
|
var current_theme := (current_scene as Control).theme
|
||||||
|
color = current_theme.get_color("font_color", "Label")
|
||||||
|
for c: CurveEditTangentPoint in get_children():
|
||||||
|
if c.visible:
|
||||||
|
draw_line(OFFSET, c.position + OFFSET, color)
|
||||||
|
draw_rect(Rect2(0, 0, 7, 7), color)
|
||||||
|
|
||||||
|
|
||||||
|
func initialize(curve: Curve, index: int) -> void:
|
||||||
|
if not is_instance_valid(parent):
|
||||||
|
await ready
|
||||||
|
position = parent.transform_point(curve.get_point_position(index)) - OFFSET
|
||||||
|
var left_tangent := curve.get_point_left_tangent(index)
|
||||||
|
var right_tangent := curve.get_point_right_tangent(index)
|
||||||
|
if left_tangent != INF:
|
||||||
|
left_slope.position = (
|
||||||
|
left_slope.distance * (parent.size * Vector2(1.0, -left_tangent)).normalized()
|
||||||
|
)
|
||||||
|
if right_tangent != INF:
|
||||||
|
right_slope.position = (
|
||||||
|
right_slope.distance * (parent.size * Vector2(1.0, -right_tangent)).normalized()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func set_control_point_visibility(left: bool, new_visible: bool) -> void:
|
||||||
|
if not is_instance_valid(left_slope):
|
||||||
|
await ready
|
||||||
|
if left:
|
||||||
|
left_slope.visible = new_visible
|
||||||
|
else:
|
||||||
|
right_slope.visible = new_visible
|
||||||
|
|
||||||
|
|
||||||
|
func set_constraint(new_min_x: float, new_max_x: float, new_min_y: float, new_max_y: float) -> void:
|
||||||
|
min_x = new_min_x
|
||||||
|
max_x = new_max_x
|
||||||
|
min_y = new_min_y
|
||||||
|
max_y = new_max_y
|
||||||
|
|
||||||
|
|
||||||
|
func _on_gui_input(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
|
if event.pressed:
|
||||||
|
moving = true
|
||||||
|
else:
|
||||||
|
moving = false
|
||||||
|
parent.update_controls()
|
||||||
|
elif event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
|
||||||
|
removed.emit(get_index())
|
||||||
|
elif moving and event is InputEventMouseMotion:
|
||||||
|
position += event.relative
|
||||||
|
if position.x < min_x:
|
||||||
|
position.x = min_x
|
||||||
|
elif position.x > max_x:
|
||||||
|
position.x = max_x
|
||||||
|
if position.y < min_y:
|
||||||
|
position.y = min_y
|
||||||
|
elif position.y > max_y:
|
||||||
|
position.y = max_y
|
||||||
|
moved.emit(get_index())
|
||||||
|
|
||||||
|
|
||||||
|
func update_tangents() -> void:
|
||||||
|
queue_redraw()
|
||||||
|
moved.emit(get_index())
|
153
src/UI/Nodes/CurveEditor/CurveEdit.gd
Normal file
153
src/UI/Nodes/CurveEditor/CurveEdit.gd
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
# Code taken and modified from Material Maker, licensed under MIT
|
||||||
|
# gdlint: ignore=max-line-length
|
||||||
|
# https://github.com/RodZill4/material-maker/blob/master/material_maker/widgets/curve_edit/curve_view.gd
|
||||||
|
# and
|
||||||
|
# gdlint: ignore=max-line-length
|
||||||
|
# https://github.com/RodZill4/material-maker/blob/master/material_maker/widgets/curve_edit/curve_editor.gd
|
||||||
|
@tool
|
||||||
|
class_name CurveEdit
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
signal value_changed(value: Curve)
|
||||||
|
|
||||||
|
@export var show_axes := true
|
||||||
|
@export var curve: Curve:
|
||||||
|
set(value):
|
||||||
|
curve = value
|
||||||
|
queue_redraw()
|
||||||
|
update_controls()
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if not is_instance_valid(curve):
|
||||||
|
curve = Curve.new()
|
||||||
|
gui_input.connect(_on_gui_input)
|
||||||
|
resized.connect(_on_resize)
|
||||||
|
queue_redraw()
|
||||||
|
update_controls()
|
||||||
|
|
||||||
|
|
||||||
|
func update_controls() -> void:
|
||||||
|
for c in get_children():
|
||||||
|
c.queue_free()
|
||||||
|
for i in curve.point_count:
|
||||||
|
var p := curve.get_point_position(i)
|
||||||
|
var control_point := CurveEditControlPoint.new()
|
||||||
|
add_child(control_point)
|
||||||
|
control_point.initialize(curve, i)
|
||||||
|
control_point.position = transform_point(p) - control_point.OFFSET
|
||||||
|
if i == 0 or i == curve.point_count - 1:
|
||||||
|
control_point.set_constraint(
|
||||||
|
control_point.position.x,
|
||||||
|
control_point.position.x,
|
||||||
|
-control_point.OFFSET.y,
|
||||||
|
size.y - control_point.OFFSET.y
|
||||||
|
)
|
||||||
|
if i == 0:
|
||||||
|
control_point.set_control_point_visibility(true, false)
|
||||||
|
else:
|
||||||
|
control_point.set_control_point_visibility(false, false)
|
||||||
|
else:
|
||||||
|
var min_x := transform_point(curve.get_point_position(i - 1)).x + 1
|
||||||
|
var max_x := transform_point(curve.get_point_position(i + 1)).x - 1
|
||||||
|
control_point.set_constraint(
|
||||||
|
min_x, max_x, -control_point.OFFSET.y, size.y - control_point.OFFSET.y
|
||||||
|
)
|
||||||
|
control_point.moved.connect(_on_control_point_moved)
|
||||||
|
control_point.removed.connect(_on_control_point_removed)
|
||||||
|
value_changed.emit(curve)
|
||||||
|
|
||||||
|
|
||||||
|
static func to_texture(from_curve: Curve, width := 256) -> CurveTexture:
|
||||||
|
var texture := CurveTexture.new()
|
||||||
|
texture.texture_mode = CurveTexture.TEXTURE_MODE_RED
|
||||||
|
texture.curve = from_curve
|
||||||
|
texture.width = width
|
||||||
|
return texture
|
||||||
|
|
||||||
|
|
||||||
|
func transform_point(p: Vector2) -> Vector2:
|
||||||
|
return (Vector2(0.0, 1.0) + Vector2(1.0, -1.0) * p) * size
|
||||||
|
|
||||||
|
|
||||||
|
func reverse_transform_point(p: Vector2) -> Vector2:
|
||||||
|
return Vector2(0.0, 1.0) + Vector2(1.0, -1.0) * p / size
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var bg := Color.DARK_GRAY
|
||||||
|
var fg := Color.GRAY
|
||||||
|
var current_scene := get_tree().current_scene
|
||||||
|
if current_scene is Control:
|
||||||
|
var current_theme := (current_scene as Control).theme
|
||||||
|
var panel_stylebox := current_theme.get_stylebox("panel", "Panel")
|
||||||
|
if panel_stylebox is StyleBoxFlat:
|
||||||
|
bg = panel_stylebox.bg_color
|
||||||
|
fg = current_theme.get_color("font_color", "Label")
|
||||||
|
var axes_color := bg.lerp(fg, 0.25)
|
||||||
|
var curve_color := bg.lerp(fg, 0.75)
|
||||||
|
if show_axes:
|
||||||
|
for i in range(5):
|
||||||
|
var p := transform_point(0.25 * Vector2(i, i))
|
||||||
|
draw_line(Vector2(p.x, 0), Vector2(p.x, size.y - 1), axes_color)
|
||||||
|
draw_line(Vector2(0, p.y), Vector2(size.x - 1, p.y), axes_color)
|
||||||
|
var points := PackedVector2Array()
|
||||||
|
for i in range(curve.point_count - 1):
|
||||||
|
var p1 := curve.get_point_position(i)
|
||||||
|
var p2 := curve.get_point_position(i + 1)
|
||||||
|
var d := (p2.x - p1.x) / 3.0
|
||||||
|
var yac := p1.y + d * curve.get_point_right_tangent(i)
|
||||||
|
var ybc := p2.y - d * curve.get_point_left_tangent(i + 1)
|
||||||
|
var p := transform_point(p1)
|
||||||
|
if points.is_empty():
|
||||||
|
points.push_back(p)
|
||||||
|
var count := maxi(1, transform_point(p2).x - p.x / 5.0)
|
||||||
|
for tt in range(count):
|
||||||
|
var t := (tt + 1.0) / count
|
||||||
|
var omt := 1.0 - t
|
||||||
|
var omt2 := omt * omt
|
||||||
|
var omt3 := omt2 * omt
|
||||||
|
var t2 := t * t
|
||||||
|
var t3 := t2 * t
|
||||||
|
var x := p1.x + (p2.x - p1.x) * t
|
||||||
|
var y := p1.y * omt3 + yac * omt2 * t * 3.0 + ybc * omt * t2 * 3.0 + p2.y * t3
|
||||||
|
p = transform_point(Vector2(x, y))
|
||||||
|
points.push_back(p)
|
||||||
|
draw_polyline(points, curve_color)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_control_point_moved(index: int) -> void:
|
||||||
|
var control_point := get_child(index) as CurveEditControlPoint
|
||||||
|
var new_point := reverse_transform_point(control_point.position + control_point.OFFSET)
|
||||||
|
curve.set_point_offset(index, new_point.x)
|
||||||
|
curve.set_point_value(index, new_point.y)
|
||||||
|
if is_instance_valid(control_point.left_slope):
|
||||||
|
var slope_vector := control_point.left_slope.position / size
|
||||||
|
if slope_vector.x != 0:
|
||||||
|
curve.set_point_left_tangent(index, -slope_vector.y / slope_vector.x)
|
||||||
|
if is_instance_valid(control_point.right_slope):
|
||||||
|
var slope_vector := control_point.right_slope.position / size
|
||||||
|
if slope_vector.x != 0:
|
||||||
|
curve.set_point_right_tangent(index, -slope_vector.y / slope_vector.x)
|
||||||
|
queue_redraw()
|
||||||
|
value_changed.emit(curve)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_control_point_removed(index: int) -> void:
|
||||||
|
if index > 0 and index < curve.point_count:
|
||||||
|
curve.remove_point(index)
|
||||||
|
queue_redraw()
|
||||||
|
update_controls()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_gui_input(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if event.button_index == MOUSE_BUTTON_LEFT and event.double_click:
|
||||||
|
var new_point_position := reverse_transform_point(get_local_mouse_position())
|
||||||
|
curve.add_point(new_point_position, 0.0, 0.0)
|
||||||
|
update_controls()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_resize() -> void:
|
||||||
|
queue_redraw()
|
||||||
|
update_controls()
|
59
src/UI/Nodes/CurveEditor/CurveTangentPoint.gd
Normal file
59
src/UI/Nodes/CurveEditor/CurveTangentPoint.gd
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# Code taken and modified from Material Maker, licensed under MIT
|
||||||
|
# gdlint: ignore=max-line-length
|
||||||
|
# https://github.com/RodZill4/material-maker/blob/master/material_maker/widgets/curve_edit/slope_point.gd
|
||||||
|
class_name CurveEditTangentPoint
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
const OFFSET := -Vector2(0, 0)
|
||||||
|
|
||||||
|
@export var distance: float
|
||||||
|
|
||||||
|
var moving = false
|
||||||
|
@onready var parent := get_parent() as CurveEditControlPoint
|
||||||
|
@onready var grandparent := parent.get_parent() as Control
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
custom_minimum_size = Vector2(8, 8)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var color := Color.GRAY
|
||||||
|
var current_scene := get_tree().current_scene
|
||||||
|
if current_scene is Control:
|
||||||
|
var current_theme := (current_scene as Control).theme
|
||||||
|
color = current_theme.get_color("font_color", "Label")
|
||||||
|
draw_circle(Vector2(3.0, 3.0), 3.0, color)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_ControlPoint_gui_input(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
|
if event.pressed:
|
||||||
|
if event.double_click:
|
||||||
|
var vector: Vector2
|
||||||
|
if get_index() == 0:
|
||||||
|
vector = (
|
||||||
|
parent.position - grandparent.get_child(parent.get_index() - 1).position
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
vector = (
|
||||||
|
grandparent.get_child(parent.get_index() + 1).position - parent.position
|
||||||
|
)
|
||||||
|
vector = distance * vector.normalized()
|
||||||
|
position = vector - OFFSET
|
||||||
|
if event.is_control_or_command_pressed():
|
||||||
|
parent.get_child(1 - get_index()).position = -vector - OFFSET
|
||||||
|
parent.update_tangents()
|
||||||
|
else:
|
||||||
|
moving = true
|
||||||
|
else:
|
||||||
|
moving = false
|
||||||
|
elif moving and event is InputEventMouseMotion:
|
||||||
|
var vector := get_global_mouse_position() - parent.get_global_rect().position + OFFSET
|
||||||
|
vector *= signf(vector.x)
|
||||||
|
vector = distance * vector.normalized()
|
||||||
|
position = vector - OFFSET
|
||||||
|
if event.is_command_or_control_pressed():
|
||||||
|
parent.get_child(1 - get_index()).position = -vector - OFFSET
|
||||||
|
parent.update_tangents()
|
|
@ -33,6 +33,7 @@ var hsv_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/HSVDialog.tscn")
|
||||||
var adjust_brightness_saturation_dialog := Dialog.new(
|
var adjust_brightness_saturation_dialog := Dialog.new(
|
||||||
"res://src/UI/Dialogs/ImageEffects/BrightnessContrastDialog.tscn"
|
"res://src/UI/Dialogs/ImageEffects/BrightnessContrastDialog.tscn"
|
||||||
)
|
)
|
||||||
|
var color_curves_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn")
|
||||||
var gaussian_blur_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/GaussianBlur.tscn")
|
var gaussian_blur_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/GaussianBlur.tscn")
|
||||||
var gradient_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/GradientDialog.tscn")
|
var gradient_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/GradientDialog.tscn")
|
||||||
var gradient_map_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/GradientMapDialog.tscn")
|
var gradient_map_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/GradientMapDialog.tscn")
|
||||||
|
@ -449,6 +450,7 @@ func _setup_effects_menu() -> void:
|
||||||
"Desaturation": "desaturation",
|
"Desaturation": "desaturation",
|
||||||
"Adjust Hue/Saturation/Value": "adjust_hsv",
|
"Adjust Hue/Saturation/Value": "adjust_hsv",
|
||||||
"Adjust Brightness/Contrast": "adjust_brightness_contrast",
|
"Adjust Brightness/Contrast": "adjust_brightness_contrast",
|
||||||
|
"Color Curves": "color_curves",
|
||||||
"Palettize": "palettize",
|
"Palettize": "palettize",
|
||||||
"Pixelize": "pixelize",
|
"Pixelize": "pixelize",
|
||||||
"Posterize": "posterize",
|
"Posterize": "posterize",
|
||||||
|
@ -934,6 +936,8 @@ func effects_menu_id_pressed(id: int) -> void:
|
||||||
hsv_dialog.popup()
|
hsv_dialog.popup()
|
||||||
Global.EffectsMenu.BRIGHTNESS_SATURATION:
|
Global.EffectsMenu.BRIGHTNESS_SATURATION:
|
||||||
adjust_brightness_saturation_dialog.popup()
|
adjust_brightness_saturation_dialog.popup()
|
||||||
|
Global.EffectsMenu.COLOR_CURVES:
|
||||||
|
color_curves_dialog.popup()
|
||||||
Global.EffectsMenu.GAUSSIAN_BLUR:
|
Global.EffectsMenu.GAUSSIAN_BLUR:
|
||||||
gaussian_blur_dialog.popup()
|
gaussian_blur_dialog.popup()
|
||||||
Global.EffectsMenu.GRADIENT:
|
Global.EffectsMenu.GRADIENT:
|
||||||
|
|
Loading…
Reference in a new issue