From 9d0c946e229fc6f8f8942e8059b99a09336f9a52 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sun, 27 Jul 2025 18:34:46 +0200 Subject: [PATCH] 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. --- js/utils/ClipboardManager.js | 34 +++-------------------------- js/utils/IconLoader.js | 6 ++---- js/utils/ImageAnalysis.js | 11 +++------- js/utils/ImageUtils.js | 28 +++++++----------------- js/utils/MaskProcessingUtils.js | 21 +++++------------- src/utils/ClipboardManager.ts | 37 +++----------------------------- src/utils/IconLoader.ts | 6 ++---- src/utils/ImageAnalysis.ts | 11 +++------- src/utils/ImageUtils.ts | 33 +++++++--------------------- src/utils/MaskProcessingUtils.ts | 21 +++++------------- 10 files changed, 42 insertions(+), 166 deletions(-) diff --git a/js/utils/ClipboardManager.js b/js/utils/ClipboardManager.js index 229db7f..4685fd7 100644 --- a/js/utils/ClipboardManager.js +++ b/js/utils/ClipboardManager.js @@ -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); - } } diff --git a/js/utils/IconLoader.js b/js/utils/IconLoader.js index f572702..2764939 100644 --- a/js/utils/IconLoader.js +++ b/js/utils/IconLoader.js @@ -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; diff --git a/js/utils/ImageAnalysis.js b/js/utils/ImageAnalysis.js index 98ef650..45c7334 100644 --- a/js/utils/ImageAnalysis.js +++ b/js/utils/ImageAnalysis.js @@ -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; diff --git a/js/utils/ImageUtils.js b/js/utils/ImageUtils.js index 3dc3bc9..86b33c7 100644 --- a/js/utils/ImageUtils.js +++ b/js/utils/ImageUtils.js @@ -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; diff --git a/js/utils/MaskProcessingUtils.js b/js/utils/MaskProcessingUtils.js index 65cc3ce..5f4dcaf 100644 --- a/js/utils/MaskProcessingUtils.js +++ b/js/utils/MaskProcessingUtils.js @@ -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"); } diff --git a/src/utils/ClipboardManager.ts b/src/utils/ClipboardManager.ts index cf3b2f9..e38374c 100644 --- a/src/utils/ClipboardManager.ts +++ b/src/utils/ClipboardManager.ts @@ -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); - } } diff --git a/src/utils/IconLoader.ts b/src/utils/IconLoader.ts index 7eb82cb..9896924 100644 --- a/src/utils/IconLoader.ts +++ b/src/utils/IconLoader.ts @@ -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; diff --git a/src/utils/ImageAnalysis.ts b/src/utils/ImageAnalysis.ts index d808f0e..9bfda94 100644 --- a/src/utils/ImageAnalysis.ts +++ b/src/utils/ImageAnalysis.ts @@ -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'); diff --git a/src/utils/ImageUtils.ts b/src/utils/ImageUtils.ts index 7b074a0..a180cc8 100644 --- a/src/utils/ImageUtils.ts +++ b/src/utils/ImageUtils.ts @@ -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 } export const createEmptyImage = withErrorHandling(function (width: number, height: number, color = 'transparent'): Promise { - 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') { diff --git a/src/utils/MaskProcessingUtils.ts b/src/utils/MaskProcessingUtils.ts index 532a056..47f9060 100644 --- a/src/utils/MaskProcessingUtils.ts +++ b/src/utils/MaskProcessingUtils.ts @@ -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");