mirror of
https://github.com/viliusle/miniPaint.git
synced 2026-02-06 15:31:53 +00:00
Merge pull request #275 from kmanaseryan/feature/clipped-layer-shadow-192
Issue #192 - Isolate shadow from clipped layer
This commit is contained in:
commit
d23b5e83fb
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user