mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-24 14:02:11 -03:00
Fix button crop icon display and update functionality
This commit is contained in:
@@ -12,6 +12,7 @@ import { createDistanceFieldMaskSync } from "./utils/ImageAnalysis.js";
|
||||
const log = createModuleLogger('CanvasLayers');
|
||||
export class CanvasLayers {
|
||||
constructor(canvas) {
|
||||
this._canvasMaskCache = new Map();
|
||||
this.blendMenuElement = null;
|
||||
this.blendMenuWorldX = 0;
|
||||
this.blendMenuWorldY = 0;
|
||||
@@ -368,13 +369,32 @@ export class CanvasLayers {
|
||||
const needsBlendAreaEffect = blendArea > 0;
|
||||
if (needsBlendAreaEffect) {
|
||||
log.debug(`Applying blend area effect for layer ${layer.id}, blendArea: ${blendArea}%`);
|
||||
// Get or create distance field mask
|
||||
const maskCanvas = this.getDistanceFieldMaskSync(layer.image, blendArea);
|
||||
// --- BLEND AREA MASK: Use cropped region if cropBounds is set ---
|
||||
let maskCanvas = null;
|
||||
let maskWidth = layer.width;
|
||||
let maskHeight = layer.height;
|
||||
if (layer.cropBounds && layer.originalWidth && layer.originalHeight) {
|
||||
// Create a cropped canvas
|
||||
const s = layer.cropBounds;
|
||||
const { canvas: cropCanvas, ctx: cropCtx } = createCanvas(s.width, s.height);
|
||||
if (cropCtx) {
|
||||
cropCtx.drawImage(layer.image, s.x, s.y, s.width, s.height, 0, 0, s.width, s.height);
|
||||
// Generate distance field mask for the cropped region
|
||||
maskCanvas = this.getDistanceFieldMaskSync(cropCanvas, blendArea);
|
||||
maskWidth = s.width;
|
||||
maskHeight = s.height;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No crop, use full image
|
||||
maskCanvas = this.getDistanceFieldMaskSync(layer.image, blendArea);
|
||||
maskWidth = layer.originalWidth || layer.width;
|
||||
maskHeight = layer.originalHeight || layer.height;
|
||||
}
|
||||
if (maskCanvas) {
|
||||
// Create a temporary canvas for the masked layer
|
||||
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(layer.width, layer.height);
|
||||
if (tempCtx) {
|
||||
// This logic is now unified to handle both cropped and non-cropped images correctly.
|
||||
const s = layer.cropBounds || { x: 0, y: 0, width: layer.originalWidth, height: layer.originalHeight };
|
||||
if (!layer.originalWidth || !layer.originalHeight) {
|
||||
tempCtx.drawImage(layer.image, 0, 0, layer.width, layer.height);
|
||||
@@ -384,17 +404,14 @@ export class CanvasLayers {
|
||||
const layerScaleY = layer.height / layer.originalHeight;
|
||||
const dWidth = s.width * layerScaleX;
|
||||
const dHeight = s.height * layerScaleY;
|
||||
// The destination is the top-left of the temp canvas, plus the scaled offset of the crop area.
|
||||
const dX = s.x * layerScaleX;
|
||||
const dY = s.y * layerScaleY;
|
||||
// We draw into a temp canvas of size layer.width x layer.height.
|
||||
// The destination rect must be positioned correctly within this temp canvas.
|
||||
// The dX/dY here are offsets from the top-left of the transform frame.
|
||||
tempCtx.drawImage(layer.image, s.x, s.y, s.width, s.height, dX, dY, dWidth, dHeight);
|
||||
// --- Apply the distance field mask only to the visible (cropped) area ---
|
||||
tempCtx.globalCompositeOperation = 'destination-in';
|
||||
// Scale the mask to match the drawn area
|
||||
tempCtx.drawImage(maskCanvas, 0, 0, maskWidth, maskHeight, dX, dY, dWidth, dHeight);
|
||||
}
|
||||
// Apply the distance field mask using destination-in for transparency effect
|
||||
tempCtx.globalCompositeOperation = 'destination-in';
|
||||
tempCtx.drawImage(maskCanvas, 0, 0, layer.width, layer.height);
|
||||
// Draw the result
|
||||
ctx.globalCompositeOperation = layer.blendMode || 'normal';
|
||||
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1;
|
||||
@@ -440,30 +457,59 @@ export class CanvasLayers {
|
||||
dX, dY, dWidth, dHeight // destination rect (scaled and positioned within the transform frame)
|
||||
);
|
||||
}
|
||||
getDistanceFieldMaskSync(image, blendArea) {
|
||||
// Check cache first
|
||||
let imageCache = this.distanceFieldCache.get(image);
|
||||
if (!imageCache) {
|
||||
imageCache = new Map();
|
||||
this.distanceFieldCache.set(image, imageCache);
|
||||
}
|
||||
let maskCanvas = imageCache.get(blendArea);
|
||||
if (!maskCanvas) {
|
||||
getDistanceFieldMaskSync(imageOrCanvas, blendArea) {
|
||||
// Use a WeakMap for images, and a Map for canvases (since canvases are not always stable references)
|
||||
let cacheKey = imageOrCanvas;
|
||||
if (imageOrCanvas instanceof HTMLCanvasElement) {
|
||||
// For canvases, use a Map on this instance (not WeakMap)
|
||||
if (!this._canvasMaskCache)
|
||||
this._canvasMaskCache = new Map();
|
||||
let canvasCache = this._canvasMaskCache.get(imageOrCanvas);
|
||||
if (!canvasCache) {
|
||||
canvasCache = new Map();
|
||||
this._canvasMaskCache.set(imageOrCanvas, canvasCache);
|
||||
}
|
||||
if (canvasCache.has(blendArea)) {
|
||||
log.info(`Using cached distance field mask for blendArea: ${blendArea}% (canvas)`);
|
||||
return canvasCache.get(blendArea) || null;
|
||||
}
|
||||
try {
|
||||
log.info(`Creating distance field mask for blendArea: ${blendArea}%`);
|
||||
maskCanvas = createDistanceFieldMaskSync(image, blendArea);
|
||||
log.info(`Creating distance field mask for blendArea: ${blendArea}% (canvas)`);
|
||||
const maskCanvas = createDistanceFieldMaskSync(imageOrCanvas, blendArea);
|
||||
log.info(`Distance field mask created successfully, size: ${maskCanvas.width}x${maskCanvas.height}`);
|
||||
imageCache.set(blendArea, maskCanvas);
|
||||
canvasCache.set(blendArea, maskCanvas);
|
||||
return maskCanvas;
|
||||
}
|
||||
catch (error) {
|
||||
log.error('Failed to create distance field mask:', error);
|
||||
log.error('Failed to create distance field mask (canvas):', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.info(`Using cached distance field mask for blendArea: ${blendArea}%`);
|
||||
// For images, use the original WeakMap cache
|
||||
let imageCache = this.distanceFieldCache.get(imageOrCanvas);
|
||||
if (!imageCache) {
|
||||
imageCache = new Map();
|
||||
this.distanceFieldCache.set(imageOrCanvas, imageCache);
|
||||
}
|
||||
let maskCanvas = imageCache.get(blendArea);
|
||||
if (!maskCanvas) {
|
||||
try {
|
||||
log.info(`Creating distance field mask for blendArea: ${blendArea}%`);
|
||||
maskCanvas = createDistanceFieldMaskSync(imageOrCanvas, blendArea);
|
||||
log.info(`Distance field mask created successfully, size: ${maskCanvas.width}x${maskCanvas.height}`);
|
||||
imageCache.set(blendArea, maskCanvas);
|
||||
}
|
||||
catch (error) {
|
||||
log.error('Failed to create distance field mask:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.info(`Using cached distance field mask for blendArea: ${blendArea}%`);
|
||||
}
|
||||
return maskCanvas;
|
||||
}
|
||||
return maskCanvas;
|
||||
}
|
||||
_drawLayers(ctx, layers, options = {}) {
|
||||
const sortedLayers = [...layers].sort((a, b) => a.zIndex - b.zIndex);
|
||||
|
||||
Reference in New Issue
Block a user