createElement to createCanvas

This commit is contained in:
Dariusz L
2025-07-27 20:02:45 +02:00
parent f36f91487f
commit 64ee2c6abb
16 changed files with 74 additions and 180 deletions

View File

@@ -12,7 +12,7 @@ import { CanvasIO } from "./CanvasIO.js";
import { ImageReferenceManager } from "./ImageReferenceManager.js"; import { ImageReferenceManager } from "./ImageReferenceManager.js";
import { BatchPreviewManager } from "./BatchPreviewManager.js"; import { BatchPreviewManager } from "./BatchPreviewManager.js";
import { createModuleLogger } from "./utils/LoggerUtils.js"; import { createModuleLogger } from "./utils/LoggerUtils.js";
import { debounce } from "./utils/CommonUtils.js"; import { debounce, createCanvas } from "./utils/CommonUtils.js";
import { MaskEditorIntegration } from "./MaskEditorIntegration.js"; import { MaskEditorIntegration } from "./MaskEditorIntegration.js";
import { CanvasSelection } from "./CanvasSelection.js"; import { CanvasSelection } from "./CanvasSelection.js";
const useChainCallback = (original, next) => { const useChainCallback = (original, next) => {
@@ -38,10 +38,10 @@ export class Canvas {
constructor(node, widget, callbacks = {}) { constructor(node, widget, callbacks = {}) {
this.node = node; this.node = node;
this.widget = widget; this.widget = widget;
this.canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(0, 0, '2d', { willReadFrequently: true });
const ctx = this.canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) if (!ctx)
throw new Error("Could not create canvas context"); throw new Error("Could not create canvas context");
this.canvas = canvas;
this.ctx = ctx; this.ctx = ctx;
this.width = 512; this.width = 512;
this.height = 512; this.height = 512;
@@ -54,11 +54,12 @@ export class Canvas {
y: -(this.height / 4), y: -(this.height / 4),
zoom: 0.8, zoom: 0.8,
}; };
this.offscreenCanvas = document.createElement('canvas'); const { canvas: offscreenCanvas, ctx: offscreenCtx } = createCanvas(0, 0, '2d', {
this.offscreenCtx = this.offscreenCanvas.getContext('2d', {
alpha: false, alpha: false,
willReadFrequently: true willReadFrequently: true
}); });
this.offscreenCanvas = offscreenCanvas;
this.offscreenCtx = offscreenCtx;
this.dataInitialized = false; this.dataInitialized = false;
this.pendingDataCheck = null; this.pendingDataCheck = null;
this.imageCache = new Map(); this.imageCache = new Map();

View File

@@ -52,10 +52,7 @@ export class CanvasIO {
const { canvas: maskCanvas, ctx: maskCtx } = createCanvas(this.canvas.width, this.canvas.height); const { canvas: maskCanvas, ctx: maskCtx } = createCanvas(this.canvas.width, this.canvas.height);
const originalShape = this.canvas.outputAreaShape; const originalShape = this.canvas.outputAreaShape;
this.canvas.outputAreaShape = null; this.canvas.outputAreaShape = null;
const visibilityCanvas = document.createElement('canvas'); const { canvas: visibilityCanvas, ctx: visibilityCtx } = createCanvas(this.canvas.width, this.canvas.height, '2d', { alpha: true });
visibilityCanvas.width = this.canvas.width;
visibilityCanvas.height = this.canvas.height;
const visibilityCtx = visibilityCanvas.getContext('2d', { alpha: true });
if (!visibilityCtx) if (!visibilityCtx)
throw new Error("Could not create visibility context"); throw new Error("Could not create visibility context");
if (!maskCtx) if (!maskCtx)
@@ -94,17 +91,11 @@ export class CanvasIO {
tempMaskData.data[i + 3] = alpha; tempMaskData.data[i + 3] = alpha;
} }
// Create a temporary canvas to hold the processed mask // Create a temporary canvas to hold the processed mask
const tempMaskCanvas = document.createElement('canvas'); const { canvas: tempMaskCanvas, ctx: tempMaskCtx } = createCanvas(this.canvas.width, this.canvas.height, '2d', { willReadFrequently: true });
tempMaskCanvas.width = this.canvas.width;
tempMaskCanvas.height = this.canvas.height;
const tempMaskCtx = tempMaskCanvas.getContext('2d', { willReadFrequently: true });
if (!tempMaskCtx) if (!tempMaskCtx)
throw new Error("Could not create temp mask context"); throw new Error("Could not create temp mask context");
// Put the processed mask data into a canvas that matches the output area size // Put the processed mask data into a canvas that matches the output area size
const outputMaskCanvas = document.createElement('canvas'); const { canvas: outputMaskCanvas, ctx: outputMaskCtx } = createCanvas(toolMaskCanvas.width, toolMaskCanvas.height, '2d', { willReadFrequently: true });
outputMaskCanvas.width = toolMaskCanvas.width;
outputMaskCanvas.height = toolMaskCanvas.height;
const outputMaskCtx = outputMaskCanvas.getContext('2d', { willReadFrequently: true });
if (!outputMaskCtx) if (!outputMaskCtx)
throw new Error("Could not create output mask context"); throw new Error("Could not create output mask context");
outputMaskCtx.putImageData(tempMaskData, 0, 0); outputMaskCtx.putImageData(tempMaskData, 0, 0);
@@ -289,12 +280,9 @@ export class CanvasIO {
if (!tensor || !tensor.data || !tensor.width || !tensor.height) { if (!tensor || !tensor.data || !tensor.width || !tensor.height) {
throw new Error("Invalid tensor data"); throw new Error("Invalid tensor data");
} }
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(tensor.width, tensor.height, '2d', { willReadFrequently: true });
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) if (!ctx)
throw new Error("Could not create canvas context"); throw new Error("Could not create canvas context");
canvas.width = tensor.width;
canvas.height = tensor.height;
const imageData = new ImageData(new Uint8ClampedArray(tensor.data), tensor.width, tensor.height); const imageData = new ImageData(new Uint8ClampedArray(tensor.data), tensor.width, tensor.height);
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -468,10 +456,7 @@ export class CanvasIO {
} }
async createImageFromData(imageData) { async createImageFromData(imageData) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(imageData.width, imageData.height, '2d', { willReadFrequently: true });
canvas.width = imageData.width;
canvas.height = imageData.height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) if (!ctx)
throw new Error("Could not create canvas context"); throw new Error("Could not create canvas context");
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);

View File

@@ -456,12 +456,9 @@ export class CanvasLayers {
} }
async getLayerImageData(layer) { async getLayerImageData(layer) {
try { try {
const tempCanvas = document.createElement('canvas'); const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(layer.width, layer.height, '2d', { willReadFrequently: true });
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
if (!tempCtx) if (!tempCtx)
throw new Error("Could not create canvas context"); throw new Error("Could not create canvas context");
tempCanvas.width = layer.width;
tempCanvas.height = layer.height;
// We need to draw the layer relative to the new canvas, so we "move" it to 0,0 // We need to draw the layer relative to the new canvas, so we "move" it to 0,0
// by creating a temporary layer object for drawing. // by creating a temporary layer object for drawing.
const layerToDraw = { const layerToDraw = {
@@ -803,10 +800,7 @@ export class CanvasLayers {
} }
bounds = { x: minX, y: minY, width: newWidth, height: newHeight }; bounds = { x: minX, y: minY, width: newWidth, height: newHeight };
} }
const tempCanvas = document.createElement('canvas'); const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(bounds.width, bounds.height, '2d', { willReadFrequently: true });
tempCanvas.width = bounds.width;
tempCanvas.height = bounds.height;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
if (!tempCtx) { if (!tempCtx) {
reject(new Error("Could not create canvas context")); reject(new Error("Could not create canvas context"));
return; return;
@@ -896,10 +890,7 @@ export class CanvasLayers {
async getFlattenedMaskAsBlob() { async getFlattenedMaskAsBlob() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const bounds = this.canvas.outputAreaBounds; const bounds = this.canvas.outputAreaBounds;
const maskCanvas = document.createElement('canvas'); const { canvas: maskCanvas, ctx: maskCtx } = createCanvas(bounds.width, bounds.height, '2d', { willReadFrequently: true });
maskCanvas.width = bounds.width;
maskCanvas.height = bounds.height;
const maskCtx = maskCanvas.getContext('2d', { willReadFrequently: true });
if (!maskCtx) { if (!maskCtx) {
reject(new Error("Could not create mask context")); reject(new Error("Could not create mask context"));
return; return;
@@ -910,10 +901,7 @@ export class CanvasLayers {
maskCtx.fillStyle = '#ffffff'; maskCtx.fillStyle = '#ffffff';
maskCtx.fillRect(0, 0, bounds.width, bounds.height); maskCtx.fillRect(0, 0, bounds.width, bounds.height);
// Stwórz canvas do sprawdzenia przezroczystości warstw // Stwórz canvas do sprawdzenia przezroczystości warstw
const visibilityCanvas = document.createElement('canvas'); const { canvas: visibilityCanvas, ctx: visibilityCtx } = createCanvas(bounds.width, bounds.height, '2d', { alpha: true });
visibilityCanvas.width = bounds.width;
visibilityCanvas.height = bounds.height;
const visibilityCtx = visibilityCanvas.getContext('2d', { alpha: true });
if (!visibilityCtx) { if (!visibilityCtx) {
reject(new Error("Could not create visibility context")); reject(new Error("Could not create visibility context"));
return; return;
@@ -946,10 +934,7 @@ export class CanvasLayers {
tempMaskData.data[i + 3] = 255; // Solidna alpha tempMaskData.data[i + 3] = 255; // Solidna alpha
} }
// Stwórz tymczasowy canvas dla przetworzonej maski // Stwórz tymczasowy canvas dla przetworzonej maski
const tempMaskCanvas = document.createElement('canvas'); const { canvas: tempMaskCanvas, ctx: tempMaskCtx } = createCanvas(toolMaskCanvas.width, toolMaskCanvas.height, '2d', { willReadFrequently: true });
tempMaskCanvas.width = toolMaskCanvas.width;
tempMaskCanvas.height = toolMaskCanvas.height;
const tempMaskCtx = tempMaskCanvas.getContext('2d', { willReadFrequently: true });
if (tempMaskCtx) { if (tempMaskCtx) {
tempMaskCtx.putImageData(tempMaskData, 0, 0); tempMaskCtx.putImageData(tempMaskData, 0, 0);
maskCtx.globalCompositeOperation = 'screen'; maskCtx.globalCompositeOperation = 'screen';
@@ -1007,10 +992,7 @@ export class CanvasLayers {
showErrorNotification("Cannot fuse layers: invalid dimensions calculated."); showErrorNotification("Cannot fuse layers: invalid dimensions calculated.");
return; return;
} }
const tempCanvas = document.createElement('canvas'); const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(fusedWidth, fusedHeight, '2d', { willReadFrequently: true });
tempCanvas.width = fusedWidth;
tempCanvas.height = fusedHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
if (!tempCtx) if (!tempCtx)
throw new Error("Could not create canvas context"); throw new Error("Could not create canvas context");
tempCtx.translate(-minX, -minY); tempCtx.translate(-minX, -minY);

View File

@@ -1,5 +1,6 @@
import { createModuleLogger } from "./utils/LoggerUtils.js"; import { createModuleLogger } from "./utils/LoggerUtils.js";
import { iconLoader, LAYERFORGE_TOOLS } from "./utils/IconLoader.js"; import { iconLoader, LAYERFORGE_TOOLS } from "./utils/IconLoader.js";
import { createCanvas } from "./utils/CommonUtils.js";
const log = createModuleLogger('CanvasLayersPanel'); const log = createModuleLogger('CanvasLayersPanel');
export class CanvasLayersPanel { export class CanvasLayersPanel {
constructor(canvas) { constructor(canvas) {
@@ -49,10 +50,7 @@ export class CanvasLayersPanel {
iconContainer.appendChild(img); iconContainer.appendChild(img);
} }
else if (icon instanceof HTMLCanvasElement) { else if (icon instanceof HTMLCanvasElement) {
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(size, size);
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
if (ctx) { if (ctx) {
ctx.drawImage(icon, 0, 0, size, size); ctx.drawImage(icon, 0, 0, size, size);
} }
@@ -95,10 +93,7 @@ export class CanvasLayersPanel {
iconContainer.appendChild(img); iconContainer.appendChild(img);
} }
else if (icon instanceof HTMLCanvasElement) { else if (icon instanceof HTMLCanvasElement) {
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(16, 16);
canvas.width = 16;
canvas.height = 16;
const ctx = canvas.getContext('2d');
if (ctx) { if (ctx) {
ctx.globalAlpha = 0.3; ctx.globalAlpha = 0.3;
ctx.drawImage(icon, 0, 0, 16, 16); ctx.drawImage(icon, 0, 0, 16, 16);
@@ -423,12 +418,9 @@ export class CanvasLayersPanel {
thumbnailContainer.style.background = '#4a4a4a'; thumbnailContainer.style.background = '#4a4a4a';
return; return;
} }
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(48, 48, '2d', { willReadFrequently: true });
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) if (!ctx)
return; return;
canvas.width = 48;
canvas.height = 48;
const scale = Math.min(48 / layer.image.width, 48 / layer.image.height); const scale = Math.min(48 / layer.image.width, 48 / layer.image.height);
const scaledWidth = layer.image.width * scale; const scaledWidth = layer.image.width * scale;
const scaledHeight = layer.image.height * scale; const scaledHeight = layer.image.height * scale;

View File

@@ -1,6 +1,6 @@
import { getCanvasState, setCanvasState, saveImage, getImage } from "./db.js"; import { getCanvasState, setCanvasState, saveImage, getImage } from "./db.js";
import { createModuleLogger } from "./utils/LoggerUtils.js"; import { createModuleLogger } from "./utils/LoggerUtils.js";
import { generateUUID, cloneLayers, getStateSignature, debounce } from "./utils/CommonUtils.js"; import { generateUUID, cloneLayers, getStateSignature, debounce, createCanvas } from "./utils/CommonUtils.js";
const log = createModuleLogger('CanvasState'); const log = createModuleLogger('CanvasState');
export class CanvasState { export class CanvasState {
constructor(canvas) { constructor(canvas) {
@@ -196,10 +196,7 @@ export class CanvasState {
img.src = imageSrc; img.src = imageSrc;
} }
else { else {
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(imageSrc.width, imageSrc.height);
canvas.width = imageSrc.width;
canvas.height = imageSrc.height;
const ctx = canvas.getContext('2d');
if (ctx) { if (ctx) {
ctx.drawImage(imageSrc, 0, 0); ctx.drawImage(imageSrc, 0, 0);
const img = new Image(); const img = new Image();
@@ -315,10 +312,7 @@ export class CanvasState {
this.maskUndoStack.pop(); this.maskUndoStack.pop();
} }
const maskCanvas = this.canvas.maskTool.getMask(); const maskCanvas = this.canvas.maskTool.getMask();
const clonedCanvas = document.createElement('canvas'); const { canvas: clonedCanvas, ctx: clonedCtx } = createCanvas(maskCanvas.width, maskCanvas.height, '2d', { willReadFrequently: true });
clonedCanvas.width = maskCanvas.width;
clonedCanvas.height = maskCanvas.height;
const clonedCtx = clonedCanvas.getContext('2d', { willReadFrequently: true });
if (clonedCtx) { if (clonedCtx) {
clonedCtx.drawImage(maskCanvas, 0, 0); clonedCtx.drawImage(maskCanvas, 0, 0);
} }

View File

@@ -6,6 +6,7 @@ import { addStylesheet, getUrl, loadTemplate } from "./utils/ResourceManager.js"
import { Canvas } from "./Canvas.js"; import { Canvas } from "./Canvas.js";
import { clearAllCanvasStates } from "./db.js"; import { clearAllCanvasStates } from "./db.js";
import { ImageCache } from "./ImageCache.js"; import { ImageCache } from "./ImageCache.js";
import { createCanvas } from "./utils/CommonUtils.js";
import { createModuleLogger } from "./utils/LoggerUtils.js"; import { createModuleLogger } from "./utils/LoggerUtils.js";
import { showErrorNotification, showSuccessNotification } from "./utils/NotificationUtils.js"; import { showErrorNotification, showSuccessNotification } from "./utils/NotificationUtils.js";
import { iconLoader, LAYERFORGE_TOOLS } from "./utils/IconLoader.js"; import { iconLoader, LAYERFORGE_TOOLS } from "./utils/IconLoader.js";
@@ -541,10 +542,7 @@ async function createCanvasWidget(node, widget, app) {
iconContainer.appendChild(img); iconContainer.appendChild(img);
} }
else if (icon instanceof HTMLCanvasElement) { else if (icon instanceof HTMLCanvasElement) {
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(16, 16);
canvas.width = 16;
canvas.height = 16;
const ctx = canvas.getContext('2d');
if (ctx) { if (ctx) {
ctx.drawImage(icon, 0, 0, 16, 16); ctx.drawImage(icon, 0, 0, 16, 16);
} }

View File

@@ -9,6 +9,7 @@ import { processImageToMask, processMaskForViewport } from "./utils/MaskProcessi
import { convertToImage } from "./utils/ImageUtils.js"; import { convertToImage } from "./utils/ImageUtils.js";
import { updateNodePreview } from "./utils/PreviewUtils.js"; import { updateNodePreview } from "./utils/PreviewUtils.js";
import { mask_editor_showing, mask_editor_listen_for_cancel } from "./utils/mask_utils.js"; import { mask_editor_showing, mask_editor_listen_for_cancel } from "./utils/mask_utils.js";
import { createCanvas } from "./utils/CommonUtils.js";
const log = createModuleLogger('MaskEditorIntegration'); const log = createModuleLogger('MaskEditorIntegration');
export class MaskEditorIntegration { export class MaskEditorIntegration {
constructor(canvas) { constructor(canvas) {
@@ -283,10 +284,7 @@ export class MaskEditorIntegration {
return null; return null;
} }
const maskCanvas = this.maskTool.maskCanvas; const maskCanvas = this.maskTool.maskCanvas;
const savedCanvas = document.createElement('canvas'); const { canvas: savedCanvas, ctx: savedCtx } = createCanvas(maskCanvas.width, maskCanvas.height, '2d', { willReadFrequently: true });
savedCanvas.width = maskCanvas.width;
savedCanvas.height = maskCanvas.height;
const savedCtx = savedCanvas.getContext('2d', { willReadFrequently: true });
if (savedCtx) { if (savedCtx) {
savedCtx.drawImage(maskCanvas, 0, 0); savedCtx.drawImage(maskCanvas, 0, 0);
} }

View File

@@ -17,11 +17,11 @@ export class MaskTool {
this.currentDrawingChunk = null; this.currentDrawingChunk = null;
this.maxActiveChunks = 25; // Safety limit to prevent memory issues (5x5 grid max) this.maxActiveChunks = 25; // Safety limit to prevent memory issues (5x5 grid max)
// Create active mask canvas (composite of chunks) // Create active mask canvas (composite of chunks)
this.activeMaskCanvas = document.createElement('canvas'); const { canvas: activeMaskCanvas, ctx: activeMaskCtx } = createCanvas(1, 1, '2d', { willReadFrequently: true });
const activeMaskCtx = this.activeMaskCanvas.getContext('2d', { willReadFrequently: true });
if (!activeMaskCtx) { if (!activeMaskCtx) {
throw new Error("Failed to get 2D context for active mask canvas"); throw new Error("Failed to get 2D context for active mask canvas");
} }
this.activeMaskCanvas = activeMaskCanvas;
this.activeMaskCtx = activeMaskCtx; this.activeMaskCtx = activeMaskCtx;
this.x = 0; this.x = 0;
this.y = 0; this.y = 0;
@@ -32,20 +32,20 @@ export class MaskTool {
this.brushHardness = 0.5; this.brushHardness = 0.5;
this.isDrawing = false; this.isDrawing = false;
this.lastPosition = null; this.lastPosition = null;
this.previewCanvas = document.createElement('canvas'); const { canvas: previewCanvas, ctx: previewCtx } = createCanvas(1, 1, '2d', { willReadFrequently: true });
const previewCtx = this.previewCanvas.getContext('2d', { willReadFrequently: true });
if (!previewCtx) { if (!previewCtx) {
throw new Error("Failed to get 2D context for preview canvas"); throw new Error("Failed to get 2D context for preview canvas");
} }
this.previewCanvas = previewCanvas;
this.previewCtx = previewCtx; this.previewCtx = previewCtx;
this.previewVisible = false; this.previewVisible = false;
this.previewCanvasInitialized = false; this.previewCanvasInitialized = false;
// Initialize shape preview system // Initialize shape preview system
this.shapePreviewCanvas = document.createElement('canvas'); const { canvas: shapePreviewCanvas, ctx: shapePreviewCtx } = createCanvas(1, 1, '2d', { willReadFrequently: true });
const shapePreviewCtx = this.shapePreviewCanvas.getContext('2d', { willReadFrequently: true });
if (!shapePreviewCtx) { if (!shapePreviewCtx) {
throw new Error("Failed to get 2D context for shape preview canvas"); throw new Error("Failed to get 2D context for shape preview canvas");
} }
this.shapePreviewCanvas = shapePreviewCanvas;
this.shapePreviewCtx = shapePreviewCtx; this.shapePreviewCtx = shapePreviewCtx;
this.shapePreviewVisible = false; this.shapePreviewVisible = false;
this.isPreviewMode = false; this.isPreviewMode = false;
@@ -1262,10 +1262,7 @@ export class MaskTool {
getMaskForOutputArea() { getMaskForOutputArea() {
const bounds = this.canvasInstance.outputAreaBounds; const bounds = this.canvasInstance.outputAreaBounds;
// Create canvas sized to output area // Create canvas sized to output area
const outputMaskCanvas = document.createElement('canvas'); const { canvas: outputMaskCanvas, ctx: outputMaskCtx } = createCanvas(bounds.width, bounds.height, '2d', { willReadFrequently: true });
outputMaskCanvas.width = bounds.width;
outputMaskCanvas.height = bounds.height;
const outputMaskCtx = outputMaskCanvas.getContext('2d', { willReadFrequently: true });
if (!outputMaskCtx) { if (!outputMaskCtx) {
throw new Error("Failed to get 2D context for output area mask canvas"); throw new Error("Failed to get 2D context for output area mask canvas");
} }
@@ -1304,7 +1301,8 @@ export class MaskTool {
const oldHeight = oldMask.height; const oldHeight = oldMask.height;
const isIncreasingWidth = width > this.canvasInstance.width; const isIncreasingWidth = width > this.canvasInstance.width;
const isIncreasingHeight = height > this.canvasInstance.height; const isIncreasingHeight = height > this.canvasInstance.height;
this.activeMaskCanvas = document.createElement('canvas'); const { canvas: activeMaskCanvas } = createCanvas(1, 1, '2d', { willReadFrequently: true });
this.activeMaskCanvas = activeMaskCanvas;
const extraSpace = 2000; const extraSpace = 2000;
const newWidth = isIncreasingWidth ? width + extraSpace : Math.max(oldWidth, width + extraSpace); const newWidth = isIncreasingWidth ? width + extraSpace : Math.max(oldWidth, width + extraSpace);
const newHeight = isIncreasingHeight ? height + extraSpace : Math.max(oldHeight, height + extraSpace); const newHeight = isIncreasingHeight ? height + extraSpace : Math.max(oldHeight, height + extraSpace);

View File

@@ -18,7 +18,7 @@ import {CanvasIO} from "./CanvasIO.js";
import {ImageReferenceManager} from "./ImageReferenceManager.js"; import {ImageReferenceManager} from "./ImageReferenceManager.js";
import {BatchPreviewManager} from "./BatchPreviewManager.js"; import {BatchPreviewManager} from "./BatchPreviewManager.js";
import {createModuleLogger} from "./utils/LoggerUtils.js"; import {createModuleLogger} from "./utils/LoggerUtils.js";
import { debounce } from "./utils/CommonUtils.js"; import { debounce, createCanvas } from "./utils/CommonUtils.js";
import {MaskEditorIntegration} from "./MaskEditorIntegration.js"; import {MaskEditorIntegration} from "./MaskEditorIntegration.js";
import {CanvasSelection} from "./CanvasSelection.js"; import {CanvasSelection} from "./CanvasSelection.js";
import type { ComfyNode, Layer, Viewport, Point, AddMode, Shape, OutputAreaBounds } from './types'; import type { ComfyNode, Layer, Viewport, Point, AddMode, Shape, OutputAreaBounds } from './types';
@@ -96,9 +96,9 @@ export class Canvas {
constructor(node: ComfyNode, widget: any, callbacks: { onStateChange?: () => void, onHistoryChange?: (historyInfo: { canUndo: boolean; canRedo: boolean; }) => void } = {}) { constructor(node: ComfyNode, widget: any, callbacks: { onStateChange?: () => void, onHistoryChange?: (historyInfo: { canUndo: boolean; canRedo: boolean; }) => void } = {}) {
this.node = node; this.node = node;
this.widget = widget; this.widget = widget;
this.canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(0, 0, '2d', {willReadFrequently: true});
const ctx = this.canvas.getContext('2d', {willReadFrequently: true});
if (!ctx) throw new Error("Could not create canvas context"); if (!ctx) throw new Error("Could not create canvas context");
this.canvas = canvas;
this.ctx = ctx; this.ctx = ctx;
this.width = 512; this.width = 512;
this.height = 512; this.height = 512;
@@ -113,11 +113,12 @@ export class Canvas {
zoom: 0.8, zoom: 0.8,
}; };
this.offscreenCanvas = document.createElement('canvas'); const { canvas: offscreenCanvas, ctx: offscreenCtx } = createCanvas(0, 0, '2d', {
this.offscreenCtx = this.offscreenCanvas.getContext('2d', {
alpha: false, alpha: false,
willReadFrequently: true willReadFrequently: true
}); });
this.offscreenCanvas = offscreenCanvas;
this.offscreenCtx = offscreenCtx;
this.dataInitialized = false; this.dataInitialized = false;
this.pendingDataCheck = null; this.pendingDataCheck = null;

View File

@@ -65,10 +65,7 @@ export class CanvasIO {
const originalShape = this.canvas.outputAreaShape; const originalShape = this.canvas.outputAreaShape;
this.canvas.outputAreaShape = null; this.canvas.outputAreaShape = null;
const visibilityCanvas = document.createElement('canvas'); const { canvas: visibilityCanvas, ctx: visibilityCtx } = createCanvas(this.canvas.width, this.canvas.height, '2d', { alpha: true });
visibilityCanvas.width = this.canvas.width;
visibilityCanvas.height = this.canvas.height;
const visibilityCtx = visibilityCanvas.getContext('2d', { alpha: true });
if (!visibilityCtx) throw new Error("Could not create visibility context"); if (!visibilityCtx) throw new Error("Could not create visibility context");
if (!maskCtx) throw new Error("Could not create mask context"); if (!maskCtx) throw new Error("Could not create mask context");
if (!tempCtx) throw new Error("Could not create temp context"); if (!tempCtx) throw new Error("Could not create temp context");
@@ -111,17 +108,11 @@ export class CanvasIO {
} }
// Create a temporary canvas to hold the processed mask // Create a temporary canvas to hold the processed mask
const tempMaskCanvas = document.createElement('canvas'); const { canvas: tempMaskCanvas, ctx: tempMaskCtx } = createCanvas(this.canvas.width, this.canvas.height, '2d', { willReadFrequently: true });
tempMaskCanvas.width = this.canvas.width;
tempMaskCanvas.height = this.canvas.height;
const tempMaskCtx = tempMaskCanvas.getContext('2d', { willReadFrequently: true });
if (!tempMaskCtx) throw new Error("Could not create temp mask context"); if (!tempMaskCtx) throw new Error("Could not create temp mask context");
// Put the processed mask data into a canvas that matches the output area size // Put the processed mask data into a canvas that matches the output area size
const outputMaskCanvas = document.createElement('canvas'); const { canvas: outputMaskCanvas, ctx: outputMaskCtx } = createCanvas(toolMaskCanvas.width, toolMaskCanvas.height, '2d', { willReadFrequently: true });
outputMaskCanvas.width = toolMaskCanvas.width;
outputMaskCanvas.height = toolMaskCanvas.height;
const outputMaskCtx = outputMaskCanvas.getContext('2d', { willReadFrequently: true });
if (!outputMaskCtx) throw new Error("Could not create output mask context"); if (!outputMaskCtx) throw new Error("Could not create output mask context");
outputMaskCtx.putImageData(tempMaskData, 0, 0); outputMaskCtx.putImageData(tempMaskData, 0, 0);
@@ -337,11 +328,8 @@ export class CanvasIO {
throw new Error("Invalid tensor data"); throw new Error("Invalid tensor data");
} }
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(tensor.width, tensor.height, '2d', { willReadFrequently: true });
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) throw new Error("Could not create canvas context"); if (!ctx) throw new Error("Could not create canvas context");
canvas.width = tensor.width;
canvas.height = tensor.height;
const imageData = new ImageData( const imageData = new ImageData(
new Uint8ClampedArray(tensor.data), new Uint8ClampedArray(tensor.data),
@@ -550,10 +538,7 @@ export class CanvasIO {
async createImageFromData(imageData: ImageData): Promise<HTMLImageElement> { async createImageFromData(imageData: ImageData): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(imageData.width, imageData.height, '2d', { willReadFrequently: true });
canvas.width = imageData.width;
canvas.height = imageData.height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) throw new Error("Could not create canvas context"); if (!ctx) throw new Error("Could not create canvas context");
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);

View File

@@ -522,13 +522,9 @@ export class CanvasLayers {
async getLayerImageData(layer: Layer): Promise<string> { async getLayerImageData(layer: Layer): Promise<string> {
try { try {
const tempCanvas = document.createElement('canvas'); const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(layer.width, layer.height, '2d', { willReadFrequently: true });
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
if (!tempCtx) throw new Error("Could not create canvas context"); if (!tempCtx) throw new Error("Could not create canvas context");
tempCanvas.width = layer.width;
tempCanvas.height = layer.height;
// We need to draw the layer relative to the new canvas, so we "move" it to 0,0 // We need to draw the layer relative to the new canvas, so we "move" it to 0,0
// by creating a temporary layer object for drawing. // by creating a temporary layer object for drawing.
const layerToDraw = { const layerToDraw = {
@@ -937,10 +933,7 @@ export class CanvasLayers {
bounds = { x: minX, y: minY, width: newWidth, height: newHeight }; bounds = { x: minX, y: minY, width: newWidth, height: newHeight };
} }
const tempCanvas = document.createElement('canvas'); const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(bounds.width, bounds.height, '2d', { willReadFrequently: true });
tempCanvas.width = bounds.width;
tempCanvas.height = bounds.height;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
if (!tempCtx) { if (!tempCtx) {
reject(new Error("Could not create canvas context")); reject(new Error("Could not create canvas context"));
return; return;
@@ -1042,10 +1035,7 @@ export class CanvasLayers {
async getFlattenedMaskAsBlob(): Promise<Blob | null> { async getFlattenedMaskAsBlob(): Promise<Blob | null> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const bounds = this.canvas.outputAreaBounds; const bounds = this.canvas.outputAreaBounds;
const maskCanvas = document.createElement('canvas'); const { canvas: maskCanvas, ctx: maskCtx } = createCanvas(bounds.width, bounds.height, '2d', { willReadFrequently: true });
maskCanvas.width = bounds.width;
maskCanvas.height = bounds.height;
const maskCtx = maskCanvas.getContext('2d', { willReadFrequently: true });
if (!maskCtx) { if (!maskCtx) {
reject(new Error("Could not create mask context")); reject(new Error("Could not create mask context"));
@@ -1060,10 +1050,7 @@ export class CanvasLayers {
maskCtx.fillRect(0, 0, bounds.width, bounds.height); maskCtx.fillRect(0, 0, bounds.width, bounds.height);
// Stwórz canvas do sprawdzenia przezroczystości warstw // Stwórz canvas do sprawdzenia przezroczystości warstw
const visibilityCanvas = document.createElement('canvas'); const { canvas: visibilityCanvas, ctx: visibilityCtx } = createCanvas(bounds.width, bounds.height, '2d', { alpha: true });
visibilityCanvas.width = bounds.width;
visibilityCanvas.height = bounds.height;
const visibilityCtx = visibilityCanvas.getContext('2d', { alpha: true });
if (!visibilityCtx) { if (!visibilityCtx) {
reject(new Error("Could not create visibility context")); reject(new Error("Could not create visibility context"));
return; return;
@@ -1101,10 +1088,7 @@ export class CanvasLayers {
} }
// Stwórz tymczasowy canvas dla przetworzonej maski // Stwórz tymczasowy canvas dla przetworzonej maski
const tempMaskCanvas = document.createElement('canvas'); const { canvas: tempMaskCanvas, ctx: tempMaskCtx } = createCanvas(toolMaskCanvas.width, toolMaskCanvas.height, '2d', { willReadFrequently: true });
tempMaskCanvas.width = toolMaskCanvas.width;
tempMaskCanvas.height = toolMaskCanvas.height;
const tempMaskCtx = tempMaskCanvas.getContext('2d', { willReadFrequently: true });
if (tempMaskCtx) { if (tempMaskCtx) {
tempMaskCtx.putImageData(tempMaskData, 0, 0); tempMaskCtx.putImageData(tempMaskData, 0, 0);
@@ -1174,10 +1158,7 @@ export class CanvasLayers {
return; return;
} }
const tempCanvas = document.createElement('canvas'); const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(fusedWidth, fusedHeight, '2d', { willReadFrequently: true });
tempCanvas.width = fusedWidth;
tempCanvas.height = fusedHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
if (!tempCtx) throw new Error("Could not create canvas context"); if (!tempCtx) throw new Error("Could not create canvas context");
tempCtx.translate(-minX, -minY); tempCtx.translate(-minX, -minY);

View File

@@ -1,5 +1,6 @@
import { createModuleLogger } from "./utils/LoggerUtils.js"; import { createModuleLogger } from "./utils/LoggerUtils.js";
import { iconLoader, LAYERFORGE_TOOLS } from "./utils/IconLoader.js"; import { iconLoader, LAYERFORGE_TOOLS } from "./utils/IconLoader.js";
import { createCanvas } from "./utils/CommonUtils.js";
import type { Canvas } from './Canvas'; import type { Canvas } from './Canvas';
import type { Layer } from './types'; import type { Layer } from './types';
@@ -65,10 +66,7 @@ export class CanvasLayersPanel {
`; `;
iconContainer.appendChild(img); iconContainer.appendChild(img);
} else if (icon instanceof HTMLCanvasElement) { } else if (icon instanceof HTMLCanvasElement) {
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(size, size);
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
if (ctx) { if (ctx) {
ctx.drawImage(icon, 0, 0, size, size); ctx.drawImage(icon, 0, 0, size, size);
} }
@@ -111,10 +109,7 @@ export class CanvasLayersPanel {
`; `;
iconContainer.appendChild(img); iconContainer.appendChild(img);
} else if (icon instanceof HTMLCanvasElement) { } else if (icon instanceof HTMLCanvasElement) {
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(16, 16);
canvas.width = 16;
canvas.height = 16;
const ctx = canvas.getContext('2d');
if (ctx) { if (ctx) {
ctx.globalAlpha = 0.3; ctx.globalAlpha = 0.3;
ctx.drawImage(icon, 0, 0, 16, 16); ctx.drawImage(icon, 0, 0, 16, 16);
@@ -465,11 +460,8 @@ export class CanvasLayersPanel {
return; return;
} }
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(48, 48, '2d', { willReadFrequently: true });
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) return; if (!ctx) return;
canvas.width = 48;
canvas.height = 48;
const scale = Math.min(48 / layer.image.width, 48 / layer.image.height); const scale = Math.min(48 / layer.image.width, 48 / layer.image.height);
const scaledWidth = layer.image.width * scale; const scaledWidth = layer.image.width * scale;

View File

@@ -1,6 +1,6 @@
import {getCanvasState, setCanvasState, saveImage, getImage} from "./db.js"; import {getCanvasState, setCanvasState, saveImage, getImage} from "./db.js";
import {createModuleLogger} from "./utils/LoggerUtils.js"; import {createModuleLogger} from "./utils/LoggerUtils.js";
import {generateUUID, cloneLayers, getStateSignature, debounce} from "./utils/CommonUtils.js"; import {generateUUID, cloneLayers, getStateSignature, debounce, createCanvas} from "./utils/CommonUtils.js";
import {withErrorHandling} from "./ErrorHandler.js"; import {withErrorHandling} from "./ErrorHandler.js";
import type { Canvas } from './Canvas'; import type { Canvas } from './Canvas';
import type { Layer, ComfyNode } from './types'; import type { Layer, ComfyNode } from './types';
@@ -230,10 +230,7 @@ export class CanvasState {
}; };
img.src = imageSrc; img.src = imageSrc;
} else { } else {
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(imageSrc.width, imageSrc.height);
canvas.width = imageSrc.width;
canvas.height = imageSrc.height;
const ctx = canvas.getContext('2d');
if (ctx) { if (ctx) {
ctx.drawImage(imageSrc, 0, 0); ctx.drawImage(imageSrc, 0, 0);
const img = new Image(); const img = new Image();
@@ -358,10 +355,7 @@ export class CanvasState {
this.maskUndoStack.pop(); this.maskUndoStack.pop();
} }
const maskCanvas = this.canvas.maskTool.getMask(); const maskCanvas = this.canvas.maskTool.getMask();
const clonedCanvas = document.createElement('canvas'); const { canvas: clonedCanvas, ctx: clonedCtx } = createCanvas(maskCanvas.width, maskCanvas.height, '2d', { willReadFrequently: true });
clonedCanvas.width = maskCanvas.width;
clonedCanvas.height = maskCanvas.height;
const clonedCtx = clonedCanvas.getContext('2d', { willReadFrequently: true });
if (clonedCtx) { if (clonedCtx) {
clonedCtx.drawImage(maskCanvas, 0, 0); clonedCtx.drawImage(maskCanvas, 0, 0);
} }

View File

@@ -12,7 +12,7 @@ import { addStylesheet, getUrl, loadTemplate } from "./utils/ResourceManager.js"
import {Canvas} from "./Canvas.js"; import {Canvas} from "./Canvas.js";
import {clearAllCanvasStates} from "./db.js"; import {clearAllCanvasStates} from "./db.js";
import {ImageCache} from "./ImageCache.js"; import {ImageCache} from "./ImageCache.js";
import {generateUniqueFileName} from "./utils/CommonUtils.js"; import {generateUniqueFileName, createCanvas} from "./utils/CommonUtils.js";
import {createModuleLogger} from "./utils/LoggerUtils.js"; import {createModuleLogger} from "./utils/LoggerUtils.js";
import {showErrorNotification, showSuccessNotification} from "./utils/NotificationUtils.js"; import {showErrorNotification, showSuccessNotification} from "./utils/NotificationUtils.js";
import { iconLoader, LAYERFORGE_TOOLS } from "./utils/IconLoader.js"; import { iconLoader, LAYERFORGE_TOOLS } from "./utils/IconLoader.js";
@@ -577,10 +577,7 @@ async function createCanvasWidget(node: ComfyNode, widget: any, app: ComfyApp):
`; `;
iconContainer.appendChild(img); iconContainer.appendChild(img);
} else if (icon instanceof HTMLCanvasElement) { } else if (icon instanceof HTMLCanvasElement) {
const canvas = document.createElement('canvas'); const { canvas, ctx } = createCanvas(16, 16);
canvas.width = 16;
canvas.height = 16;
const ctx = canvas.getContext('2d');
if (ctx) { if (ctx) {
ctx.drawImage(icon, 0, 0, 16, 16); ctx.drawImage(icon, 0, 0, 16, 16);
} }

View File

@@ -11,6 +11,7 @@ import { processImageToMask, processMaskForViewport } from "./utils/MaskProcessi
import { convertToImage } from "./utils/ImageUtils.js"; import { convertToImage } from "./utils/ImageUtils.js";
import { updateNodePreview } from "./utils/PreviewUtils.js"; import { updateNodePreview } from "./utils/PreviewUtils.js";
import { mask_editor_showing, mask_editor_listen_for_cancel } from "./utils/mask_utils.js"; import { mask_editor_showing, mask_editor_listen_for_cancel } from "./utils/mask_utils.js";
import { createCanvas } from "./utils/CommonUtils.js";
const log = createModuleLogger('MaskEditorIntegration'); const log = createModuleLogger('MaskEditorIntegration');
@@ -347,10 +348,7 @@ export class MaskEditorIntegration {
} }
const maskCanvas = this.maskTool.maskCanvas; const maskCanvas = this.maskTool.maskCanvas;
const savedCanvas = document.createElement('canvas'); const { canvas: savedCanvas, ctx: savedCtx } = createCanvas(maskCanvas.width, maskCanvas.height, '2d', {willReadFrequently: true});
savedCanvas.width = maskCanvas.width;
savedCanvas.height = maskCanvas.height;
const savedCtx = savedCanvas.getContext('2d', {willReadFrequently: true});
if (savedCtx) { if (savedCtx) {
savedCtx.drawImage(maskCanvas, 0, 0); savedCtx.drawImage(maskCanvas, 0, 0);
} }

View File

@@ -83,11 +83,11 @@ export class MaskTool {
this.maxActiveChunks = 25; // Safety limit to prevent memory issues (5x5 grid max) this.maxActiveChunks = 25; // Safety limit to prevent memory issues (5x5 grid max)
// Create active mask canvas (composite of chunks) // Create active mask canvas (composite of chunks)
this.activeMaskCanvas = document.createElement('canvas'); const { canvas: activeMaskCanvas, ctx: activeMaskCtx } = createCanvas(1, 1, '2d', { willReadFrequently: true });
const activeMaskCtx = this.activeMaskCanvas.getContext('2d', { willReadFrequently: true });
if (!activeMaskCtx) { if (!activeMaskCtx) {
throw new Error("Failed to get 2D context for active mask canvas"); throw new Error("Failed to get 2D context for active mask canvas");
} }
this.activeMaskCanvas = activeMaskCanvas;
this.activeMaskCtx = activeMaskCtx; this.activeMaskCtx = activeMaskCtx;
this.x = 0; this.x = 0;
@@ -101,21 +101,21 @@ export class MaskTool {
this.isDrawing = false; this.isDrawing = false;
this.lastPosition = null; this.lastPosition = null;
this.previewCanvas = document.createElement('canvas'); const { canvas: previewCanvas, ctx: previewCtx } = createCanvas(1, 1, '2d', { willReadFrequently: true });
const previewCtx = this.previewCanvas.getContext('2d', { willReadFrequently: true });
if (!previewCtx) { if (!previewCtx) {
throw new Error("Failed to get 2D context for preview canvas"); throw new Error("Failed to get 2D context for preview canvas");
} }
this.previewCanvas = previewCanvas;
this.previewCtx = previewCtx; this.previewCtx = previewCtx;
this.previewVisible = false; this.previewVisible = false;
this.previewCanvasInitialized = false; this.previewCanvasInitialized = false;
// Initialize shape preview system // Initialize shape preview system
this.shapePreviewCanvas = document.createElement('canvas'); const { canvas: shapePreviewCanvas, ctx: shapePreviewCtx } = createCanvas(1, 1, '2d', { willReadFrequently: true });
const shapePreviewCtx = this.shapePreviewCanvas.getContext('2d', { willReadFrequently: true });
if (!shapePreviewCtx) { if (!shapePreviewCtx) {
throw new Error("Failed to get 2D context for shape preview canvas"); throw new Error("Failed to get 2D context for shape preview canvas");
} }
this.shapePreviewCanvas = shapePreviewCanvas;
this.shapePreviewCtx = shapePreviewCtx; this.shapePreviewCtx = shapePreviewCtx;
this.shapePreviewVisible = false; this.shapePreviewVisible = false;
this.isPreviewMode = false; this.isPreviewMode = false;
@@ -1558,10 +1558,7 @@ export class MaskTool {
const bounds = this.canvasInstance.outputAreaBounds; const bounds = this.canvasInstance.outputAreaBounds;
// Create canvas sized to output area // Create canvas sized to output area
const outputMaskCanvas = document.createElement('canvas'); const { canvas: outputMaskCanvas, ctx: outputMaskCtx } = createCanvas(bounds.width, bounds.height, '2d', { willReadFrequently: true });
outputMaskCanvas.width = bounds.width;
outputMaskCanvas.height = bounds.height;
const outputMaskCtx = outputMaskCanvas.getContext('2d', { willReadFrequently: true });
if (!outputMaskCtx) { if (!outputMaskCtx) {
throw new Error("Failed to get 2D context for output area mask canvas"); throw new Error("Failed to get 2D context for output area mask canvas");
@@ -1612,7 +1609,8 @@ export class MaskTool {
const isIncreasingWidth = width > this.canvasInstance.width; const isIncreasingWidth = width > this.canvasInstance.width;
const isIncreasingHeight = height > this.canvasInstance.height; const isIncreasingHeight = height > this.canvasInstance.height;
this.activeMaskCanvas = document.createElement('canvas'); const { canvas: activeMaskCanvas } = createCanvas(1, 1, '2d', { willReadFrequently: true });
this.activeMaskCanvas = activeMaskCanvas;
const extraSpace = 2000; const extraSpace = 2000;