diff --git a/Translations/Translations.pot b/Translations/Translations.pot index fcca899e0..11133defc 100644 --- a/Translations/Translations.pot +++ b/Translations/Translations.pot @@ -855,16 +855,16 @@ msgstr "" msgid "Steps:" msgstr "" -msgid "Top to Bottom" +#. An image effect. For more details about what it does, you can refer to GIMP's documentation https://docs.gimp.org/2.8/en/gimp-tool-posterize.html +msgid "Posterize" msgstr "" -msgid "Bottom to Top" +#. An option for the posterize image effect. For more details about what it does, you can refer to GIMP's documentation https://docs.gimp.org/2.8/en/gimp-tool-posterize.html +msgid "Posterize levels:" msgstr "" -msgid "Left to Right" -msgstr "" - -msgid "Right to Left" +#. An option for the posterize image effect. +msgid "Dither intensity:" msgstr "" msgid "View Splash Screen" diff --git a/project.godot b/project.godot index c363c4427..0abe4a7a7 100644 --- a/project.godot +++ b/project.godot @@ -988,6 +988,14 @@ gradient={ "deadzone": 0.5, "events": [ ] } +gradient_map={ +"deadzone": 0.5, +"events": [ ] +} +posterize={ +"deadzone": 0.5, +"events": [ ] +} view_splash_screen={ "deadzone": 0.5, "events": [ ] @@ -1008,10 +1016,6 @@ about_pixelorama={ "deadzone": 0.5, "events": [ ] } -gradient_map={ -"deadzone": 0.5, -"events": [ ] -} left_paint_selection_tool={ "deadzone": 0.5, "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":73,"physical_scancode":0,"unicode":0,"echo":false,"script":null) diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index f91739143..70a1fdf0f 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -37,6 +37,7 @@ enum ImageMenu { HSV, GRADIENT, GRADIENT_MAP, + POSTERIZE, SHADER } enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT } @@ -310,6 +311,8 @@ func _initialize_keychain() -> void: Keychain.MenuInputAction.new("", "Image menu", true, "ImageMenu", ImageMenu.GRADIENT), "gradient_map": Keychain.MenuInputAction.new("", "Image menu", true, "ImageMenu", ImageMenu.GRADIENT_MAP), + "posterize": + Keychain.MenuInputAction.new("", "Image menu", true, "ImageMenu", ImageMenu.POSTERIZE), "mirror_view": Keychain.MenuInputAction.new("", "View menu", true, "ViewMenu", ViewMenu.MIRROR_VIEW), "show_grid": diff --git a/src/Shaders/Posterize.gdshader b/src/Shaders/Posterize.gdshader new file mode 100644 index 000000000..9226d4fbe --- /dev/null +++ b/src/Shaders/Posterize.gdshader @@ -0,0 +1,27 @@ +// https://godotshaders.com/shader/color-reduction-and-dither/ +shader_type canvas_item; + +uniform sampler2D selection; +uniform float colors : hint_range(1.0, 255.0) = 2.0; +uniform float dither : hint_range(0.0, 0.5) = 0.0; + +void fragment() +{ + vec4 color = texture(TEXTURE, UV); + vec4 selection_color = texture(selection, UV); + + float a = floor(mod(UV.x / TEXTURE_PIXEL_SIZE.x, 2.0)); + float b = floor(mod(UV.y / TEXTURE_PIXEL_SIZE.y, 2.0)); + float c = mod(a + b, 2.0); + vec4 col; + col.r = (round(color.r * colors + dither) / colors) * c; + col.g = (round(color.g * colors + dither) / colors) * c; + col.b = (round(color.b * colors + dither) / colors) * c; + c = 1.0 - c; + col.r += (round(color.r * colors - dither) / colors) * c; + col.g += (round(color.g * colors - dither) / colors) * c; + col.b += (round(color.b * colors - dither) / colors) * c; + col.a = color.a; + vec4 output = mix(color.rgba, col, selection_color.a); + COLOR = output; +} diff --git a/src/UI/Dialogs/ImageEffects/ImageEffects.tscn b/src/UI/Dialogs/ImageEffects/ImageEffects.tscn index 45da9b43a..a9715a971 100644 --- a/src/UI/Dialogs/ImageEffects/ImageEffects.tscn +++ b/src/UI/Dialogs/ImageEffects/ImageEffects.tscn @@ -1,10 +1,11 @@ -[gd_scene load_steps=13 format=2] +[gd_scene load_steps=14 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/Posterize.tscn" type="PackedScene" id=6] [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] @@ -44,4 +45,6 @@ margin_bottom = 214.0 [node name="GradientMapDialog" parent="." instance=ExtResource( 5 )] +[node name="Posterize" parent="." instance=ExtResource( 6 )] + [node name="ShaderEffect" parent="." instance=ExtResource( 10 )] diff --git a/src/UI/Dialogs/ImageEffects/Posterize.gd b/src/UI/Dialogs/ImageEffects/Posterize.gd new file mode 100644 index 000000000..373dc87c1 --- /dev/null +++ b/src/UI/Dialogs/ImageEffects/Posterize.gd @@ -0,0 +1,43 @@ +extends ImageEffect + +var shader: Shader = preload("res://src/Shaders/Posterize.gdshader") +var levels := 2.0 +var dither := 0.0 + + +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 commit_action(cel: Image, project: Project = Global.current_project) -> void: + var selection_tex := ImageTexture.new() + if selection_checkbox.pressed and project.has_selection: + selection_tex.create_from_image(project.selection_map, 0) + + var params := {"colors": levels, "dither": dither, "selection": selection_tex} + + if !confirmed: + 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_LevelsSlider_value_changed(value: float) -> void: + levels = value - 1.0 + update_preview() + + +func _on_DitherSlider_value_changed(value: float) -> void: + dither = value + update_preview() diff --git a/src/UI/Dialogs/ImageEffects/Posterize.tscn b/src/UI/Dialogs/ImageEffects/Posterize.tscn new file mode 100644 index 000000000..bcf935b88 --- /dev/null +++ b/src/UI/Dialogs/ImageEffects/Posterize.tscn @@ -0,0 +1,83 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://src/UI/Dialogs/ImageEffects/ImageEffectParent.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/UI/Nodes/ValueSlider.gd" type="Script" id=2] +[ext_resource path="res://src/UI/Dialogs/ImageEffects/Posterize.gd" type="Script" id=3] + +[node name="Posterize" instance=ExtResource( 1 )] +window_title = "Posterize" +script = ExtResource( 3 ) + +[node name="VBoxContainer" parent="." index="3"] +margin_bottom = 292.0 + +[node name="AspectRatioContainer" parent="VBoxContainer" index="0"] +margin_right = 278.0 + +[node name="Preview" parent="VBoxContainer/AspectRatioContainer" index="0"] +margin_left = 39.0 +margin_right = 239.0 + +[node name="LevelsSlider" type="TextureProgress" parent="VBoxContainer" index="1"] +margin_top = 204.0 +margin_right = 278.0 +margin_bottom = 228.0 +rect_min_size = Vector2( 0, 24 ) +mouse_default_cursor_shape = 2 +theme_type_variation = "ValueSlider" +min_value = 2.0 +max_value = 256.0 +step = 0.01 +value = 3.0 +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +script = ExtResource( 2 ) +prefix = "Posterize levels:" +snap_by_default = true + +[node name="DitherSlider" type="TextureProgress" parent="VBoxContainer" index="2"] +margin_top = 232.0 +margin_right = 278.0 +margin_bottom = 256.0 +rect_min_size = Vector2( 0, 24 ) +mouse_default_cursor_shape = 2 +theme_type_variation = "ValueSlider" +max_value = 0.5 +step = 0.01 +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +script = ExtResource( 2 ) +prefix = "Dither intensity:" +snap_step = 0.1 + +[node name="OptionsContainer" parent="VBoxContainer" index="3"] +margin_top = 260.0 +margin_right = 278.0 +margin_bottom = 284.0 + +[node name="AffectOptionButton" parent="VBoxContainer/OptionsContainer" index="1"] +margin_right = 278.0 +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 ] + +[node name="AnimationOptions" parent="VBoxContainer" index="4"] +visible = false +margin_right = 278.0 + +[node name="PanelContainer" parent="VBoxContainer/AnimationOptions" index="1"] +margin_right = 157.0 + +[node name="AnimateMenu" parent="VBoxContainer/AnimationOptions/PanelContainer" index="0"] +margin_right = 88.0 + +[node name="InitalButton" parent="VBoxContainer/AnimationOptions" index="2"] +margin_left = 161.0 +margin_right = 278.0 + +[connection signal="value_changed" from="VBoxContainer/LevelsSlider" to="." method="_on_LevelsSlider_value_changed"] +[connection signal="value_changed" from="VBoxContainer/DitherSlider" to="." method="_on_DitherSlider_value_changed"] diff --git a/src/UI/TopMenuContainer/TopMenuContainer.gd b/src/UI/TopMenuContainer/TopMenuContainer.gd index 90a5d16f9..6147882b9 100644 --- a/src/UI/TopMenuContainer/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer/TopMenuContainer.gd @@ -285,6 +285,7 @@ func _setup_image_menu() -> void: "Adjust Hue/Saturation/Value", "Gradient", "Gradient Map", + "Posterize", # "Shader" ] var image_menu: PopupMenu = image_menu_button.get_popup() @@ -680,6 +681,9 @@ func image_menu_id_pressed(id: int) -> void: Global.ImageMenu.GRADIENT_MAP: _popup_dialog(Global.control.get_node("Dialogs/ImageEffects/GradientMapDialog")) + Global.ImageMenu.POSTERIZE: + _popup_dialog(Global.control.get_node("Dialogs/ImageEffects/Posterize")) + # Global.ImageMenu.SHADER: # _popup_dialog(Global.control.get_node("Dialogs/ImageEffects/ShaderEffect"))