Refactor canvas creation to use createCanvas utility

Replaces direct usage of document.createElement('canvas') and manual context setup with the createCanvas utility across multiple utility modules. This change improves code consistency, reduces duplication, and centralizes canvas/context creation logic. Also updates notification usage in ClipboardManager to use showNotification and showInfoNotification utilities.
This commit is contained in:
Dariusz L
2025-07-27 18:34:46 +02:00
parent 7e71d3ec3e
commit 9d0c946e22
10 changed files with 42 additions and 166 deletions

View File

@@ -1,4 +1,5 @@
import { createModuleLogger } from "./LoggerUtils.js";
import { showNotification, showInfoNotification } from "./NotificationUtils.js";
// @ts-ignore
import { api } from "../../../scripts/api.js";
// @ts-ignore
@@ -352,7 +353,7 @@ export class ClipboardManager {
document.body.removeChild(fileInput);
resolve(false);
};
this.showNotification(`Detected image path: ${fileName}. Please select the file to load it.`, 3000);
showInfoNotification(`Detected image path: ${fileName}. Please select the file to load it.`, 3000);
document.body.appendChild(fileInput);
fileInput.click();
});
@@ -364,7 +365,7 @@ export class ClipboardManager {
showFilePathMessage(filePath) {
const fileName = filePath.split(/[\\\/]/).pop();
const message = `Cannot load local file directly due to browser security restrictions. File detected: ${fileName}`;
this.showNotification(message, 5000);
showNotification(message, "#c54747", 5000);
log.info("Showed file path limitation message to user");
}
/**
@@ -431,33 +432,4 @@ export class ClipboardManager {
}, 12000);
log.info("Showed enhanced empty clipboard message with file picker option");
}
/**
* Shows a temporary notification to the user
* @param {string} message - The message to show
* @param {number} duration - Duration in milliseconds
*/
showNotification(message, duration = 3000) {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #333;
color: white;
padding: 12px 16px;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
z-index: 10001;
max-width: 300px;
font-size: 14px;
line-height: 1.4;
`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, duration);
}
}

View File

@@ -1,4 +1,5 @@
import { createModuleLogger } from "./LoggerUtils.js";
import { createCanvas } from "./CommonUtils.js";
const log = createModuleLogger('IconLoader');
// Define tool constants for LayerForge
export const LAYERFORGE_TOOLS = {
@@ -114,10 +115,7 @@ export class IconLoader {
* Create a fallback canvas icon with colored background and text
*/
createFallbackIcon(tool) {
const canvas = document.createElement('canvas');
canvas.width = 24;
canvas.height = 24;
const ctx = canvas.getContext('2d');
const { canvas, ctx } = createCanvas(24, 24);
if (!ctx) {
log.error('Failed to get canvas context for fallback icon');
return canvas;

View File

@@ -1,4 +1,5 @@
import { createModuleLogger } from "./LoggerUtils.js";
import { createCanvas } from "./CommonUtils.js";
const log = createModuleLogger('ImageAnalysis');
/**
* Creates a distance field mask based on the alpha channel of an image.
@@ -8,10 +9,7 @@ const log = createModuleLogger('ImageAnalysis');
* @returns HTMLCanvasElement containing the distance field mask
*/
export function createDistanceFieldMask(image, blendArea) {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const { canvas, ctx } = createCanvas(image.width, image.height, '2d', { willReadFrequently: true });
if (!ctx) {
log.error('Failed to create canvas context for distance field mask');
return canvas;
@@ -186,10 +184,7 @@ function calculateDistanceFromEdges(width, height) {
* @returns HTMLCanvasElement containing the radial gradient mask
*/
export function createRadialGradientMask(width, height, blendArea) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const { canvas, ctx } = createCanvas(width, height);
if (!ctx) {
log.error('Failed to create canvas context for radial gradient mask');
return canvas;

View File

@@ -1,5 +1,6 @@
import { createModuleLogger } from "./LoggerUtils.js";
import { withErrorHandling, createValidationError } from "../ErrorHandler.js";
import { createCanvas } from "./CommonUtils.js";
const log = createModuleLogger('ImageUtils');
export function validateImageData(data) {
log.debug("Validating data structure:", {
@@ -126,10 +127,7 @@ export const imageToTensor = withErrorHandling(async function (image) {
if (!image) {
throw createValidationError("Image is required");
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = image.width;
canvas.height = image.height;
const { canvas, ctx } = createCanvas(image.width, image.height, '2d', { willReadFrequently: true });
if (ctx) {
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
@@ -154,10 +152,7 @@ export const tensorToImage = withErrorHandling(async function (tensor) {
throw createValidationError("Invalid tensor format", { tensor });
}
const [, height, width, channels] = tensor.shape;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = width;
canvas.height = height;
const { canvas, ctx } = createCanvas(width, height, '2d', { willReadFrequently: true });
if (ctx) {
const imageData = ctx.createImageData(width, height);
const data = tensor.data;
@@ -183,15 +178,12 @@ export const resizeImage = withErrorHandling(async function (image, maxWidth, ma
if (!image) {
throw createValidationError("Image is required");
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const originalWidth = image.width;
const originalHeight = image.height;
const scale = Math.min(maxWidth / originalWidth, maxHeight / originalHeight);
const newWidth = Math.round(originalWidth * scale);
const newHeight = Math.round(originalHeight * scale);
canvas.width = newWidth;
canvas.height = newHeight;
const { canvas, ctx } = createCanvas(newWidth, newHeight, '2d', { willReadFrequently: true });
if (ctx) {
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
@@ -212,10 +204,9 @@ export const imageToBase64 = withErrorHandling(function (image, format = 'png',
if (!image) {
throw createValidationError("Image is required");
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = image instanceof HTMLImageElement ? image.naturalWidth : image.width;
canvas.height = image instanceof HTMLImageElement ? image.naturalHeight : image.height;
const width = image instanceof HTMLImageElement ? image.naturalWidth : image.width;
const height = image instanceof HTMLImageElement ? image.naturalHeight : image.height;
const { canvas, ctx } = createCanvas(width, height, '2d', { willReadFrequently: true });
if (ctx) {
ctx.drawImage(image, 0, 0);
const mimeType = `image/${format}`;
@@ -262,10 +253,7 @@ export function createImageFromSource(source) {
});
}
export const createEmptyImage = withErrorHandling(function (width, height, color = 'transparent') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = width;
canvas.height = height;
const { canvas, ctx } = createCanvas(width, height, '2d', { willReadFrequently: true });
if (ctx) {
if (color !== 'transparent') {
ctx.fillStyle = color;

View File

@@ -1,4 +1,5 @@
import { createModuleLogger } from "./LoggerUtils.js";
import { createCanvas } from "./CommonUtils.js";
const log = createModuleLogger('MaskProcessingUtils');
/**
* Processes an image to create a mask with inverted alpha channel
@@ -15,10 +16,7 @@ export async function processImageToMask(sourceImage, options = {}) {
maskColor
});
// Create temporary canvas for processing
const tempCanvas = document.createElement('canvas');
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(targetWidth, targetHeight, '2d', { willReadFrequently: true });
if (!tempCtx) {
throw new Error("Failed to get 2D context for mask processing");
}
@@ -56,10 +54,7 @@ export async function processImageToMask(sourceImage, options = {}) {
*/
export async function processImageWithTransform(sourceImage, pixelTransform, options = {}) {
const { targetWidth = sourceImage.width, targetHeight = sourceImage.height } = options;
const tempCanvas = document.createElement('canvas');
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(targetWidth, targetHeight, '2d', { willReadFrequently: true });
if (!tempCtx) {
throw new Error("Failed to get 2D context for image processing");
}
@@ -105,10 +100,7 @@ export async function cropImage(sourceImage, cropArea) {
sourceSize: { width: sourceImage.width, height: sourceImage.height },
cropArea
});
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const { canvas, ctx } = createCanvas(width, height);
if (!ctx) {
throw new Error("Failed to get 2D context for image cropping");
}
@@ -132,10 +124,7 @@ export async function processMaskForViewport(maskImage, targetWidth, targetHeigh
targetSize: { width: targetWidth, height: targetHeight },
viewportOffset
});
const tempCanvas = document.createElement('canvas');
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(targetWidth, targetHeight, '2d', { willReadFrequently: true });
if (!tempCtx) {
throw new Error("Failed to get 2D context for viewport mask processing");
}

View File

@@ -1,4 +1,5 @@
import {createModuleLogger} from "./LoggerUtils.js";
import { showNotification, showInfoNotification } from "./NotificationUtils.js";
// @ts-ignore
import {api} from "../../../scripts/api.js";
@@ -401,7 +402,7 @@ export class ClipboardManager {
resolve(false);
};
this.showNotification(`Detected image path: ${fileName}. Please select the file to load it.`, 3000);
showInfoNotification(`Detected image path: ${fileName}. Please select the file to load it.`, 3000);
document.body.appendChild(fileInput);
fileInput.click();
@@ -415,7 +416,7 @@ export class ClipboardManager {
showFilePathMessage(filePath: string): void {
const fileName = filePath.split(/[\\\/]/).pop();
const message = `Cannot load local file directly due to browser security restrictions. File detected: ${fileName}`;
this.showNotification(message, 5000);
showNotification(message, "#c54747", 5000);
log.info("Showed file path limitation message to user");
}
@@ -489,36 +490,4 @@ export class ClipboardManager {
log.info("Showed enhanced empty clipboard message with file picker option");
}
/**
* Shows a temporary notification to the user
* @param {string} message - The message to show
* @param {number} duration - Duration in milliseconds
*/
showNotification(message: string, duration = 3000): void {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #333;
color: white;
padding: 12px 16px;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
z-index: 10001;
max-width: 300px;
font-size: 14px;
line-height: 1.4;
`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, duration);
}
}

View File

@@ -1,4 +1,5 @@
import { createModuleLogger } from "./LoggerUtils.js";
import { createCanvas } from "./CommonUtils.js";
const log = createModuleLogger('IconLoader');
@@ -147,11 +148,8 @@ export class IconLoader {
* Create a fallback canvas icon with colored background and text
*/
private createFallbackIcon(tool: string): HTMLCanvasElement {
const canvas = document.createElement('canvas');
canvas.width = 24;
canvas.height = 24;
const { canvas, ctx } = createCanvas(24, 24);
const ctx = canvas.getContext('2d');
if (!ctx) {
log.error('Failed to get canvas context for fallback icon');
return canvas;

View File

@@ -1,4 +1,5 @@
import { createModuleLogger } from "./LoggerUtils.js";
import { createCanvas } from "./CommonUtils.js";
const log = createModuleLogger('ImageAnalysis');
@@ -10,10 +11,7 @@ const log = createModuleLogger('ImageAnalysis');
* @returns HTMLCanvasElement containing the distance field mask
*/
export function createDistanceFieldMask(image: HTMLImageElement, blendArea: number): HTMLCanvasElement {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const { canvas, ctx } = createCanvas(image.width, image.height, '2d', { willReadFrequently: true });
if (!ctx) {
log.error('Failed to create canvas context for distance field mask');
@@ -217,10 +215,7 @@ function calculateDistanceFromEdges(width: number, height: number): Float32Array
* @returns HTMLCanvasElement containing the radial gradient mask
*/
export function createRadialGradientMask(width: number, height: number, blendArea: number): HTMLCanvasElement {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const { canvas, ctx } = createCanvas(width, height);
if (!ctx) {
log.error('Failed to create canvas context for radial gradient mask');

View File

@@ -1,5 +1,6 @@
import {createModuleLogger} from "./LoggerUtils.js";
import {withErrorHandling, createValidationError} from "../ErrorHandler.js";
import { createCanvas } from "./CommonUtils.js";
import type { Tensor, ImageDataPixel } from '../types';
const log = createModuleLogger('ImageUtils');
@@ -163,11 +164,7 @@ export const imageToTensor = withErrorHandling(async function (image: HTMLImageE
throw createValidationError("Image is required");
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = image.width;
canvas.height = image.height;
const { canvas, ctx } = createCanvas(image.width, image.height, '2d', { willReadFrequently: true });
if (ctx) {
ctx.drawImage(image, 0, 0);
@@ -197,11 +194,7 @@ export const tensorToImage = withErrorHandling(async function (tensor: Tensor):
}
const [, height, width, channels] = tensor.shape;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = width;
canvas.height = height;
const { canvas, ctx } = createCanvas(width, height, '2d', { willReadFrequently: true });
if (ctx) {
const imageData = ctx.createImageData(width, height);
@@ -234,17 +227,13 @@ export const resizeImage = withErrorHandling(async function (image: HTMLImageEle
throw createValidationError("Image is required");
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const originalWidth = image.width;
const originalHeight = image.height;
const scale = Math.min(maxWidth / originalWidth, maxHeight / originalHeight);
const newWidth = Math.round(originalWidth * scale);
const newHeight = Math.round(originalHeight * scale);
canvas.width = newWidth;
canvas.height = newHeight;
const { canvas, ctx } = createCanvas(newWidth, newHeight, '2d', { willReadFrequently: true });
if (ctx) {
ctx.imageSmoothingEnabled = true;
@@ -270,11 +259,9 @@ export const imageToBase64 = withErrorHandling(function (image: HTMLImageElement
throw createValidationError("Image is required");
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = image instanceof HTMLImageElement ? image.naturalWidth : image.width;
canvas.height = image instanceof HTMLImageElement ? image.naturalHeight : image.height;
const width = image instanceof HTMLImageElement ? image.naturalWidth : image.width;
const height = image instanceof HTMLImageElement ? image.naturalHeight : image.height;
const { canvas, ctx } = createCanvas(width, height, '2d', { willReadFrequently: true });
if (ctx) {
ctx.drawImage(image, 0, 0);
@@ -330,11 +317,7 @@ export function createImageFromSource(source: string): Promise<HTMLImageElement>
}
export const createEmptyImage = withErrorHandling(function (width: number, height: number, color = 'transparent'): Promise<HTMLImageElement> {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = width;
canvas.height = height;
const { canvas, ctx } = createCanvas(width, height, '2d', { willReadFrequently: true });
if (ctx) {
if (color !== 'transparent') {

View File

@@ -1,4 +1,5 @@
import { createModuleLogger } from "./LoggerUtils.js";
import { createCanvas } from "./CommonUtils.js";
const log = createModuleLogger('MaskProcessingUtils');
@@ -42,10 +43,7 @@ export async function processImageToMask(
});
// Create temporary canvas for processing
const tempCanvas = document.createElement('canvas');
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(targetWidth, targetHeight, '2d', { willReadFrequently: true });
if (!tempCtx) {
throw new Error("Failed to get 2D context for mask processing");
@@ -99,10 +97,7 @@ export async function processImageWithTransform(
targetHeight = sourceImage.height
} = options;
const tempCanvas = document.createElement('canvas');
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(targetWidth, targetHeight, '2d', { willReadFrequently: true });
if (!tempCtx) {
throw new Error("Failed to get 2D context for image processing");
@@ -162,10 +157,7 @@ export async function cropImage(
cropArea
});
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const { canvas, ctx } = createCanvas(width, height);
if (!ctx) {
throw new Error("Failed to get 2D context for image cropping");
@@ -202,10 +194,7 @@ export async function processMaskForViewport(
viewportOffset
});
const tempCanvas = document.createElement('canvas');
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(targetWidth, targetHeight, '2d', { willReadFrequently: true });
if (!tempCtx) {
throw new Error("Failed to get 2D context for viewport mask processing");