Merge pull request #275 from kmanaseryan/feature/clipped-layer-shadow-192

Issue #192 - Isolate shadow from clipped layer
This commit is contained in:
Vilius 2021-11-28 21:08:39 +02:00 committed by GitHub
commit d23b5e83fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -175,36 +175,8 @@ class Base_layers_class {
config.HEIGHT,
config.WIDTH
);
const ctxForSourceAtop = newCanvas.getContext("2d");
let hasSourceAtopLayer = false;
this.ctx.save();
//render main canvas
for (var i = layers_sorted.length - 1; i >= 0; i--) {
var value = layers_sorted[i];
const nextValue = layers_sorted[i - 1];
if (
value.composition === "source-atop" ||
(nextValue && nextValue.composition === "source-atop")
) {
hasSourceAtopLayer = true;
ctxForSourceAtop.globalAlpha = value.opacity / 100;
ctxForSourceAtop.globalCompositeOperation =
value.composition;
this.render_object(ctxForSourceAtop, value);
} else {
this.ctx.globalAlpha = value.opacity / 100;
this.ctx.globalCompositeOperation = value.composition;
this.render_object(this.ctx, value);
}
}
if (hasSourceAtopLayer) {
this.ctx.restore();
this.ctx.drawImage(newCanvas, 0, 0);
}
this.renderObjects(this.ctx, newCanvas, layers_sorted);
//grid
this.Base_gui.draw_grid(this.ctx);
@ -246,11 +218,11 @@ class Base_layers_class {
if (
typeof this.Base_gui.GUI_tools.tools_modules[render_class].object[
render_function
] != "undefined"
] != "undefined"
) {
this.Base_gui.GUI_tools.tools_modules[render_class].object[
render_function
](this.ctx);
](this.ctx);
}
}
@ -267,6 +239,72 @@ class Base_layers_class {
return newCanvas;
}
/**
* Renders objects based on the provided layers
* @param {canvas.context} ctx - Main canvas context where it needs to be rendered
* @param {canvas} tempCanvas - A temporary canvas which is a copy of the original canvas, but will be used if there will be needed to isolate an effect from others
* @param {Object[]} layers - Array of layers
* @param {Function} prepare - An optional function to prepare temporary canvas before the render if needed
* @param {Function} shouldSkip - An optional boolean function for skipping those layers which are not needed to be rendered
*/
renderObjects(ctx, tempCanvas, layers, prepare, shouldSkip) {
const tempCtx = tempCanvas.getContext("2d");
let hasSourceAtopLayer = false;
// Prepare the temporary canvas if needed
prepare && prepare(tempCtx);
this.ctx.save();
for (var i = layers.length - 1; i >= 0; i--) {
var layer = layers[i];
const nextLayer = layers[i - 1];
// Skip the layer if not needed to be rendered
if (shouldSkip && shouldSkip(layer)) {
continue;
}
// If the layer or next layer has clip masking effect (source-atop).
// If there are such layers, this will make sure that layers will be rendered
// in an isolated temporary canvas
if (
layer.composition === "source-atop" ||
(nextLayer && nextLayer.composition === "source-atop")
) {
hasSourceAtopLayer = true;
// Apply the effect in a isolated temporary canvas
tempCtx.globalAlpha = layer.opacity / 100;
tempCtx.globalCompositeOperation = layer.composition;
// If the next layer has the clip masking effect then
// isolated the shadow filter from temporary canvas and keep that in the original canvas
if (nextLayer?.composition === "source-atop") {
// Render the layer
this.render_object(ctx, layer);
// Then remove the shadow (if it exists) from the render process in the temporary canvas
const filters = layer.filters.filter((filter) => {
return filter.name !== "shadow";
});
this.render_object(tempCtx, {
...layer,
filters,
});
} else {
this.render_object(tempCtx, layer);
}
} else {
ctx.globalAlpha = layer.opacity / 100;
ctx.globalCompositeOperation = layer.composition;
this.render_object(ctx, layer);
}
}
if (hasSourceAtopLayer) {
ctx.restore();
ctx.drawImage(tempCanvas, 0, 0);
}
}
render_preview(layers) {
var w = this.Base_gui.GUI_preview.PREVIEW_SIZE.w;
var h = this.Base_gui.GUI_preview.PREVIEW_SIZE.h;
@ -278,42 +316,9 @@ class Base_layers_class {
this.ctx_preview.scale(w / config.WIDTH, h / config.HEIGHT);
const newCanvas = this.createNewCanvas(this.ctx_preview);
const ctxForSourceAtop = newCanvas.getContext("2d");
ctxForSourceAtop.scale(w / config.WIDTH, h / config.HEIGHT);
let hasSourceAtopLayer = false;
for (var i = layers.length - 1; i >= 0; i--) {
const value = layers[i];
const nextValue = layers[i - 1];
if (value.visible == false) {
//not visible
continue;
}
if (value.type == null) {
//empty type
continue;
}
if (
value.composition === "source-atop" ||
(nextValue && nextValue.composition === "source-atop")
) {
hasSourceAtopLayer = true;
ctxForSourceAtop.globalAlpha = value.opacity / 100;
ctxForSourceAtop.globalCompositeOperation = value.composition;
this.render_object(ctxForSourceAtop, value);
} else {
this.ctx_preview.globalAlpha = value.opacity / 100;
this.ctx_preview.globalCompositeOperation = value.composition;
this.render_object(this.ctx_preview, value);
}
}
if (hasSourceAtopLayer) {
this.ctx_preview.restore();
this.ctx_preview.drawImage(newCanvas, 0, 0);
}
this.renderObjects(this.ctx_preview, newCanvas, layers, (tempCtx) => {
tempCtx.scale(w / config.WIDTH, h / config.HEIGHT);
});
this.ctx_preview.restore();
this.Base_gui.GUI_preview.render_preview_active_zone();
@ -336,10 +341,7 @@ class Base_layers_class {
//image - default behavior
ctx.save();
ctx.translate(
object.x + object.width / 2,
object.y + object.height / 2
);
ctx.translate(object.x + object.width / 2, object.y + object.height / 2);
ctx.rotate((object.rotate * Math.PI) / 180);
// TODO - Not sure why the check should be with null,
// if nothing will break, then better to check if it's just truthy
@ -362,7 +364,7 @@ class Base_layers_class {
) {
this.Base_gui.GUI_tools.tools_modules[render_class].object[
render_function
](ctx, object, is_preview);
](ctx, object, is_preview);
} else {
this.render_success = false;
console.log("Error: unknown layer type: " + object.type);
@ -390,8 +392,7 @@ class Base_layers_class {
//find filter
var found = false;
for (var i in this.Base_gui.modules) {
if (i.indexOf("effects") == -1 || i.indexOf("abstract") > -1)
continue;
if (i.indexOf("effects") == -1 || i.indexOf("abstract") > -1) continue;
var filter_class = this.Base_gui.modules[i];
var module_name = i.split("/").pop();
@ -425,8 +426,7 @@ class Base_layers_class {
//find filter
var found = false;
for (var i in this.Base_gui.modules) {
if (i.indexOf("effects") == -1 || i.indexOf("abstract") > -1)
continue;
if (i.indexOf("effects") == -1 || i.indexOf("abstract") > -1) continue;
var filter_class = this.Base_gui.modules[i];
var module_name = i.split("/").pop();
@ -500,9 +500,7 @@ class Base_layers_class {
* @param {boolean} force - Force to delete first layer?
*/
async delete(id, force) {
return app.State.do_action(
new app.Actions.Delete_layer_action(id, force)
);
return app.State.do_action(new app.Actions.Delete_layer_action(id, force));
}
/*
@ -554,7 +552,9 @@ class Base_layers_class {
value = 100;
}
return app.State.do_action(
new app.Actions.Update_layer_action(id, {opacity: value})
new app.Actions.Update_layer_action(id, {
opacity: value,
})
);
}
@ -702,24 +702,21 @@ class Base_layers_class {
* @param {boolean} is_preview Optional
*/
convert_layers_to_canvas(ctx, layer_id = null, is_preview = true) {
var layers_sorted = this.get_sorted_layers();
for (var i = layers_sorted.length - 1; i >= 0; i--) {
var value = layers_sorted[i];
const newCanvas = this.createNewCanvas(
ctx,
ctx.canvas.width,
ctx.canvas.height
);
const layers_sorted = this.get_sorted_layers();
this.renderObjects(ctx, newCanvas, layers_sorted, null, (value) => {
if (value.visible == false || value.type == null) {
continue;
return true;
}
if (layer_id != null && value.id != layer_id) {
continue;
return true;
}
ctx.globalAlpha = value.opacity / 100;
ctx.globalCompositeOperation = value.composition;
this.render_object(ctx, value, is_preview);
}
});
}
/**
* exports (active) layer to canvas for saving
*
@ -768,13 +765,7 @@ class Base_layers_class {
var w = canvas.width - trim_info.left - trim_info.right;
var h = canvas.height - trim_info.top - trim_info.bottom;
if (w > 1 && h > 1) {
this.Helper.change_canvas_size(
canvas,
w,
h,
offset_x,
offset_y
);
this.Helper.change_canvas_size(canvas, w, h, offset_x, offset_y);
}
}
}