From 048058bd359732a64bd5b8ad89fd413e584e07e6 Mon Sep 17 00:00:00 2001 From: Emmanouil Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sun, 15 Dec 2024 18:10:55 +0200 Subject: [PATCH] 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. --- project.godot | 4 + src/Autoload/Global.gd | 2 + src/Shaders/Effects/ColorCurves.gdshader | 67 ++++++++ .../Dialogs/ImageEffects/ColorCurvesDialog.gd | 50 ++++++ .../ImageEffects/ColorCurvesDialog.tscn | 60 +++++++ src/UI/Nodes/CurveEditor/CurveControlPoint.gd | 104 ++++++++++++ src/UI/Nodes/CurveEditor/CurveEdit.gd | 153 ++++++++++++++++++ src/UI/Nodes/CurveEditor/CurveTangentPoint.gd | 59 +++++++ src/UI/TopMenuContainer/TopMenuContainer.gd | 4 + 9 files changed, 503 insertions(+) create mode 100644 src/Shaders/Effects/ColorCurves.gdshader create mode 100644 src/UI/Dialogs/ImageEffects/ColorCurvesDialog.gd create mode 100644 src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn create mode 100644 src/UI/Nodes/CurveEditor/CurveControlPoint.gd create mode 100644 src/UI/Nodes/CurveEditor/CurveEdit.gd create mode 100644 src/UI/Nodes/CurveEditor/CurveTangentPoint.gd diff --git a/project.godot b/project.godot index 0d3ae5115..59827b487 100644 --- a/project.godot +++ b/project.godot @@ -685,6 +685,10 @@ adjust_brightness_contrast={ "deadzone": 0.5, "events": [] } +color_curves={ +"deadzone": 0.5, +"events": [] +} gradient={ "deadzone": 0.5, "events": [] diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 420ec8672..1a02862a9 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -64,6 +64,7 @@ enum EffectsMenu { DESATURATION, HSV, BRIGHTNESS_SATURATION, + COLOR_CURVES, PALETTIZE, PIXELIZE, POSTERIZE, @@ -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), diff --git a/src/Shaders/Effects/ColorCurves.gdshader b/src/Shaders/Effects/ColorCurves.gdshader new file mode 100644 index 000000000..bd36d04c6 --- /dev/null +++ b/src/Shaders/Effects/ColorCurves.gdshader @@ -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; +} diff --git a/src/UI/Dialogs/ImageEffects/ColorCurvesDialog.gd b/src/UI/Dialogs/ImageEffects/ColorCurvesDialog.gd new file mode 100644 index 000000000..24ef62d13 --- /dev/null +++ b/src/UI/Dialogs/ImageEffects/ColorCurvesDialog.gd @@ -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] diff --git a/src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn b/src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn new file mode 100644 index 000000000..e14f5c577 --- /dev/null +++ b/src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn @@ -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"] diff --git a/src/UI/Nodes/CurveEditor/CurveControlPoint.gd b/src/UI/Nodes/CurveEditor/CurveControlPoint.gd new file mode 100644 index 000000000..4ba12ee32 --- /dev/null +++ b/src/UI/Nodes/CurveEditor/CurveControlPoint.gd @@ -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()) diff --git a/src/UI/Nodes/CurveEditor/CurveEdit.gd b/src/UI/Nodes/CurveEditor/CurveEdit.gd new file mode 100644 index 000000000..21f533166 --- /dev/null +++ b/src/UI/Nodes/CurveEditor/CurveEdit.gd @@ -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() diff --git a/src/UI/Nodes/CurveEditor/CurveTangentPoint.gd b/src/UI/Nodes/CurveEditor/CurveTangentPoint.gd new file mode 100644 index 000000000..16e7b44ef --- /dev/null +++ b/src/UI/Nodes/CurveEditor/CurveTangentPoint.gd @@ -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() diff --git a/src/UI/TopMenuContainer/TopMenuContainer.gd b/src/UI/TopMenuContainer/TopMenuContainer.gd index f0867343b..df41698e8 100644 --- a/src/UI/TopMenuContainer/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer/TopMenuContainer.gd @@ -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", @@ -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: