From 2da5b1e94464fec7289e91e759e02c3a8c517bd0 Mon Sep 17 00:00:00 2001 From: Emmanouil Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 22 Apr 2022 21:11:02 +0300 Subject: [PATCH] Outline generation with shaders This results in both better and faster results. Unfortunately, the shader does not work in the Web version, so we have to rely on the old method for that platform. --- src/Shaders/OutlineInline.gdshader | 49 +++++++++++++ src/UI/Dialogs/ImageEffects/OutlineDialog.gd | 69 ++++++++++++++++--- .../Dialogs/ImageEffects/OutlineDialog.tscn | 60 +++++++++------- 3 files changed, 142 insertions(+), 36 deletions(-) create mode 100644 src/Shaders/OutlineInline.gdshader diff --git a/src/Shaders/OutlineInline.gdshader b/src/Shaders/OutlineInline.gdshader new file mode 100644 index 000000000..aebab3606 --- /dev/null +++ b/src/Shaders/OutlineInline.gdshader @@ -0,0 +1,49 @@ +// Based on https://godotshaders.com/shader/2d-outline-inline/ +shader_type canvas_item; +render_mode unshaded; + +uniform vec4 color : hint_color = vec4(1.0); +uniform float width : hint_range(0, 10) = 1.0; +uniform int pattern : hint_range(0, 2) = 0; // diamond, circle, square +uniform bool inside = false; +uniform sampler2D selection; + + +bool has_contrary_neighbour(vec2 uv, vec2 texture_pixel_size, sampler2D tex) { + for (float i = -ceil(width); i <= ceil(width); i++) { + float x = abs(i) > width ? width * sign(i) : i; + float offset; + + if (pattern == 0) { + offset = width - abs(x); + } else if (pattern == 1) { + offset = floor(sqrt(pow(width + 0.5, 2) - x * x)); + } else if (pattern == 2) { + offset = width; + } + + for (float j = -ceil(offset); j <= ceil(offset); j++) { + float y = abs(j) > offset ? offset * sign(j) : j; + vec2 xy = uv + texture_pixel_size * vec2(x, y); + + if ((xy != clamp(xy, vec2(0.0), vec2(1.0)) || texture(tex, xy).a == 0.0) == inside) { + return true; + } + } + } + + return false; +} + +void fragment() { + vec4 original_color = texture(TEXTURE, UV); + vec4 selection_color = texture(selection, UV); + vec4 output = texture(TEXTURE, UV); + + if ((output.a > 0.0) == inside && has_contrary_neighbour(UV, TEXTURE_PIXEL_SIZE, TEXTURE)) { + output.rgb = inside ? mix(output.rgb, color.rgb, color.a) : color.rgb; + output.a += (1.0 - output.a) * color.a; + } + + COLOR = mix(original_color, output, selection_color.a); +} diff --git a/src/UI/Dialogs/ImageEffects/OutlineDialog.gd b/src/UI/Dialogs/ImageEffects/OutlineDialog.gd index 08a003f7c..ba811080f 100644 --- a/src/UI/Dialogs/ImageEffects/OutlineDialog.gd +++ b/src/UI/Dialogs/ImageEffects/OutlineDialog.gd @@ -2,27 +2,76 @@ extends ImageEffect var color := Color.red var thickness := 1 -var diagonal := false +var pattern := 0 var inside_image := false +var confirmed := false +var shader: Shader onready var outline_color = $VBoxContainer/OptionsContainer/OutlineColor func _ready() -> void: + if OS.get_name() == "HTML5" and OS.get_current_video_driver() == OS.VIDEO_DRIVER_GLES2: + $VBoxContainer/OptionsContainer/PatternOptionButton.disabled = true + else: + shader = load("res://src/Shaders/OutlineInline.gdshader") outline_color.get_picker().presets_visible = false color = outline_color.color +func _about_to_show() -> void: + confirmed = false + if shader: + var sm := ShaderMaterial.new() + sm.shader = shader + preview.set_material(sm) + ._about_to_show() + + +func _confirmed() -> void: + confirmed = true + ._confirmed() + + func set_nodes() -> void: preview = $VBoxContainer/AspectRatioContainer/Preview selection_checkbox = $VBoxContainer/OptionsContainer/SelectionCheckBox affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel: Image, _project: Project = Global.current_project) -> void: - DrawingAlgos.generate_outline( - _cel, selection_checkbox.pressed, _project, color, thickness, diagonal, inside_image - ) +func commit_action(cel: Image, project: Project = Global.current_project) -> void: + if !shader: # Web version + DrawingAlgos.generate_outline( + cel, selection_checkbox.pressed, project, color, thickness, false, inside_image + ) + return + + 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) + + if !confirmed: + preview.material.set_shader_param("color", color) + preview.material.set_shader_param("width", thickness) + preview.material.set_shader_param("pattern", pattern) + preview.material.set_shader_param("inside", inside_image) + preview.material.set_shader_param("selection", selection_tex) + preview.material.set_shader_param("affect_selection", selection_checkbox.pressed) + preview.material.set_shader_param("has_selection", project.has_selection) + else: + var params := { + "color": color, + "width": thickness, + "pattern": pattern, + "inside": inside_image, + "selection": selection_tex, + "affect_selection": selection_checkbox.pressed, + "has_selection": project.has_selection + } + var gen := ShaderImageEffect.new() + gen.generate_image(cel, shader, params, project.size) + yield(gen, "done") func _on_ThickValue_value_changed(value: int) -> void: @@ -35,11 +84,11 @@ func _on_OutlineColor_color_changed(_color: Color) -> void: update_preview() -func _on_DiagonalCheckBox_toggled(button_pressed: bool) -> void: - diagonal = button_pressed - update_preview() - - func _on_InsideImageCheckBox_toggled(button_pressed: bool) -> void: inside_image = button_pressed update_preview() + + +func _on_PatternOptionButton_item_selected(index: int) -> void: + pattern = index + update_preview() diff --git a/src/UI/Dialogs/ImageEffects/OutlineDialog.tscn b/src/UI/Dialogs/ImageEffects/OutlineDialog.tscn index a5efec6fe..51c2b75b6 100644 --- a/src/UI/Dialogs/ImageEffects/OutlineDialog.tscn +++ b/src/UI/Dialogs/ImageEffects/OutlineDialog.tscn @@ -25,13 +25,13 @@ __meta__ = { [node name="AspectRatioContainer" type="AspectRatioContainer" parent="VBoxContainer"] margin_right = 527.0 -margin_bottom = 359.0 +margin_bottom = 339.0 size_flags_vertical = 3 [node name="Preview" type="TextureRect" parent="VBoxContainer/AspectRatioContainer"] -margin_left = 84.0 -margin_right = 443.0 -margin_bottom = 359.0 +margin_left = 94.0 +margin_right = 433.0 +margin_bottom = 339.0 rect_min_size = Vector2( 200, 200 ) expand = true stretch_mode = 5 @@ -44,7 +44,7 @@ margin_right = 0.0 margin_bottom = 0.0 [node name="OptionsContainer" type="GridContainer" parent="VBoxContainer"] -margin_top = 363.0 +margin_top = 343.0 margin_right = 527.0 margin_bottom = 467.0 custom_constants/vseparation = 4 @@ -56,12 +56,12 @@ __meta__ = { [node name="ThickLabel" type="Label" parent="VBoxContainer/OptionsContainer"] margin_top = 5.0 -margin_right = 160.0 +margin_right = 148.0 margin_bottom = 19.0 text = "Thickness:" [node name="ThickValue" type="SpinBox" parent="VBoxContainer/OptionsContainer"] -margin_left = 164.0 +margin_left = 152.0 margin_right = 312.0 margin_bottom = 24.0 mouse_default_cursor_shape = 2 @@ -72,46 +72,54 @@ suffix = "px" [node name="OutlineColorLabel" type="Label" parent="VBoxContainer/OptionsContainer"] margin_top = 31.0 -margin_right = 160.0 +margin_right = 148.0 margin_bottom = 45.0 text = "Fill with color:" [node name="OutlineColor" type="ColorPickerButton" parent="VBoxContainer/OptionsContainer"] -margin_left = 164.0 +margin_left = 152.0 margin_top = 28.0 margin_right = 312.0 margin_bottom = 48.0 rect_min_size = Vector2( 64, 20 ) color = Color( 1, 0, 0, 1 ) -[node name="DiagonalCheckBox" type="CheckBox" parent="VBoxContainer/OptionsContainer"] -margin_top = 52.0 -margin_right = 160.0 -margin_bottom = 76.0 -mouse_default_cursor_shape = 2 -text = "Diagonal" +[node name="PatternLabel" type="Label" parent="VBoxContainer/OptionsContainer"] +margin_top = 55.0 +margin_right = 148.0 +margin_bottom = 69.0 +text = "Pattern:" -[node name="InsideImageCheckBox" type="CheckBox" parent="VBoxContainer/OptionsContainer"] -margin_left = 164.0 +[node name="PatternOptionButton" type="OptionButton" parent="VBoxContainer/OptionsContainer"] +margin_left = 152.0 margin_top = 52.0 margin_right = 312.0 -margin_bottom = 76.0 +margin_bottom = 72.0 +mouse_default_cursor_shape = 2 +text = "Diamond" +items = [ "Diamond", null, false, 0, null, "Circle", null, false, 1, null, "Square", null, false, 2, null ] +selected = 0 + +[node name="InsideImageCheckBox" type="CheckBox" parent="VBoxContainer/OptionsContainer"] +margin_top = 76.0 +margin_right = 148.0 +margin_bottom = 100.0 mouse_default_cursor_shape = 2 text = "Place inside image" [node name="SelectionCheckBox" type="CheckBox" parent="VBoxContainer/OptionsContainer"] -margin_top = 80.0 -margin_right = 160.0 -margin_bottom = 104.0 +margin_left = 152.0 +margin_top = 76.0 +margin_right = 312.0 +margin_bottom = 100.0 mouse_default_cursor_shape = 2 pressed = true text = "Only affect selection" [node name="AffectOptionButton" type="OptionButton" parent="VBoxContainer/OptionsContainer"] -margin_left = 164.0 -margin_top = 80.0 -margin_right = 312.0 -margin_bottom = 104.0 +margin_top = 104.0 +margin_right = 148.0 +margin_bottom = 124.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 ] @@ -119,5 +127,5 @@ selected = 0 [connection signal="value_changed" from="VBoxContainer/OptionsContainer/ThickValue" to="." method="_on_ThickValue_value_changed"] [connection signal="color_changed" from="VBoxContainer/OptionsContainer/OutlineColor" to="." method="_on_OutlineColor_color_changed"] -[connection signal="toggled" from="VBoxContainer/OptionsContainer/DiagonalCheckBox" to="." method="_on_DiagonalCheckBox_toggled"] +[connection signal="item_selected" from="VBoxContainer/OptionsContainer/PatternOptionButton" to="." method="_on_PatternOptionButton_item_selected"] [connection signal="toggled" from="VBoxContainer/OptionsContainer/InsideImageCheckBox" to="." method="_on_InsideImageCheckBox_toggled"]