mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-02-20 12:33:14 +00:00
Add a Gradient Map image effect, implements the second half of #595
The gradient edit code was taken and modified from Material Maker, MIT license.
This commit is contained in:
parent
872ac62722
commit
c9f0301c79
9 changed files with 364 additions and 4 deletions
|
@ -44,6 +44,11 @@ _global_script_classes=[ {
|
|||
"language": "GDScript",
|
||||
"path": "res://src/Classes/Frame.gd"
|
||||
}, {
|
||||
"base": "TextureRect",
|
||||
"class": "GradientEditNode",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/UI/Nodes/GradientEdit.gd"
|
||||
}, {
|
||||
"base": "Line2D",
|
||||
"class": "Guide",
|
||||
"language": "GDScript",
|
||||
|
@ -127,6 +132,7 @@ _global_script_class_icons={
|
|||
"Cel": "",
|
||||
"Drawer": "",
|
||||
"Frame": "",
|
||||
"GradientEditNode": "",
|
||||
"Guide": "",
|
||||
"ImageEffect": "",
|
||||
"Layer": "",
|
||||
|
@ -830,6 +836,10 @@ about_pixelorama={
|
|||
"deadzone": 0.5,
|
||||
"events": [ ]
|
||||
}
|
||||
gradient_map={
|
||||
"deadzone": 0.5,
|
||||
"events": [ ]
|
||||
}
|
||||
|
||||
[locale]
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ enum ImageMenu {
|
|||
DROP_SHADOW,
|
||||
HSV,
|
||||
GRADIENT,
|
||||
GRADIENT_MAP,
|
||||
SHADER
|
||||
}
|
||||
enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT }
|
||||
|
@ -281,6 +282,8 @@ func _initialize_keychain() -> void:
|
|||
Keychain.MenuInputAction.new("", "Image menu", true, "ImageMenu", ImageMenu.HSV),
|
||||
"gradient":
|
||||
Keychain.MenuInputAction.new("", "Image menu", true, "ImageMenu", ImageMenu.GRADIENT),
|
||||
"gradient_map":
|
||||
Keychain.MenuInputAction.new("", "Image menu", true, "ImageMenu", ImageMenu.GRADIENT_MAP),
|
||||
"mirror_view":
|
||||
Keychain.MenuInputAction.new("", "View menu", true, "ViewMenu", ViewMenu.MIRROR_VIEW),
|
||||
"show_grid":
|
||||
|
|
16
src/Shaders/GradientMap.gdshader
Normal file
16
src/Shaders/GradientMap.gdshader
Normal file
|
@ -0,0 +1,16 @@
|
|||
shader_type canvas_item;
|
||||
|
||||
uniform sampler2D map; // GradientTexture
|
||||
uniform sampler2D selection;
|
||||
|
||||
void fragment() {
|
||||
vec4 original_color = texture(TEXTURE, UV);
|
||||
vec4 selection_color = texture(selection, UV);
|
||||
vec4 output = original_color;
|
||||
float value = (0.2126 * original_color.r) + (0.7152 * original_color.g) + (0.0722 * original_color.b);
|
||||
vec4 gradient_color = texture(map, vec2(value, 0.0));
|
||||
output.rgb = gradient_color.rgb;
|
||||
output.a *= gradient_color.a;
|
||||
|
||||
COLOR = mix(original_color, output, selection_color.a);
|
||||
}
|
49
src/UI/Dialogs/ImageEffects/GradientMapDialog.gd
Normal file
49
src/UI/Dialogs/ImageEffects/GradientMapDialog.gd
Normal file
|
@ -0,0 +1,49 @@
|
|||
extends ImageEffect
|
||||
|
||||
var shader: Shader = preload("res://src/Shaders/GradientMap.gdshader")
|
||||
var confirmed := false
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
var sm := ShaderMaterial.new()
|
||||
sm.shader = shader
|
||||
preview.set_material(sm)
|
||||
|
||||
|
||||
func set_nodes() -> void:
|
||||
preview = $VBoxContainer/AspectRatioContainer/Preview
|
||||
selection_checkbox = $VBoxContainer/OptionsContainer/SelectionCheckBox
|
||||
affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton
|
||||
|
||||
|
||||
func _about_to_show() -> void:
|
||||
confirmed = false
|
||||
._about_to_show()
|
||||
|
||||
|
||||
func _confirmed() -> void:
|
||||
confirmed = true
|
||||
._confirmed()
|
||||
|
||||
|
||||
func commit_action(cel: Image, project: Project = Global.current_project) -> void:
|
||||
var selection_tex := ImageTexture.new()
|
||||
if selection_checkbox.pressed and project.has_selection:
|
||||
var selection: Image = project.bitmap_to_image(project.selection_bitmap)
|
||||
selection_tex.create_from_image(selection, 0)
|
||||
|
||||
var params := {"selection": selection_tex, "map": $VBoxContainer/GradientEdit.texture}
|
||||
|
||||
if !confirmed:
|
||||
preview.material.shader = shader
|
||||
for param in params:
|
||||
preview.material.set_shader_param(param, params[param])
|
||||
else:
|
||||
var gen := ShaderImageEffect.new()
|
||||
gen.generate_image(cel, shader, params, project.size)
|
||||
yield(gen, "done")
|
||||
|
||||
|
||||
func _on_GradientEdit_updated(_gradient, _cc) -> void:
|
||||
update_preview()
|
77
src/UI/Dialogs/ImageEffects/GradientMapDialog.tscn
Normal file
77
src/UI/Dialogs/ImageEffects/GradientMapDialog.tscn
Normal file
|
@ -0,0 +1,77 @@
|
|||
[gd_scene load_steps=4 format=2]
|
||||
|
||||
[ext_resource path="res://src/UI/TransparentChecker.tscn" type="PackedScene" id=1]
|
||||
[ext_resource path="res://src/UI/Nodes/GradientEdit.tscn" type="PackedScene" id=2]
|
||||
[ext_resource path="res://src/UI/Dialogs/ImageEffects/GradientMapDialog.gd" type="Script" id=3]
|
||||
|
||||
[node name="GradientMapDialog" type="ConfirmationDialog"]
|
||||
margin_right = 200.0
|
||||
margin_bottom = 70.0
|
||||
window_title = "Gradient Map"
|
||||
resizable = true
|
||||
script = ExtResource( 3 )
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
margin_left = 8.0
|
||||
margin_top = 8.0
|
||||
margin_right = -8.0
|
||||
margin_bottom = -36.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="AspectRatioContainer" type="AspectRatioContainer" parent="VBoxContainer"]
|
||||
margin_right = 278.0
|
||||
margin_bottom = 200.0
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Preview" type="TextureRect" parent="VBoxContainer/AspectRatioContainer"]
|
||||
margin_left = 39.0
|
||||
margin_right = 239.0
|
||||
margin_bottom = 200.0
|
||||
rect_min_size = Vector2( 200, 200 )
|
||||
size_flags_horizontal = 5
|
||||
size_flags_vertical = 3
|
||||
expand = true
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="TransparentChecker" parent="VBoxContainer/AspectRatioContainer/Preview" instance=ExtResource( 1 )]
|
||||
show_behind_parent = true
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
margin_right = 0.0
|
||||
margin_bottom = 0.0
|
||||
|
||||
[node name="GradientEdit" parent="VBoxContainer" instance=ExtResource( 2 )]
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 204.0
|
||||
margin_right = 278.0
|
||||
margin_bottom = 234.0
|
||||
rect_min_size = Vector2( 0, 30 )
|
||||
|
||||
[node name="OptionsContainer" type="GridContainer" parent="VBoxContainer"]
|
||||
margin_top = 238.0
|
||||
margin_right = 278.0
|
||||
margin_bottom = 262.0
|
||||
columns = 2
|
||||
|
||||
[node name="SelectionCheckBox" type="CheckBox" parent="VBoxContainer/OptionsContainer" groups=["gradient_common"]]
|
||||
margin_right = 160.0
|
||||
margin_bottom = 24.0
|
||||
mouse_default_cursor_shape = 2
|
||||
pressed = true
|
||||
text = "Only affect selection"
|
||||
|
||||
[node name="AffectOptionButton" type="OptionButton" parent="VBoxContainer/OptionsContainer" groups=["gradient_common"]]
|
||||
margin_left = 164.0
|
||||
margin_right = 278.0
|
||||
margin_bottom = 24.0
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Selected cels"
|
||||
items = [ "Selected cels", null, false, 0, null, "Current frame", null, false, 1, null, "All frames", null, false, 2, null, "All projects", null, false, 3, null ]
|
||||
selected = 0
|
||||
|
||||
[connection signal="updated" from="VBoxContainer/GradientEdit" to="." method="_on_GradientEdit_updated"]
|
|
@ -1,9 +1,10 @@
|
|||
[gd_scene load_steps=12 format=2]
|
||||
[gd_scene load_steps=13 format=2]
|
||||
|
||||
[ext_resource path="res://src/UI/Dialogs/ImageEffects/FlipImageDialog.tscn" type="PackedScene" id=1]
|
||||
[ext_resource path="res://src/UI/Dialogs/ImageEffects/InvertColorsDialog.tscn" type="PackedScene" id=2]
|
||||
[ext_resource path="res://src/UI/Dialogs/ImageEffects/DesaturateDialog.tscn" type="PackedScene" id=3]
|
||||
[ext_resource path="res://src/UI/Dialogs/ImageEffects/DropShadowDialog.tscn" type="PackedScene" id=4]
|
||||
[ext_resource path="res://src/UI/Dialogs/ImageEffects/GradientMapDialog.tscn" type="PackedScene" id=5]
|
||||
[ext_resource path="res://src/UI/Dialogs/ImageEffects/ResizeCanvas.tscn" type="PackedScene" id=8]
|
||||
[ext_resource path="res://src/UI/Dialogs/ImageEffects/RotateImage.tscn" type="PackedScene" id=9]
|
||||
[ext_resource path="res://src/UI/Dialogs/ImageEffects/ShaderEffect.tscn" type="PackedScene" id=10]
|
||||
|
@ -14,9 +15,6 @@
|
|||
|
||||
[node name="ImageEffects" type="Control"]
|
||||
mouse_filter = 2
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="ScaleImage" parent="." instance=ExtResource( 14 )]
|
||||
margin_bottom = 127.0
|
||||
|
@ -44,4 +42,6 @@ margin_bottom = 106.0
|
|||
[node name="GradientDialog" parent="." instance=ExtResource( 12 )]
|
||||
margin_bottom = 214.0
|
||||
|
||||
[node name="GradientMapDialog" parent="." instance=ExtResource( 5 )]
|
||||
|
||||
[node name="ShaderEffect" parent="." instance=ExtResource( 10 )]
|
||||
|
|
162
src/UI/Nodes/GradientEdit.gd
Normal file
162
src/UI/Nodes/GradientEdit.gd
Normal file
|
@ -0,0 +1,162 @@
|
|||
# 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/gradient_editor/gradient_editor.gd
|
||||
class_name GradientEditNode
|
||||
extends TextureRect
|
||||
|
||||
signal updated(gradient, cc)
|
||||
|
||||
var continuous_change := true
|
||||
var active_cursor: GradientCursor # Showing a color picker popup to change a cursor's color
|
||||
|
||||
onready var x_offset: float = rect_size.x - GradientCursor.WIDTH
|
||||
onready var gradient: Gradient = texture.gradient
|
||||
|
||||
|
||||
class GradientCursor:
|
||||
extends Control
|
||||
|
||||
const WIDTH := 10
|
||||
var color: Color
|
||||
var sliding := false
|
||||
onready var label: Label = get_parent().get_node("Value")
|
||||
|
||||
func _ready() -> void:
|
||||
rect_position = Vector2(0, 15)
|
||||
rect_size = Vector2(WIDTH, 15)
|
||||
|
||||
func _draw() -> void:
|
||||
# warning-ignore:integer_division
|
||||
var polygon := PoolVector2Array(
|
||||
[
|
||||
Vector2(0, 5),
|
||||
Vector2(WIDTH / 2, 0),
|
||||
Vector2(WIDTH, 5),
|
||||
Vector2(WIDTH, 15),
|
||||
Vector2(0, 15),
|
||||
Vector2(0, 5)
|
||||
]
|
||||
)
|
||||
var c := color
|
||||
c.a = 1.0
|
||||
draw_colored_polygon(polygon, c)
|
||||
draw_polyline(polygon, Color(0.0, 0.0, 0.0) if color.v > 0.5 else Color(1.0, 1.0, 1.0))
|
||||
|
||||
func _gui_input(ev: InputEvent) -> void:
|
||||
if ev is InputEventMouseButton:
|
||||
if ev.button_index == BUTTON_LEFT:
|
||||
if ev.doubleclick:
|
||||
get_parent().select_color(self, ev.global_position)
|
||||
elif ev.pressed:
|
||||
get_parent().continuous_change = false
|
||||
sliding = true
|
||||
label.visible = true
|
||||
label.text = "%.03f" % get_cursor_position()
|
||||
else:
|
||||
sliding = false
|
||||
label.visible = false
|
||||
elif ev.button_index == BUTTON_RIGHT and get_parent().get_sorted_cursors().size() > 2:
|
||||
var parent = get_parent()
|
||||
parent.remove_child(self)
|
||||
parent.continuous_change = false
|
||||
parent.update_from_value()
|
||||
queue_free()
|
||||
elif ev is InputEventMouseMotion and (ev.button_mask & BUTTON_MASK_LEFT) != 0 and sliding:
|
||||
rect_position.x += get_local_mouse_position().x
|
||||
if ev.control:
|
||||
rect_position.x = (
|
||||
round(get_cursor_position() * 20.0)
|
||||
* 0.05
|
||||
* (get_parent().rect_size.x - WIDTH)
|
||||
)
|
||||
rect_position.x = min(max(0, rect_position.x), get_parent().rect_size.x - rect_size.x)
|
||||
get_parent().update_from_value()
|
||||
label.text = "%.03f" % get_cursor_position()
|
||||
|
||||
func get_cursor_position() -> float:
|
||||
return rect_position.x / (get_parent().rect_size.x - WIDTH)
|
||||
|
||||
func set_color(c: Color) -> void:
|
||||
color = c
|
||||
get_parent().update_from_value()
|
||||
update()
|
||||
|
||||
static func sort(a, b) -> bool:
|
||||
return a.get_position() < b.get_position()
|
||||
|
||||
func can_drop_data(_position, data) -> bool:
|
||||
return typeof(data) == TYPE_COLOR
|
||||
|
||||
func drop_data(_position, data) -> void:
|
||||
set_color(data)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
create_cursors()
|
||||
|
||||
|
||||
func create_cursors() -> void:
|
||||
for c in get_children():
|
||||
if c is GradientCursor:
|
||||
remove_child(c)
|
||||
c.queue_free()
|
||||
for i in gradient.get_point_count():
|
||||
var p: float = gradient.get_offset(i)
|
||||
add_cursor(p * x_offset, gradient.get_color(i))
|
||||
|
||||
|
||||
func _gui_input(ev: InputEvent) -> void:
|
||||
if ev.is_action_pressed("left_mouse"):
|
||||
var p = clamp(ev.position.x, 0, x_offset)
|
||||
add_cursor(p, get_gradient_color(p))
|
||||
continuous_change = false
|
||||
update_from_value()
|
||||
|
||||
|
||||
func update_from_value() -> void:
|
||||
gradient.offsets = []
|
||||
for c in get_children():
|
||||
if c is GradientCursor:
|
||||
var point: float = c.rect_position.x / x_offset
|
||||
gradient.add_point(point, c.color)
|
||||
emit_signal("updated", gradient, continuous_change)
|
||||
continuous_change = true
|
||||
|
||||
|
||||
func add_cursor(x: float, color: Color) -> void:
|
||||
var cursor := GradientCursor.new()
|
||||
add_child(cursor)
|
||||
cursor.rect_position.x = x
|
||||
cursor.color = color
|
||||
|
||||
|
||||
func select_color(cursor: GradientCursor, position: Vector2) -> void:
|
||||
active_cursor = cursor
|
||||
var color_picker = $Popup.get_node("ColorPicker")
|
||||
color_picker.color = cursor.color
|
||||
$Popup.rect_position = position
|
||||
$Popup.popup()
|
||||
|
||||
|
||||
func get_sorted_cursors() -> Array:
|
||||
var array := []
|
||||
for c in get_children():
|
||||
if c is GradientCursor:
|
||||
array.append(c)
|
||||
array.sort_custom(GradientCursor, "sort")
|
||||
return array
|
||||
|
||||
|
||||
func get_gradient_color(x: float) -> Color:
|
||||
return gradient.interpolate(x / x_offset)
|
||||
|
||||
|
||||
func _on_ColorPicker_color_changed(color: Color) -> void:
|
||||
active_cursor.set_color(color)
|
||||
|
||||
|
||||
func _on_GradientEdit_resized() -> void:
|
||||
if not gradient:
|
||||
return
|
||||
x_offset = rect_size.x - GradientCursor.WIDTH
|
||||
create_cursors()
|
38
src/UI/Nodes/GradientEdit.tscn
Normal file
38
src/UI/Nodes/GradientEdit.tscn
Normal file
|
@ -0,0 +1,38 @@
|
|||
[gd_scene load_steps=4 format=2]
|
||||
|
||||
[ext_resource path="res://src/UI/Nodes/GradientEdit.gd" type="Script" id=1]
|
||||
|
||||
[sub_resource type="Gradient" id=1]
|
||||
|
||||
[sub_resource type="GradientTexture" id=2]
|
||||
gradient = SubResource( 1 )
|
||||
|
||||
[node name="GradientEdit" type="TextureRect"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
texture = SubResource( 2 )
|
||||
expand = true
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="Popup" type="PopupPanel" parent="."]
|
||||
margin_right = 316.0
|
||||
margin_bottom = 470.0
|
||||
|
||||
[node name="ColorPicker" type="ColorPicker" parent="Popup"]
|
||||
margin_left = 4.0
|
||||
margin_top = 4.0
|
||||
margin_right = 312.0
|
||||
margin_bottom = 466.0
|
||||
|
||||
[node name="Value" type="Label" parent="."]
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -20.0
|
||||
margin_top = -7.0
|
||||
margin_right = 20.0
|
||||
margin_bottom = 7.0
|
||||
|
||||
[connection signal="resized" from="." to="." method="_on_GradientEdit_resized"]
|
||||
[connection signal="color_changed" from="Popup/ColorPicker" to="." method="_on_ColorPicker_color_changed"]
|
|
@ -252,6 +252,7 @@ func _setup_image_menu() -> void:
|
|||
"Drop Shadow",
|
||||
"Adjust Hue/Saturation/Value",
|
||||
"Gradient",
|
||||
"Gradient Map",
|
||||
# "Shader"
|
||||
]
|
||||
var image_menu: PopupMenu = image_menu_button.get_popup()
|
||||
|
@ -620,6 +621,10 @@ func image_menu_id_pressed(id: int) -> void:
|
|||
Global.control.get_node("Dialogs/ImageEffects/GradientDialog").popup_centered()
|
||||
Global.dialog_open(true)
|
||||
|
||||
Global.ImageMenu.GRADIENT_MAP:
|
||||
Global.control.get_node("Dialogs/ImageEffects/GradientMapDialog").popup_centered()
|
||||
Global.dialog_open(true)
|
||||
|
||||
# Global.ImageMenu.SHADER:
|
||||
# Global.control.get_node("Dialogs/ImageEffects/ShaderEffect").popup_centered()
|
||||
# Global.dialog_open(true)
|
||||
|
|
Loading…
Add table
Reference in a new issue