Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Classes/Layers/BaseLayer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ signal ui_color_changed ## Emits when [member ui_color] is changed.
enum BlendModes {
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.
ERASE, ## Erases the non-transparent areas of the upper layer from the lower layer's alpha.
ERASE, ## Erases the upper layer's alpha from the alpha of base layers.
INTERSECTION, ## Erases the un-common areas between the blend and the base layers.
LOGICAL_AND, ## Performs Logical AND on colors between the blend and the base layers.
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.
Expand Down
78 changes: 57 additions & 21 deletions src/Shaders/BlendLayers.gdshader
Original file line number Diff line number Diff line change
Expand Up @@ -58,77 +58,111 @@ vec3 rgb_to_hsl(vec3 rgb)
vec4 blend(int blend_type, vec4 current_color, vec4 prev_color, float opacity) {
current_color.a *= opacity; // Combine the layer opacity
if (current_color.a <= 0.001) {
return prev_color;
// NOTE: Unless the layer itself is transparent, we need transparent colors as well in
// Intersect and Logical AND blending.
if ((blend_type != 2 && blend_type != 3) || opacity <= 0.001){
return prev_color;
}
}

vec4 result;
bool should_blend_alpha = true;
switch(blend_type) {
case 1: // Erase
result = prev_color;
result.a -= current_color.a; // clamping will be done at the end so not doing it here.
current_color.a = 0.0;
should_blend_alpha = false;
break;
case 2: // Darken
case 2: // Intersection
result.rgb = current_color.rgb;
// Perform an Alpha focused relaxed intersection (In those pixels where current_color
// has alpha, but prev_color doesn't and vice versa, make them transparent)
if (current_color.a * prev_color.a <= 0.001){
// NOTE: Setting both prev_color.a and current_color.a to zero gives the net result
// of the pixel being transparent.
current_color.a = 0.0;
prev_color.a = 0.0;
}
break;
case 3: // Logical AND
result = current_color;
bool similar_colors = (
abs(current_color.r - prev_color.r) <= 0.001
&& abs(current_color.g - prev_color.g) <= 0.001
&& abs(current_color.b - prev_color.b) <= 0.001
&& abs(current_color.a - prev_color.a) <= 0.001
);
// Wherever colors are different, use transparent color.
if (!similar_colors){
// NOTE: Setting both prev_color and current_color to zero gives the net result
// of the pixel being transparent.
prev_color.a = 0.0;
current_color = vec4(0.0);
}
should_blend_alpha = false;
break;
case 4: // Darken
result.rgb = min(prev_color.rgb, current_color.rgb);
break;
case 3: // Multiply
case 5: // Multiply
result.rgb = prev_color.rgb * current_color.rgb;
break;
case 4: // Color burn
case 6: // Color burn
result.rgb = 1.0 - (1.0 - prev_color.rgb) / current_color.rgb;
break;
case 5: // Linear burn
case 7: // Linear burn
result.rgb = prev_color.rgb + current_color.rgb - 1.0;
break;
case 6: // Lighten
case 8: // Lighten
result.rgb = max(prev_color.rgb, current_color.rgb);
break;
case 7: // Screen
case 9: // Screen
result.rgb = 1.0 - (1.0 - prev_color.rgb) * (1.0 - current_color.rgb);
break;
case 8: // Color dodge
case 10: // Color dodge
result.rgb = prev_color.rgb / (1.0 - current_color.rgb);
break;
case 9: // Add (linear dodge)
case 11: // Add (linear dodge)
result.rgb = prev_color.rgb + current_color.rgb;
break;
case 10: // Overlay
case 12: // 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 11: // Soft light
case 13: // 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 12: // Hard light
case 14: // 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 13: // Difference
case 15: // Difference
result.rgb = abs(prev_color.rgb - current_color.rgb);
break;
case 14: // Exclusion
case 16: // Exclusion
result.rgb = prev_color.rgb + current_color.rgb - 2.0 * prev_color.rgb * current_color.rgb;
break;
case 15: // Subtract
case 17: // Subtract
result.rgb = prev_color.rgb - current_color.rgb;
break;
case 16: // Divide
case 18: // Divide
result.rgb = prev_color.rgb / current_color.rgb;
break;
case 17: // Hue
case 19: // 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 18: // Saturation
case 20: // 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 19: // Color
case 21: // 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 20: // Luminosity
case 22: // 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));
Expand All @@ -142,6 +176,7 @@ vec4 blend(int blend_type, vec4 current_color, vec4 prev_color, float opacity) {
result.a = prev_color.a * (1.0 - current_color.a) + current_color.a;
}
result = clamp(result, 0.0, 1.0);
// Show blended result proportional to how much previous color had alpha there.
return mix(current_color, result, prev_color.a);
}

Expand Down Expand Up @@ -187,6 +222,7 @@ void fragment() {
// Blend modes are being stored as integers divided by 255, so convert them back to
// their integer form
int current_blend_mode = int(floor(blend_mode_float * 255.0));

vec2 current_origin = texture(metadata, vec2(layer_index, 2.0 / metadata_size_float.y)).rg;
if (!origin_x_positive) {
current_origin.x = -current_origin.x;
Expand Down Expand Up @@ -214,4 +250,4 @@ void fragment() {
result_color = blend(current_blend_mode, layer_color, result_color, current_opacity);
}
COLOR = result_color;
}
}
2 changes: 2 additions & 0 deletions src/UI/Timeline/AnimationTimeline.gd
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,8 @@ func _fill_blend_modes_option_button() -> void:
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("Erase", BaseLayer.BlendModes.ERASE)
blend_modes_button.add_item("Intersection", BaseLayer.BlendModes.INTERSECTION)
blend_modes_button.add_item("Logical AND", BaseLayer.BlendModes.LOGICAL_AND)
blend_modes_button.add_separator("Darken")
blend_modes_button.add_item("Darken", BaseLayer.BlendModes.DARKEN)
blend_modes_button.add_item("Multiply", BaseLayer.BlendModes.MULTIPLY)
Expand Down
2 changes: 2 additions & 0 deletions src/UI/Timeline/LayerProperties.gd
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func _fill_blend_modes_option_button() -> void:
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("Erase", BaseLayer.BlendModes.ERASE)
blend_modes_button.add_item("Intersection", BaseLayer.BlendModes.INTERSECTION)
blend_modes_button.add_item("Logical AND", BaseLayer.BlendModes.LOGICAL_AND)
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)
Expand Down
Loading