mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-19 01:29:49 +00:00
Layer blend modes (#911)
* Preview blend modes No support for exporting and layer merging yet. Also need to fix the move tool preview. * Preview blend modes on tile mode * Raise layer limit to 1024 * Export images with layer blending modes * Save blend modes in pxo files * Merge layers with blending modes * Fix crash when adding a new layer * Preview blending in the other canvases * Update DrawingAlgos.gd * Move tool preview * Re-arrange blend menu and add lighten, darken, linear burn and exclusion * Add divide blend mode * Add hue, saturation, color & luminosity blend modes * Undo/redo when changing blend modes
This commit is contained in:
parent
6247ab2252
commit
8de9697be0
|
@ -3,10 +3,73 @@ extends Node
|
||||||
enum GradientDirection { TOP, BOTTOM, LEFT, RIGHT }
|
enum GradientDirection { TOP, BOTTOM, LEFT, RIGHT }
|
||||||
## Continuation from Image.Interpolation
|
## Continuation from Image.Interpolation
|
||||||
enum Interpolation { SCALE3X = 5, CLEANEDGE = 6, OMNISCALE = 7 }
|
enum Interpolation { SCALE3X = 5, CLEANEDGE = 6, OMNISCALE = 7 }
|
||||||
|
var blend_layers_shader := preload("res://src/Shaders/BlendLayers.gdshader")
|
||||||
var clean_edge_shader: Shader
|
var clean_edge_shader: Shader
|
||||||
var omniscale_shader := preload("res://src/Shaders/Rotation/OmniScale.gdshader")
|
var omniscale_shader := preload("res://src/Shaders/Rotation/OmniScale.gdshader")
|
||||||
|
|
||||||
|
|
||||||
|
## Blends canvas layers into passed image starting from the origin position
|
||||||
|
func blend_all_layers(
|
||||||
|
image: Image, frame: Frame, origin := Vector2i.ZERO, project := Global.current_project
|
||||||
|
) -> void:
|
||||||
|
var current_cels := frame.cels
|
||||||
|
var textures: Array[Image] = []
|
||||||
|
var opacities := PackedFloat32Array()
|
||||||
|
var blend_modes := PackedInt32Array()
|
||||||
|
|
||||||
|
for i in Global.current_project.layers.size():
|
||||||
|
if current_cels[i] is GroupCel:
|
||||||
|
continue
|
||||||
|
if not Global.current_project.layers[i].is_visible_in_hierarchy():
|
||||||
|
continue
|
||||||
|
textures.append(current_cels[i].get_image())
|
||||||
|
opacities.append(current_cels[i].opacity)
|
||||||
|
blend_modes.append(Global.current_project.layers[i].blend_mode)
|
||||||
|
var texture_array := Texture2DArray.new()
|
||||||
|
texture_array.create_from_images(textures)
|
||||||
|
var params := {
|
||||||
|
"layers": texture_array,
|
||||||
|
"opacities": opacities,
|
||||||
|
"blend_modes": blend_modes,
|
||||||
|
}
|
||||||
|
var blended := Image.create(project.size.x, project.size.y, false, image.get_format())
|
||||||
|
var gen := ShaderImageEffect.new()
|
||||||
|
gen.generate_image(blended, blend_layers_shader, params, project.size)
|
||||||
|
image.blend_rect(blended, Rect2i(Vector2i.ZERO, project.size), origin)
|
||||||
|
|
||||||
|
|
||||||
|
## Blends selected cels of the given frame into passed image starting from the origin position
|
||||||
|
func blend_selected_cels(
|
||||||
|
image: Image, frame: Frame, origin := Vector2i.ZERO, project := Global.current_project
|
||||||
|
) -> void:
|
||||||
|
var textures: Array[Image] = []
|
||||||
|
var opacities := PackedFloat32Array()
|
||||||
|
var blend_modes := PackedInt32Array()
|
||||||
|
for cel_ind in frame.cels.size():
|
||||||
|
var test_array := [project.current_frame, cel_ind]
|
||||||
|
if not test_array in project.selected_cels:
|
||||||
|
continue
|
||||||
|
if frame.cels[cel_ind] is GroupCel:
|
||||||
|
continue
|
||||||
|
if not project.layers[cel_ind].is_visible_in_hierarchy():
|
||||||
|
continue
|
||||||
|
var cel := frame.cels[cel_ind]
|
||||||
|
textures.append(cel.get_image())
|
||||||
|
opacities.append(cel.opacity)
|
||||||
|
blend_modes.append(Global.current_project.layers[cel_ind].blend_mode)
|
||||||
|
var texture_array := Texture2DArray.new()
|
||||||
|
texture_array.create_from_images(textures)
|
||||||
|
var params := {
|
||||||
|
"layers": texture_array,
|
||||||
|
"opacities": opacities,
|
||||||
|
"blend_modes": blend_modes,
|
||||||
|
}
|
||||||
|
var blended := Image.create(project.size.x, project.size.y, false, image.get_format())
|
||||||
|
var gen := ShaderImageEffect.new()
|
||||||
|
gen.generate_image(blended, blend_layers_shader, params, project.size)
|
||||||
|
image.blend_rect(blended, Rect2i(Vector2i.ZERO, project.size), origin)
|
||||||
|
|
||||||
|
|
||||||
## Algorithm based on http://members.chello.at/easyfilter/bresenham.html
|
## Algorithm based on http://members.chello.at/easyfilter/bresenham.html
|
||||||
func get_ellipse_points(pos: Vector2i, size: Vector2i) -> Array[Vector2i]:
|
func get_ellipse_points(pos: Vector2i, size: Vector2i) -> Array[Vector2i]:
|
||||||
var array: Array[Vector2i] = []
|
var array: Array[Vector2i] = []
|
||||||
|
|
|
@ -484,9 +484,9 @@ func _blend_layers(
|
||||||
image: Image, frame: Frame, origin := Vector2i.ZERO, project := Global.current_project
|
image: Image, frame: Frame, origin := Vector2i.ZERO, project := Global.current_project
|
||||||
) -> void:
|
) -> void:
|
||||||
if export_layers == 0:
|
if export_layers == 0:
|
||||||
blend_all_layers(image, frame, origin, project)
|
DrawingAlgos.blend_all_layers(image, frame, origin, project)
|
||||||
elif export_layers == 1:
|
elif export_layers == 1:
|
||||||
blend_selected_cels(image, frame, origin, project)
|
DrawingAlgos.blend_selected_cels(image, frame, origin, project)
|
||||||
else:
|
else:
|
||||||
var layer := project.layers[export_layers - 2]
|
var layer := project.layers[export_layers - 2]
|
||||||
var layer_image := Image.new()
|
var layer_image := Image.new()
|
||||||
|
@ -497,57 +497,5 @@ func _blend_layers(
|
||||||
image.blend_rect(layer_image, Rect2i(Vector2i.ZERO, project.size), origin)
|
image.blend_rect(layer_image, Rect2i(Vector2i.ZERO, project.size), origin)
|
||||||
|
|
||||||
|
|
||||||
## Blends canvas layers into passed image starting from the origin position
|
|
||||||
func blend_all_layers(
|
|
||||||
image: Image, frame: Frame, origin := Vector2i.ZERO, project := Global.current_project
|
|
||||||
) -> void:
|
|
||||||
var layer_i := 0
|
|
||||||
for cel in frame.cels:
|
|
||||||
if not project.layers[layer_i].is_visible_in_hierarchy():
|
|
||||||
layer_i += 1
|
|
||||||
continue
|
|
||||||
if cel is GroupCel:
|
|
||||||
layer_i += 1
|
|
||||||
continue
|
|
||||||
var cel_image := Image.new()
|
|
||||||
cel_image.copy_from(cel.get_image())
|
|
||||||
if cel.opacity < 1: # If we have cel transparency
|
|
||||||
for xx in cel_image.get_size().x:
|
|
||||||
for yy in cel_image.get_size().y:
|
|
||||||
var pixel_color := cel_image.get_pixel(xx, yy)
|
|
||||||
var alpha: float = pixel_color.a * cel.opacity
|
|
||||||
cel_image.set_pixel(
|
|
||||||
xx, yy, Color(pixel_color.r, pixel_color.g, pixel_color.b, alpha)
|
|
||||||
)
|
|
||||||
image.blend_rect(cel_image, Rect2i(Vector2i.ZERO, project.size), origin)
|
|
||||||
layer_i += 1
|
|
||||||
|
|
||||||
|
|
||||||
## Blends selected cels of the given frame into passed image starting from the origin position
|
|
||||||
func blend_selected_cels(
|
|
||||||
image: Image, frame: Frame, origin := Vector2i.ZERO, project := Global.current_project
|
|
||||||
) -> void:
|
|
||||||
for cel_ind in frame.cels.size():
|
|
||||||
var test_array := [project.current_frame, cel_ind]
|
|
||||||
if not test_array in project.selected_cels:
|
|
||||||
continue
|
|
||||||
if frame.cels[cel_ind] is GroupCel:
|
|
||||||
continue
|
|
||||||
if not project.layers[cel_ind].is_visible_in_hierarchy():
|
|
||||||
continue
|
|
||||||
var cel: BaseCel = frame.cels[cel_ind]
|
|
||||||
var cel_image := Image.new()
|
|
||||||
cel_image.copy_from(cel.get_image())
|
|
||||||
if cel.opacity < 1: # If we have cel transparency
|
|
||||||
for xx in cel_image.get_size().x:
|
|
||||||
for yy in cel_image.get_size().y:
|
|
||||||
var pixel_color := cel_image.get_pixel(xx, yy)
|
|
||||||
var alpha: float = pixel_color.a * cel.opacity
|
|
||||||
cel_image.set_pixel(
|
|
||||||
xx, yy, Color(pixel_color.r, pixel_color.g, pixel_color.b, alpha)
|
|
||||||
)
|
|
||||||
image.blend_rect(cel_image, Rect2i(Vector2i.ZERO, project.size), origin)
|
|
||||||
|
|
||||||
|
|
||||||
func frames_divided_by_spritesheet_lines() -> int:
|
func frames_divided_by_spritesheet_lines() -> int:
|
||||||
return ceili(number_of_frames / float(lines_count))
|
return ceili(number_of_frames / float(lines_count))
|
||||||
|
|
|
@ -2,10 +2,34 @@ class_name BaseLayer
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
## Base class for layer properties. Different layer types extend from this class.
|
## Base class for layer properties. Different layer types extend from this class.
|
||||||
|
|
||||||
|
enum BlendModes {
|
||||||
|
NORMAL,
|
||||||
|
DARKEN,
|
||||||
|
MULTIPLY,
|
||||||
|
COLOR_BURN,
|
||||||
|
LINEAR_BURN,
|
||||||
|
LIGHTEN,
|
||||||
|
SCREEN,
|
||||||
|
COLOR_DODGE,
|
||||||
|
ADD,
|
||||||
|
OVERLAY,
|
||||||
|
SOFT_LIGHT,
|
||||||
|
HARD_LIGHT,
|
||||||
|
DIFFERENCE,
|
||||||
|
EXCLUSION,
|
||||||
|
SUBTRACT,
|
||||||
|
DIVIDE,
|
||||||
|
HUE,
|
||||||
|
SATURATION,
|
||||||
|
COLOR,
|
||||||
|
LUMINOSITY
|
||||||
|
}
|
||||||
|
|
||||||
var name := ""
|
var name := ""
|
||||||
var project: Project
|
var project: Project
|
||||||
var index: int
|
var index: int
|
||||||
var parent: BaseLayer
|
var parent: BaseLayer
|
||||||
|
var blend_mode := BlendModes.NORMAL
|
||||||
var visible := true
|
var visible := true
|
||||||
var locked := false
|
var locked := false
|
||||||
var new_cels_linked := false
|
var new_cels_linked := false
|
||||||
|
@ -130,6 +154,7 @@ func serialize() -> Dictionary:
|
||||||
"name": name,
|
"name": name,
|
||||||
"visible": visible,
|
"visible": visible,
|
||||||
"locked": locked,
|
"locked": locked,
|
||||||
|
"blend_mode": blend_mode,
|
||||||
"parent": parent.index if is_instance_valid(parent) else -1
|
"parent": parent.index if is_instance_valid(parent) else -1
|
||||||
}
|
}
|
||||||
if not cel_link_sets.is_empty():
|
if not cel_link_sets.is_empty():
|
||||||
|
@ -148,6 +173,8 @@ func deserialize(dict: Dictionary) -> void:
|
||||||
name = dict.name
|
name = dict.name
|
||||||
visible = dict.visible
|
visible = dict.visible
|
||||||
locked = dict.locked
|
locked = dict.locked
|
||||||
|
if dict.has("blend_mode"):
|
||||||
|
blend_mode = dict.blend_mode
|
||||||
if dict.get("parent", -1) != -1:
|
if dict.get("parent", -1) != -1:
|
||||||
parent = project.layers[dict.parent]
|
parent = project.layers[dict.parent]
|
||||||
if dict.has("linked_cels") and not dict["linked_cels"].is_empty(): # Backwards compatibility
|
if dict.has("linked_cels") and not dict["linked_cels"].is_empty(): # Backwards compatibility
|
||||||
|
|
|
@ -204,10 +204,10 @@ func set_and_update_preview_image(frame_idx: int) -> void:
|
||||||
var frame := Global.current_project.frames[frame_idx]
|
var frame := Global.current_project.frames[frame_idx]
|
||||||
selected_cels.resize(Global.current_project.size.x, Global.current_project.size.y)
|
selected_cels.resize(Global.current_project.size.x, Global.current_project.size.y)
|
||||||
selected_cels.fill(Color(0, 0, 0, 0))
|
selected_cels.fill(Color(0, 0, 0, 0))
|
||||||
Export.blend_selected_cels(selected_cels, frame)
|
DrawingAlgos.blend_selected_cels(selected_cels, frame)
|
||||||
current_frame.resize(Global.current_project.size.x, Global.current_project.size.y)
|
current_frame.resize(Global.current_project.size.x, Global.current_project.size.y)
|
||||||
current_frame.fill(Color(0, 0, 0, 0))
|
current_frame.fill(Color(0, 0, 0, 0))
|
||||||
Export.blend_all_layers(current_frame, frame)
|
DrawingAlgos.blend_all_layers(current_frame, frame)
|
||||||
update_preview()
|
update_preview()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -522,8 +522,9 @@ func change_cel(new_frame: int, new_layer := -1) -> void:
|
||||||
toggle_layer_buttons()
|
toggle_layer_buttons()
|
||||||
|
|
||||||
if current_frame < frames.size(): # Set opacity slider
|
if current_frame < frames.size(): # Set opacity slider
|
||||||
var cel_opacity: float = frames[current_frame].cels[current_layer].opacity
|
var cel_opacity := frames[current_frame].cels[current_layer].opacity
|
||||||
Global.layer_opacity_slider.value = cel_opacity * 100
|
Global.layer_opacity_slider.value = cel_opacity * 100
|
||||||
|
Global.animation_timeline.blend_modes_button.selected = layers[current_layer].blend_mode
|
||||||
Global.canvas.queue_redraw()
|
Global.canvas.queue_redraw()
|
||||||
Global.transparent_checker.update_rect()
|
Global.transparent_checker.update_rect()
|
||||||
Global.cel_changed.emit()
|
Global.cel_changed.emit()
|
||||||
|
|
|
@ -31,7 +31,7 @@ func generate_image(img: Image, shader: Shader, params: Dictionary, size: Vector
|
||||||
RenderingServer.canvas_item_set_material(ci_rid, mat_rid)
|
RenderingServer.canvas_item_set_material(ci_rid, mat_rid)
|
||||||
for key in params:
|
for key in params:
|
||||||
var param = params[key]
|
var param = params[key]
|
||||||
if param is Texture2D:
|
if param is Texture2D or param is Texture2DArray:
|
||||||
RenderingServer.material_set_param(mat_rid, key, [param])
|
RenderingServer.material_set_param(mat_rid, key, [param])
|
||||||
else:
|
else:
|
||||||
RenderingServer.material_set_param(mat_rid, key, param)
|
RenderingServer.material_set_param(mat_rid, key, param)
|
||||||
|
|
146
src/Shaders/BlendLayers.gdshader
Normal file
146
src/Shaders/BlendLayers.gdshader
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
shader_type canvas_item;
|
||||||
|
render_mode unshaded;
|
||||||
|
|
||||||
|
const float HCV_EPSILON = 1e-10;
|
||||||
|
const float HSL_EPSILON = 1e-10;
|
||||||
|
|
||||||
|
uniform sampler2DArray layers : filter_nearest;
|
||||||
|
uniform float[1024] opacities;
|
||||||
|
uniform int[1024] blend_modes;
|
||||||
|
uniform vec2[1024] origins;
|
||||||
|
|
||||||
|
// Conversion functions from
|
||||||
|
// https://gist.github.com/unitycoder/aaf94ddfe040ec2da93b58d3c65ab9d9
|
||||||
|
// licensed under MIT
|
||||||
|
|
||||||
|
// Converts from pure Hue to linear RGB
|
||||||
|
vec3 hue_to_rgb(float hue)
|
||||||
|
{
|
||||||
|
float R = abs(hue * 6.0 - 3.0) - 1.0;
|
||||||
|
float G = 2.0 - abs(hue * 6.0 - 2.0);
|
||||||
|
float B = 2.0 - abs(hue * 6.0 - 4.0);
|
||||||
|
return clamp(vec3(R, G, B), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts from HSL to linear RGB
|
||||||
|
vec3 hsl_to_rgb(vec3 hsl)
|
||||||
|
{
|
||||||
|
vec3 rgb = hue_to_rgb(hsl.x);
|
||||||
|
float C = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y;
|
||||||
|
return (rgb - 0.5) * C + hsl.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Converts a value from linear RGB to HCV (Hue, Chroma, Value)
|
||||||
|
vec3 rgb_to_hcv(vec3 rgb)
|
||||||
|
{
|
||||||
|
// Based on work by Sam Hocevar and Emil Persson
|
||||||
|
vec4 P = (rgb.g < rgb.b) ? vec4(rgb.bg, -1.0, 2.0/3.0) : vec4(rgb.gb, 0.0, -1.0/3.0);
|
||||||
|
vec4 Q = (rgb.r < P.x) ? vec4(P.xyw, rgb.r) : vec4(rgb.r, P.yzx);
|
||||||
|
float C = Q.x - min(Q.w, Q.y);
|
||||||
|
float H = abs((Q.w - Q.y) / (6.0 * C + HCV_EPSILON) + Q.z);
|
||||||
|
return vec3(H, C, Q.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts from linear rgb to HSL
|
||||||
|
vec3 rgb_to_hsl(vec3 rgb)
|
||||||
|
{
|
||||||
|
vec3 HCV = rgb_to_hcv(rgb);
|
||||||
|
float L = HCV.z - HCV.y * 0.5;
|
||||||
|
float S = HCV.y / (1.0 - abs(L * 2.0 - 1.0) + HSL_EPSILON);
|
||||||
|
return vec3(HCV.x, S, L);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vec4 blend(int blend_type, vec4 current_color, vec4 prev_color, float opacity) {
|
||||||
|
vec4 result;
|
||||||
|
if (current_color.a <= 0.01) {
|
||||||
|
return prev_color;
|
||||||
|
}
|
||||||
|
current_color.rgb = current_color.rgb * opacity; // Premultiply with the layer texture's alpha to prevent semi transparent pixels from being too bright (ALL LAYER TYPES!)
|
||||||
|
current_color.a = current_color.a * opacity; // Combine the layer opacity
|
||||||
|
switch(blend_type) {
|
||||||
|
case 1: // Darken
|
||||||
|
result.rgb = min(prev_color.rgb, current_color.rgb);
|
||||||
|
break;
|
||||||
|
case 2: // Multiply
|
||||||
|
result.rgb = prev_color.rgb * current_color.rgb;
|
||||||
|
break;
|
||||||
|
case 3: // Color burn
|
||||||
|
result.rgb = 1.0 - (1.0 - prev_color.rgb) / current_color.rgb;
|
||||||
|
break;
|
||||||
|
case 4: // Linear burn
|
||||||
|
result.rgb = prev_color.rgb + current_color.rgb - 1.0;
|
||||||
|
break;
|
||||||
|
case 5: // Lighten
|
||||||
|
result.rgb = max(prev_color.rgb, current_color.rgb);
|
||||||
|
break;
|
||||||
|
case 6: // Screen
|
||||||
|
result.rgb = mix(prev_color.rgb, 1.0 - (1.0 - prev_color.rgb) * (1.0 - current_color.rgb), current_color.a);
|
||||||
|
break;
|
||||||
|
case 7: // Color dodge
|
||||||
|
result.rgb = prev_color.rgb / (1.0 - current_color.rgb);
|
||||||
|
break;
|
||||||
|
case 8: // Add (linear dodge)
|
||||||
|
result.rgb = prev_color.rgb + current_color.rgb;
|
||||||
|
break;
|
||||||
|
case 9: // Overlay
|
||||||
|
result.rgb = mix(2.0 * prev_color.rgb * current_color.rgb, 1.0 - 2.0 * (1.0 - current_color.rgb) * (1.0 - prev_color.rgb), round(prev_color.rgb));
|
||||||
|
break;
|
||||||
|
case 10: // Soft light
|
||||||
|
result.rgb = mix(2.0 * prev_color.rgb * current_color.rgb + prev_color.rgb * prev_color.rgb * (1.0 - 2.0 * current_color.rgb), sqrt(prev_color.rgb) * (2.0 * current_color.rgb - 1.0) + (2.0 * prev_color.rgb) * (1.0 - current_color.rgb), round(prev_color.rgb));
|
||||||
|
break;
|
||||||
|
case 11: // Hard light
|
||||||
|
result.rgb = mix(2.0 * prev_color.rgb * current_color.rgb, 1.0 - 2.0 * (1.0 - current_color.rgb) * (1.0 - prev_color.rgb), round(current_color.rgb));
|
||||||
|
break;
|
||||||
|
case 12: // Difference
|
||||||
|
result.rgb = abs(prev_color.rgb - current_color.rgb);
|
||||||
|
break;
|
||||||
|
case 13: // Exclusion
|
||||||
|
result.rgb = prev_color.rgb + current_color.rgb - 2.0 * prev_color.rgb * current_color.rgb;
|
||||||
|
break;
|
||||||
|
case 14: // Subtract
|
||||||
|
result.rgb = prev_color.rgb - current_color.rgb;
|
||||||
|
break;
|
||||||
|
case 15: // Divide
|
||||||
|
result.rgb = prev_color.rgb / current_color.rgb;
|
||||||
|
break;
|
||||||
|
case 16: // Hue
|
||||||
|
vec3 current_hsl = rgb_to_hsl(current_color.rgb);
|
||||||
|
vec3 prev_hsl = rgb_to_hsl(prev_color.rgb);
|
||||||
|
result.rgb = hsl_to_rgb(vec3(current_hsl.r, prev_hsl.g, prev_hsl.b));
|
||||||
|
break;
|
||||||
|
case 17: // Saturation
|
||||||
|
vec3 current_hsl = rgb_to_hsl(current_color.rgb);
|
||||||
|
vec3 prev_hsl = rgb_to_hsl(prev_color.rgb);
|
||||||
|
result.rgb = hsl_to_rgb(vec3(prev_hsl.r, current_hsl.g, prev_hsl.b));
|
||||||
|
break;
|
||||||
|
case 18: // Color
|
||||||
|
vec3 current_hsl = rgb_to_hsl(current_color.rgb);
|
||||||
|
vec3 prev_hsl = rgb_to_hsl(prev_color.rgb);
|
||||||
|
result.rgb = hsl_to_rgb(vec3(current_hsl.r, current_hsl.g, prev_hsl.b));
|
||||||
|
break;
|
||||||
|
case 19: // Luminosity
|
||||||
|
vec3 current_hsl = rgb_to_hsl(current_color.rgb);
|
||||||
|
vec3 prev_hsl = rgb_to_hsl(prev_color.rgb);
|
||||||
|
result.rgb = hsl_to_rgb(vec3(prev_hsl.r, prev_hsl.g, current_hsl.b));
|
||||||
|
break;
|
||||||
|
default: // Normal (case 0)
|
||||||
|
result.rgb = prev_color.rgb * (1.0 - current_color.a) + current_color.rgb;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.a = prev_color.a * (1.0 - current_color.a) + current_color.a;
|
||||||
|
result = clamp(result, 0.0, 1.0);
|
||||||
|
return mix(current_color, result, prev_color.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
vec4 col = texture(layers, vec3(UV - origins[0], 0.0));
|
||||||
|
col.a *= opacities[0];
|
||||||
|
for(int i = 1; i < textureSize(layers, 0).z; i++) // Loops through every layer
|
||||||
|
{
|
||||||
|
col = blend(blend_modes[i], texture(layers, vec3(UV - origins[i], float(i))), col, opacities[i]);
|
||||||
|
}
|
||||||
|
COLOR = col;
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ const CURSOR_SPEED_RATE := 6.0
|
||||||
|
|
||||||
var current_pixel := Vector2.ZERO
|
var current_pixel := Vector2.ZERO
|
||||||
var sprite_changed_this_frame := false ## For optimization purposes
|
var sprite_changed_this_frame := false ## For optimization purposes
|
||||||
var move_preview_location := Vector2.ZERO
|
var move_preview_location := Vector2i.ZERO
|
||||||
|
|
||||||
@onready var currently_visible_frame := $CurrentlyVisibleFrame as SubViewport
|
@onready var currently_visible_frame := $CurrentlyVisibleFrame as SubViewport
|
||||||
@onready var current_frame_drawer := $CurrentlyVisibleFrame/CurrentFrameDrawer as Node2D
|
@onready var current_frame_drawer := $CurrentlyVisibleFrame/CurrentFrameDrawer as Node2D
|
||||||
|
@ -34,32 +34,18 @@ func _ready() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
var current_cels: Array = (
|
|
||||||
Global.current_project.frames[Global.current_project.current_frame].cels
|
|
||||||
)
|
|
||||||
var position_tmp := position
|
var position_tmp := position
|
||||||
var scale_tmp := scale
|
var scale_tmp := scale
|
||||||
if Global.mirror_view:
|
if Global.mirror_view:
|
||||||
position_tmp.x = position_tmp.x + Global.current_project.size.x
|
position_tmp.x = position_tmp.x + Global.current_project.size.x
|
||||||
scale_tmp.x = -1
|
scale_tmp.x = -1
|
||||||
draw_set_transform(position_tmp, rotation, scale_tmp)
|
draw_set_transform(position_tmp, rotation, scale_tmp)
|
||||||
# Draw current frame layers
|
# Placeholder so we can have a material here
|
||||||
for i in range(Global.current_project.layers.size()):
|
draw_texture(
|
||||||
if current_cels[i] is GroupCel:
|
Global.current_project.frames[Global.current_project.current_frame].cels[0].image_texture,
|
||||||
continue
|
Vector2.ZERO
|
||||||
var modulate_color := Color(1, 1, 1, current_cels[i].opacity)
|
)
|
||||||
if Global.current_project.layers[i].is_visible_in_hierarchy():
|
draw_layers()
|
||||||
var selected_layers = []
|
|
||||||
if move_preview_location != Vector2.ZERO:
|
|
||||||
for cel_pos in Global.current_project.selected_cels:
|
|
||||||
if cel_pos[0] == Global.current_project.current_frame:
|
|
||||||
if Global.current_project.layers[cel_pos[1]].can_layer_get_drawn():
|
|
||||||
selected_layers.append(cel_pos[1])
|
|
||||||
if i in selected_layers:
|
|
||||||
draw_texture(current_cels[i].image_texture, move_preview_location, modulate_color)
|
|
||||||
else:
|
|
||||||
draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color)
|
|
||||||
|
|
||||||
if Global.onion_skinning:
|
if Global.onion_skinning:
|
||||||
refresh_onion()
|
refresh_onion()
|
||||||
currently_visible_frame.size = Global.current_project.size
|
currently_visible_frame.size = Global.current_project.size
|
||||||
|
@ -133,6 +119,33 @@ func update_selected_cels_textures(project := Global.current_project) -> void:
|
||||||
current_cel.update_texture()
|
current_cel.update_texture()
|
||||||
|
|
||||||
|
|
||||||
|
func draw_layers() -> void:
|
||||||
|
var current_cels := Global.current_project.frames[Global.current_project.current_frame].cels
|
||||||
|
var textures: Array[Image] = []
|
||||||
|
var opacities := PackedFloat32Array()
|
||||||
|
var blend_modes := PackedInt32Array()
|
||||||
|
var origins := PackedVector2Array()
|
||||||
|
# Draw current frame layers
|
||||||
|
for i in Global.current_project.layers.size():
|
||||||
|
if current_cels[i] is GroupCel:
|
||||||
|
continue
|
||||||
|
if Global.current_project.layers[i].is_visible_in_hierarchy():
|
||||||
|
var cel_image := current_cels[i].get_image()
|
||||||
|
textures.append(cel_image)
|
||||||
|
opacities.append(current_cels[i].opacity)
|
||||||
|
if [Global.current_project.current_frame, i] in Global.current_project.selected_cels:
|
||||||
|
origins.append(Vector2(move_preview_location) / Vector2(cel_image.get_size()))
|
||||||
|
else:
|
||||||
|
origins.append(Vector2.ZERO)
|
||||||
|
blend_modes.append(Global.current_project.layers[i].blend_mode)
|
||||||
|
var texture_array := Texture2DArray.new()
|
||||||
|
texture_array.create_from_images(textures)
|
||||||
|
material.set_shader_parameter("layers", texture_array)
|
||||||
|
material.set_shader_parameter("opacities", opacities)
|
||||||
|
material.set_shader_parameter("blend_modes", blend_modes)
|
||||||
|
material.set_shader_parameter("origins", origins)
|
||||||
|
|
||||||
|
|
||||||
func refresh_onion() -> void:
|
func refresh_onion() -> void:
|
||||||
onion_past.queue_redraw()
|
onion_past.queue_redraw()
|
||||||
onion_future.queue_redraw()
|
onion_future.queue_redraw()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[gd_scene load_steps=18 format=3 uid="uid://ba24iuv55m4l3"]
|
[gd_scene load_steps=20 format=3 uid="uid://ba24iuv55m4l3"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://src/UI/Canvas/Canvas.gd" id="1"]
|
[ext_resource type="Script" path="res://src/UI/Canvas/Canvas.gd" id="1"]
|
||||||
|
[ext_resource type="Shader" path="res://src/Shaders/BlendLayers.gdshader" id="1_253dh"]
|
||||||
[ext_resource type="Script" path="res://src/UI/Canvas/Grid.gd" id="2"]
|
[ext_resource type="Script" path="res://src/UI/Canvas/Grid.gd" id="2"]
|
||||||
[ext_resource type="Script" path="res://src/UI/Canvas/Indicators.gd" id="3"]
|
[ext_resource type="Script" path="res://src/UI/Canvas/Indicators.gd" id="3"]
|
||||||
[ext_resource type="Script" path="res://src/UI/Canvas/TileMode.gd" id="4"]
|
[ext_resource type="Script" path="res://src/UI/Canvas/TileMode.gd" id="4"]
|
||||||
|
@ -15,6 +16,12 @@
|
||||||
[ext_resource type="Script" path="res://src/UI/Canvas/CropRect.gd" id="13"]
|
[ext_resource type="Script" path="res://src/UI/Canvas/CropRect.gd" id="13"]
|
||||||
[ext_resource type="Script" path="res://src/UI/Canvas/Gizmos3D.gd" id="14"]
|
[ext_resource type="Script" path="res://src/UI/Canvas/Gizmos3D.gd" id="14"]
|
||||||
|
|
||||||
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_6b0ox"]
|
||||||
|
shader = ExtResource("1_253dh")
|
||||||
|
shader_parameter/opacities = null
|
||||||
|
shader_parameter/blend_modes = null
|
||||||
|
shader_parameter/origins = null
|
||||||
|
|
||||||
[sub_resource type="CanvasItemMaterial" id="1"]
|
[sub_resource type="CanvasItemMaterial" id="1"]
|
||||||
blend_mode = 4
|
blend_mode = 4
|
||||||
|
|
||||||
|
@ -31,6 +38,7 @@ shader_parameter/stripe_direction = 0.5
|
||||||
shader = ExtResource("10")
|
shader = ExtResource("10")
|
||||||
|
|
||||||
[node name="Canvas" type="Node2D"]
|
[node name="Canvas" type="Node2D"]
|
||||||
|
material = SubResource("ShaderMaterial_6b0ox")
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
|
|
||||||
[node name="CurrentlyVisibleFrame" type="SubViewport" parent="."]
|
[node name="CurrentlyVisibleFrame" type="SubViewport" parent="."]
|
||||||
|
@ -40,6 +48,7 @@ handle_input_locally = false
|
||||||
render_target_update_mode = 3
|
render_target_update_mode = 3
|
||||||
|
|
||||||
[node name="CurrentFrameDrawer" type="Node2D" parent="CurrentlyVisibleFrame"]
|
[node name="CurrentFrameDrawer" type="Node2D" parent="CurrentlyVisibleFrame"]
|
||||||
|
material = SubResource("ShaderMaterial_6b0ox")
|
||||||
script = ExtResource("5")
|
script = ExtResource("5")
|
||||||
|
|
||||||
[node name="TileMode" type="Node2D" parent="."]
|
[node name="TileMode" type="Node2D" parent="."]
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
enum Mode { TIMELINE, SPRITESHEET }
|
enum Mode { TIMELINE, SPRITESHEET }
|
||||||
var mode: int = Mode.TIMELINE
|
var mode := Mode.TIMELINE
|
||||||
|
|
||||||
var h_frames := 1
|
var h_frames := 1
|
||||||
var v_frames := 1
|
var v_frames := 1
|
||||||
var start_sprite_sheet_frame := 1
|
var start_sprite_sheet_frame := 1
|
||||||
var end_sprite_sheet_frame := 1
|
var end_sprite_sheet_frame := 1
|
||||||
var sprite_frames := []
|
|
||||||
var frame_index := 0
|
var frame_index := 0
|
||||||
|
|
||||||
@onready var animation_timer := $AnimationTimer as Timer
|
@onready var animation_timer := $AnimationTimer as Timer
|
||||||
|
@ -19,81 +18,78 @@ func _ready() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
var current_project := Global.current_project
|
var project := Global.current_project
|
||||||
match mode:
|
match mode:
|
||||||
Mode.TIMELINE:
|
Mode.TIMELINE:
|
||||||
var modulate_color := Color.WHITE
|
if frame_index >= project.frames.size():
|
||||||
if frame_index >= current_project.frames.size():
|
frame_index = project.current_frame
|
||||||
frame_index = current_project.current_frame
|
|
||||||
if animation_timer.is_stopped():
|
if animation_timer.is_stopped():
|
||||||
frame_index = current_project.current_frame
|
frame_index = project.current_frame
|
||||||
var frame := current_project.frames[frame_index]
|
var frame := project.frames[frame_index]
|
||||||
animation_timer.wait_time = frame.duration * (1.0 / current_project.fps)
|
animation_timer.wait_time = frame.duration * (1.0 / project.fps)
|
||||||
var current_cels := frame.cels
|
var texture := frame.cels[0].image_texture
|
||||||
|
draw_texture(texture, Vector2.ZERO) # Placeholder so we can have a material here
|
||||||
# Draw current frame layers
|
|
||||||
for i in range(current_cels.size()):
|
|
||||||
var cel := current_cels[i]
|
|
||||||
if cel is GroupCel:
|
|
||||||
continue
|
|
||||||
modulate_color = Color(1, 1, 1, cel.opacity)
|
|
||||||
if (
|
|
||||||
i < current_project.layers.size()
|
|
||||||
and current_project.layers[i].is_visible_in_hierarchy()
|
|
||||||
):
|
|
||||||
draw_texture(cel.image_texture, Vector2.ZERO, modulate_color)
|
|
||||||
Mode.SPRITESHEET:
|
Mode.SPRITESHEET:
|
||||||
var texture_to_draw: ImageTexture
|
var image := project.frames[project.current_frame].cels[0].get_image()
|
||||||
var target_frame: Frame = current_project.frames[current_project.current_frame]
|
var slices := _split_spritesheet(image, h_frames, v_frames)
|
||||||
var frame_image := Image.create(
|
# Limit start and end
|
||||||
current_project.size.x, current_project.size.y, false, Image.FORMAT_RGBA8
|
if end_sprite_sheet_frame > slices.size():
|
||||||
)
|
end_sprite_sheet_frame = slices.size()
|
||||||
Export.blend_all_layers(frame_image, target_frame)
|
|
||||||
sprite_frames = _split_spritesheet(frame_image, h_frames, v_frames)
|
|
||||||
|
|
||||||
# limit start and end
|
|
||||||
if end_sprite_sheet_frame > sprite_frames.size():
|
|
||||||
end_sprite_sheet_frame = sprite_frames.size()
|
|
||||||
if start_sprite_sheet_frame < 0:
|
if start_sprite_sheet_frame < 0:
|
||||||
start_sprite_sheet_frame = 0
|
start_sprite_sheet_frame = 0
|
||||||
# reset frame if required
|
|
||||||
if frame_index >= end_sprite_sheet_frame:
|
if frame_index >= end_sprite_sheet_frame:
|
||||||
frame_index = start_sprite_sheet_frame - 1
|
frame_index = start_sprite_sheet_frame - 1
|
||||||
texture_to_draw = sprite_frames[frame_index]
|
var src_rect := slices[frame_index]
|
||||||
draw_texture(texture_to_draw, Vector2.ZERO)
|
var rect := Rect2(Vector2.ZERO, src_rect.size)
|
||||||
|
var texture := project.frames[project.current_frame].cels[0].image_texture
|
||||||
var rect := Rect2(Vector2.ZERO, texture_to_draw.get_image().get_size())
|
# Placeholder so we can have a material here
|
||||||
|
draw_texture_rect_region(texture, rect, src_rect)
|
||||||
transparent_checker.fit_rect(rect)
|
transparent_checker.fit_rect(rect)
|
||||||
|
_draw_layers()
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_layers() -> void:
|
||||||
|
var current_cels := Global.current_project.frames[frame_index].cels
|
||||||
|
var textures: Array[Image] = []
|
||||||
|
var opacities := PackedFloat32Array()
|
||||||
|
var blend_modes := PackedInt32Array()
|
||||||
|
# Draw current frame layers
|
||||||
|
for i in Global.current_project.layers.size():
|
||||||
|
if current_cels[i] is GroupCel:
|
||||||
|
continue
|
||||||
|
if Global.current_project.layers[i].is_visible_in_hierarchy():
|
||||||
|
textures.append(current_cels[i].get_image())
|
||||||
|
opacities.append(current_cels[i].opacity)
|
||||||
|
blend_modes.append(Global.current_project.layers[i].blend_mode)
|
||||||
|
var texture_array := Texture2DArray.new()
|
||||||
|
texture_array.create_from_images(textures)
|
||||||
|
material.set_shader_parameter("layers", texture_array)
|
||||||
|
material.set_shader_parameter("opacities", opacities)
|
||||||
|
material.set_shader_parameter("blend_modes", blend_modes)
|
||||||
|
|
||||||
|
|
||||||
func _on_AnimationTimer_timeout() -> void:
|
func _on_AnimationTimer_timeout() -> void:
|
||||||
match mode:
|
match mode:
|
||||||
Mode.TIMELINE:
|
Mode.TIMELINE:
|
||||||
var current_project := Global.current_project
|
var project := Global.current_project
|
||||||
var first_frame := 0
|
var first_frame := 0
|
||||||
var last_frame := current_project.frames.size() - 1
|
var last_frame := project.frames.size() - 1
|
||||||
|
|
||||||
if Global.play_only_tags:
|
if Global.play_only_tags:
|
||||||
for tag in current_project.animation_tags:
|
for tag in project.animation_tags:
|
||||||
if (
|
if project.current_frame + 1 >= tag.from && project.current_frame + 1 <= tag.to:
|
||||||
current_project.current_frame + 1 >= tag.from
|
|
||||||
&& current_project.current_frame + 1 <= tag.to
|
|
||||||
):
|
|
||||||
first_frame = tag.from - 1
|
first_frame = tag.from - 1
|
||||||
last_frame = min(current_project.frames.size() - 1, tag.to - 1)
|
last_frame = mini(project.frames.size() - 1, tag.to - 1)
|
||||||
|
|
||||||
if frame_index < last_frame:
|
if frame_index < last_frame:
|
||||||
frame_index += 1
|
frame_index += 1
|
||||||
else:
|
else:
|
||||||
frame_index = first_frame
|
frame_index = first_frame
|
||||||
|
animation_timer.wait_time = project.frames[frame_index].duration * (1.0 / project.fps)
|
||||||
animation_timer.wait_time = (
|
|
||||||
current_project.frames[frame_index].duration * (1.0 / current_project.fps)
|
|
||||||
)
|
|
||||||
|
|
||||||
Mode.SPRITESHEET:
|
Mode.SPRITESHEET:
|
||||||
frame_index += 1
|
frame_index += 1
|
||||||
animation_timer.wait_time = (1.0 / Global.current_project.fps)
|
animation_timer.wait_time = 1.0 / Global.current_project.fps
|
||||||
animation_timer.set_one_shot(true)
|
animation_timer.set_one_shot(true)
|
||||||
animation_timer.start()
|
animation_timer.start()
|
||||||
queue_redraw()
|
queue_redraw()
|
||||||
|
@ -103,18 +99,13 @@ func _cel_changed() -> void:
|
||||||
queue_redraw()
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
func _split_spritesheet(image: Image, horiz: int, vert: int) -> Array:
|
func _split_spritesheet(image: Image, horiz: int, vert: int) -> Array[Rect2]:
|
||||||
var result := []
|
var result: Array[Rect2] = []
|
||||||
horiz = mini(horiz, image.get_size().x)
|
horiz = mini(horiz, image.get_size().x)
|
||||||
vert = mini(vert, image.get_size().y)
|
vert = mini(vert, image.get_size().y)
|
||||||
var frame_width := image.get_size().x / horiz
|
var frame_width := image.get_size().x / horiz
|
||||||
var frame_height := image.get_size().y / vert
|
var frame_height := image.get_size().y / vert
|
||||||
for yy in range(vert):
|
for yy in range(vert):
|
||||||
for xx in range(horiz):
|
for xx in range(horiz):
|
||||||
var cropped_image := Image.new()
|
result.append(Rect2(frame_width * xx, frame_height * yy, frame_width, frame_height))
|
||||||
var rect := Rect2(frame_width * xx, frame_height * yy, frame_width, frame_height)
|
|
||||||
cropped_image = image.get_region(rect)
|
|
||||||
cropped_image.convert(Image.FORMAT_RGBA8)
|
|
||||||
var tex := ImageTexture.create_from_image(cropped_image)
|
|
||||||
result.append(tex)
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
[gd_scene load_steps=2 format=3 uid="uid://c546tskdu53j1"]
|
[gd_scene load_steps=4 format=3 uid="uid://c546tskdu53j1"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://src/UI/Canvas/CanvasPreview.gd" id="1"]
|
[ext_resource type="Script" path="res://src/UI/Canvas/CanvasPreview.gd" id="1"]
|
||||||
|
[ext_resource type="Shader" path="res://src/Shaders/BlendLayers.gdshader" id="1_28j41"]
|
||||||
|
|
||||||
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_21d5l"]
|
||||||
|
shader = ExtResource("1_28j41")
|
||||||
|
shader_parameter/opacities = null
|
||||||
|
shader_parameter/blend_modes = null
|
||||||
|
|
||||||
[node name="CanvasPreview" type="Node2D"]
|
[node name="CanvasPreview" type="Node2D"]
|
||||||
|
material = SubResource("ShaderMaterial_21d5l")
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
|
|
||||||
[node name="AnimationTimer" type="Timer" parent="."]
|
[node name="AnimationTimer" type="Timer" parent="."]
|
||||||
|
|
|
@ -2,15 +2,8 @@ extends Node2D
|
||||||
|
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
var current_cels: Array = (
|
# Placeholder so we can have a material here
|
||||||
Global.current_project.frames[Global.current_project.current_frame].cels
|
draw_texture(
|
||||||
|
Global.current_project.frames[Global.current_project.current_frame].cels[0].image_texture,
|
||||||
|
Vector2.ZERO
|
||||||
)
|
)
|
||||||
for i in range(Global.current_project.layers.size()):
|
|
||||||
if current_cels[i] is GroupCel:
|
|
||||||
continue
|
|
||||||
if (
|
|
||||||
Global.current_project.layers[i].is_visible_in_hierarchy()
|
|
||||||
and current_cels[i].opacity > 0
|
|
||||||
):
|
|
||||||
var modulate_color := Color(1, 1, 1, current_cels[i].opacity)
|
|
||||||
draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color)
|
|
||||||
|
|
|
@ -907,7 +907,7 @@ func clear_selection(use_undo := false) -> void:
|
||||||
func _get_preview_image() -> void:
|
func _get_preview_image() -> void:
|
||||||
var project := Global.current_project
|
var project := Global.current_project
|
||||||
var blended_image := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
var blended_image := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||||
Export.blend_selected_cels(blended_image, project.frames[project.current_frame])
|
DrawingAlgos.blend_selected_cels(blended_image, project.frames[project.current_frame])
|
||||||
if original_preview_image.is_empty():
|
if original_preview_image.is_empty():
|
||||||
original_preview_image = blended_image.get_region(big_bounding_rectangle)
|
original_preview_image = blended_image.get_region(big_bounding_rectangle)
|
||||||
# For non-rectangular selections
|
# For non-rectangular selections
|
||||||
|
|
|
@ -10,7 +10,7 @@ var param_names: PackedStringArray = []
|
||||||
func _about_to_popup() -> void:
|
func _about_to_popup() -> void:
|
||||||
Global.canvas.selection.transform_content_confirm()
|
Global.canvas.selection.transform_content_confirm()
|
||||||
var frame := Global.current_project.frames[Global.current_project.current_frame]
|
var frame := Global.current_project.frames[Global.current_project.current_frame]
|
||||||
Export.blend_selected_cels(selected_cels, frame)
|
DrawingAlgos.blend_selected_cels(selected_cels, frame)
|
||||||
|
|
||||||
preview_image.copy_from(selected_cels)
|
preview_image.copy_from(selected_cels)
|
||||||
preview_texture = ImageTexture.create_from_image(preview_image)
|
preview_texture = ImageTexture.create_from_image(preview_image)
|
||||||
|
|
|
@ -125,7 +125,7 @@ func change_mask():
|
||||||
var tiles := Global.current_project.tiles
|
var tiles := Global.current_project.tiles
|
||||||
var tiles_size := tiles.tile_size
|
var tiles_size := tiles.tile_size
|
||||||
var image := Image.create(tiles_size.x, tiles_size.y, false, Image.FORMAT_RGBA8)
|
var image := Image.create(tiles_size.x, tiles_size.y, false, Image.FORMAT_RGBA8)
|
||||||
Export.blend_all_layers(image, current_frame)
|
DrawingAlgos.blend_all_layers(image, current_frame)
|
||||||
if (
|
if (
|
||||||
image.get_used_rect().size == Vector2i.ZERO
|
image.get_used_rect().size == Vector2i.ZERO
|
||||||
or not $VBoxContainer/HBoxContainer/Masking.button_pressed
|
or not $VBoxContainer/HBoxContainer/Masking.button_pressed
|
||||||
|
|
|
@ -74,7 +74,7 @@ func capture_frame() -> void:
|
||||||
else:
|
else:
|
||||||
var frame := project.frames[project.current_frame]
|
var frame := project.frames[project.current_frame]
|
||||||
image = Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
image = Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||||
Export.blend_all_layers(image, frame, Vector2i.ZERO, project)
|
DrawingAlgos.blend_all_layers(image, frame, Vector2i.ZERO, project)
|
||||||
|
|
||||||
if mode == Mode.CANVAS:
|
if mode == Mode.CANVAS:
|
||||||
if resize != 100:
|
if resize != 100:
|
||||||
|
|
|
@ -19,6 +19,7 @@ var frame_button_node := preload("res://src/UI/Timeline/FrameButton.tscn")
|
||||||
@onready var tag_spacer = find_child("TagSpacer")
|
@onready var tag_spacer = find_child("TagSpacer")
|
||||||
@onready var start_spacer = find_child("StartSpacer")
|
@onready var start_spacer = find_child("StartSpacer")
|
||||||
@onready var add_layer_list: MenuButton = $"%AddLayerList"
|
@onready var add_layer_list: MenuButton = $"%AddLayerList"
|
||||||
|
@onready var blend_modes_button := %BlendModes as OptionButton
|
||||||
|
|
||||||
@onready var timeline_scroll: ScrollContainer = find_child("TimelineScroll")
|
@onready var timeline_scroll: ScrollContainer = find_child("TimelineScroll")
|
||||||
@onready var frame_scroll_container: Control = find_child("FrameScrollContainer")
|
@onready var frame_scroll_container: Control = find_child("FrameScrollContainer")
|
||||||
|
@ -152,6 +153,21 @@ func _cel_size_changed(value: int) -> void:
|
||||||
tag_c.get_node("Line2D").points[3] = Vector2(tag_c.custom_minimum_size.x, 32)
|
tag_c.get_node("Line2D").points[3] = Vector2(tag_c.custom_minimum_size.x, 32)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_blend_modes_item_selected(index: BaseLayer.BlendModes) -> void:
|
||||||
|
var current_layer := Global.current_project.layers[Global.current_project.current_layer]
|
||||||
|
var previous_index := current_layer.blend_mode
|
||||||
|
Global.current_project.undo_redo.create_action("Set Blend Mode")
|
||||||
|
Global.current_project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
|
||||||
|
Global.current_project.undo_redo.add_do_property(current_layer, "blend_mode", index)
|
||||||
|
Global.current_project.undo_redo.add_do_method(blend_modes_button.select.bind(index))
|
||||||
|
Global.current_project.undo_redo.add_do_method(Global.canvas.draw_layers)
|
||||||
|
Global.current_project.undo_redo.add_undo_property(current_layer, "blend_mode", previous_index)
|
||||||
|
Global.current_project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
|
||||||
|
Global.current_project.undo_redo.add_undo_method(blend_modes_button.select.bind(previous_index))
|
||||||
|
Global.current_project.undo_redo.add_undo_method(Global.canvas.draw_layers)
|
||||||
|
Global.current_project.undo_redo.commit_action()
|
||||||
|
|
||||||
|
|
||||||
func add_frame() -> void:
|
func add_frame() -> void:
|
||||||
var project := Global.current_project
|
var project := Global.current_project
|
||||||
var frame_add_index := project.current_frame + 1
|
var frame_add_index := project.current_frame + 1
|
||||||
|
@ -842,21 +858,30 @@ func _on_MergeDownLayer_pressed() -> void:
|
||||||
for frame in project.frames:
|
for frame in project.frames:
|
||||||
top_cels.append(frame.cels[top_layer.index]) # Store for undo purposes
|
top_cels.append(frame.cels[top_layer.index]) # Store for undo purposes
|
||||||
|
|
||||||
var top_image := Image.new()
|
var top_image := frame.cels[top_layer.index].get_image()
|
||||||
top_image.copy_from(frame.cels[top_layer.index].get_image())
|
|
||||||
|
|
||||||
if frame.cels[top_layer.index].opacity < 1: # If we have layer transparency
|
|
||||||
for xx in top_image.get_size().x:
|
|
||||||
for yy in top_image.get_size().y:
|
|
||||||
var pixel_color := top_image.get_pixel(xx, yy)
|
|
||||||
var alpha := pixel_color.a * frame.cels[top_layer.index].opacity
|
|
||||||
top_image.set_pixel(
|
|
||||||
xx, yy, Color(pixel_color.r, pixel_color.g, pixel_color.b, alpha)
|
|
||||||
)
|
|
||||||
var bottom_cel := frame.cels[bottom_layer.index]
|
var bottom_cel := frame.cels[bottom_layer.index]
|
||||||
var bottom_image := Image.new()
|
var textures: Array[Image] = []
|
||||||
bottom_image.copy_from(bottom_cel.get_image())
|
var opacities := PackedFloat32Array()
|
||||||
bottom_image.blend_rect(top_image, Rect2i(Vector2i.ZERO, project.size), Vector2i.ZERO)
|
var blend_modes := PackedInt32Array()
|
||||||
|
textures.append(bottom_cel.get_image())
|
||||||
|
opacities.append(bottom_cel.opacity)
|
||||||
|
blend_modes.append(bottom_layer.blend_mode)
|
||||||
|
textures.append(top_image)
|
||||||
|
opacities.append(frame.cels[top_layer.index].opacity)
|
||||||
|
blend_modes.append(top_layer.blend_mode)
|
||||||
|
var texture_array := Texture2DArray.new()
|
||||||
|
texture_array.create_from_images(textures)
|
||||||
|
var params := {
|
||||||
|
"layers": texture_array,
|
||||||
|
"opacities": opacities,
|
||||||
|
"blend_modes": blend_modes,
|
||||||
|
}
|
||||||
|
var bottom_image := Image.create(
|
||||||
|
top_image.get_width(), top_image.get_height(), false, top_image.get_format()
|
||||||
|
)
|
||||||
|
var gen := ShaderImageEffect.new()
|
||||||
|
gen.generate_image(bottom_image, DrawingAlgos.blend_layers_shader, params, project.size)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
bottom_cel.link_set != null
|
bottom_cel.link_set != null
|
||||||
and bottom_cel.link_set.size() > 1
|
and bottom_cel.link_set.size() > 1
|
||||||
|
|
|
@ -371,6 +371,61 @@ size_flags_horizontal = 0
|
||||||
size_flags_vertical = 0
|
size_flags_vertical = 0
|
||||||
texture = ExtResource("5")
|
texture = ExtResource("5")
|
||||||
|
|
||||||
|
[node name="HBoxContainer" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/HBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Blend mode:"
|
||||||
|
|
||||||
|
[node name="BlendModes" type="OptionButton" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/HBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
item_count = 20
|
||||||
|
selected = 0
|
||||||
|
popup/item_0/text = "Normal"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "Darken"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
popup/item_2/text = "Multiply"
|
||||||
|
popup/item_2/id = 2
|
||||||
|
popup/item_3/text = "Color burn"
|
||||||
|
popup/item_3/id = 3
|
||||||
|
popup/item_4/text = "Linear burn"
|
||||||
|
popup/item_4/id = 4
|
||||||
|
popup/item_5/text = "Lighten"
|
||||||
|
popup/item_5/id = 5
|
||||||
|
popup/item_6/text = "Screen"
|
||||||
|
popup/item_6/id = 6
|
||||||
|
popup/item_7/text = "Color dodge"
|
||||||
|
popup/item_7/id = 7
|
||||||
|
popup/item_8/text = "Add"
|
||||||
|
popup/item_8/id = 8
|
||||||
|
popup/item_9/text = "Overlay"
|
||||||
|
popup/item_9/id = 9
|
||||||
|
popup/item_10/text = "Soft Light"
|
||||||
|
popup/item_10/id = 10
|
||||||
|
popup/item_11/text = "Hard light"
|
||||||
|
popup/item_11/id = 11
|
||||||
|
popup/item_12/text = "Difference"
|
||||||
|
popup/item_12/id = 12
|
||||||
|
popup/item_13/text = "Exclusion"
|
||||||
|
popup/item_13/id = 13
|
||||||
|
popup/item_14/text = "Subtract"
|
||||||
|
popup/item_14/id = 14
|
||||||
|
popup/item_15/text = "Divide"
|
||||||
|
popup/item_15/id = 15
|
||||||
|
popup/item_16/text = "Hue"
|
||||||
|
popup/item_16/id = 16
|
||||||
|
popup/item_17/text = "Saturation"
|
||||||
|
popup/item_17/id = 17
|
||||||
|
popup/item_18/text = "Color"
|
||||||
|
popup/item_18/id = 18
|
||||||
|
popup/item_19/text = "Luminosity"
|
||||||
|
popup/item_19/id = 19
|
||||||
|
|
||||||
[node name="BlendingHBox" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer"]
|
[node name="BlendingHBox" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_vertical = 10
|
size_flags_vertical = 10
|
||||||
|
@ -920,6 +975,7 @@ script = ExtResource("12")
|
||||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/MoveDownLayer" to="." method="change_layer_order" binds= [false]]
|
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/MoveDownLayer" to="." method="change_layer_order" binds= [false]]
|
||||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/CloneLayer" to="." method="_on_CloneLayer_pressed"]
|
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/CloneLayer" to="." method="_on_CloneLayer_pressed"]
|
||||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/MergeDownLayer" to="." method="_on_MergeDownLayer_pressed"]
|
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/MergeDownLayer" to="." method="_on_MergeDownLayer_pressed"]
|
||||||
|
[connection signal="item_selected" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/HBoxContainer/BlendModes" to="." method="_on_blend_modes_item_selected"]
|
||||||
[connection signal="value_changed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/BlendingHBox/OpacitySlider" to="." method="_on_OpacitySlider_value_changed"]
|
[connection signal="value_changed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/BlendingHBox/OpacitySlider" to="." method="_on_OpacitySlider_value_changed"]
|
||||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/AddFrame" to="." method="add_frame"]
|
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/AddFrame" to="." method="add_frame"]
|
||||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/DeleteFrame" to="." method="_on_DeleteFrame_pressed"]
|
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/DeleteFrame" to="." method="_on_DeleteFrame_pressed"]
|
||||||
|
|
Loading…
Reference in a new issue