mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-31 07:29:49 +00:00
Compare commits
9 commits
c9451580d8
...
1380b4c68a
Author | SHA1 | Date | |
---|---|---|---|
1380b4c68a | |||
8ceeba76c0 | |||
93eab6929b | |||
1d9b9fda1e | |||
482dbecd13 | |||
cf8dacf0f5 | |||
048058bd35 | |||
605bff7324 | |||
5a8c79339d |
|
@ -211,6 +211,10 @@ msgstr ""
|
|||
msgid "Initial angle:"
|
||||
msgstr ""
|
||||
|
||||
#. Found under the Select menu, It's a checkbox that, if enabled, wraps around brush strokes if some part of them goes out of selection bounds.
|
||||
msgid "Wrap Strokes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clear"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1157,6 +1161,27 @@ msgstr ""
|
|||
msgid "Tint effect factor:"
|
||||
msgstr ""
|
||||
|
||||
#. An image effect that adjusts the colors of the image by using curves.
|
||||
msgid "Color Curves"
|
||||
msgstr ""
|
||||
|
||||
#. Refers to a color channel, such as the red, green, blue or alpha channels.
|
||||
msgid "Channel:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Red"
|
||||
msgstr ""
|
||||
|
||||
msgid "Green"
|
||||
msgstr ""
|
||||
|
||||
#. Refers to the value (as in HSV) of the colors of an image.
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
msgid "Presets"
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -685,6 +685,10 @@ adjust_brightness_contrast={
|
|||
"deadzone": 0.5,
|
||||
"events": []
|
||||
}
|
||||
color_curves={
|
||||
"deadzone": 0.5,
|
||||
"events": []
|
||||
}
|
||||
gradient={
|
||||
"deadzone": 0.5,
|
||||
"events": []
|
||||
|
|
|
@ -64,6 +64,7 @@ enum EffectsMenu {
|
|||
DESATURATION,
|
||||
HSV,
|
||||
BRIGHTNESS_SATURATION,
|
||||
COLOR_CURVES,
|
||||
PALETTIZE,
|
||||
PIXELIZE,
|
||||
POSTERIZE,
|
||||
|
@ -73,7 +74,7 @@ enum EffectsMenu {
|
|||
SHADER
|
||||
}
|
||||
## Enumeration of items present in the Select Menu.
|
||||
enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT, TILE_MODE, MODIFY }
|
||||
enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT, WRAP_STROKES, MODIFY }
|
||||
## Enumeration of items present in the Help Menu.
|
||||
enum HelpMenu {
|
||||
VIEW_SPLASH_SCREEN,
|
||||
|
@ -809,6 +810,7 @@ func _initialize_keychain() -> void:
|
|||
&"drop_shadow": Keychain.InputAction.new("", "Effects menu", true),
|
||||
&"adjust_hsv": 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),
|
||||
&"gradient": Keychain.InputAction.new("", "Effects menu", true),
|
||||
&"gradient_map": Keychain.InputAction.new("", "Effects menu", true),
|
||||
|
|
|
@ -25,7 +25,13 @@ func get_content() -> ImageExtended:
|
|||
|
||||
|
||||
func set_content(content, texture: ImageTexture = null) -> void:
|
||||
image = content
|
||||
var proper_content: ImageExtended
|
||||
if content is not ImageExtended:
|
||||
proper_content = ImageExtended.new()
|
||||
proper_content.copy_from_custom(content, image.is_indexed)
|
||||
else:
|
||||
proper_content = content
|
||||
image = proper_content
|
||||
if is_instance_valid(texture) and is_instance_valid(texture.get_image()):
|
||||
image_texture = texture
|
||||
if image_texture.get_image().get_size() != image.get_size():
|
||||
|
|
|
@ -252,6 +252,17 @@ static func create_ui_for_shader_uniforms(
|
|||
func(_gradient, _cc): value_changed.call(gradient_edit.texture, u_name)
|
||||
)
|
||||
hbox.add_child(gradient_edit)
|
||||
elif u_name.begins_with("curve_"):
|
||||
var curve_edit := CurveEdit.new()
|
||||
curve_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
if params.has(u_name) and params[u_name] is CurveTexture:
|
||||
curve_edit.curve = params[u_name].curve
|
||||
else:
|
||||
curve_edit.set_default_curve()
|
||||
curve_edit.value_changed.connect(
|
||||
func(curve: Curve): value_changed.call(CurveEdit.to_texture(curve), u_name)
|
||||
)
|
||||
hbox.add_child(curve_edit)
|
||||
else: ## Simple texture
|
||||
var file_dialog := FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
|
|
|
@ -64,6 +64,7 @@ func draw_palette() -> void:
|
|||
var grid_index := i + grid_size.x * j
|
||||
var index := convert_grid_index_to_palette_index(grid_index)
|
||||
var swatch := swatches[grid_index]
|
||||
swatch.color_index = index
|
||||
swatch.show_left_highlight = Palettes.left_selected_color == index
|
||||
swatch.show_right_highlight = Palettes.right_selected_color == index
|
||||
var color = current_palette.get_color(index)
|
||||
|
|
|
@ -8,6 +8,7 @@ signal dropped(source_index: int, new_index: int)
|
|||
const DEFAULT_COLOR := Color(0.0, 0.0, 0.0, 0.0)
|
||||
|
||||
var index := -1
|
||||
var color_index := -1
|
||||
var show_left_highlight := false
|
||||
var show_right_highlight := false
|
||||
var empty := true:
|
||||
|
@ -48,6 +49,23 @@ func _draw() -> void:
|
|||
draw_rect(
|
||||
Rect2(margin - Vector2.ONE, size - margin * 2 + Vector2(2, 2)), Color.WHITE, false, 1
|
||||
)
|
||||
if Global.show_pixel_indices:
|
||||
var font := Themes.get_font()
|
||||
var str_pos := Vector2(size.x / 2, size.y - 2)
|
||||
var text_color := Global.control.theme.get_color(&"font_color", &"Label")
|
||||
draw_string_outline(
|
||||
font,
|
||||
str_pos,
|
||||
str(color_index),
|
||||
HORIZONTAL_ALIGNMENT_RIGHT,
|
||||
-1,
|
||||
size.x / 2,
|
||||
1,
|
||||
text_color.inverted()
|
||||
)
|
||||
draw_string(
|
||||
font, str_pos, str(color_index), HORIZONTAL_ALIGNMENT_RIGHT, -1, size.x / 2, text_color
|
||||
)
|
||||
|
||||
|
||||
## Enables drawing of highlights which indicate selected swatches
|
||||
|
|
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]
|
61
src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn
Normal file
61
src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn
Normal file
|
@ -0,0 +1,61 @@
|
|||
[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_3yyhs"]
|
||||
|
||||
[sub_resource type="Curve" id="Curve_gvi51"]
|
||||
_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, 481)
|
||||
script = ExtResource("2_xkivc")
|
||||
|
||||
[node name="VBoxContainer" parent="." index="3"]
|
||||
offset_bottom = 432.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="VBoxContainer" parent="VBoxContainer" index="4"]
|
||||
custom_minimum_size = Vector2(32, 150)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("3_3yyhs")
|
||||
curve = SubResource("Curve_gvi51")
|
||||
|
||||
[connection signal="item_selected" from="VBoxContainer/ColorOptions/ChannelOptionButton" to="." method="_on_channel_option_button_item_selected"]
|
103
src/UI/Nodes/CurveEditor/CurveControlPoint.gd
Normal file
103
src/UI/Nodes/CurveEditor/CurveControlPoint.gd
Normal file
|
@ -0,0 +1,103 @@
|
|||
# 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 Control
|
||||
@onready var grandparent := parent.get_parent() as CurveEdit
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
gui_input.connect(_on_gui_input)
|
||||
custom_minimum_size = Vector2(8, 8)
|
||||
left_slope = CurveEditTangentPoint.new()
|
||||
right_slope = CurveEditTangentPoint.new()
|
||||
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 = grandparent.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
|
||||
grandparent.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())
|
273
src/UI/Nodes/CurveEditor/CurveEdit.gd
Normal file
273
src/UI/Nodes/CurveEditor/CurveEdit.gd
Normal file
|
@ -0,0 +1,273 @@
|
|||
# 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 VBoxContainer
|
||||
|
||||
signal value_changed(value: Curve)
|
||||
|
||||
@export var show_axes := true
|
||||
@export var curve: Curve:
|
||||
set(value):
|
||||
curve = value
|
||||
queue_redraw()
|
||||
update_controls()
|
||||
|
||||
## Array of dictionaries of key [String] and value [Array] of type [CurveEdit.CurvePoint].
|
||||
var presets: Array[Dictionary] = [
|
||||
{"Linear": [CurvePoint.new(0.0, 0.0, 0.0, 1.0), CurvePoint.new(1.0, 1.0, 1.0, 0.0)]},
|
||||
{
|
||||
"Ease out":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 4.0),
|
||||
CurvePoint.new(0.292893, 0.707107, 1.0, 1.0),
|
||||
CurvePoint.new(1.0, 1.0, 0.0, 0.0)
|
||||
]
|
||||
},
|
||||
{
|
||||
"Ease in out":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 0.0),
|
||||
CurvePoint.new(0.5, 0.5, 3.0, 3.0),
|
||||
CurvePoint.new(1.0, 1.0, 0.0, 0.0)
|
||||
]
|
||||
},
|
||||
{
|
||||
"Ease in":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 0.0),
|
||||
CurvePoint.new(0.707107, 0.292893, 1.0, 1.0),
|
||||
CurvePoint.new(1.0, 1.0, 4.0, 0.0)
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sawtooth":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 2.0),
|
||||
CurvePoint.new(0.5, 1.0, 2.0, -2.0),
|
||||
CurvePoint.new(1.0, 0.0, -2.0, 0.0)
|
||||
]
|
||||
},
|
||||
{
|
||||
"Bounce":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 5.0),
|
||||
CurvePoint.new(0.15, 0.65, 2.45201, 2.45201),
|
||||
CurvePoint.new(0.5, 1.0, 0.0, 0.0),
|
||||
CurvePoint.new(0.85, 0.65, -2.45201, -2.45201),
|
||||
CurvePoint.new(1.0, 0.0, -5.0, 0.0)
|
||||
]
|
||||
},
|
||||
{
|
||||
"Bevel":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 2.38507),
|
||||
CurvePoint.new(0.292893, 0.707107, 2.34362, 0.428147),
|
||||
CurvePoint.new(1.0, 1.0, 0.410866, 0.0)
|
||||
]
|
||||
}
|
||||
]
|
||||
var curve_editor := Control.new()
|
||||
var hbox := HBoxContainer.new()
|
||||
|
||||
|
||||
class CurvePoint:
|
||||
var pos: Vector2
|
||||
var left_tangent: float
|
||||
var right_tangent: float
|
||||
|
||||
func _init(x: float, y: float, _left_tangent := 0.0, _right_tangent := 0.0) -> void:
|
||||
pos = Vector2(x, y)
|
||||
left_tangent = _left_tangent
|
||||
right_tangent = _right_tangent
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not is_instance_valid(curve):
|
||||
curve = Curve.new()
|
||||
if custom_minimum_size.is_zero_approx():
|
||||
custom_minimum_size = Vector2(32, 150)
|
||||
curve_editor.gui_input.connect(_on_gui_input)
|
||||
resized.connect(_on_resize)
|
||||
curve_editor.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
add_child(curve_editor)
|
||||
add_child(hbox)
|
||||
var presets_button := MenuButton.new()
|
||||
presets_button.text = "Presets"
|
||||
presets_button.flat = false
|
||||
presets_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
presets_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
presets_button.get_popup().id_pressed.connect(_on_presets_item_selected)
|
||||
for preset in presets:
|
||||
presets_button.get_popup().add_item(preset.keys()[0])
|
||||
var invert_button := Button.new()
|
||||
invert_button.text = "Invert"
|
||||
invert_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
invert_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
invert_button.pressed.connect(_on_invert_button_pressed)
|
||||
hbox.add_child(presets_button)
|
||||
hbox.add_child(invert_button)
|
||||
_on_resize.call_deferred()
|
||||
|
||||
|
||||
func update_controls() -> void:
|
||||
for c in curve_editor.get_children():
|
||||
if c is CurveEditControlPoint:
|
||||
c.queue_free()
|
||||
for i in curve.point_count:
|
||||
var p := curve.get_point_position(i)
|
||||
var control_point := CurveEditControlPoint.new()
|
||||
curve_editor.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,
|
||||
available_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, available_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 set_default_curve() -> void:
|
||||
if not is_instance_valid(curve):
|
||||
curve = Curve.new()
|
||||
_on_presets_item_selected(0)
|
||||
|
||||
|
||||
func available_size() -> Vector2:
|
||||
if curve_editor.size.is_zero_approx():
|
||||
return Vector2.ONE
|
||||
return curve_editor.size
|
||||
|
||||
|
||||
func transform_point(p: Vector2) -> Vector2:
|
||||
return (Vector2(0.0, 1.0) + Vector2(1.0, -1.0) * p) * available_size()
|
||||
|
||||
|
||||
func reverse_transform_point(p: Vector2) -> Vector2:
|
||||
return Vector2(0.0, 1.0) + Vector2(1.0, -1.0) * p / available_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, available_size().y - 1), axes_color)
|
||||
draw_line(Vector2(0, p.y), Vector2(available_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 := curve_editor.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 / available_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 / available_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()
|
||||
|
||||
|
||||
func _on_presets_item_selected(index: int) -> void:
|
||||
curve.clear_points()
|
||||
var preset_points: Array = presets[index].values()[0]
|
||||
for point: CurvePoint in preset_points:
|
||||
curve.add_point(point.pos, point.left_tangent, point.right_tangent)
|
||||
curve = curve # Call setter
|
||||
|
||||
|
||||
func _on_invert_button_pressed() -> void:
|
||||
var copy_curve := curve.duplicate() as Curve
|
||||
curve.clear_points()
|
||||
for i in copy_curve.point_count:
|
||||
var point := copy_curve.get_point_position(i)
|
||||
point.y = 1.0 - point.y
|
||||
var left_tangent := -copy_curve.get_point_left_tangent(i)
|
||||
var right_tangent := -copy_curve.get_point_right_tangent(i)
|
||||
curve.add_point(point, left_tangent, right_tangent)
|
||||
curve = curve # Call setter
|
62
src/UI/Nodes/CurveEditor/CurveTangentPoint.gd
Normal file
62
src/UI/Nodes/CurveEditor/CurveTangentPoint.gd
Normal file
|
@ -0,0 +1,62 @@
|
|||
# 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 := 30
|
||||
|
||||
var moving = false
|
||||
@onready var parent := get_parent() as CurveEditControlPoint
|
||||
@onready var grandparent := parent.get_parent() as Control
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
gui_input.connect(_on_gui_input)
|
||||
custom_minimum_size = Vector2(8, 8)
|
||||
if get_index() == 0:
|
||||
distance = -distance
|
||||
|
||||
|
||||
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_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()
|
|
@ -20,6 +20,7 @@ var effects: Array[LayerEffect] = [
|
|||
"Adjust Brightness/Contrast",
|
||||
preload("res://src/Shaders/Effects/BrightnessContrast.gdshader")
|
||||
),
|
||||
LayerEffect.new("Color Curves", preload("res://src/Shaders/Effects/ColorCurves.gdshader")),
|
||||
LayerEffect.new("Palettize", preload("res://src/Shaders/Effects/Palettize.gdshader")),
|
||||
LayerEffect.new("Pixelize", preload("res://src/Shaders/Effects/Pixelize.gdshader")),
|
||||
LayerEffect.new("Posterize", preload("res://src/Shaders/Effects/Posterize.gdshader")),
|
||||
|
|
|
@ -33,6 +33,7 @@ var hsv_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/HSVDialog.tscn")
|
|||
var adjust_brightness_saturation_dialog := Dialog.new(
|
||||
"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 gradient_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/GradientDialog.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",
|
||||
"Adjust Hue/Saturation/Value": "adjust_hsv",
|
||||
"Adjust Brightness/Contrast": "adjust_brightness_contrast",
|
||||
"Color Curves": "color_curves",
|
||||
"Palettize": "palettize",
|
||||
"Pixelize": "pixelize",
|
||||
"Posterize": "posterize",
|
||||
|
@ -470,12 +472,12 @@ func _setup_select_menu() -> void:
|
|||
"All": "select_all",
|
||||
"Clear": "clear_selection",
|
||||
"Invert": "invert_selection",
|
||||
"Tile Mode": "",
|
||||
"Wrap Strokes": "",
|
||||
"Modify": ""
|
||||
}
|
||||
for i in select_menu_items.size():
|
||||
var item: String = select_menu_items.keys()[i]
|
||||
if item == "Tile Mode":
|
||||
if item == "Wrap Strokes":
|
||||
select_menu.add_check_item(item, i)
|
||||
elif item == "Modify":
|
||||
_setup_selection_modify_submenu(item)
|
||||
|
@ -934,6 +936,8 @@ func effects_menu_id_pressed(id: int) -> void:
|
|||
hsv_dialog.popup()
|
||||
Global.EffectsMenu.BRIGHTNESS_SATURATION:
|
||||
adjust_brightness_saturation_dialog.popup()
|
||||
Global.EffectsMenu.COLOR_CURVES:
|
||||
color_curves_dialog.popup()
|
||||
Global.EffectsMenu.GAUSSIAN_BLUR:
|
||||
gaussian_blur_dialog.popup()
|
||||
Global.EffectsMenu.GRADIENT:
|
||||
|
@ -960,7 +964,7 @@ func select_menu_id_pressed(id: int) -> void:
|
|||
Global.canvas.selection.clear_selection(true)
|
||||
Global.SelectMenu.INVERT:
|
||||
Global.canvas.selection.invert()
|
||||
Global.SelectMenu.TILE_MODE:
|
||||
Global.SelectMenu.WRAP_STROKES:
|
||||
var state = select_menu.is_item_checked(id)
|
||||
Global.canvas.selection.flag_tilemode = !state
|
||||
select_menu.set_item_checked(id, !state)
|
||||
|
|
Loading…
Reference in a new issue