mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 20:52:12 -03:00
Fix DataCloneError by excluding non-serializable cache from state
Excluded blendedImageCache and blendedImageDirty properties from layer serialization in CanvasState.ts to prevent DataCloneError when saving state. This ensures that only serializable data is sent to Web Workers, while runtime caches are regenerated as needed. Blend area performance optimization remains functional without serialization issues.
This commit is contained in:
@@ -310,6 +310,7 @@ export class CanvasLayers {
|
||||
this.canvas.canvasSelection.selectedLayers.forEach((layer) => {
|
||||
layer.width *= scale;
|
||||
layer.height *= scale;
|
||||
this.invalidateBlendCache(layer);
|
||||
});
|
||||
this.canvas.render();
|
||||
this.canvas.requestSaveState();
|
||||
@@ -319,6 +320,7 @@ export class CanvasLayers {
|
||||
return;
|
||||
this.canvas.canvasSelection.selectedLayers.forEach((layer) => {
|
||||
layer.rotation += angle;
|
||||
this.invalidateBlendCache(layer);
|
||||
});
|
||||
this.canvas.render();
|
||||
this.canvas.requestSaveState();
|
||||
@@ -368,64 +370,27 @@ export class CanvasLayers {
|
||||
const blendArea = layer.blendArea ?? 0;
|
||||
const needsBlendAreaEffect = blendArea > 0;
|
||||
if (needsBlendAreaEffect) {
|
||||
log.debug(`Applying blend area effect for layer ${layer.id}, blendArea: ${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;
|
||||
}
|
||||
// Check if we have a valid cached blended image
|
||||
if (layer.blendedImageCache && !layer.blendedImageDirty) {
|
||||
// Use cached blended image for optimal performance
|
||||
ctx.globalCompositeOperation = layer.blendMode || 'normal';
|
||||
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1;
|
||||
ctx.drawImage(layer.blendedImageCache, -layer.width / 2, -layer.height / 2, layer.width, layer.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) {
|
||||
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);
|
||||
}
|
||||
else {
|
||||
const layerScaleX = layer.width / layer.originalWidth;
|
||||
const layerScaleY = layer.height / layer.originalHeight;
|
||||
const dWidth = s.width * layerScaleX;
|
||||
const dHeight = s.height * layerScaleY;
|
||||
const dX = s.x * layerScaleX;
|
||||
const dY = s.y * layerScaleY;
|
||||
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);
|
||||
}
|
||||
// Draw the result
|
||||
// Cache is invalid or doesn't exist, update it
|
||||
this.updateLayerBlendEffect(layer);
|
||||
// Use the newly created cache if available, otherwise fallback
|
||||
if (layer.blendedImageCache) {
|
||||
ctx.globalCompositeOperation = layer.blendMode || 'normal';
|
||||
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1;
|
||||
ctx.drawImage(tempCanvas, -layer.width / 2, -layer.height / 2, layer.width, layer.height);
|
||||
ctx.drawImage(layer.blendedImageCache, -layer.width / 2, -layer.height / 2, layer.width, layer.height);
|
||||
}
|
||||
else {
|
||||
// Fallback to normal drawing
|
||||
this._drawLayerImage(ctx, layer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Fallback to normal drawing
|
||||
this._drawLayerImage(ctx, layer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Normal drawing without blend area effect
|
||||
@@ -457,6 +422,92 @@ export class CanvasLayers {
|
||||
dX, dY, dWidth, dHeight // destination rect (scaled and positioned within the transform frame)
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Invalidates the blended image cache for a layer
|
||||
*/
|
||||
invalidateBlendCache(layer) {
|
||||
layer.blendedImageDirty = true;
|
||||
layer.blendedImageCache = undefined;
|
||||
}
|
||||
/**
|
||||
* Updates the blended image cache for a layer with blendArea effect
|
||||
*/
|
||||
updateLayerBlendEffect(layer) {
|
||||
const blendArea = layer.blendArea ?? 0;
|
||||
if (blendArea <= 0) {
|
||||
// No blend effect needed, clear cache
|
||||
layer.blendedImageCache = undefined;
|
||||
layer.blendedImageDirty = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log.debug(`Updating blend effect cache for layer ${layer.id}, blendArea: ${blendArea}%`);
|
||||
// Create the blended image using the same logic as _drawLayer
|
||||
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 the final blended canvas
|
||||
const { canvas: blendedCanvas, ctx: blendedCtx } = createCanvas(layer.width, layer.height);
|
||||
if (blendedCtx) {
|
||||
const s = layer.cropBounds || { x: 0, y: 0, width: layer.originalWidth, height: layer.originalHeight };
|
||||
if (!layer.originalWidth || !layer.originalHeight) {
|
||||
blendedCtx.drawImage(layer.image, 0, 0, layer.width, layer.height);
|
||||
}
|
||||
else {
|
||||
const layerScaleX = layer.width / layer.originalWidth;
|
||||
const layerScaleY = layer.height / layer.originalHeight;
|
||||
const dWidth = s.width * layerScaleX;
|
||||
const dHeight = s.height * layerScaleY;
|
||||
const dX = s.x * layerScaleX;
|
||||
const dY = s.y * layerScaleY;
|
||||
blendedCtx.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
|
||||
blendedCtx.globalCompositeOperation = 'destination-in';
|
||||
// Scale the mask to match the drawn area
|
||||
blendedCtx.drawImage(maskCanvas, 0, 0, maskWidth, maskHeight, dX, dY, dWidth, dHeight);
|
||||
}
|
||||
// Store the blended result in cache
|
||||
layer.blendedImageCache = blendedCanvas;
|
||||
layer.blendedImageDirty = false;
|
||||
log.debug(`Blend effect cache updated for layer ${layer.id}`);
|
||||
}
|
||||
else {
|
||||
log.warn(`Failed to create blended canvas context for layer ${layer.id}`);
|
||||
layer.blendedImageCache = undefined;
|
||||
layer.blendedImageDirty = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn(`Failed to create distance field mask for layer ${layer.id}`);
|
||||
layer.blendedImageCache = undefined;
|
||||
layer.blendedImageDirty = false;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
log.error(`Error updating blend effect for layer ${layer.id}:`, error);
|
||||
layer.blendedImageCache = undefined;
|
||||
layer.blendedImageDirty = false;
|
||||
}
|
||||
}
|
||||
getDistanceFieldMaskSync(imageOrCanvas, blendArea) {
|
||||
// Use a WeakMap for images, and a Map for canvases (since canvases are not always stable references)
|
||||
let cacheKey = imageOrCanvas;
|
||||
@@ -527,6 +578,7 @@ export class CanvasLayers {
|
||||
return;
|
||||
this.canvas.canvasSelection.selectedLayers.forEach((layer) => {
|
||||
layer.flipH = !layer.flipH;
|
||||
this.invalidateBlendCache(layer);
|
||||
});
|
||||
this.canvas.render();
|
||||
this.canvas.requestSaveState();
|
||||
@@ -536,6 +588,7 @@ export class CanvasLayers {
|
||||
return;
|
||||
this.canvas.canvasSelection.selectedLayers.forEach((layer) => {
|
||||
layer.flipV = !layer.flipV;
|
||||
this.invalidateBlendCache(layer);
|
||||
});
|
||||
this.canvas.render();
|
||||
this.canvas.requestSaveState();
|
||||
@@ -815,10 +868,16 @@ export class CanvasLayers {
|
||||
if (selectedLayer) {
|
||||
const newValue = parseInt(blendAreaSlider.value, 10);
|
||||
selectedLayer.blendArea = newValue;
|
||||
// Invalidate cache when blend area changes
|
||||
this.invalidateBlendCache(selectedLayer);
|
||||
this.canvas.render();
|
||||
}
|
||||
};
|
||||
blendAreaSlider.addEventListener('change', () => {
|
||||
if (selectedLayer) {
|
||||
// Update the blend effect cache when the slider value is finalized
|
||||
this.updateLayerBlendEffect(selectedLayer);
|
||||
}
|
||||
this.canvas.saveState();
|
||||
});
|
||||
blendAreaContainer.appendChild(blendAreaLabel);
|
||||
|
||||
@@ -286,6 +286,9 @@ If you see dark images or masks in the output, make sure node_id is set to ${cor
|
||||
const preparedLayers = await Promise.all(this.canvas.layers.map(async (layer, index) => {
|
||||
const newLayer = { ...layer, imageId: layer.imageId || '' };
|
||||
delete newLayer.image;
|
||||
// Remove cache properties that cannot be serialized for the worker
|
||||
delete newLayer.blendedImageCache;
|
||||
delete newLayer.blendedImageDirty;
|
||||
if (layer.image instanceof HTMLImageElement) {
|
||||
if (layer.imageId) {
|
||||
newLayer.imageId = layer.imageId;
|
||||
|
||||
@@ -355,6 +355,7 @@ export class CanvasLayers {
|
||||
this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer) => {
|
||||
layer.width *= scale;
|
||||
layer.height *= scale;
|
||||
this.invalidateBlendCache(layer);
|
||||
});
|
||||
this.canvas.render();
|
||||
this.canvas.requestSaveState();
|
||||
@@ -365,6 +366,7 @@ export class CanvasLayers {
|
||||
|
||||
this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer) => {
|
||||
layer.rotation += angle;
|
||||
this.invalidateBlendCache(layer);
|
||||
});
|
||||
this.canvas.render();
|
||||
this.canvas.requestSaveState();
|
||||
@@ -428,80 +430,25 @@ export class CanvasLayers {
|
||||
const needsBlendAreaEffect = blendArea > 0;
|
||||
|
||||
if (needsBlendAreaEffect) {
|
||||
log.debug(`Applying blend area effect for layer ${layer.id}, blendArea: ${blendArea}%`);
|
||||
|
||||
// --- BLEND AREA MASK: Use cropped region if cropBounds is set ---
|
||||
let maskCanvas: HTMLCanvasElement | null = 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;
|
||||
}
|
||||
// Check if we have a valid cached blended image
|
||||
if (layer.blendedImageCache && !layer.blendedImageDirty) {
|
||||
// Use cached blended image for optimal performance
|
||||
ctx.globalCompositeOperation = layer.blendMode as any || 'normal';
|
||||
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1;
|
||||
ctx.drawImage(layer.blendedImageCache, -layer.width / 2, -layer.height / 2, layer.width, layer.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);
|
||||
// Cache is invalid or doesn't exist, update it
|
||||
this.updateLayerBlendEffect(layer);
|
||||
|
||||
if (tempCtx) {
|
||||
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);
|
||||
} else {
|
||||
const layerScaleX = layer.width / layer.originalWidth;
|
||||
const layerScaleY = layer.height / layer.originalHeight;
|
||||
|
||||
const dWidth = s.width * layerScaleX;
|
||||
const dHeight = s.height * layerScaleY;
|
||||
const dX = s.x * layerScaleX;
|
||||
const dY = s.y * layerScaleY;
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
// Draw the result
|
||||
// Use the newly created cache if available, otherwise fallback
|
||||
if (layer.blendedImageCache) {
|
||||
ctx.globalCompositeOperation = layer.blendMode as any || 'normal';
|
||||
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1;
|
||||
ctx.drawImage(tempCanvas, -layer.width / 2, -layer.height / 2, layer.width, layer.height);
|
||||
ctx.drawImage(layer.blendedImageCache, -layer.width / 2, -layer.height / 2, layer.width, layer.height);
|
||||
} else {
|
||||
// Fallback to normal drawing
|
||||
this._drawLayerImage(ctx, layer);
|
||||
}
|
||||
} else {
|
||||
// Fallback to normal drawing
|
||||
this._drawLayerImage(ctx, layer);
|
||||
}
|
||||
} else {
|
||||
// Normal drawing without blend area effect
|
||||
@@ -544,6 +491,113 @@ export class CanvasLayers {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the blended image cache for a layer
|
||||
*/
|
||||
public invalidateBlendCache(layer: Layer): void {
|
||||
layer.blendedImageDirty = true;
|
||||
layer.blendedImageCache = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the blended image cache for a layer with blendArea effect
|
||||
*/
|
||||
public updateLayerBlendEffect(layer: Layer): void {
|
||||
const blendArea = layer.blendArea ?? 0;
|
||||
|
||||
if (blendArea <= 0) {
|
||||
// No blend effect needed, clear cache
|
||||
layer.blendedImageCache = undefined;
|
||||
layer.blendedImageDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
log.debug(`Updating blend effect cache for layer ${layer.id}, blendArea: ${blendArea}%`);
|
||||
|
||||
// Create the blended image using the same logic as _drawLayer
|
||||
let maskCanvas: HTMLCanvasElement | null = 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 the final blended canvas
|
||||
const { canvas: blendedCanvas, ctx: blendedCtx } = createCanvas(layer.width, layer.height);
|
||||
|
||||
if (blendedCtx) {
|
||||
const s = layer.cropBounds || { x: 0, y: 0, width: layer.originalWidth, height: layer.originalHeight };
|
||||
|
||||
if (!layer.originalWidth || !layer.originalHeight) {
|
||||
blendedCtx.drawImage(layer.image, 0, 0, layer.width, layer.height);
|
||||
} else {
|
||||
const layerScaleX = layer.width / layer.originalWidth;
|
||||
const layerScaleY = layer.height / layer.originalHeight;
|
||||
|
||||
const dWidth = s.width * layerScaleX;
|
||||
const dHeight = s.height * layerScaleY;
|
||||
const dX = s.x * layerScaleX;
|
||||
const dY = s.y * layerScaleY;
|
||||
|
||||
blendedCtx.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
|
||||
blendedCtx.globalCompositeOperation = 'destination-in';
|
||||
// Scale the mask to match the drawn area
|
||||
blendedCtx.drawImage(
|
||||
maskCanvas,
|
||||
0, 0, maskWidth, maskHeight,
|
||||
dX, dY, dWidth, dHeight
|
||||
);
|
||||
}
|
||||
|
||||
// Store the blended result in cache
|
||||
layer.blendedImageCache = blendedCanvas;
|
||||
layer.blendedImageDirty = false;
|
||||
|
||||
log.debug(`Blend effect cache updated for layer ${layer.id}`);
|
||||
} else {
|
||||
log.warn(`Failed to create blended canvas context for layer ${layer.id}`);
|
||||
layer.blendedImageCache = undefined;
|
||||
layer.blendedImageDirty = false;
|
||||
}
|
||||
} else {
|
||||
log.warn(`Failed to create distance field mask for layer ${layer.id}`);
|
||||
layer.blendedImageCache = undefined;
|
||||
layer.blendedImageDirty = false;
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Error updating blend effect for layer ${layer.id}:`, error);
|
||||
layer.blendedImageCache = undefined;
|
||||
layer.blendedImageDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
private getDistanceFieldMaskSync(imageOrCanvas: HTMLImageElement | HTMLCanvasElement, blendArea: number): HTMLCanvasElement | null {
|
||||
// Use a WeakMap for images, and a Map for canvases (since canvases are not always stable references)
|
||||
let cacheKey: any = imageOrCanvas;
|
||||
@@ -611,6 +665,7 @@ export class CanvasLayers {
|
||||
if (this.canvas.canvasSelection.selectedLayers.length === 0) return;
|
||||
this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer) => {
|
||||
layer.flipH = !layer.flipH;
|
||||
this.invalidateBlendCache(layer);
|
||||
});
|
||||
this.canvas.render();
|
||||
this.canvas.requestSaveState();
|
||||
@@ -620,6 +675,7 @@ export class CanvasLayers {
|
||||
if (this.canvas.canvasSelection.selectedLayers.length === 0) return;
|
||||
this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer) => {
|
||||
layer.flipV = !layer.flipV;
|
||||
this.invalidateBlendCache(layer);
|
||||
});
|
||||
this.canvas.render();
|
||||
this.canvas.requestSaveState();
|
||||
@@ -957,11 +1013,17 @@ export class CanvasLayers {
|
||||
if (selectedLayer) {
|
||||
const newValue = parseInt(blendAreaSlider.value, 10);
|
||||
selectedLayer.blendArea = newValue;
|
||||
// Invalidate cache when blend area changes
|
||||
this.invalidateBlendCache(selectedLayer);
|
||||
this.canvas.render();
|
||||
}
|
||||
};
|
||||
|
||||
blendAreaSlider.addEventListener('change', () => {
|
||||
if (selectedLayer) {
|
||||
// Update the blend effect cache when the slider value is finalized
|
||||
this.updateLayerBlendEffect(selectedLayer);
|
||||
}
|
||||
this.canvas.saveState();
|
||||
});
|
||||
|
||||
|
||||
@@ -326,6 +326,9 @@ If you see dark images or masks in the output, make sure node_id is set to ${cor
|
||||
const preparedLayers = await Promise.all(this.canvas.layers.map(async (layer: Layer, index: number) => {
|
||||
const newLayer: Omit<Layer, 'image'> & { imageId: string } = { ...layer, imageId: layer.imageId || '' };
|
||||
delete (newLayer as any).image;
|
||||
// Remove cache properties that cannot be serialized for the worker
|
||||
delete (newLayer as any).blendedImageCache;
|
||||
delete (newLayer as any).blendedImageDirty;
|
||||
|
||||
if (layer.image instanceof HTMLImageElement) {
|
||||
if (layer.imageId) {
|
||||
|
||||
@@ -28,6 +28,8 @@ export interface Layer {
|
||||
width: number; // szerokość widocznego obszaru
|
||||
height: number; // wysokość widocznego obszaru
|
||||
};
|
||||
blendedImageCache?: HTMLCanvasElement; // Cache for the pre-rendered blendArea effect
|
||||
blendedImageDirty?: boolean; // Flag to invalidate the cache
|
||||
}
|
||||
|
||||
export interface ComfyNode {
|
||||
|
||||
Reference in New Issue
Block a user