mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 09:09:47 +00:00
Implement support for group layer blending (#1077)
* Blend group layers on `DrawingAlgos.blend_layers()` * Support group layer blending on the canvas * Allow editing of group layer properties * Fix issues with group layer blending in canvas, and unite common code * Group layers can now be used as clipping masks * Make move tool preview work on child layers * Change OffsetImage's `blend_layers()` to support group layer blending * Support group layer blending in the canvas preview * Fix layer blending mode, clipping mask opacity and cel opacity not being updated automatically if the layer/cel changed is not selected * Add a pass through blending mode to layer groups Fingers crossed that no bugs were introduced * Fix issue with layers that belong to pass through groups not updating their textures on the canvas automatically on undo
This commit is contained in:
parent
f6f40e03e5
commit
077c57c53a
|
@ -2143,6 +2143,10 @@ msgstr ""
|
|||
msgid "Blend mode:"
|
||||
msgstr ""
|
||||
|
||||
#. Found in the layer's section of the timeline, as a blend mode option, only available for group layers. If enabled, group blending is disabled and the group simply acts as a way to organize layers instead of affecting blending.
|
||||
msgid "Pass through"
|
||||
msgstr ""
|
||||
|
||||
#. Adjective, refers to something usual/regular, such as the normal blend mode.
|
||||
msgid "Normal"
|
||||
msgstr ""
|
||||
|
|
|
@ -53,8 +53,18 @@ func blend_layers(
|
|||
if DisplayServer.get_name() == "headless":
|
||||
blend_layers_headless(image, project, layer, cel, origin)
|
||||
else:
|
||||
var cel_image := layer.display_effects(cel)
|
||||
textures.append(cel_image)
|
||||
if layer is GroupLayer and layer.blend_mode != BaseLayer.BlendModes.PASS_THROUGH:
|
||||
var cel_image := (layer as GroupLayer).blend_children(frame)
|
||||
textures.append(cel_image)
|
||||
else:
|
||||
var cel_image := layer.display_effects(cel)
|
||||
textures.append(cel_image)
|
||||
if (
|
||||
layer.is_blended_by_ancestor()
|
||||
and not only_selected_cels
|
||||
and not only_selected_layers
|
||||
):
|
||||
include = false
|
||||
set_layer_metadata_image(layer, cel, metadata_image, ordered_index, include)
|
||||
if DisplayServer.get_name() != "headless":
|
||||
var texture_array := Texture2DArray.new()
|
||||
|
@ -84,9 +94,14 @@ func set_layer_metadata_image(
|
|||
image.set_pixel(index, 1, Color())
|
||||
# Store the clipping mask boolean
|
||||
if layer.clipping_mask:
|
||||
image.set_pixel(index, 3, Color.WHITE)
|
||||
image.set_pixel(index, 3, Color.RED)
|
||||
else:
|
||||
image.set_pixel(index, 3, Color.BLACK)
|
||||
if not include:
|
||||
# Store a small red value as a way to indicate that this layer should be skipped
|
||||
# Used for layers such as child layers of a group, so that the group layer itself can
|
||||
# sucessfuly be used as a clipping mask with the layer below it.
|
||||
image.set_pixel(index, 3, Color(0.2, 0.0, 0.0, 0.0))
|
||||
|
||||
|
||||
func blend_layers_headless(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# gdlint: ignore=max-public-methods
|
||||
class_name BaseLayer
|
||||
extends RefCounted
|
||||
## Base class for layer properties. Different layer types extend from this class.
|
||||
|
@ -9,7 +10,8 @@ signal visibility_changed ## Emits when [member visible] is changed.
|
|||
## is the blend layer, and the bottom layer is the base layer.
|
||||
## For more information, refer to: [url]https://en.wikipedia.org/wiki/Blend_modes[/url]
|
||||
enum BlendModes {
|
||||
NORMAL, ## The blend layer colors are simply placed on top of the base colors.
|
||||
PASS_THROUGH = -2, ## Only for group layers. Ignores group blending, like it doesn't exist.
|
||||
NORMAL = 0, ## The blend layer colors are simply placed on top of the base colors.
|
||||
DARKEN, ## Keeps the darker colors between the blend and the base layers.
|
||||
MULTIPLY, ## Multiplies the numerical values of the two colors, giving a darker result.
|
||||
COLOR_BURN, ## Darkens by increasing the contrast between the blend and base colors.
|
||||
|
@ -124,6 +126,17 @@ func is_locked_in_hierarchy() -> bool:
|
|||
return locked
|
||||
|
||||
|
||||
## Returns [code]true[/code] if the layer has at least one ancestor
|
||||
## that does not have its blend mode set to pass through.
|
||||
func is_blended_by_ancestor() -> bool:
|
||||
var is_blended := false
|
||||
for ancestor in get_ancestors():
|
||||
if ancestor.blend_mode != BlendModes.PASS_THROUGH:
|
||||
is_blended = true
|
||||
break
|
||||
return is_blended
|
||||
|
||||
|
||||
## Returns an [Array] of [BaseLayer]s that are ancestors of this layer.
|
||||
## If there are no ancestors, returns an empty array.
|
||||
func get_ancestors() -> Array[BaseLayer]:
|
||||
|
@ -141,6 +154,20 @@ func get_hierarchy_depth() -> int:
|
|||
return 0
|
||||
|
||||
|
||||
## Returns the layer's top most parent that is responsible for its blending.
|
||||
## For example, if a layer belongs in a group with its blend mode set to anything but pass through,
|
||||
## and that group has no parents of its own, then that group gets returned.
|
||||
## If that group is a child of another non-pass through group,
|
||||
## then the grandparent group is returned, and so on.
|
||||
## If the layer has no ancestors, or if they are set to pass through mode, it returns self.
|
||||
func get_blender_ancestor() -> BaseLayer:
|
||||
var blender := self
|
||||
for ancestor in get_ancestors():
|
||||
if ancestor.blend_mode != BlendModes.PASS_THROUGH:
|
||||
blender = ancestor
|
||||
return blender
|
||||
|
||||
|
||||
## Returns the path of the layer in the timeline as a [String].
|
||||
func get_layer_path() -> String:
|
||||
if is_instance_valid(parent):
|
||||
|
@ -189,9 +216,12 @@ func link_cel(cel: BaseCel, link_set = null) -> void:
|
|||
## Returns a copy of the [param cel]'s [Image] with all of the effects applied to it.
|
||||
## This method is not destructive as it does NOT change the data of the image,
|
||||
## it just returns a copy.
|
||||
func display_effects(cel: BaseCel) -> Image:
|
||||
func display_effects(cel: BaseCel, image_override: Image = null) -> Image:
|
||||
var image := Image.new()
|
||||
image.copy_from(cel.get_image())
|
||||
if is_instance_valid(image_override):
|
||||
image.copy_from(image_override)
|
||||
else:
|
||||
image.copy_from(cel.get_image())
|
||||
if not effects_enabled:
|
||||
return image
|
||||
var image_size := image.get_size()
|
||||
|
@ -200,8 +230,10 @@ func display_effects(cel: BaseCel) -> Image:
|
|||
continue
|
||||
var shader_image_effect := ShaderImageEffect.new()
|
||||
shader_image_effect.generate_image(image, effect.shader, effect.params, image_size)
|
||||
# Inherit effects from the parents
|
||||
# Inherit effects from the parents, if their blend mode is set to pass through
|
||||
for ancestor in get_ancestors():
|
||||
if ancestor.blend_mode != BlendModes.PASS_THROUGH:
|
||||
break
|
||||
if not ancestor.effects_enabled:
|
||||
continue
|
||||
for effect in ancestor.effects:
|
||||
|
|
|
@ -11,44 +11,130 @@ func _init(_project: Project, _name := "") -> void:
|
|||
|
||||
|
||||
## Blends all of the images of children layer of the group layer into a single image.
|
||||
func blend_children(frame: Frame, origin := Vector2i.ZERO) -> Image:
|
||||
func blend_children(frame: Frame, origin := Vector2i.ZERO, apply_effects := true) -> Image:
|
||||
var image := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var children := get_children(false)
|
||||
if children.size() <= 0:
|
||||
return image
|
||||
var blend_rect := Rect2i(Vector2i.ZERO, project.size)
|
||||
var textures: Array[Image] = []
|
||||
var metadata_image := Image.create(children.size(), 4, false, Image.FORMAT_R8)
|
||||
var metadata_image := Image.create(children.size(), 4, false, Image.FORMAT_RG8)
|
||||
var current_child_index := 0
|
||||
for i in children.size():
|
||||
var layer := children[i]
|
||||
if not layer.is_visible_in_hierarchy():
|
||||
current_child_index += 1
|
||||
continue
|
||||
var cel := frame.cels[layer.index]
|
||||
if layer is GroupLayer:
|
||||
var blended_children: Image = layer.blend_children(frame, origin)
|
||||
if DisplayServer.get_name() == "headless":
|
||||
image.blend_rect(blended_children, blend_rect, origin)
|
||||
else:
|
||||
textures.append(blended_children)
|
||||
DrawingAlgos.set_layer_metadata_image(layer, cel, metadata_image, i)
|
||||
current_child_index = _blend_child_group(
|
||||
image,
|
||||
layer,
|
||||
frame,
|
||||
textures,
|
||||
metadata_image,
|
||||
current_child_index,
|
||||
origin,
|
||||
apply_effects
|
||||
)
|
||||
else:
|
||||
if DisplayServer.get_name() == "headless":
|
||||
DrawingAlgos.blend_layers_headless(image, project, layer, cel, origin)
|
||||
else:
|
||||
textures.append(layer.display_effects(cel))
|
||||
DrawingAlgos.set_layer_metadata_image(layer, cel, metadata_image, i)
|
||||
_include_child_in_blending(
|
||||
image,
|
||||
layer,
|
||||
frame,
|
||||
textures,
|
||||
metadata_image,
|
||||
current_child_index,
|
||||
origin,
|
||||
apply_effects
|
||||
)
|
||||
current_child_index += 1
|
||||
|
||||
if DisplayServer.get_name() != "headless":
|
||||
if DisplayServer.get_name() != "headless" and textures.size() > 0:
|
||||
var texture_array := Texture2DArray.new()
|
||||
texture_array.create_from_images(textures)
|
||||
var params := {
|
||||
"layers": texture_array, "metadata": ImageTexture.create_from_image(metadata_image)
|
||||
"layers": texture_array,
|
||||
"metadata": ImageTexture.create_from_image(metadata_image),
|
||||
"origin_x_positive": origin.x > 0,
|
||||
"origin_y_positive": origin.y > 0,
|
||||
}
|
||||
var gen := ShaderImageEffect.new()
|
||||
gen.generate_image(image, DrawingAlgos.blend_layers_shader, params, project.size)
|
||||
if apply_effects:
|
||||
image = display_effects(frame.cels[index], image)
|
||||
return image
|
||||
|
||||
|
||||
func _include_child_in_blending(
|
||||
image: Image,
|
||||
layer: BaseLayer,
|
||||
frame: Frame,
|
||||
textures: Array[Image],
|
||||
metadata_image: Image,
|
||||
i: int,
|
||||
origin: Vector2i,
|
||||
apply_effects: bool
|
||||
) -> void:
|
||||
var cel := frame.cels[layer.index]
|
||||
if DisplayServer.get_name() == "headless":
|
||||
DrawingAlgos.blend_layers_headless(image, project, layer, cel, origin)
|
||||
else:
|
||||
var cel_image: Image
|
||||
if apply_effects:
|
||||
cel_image = layer.display_effects(cel)
|
||||
else:
|
||||
cel_image = cel.get_image()
|
||||
textures.append(cel_image)
|
||||
DrawingAlgos.set_layer_metadata_image(layer, cel, metadata_image, i)
|
||||
if origin != Vector2i.ZERO:
|
||||
# Only used as a preview for the move tool, when used on a group's children
|
||||
var test_array := [project.frames.find(frame), project.layers.find(layer)]
|
||||
if test_array in project.selected_cels:
|
||||
var origin_fixed := Vector2(origin).abs() / Vector2(cel_image.get_size())
|
||||
metadata_image.set_pixel(i, 2, Color(origin_fixed.x, origin_fixed.y, 0.0, 0.0))
|
||||
|
||||
|
||||
## Include a child group in the blending process.
|
||||
## If the child group is set to pass through mode, loop through its children
|
||||
## and include them as separate images, instead of blending them all together.
|
||||
## Gets called recursively if the child group has children groups of its own,
|
||||
## and they are also set to pass through mode.
|
||||
func _blend_child_group(
|
||||
image: Image,
|
||||
layer: BaseLayer,
|
||||
frame: Frame,
|
||||
textures: Array[Image],
|
||||
metadata_image: Image,
|
||||
i: int,
|
||||
origin: Vector2i,
|
||||
apply_effects: bool
|
||||
) -> int:
|
||||
var new_i := i
|
||||
var blend_rect := Rect2i(Vector2i.ZERO, project.size)
|
||||
var cel := frame.cels[layer.index]
|
||||
if layer.blend_mode == BlendModes.PASS_THROUGH:
|
||||
var children := layer.get_children(false)
|
||||
for j in children.size():
|
||||
var child := children[j]
|
||||
if child is GroupLayer:
|
||||
new_i = _blend_child_group(
|
||||
image, child, frame, textures, metadata_image, i + j, origin, apply_effects
|
||||
)
|
||||
else:
|
||||
new_i += j
|
||||
metadata_image.crop(metadata_image.get_width() + 1, metadata_image.get_height())
|
||||
_include_child_in_blending(
|
||||
image, child, frame, textures, metadata_image, new_i, origin, apply_effects
|
||||
)
|
||||
else:
|
||||
var blended_children := (layer as GroupLayer).blend_children(frame, origin)
|
||||
if DisplayServer.get_name() == "headless":
|
||||
image.blend_rect(blended_children, blend_rect, origin)
|
||||
else:
|
||||
textures.append(blended_children)
|
||||
DrawingAlgos.set_layer_metadata_image(layer, cel, metadata_image, i)
|
||||
return new_i
|
||||
|
||||
|
||||
# Overridden Methods:
|
||||
|
||||
|
||||
|
|
|
@ -144,6 +144,20 @@ float border_trim(vec4 color, vec2 uv) {
|
|||
}
|
||||
|
||||
|
||||
int find_previous_opaque_layer(int index, vec2 metadata_size_float) {
|
||||
for (int i = index - 1; i > 0; i--) {
|
||||
float layer_index = float(i) / metadata_size_float.x;
|
||||
float clipping_mask = texture(metadata, vec2(layer_index, 3.0 / metadata_size_float.y)).r;
|
||||
// If the red value is ~0.2, it means that it should be skipped.
|
||||
// Otherwise, return the index.
|
||||
if (clipping_mask > 0.25 || clipping_mask < 0.15) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void fragment() {
|
||||
ivec2 metadata_size = textureSize(metadata, 0) - 1;
|
||||
vec2 metadata_size_float = vec2(metadata_size);
|
||||
|
@ -173,17 +187,18 @@ void fragment() {
|
|||
current_origin.y = -current_origin.y;
|
||||
}
|
||||
// get origin of previous layer (used for clipping masks to work correctly)
|
||||
vec2 prev_origin = texture(metadata, vec2(float(i - 1), 2.0 / metadata_size_float.y)).rg;
|
||||
float clipping_mask_index = float(find_previous_opaque_layer(i, metadata_size_float));
|
||||
vec2 prev_origin = texture(metadata, vec2(clipping_mask_index, 2.0 / metadata_size_float.y)).rg;
|
||||
if (!origin_x_positive) {
|
||||
prev_origin.x = -prev_origin.x;
|
||||
prev_origin.x = -prev_origin.x;
|
||||
}
|
||||
if (!origin_y_positive) {
|
||||
prev_origin.y = -prev_origin.y;
|
||||
prev_origin.y = -prev_origin.y;
|
||||
}
|
||||
float current_opacity = texture(metadata, vec2(layer_index, 1.0 / metadata_size_float.y)).r;
|
||||
vec2 uv = UV - current_origin;
|
||||
vec4 layer_color = texture(layers, vec3(uv, float(i)));
|
||||
vec4 prev_layer_color = texture(layers, vec3(UV - prev_origin, float(i - 1)));
|
||||
vec4 prev_layer_color = texture(layers, vec3(UV - prev_origin, clipping_mask_index));
|
||||
float clipping_mask = texture(metadata, vec2(layer_index, 3.0 / metadata_size_float.y)).r;
|
||||
layer_color.a *= prev_layer_color.a * step(0.5, clipping_mask) + 1.0 * step(clipping_mask, 0.5);
|
||||
layer_color.a = border_trim(layer_color, uv);
|
||||
|
|
|
@ -117,17 +117,22 @@ func update_texture(layer_i: int, frame_i := -1, project := Global.current_proje
|
|||
if frame_i != project.current_frame:
|
||||
# Don't update if the cel is on a different frame (can happen with undo/redo)
|
||||
return
|
||||
var layer := project.layers[layer_i]
|
||||
var layer := project.layers[layer_i].get_blender_ancestor()
|
||||
var cel_image: Image
|
||||
if Global.display_layer_effects:
|
||||
cel_image = layer.display_effects(current_cel)
|
||||
if layer is GroupLayer:
|
||||
cel_image = layer.blend_children(
|
||||
project.frames[project.current_frame], Vector2i.ZERO, Global.display_layer_effects
|
||||
)
|
||||
else:
|
||||
cel_image = current_cel.get_image()
|
||||
if Global.display_layer_effects:
|
||||
cel_image = layer.display_effects(current_cel)
|
||||
else:
|
||||
cel_image = current_cel.get_image()
|
||||
if (
|
||||
cel_image.get_size()
|
||||
== Vector2i(layer_texture_array.get_width(), layer_texture_array.get_height())
|
||||
):
|
||||
layer_texture_array.update_layer(cel_image, project.ordered_layers[layer_i])
|
||||
layer_texture_array.update_layer(cel_image, project.ordered_layers[layer.index])
|
||||
|
||||
|
||||
func update_selected_cels_textures(project := Global.current_project) -> void:
|
||||
|
@ -141,7 +146,6 @@ func update_selected_cels_textures(project := Global.current_project) -> void:
|
|||
|
||||
func draw_layers(force_recreate := false) -> void:
|
||||
var project := Global.current_project
|
||||
var current_cels := project.frames[project.current_frame].cels
|
||||
var recreate_texture_array := (
|
||||
layer_texture_array.get_layers() != project.layers.size()
|
||||
or layer_texture_array.get_width() != project.size.x
|
||||
|
@ -157,16 +161,11 @@ func draw_layers(force_recreate := false) -> void:
|
|||
layer_metadata_image = Image.create(project.layers.size(), 4, false, Image.FORMAT_RG8)
|
||||
# Draw current frame layers
|
||||
for i in project.layers.size():
|
||||
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()
|
||||
var ordered_index := project.ordered_layers[layer.index]
|
||||
var cel_image := Image.new()
|
||||
_update_texture_array_layer(project, layer, cel_image, false)
|
||||
textures[ordered_index] = cel_image
|
||||
DrawingAlgos.set_layer_metadata_image(layer, cel, layer_metadata_image, ordered_index)
|
||||
# Store the origin
|
||||
if [project.current_frame, i] in project.selected_cels:
|
||||
var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size())
|
||||
|
@ -185,18 +184,14 @@ func draw_layers(force_recreate := false) -> void:
|
|||
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)
|
||||
DrawingAlgos.set_layer_metadata_image(
|
||||
layer, cel, layer_metadata_image, ordered_index
|
||||
)
|
||||
var ordered_index := project.ordered_layers[layer.index]
|
||||
var cel_image := Image.new()
|
||||
_update_texture_array_layer(project, layer, cel_image, true)
|
||||
var parent_layer := layer.get_blender_ancestor()
|
||||
if layer != parent_layer:
|
||||
# True when the layer has parents. In that case, update its top-most parent.
|
||||
_update_texture_array_layer(project, parent_layer, Image.new(), true)
|
||||
# Update the origin
|
||||
var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size())
|
||||
layer_metadata_image.set_pixel(
|
||||
|
@ -209,6 +204,32 @@ func draw_layers(force_recreate := false) -> void:
|
|||
update_all_layers = false
|
||||
|
||||
|
||||
func _update_texture_array_layer(
|
||||
project: Project, layer: BaseLayer, cel_image: Image, update_layer: bool
|
||||
) -> void:
|
||||
var ordered_index := project.ordered_layers[layer.index]
|
||||
var cel := project.frames[project.current_frame].cels[layer.index]
|
||||
var include := true
|
||||
if layer is GroupLayer and layer.blend_mode != BaseLayer.BlendModes.PASS_THROUGH:
|
||||
cel_image.copy_from(
|
||||
layer.blend_children(
|
||||
project.frames[project.current_frame],
|
||||
move_preview_location,
|
||||
Global.display_layer_effects
|
||||
)
|
||||
)
|
||||
else:
|
||||
if Global.display_layer_effects:
|
||||
cel_image.copy_from(layer.display_effects(cel))
|
||||
else:
|
||||
cel_image.copy_from(cel.get_image())
|
||||
if layer.is_blended_by_ancestor():
|
||||
include = false
|
||||
if update_layer:
|
||||
layer_texture_array.update_layer(cel_image, ordered_index)
|
||||
DrawingAlgos.set_layer_metadata_image(layer, cel, layer_metadata_image, ordered_index, include)
|
||||
|
||||
|
||||
func refresh_onion() -> void:
|
||||
onion_past.queue_redraw()
|
||||
onion_future.queue_redraw()
|
||||
|
|
|
@ -83,14 +83,17 @@ func _draw_layers() -> void:
|
|||
# Draw current frame layers
|
||||
for i in project.ordered_layers:
|
||||
var cel := current_cels[i]
|
||||
if current_cels[i] is GroupCel:
|
||||
continue
|
||||
var layer := project.layers[i]
|
||||
var cel_image: Image
|
||||
if Global.display_layer_effects:
|
||||
cel_image = layer.display_effects(cel)
|
||||
if layer is GroupLayer and layer.blend_mode != BaseLayer.BlendModes.PASS_THROUGH:
|
||||
cel_image = layer.blend_children(
|
||||
current_frame, Vector2i.ZERO, Global.display_layer_effects
|
||||
)
|
||||
else:
|
||||
cel_image = cel.get_image()
|
||||
if Global.display_layer_effects:
|
||||
cel_image = layer.display_effects(cel)
|
||||
else:
|
||||
cel_image = cel.get_image()
|
||||
textures.append(cel_image)
|
||||
DrawingAlgos.set_layer_metadata_image(layer, cel, metadata_image, i)
|
||||
var texture_array := Texture2DArray.new()
|
||||
|
|
|
@ -66,7 +66,7 @@ func recalculate_preview(params: Dictionary) -> void:
|
|||
|
||||
|
||||
## Altered version of blend_layers() located in DrawingAlgos.gd
|
||||
## This function is REQUIRED in order for offset effect to work correctly with cliping masks
|
||||
## This function is REQUIRED in order for offset effect to work correctly with clipping masks
|
||||
func blend_layers(
|
||||
image: Image,
|
||||
frame: Frame,
|
||||
|
@ -101,8 +101,14 @@ func blend_layers(
|
|||
if not layer_is_selected:
|
||||
include = false
|
||||
var cel := frame.cels[ordered_index]
|
||||
var cel_image := layer.display_effects(cel)
|
||||
if include: ## apply offset effect to it
|
||||
var cel_image: Image
|
||||
if layer is GroupLayer and layer.blend_mode != BaseLayer.BlendModes.PASS_THROUGH:
|
||||
cel_image = (layer as GroupLayer).blend_children(frame)
|
||||
else:
|
||||
cel_image = layer.display_effects(cel)
|
||||
if layer.is_blended_by_ancestor() and not only_selected_cels and not only_selected_layers:
|
||||
include = false
|
||||
if include: # Apply offset effect to it
|
||||
gen.generate_image(cel_image, shader, effect_params, project.size)
|
||||
textures.append(cel_image)
|
||||
DrawingAlgos.set_layer_metadata_image(layer, cel, metadata_image, ordered_index, include)
|
||||
|
|
|
@ -65,34 +65,7 @@ func _ready() -> void:
|
|||
frame_scroll_bar.value_changed.connect(_frame_scroll_changed)
|
||||
Global.animation_timer.wait_time = 1 / Global.current_project.fps
|
||||
fps_spinbox.value = Global.current_project.fps
|
||||
|
||||
# Fill the blend modes OptionButton with items
|
||||
blend_modes_button.add_item("Normal", BaseLayer.BlendModes.NORMAL)
|
||||
blend_modes_button.add_separator("Darken")
|
||||
blend_modes_button.add_item("Darken", BaseLayer.BlendModes.DARKEN)
|
||||
blend_modes_button.add_item("Multiply", BaseLayer.BlendModes.MULTIPLY)
|
||||
blend_modes_button.add_item("Color burn", BaseLayer.BlendModes.COLOR_BURN)
|
||||
blend_modes_button.add_item("Linear burn", BaseLayer.BlendModes.LINEAR_BURN)
|
||||
blend_modes_button.add_separator("Lighten")
|
||||
blend_modes_button.add_item("Lighten", BaseLayer.BlendModes.LIGHTEN)
|
||||
blend_modes_button.add_item("Screen", BaseLayer.BlendModes.SCREEN)
|
||||
blend_modes_button.add_item("Color dodge", BaseLayer.BlendModes.COLOR_DODGE)
|
||||
blend_modes_button.add_item("Add", BaseLayer.BlendModes.ADD)
|
||||
blend_modes_button.add_separator("Contrast")
|
||||
blend_modes_button.add_item("Overlay", BaseLayer.BlendModes.OVERLAY)
|
||||
blend_modes_button.add_item("Soft light", BaseLayer.BlendModes.SOFT_LIGHT)
|
||||
blend_modes_button.add_item("Hard light", BaseLayer.BlendModes.HARD_LIGHT)
|
||||
blend_modes_button.add_separator("Inversion")
|
||||
blend_modes_button.add_item("Difference", BaseLayer.BlendModes.DIFFERENCE)
|
||||
blend_modes_button.add_item("Exclusion", BaseLayer.BlendModes.EXCLUSION)
|
||||
blend_modes_button.add_item("Subtract", BaseLayer.BlendModes.SUBTRACT)
|
||||
blend_modes_button.add_item("Divide", BaseLayer.BlendModes.DIVIDE)
|
||||
blend_modes_button.add_separator("Component")
|
||||
blend_modes_button.add_item("Hue", BaseLayer.BlendModes.HUE)
|
||||
blend_modes_button.add_item("Saturation", BaseLayer.BlendModes.SATURATION)
|
||||
blend_modes_button.add_item("Color", BaseLayer.BlendModes.COLOR)
|
||||
blend_modes_button.add_item("Luminosity", BaseLayer.BlendModes.LUMINOSITY)
|
||||
|
||||
_fill_blend_modes_option_button()
|
||||
# Config loading
|
||||
layer_frame_h_split.split_offset = Global.config_cache.get_value("timeline", "layer_size", 0)
|
||||
cel_size = Global.config_cache.get_value("timeline", "cel_size", cel_size) # Call setter
|
||||
|
@ -220,6 +193,48 @@ func _cel_size_changed(value: int) -> void:
|
|||
tag_c.update_position_and_size()
|
||||
|
||||
|
||||
## Fill the blend modes OptionButton with items
|
||||
func _fill_blend_modes_option_button() -> void:
|
||||
blend_modes_button.clear()
|
||||
var selected_layers_are_groups := true
|
||||
if Global.current_project.layers.size() == 0:
|
||||
selected_layers_are_groups = false
|
||||
else:
|
||||
for idx_pair in Global.current_project.selected_cels:
|
||||
var layer := Global.current_project.layers[idx_pair[1]]
|
||||
if not layer is GroupLayer:
|
||||
selected_layers_are_groups = false
|
||||
break
|
||||
if selected_layers_are_groups:
|
||||
# Special blend mode that appears only when group layers are selected
|
||||
blend_modes_button.add_item("Pass through", BaseLayer.BlendModes.PASS_THROUGH)
|
||||
blend_modes_button.add_item("Normal", BaseLayer.BlendModes.NORMAL)
|
||||
blend_modes_button.add_separator("Darken")
|
||||
blend_modes_button.add_item("Darken", BaseLayer.BlendModes.DARKEN)
|
||||
blend_modes_button.add_item("Multiply", BaseLayer.BlendModes.MULTIPLY)
|
||||
blend_modes_button.add_item("Color burn", BaseLayer.BlendModes.COLOR_BURN)
|
||||
blend_modes_button.add_item("Linear burn", BaseLayer.BlendModes.LINEAR_BURN)
|
||||
blend_modes_button.add_separator("Lighten")
|
||||
blend_modes_button.add_item("Lighten", BaseLayer.BlendModes.LIGHTEN)
|
||||
blend_modes_button.add_item("Screen", BaseLayer.BlendModes.SCREEN)
|
||||
blend_modes_button.add_item("Color dodge", BaseLayer.BlendModes.COLOR_DODGE)
|
||||
blend_modes_button.add_item("Add", BaseLayer.BlendModes.ADD)
|
||||
blend_modes_button.add_separator("Contrast")
|
||||
blend_modes_button.add_item("Overlay", BaseLayer.BlendModes.OVERLAY)
|
||||
blend_modes_button.add_item("Soft light", BaseLayer.BlendModes.SOFT_LIGHT)
|
||||
blend_modes_button.add_item("Hard light", BaseLayer.BlendModes.HARD_LIGHT)
|
||||
blend_modes_button.add_separator("Inversion")
|
||||
blend_modes_button.add_item("Difference", BaseLayer.BlendModes.DIFFERENCE)
|
||||
blend_modes_button.add_item("Exclusion", BaseLayer.BlendModes.EXCLUSION)
|
||||
blend_modes_button.add_item("Subtract", BaseLayer.BlendModes.SUBTRACT)
|
||||
blend_modes_button.add_item("Divide", BaseLayer.BlendModes.DIVIDE)
|
||||
blend_modes_button.add_separator("Component")
|
||||
blend_modes_button.add_item("Hue", BaseLayer.BlendModes.HUE)
|
||||
blend_modes_button.add_item("Saturation", BaseLayer.BlendModes.SATURATION)
|
||||
blend_modes_button.add_item("Color", BaseLayer.BlendModes.COLOR)
|
||||
blend_modes_button.add_item("Luminosity", BaseLayer.BlendModes.LUMINOSITY)
|
||||
|
||||
|
||||
func _on_blend_modes_item_selected(index: int) -> void:
|
||||
var project := Global.current_project
|
||||
var current_mode := blend_modes_button.get_item_id(index)
|
||||
|
@ -231,13 +246,18 @@ func _on_blend_modes_item_selected(index: int) -> void:
|
|||
project.undo_redo.add_undo_property(layer, "blend_mode", previous_mode)
|
||||
project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
|
||||
project.undo_redo.add_do_method(_update_layer_ui)
|
||||
project.undo_redo.add_do_method(Global.canvas.draw_layers)
|
||||
project.undo_redo.add_do_method(_update_layers)
|
||||
project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
|
||||
project.undo_redo.add_undo_method(_update_layer_ui)
|
||||
project.undo_redo.add_undo_method(Global.canvas.draw_layers)
|
||||
project.undo_redo.add_undo_method(_update_layers)
|
||||
project.undo_redo.commit_action()
|
||||
|
||||
|
||||
func _update_layers() -> void:
|
||||
Global.canvas.update_all_layers = true
|
||||
Global.canvas.draw_layers()
|
||||
|
||||
|
||||
func add_frame() -> void:
|
||||
var project := Global.current_project
|
||||
var frame_add_index := project.current_frame + 1
|
||||
|
@ -1068,6 +1088,7 @@ func _on_timeline_settings_visibility_changed() -> void:
|
|||
func _cel_switched() -> void:
|
||||
_toggle_frame_buttons()
|
||||
_toggle_layer_buttons()
|
||||
_fill_blend_modes_option_button()
|
||||
# Temporarily disconnect it in order to prevent layer opacity changing
|
||||
# in the rest of the selected layers, if there are any.
|
||||
opacity_slider.value_changed.disconnect(_on_opacity_slider_value_changed)
|
||||
|
@ -1077,10 +1098,9 @@ func _cel_switched() -> void:
|
|||
|
||||
func _update_layer_ui() -> void:
|
||||
var project := Global.current_project
|
||||
opacity_slider.value = project.layers[project.current_layer].opacity * 100
|
||||
var blend_mode_index := blend_modes_button.get_item_index(
|
||||
project.layers[project.current_layer].blend_mode
|
||||
)
|
||||
var layer := project.layers[project.current_layer]
|
||||
opacity_slider.value = layer.opacity * 100
|
||||
var blend_mode_index := blend_modes_button.get_item_index(layer.blend_mode)
|
||||
blend_modes_button.selected = blend_mode_index
|
||||
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ func _on_opacity_slider_value_changed(value: float) -> void:
|
|||
for cel_index in cel_indices:
|
||||
var cel := Global.current_project.frames[cel_index[0]].cels[cel_index[1]]
|
||||
cel.opacity = value / 100.0
|
||||
Global.canvas.update_all_layers = true
|
||||
Global.canvas.queue_redraw()
|
||||
|
||||
|
||||
|
|
|
@ -145,9 +145,7 @@ func _on_main_button_gui_input(event: InputEvent) -> void:
|
|||
line_edit.grab_focus()
|
||||
|
||||
elif event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
|
||||
var layer := Global.current_project.layers[layer_index]
|
||||
if not layer is GroupLayer:
|
||||
popup_menu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2.ONE))
|
||||
popup_menu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2.ONE))
|
||||
|
||||
|
||||
func _on_layer_name_line_edit_focus_exited() -> void:
|
||||
|
@ -222,6 +220,7 @@ func _on_popup_menu_id_pressed(id: int) -> void:
|
|||
layer.clipping_mask = not layer.clipping_mask
|
||||
popup_menu.set_item_checked(id, layer.clipping_mask)
|
||||
clipping_mask_icon.visible = layer.clipping_mask
|
||||
Global.canvas.update_all_layers = true
|
||||
Global.canvas.draw_layers()
|
||||
|
||||
|
||||
|
|
|
@ -10,8 +10,34 @@ var layer_indices: PackedInt32Array
|
|||
@onready var user_data_text_edit := $GridContainer/UserDataTextEdit as TextEdit
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# Fill the blend modes OptionButton with items
|
||||
func _on_visibility_changed() -> void:
|
||||
if layer_indices.size() == 0:
|
||||
return
|
||||
Global.dialog_open(visible)
|
||||
var first_layer := Global.current_project.layers[layer_indices[0]]
|
||||
if visible:
|
||||
_fill_blend_modes_option_button()
|
||||
name_line_edit.text = first_layer.name
|
||||
opacity_slider.value = first_layer.opacity * 100.0
|
||||
var blend_mode_index := blend_modes_button.get_item_index(first_layer.blend_mode)
|
||||
blend_modes_button.selected = blend_mode_index
|
||||
user_data_text_edit.text = first_layer.user_data
|
||||
else:
|
||||
layer_indices = []
|
||||
|
||||
|
||||
## Fill the blend modes OptionButton with items
|
||||
func _fill_blend_modes_option_button() -> void:
|
||||
blend_modes_button.clear()
|
||||
var selected_layers_are_groups := true
|
||||
for layer_index in layer_indices:
|
||||
var layer := Global.current_project.layers[layer_index]
|
||||
if not layer is GroupLayer:
|
||||
selected_layers_are_groups = false
|
||||
break
|
||||
if selected_layers_are_groups:
|
||||
# Special blend mode that appears only when group layers are selected
|
||||
blend_modes_button.add_item("Pass through", BaseLayer.BlendModes.PASS_THROUGH)
|
||||
blend_modes_button.add_item("Normal", BaseLayer.BlendModes.NORMAL)
|
||||
blend_modes_button.add_item("Darken", BaseLayer.BlendModes.DARKEN)
|
||||
blend_modes_button.add_item("Multiply", BaseLayer.BlendModes.MULTIPLY)
|
||||
|
@ -34,20 +60,6 @@ func _ready() -> void:
|
|||
blend_modes_button.add_item("Luminosity", BaseLayer.BlendModes.LUMINOSITY)
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if layer_indices.size() == 0:
|
||||
return
|
||||
Global.dialog_open(visible)
|
||||
var first_layer := Global.current_project.layers[layer_indices[0]]
|
||||
if visible:
|
||||
name_line_edit.text = first_layer.name
|
||||
opacity_slider.value = first_layer.opacity * 100.0
|
||||
blend_modes_button.selected = first_layer.blend_mode
|
||||
user_data_text_edit.text = first_layer.user_data
|
||||
else:
|
||||
layer_indices = []
|
||||
|
||||
|
||||
func _on_name_line_edit_text_changed(new_text: String) -> void:
|
||||
if layer_indices.size() == 0:
|
||||
return
|
||||
|
@ -63,18 +75,21 @@ func _on_opacity_slider_value_changed(value: float) -> void:
|
|||
var layer := Global.current_project.layers[layer_index]
|
||||
layer.opacity = value / 100.0
|
||||
_emit_layer_property_signal()
|
||||
Global.canvas.update_all_layers = true
|
||||
Global.canvas.queue_redraw()
|
||||
|
||||
|
||||
func _on_blend_mode_option_button_item_selected(index: BaseLayer.BlendModes) -> void:
|
||||
if layer_indices.size() == 0:
|
||||
return
|
||||
Global.canvas.update_all_layers = true
|
||||
var project := Global.current_project
|
||||
var current_mode := blend_modes_button.get_item_id(index)
|
||||
project.undo_redo.create_action("Set Blend Mode")
|
||||
for layer_index in layer_indices:
|
||||
var layer := project.layers[layer_index]
|
||||
var previous_mode := layer.blend_mode
|
||||
project.undo_redo.add_do_property(layer, "blend_mode", index)
|
||||
project.undo_redo.add_do_property(layer, "blend_mode", current_mode)
|
||||
project.undo_redo.add_undo_property(layer, "blend_mode", previous_mode)
|
||||
project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
|
||||
project.undo_redo.add_do_method(Global.canvas.draw_layers)
|
||||
|
|
Loading…
Reference in a new issue