diff --git a/src/Classes/Layers/BaseLayer.gd b/src/Classes/Layers/BaseLayer.gd index 6eccd821bccf..d39d7423f8fd 100644 --- a/src/Classes/Layers/BaseLayer.gd +++ b/src/Classes/Layers/BaseLayer.gd @@ -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. diff --git a/src/Shaders/BlendLayers.gdshader b/src/Shaders/BlendLayers.gdshader index b24a68aaf9d5..384c93a213ca 100644 --- a/src/Shaders/BlendLayers.gdshader +++ b/src/Shaders/BlendLayers.gdshader @@ -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)); @@ -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); } @@ -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; @@ -214,4 +250,4 @@ void fragment() { result_color = blend(current_blend_mode, layer_color, result_color, current_opacity); } COLOR = result_color; -} +} \ No newline at end of file diff --git a/src/UI/Timeline/AnimationTimeline.gd b/src/UI/Timeline/AnimationTimeline.gd index 239153a198e7..b05890b10f31 100644 --- a/src/UI/Timeline/AnimationTimeline.gd +++ b/src/UI/Timeline/AnimationTimeline.gd @@ -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) diff --git a/src/UI/Timeline/LayerProperties.gd b/src/UI/Timeline/LayerProperties.gd index 7c22fab59f5c..c4c9c7762c34 100644 --- a/src/UI/Timeline/LayerProperties.gd +++ b/src/UI/Timeline/LayerProperties.gd @@ -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)