1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-31 07:29:49 +00:00

Add cel properties and z-index to individual cels

This commit is contained in:
Emmanouil Papadeas 2023-12-06 03:22:33 +02:00
parent f893e68d59
commit c0a8202145
10 changed files with 146 additions and 49 deletions

View file

@ -1815,12 +1815,15 @@ msgstr ""
msgid "Unlink Cels" msgid "Unlink Cels"
msgstr "" msgstr ""
msgid "Frame Properties" msgid "Properties"
msgstr "" msgstr ""
msgid "Frame properties" msgid "Frame properties"
msgstr "" msgstr ""
msgid "Cel properties"
msgstr ""
#. Found on the popup menu that appears when a user right-clicks on a frame button. When clicked, the order of the selected frames is being reversed. #. Found on the popup menu that appears when a user right-clicks on a frame button. When clicked, the order of the selected frames is being reversed.
msgid "Reverse Frames" msgid "Reverse Frames"
msgstr "" msgstr ""

View file

@ -21,22 +21,23 @@ func blend_layers(
# the second are the opacities and the third are the origins # the second are the opacities and the third are the origins
var metadata_image := Image.create(project.layers.size(), 3, false, Image.FORMAT_R8) var metadata_image := Image.create(project.layers.size(), 3, false, Image.FORMAT_R8)
for i in project.layers.size(): for i in project.layers.size():
var layer := project.layers[i] var ordered_index := project.ordered_layers[i]
var layer := project.layers[ordered_index]
var include := true if layer.is_visible_in_hierarchy() else false var include := true if layer.is_visible_in_hierarchy() else false
if only_selected and include: if only_selected and include:
var test_array := [project.frames.find(frame), i] var test_array := [project.frames.find(frame), i]
if not test_array in project.selected_cels: if not test_array in project.selected_cels:
include = false include = false
var cel := frame.cels[i] var cel := frame.cels[ordered_index]
var cel_image := layer.display_effects(cel) var cel_image := layer.display_effects(cel)
textures.append(cel_image) textures.append(cel_image)
# Store the blend mode # Store the blend mode
metadata_image.set_pixel(i, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0)) metadata_image.set_pixel(ordered_index, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0))
# Store the opacity # Store the opacity
if include: if include:
metadata_image.set_pixel(i, 1, Color(cel.opacity, 0.0, 0.0, 0.0)) metadata_image.set_pixel(ordered_index, 1, Color(cel.opacity, 0.0, 0.0, 0.0))
else: else:
metadata_image.set_pixel(i, 1, Color()) metadata_image.set_pixel(ordered_index, 1, Color())
var texture_array := Texture2DArray.new() var texture_array := Texture2DArray.new()
texture_array.create_from_images(textures) texture_array.create_from_images(textures)
var params := { var params := {

View file

@ -1,9 +1,9 @@
class_name BaseCel class_name BaseCel
extends RefCounted extends RefCounted
## Base class for cel properties. ## Base class for cel properties.
## The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel). ## "Cel" is short for the term "celluloid" [url]https://en.wikipedia.org/wiki/Cel[/url].
signal texture_changed ## Emitted whenever cel's tecture is changed signal texture_changed ## Emitted whenever the cel's texture is changed
var opacity := 1.0 ## Opacity/Transparency of the cel. var opacity := 1.0 ## Opacity/Transparency of the cel.
## The image stored in the cel. ## The image stored in the cel.
@ -14,6 +14,7 @@ var image_texture: Texture2D:
## [br] If the cel is not linked then it is [code]null[/code]. ## [br] If the cel is not linked then it is [code]null[/code].
var link_set = null # { "cels": Array, "hue": float } or null var link_set = null # { "cels": Array, "hue": float } or null
var transformed_content: Image ## Used in transformations (moving, scaling etc with selections). var transformed_content: Image ## Used in transformations (moving, scaling etc with selections).
var z_index := 0
# Methods to Override: # Methods to Override:
@ -70,12 +71,14 @@ func update_texture() -> void:
## Returns a curated [Dictionary] containing the cel data. ## Returns a curated [Dictionary] containing the cel data.
func serialize() -> Dictionary: func serialize() -> Dictionary:
return {"opacity": opacity} return {"opacity": opacity, "z_index": z_index}
## Sets the cel data according to a curated [Dictionary] obtained from [method serialize]. ## Sets the cel data according to a curated [Dictionary] obtained from [method serialize].
func deserialize(dict: Dictionary) -> void: func deserialize(dict: Dictionary) -> void:
opacity = dict["opacity"] opacity = dict["opacity"]
if dict.has("z_index"):
z_index = dict["z_index"]
## Used to perform cleanup after a cel is removed. ## Used to perform cleanup after a cel is removed.

View file

@ -33,14 +33,15 @@ var frames: Array[Frame] = []
var layers: Array[BaseLayer] = [] var layers: Array[BaseLayer] = []
var current_frame := 0 var current_frame := 0
var current_layer := 0 var current_layer := 0
var selected_cels := [[0, 0]] # Array of Arrays of 2 integers (frame & layer) var selected_cels := [[0, 0]] ## Array of Arrays of 2 integers (frame & layer)
var ordered_layers: Array[int] = [0]
var animation_tags: Array[AnimationTag] = []: var animation_tags: Array[AnimationTag] = []:
set = _animation_tags_changed set = _animation_tags_changed
var guides: Array[Guide] = [] var guides: Array[Guide] = []
var brushes: Array[Image] = [] var brushes: Array[Image] = []
var reference_images: Array[ReferenceImage] = [] var reference_images: Array[ReferenceImage] = []
var vanishing_points := [] # Array of Vanishing Points var vanishing_points := [] ## Array of Vanishing Points
var fps := 6.0 var fps := 6.0
var x_symmetry_point: float var x_symmetry_point: float
@ -49,15 +50,15 @@ var x_symmetry_axis := SymmetryGuide.new()
var y_symmetry_axis := SymmetryGuide.new() var y_symmetry_axis := SymmetryGuide.new()
var selection_map := SelectionMap.new() var selection_map := SelectionMap.new()
# This is useful for when the selection is outside of the canvas boundaries, ## This is useful for when the selection is outside of the canvas boundaries,
# on the left and/or above (negative coords) ## on the left and/or above (negative coords)
var selection_offset := Vector2i.ZERO: var selection_offset := Vector2i.ZERO:
set(value): set(value):
selection_offset = value selection_offset = value
Global.canvas.selection.marching_ants_outline.offset = selection_offset Global.canvas.selection.marching_ants_outline.offset = selection_offset
var has_selection := false var has_selection := false
# For every camera (currently there are 3) ## For every camera (currently there are 3)
var cameras_rotation: PackedFloat32Array = [0.0, 0.0, 0.0] var cameras_rotation: PackedFloat32Array = [0.0, 0.0, 0.0]
var cameras_zoom: PackedVector2Array = [ var cameras_zoom: PackedVector2Array = [
Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15) Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15)
@ -436,6 +437,7 @@ func deserialize(dict: Dictionary) -> void:
if dict.has("fps"): if dict.has("fps"):
fps = dict.fps fps = dict.fps
_deserialize_metadata(self, dict) _deserialize_metadata(self, dict)
order_layers()
func _serialize_metadata(object: Object) -> Dictionary: func _serialize_metadata(object: Object) -> Dictionary:
@ -613,6 +615,25 @@ func find_first_drawable_cel(frame := frames[current_frame]) -> BaseCel:
return result return result
func order_layers(frame_index := current_frame) -> void:
ordered_layers = []
for i in layers.size():
ordered_layers.append(i)
ordered_layers.sort_custom(_z_index_sort.bind(frame_index))
func _z_index_sort(a: int, b: int, frame_index: int) -> bool:
var z_index_a := frames[frame_index].cels[a].z_index
var z_index_b := frames[frame_index].cels[b].z_index
var layer_index_a := layers[a].index + z_index_a
var layer_index_b := layers[b].index + z_index_b
if layer_index_a < layer_index_b:
return true
if layer_index_a == layer_index_b and z_index_a < z_index_b:
return true
return false
# Timeline modifications # Timeline modifications
# Modifying layers or frames Arrays on the current project should generally only be done # Modifying layers or frames Arrays on the current project should generally only be done
# through these methods. # through these methods.
@ -832,6 +853,7 @@ func _update_frame_ui() -> void:
## Update the layer indices and layer/cel buttons ## Update the layer indices and layer/cel buttons
func _update_layer_ui() -> void: func _update_layer_ui() -> void:
order_layers()
for l in layers.size(): for l in layers.size():
layers[l].index = l layers[l].index = l
Global.layer_vbox.get_child(layers.size() - 1 - l).layer_index = l Global.layer_vbox.get_child(layers.size() - 1 - l).layer_index = l

View file

@ -29,6 +29,8 @@ var layer_metadata_texture := ImageTexture.new()
func _ready() -> void: func _ready() -> void:
material.set_shader_parameter("layers", layer_texture_array)
material.set_shader_parameter("metadata", layer_metadata_texture)
Global.project_changed.connect(queue_redraw) Global.project_changed.connect(queue_redraw)
onion_past.type = onion_past.PAST onion_past.type = onion_past.PAST
onion_past.blue_red_color = Global.onion_skinning_past_color onion_past.blue_red_color = Global.onion_skinning_past_color
@ -124,7 +126,7 @@ func update_texture(layer_i: int, frame_i := -1, project := Global.current_proje
cel_image.get_size() cel_image.get_size()
== Vector2i(layer_texture_array.get_width(), layer_texture_array.get_height()) == Vector2i(layer_texture_array.get_width(), layer_texture_array.get_height())
): ):
layer_texture_array.update_layer(cel_image, layer_i) layer_texture_array.update_layer(cel_image, project.ordered_layers[layer_i])
func update_selected_cels_textures(project := Global.current_project) -> void: func update_selected_cels_textures(project := Global.current_project) -> void:
@ -146,60 +148,72 @@ func draw_layers() -> void:
) )
if recreate_texture_array: if recreate_texture_array:
var textures: Array[Image] = [] var textures: Array[Image] = []
textures.resize(project.layers.size())
# Nx3 texture, where N is the number of layers and the first row are the blend modes, # Nx3 texture, where N is the number of layers and the first row are the blend modes,
# the second are the opacities and the third are the origins # the second are the opacities and the third are the origins
layer_metadata_image = Image.create(project.layers.size(), 3, false, Image.FORMAT_RG8) layer_metadata_image = Image.create(project.layers.size(), 3, false, Image.FORMAT_RG8)
# Draw current frame layers # Draw current frame layers
for i in project.layers.size(): for i in project.layers.size():
var ordered_index := project.ordered_layers[i]
var layer := project.layers[i] var layer := project.layers[i]
var cel_image: Image
if Global.display_layer_effects:
cel_image = layer.display_effects(current_cels[i])
else:
cel_image = current_cels[i].get_image()
textures.append(cel_image)
# Store the blend mode
layer_metadata_image.set_pixel(i, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0))
# Store the opacity
if layer.is_visible_in_hierarchy():
layer_metadata_image.set_pixel(i, 1, Color(current_cels[i].opacity, 0.0, 0.0, 0.0))
else:
layer_metadata_image.set_pixel(i, 1, Color())
# Store the origin
if [project.current_frame, i] in project.selected_cels:
var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size())
layer_metadata_image.set_pixel(i, 2, Color(origin.x, origin.y, 0.0, 0.0))
else:
layer_metadata_image.set_pixel(i, 2, Color())
layer_texture_array.create_from_images(textures)
layer_metadata_texture.set_image(layer_metadata_image)
else: # Update the TextureArray
if layer_texture_array.get_layers() > 0:
for i in project.layers.size():
var layer := project.layers[i]
var test_array := [project.current_frame, i]
if not update_all_layers:
if not test_array in project.selected_cels:
continue
var cel := current_cels[i] var cel := current_cels[i]
var cel_image: Image var cel_image: Image
if Global.display_layer_effects: if Global.display_layer_effects:
cel_image = layer.display_effects(cel) cel_image = layer.display_effects(cel)
else: else:
cel_image = cel.get_image() cel_image = cel.get_image()
layer_texture_array.update_layer(cel_image, i) textures[ordered_index] = cel_image
layer_metadata_image.set_pixel(i, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0)) # Store the blend mode
layer_metadata_image.set_pixel(
ordered_index, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0)
)
# Store the opacity
if layer.is_visible_in_hierarchy(): if layer.is_visible_in_hierarchy():
layer_metadata_image.set_pixel(i, 1, Color(cel.opacity, 0.0, 0.0, 0.0)) layer_metadata_image.set_pixel(ordered_index, 1, Color(cel.opacity, 0.0, 0.0, 0.0))
else: else:
layer_metadata_image.set_pixel(i, 1, Color()) layer_metadata_image.set_pixel(ordered_index, 1, Color())
# Store the origin
if [project.current_frame, i] in project.selected_cels:
var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size()) var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size())
layer_metadata_image.set_pixel(i, 2, Color(origin.x, origin.y, 0.0, 0.0)) layer_metadata_image.set_pixel(
ordered_index, 2, Color(origin.x, origin.y, 0.0, 0.0)
)
else:
layer_metadata_image.set_pixel(ordered_index, 2, Color())
layer_texture_array.create_from_images(textures)
layer_metadata_texture.set_image(layer_metadata_image)
else: # Update the TextureArray
if layer_texture_array.get_layers() > 0:
for i in project.layers.size():
if not update_all_layers:
var test_array := [project.current_frame, i]
if not test_array in project.selected_cels:
continue
var ordered_index := project.ordered_layers[i]
var layer := project.layers[i]
var cel := current_cels[i]
var cel_image: Image
if Global.display_layer_effects:
cel_image = layer.display_effects(cel)
else:
cel_image = cel.get_image()
layer_texture_array.update_layer(cel_image, ordered_index)
layer_metadata_image.set_pixel(
ordered_index, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0)
)
if layer.is_visible_in_hierarchy():
layer_metadata_image.set_pixel(
ordered_index, 1, Color(cel.opacity, 0.0, 0.0, 0.0)
)
else:
layer_metadata_image.set_pixel(ordered_index, 1, Color())
var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size())
layer_metadata_image.set_pixel(
ordered_index, 2, Color(origin.x, origin.y, 0.0, 0.0)
)
layer_metadata_texture.update(layer_metadata_image) layer_metadata_texture.update(layer_metadata_image)
material.set_shader_parameter("layers", layer_texture_array)
material.set_shader_parameter("metadata", layer_metadata_texture)
material.set_shader_parameter("origin_x_positive", move_preview_location.x > 0) material.set_shader_parameter("origin_x_positive", move_preview_location.x > 0)
material.set_shader_parameter("origin_y_positive", move_preview_location.y > 0) material.set_shader_parameter("origin_y_positive", move_preview_location.y > 0)
update_all_layers = false update_all_layers = false

View file

@ -80,7 +80,7 @@ func _draw_layers() -> void:
# the second are the opacities and the third are the origins # the second are the opacities and the third are the origins
var metadata_image := Image.create(project.layers.size(), 3, false, Image.FORMAT_R8) var metadata_image := Image.create(project.layers.size(), 3, false, Image.FORMAT_R8)
# Draw current frame layers # Draw current frame layers
for i in project.layers.size(): for i in project.ordered_layers:
if current_cels[i] is GroupCel: if current_cels[i] is GroupCel:
continue continue
var layer := project.layers[i] var layer := project.layers[i]

View file

@ -1,6 +1,6 @@
extends Button extends Button
enum MenuOptions { DELETE, LINK, UNLINK, PROPERTIES } enum MenuOptions { PROPERTIES, DELETE, LINK, UNLINK }
var frame := 0 var frame := 0
var layer := 0 var layer := 0
@ -10,6 +10,7 @@ var cel: BaseCel
@onready var linked_indicator: Polygon2D = get_node_or_null("LinkedIndicator") @onready var linked_indicator: Polygon2D = get_node_or_null("LinkedIndicator")
@onready var cel_texture: TextureRect = $CelTexture @onready var cel_texture: TextureRect = $CelTexture
@onready var transparent_checker: ColorRect = $CelTexture/TransparentChecker @onready var transparent_checker: ColorRect = $CelTexture/TransparentChecker
@onready var properties: AcceptDialog = $Properties
func _ready() -> void: func _ready() -> void:
@ -98,6 +99,8 @@ func _on_CelButton_pressed() -> void:
func _on_PopupMenu_id_pressed(id: int) -> void: func _on_PopupMenu_id_pressed(id: int) -> void:
match id: match id:
MenuOptions.PROPERTIES:
properties.popup_centered()
MenuOptions.DELETE: MenuOptions.DELETE:
_delete_cel_content() _delete_cel_content()
@ -294,3 +297,14 @@ func _get_region_rect(x_begin: float, x_end: float) -> Rect2:
rect.position.x += rect.size.x * x_begin rect.position.x += rect.size.x * x_begin
rect.size.x *= x_end - x_begin rect.size.x *= x_end - x_begin
return rect return rect
func _on_z_index_slider_value_changed(value: float) -> void:
cel.z_index = value
Global.current_project.order_layers()
Global.canvas.update_all_layers = true
Global.canvas.queue_redraw()
func _on_properties_visibility_changed() -> void:
Global.dialog_open(properties.visible)

View file

@ -1,8 +1,9 @@
[gd_scene load_steps=5 format=3 uid="uid://dw7ci3uixjuev"] [gd_scene load_steps=6 format=3 uid="uid://dw7ci3uixjuev"]
[ext_resource type="Script" path="res://src/UI/Timeline/CelButton.gd" id="1_iewgo"] [ext_resource type="Script" path="res://src/UI/Timeline/CelButton.gd" id="1_iewgo"]
[ext_resource type="PackedScene" uid="uid://3pmb60gpst7b" path="res://src/UI/Nodes/TransparentChecker.tscn" id="2_mi8wp"] [ext_resource type="PackedScene" uid="uid://3pmb60gpst7b" path="res://src/UI/Nodes/TransparentChecker.tscn" id="2_mi8wp"]
[ext_resource type="Shader" path="res://src/Shaders/TransparentChecker.gdshader" id="3_qv21g"] [ext_resource type="Shader" path="res://src/Shaders/TransparentChecker.gdshader" id="3_qv21g"]
[ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="4_wcpcc"]
[sub_resource type="ShaderMaterial" id="1"] [sub_resource type="ShaderMaterial" id="1"]
shader = ExtResource("3_qv21g") shader = ExtResource("3_qv21g")
@ -53,13 +54,15 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
[node name="PopupMenu" type="PopupMenu" parent="."] [node name="PopupMenu" type="PopupMenu" parent="."]
item_count = 3 item_count = 4
item_0/text = "Delete" item_0/text = "Properties"
item_0/id = -1 item_0/id = 0
item_1/text = "Link Cels to" item_1/text = "Delete"
item_1/id = -1 item_1/id = 1
item_2/text = "Unlink Cels" item_2/text = "Link Cels to"
item_2/id = 2 item_2/id = 2
item_3/text = "Unlink Cels"
item_3/id = 3
[node name="LinkedIndicator" type="Polygon2D" parent="."] [node name="LinkedIndicator" type="Polygon2D" parent="."]
color = Color(0, 1, 0, 1) color = Color(0, 1, 0, 1)
@ -67,6 +70,43 @@ invert_enabled = true
invert_border = 1.0 invert_border = 1.0
polygon = PackedVector2Array(0, 0, 36, 0, 36, 36, 0, 36) polygon = PackedVector2Array(0, 0, 36, 0, 36, 36, 0, 36)
[node name="Properties" type="AcceptDialog" parent="."]
title = "Cel properties"
size = Vector2i(300, 100)
exclusive = false
popup_window = true
[node name="GridContainer" type="GridContainer" parent="Properties"]
offset_left = 8.0
offset_top = 8.0
offset_right = 292.0
offset_bottom = 55.0
columns = 2
[node name="Label" type="Label" parent="Properties/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Z-Index:"
[node name="ZIndexSlider" type="TextureProgressBar" parent="Properties/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 2
mouse_default_cursor_shape = 2
theme_type_variation = &"ValueSlider"
min_value = -64.0
max_value = 64.0
allow_greater = true
allow_lesser = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource("4_wcpcc")
[connection signal="pressed" from="." to="." method="_on_CelButton_pressed"] [connection signal="pressed" from="." to="." method="_on_CelButton_pressed"]
[connection signal="resized" from="." to="." method="_on_CelButton_resized"] [connection signal="resized" from="." to="." method="_on_CelButton_resized"]
[connection signal="id_pressed" from="PopupMenu" to="." method="_on_PopupMenu_id_pressed"] [connection signal="id_pressed" from="PopupMenu" to="." method="_on_PopupMenu_id_pressed"]
[connection signal="visibility_changed" from="Properties" to="." method="_on_properties_visibility_changed"]
[connection signal="value_changed" from="Properties/GridContainer/ZIndexSlider" to="." method="_on_z_index_slider_value_changed"]

View file

@ -1,6 +1,6 @@
extends Button extends Button
enum { REMOVE, CLONE, MOVE_LEFT, MOVE_RIGHT, PROPERTIES, REVERSE, CENTER } enum { PROPERTIES, REMOVE, CLONE, MOVE_LEFT, MOVE_RIGHT, REVERSE, CENTER }
var frame := 0 var frame := 0

View file

@ -14,19 +14,19 @@ script = ExtResource("1")
[node name="PopupMenu" type="PopupMenu" parent="."] [node name="PopupMenu" type="PopupMenu" parent="."]
item_count = 7 item_count = 7
item_0/text = "Remove Frame" item_0/text = "Properties"
item_0/id = -1 item_0/id = -1
item_0/disabled = true item_1/text = "Remove Frame"
item_1/text = "Clone Frame"
item_1/id = -1 item_1/id = -1
item_2/text = "Move Left" item_1/disabled = true
item_2/text = "Clone Frame"
item_2/id = -1 item_2/id = -1
item_2/disabled = true item_3/text = "Move Left"
item_3/text = "Move Right"
item_3/id = -1 item_3/id = -1
item_3/disabled = true item_3/disabled = true
item_4/text = "Frame Properties" item_4/text = "Move Right"
item_4/id = -1 item_4/id = -1
item_4/disabled = true
item_5/text = "Reverse Frames" item_5/text = "Reverse Frames"
item_5/id = 5 item_5/id = 5
item_5/disabled = true item_5/disabled = true