1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-30 23:19:49 +00:00

Compare commits

...

9 commits

Author SHA1 Message Date
Variable 1380b4c68a
Merge 5a8c79339d into 8ceeba76c0 2024-12-15 21:37:24 +02:00
Emmanouil Papadeas 8ceeba76c0 Update Translations.pot 2024-12-15 21:14:11 +02:00
Emmanouil Papadeas 93eab6929b For PixelCel's set_content not working when passing an Image instead of ImageExtended 2024-12-15 21:05:10 +02:00
Emmanouil Papadeas 1d9b9fda1e Add color curves layer effect 2024-12-15 21:04:32 +02:00
Emmanouil Papadeas 482dbecd13 Add presets to the curve edit widget 2024-12-15 20:46:07 +02:00
Emmanouil Papadeas cf8dacf0f5 Fix curve edit tangent points
They are working properly now
2024-12-15 18:37:21 +02:00
Emmanouil Papadeas 048058bd35 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.
2024-12-15 18:10:55 +02:00
Emmanouil Papadeas 605bff7324 Show the index of the palette swatches when color indices are visible on the canvas 2024-12-15 00:13:59 +02:00
Variable 5a8c79339d renamed the selection tile mode to wrap strokes, as it is a more accurate description. 2024-12-14 12:31:03 +05:00
15 changed files with 693 additions and 5 deletions

View file

@ -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 ""

View file

@ -685,6 +685,10 @@ adjust_brightness_contrast={
"deadzone": 0.5,
"events": []
}
color_curves={
"deadzone": 0.5,
"events": []
}
gradient={
"deadzone": 0.5,
"events": []

View file

@ -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),

View file

@ -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():

View file

@ -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

View 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)

View file

@ -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

View 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;
}

View 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]

View 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"]

View 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())

View 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

View 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()

View file

@ -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")),

View file

@ -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)