1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-03-15 15:55:18 +00:00

Compare commits

...

5 commits

Author SHA1 Message Date
Emmanouil Papadeas
64b2b7b9af Make 3D rotation gizmos lines a bit thinner 2024-04-10 02:24:21 +03:00
Emmanouil Papadeas
97a1de6db2 Fix bug where images with width or height 1 are unable to be exported/affected by image effects
For some reason, ShaderImageEffect does not like images with width/height of 1.
2024-04-10 02:15:11 +03:00
Emmanouil Papadeas
018f95158e If there's already a 3D object with the same name in the 3DShapeEdit selected object OptionButton, add a number next to it 2024-04-10 02:09:18 +03:00
Emmanouil Papadeas
a4f06d8cbe Fix 3D layer buttons not having the same offset as the other layer type buttons 2024-04-10 01:25:57 +03:00
Emmanouil Papadeas
1c9c8bf4e3 Add Palettize and Pixelize effects
Pixelize makes the image pixelated, and Palettize maps the color of the input to the nearest color in the selected palette. Useful for limiting color in pixel art and for artistic effects.
2024-04-10 01:20:28 +03:00
18 changed files with 275 additions and 20 deletions

View file

@ -978,6 +978,14 @@ msgstr ""
msgid "Steps:"
msgstr ""
#. An image effect. It maps the color of the input to the nearest color in the selected palette. Useful for limiting color in pixel art and for artistic effects.
msgid "Palettize"
msgstr ""
#. An image effect. It makes the input image pixelated.
msgid "Pixelize"
msgstr ""
#. 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 ""

View file

@ -845,6 +845,14 @@ project_properties={
"deadzone": 0.5,
"events": []
}
palettize={
"deadzone": 0.5,
"events": []
}
pixelize={
"deadzone": 0.5,
"events": []
}
[input_devices]

View file

@ -59,6 +59,8 @@ enum EffectsMenu {
INVERT_COLORS,
DESATURATION,
HSV,
PALETTIZE,
PIXELIZE,
POSTERIZE,
GRADIENT,
GRADIENT_MAP,
@ -739,6 +741,8 @@ func _initialize_keychain() -> void:
"adjust_hsv": Keychain.InputAction.new("", "Effects menu", true),
"gradient": Keychain.InputAction.new("", "Effects menu", true),
"gradient_map": Keychain.InputAction.new("", "Effects menu", true),
&"palettize": Keychain.InputAction.new("", "Effects menu", true),
&"pixelize": Keychain.InputAction.new("", "Effects menu", true),
"posterize": Keychain.InputAction.new("", "Effects menu", true),
"mirror_view": Keychain.InputAction.new("", "View menu", true),
"show_grid": Keychain.InputAction.new("", "View menu", true),
@ -1144,14 +1148,16 @@ func create_ui_for_shader_uniforms(
hbox.add_child(label)
hbox.add_child(slider)
parent_node.add_child(hbox)
elif u_type == "vec2":
elif u_type == "vec2" or u_type == "ivec2" or u_type == "uvec2":
var label := Label.new()
label.text = humanized_u_name
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var vector2 := _vec2str_to_vector2(u_value)
var slider := VALUE_SLIDER_V2_TSCN.instantiate() as ValueSliderV2
slider.show_ratio = true
slider.allow_greater = true
slider.allow_lesser = true
if u_type != "uvec2":
slider.allow_lesser = true
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
slider.value = vector2
if params.has(u_name):
@ -1186,6 +1192,17 @@ func create_ui_for_shader_uniforms(
elif u_type == "sampler2D":
if u_name == "selection":
continue
if u_name == "palette_texture":
var palette := Palettes.current_palette
var palette_texture := ImageTexture.create_from_image(palette.convert_to_image())
value_changed.call(palette_texture, u_name)
Palettes.palette_selected.connect(
func(_name): _shader_change_palette(value_changed, u_name)
)
palette.data_changed.connect(
func(): _shader_update_palette_texture(palette, value_changed, u_name)
)
continue
var label := Label.new()
label.text = humanized_u_name
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
@ -1241,6 +1258,8 @@ func create_ui_for_shader_uniforms(
func _vec2str_to_vector2(vec2: String) -> Vector2:
vec2 = vec2.replace("uvec2", "vec2")
vec2 = vec2.replace("ivec2", "vec2")
vec2 = vec2.replace("vec2(", "")
vec2 = vec2.replace(")", "")
var vec_values := vec2.split(",")
@ -1272,3 +1291,18 @@ func _vec4str_to_color(vec4: String) -> Color:
alpha = float(rgba_values[3])
var color := Color(red, green, blue, alpha)
return color
func _shader_change_palette(value_changed: Callable, parameter_name: String) -> void:
var palette := Palettes.current_palette
_shader_update_palette_texture(palette, value_changed, parameter_name)
#if not palette.data_changed.is_connected(_shader_update_palette_texture):
palette.data_changed.connect(
func(): _shader_update_palette_texture(palette, value_changed, parameter_name)
)
func _shader_update_palette_texture(
palette: Palette, value_changed: Callable, parameter_name: String
) -> void:
value_changed.call(ImageTexture.create_from_image(palette.convert_to_image()), parameter_name)

View file

@ -7,6 +7,16 @@ signal done
func generate_image(img: Image, shader: Shader, params: Dictionary, size: Vector2i) -> void:
# duplicate shader before modifying code to avoid affecting original resource
var resized_width := false
var resized_height := false
if size.x == 1:
size.x = 2
img.crop(2, img.get_height())
resized_width = true
if size.y == 1:
size.y = 2
img.crop(img.get_width(), 2)
resized_height = true
shader = shader.duplicate()
shader.code = shader.code.replace("unshaded", "unshaded, blend_premul_alpha")
var vp := RenderingServer.viewport_create()
@ -46,4 +56,8 @@ func generate_image(img: Image, shader: Shader, params: Dictionary, size: Vector
RenderingServer.free_rid(texture)
viewport_texture.convert(Image.FORMAT_RGBA8)
img.copy_from(viewport_texture)
if resized_width:
img.crop(img.get_width() - 1, img.get_height())
if resized_height:
img.crop(img.get_width(), img.get_height() - 1)
done.emit()

View file

@ -1,6 +1,8 @@
class_name Palette
extends RefCounted
signal data_changed
const DEFAULT_WIDTH := 8
const DEFAULT_HEIGHT := 8
@ -172,6 +174,7 @@ func add_color(new_color: Color, start_index := 0) -> void:
if not colors.has(i):
colors[i] = PaletteColor.new(new_color, i)
break
data_changed.emit()
## Returns color at index or null if no color exists
@ -186,11 +189,13 @@ func get_color(index: int):
func set_color(index: int, new_color: Color) -> void:
if colors.has(index):
colors[index].color = new_color
data_changed.emit()
## Removes a color at the specified index
func remove_color(index: int) -> void:
colors.erase(index)
data_changed.emit()
## Inserts a color to the specified index
@ -200,12 +205,13 @@ func insert_color(index: int, new_color: Color) -> void:
# If insert happens on non empty swatch recursively move the original color
# and every other color to its right one swatch to right
if colors[index] != null:
move_right(index)
_move_right(index)
colors[index] = c
data_changed.emit()
## Recursive function that moves every color to right until one of them is moved to empty swatch
func move_right(index: int) -> void:
func _move_right(index: int) -> void:
# Moving colors to right would overflow the size of the palette
# so increase its height automatically
if index + 1 == colors_max:
@ -214,7 +220,7 @@ func move_right(index: int) -> void:
# If swatch to right to this color is not empty move that color right too
if colors[index + 1] != null:
move_right(index + 1)
_move_right(index + 1)
colors[index + 1] = colors[index]
@ -237,6 +243,7 @@ func swap_colors(from_index: int, to_index: int) -> void:
colors[to_index].index = to_index
colors[from_index] = to_color
colors[from_index].index = from_index
data_changed.emit()
## Copies color
@ -245,6 +252,7 @@ func copy_colors(from_index: int, to_index: int) -> void:
if colors[from_index] != null:
colors[to_index] = colors[from_index].duplicate()
colors[to_index].index = to_index
data_changed.emit()
func reverse_colors() -> void:
@ -254,6 +262,7 @@ func reverse_colors() -> void:
for i in reversed_colors.size():
reversed_colors[i].index = i
colors[i] = reversed_colors[i]
data_changed.emit()
func sort(option: Palettes.SortOptions) -> void:
@ -279,6 +288,7 @@ func sort(option: Palettes.SortOptions) -> void:
for i in sorted_colors.size():
sorted_colors[i].index = i
colors[i] = sorted_colors[i]
data_changed.emit()
## True if all swatches are occupied

View file

@ -0,0 +1,33 @@
// Maps the color of the input to the nearest color in the selected palette.
// Similar to Krita's Palettize filter
shader_type canvas_item;
uniform sampler2D palette_texture : filter_nearest;
uniform sampler2D selection;
vec4 swap_color(vec4 color) {
if (color.a <= 0.01) {
return color;
}
int color_index = 0;
int n_of_colors = textureSize(palette_texture, 0).x;
float smaller_distance = distance(color, texture(palette_texture, vec2(0.0)));
for (int i = 0; i <= n_of_colors; i++) {
vec2 uv = vec2(float(i) / float(n_of_colors), 0.0);
vec4 palette_color = texture(palette_texture, uv);
float dist = distance(color, palette_color);
if (dist < smaller_distance) {
smaller_distance = dist;
color_index = i;
}
}
return texture(palette_texture, vec2(float(color_index) / float(n_of_colors), 0.0));
}
void fragment() {
vec4 original_color = texture(TEXTURE, UV);
vec4 selection_color = texture(selection, UV);
vec4 color = swap_color(original_color);
COLOR = mix(original_color.rgba, color, selection_color.a);
}

View file

@ -0,0 +1,25 @@
/*
Shader from Godot Shaders - the free shader library.
https://godotshaders.com/shader/pixelate-2/
This shader is under MIT license
*/
shader_type canvas_item;
uniform uvec2 pixel_size = uvec2(4);
uniform sampler2D selection;
void fragment() {
vec4 original_color = texture(TEXTURE, UV);
vec4 selection_color = texture(selection, UV);
ivec2 size = textureSize(TEXTURE, 0);
int xRes = size.x;
int yRes = size.y;
float xFactor = float(xRes) / float(pixel_size.x);
float yFactor = float(yRes) / float(pixel_size.y);
float grid_uv_x = round(UV.x * xFactor) / xFactor;
float grid_uv_y = round(UV.y * yFactor) / yFactor;
vec4 pixelated_color = texture(TEXTURE, vec2(grid_uv_x, grid_uv_y));
COLOR = mix(original_color.rgba, pixelated_color, selection_color.a);
}

View file

@ -463,8 +463,15 @@ func _fill_object_option_button() -> void:
return
object_option_button.clear()
object_option_button.add_item("None", 0)
var existing_names := {}
for id in _cel.object_properties:
var item_name: String = _object_names[_cel.object_properties[id]["type"]]
if item_name in existing_names:
# If there is already an object with the same name, under a number next to it
existing_names[item_name] += 1
item_name += " (%s)" % existing_names[item_name]
else:
existing_names[item_name] = 1
object_option_button.add_item(item_name, id + 1)

View file

@ -4,7 +4,7 @@ enum { X, Y, Z }
const ARROW_LENGTH := 14
const LIGHT_ARROW_LENGTH := 25
const GIZMO_WIDTH := 1.1
const GIZMO_WIDTH := 0.4
const SCALE_CIRCLE_LENGTH := 8
const SCALE_CIRCLE_RADIUS := 1
const CHAR_SCALE := 0.16

View file

@ -0,0 +1,29 @@
extends ImageEffect
var shader := preload("res://src/Shaders/Effects/Palettize.gdshader")
func _ready() -> void:
super._ready()
var sm := ShaderMaterial.new()
sm.shader = shader
preview.set_material(sm)
func commit_action(cel: Image, project := Global.current_project) -> void:
var selection_tex: ImageTexture
if selection_checkbox.button_pressed and project.has_selection:
selection_tex = ImageTexture.create_from_image(project.selection_map)
if not is_instance_valid(Palettes.current_palette):
return
var palette_image := Palettes.current_palette.convert_to_image()
var palette_texture := ImageTexture.create_from_image(palette_image)
var params := {"palette_texture": palette_texture, "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)

View file

@ -0,0 +1,11 @@
[gd_scene load_steps=3 format=3 uid="uid://d4gbo50bjenut"]
[ext_resource type="PackedScene" uid="uid://bybqhhayl5ay5" path="res://src/UI/Dialogs/ImageEffects/ImageEffectParent.tscn" id="1_cux3a"]
[ext_resource type="Script" path="res://src/UI/Dialogs/ImageEffects/PalettizeDialog.gd" id="2_4517g"]
[node name="PalettizeDialog" instance=ExtResource("1_cux3a")]
title = "Palettize"
script = ExtResource("2_4517g")
[node name="ShowAnimate" parent="VBoxContainer" index="0"]
visible = false

View file

@ -0,0 +1,30 @@
extends ImageEffect
var shader := preload("res://src/Shaders/Effects/Pixelize.gdshader")
var pixel_size := Vector2i.ONE
func _ready() -> void:
super._ready()
var sm := ShaderMaterial.new()
sm.shader = shader
preview.set_material(sm)
func commit_action(cel: Image, project := Global.current_project) -> void:
var selection_tex: ImageTexture
if selection_checkbox.button_pressed and project.has_selection:
selection_tex = ImageTexture.create_from_image(project.selection_map)
var params := {"pixel_size": pixel_size, "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_pixel_size_value_changed(value: Vector2) -> void:
pixel_size = value
update_preview()

View file

@ -0,0 +1,31 @@
[gd_scene load_steps=4 format=3 uid="uid://ts831nyvn6y7"]
[ext_resource type="PackedScene" uid="uid://bybqhhayl5ay5" path="res://src/UI/Dialogs/ImageEffects/ImageEffectParent.tscn" id="1_eiotn"]
[ext_resource type="Script" path="res://src/UI/Dialogs/ImageEffects/PixelizeDialog.gd" id="2_x5pd6"]
[ext_resource type="PackedScene" path="res://src/UI/Nodes/ValueSliderV2.tscn" id="3_s7ey1"]
[node name="PixelizeDialog" instance=ExtResource("1_eiotn")]
title = "Pixelize"
script = ExtResource("2_x5pd6")
[node name="ShowAnimate" parent="VBoxContainer" index="0"]
visible = false
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer" index="2"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer" index="0"]
layout_mode = 2
size_flags_horizontal = 3
text = "Pixel size:"
[node name="PixelSize" parent="VBoxContainer/HBoxContainer" index="1" instance=ExtResource("3_s7ey1")]
layout_mode = 2
size_flags_horizontal = 3
value = Vector2(1, 1)
min_value = Vector2(1, 1)
max_value = Vector2(255, 255)
allow_greater = true
show_ratio = true
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/PixelSize" to="." method="_on_pixel_size_value_changed"]

View file

@ -4,7 +4,7 @@ extends HBoxContainer
## A class that combines two ValueSlider nodes, for easy usage with Vector2 values.
## Also supports aspect ratio locking.
signal value_changed(value: float)
signal value_changed(value: Vector2)
signal ratio_toggled(button_pressed: bool)
@export var editable := true:

View file

@ -45,7 +45,7 @@ func _ready() -> void:
for child in get_children():
if not child is Button:
continue
var texture = child.get_child(0)
var texture := child.get_child(0)
if not texture is TextureRect:
continue
texture.modulate = Global.modulate_icon_color

View file

@ -18,7 +18,12 @@ size_flags_horizontal = 3
theme_override_constants/separation = 0
script = ExtResource("1_6hlpe")
[node name="VisibilityButton" type="Button" parent="." groups=["UIButtons"]]
[node name="HBoxContainer" type="HBoxContainer" parent="."]
custom_minimum_size = Vector2(84, 0)
layout_mode = 2
theme_override_constants/separation = 0
[node name="VisibilityButton" type="Button" parent="HBoxContainer" groups=["UIButtons"]]
unique_name_in_owner = true
custom_minimum_size = Vector2(28, 22)
layout_mode = 2
@ -26,7 +31,7 @@ tooltip_text = "Toggle layer's visibility"
focus_mode = 0
mouse_default_cursor_shape = 2
[node name="TextureRect" type="TextureRect" parent="VisibilityButton"]
[node name="TextureRect" type="TextureRect" parent="HBoxContainer/VisibilityButton"]
layout_mode = 0
anchor_left = 0.5
anchor_top = 0.5
@ -40,7 +45,7 @@ size_flags_horizontal = 0
size_flags_vertical = 0
texture = ExtResource("2_ef6fb")
[node name="LockButton" type="Button" parent="." groups=["UIButtons"]]
[node name="LockButton" type="Button" parent="HBoxContainer" groups=["UIButtons"]]
unique_name_in_owner = true
custom_minimum_size = Vector2(28, 22)
layout_mode = 2
@ -48,7 +53,7 @@ tooltip_text = "Lock/unlock layer"
focus_mode = 0
mouse_default_cursor_shape = 2
[node name="TextureRect" type="TextureRect" parent="LockButton"]
[node name="TextureRect" type="TextureRect" parent="HBoxContainer/LockButton"]
layout_mode = 0
anchor_left = 0.5
anchor_top = 0.5
@ -62,7 +67,7 @@ size_flags_horizontal = 0
size_flags_vertical = 0
texture = ExtResource("3_ah1my")
[node name="LinkButton" type="Button" parent="." groups=["UIButtons"]]
[node name="LinkButton" type="Button" parent="HBoxContainer" groups=["UIButtons"]]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(28, 22)
@ -73,7 +78,7 @@ Linked cels share content across multiple frames"
focus_mode = 0
mouse_default_cursor_shape = 2
[node name="TextureRect" type="TextureRect" parent="LinkButton"]
[node name="TextureRect" type="TextureRect" parent="HBoxContainer/LinkButton"]
layout_mode = 0
anchor_left = 0.5
anchor_top = 0.5
@ -87,7 +92,7 @@ size_flags_horizontal = 0
size_flags_vertical = 0
texture = ExtResource("4_058qm")
[node name="ExpandButton" type="Button" parent="." groups=["UIButtons"]]
[node name="ExpandButton" type="Button" parent="HBoxContainer" groups=["UIButtons"]]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(28, 22)
@ -96,7 +101,7 @@ tooltip_text = "Expand/collapse group"
focus_mode = 0
mouse_default_cursor_shape = 2
[node name="TextureRect" type="TextureRect" parent="ExpandButton"]
[node name="TextureRect" type="TextureRect" parent="HBoxContainer/ExpandButton"]
layout_mode = 0
anchor_left = 0.5
anchor_top = 0.5
@ -170,10 +175,10 @@ item_1/text = "Clipping mask"
item_1/checkable = 1
item_1/id = 1
[connection signal="pressed" from="VisibilityButton" to="." method="_on_visibility_button_pressed"]
[connection signal="pressed" from="LockButton" to="." method="_on_lock_button_pressed"]
[connection signal="pressed" from="LinkButton" to="." method="_on_link_button_pressed"]
[connection signal="pressed" from="ExpandButton" to="." method="_on_expand_button_pressed"]
[connection signal="pressed" from="HBoxContainer/VisibilityButton" to="." method="_on_visibility_button_pressed"]
[connection signal="pressed" from="HBoxContainer/LockButton" to="." method="_on_lock_button_pressed"]
[connection signal="pressed" from="HBoxContainer/LinkButton" to="." method="_on_link_button_pressed"]
[connection signal="pressed" from="HBoxContainer/ExpandButton" to="." method="_on_expand_button_pressed"]
[connection signal="gui_input" from="LayerMainButton" to="." method="_on_main_button_gui_input"]
[connection signal="focus_exited" from="LayerMainButton/LayerName/LayerNameLineEdit" to="." method="_on_layer_name_line_edit_focus_exited"]
[connection signal="id_pressed" from="PopupMenu" to="." method="_on_popup_menu_id_pressed"]

View file

@ -13,6 +13,8 @@ var effects: Array[LayerEffect] = [
LayerEffect.new(
"Adjust Hue/Saturation/Value", preload("res://src/Shaders/Effects/HSV.gdshader")
),
LayerEffect.new("Palettize", preload("res://src/Shaders/Effects/Palettize.gdshader")),
LayerEffect.new("Pixelize", preload("res://src/Shaders/Effects/Pixelize.gdshader")),
LayerEffect.new("Posterize", preload("res://src/Shaders/Effects/Posterize.gdshader")),
LayerEffect.new("Gradient Map", preload("res://src/Shaders/Effects/GradientMap.gdshader")),
]

View file

@ -29,6 +29,8 @@ var drop_shadow_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/DropShad
var hsv_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/HSVDialog.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")
var palettize_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/PalettizeDialog.tscn")
var pixelize_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/PixelizeDialog.tscn")
var posterize_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/Posterize.tscn")
var shader_effect_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/ShaderEffect.tscn")
var manage_layouts_dialog := Dialog.new("res://src/UI/Dialogs/ManageLayouts.tscn")
@ -364,6 +366,8 @@ func _setup_effects_menu() -> void:
"Invert Colors": "invert_colors",
"Desaturation": "desaturation",
"Adjust Hue/Saturation/Value": "adjust_hsv",
"Palettize": "palettize",
"Pixelize": "pixelize",
"Posterize": "posterize",
"Gradient": "gradient",
"Gradient Map": "gradient_map",
@ -771,6 +775,10 @@ func effects_menu_id_pressed(id: int) -> void:
gradient_dialog.popup()
Global.EffectsMenu.GRADIENT_MAP:
gradient_map_dialog.popup()
Global.EffectsMenu.PALETTIZE:
palettize_dialog.popup()
Global.EffectsMenu.PIXELIZE:
pixelize_dialog.popup()
Global.EffectsMenu.POSTERIZE:
posterize_dialog.popup()
#Global.EffectsMenu.SHADER: