Skip to content

Commit

Permalink
Add a Gradient Map image effect, implements the second half of #595
Browse files Browse the repository at this point in the history
The gradient edit code was taken and modified from Material Maker, MIT license.
  • Loading branch information
OverloadedOrama committed Jun 11, 2022
1 parent 872ac62 commit c9f0301
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 4 deletions.
10 changes: 10 additions & 0 deletions project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -127,6 +132,7 @@ _global_script_class_icons={
"Cel": "",
"Drawer": "",
"Frame": "",
"GradientEditNode": "",
"Guide": "",
"ImageEffect": "",
"Layer": "",
Expand Down Expand Up @@ -830,6 +836,10 @@ about_pixelorama={
"deadzone": 0.5,
"events": [ ]
}
gradient_map={
"deadzone": 0.5,
"events": [ ]
}

[locale]

Expand Down
3 changes: 3 additions & 0 deletions src/Autoload/Global.gd
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ enum ImageMenu {
DROP_SHADOW,
HSV,
GRADIENT,
GRADIENT_MAP,
SHADER
}
enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT }
Expand Down Expand Up @@ -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":
Expand Down
16 changes: 16 additions & 0 deletions src/Shaders/GradientMap.gdshader
Original file line number Diff line number Diff line change
@@ -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 changes: 49 additions & 0 deletions src/UI/Dialogs/ImageEffects/GradientMapDialog.gd
Original file line number Diff line number Diff line change
@@ -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 changes: 77 additions & 0 deletions src/UI/Dialogs/ImageEffects/GradientMapDialog.tscn
Original file line number Diff line number Diff line change
@@ -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"]
8 changes: 4 additions & 4 deletions src/UI/Dialogs/ImageEffects/ImageEffects.tscn
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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 changes: 162 additions & 0 deletions src/UI/Nodes/GradientEdit.gd
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit c9f0301

Please sign in to comment.