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");