@pixerate/editor 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/image-editor/index.cjs +732 -497
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +105 -9
- package/dist/image-editor/index.d.ts +105 -9
- package/dist/image-editor/index.js +721 -486
- package/dist/image-editor/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/image-editor/index.cjs","../../src/image-editor/filters.ts","../../src/image-editor/renderer.ts","../../src/image-editor/controller.ts","../../src/image-editor/geometry.ts"],"names":[],"mappings":"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","file":"/home/runner/work/editor/editor/packages/core/dist/image-editor/index.cjs","sourcesContent":[null,"import type { DepthMaskOptions, ImageAdjustments } from './types';\n\nfunction clamp(value: number, min = 0, max = 255): number {\n return Math.max(min, Math.min(max, value));\n}\n\n/**\n * Applies color and tone adjustments (brightness, contrast, saturation, exposure, temperature, opacity, blur)\n * directly to the pixel data of an ImageData buffer.\n */\nexport function applyAdjustments(imageData: ImageData, adjustments: ImageAdjustments): void {\n const { brightness, contrast, saturation, exposure, temperature, opacity, blur } = adjustments;\n const data = imageData.data;\n const len = data.length;\n\n const hasBrightness = brightness !== 0;\n const hasContrast = contrast !== 0;\n const hasSaturation = saturation !== 0;\n const hasExposure = exposure !== 0;\n const hasTemperature = temperature !== 0;\n const hasOpacity = opacity < 1;\n\n // Pre-calculate contrast factor\n const contrastFactor = hasContrast\n ? (259 * (contrast + 255)) / (255 * (259 - contrast))\n : 1;\n\n // Pre-calculate exposure multiplier\n const exposureMultiplier = hasExposure ? Math.pow(2, exposure / 50) : 1;\n\n // Pre-calculate saturation multiplier\n const satMultiplier = hasSaturation ? 1 + saturation / 100 : 1;\n\n // Pre-calculate temperature offsets\n const tempOffset = hasTemperature ? temperature * 0.8 : 0;\n\n if (hasBrightness || hasContrast || hasSaturation || hasExposure || hasTemperature || hasOpacity) {\n for (let i = 0; i < len; i += 4) {\n let r = data[i];\n let g = data[i + 1];\n let b = data[i + 2];\n let a = data[i + 3];\n\n // Exposure\n if (hasExposure) {\n r = clamp(r * exposureMultiplier);\n g = clamp(g * exposureMultiplier);\n b = clamp(b * exposureMultiplier);\n }\n\n // Brightness\n if (hasBrightness) {\n const bOffset = brightness * 2.55;\n r = clamp(r + bOffset);\n g = clamp(g + bOffset);\n b = clamp(b + bOffset);\n }\n\n // Contrast\n if (hasContrast) {\n r = clamp(contrastFactor * (r - 128) + 128);\n g = clamp(contrastFactor * (g - 128) + 128);\n b = clamp(contrastFactor * (b - 128) + 128);\n }\n\n // Saturation\n if (hasSaturation) {\n const luminance = 0.2989 * r + 0.587 * g + 0.114 * b;\n r = clamp(luminance + (r - luminance) * satMultiplier);\n g = clamp(luminance + (g - luminance) * satMultiplier);\n b = clamp(luminance + (b - luminance) * satMultiplier);\n }\n\n // Temperature / Warmth\n if (hasTemperature) {\n r = clamp(r + tempOffset);\n b = clamp(b - tempOffset);\n }\n\n // Opacity\n if (hasOpacity) {\n a = clamp(a * opacity);\n }\n\n data[i] = r;\n data[i + 1] = g;\n data[i + 2] = b;\n data[i + 3] = a;\n }\n }\n\n // Apply blur if requested\n if (blur > 0) {\n applyFastBoxBlur(imageData, Math.min(Math.round(blur), 40));\n }\n}\n\n/**\n * Fast separable box blur running horizontally and vertically.\n */\nfunction applyFastBoxBlur(imageData: ImageData, radius: number): void {\n if (radius < 1) return;\n const w = imageData.width;\n const h = imageData.height;\n const src = imageData.data;\n const temp = new Uint8ClampedArray(src.length);\n\n // Horizontal blur pass\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n let r = 0, g = 0, b = 0, a = 0;\n let count = 0;\n for (let k = -radius; k <= radius; k++) {\n const px = Math.min(w - 1, Math.max(0, x + k));\n const idx = (y * w + px) * 4;\n r += src[idx];\n g += src[idx + 1];\n b += src[idx + 2];\n a += src[idx + 3];\n count++;\n }\n const dstIdx = (y * w + x) * 4;\n temp[dstIdx] = Math.round(r / count);\n temp[dstIdx + 1] = Math.round(g / count);\n temp[dstIdx + 2] = Math.round(b / count);\n temp[dstIdx + 3] = Math.round(a / count);\n }\n }\n\n // Vertical blur pass back into src\n for (let x = 0; x < w; x++) {\n for (let y = 0; y < h; y++) {\n let r = 0, g = 0, b = 0, a = 0;\n let count = 0;\n for (let k = -radius; k <= radius; k++) {\n const py = Math.min(h - 1, Math.max(0, y + k));\n const idx = (py * w + x) * 4;\n r += temp[idx];\n g += temp[idx + 1];\n b += temp[idx + 2];\n a += temp[idx + 3];\n count++;\n }\n const dstIdx = (y * w + x) * 4;\n src[dstIdx] = Math.round(r / count);\n src[dstIdx + 1] = Math.round(g / count);\n src[dstIdx + 2] = Math.round(b / count);\n src[dstIdx + 3] = Math.round(a / count);\n }\n }\n}\n\n/**\n * Blends a depth/alpha mask into the main image data based on Gleamforge depth masking.\n * Supports threshold range with softness gradation, brightness mode, and optional inversion.\n */\nexport function applyDepthMask(\n mainImageData: ImageData,\n maskImageData: ImageData,\n options: DepthMaskOptions\n): void {\n const { mode, depthRange, softness, invert } = options;\n const mainData = mainImageData.data;\n const maskData = maskImageData.data;\n const len = Math.min(mainData.length, maskData.length);\n const effectiveSoftness = Math.max(1, softness || 10);\n const minDepth = depthRange[0];\n const maxDepth = depthRange[1];\n\n for (let i = 0; i < len; i += 4) {\n let maskValue = maskData[i]; // Use red channel of mask (or grayscale)\n if (invert) {\n maskValue = 255 - maskValue;\n }\n\n let alphaValue: number;\n\n if (mode === 'threshold') {\n if (maskValue >= maxDepth + effectiveSoftness) {\n alphaValue = 0;\n } else if (maskValue > maxDepth) {\n alphaValue = Math.round(((maxDepth + effectiveSoftness - maskValue) / effectiveSoftness) * 255);\n } else if (maskValue >= minDepth) {\n alphaValue = 255;\n } else if (maskValue < minDepth - effectiveSoftness) {\n alphaValue = 0;\n } else {\n alphaValue = Math.round(((maskValue - (minDepth - effectiveSoftness)) / effectiveSoftness) * 255);\n }\n } else {\n // Brightness mode\n alphaValue = clamp(maskValue + minDepth);\n }\n\n // Multiply existing alpha with mask alpha\n mainData[i + 3] = clamp((mainData[i + 3] * alphaValue) / 255);\n }\n}\n\n/**\n * Removes alpha channel by setting all alpha bytes to 255 (completely opaque).\n * From Gleamforge image-filters.ts.\n */\nexport function removeAlpha(imageData: ImageData): void {\n const data = imageData.data;\n const len = data.length;\n for (let i = 3; i < len; i += 4) {\n data[i] = 255;\n }\n}\n","import type {\n Annotation,\n CropBox,\n ExportOptions,\n ImageEditorState,\n ImageTransform,\n PenAnnotation,\n RectAnnotation,\n CircleAnnotation,\n ArrowAnnotation,\n LineAnnotation,\n TextAnnotation,\n ImageAnnotation,\n} from './types';\nimport { applyAdjustments, applyDepthMask } from './filters';\n\nexport interface RenderOptions {\n viewportWidth?: number;\n viewportHeight?: number;\n showCropOverlay?: boolean;\n showSelectionOverlay?: boolean;\n interactive?: boolean;\n draftAnnotation?: Annotation | null;\n draftCrop?: CropBox | null;\n}\n\n/**\n * Image Cache to avoid refetching HTMLImageElement objects repeatedly during re-renders.\n */\nclass ImageCache {\n private cache = new Map<string, HTMLImageElement>();\n\n public async get(src: string): Promise<HTMLImageElement> {\n const existing = this.cache.get(src);\n if (existing && existing.complete) {\n return existing;\n }\n\n return new Promise((resolve, reject) => {\n if (typeof window === 'undefined' || typeof Image === 'undefined') {\n reject(new Error('Image loading is only supported in browser/DOM environments'));\n return;\n }\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n this.cache.set(src, img);\n resolve(img);\n };\n img.onerror = (e) => reject(new Error(`Failed to load image from: ${src}`));\n img.src = src;\n\n // In JSDOM or test environments where Image resource loading is inactive, resolve promptly\n if (typeof navigator !== 'undefined' && /jsdom/i.test(navigator.userAgent)) {\n setTimeout(() => {\n try {\n Object.defineProperty(img, 'naturalWidth', { value: 800, configurable: true });\n Object.defineProperty(img, 'naturalHeight', { value: 600, configurable: true });\n Object.defineProperty(img, 'complete', { value: true, configurable: true });\n } catch {\n // Ignore definition errors in restrictive environments\n }\n this.cache.set(src, img);\n resolve(img);\n }, 10);\n }\n });\n }\n\n public set(src: string, img: HTMLImageElement): void {\n this.cache.set(src, img);\n }\n\n public clear(): void {\n this.cache.clear();\n }\n}\n\nexport const globalImageCache = new ImageCache();\n\n/**\n * Renders an image with all its transformations, filters, depth masks, and annotations\n * to a target HTMLCanvasElement.\n */\nexport class ImageEditorRenderer {\n public imageCache = globalImageCache;\n private cachedBaseCanvas: HTMLCanvasElement | null = null;\n private cachedBaseKey = '';\n\n /**\n * Helper to create a canvas element safely in browser/JSDOM.\n */\n public createCanvas(width: number, height: number): HTMLCanvasElement {\n if (typeof document !== 'undefined' && document.createElement) {\n const c = document.createElement('canvas');\n c.width = Math.max(1, Math.round(width));\n c.height = Math.max(1, Math.round(height));\n return c;\n }\n // Fallback for non-DOM / headless\n return {\n width: Math.max(1, Math.round(width)),\n height: Math.max(1, Math.round(height)),\n getContext: () => null,\n toDataURL: () => '',\n toBlob: () => {},\n } as unknown as HTMLCanvasElement;\n }\n\n /**\n * Renders the editor state onto a canvas (viewport or preview).\n */\n public async renderToCanvas(\n targetCanvas: HTMLCanvasElement,\n imageSource: CanvasImageSource | string,\n state: ImageEditorState,\n options: RenderOptions = {}\n ): Promise<void> {\n const ctx = targetCanvas.getContext('2d');\n if (!ctx) return;\n\n let img: CanvasImageSource;\n if (typeof imageSource === 'string') {\n img = await this.imageCache.get(imageSource);\n } else {\n img = imageSource;\n }\n\n const naturalWidth = (img as any).naturalWidth || (img as any).videoWidth || (img as any).width || 800;\n const naturalHeight = (img as any).naturalHeight || (img as any).videoHeight || (img as any).height || 600;\n\n const { crop, transform, adjustments, depthMask, annotations, selectedAnnotationId, zoom, pan, isOriginalCompared } = state;\n\n // Viewport dimensions\n const vpWidth = options.viewportWidth || targetCanvas.width;\n const vpHeight = options.viewportHeight || targetCanvas.height;\n\n targetCanvas.width = vpWidth;\n targetCanvas.height = vpHeight;\n\n ctx.clearRect(0, 0, vpWidth, vpHeight);\n\n // If in compare mode, render original image directly and exit\n if (isOriginalCompared) {\n this.drawFittedImage(ctx, img, naturalWidth, naturalHeight, vpWidth, vpHeight, zoom, pan);\n return;\n }\n\n // Determine working base dimensions based on crop or natural\n // When actively in crop tool or overlay requested, show full image so user can adjust the crop box\n const isActivelyCropping = Boolean(options.showCropOverlay || state.activeTool === 'crop');\n const cropX = (!isActivelyCropping && crop) ? Math.max(0, crop.x) : 0;\n const cropY = (!isActivelyCropping && crop) ? Math.max(0, crop.y) : 0;\n const cropW = (!isActivelyCropping && crop) ? Math.min(naturalWidth - cropX, crop.width) : naturalWidth;\n const cropH = (!isActivelyCropping && crop) ? Math.min(naturalHeight - cropY, crop.height) : naturalHeight;\n\n const angle = ((transform.rotate % 360) + 360) % 360;\n const isSwapped = angle === 90 || angle === 270;\n const baseW = isSwapped ? cropH : cropW;\n const baseH = isSwapped ? cropW : cropH;\n\n // 1. Offscreen canvas for Base Image + Transform (Flip/Rotate) + Crop + Adjustments\n const baseKey = `${state.sourceUrl}-${naturalWidth}x${naturalHeight}-${cropX},${cropY},${cropW},${cropH}-${angle},${transform.flipH},${transform.flipV}-${adjustments.brightness},${adjustments.contrast},${adjustments.saturation},${adjustments.exposure},${adjustments.temperature},${adjustments.blur},${adjustments.opacity}-${depthMask.enabled},${depthMask.depthRange?.[0]}-${depthMask.depthRange?.[1]},${depthMask.softness},${depthMask.invert},${depthMask.mode}`;\n\n let baseCanvas = this.cachedBaseCanvas;\n if (!baseCanvas || this.cachedBaseKey !== baseKey) {\n baseCanvas = this.createCanvas(baseW, baseH);\n const baseCtx = baseCanvas.getContext('2d');\n\n if (baseCtx) {\n baseCtx.save();\n\n // Handle Flip & Rotate\n const rad = (angle * Math.PI) / 180;\n baseCtx.translate(baseW / 2, baseH / 2);\n baseCtx.rotate(rad);\n baseCtx.scale(transform.flipH ? -1 : 1, transform.flipV ? -1 : 1);\n\n // Draw the cropped portion centered\n baseCtx.drawImage(\n img,\n cropX, cropY, cropW, cropH,\n -cropW / 2, -cropH / 2, cropW, cropH\n );\n\n baseCtx.restore();\n\n // 2. Pixel adjustments & Depth Masking\n const imageData = baseCtx.getImageData(0, 0, baseW, baseH);\n if (imageData) {\n applyAdjustments(imageData, adjustments);\n\n if (depthMask.enabled && depthMask.maskSource) {\n try {\n const maskImg = await this.imageCache.get(depthMask.maskSource);\n const maskCanvas = this.createCanvas(baseW, baseH);\n const maskCtx = maskCanvas.getContext('2d');\n if (maskCtx) {\n maskCtx.drawImage(maskImg, cropX, cropY, cropW, cropH, 0, 0, baseW, baseH);\n const maskData = maskCtx.getImageData(0, 0, baseW, baseH);\n if (maskData) {\n applyDepthMask(imageData, maskData, depthMask);\n }\n }\n } catch {\n // Mask failed to load; continue without crashing\n }\n }\n\n baseCtx.putImageData(imageData, 0, 0);\n }\n }\n this.cachedBaseCanvas = baseCanvas;\n this.cachedBaseKey = baseKey;\n }\n\n // 3. Render base canvas onto target canvas with viewport zoom & pan\n ctx.save();\n // Center of canvas\n ctx.translate(vpWidth / 2 + pan.x, vpHeight / 2 + pan.y);\n ctx.scale(zoom, zoom);\n\n // Calculate fitted dimensions\n const fitScale = Math.min((vpWidth * 0.85) / baseW, (vpHeight * 0.85) / baseH, 1);\n const renderW = baseW * fitScale;\n const renderH = baseH * fitScale;\n\n // Draw base image\n ctx.drawImage(baseCanvas, -renderW / 2, -renderH / 2, renderW, renderH);\n\n // 4. Render Annotations scaled to fit image space\n const scaleFactor = fitScale;\n ctx.save();\n ctx.translate(-renderW / 2, -renderH / 2);\n ctx.scale(scaleFactor, scaleFactor);\n\n for (const ann of annotations) {\n await this.drawAnnotation(ctx, ann);\n if (options.showSelectionOverlay && ann.id === selectedAnnotationId) {\n this.drawAnnotationSelection(ctx, ann);\n }\n }\n\n // Render active live draft annotation\n if (options.draftAnnotation) {\n await this.drawAnnotation(ctx, options.draftAnnotation);\n }\n\n ctx.restore();\n\n ctx.restore();\n\n // 5. Draw Crop Grid Overlay if crop overlay is requested\n const isCroppingActive = Boolean(options.showCropOverlay || state.activeTool === 'crop');\n const activeCrop =\n (options.draftCrop !== undefined && options.draftCrop !== null)\n ? options.draftCrop\n : (crop ?? (isCroppingActive ? {\n x: 0,\n y: 0,\n width: naturalWidth,\n height: naturalHeight,\n } : null));\n\n if (isCroppingActive && activeCrop) {\n this.drawCropOverlay(ctx, state, vpWidth, vpHeight, naturalWidth, naturalHeight, activeCrop);\n }\n }\n\n /**\n * Draws an individual annotation onto the canvas.\n */\n public async drawAnnotation(ctx: CanvasRenderingContext2D, ann: Annotation): Promise<void> {\n ctx.save();\n if (ann.opacity !== undefined) {\n ctx.globalAlpha = ann.opacity;\n }\n\n if (ann.rotation) {\n ctx.translate(ann.x, ann.y);\n ctx.rotate((ann.rotation * Math.PI) / 180);\n ctx.translate(-ann.x, -ann.y);\n }\n\n switch (ann.type) {\n case 'pen': {\n const pen = ann as PenAnnotation;\n if (pen.points.length > 0) {\n ctx.beginPath();\n ctx.strokeStyle = pen.strokeColor;\n ctx.lineWidth = pen.strokeWidth;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n ctx.moveTo(pen.points[0].x, pen.points[0].y);\n for (let i = 1; i < pen.points.length; i++) {\n ctx.lineTo(pen.points[i].x, pen.points[i].y);\n }\n ctx.stroke();\n }\n break;\n }\n\n case 'rect': {\n const rect = ann as RectAnnotation;\n ctx.strokeStyle = rect.strokeColor;\n ctx.lineWidth = rect.strokeWidth;\n if (rect.fillColor) {\n ctx.fillStyle = rect.fillColor;\n ctx.fillRect(rect.x, rect.y, rect.width, rect.height);\n }\n if (rect.strokeWidth > 0) {\n ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);\n }\n break;\n }\n\n case 'circle': {\n const circle = ann as CircleAnnotation;\n ctx.beginPath();\n ctx.strokeStyle = circle.strokeColor;\n ctx.lineWidth = circle.strokeWidth;\n ctx.ellipse(circle.x, circle.y, Math.abs(circle.radiusX), Math.abs(circle.radiusY), 0, 0, 2 * Math.PI);\n if (circle.fillColor) {\n ctx.fillStyle = circle.fillColor;\n ctx.fill();\n }\n if (circle.strokeWidth > 0) {\n ctx.stroke();\n }\n break;\n }\n\n case 'arrow': {\n const arrow = ann as ArrowAnnotation;\n ctx.strokeStyle = arrow.strokeColor;\n ctx.fillStyle = arrow.strokeColor;\n ctx.lineWidth = arrow.strokeWidth;\n\n // Line body\n ctx.beginPath();\n ctx.moveTo(arrow.x, arrow.y);\n ctx.lineTo(arrow.endX, arrow.endY);\n ctx.stroke();\n\n // Arrow head\n const angle = Math.atan2(arrow.endY - arrow.y, arrow.endX - arrow.x);\n const headLen = Math.max(12, arrow.strokeWidth * 3);\n ctx.beginPath();\n ctx.moveTo(arrow.endX, arrow.endY);\n ctx.lineTo(\n arrow.endX - headLen * Math.cos(angle - Math.PI / 6),\n arrow.endY - headLen * Math.sin(angle - Math.PI / 6)\n );\n ctx.lineTo(\n arrow.endX - headLen * Math.cos(angle + Math.PI / 6),\n arrow.endY - headLen * Math.sin(angle + Math.PI / 6)\n );\n ctx.closePath();\n ctx.fill();\n break;\n }\n\n case 'line': {\n const line = ann as LineAnnotation;\n ctx.beginPath();\n ctx.strokeStyle = line.strokeColor;\n ctx.lineWidth = line.strokeWidth;\n ctx.moveTo(line.x, line.y);\n ctx.lineTo(line.endX, line.endY);\n ctx.stroke();\n break;\n }\n\n case 'text': {\n const textAnn = ann as TextAnnotation;\n ctx.font = `${textAnn.fontWeight || 'normal'} ${textAnn.fontSize}px ${textAnn.fontFamily || 'sans-serif'}`;\n ctx.fillStyle = textAnn.color;\n ctx.textBaseline = 'top';\n ctx.fillText(textAnn.text, textAnn.x, textAnn.y);\n break;\n }\n\n case 'image': {\n const imgAnn = ann as ImageAnnotation;\n try {\n const loadedImg = await this.imageCache.get(imgAnn.src);\n ctx.drawImage(loadedImg, imgAnn.x, imgAnn.y, imgAnn.width, imgAnn.height);\n } catch {\n // Skip if watermark image failed to load\n }\n break;\n }\n }\n\n ctx.restore();\n }\n\n /**\n * Draws selection box around active annotation.\n */\n private drawAnnotationSelection(ctx: CanvasRenderingContext2D, ann: Annotation): void {\n ctx.save();\n ctx.strokeStyle = '#3b82f6'; // Tailwind blue-500\n ctx.lineWidth = 1.5;\n ctx.setLineDash([4, 4]);\n\n const bounds = this.getAnnotationBounds(ann);\n const pad = 4;\n ctx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.width + pad * 2, bounds.height + pad * 2);\n\n // Draw handles\n ctx.fillStyle = '#ffffff';\n ctx.strokeStyle = '#3b82f6';\n ctx.setLineDash([]);\n const handleSize = 6;\n const corners = [\n { x: bounds.x - pad, y: bounds.y - pad },\n { x: bounds.x + bounds.width + pad, y: bounds.y - pad },\n { x: bounds.x - pad, y: bounds.y + bounds.height + pad },\n { x: bounds.x + bounds.width + pad, y: bounds.y + bounds.height + pad },\n ];\n for (const c of corners) {\n ctx.fillRect(c.x - handleSize / 2, c.y - handleSize / 2, handleSize, handleSize);\n ctx.strokeRect(c.x - handleSize / 2, c.y - handleSize / 2, handleSize, handleSize);\n }\n\n ctx.restore();\n }\n\n public getAnnotationBounds(ann: Annotation): { x: number; y: number; width: number; height: number } {\n switch (ann.type) {\n case 'rect':\n return { x: ann.x, y: ann.y, width: ann.width, height: ann.height };\n case 'circle':\n return {\n x: ann.x - Math.abs(ann.radiusX),\n y: ann.y - Math.abs(ann.radiusY),\n width: Math.abs(ann.radiusX) * 2,\n height: Math.abs(ann.radiusY) * 2,\n };\n case 'arrow':\n case 'line': {\n const minX = Math.min(ann.x, ann.endX);\n const minY = Math.min(ann.y, ann.endY);\n return {\n x: minX,\n y: minY,\n width: Math.max(Math.abs(ann.endX - ann.x), 10),\n height: Math.max(Math.abs(ann.endY - ann.y), 10),\n };\n }\n case 'pen': {\n if (!ann.points.length) return { x: ann.x, y: ann.y, width: 0, height: 0 };\n let minX = ann.points[0].x, maxX = ann.points[0].x;\n let minY = ann.points[0].y, maxY = ann.points[0].y;\n for (const pt of ann.points) {\n if (pt.x < minX) minX = pt.x;\n if (pt.x > maxX) maxX = pt.x;\n if (pt.y < minY) minY = pt.y;\n if (pt.y > maxY) maxY = pt.y;\n }\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n }\n case 'text':\n return {\n x: ann.x,\n y: ann.y,\n width: (ann.text.length * ann.fontSize * 0.6),\n height: ann.fontSize * 1.2,\n };\n case 'image':\n return { x: ann.x, y: ann.y, width: ann.width, height: ann.height };\n }\n }\n\n /**\n * Draws a rule-of-thirds crop grid overlay.\n */\n private drawCropOverlay(\n ctx: CanvasRenderingContext2D,\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n imgWidth: number,\n imgHeight: number,\n cropOverride?: CropBox | null\n ): void {\n const crop =\n (cropOverride !== undefined && cropOverride !== null)\n ? cropOverride\n : (state.crop ?? {\n x: 0,\n y: 0,\n width: imgWidth,\n height: imgHeight,\n });\n if (!crop) return;\n\n const { transform, pan, zoom } = state;\n const angle = ((transform.rotate % 360) + 360) % 360;\n const isSwapped = angle === 90 || angle === 270;\n const baseW = isSwapped ? imgHeight : imgWidth;\n const baseH = isSwapped ? imgWidth : imgHeight;\n\n ctx.save();\n ctx.translate(vpWidth / 2 + pan.x, vpHeight / 2 + pan.y);\n ctx.scale(zoom, zoom);\n\n const fitScale = Math.min((vpWidth * 0.85) / baseW, (vpHeight * 0.85) / baseH, 1);\n const renderW = baseW * fitScale;\n const renderH = baseH * fitScale;\n\n const startX = -renderW / 2 + (crop.x / imgWidth) * renderW;\n const startY = -renderH / 2 + (crop.y / imgHeight) * renderH;\n const cropBoxW = (crop.width / imgWidth) * renderW;\n const cropBoxH = (crop.height / imgHeight) * renderH;\n\n // Dark semi-transparent scrim outside crop box\n ctx.fillStyle = 'rgba(0, 0, 0, 0.45)';\n // Top\n ctx.fillRect(-renderW / 2, -renderH / 2, renderW, startY - (-renderH / 2));\n // Bottom\n ctx.fillRect(-renderW / 2, startY + cropBoxH, renderW, renderH / 2 - (startY + cropBoxH));\n // Left\n ctx.fillRect(-renderW / 2, startY, startX - (-renderW / 2), cropBoxH);\n // Right\n ctx.fillRect(startX + cropBoxW, startY, renderW / 2 - (startX + cropBoxW), cropBoxH);\n\n // Crop border\n ctx.strokeStyle = '#ffffff';\n ctx.lineWidth = 2;\n ctx.strokeRect(startX, startY, cropBoxW, cropBoxH);\n\n // Rule of thirds lines\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';\n ctx.lineWidth = 1;\n ctx.beginPath();\n // Vertical lines\n ctx.moveTo(startX + cropBoxW / 3, startY);\n ctx.lineTo(startX + cropBoxW / 3, startY + cropBoxH);\n ctx.moveTo(startX + (cropBoxW * 2) / 3, startY);\n ctx.lineTo(startX + (cropBoxW * 2) / 3, startY + cropBoxH);\n // Horizontal lines\n ctx.moveTo(startX, startY + cropBoxH / 3);\n ctx.lineTo(startX + cropBoxW, startY + cropBoxH / 3);\n ctx.moveTo(startX, startY + (cropBoxH * 2) / 3);\n ctx.lineTo(startX + cropBoxW, startY + (cropBoxH * 2) / 3);\n ctx.stroke();\n\n // Corner and edge handles\n ctx.fillStyle = '#ffffff';\n ctx.strokeStyle = '#0284c7';\n ctx.lineWidth = 1.5;\n const handleSize = 10;\n const handles = [\n // Corners\n { x: startX, y: startY },\n { x: startX + cropBoxW, y: startY },\n { x: startX, y: startY + cropBoxH },\n { x: startX + cropBoxW, y: startY + cropBoxH },\n // Edges\n { x: startX + cropBoxW / 2, y: startY },\n { x: startX + cropBoxW / 2, y: startY + cropBoxH },\n { x: startX, y: startY + cropBoxH / 2 },\n { x: startX + cropBoxW, y: startY + cropBoxH / 2 },\n ];\n for (const h of handles) {\n ctx.fillRect(h.x - handleSize / 2, h.y - handleSize / 2, handleSize, handleSize);\n ctx.strokeRect(h.x - handleSize / 2, h.y - handleSize / 2, handleSize, handleSize);\n }\n\n ctx.restore();\n }\n\n private drawFittedImage(\n ctx: CanvasRenderingContext2D,\n img: CanvasImageSource,\n imgW: number,\n imgH: number,\n vpW: number,\n vpH: number,\n zoom: number,\n pan: { x: number; y: number }\n ): void {\n ctx.save();\n ctx.translate(vpW / 2 + pan.x, vpH / 2 + pan.y);\n ctx.scale(zoom, zoom);\n const fitScale = Math.min((vpW * 0.85) / imgW, (vpH * 0.85) / imgH, 1);\n const renderW = imgW * fitScale;\n const renderH = imgH * fitScale;\n ctx.drawImage(img, -renderW / 2, -renderH / 2, renderW, renderH);\n ctx.restore();\n }\n\n /**\n * Performs an export render at exact pixel dimensions without zoom/pan viewport offsets.\n */\n public async renderExport(\n imageSource: CanvasImageSource | string,\n state: ImageEditorState,\n options: ExportOptions = {}\n ): Promise<HTMLCanvasElement> {\n let img: CanvasImageSource;\n if (typeof imageSource === 'string') {\n img = await this.imageCache.get(imageSource);\n } else {\n img = imageSource;\n }\n\n const naturalWidth = (img as any).naturalWidth || (img as any).videoWidth || (img as any).width || 800;\n const naturalHeight = (img as any).naturalHeight || (img as any).videoHeight || (img as any).height || 600;\n\n const { crop, transform, adjustments, depthMask, annotations } = state;\n\n const cropX = crop ? Math.max(0, crop.x) : 0;\n const cropY = crop ? Math.max(0, crop.y) : 0;\n const cropW = crop ? Math.min(naturalWidth - cropX, crop.width) : naturalWidth;\n const cropH = crop ? Math.min(naturalHeight - cropY, crop.height) : naturalHeight;\n\n const angle = ((transform.rotate % 360) + 360) % 360;\n const isSwapped = angle === 90 || angle === 270;\n const baseW = isSwapped ? cropH : cropW;\n const baseH = isSwapped ? cropW : cropH;\n\n const exportW = options.width || baseW;\n const exportH = options.height || baseH;\n\n const exportCanvas = this.createCanvas(exportW, exportH);\n const ctx = exportCanvas.getContext('2d');\n if (!ctx) return exportCanvas;\n\n // Draw transformed base image\n const intermediateCanvas = this.createCanvas(baseW, baseH);\n const interCtx = intermediateCanvas.getContext('2d');\n if (interCtx) {\n interCtx.save();\n const rad = (angle * Math.PI) / 180;\n interCtx.translate(baseW / 2, baseH / 2);\n interCtx.rotate(rad);\n interCtx.scale(transform.flipH ? -1 : 1, transform.flipV ? -1 : 1);\n interCtx.drawImage(img, cropX, cropY, cropW, cropH, -cropW / 2, -cropH / 2, cropW, cropH);\n interCtx.restore();\n\n // Apply adjustments\n const imageData = interCtx.getImageData(0, 0, baseW, baseH);\n if (imageData) {\n applyAdjustments(imageData, adjustments);\n\n if (depthMask.enabled && depthMask.maskSource) {\n try {\n const maskImg = await this.imageCache.get(depthMask.maskSource);\n const maskCanvas = this.createCanvas(baseW, baseH);\n const maskCtx = maskCanvas.getContext('2d');\n if (maskCtx) {\n maskCtx.drawImage(maskImg, cropX, cropY, cropW, cropH, 0, 0, baseW, baseH);\n const maskData = maskCtx.getImageData(0, 0, baseW, baseH);\n if (maskData) {\n applyDepthMask(imageData, maskData, depthMask);\n }\n }\n } catch {\n // Mask skip\n }\n }\n\n interCtx.putImageData(imageData, 0, 0);\n }\n }\n\n // Scale to target export dimensions\n ctx.drawImage(intermediateCanvas, 0, 0, baseW, baseH, 0, 0, exportW, exportH);\n\n // Draw annotations if included\n if (options.includeAnnotations !== false && annotations.length > 0) {\n const annScaleX = exportW / baseW;\n const annScaleY = exportH / baseH;\n ctx.save();\n ctx.scale(annScaleX, annScaleY);\n for (const ann of annotations) {\n await this.drawAnnotation(ctx, ann);\n }\n ctx.restore();\n }\n\n return exportCanvas;\n }\n\n /**\n * Generates a binary mask canvas (white for mask/selected area, black for unmasked).\n * Useful for AI inpainting and selective generative fill.\n */\n public async renderMask(\n state: ImageEditorState,\n options?: { useAnnotations?: boolean; useCrop?: boolean }\n ): Promise<HTMLCanvasElement> {\n const naturalWidth = state.imageDimensions.width;\n const naturalHeight = state.imageDimensions.height;\n const maskCanvas = this.createCanvas(naturalWidth, naturalHeight);\n const ctx = maskCanvas.getContext('2d');\n if (!ctx) return maskCanvas;\n\n // Background: unmasked (black)\n ctx.fillStyle = '#000000';\n ctx.fillRect(0, 0, naturalWidth, naturalHeight);\n\n const useCrop = options?.useCrop ?? true;\n const useAnnotations = options?.useAnnotations ?? true;\n\n // Crop box mask (if active)\n if (useCrop && state.crop) {\n ctx.fillStyle = '#ffffff';\n ctx.fillRect(state.crop.x, state.crop.y, state.crop.width, state.crop.height);\n }\n\n // Annotation masks (drawn in solid white)\n if (useAnnotations && state.annotations.length > 0) {\n for (const ann of state.annotations) {\n ctx.save();\n if (ann.rotation) {\n ctx.translate(ann.x, ann.y);\n ctx.rotate((ann.rotation * Math.PI) / 180);\n ctx.translate(-ann.x, -ann.y);\n }\n\n if (ann.type === 'pen') {\n const pen = ann as PenAnnotation;\n if (pen.points.length > 0) {\n ctx.beginPath();\n ctx.strokeStyle = '#ffffff';\n ctx.lineWidth = Math.max(pen.strokeWidth, 8);\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n ctx.moveTo(pen.points[0].x, pen.points[0].y);\n for (let i = 1; i < pen.points.length; i++) {\n ctx.lineTo(pen.points[i].x, pen.points[i].y);\n }\n ctx.stroke();\n }\n } else if (ann.type === 'rect') {\n const rect = ann as RectAnnotation;\n ctx.fillStyle = '#ffffff';\n ctx.fillRect(rect.x, rect.y, rect.width, rect.height);\n } else if (ann.type === 'circle') {\n const circ = ann as CircleAnnotation;\n ctx.fillStyle = '#ffffff';\n ctx.beginPath();\n ctx.ellipse(circ.x, circ.y, circ.radiusX, circ.radiusY, 0, 0, Math.PI * 2);\n ctx.fill();\n } else {\n ctx.strokeStyle = '#ffffff';\n ctx.fillStyle = '#ffffff';\n await this.drawAnnotation(ctx, ann);\n }\n ctx.restore();\n }\n }\n\n return maskCanvas;\n }\n}\n\n","import {\n Annotation,\n CreateAnnotationPayload,\n AspectRatio,\n CropBox,\n DepthMaskOptions,\n ExportOptions,\n ImageAdjustments,\n ImageDimensions,\n ImageEditorOptions,\n ImageEditorState,\n ImageEditorTool,\n ImageTransform,\n ImagePoint,\n SerializedImageEditorState,\n} from './types';\nimport { ImageEditorRenderer, RenderOptions } from './renderer';\n\nexport const DEFAULT_ADJUSTMENTS: ImageAdjustments = {\n brightness: 0,\n contrast: 0,\n saturation: 0,\n exposure: 0,\n temperature: 0,\n blur: 0,\n opacity: 1,\n};\n\nexport const DEFAULT_TRANSFORM: ImageTransform = {\n rotate: 0,\n flipH: false,\n flipV: false,\n};\n\nexport const DEFAULT_DEPTH_MASK: DepthMaskOptions = {\n enabled: false,\n maskSource: null,\n mode: 'threshold',\n depthRange: [0, 255],\n softness: 10,\n invert: false,\n};\n\n/**\n * ImageEditorController manages the complete lifecycle, canvas rendering pipeline,\n * tool interaction states, undo/redo history, serialization, and image export.\n */\nexport class ImageEditorController {\n private state: ImageEditorState;\n private renderer: ImageEditorRenderer;\n private imageElement: CanvasImageSource | null = null;\n private listeners = new Set<(state: ImageEditorState) => void>();\n private onStateChange?: (state: ImageEditorState) => void;\n\n // History stacks\n private undoStack: SerializedImageEditorState[] = [];\n private redoStack: SerializedImageEditorState[] = [];\n private maxHistoryDepth: number;\n private isApplyingHistory = false;\n private initialSnapshot: SerializedImageEditorState;\n\n constructor(options: ImageEditorOptions = {}) {\n this.maxHistoryDepth = options.maxHistoryDepth ?? 50;\n this.onStateChange = options.onStateChange;\n this.renderer = new ImageEditorRenderer();\n\n const initialAdjustments: ImageAdjustments = {\n ...DEFAULT_ADJUSTMENTS,\n ...options.initialState?.adjustments,\n };\n\n const initialTransform: ImageTransform = {\n ...DEFAULT_TRANSFORM,\n ...options.initialState?.transform,\n };\n\n const initialDepthMask: DepthMaskOptions = {\n ...DEFAULT_DEPTH_MASK,\n ...options.initialState?.depthMask,\n };\n\n this.state = {\n sourceUrl: typeof options.image === 'string' ? options.image : null,\n imageDimensions: { width: 800, height: 600 },\n crop: options.initialState?.crop ?? null,\n transform: initialTransform,\n adjustments: initialAdjustments,\n depthMask: initialDepthMask,\n annotations: options.initialState?.annotations ?? [],\n selectedAnnotationId: null,\n activeTool: 'select',\n zoom: 1,\n pan: { x: 0, y: 0 },\n isOriginalCompared: false,\n };\n\n if (options.image instanceof Object && 'width' in options.image) {\n this.imageElement = options.image as CanvasImageSource;\n this.state.imageDimensions = {\n width: (options.image as any).naturalWidth || (options.image as any).width || 800,\n height: (options.image as any).naturalHeight || (options.image as any).height || 600,\n };\n }\n\n this.initialSnapshot = this.serializeState();\n\n if (typeof options.image === 'string') {\n this.loadImage(options.image);\n }\n }\n\n // --- Image Loading ---\n\n public async loadImage(src: string | HTMLImageElement): Promise<void> {\n if (typeof src === 'string') {\n this.state.sourceUrl = src;\n try {\n const img = await this.renderer.imageCache.get(src);\n this.imageElement = img;\n this.state.imageDimensions = {\n width: img.naturalWidth || img.width || 800,\n height: img.naturalHeight || img.height || 600,\n };\n } catch (err) {\n // In SSR / non-DOM environments, retain default dimensions\n }\n } else {\n this.imageElement = src;\n this.state.sourceUrl = (src as any).src || null;\n this.state.imageDimensions = {\n width: src.naturalWidth || src.width || 800,\n height: src.naturalHeight || src.height || 600,\n };\n }\n\n this.initialSnapshot = this.serializeState();\n this.notify();\n }\n\n public getImageElement(): CanvasImageSource | null {\n return this.imageElement;\n }\n\n // --- State & Subscription ---\n\n public getState(): ImageEditorState {\n return this.state;\n }\n\n public subscribe(listener: (state: ImageEditorState) => void): () => void {\n this.listeners.add(listener);\n listener(this.state);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private notify(): void {\n const currentState = { ...this.state };\n this.onStateChange?.(currentState);\n this.listeners.forEach((l) => l(currentState));\n }\n\n // --- History (Undo / Redo / Reset) ---\n\n private recordHistory(): void {\n if (this.isApplyingHistory) return;\n this.undoStack.push(this.serializeState());\n if (this.undoStack.length > this.maxHistoryDepth) {\n this.undoStack.shift();\n }\n this.redoStack = [];\n }\n\n public get canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n public get canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n public undo(): boolean {\n if (!this.canUndo) return false;\n this.isApplyingHistory = true;\n const current = this.serializeState();\n this.redoStack.push(current);\n\n const prev = this.undoStack.pop()!;\n this.applySerializedState(prev);\n this.isApplyingHistory = false;\n this.notify();\n return true;\n }\n\n public redo(): boolean {\n if (!this.canRedo) return false;\n this.isApplyingHistory = true;\n const current = this.serializeState();\n this.undoStack.push(current);\n\n const next = this.redoStack.pop()!;\n this.applySerializedState(next);\n this.isApplyingHistory = false;\n this.notify();\n return true;\n }\n\n public reset(): void {\n this.recordHistory();\n this.applySerializedState(this.initialSnapshot);\n this.state.zoom = 1;\n this.state.pan = { x: 0, y: 0 };\n this.state.activeTool = 'select';\n this.state.selectedAnnotationId = null;\n this.notify();\n }\n\n // --- Active Tool ---\n\n public setTool(tool: ImageEditorTool): void {\n if (this.state.activeTool === tool) return;\n this.state.activeTool = tool;\n if (tool !== 'select') {\n this.state.selectedAnnotationId = null;\n }\n // If switching to crop tool and no crop is defined, initialize crop to full image\n if (tool === 'crop' && !this.state.crop) {\n this.state.crop = {\n x: 0,\n y: 0,\n width: this.state.imageDimensions.width,\n height: this.state.imageDimensions.height,\n };\n }\n this.notify();\n }\n\n // --- Crop ---\n\n public setCrop(crop: CropBox | null, addToHistory = true): void {\n if (addToHistory) {\n this.recordHistory();\n }\n this.state.crop = crop;\n this.notify();\n }\n\n public applyCropPreset(ratio: AspectRatio): void {\n this.recordHistory();\n const { width, height } = this.state.imageDimensions;\n if (!ratio) {\n // Freeform: full image\n this.state.crop = { x: 0, y: 0, width, height };\n } else {\n let targetW = width;\n let targetH = targetW / ratio;\n\n if (targetH > height) {\n targetH = height;\n targetW = targetH * ratio;\n }\n\n const x = Math.max(0, Math.round((width - targetW) / 2));\n const y = Math.max(0, Math.round((height - targetH) / 2));\n this.state.crop = {\n x,\n y,\n width: Math.round(targetW),\n height: Math.round(targetH),\n };\n }\n this.notify();\n }\n\n public resetCrop(): void {\n this.recordHistory();\n this.state.crop = null;\n this.notify();\n }\n\n // --- Transformations (Rotate / Flip) ---\n\n public rotate(degrees: number): void {\n this.recordHistory();\n const current = this.state.transform.rotate;\n this.state.transform.rotate = (current + degrees) % 360;\n this.notify();\n }\n\n public setRotation(degrees: number): void {\n this.recordHistory();\n this.state.transform.rotate = degrees % 360;\n this.notify();\n }\n\n public flipHorizontal(): void {\n this.recordHistory();\n this.state.transform.flipH = !this.state.transform.flipH;\n this.notify();\n }\n\n public flipVertical(): void {\n this.recordHistory();\n this.state.transform.flipV = !this.state.transform.flipV;\n this.notify();\n }\n\n // --- Adjustments ---\n\n public setAdjustments(partial: Partial<ImageAdjustments>, commitToHistory = true): void {\n if (commitToHistory) {\n this.recordHistory();\n }\n this.state.adjustments = {\n ...this.state.adjustments,\n ...partial,\n };\n this.notify();\n }\n\n public resetAdjustments(): void {\n this.recordHistory();\n this.state.adjustments = { ...DEFAULT_ADJUSTMENTS };\n this.notify();\n }\n\n // --- Depth Masking (Gleamforge) ---\n\n public setDepthMask(partial: Partial<DepthMaskOptions>): void {\n this.recordHistory();\n this.state.depthMask = {\n ...this.state.depthMask,\n ...partial,\n };\n this.notify();\n }\n\n public resetDepthMask(): void {\n this.recordHistory();\n this.state.depthMask = { ...DEFAULT_DEPTH_MASK };\n this.notify();\n }\n\n // --- Annotations & Layers ---\n\n public addAnnotation(annotation: CreateAnnotationPayload): string {\n this.recordHistory();\n const id = `ann_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\n const fullAnnotation = { ...annotation, id } as Annotation;\n this.state.annotations = [...this.state.annotations, fullAnnotation];\n this.state.selectedAnnotationId = id;\n this.notify();\n return id;\n }\n\n public updateAnnotation(id: string, partial: Partial<Annotation>, commitToHistory = true): void {\n if (commitToHistory) {\n this.recordHistory();\n }\n this.state.annotations = this.state.annotations.map((ann) => {\n if (ann.id === id) {\n return { ...ann, ...partial } as Annotation;\n }\n return ann;\n });\n this.notify();\n }\n\n public removeAnnotation(id: string): void {\n this.recordHistory();\n this.state.annotations = this.state.annotations.filter((ann) => ann.id !== id);\n if (this.state.selectedAnnotationId === id) {\n this.state.selectedAnnotationId = null;\n }\n this.notify();\n }\n\n public clearAnnotations(): void {\n this.recordHistory();\n this.state.annotations = [];\n this.state.selectedAnnotationId = null;\n this.notify();\n }\n\n public selectAnnotation(id: string | null): void {\n this.state.selectedAnnotationId = id;\n this.notify();\n }\n\n // --- Zoom & Pan ---\n\n public setZoom(zoom: number): void {\n this.state.zoom = Math.max(0.1, Math.min(10, zoom));\n this.notify();\n }\n\n public zoomIn(factor = 1.2): void {\n this.setZoom(this.state.zoom * factor);\n }\n\n public zoomOut(factor = 1.2): void {\n this.setZoom(this.state.zoom / factor);\n }\n\n public resetZoom(): void {\n this.state.zoom = 1;\n this.state.pan = { x: 0, y: 0 };\n this.notify();\n }\n\n public setPan(pan: ImagePoint): void {\n this.state.pan = pan;\n this.notify();\n }\n\n public setComparing(comparing: boolean): void {\n if (this.state.isOriginalCompared === comparing) return;\n this.state.isOriginalCompared = comparing;\n this.notify();\n }\n\n // --- Rendering & Export ---\n\n public async render(targetCanvas: HTMLCanvasElement, options?: RenderOptions): Promise<void> {\n const source = this.imageElement || this.state.sourceUrl;\n if (!source) return;\n try {\n await this.renderer.renderToCanvas(targetCanvas, source, this.state, options);\n } catch {\n // In non-DOM or test mock environments without image loading, safely ignore\n }\n }\n\n public async renderExport(options?: ExportOptions): Promise<HTMLCanvasElement> {\n const source = this.imageElement || this.state.sourceUrl;\n if (!source) {\n return this.renderer.createCanvas(1, 1);\n }\n return this.renderer.renderExport(source, this.state, options);\n }\n\n public async toDataURL(options: ExportOptions = {}): Promise<string> {\n const canvas = await this.renderExport(options);\n const format = options.format || 'image/png';\n const quality = options.quality ?? 0.92;\n return canvas.toDataURL(format, quality);\n }\n\n public async toBlob(options: ExportOptions = {}): Promise<Blob> {\n const canvas = await this.renderExport(options);\n const format = options.format || 'image/png';\n const quality = options.quality ?? 0.92;\n\n return new Promise((resolve, reject) => {\n if (canvas.toBlob) {\n canvas.toBlob((blob) => {\n if (blob) resolve(blob);\n else reject(new Error('Failed to create Blob from canvas'));\n }, format, quality);\n } else {\n // Fallback for environments lacking canvas.toBlob\n try {\n const dataUrl = canvas.toDataURL(format, quality);\n const parts = dataUrl.split(',');\n const byteStr = atob(parts[1]);\n const ab = new ArrayBuffer(byteStr.length);\n const ia = new Uint8Array(ab);\n for (let i = 0; i < byteStr.length; i++) {\n ia[i] = byteStr.charCodeAt(i);\n }\n resolve(new Blob([ab], { type: format }));\n } catch (err) {\n reject(err);\n }\n }\n });\n }\n\n // --- AI Masking & Transformation Helpers ---\n\n public async renderMask(options?: { useAnnotations?: boolean; useCrop?: boolean }): Promise<HTMLCanvasElement> {\n return this.renderer.renderMask(this.state, options);\n }\n\n public async toMaskDataURL(options?: { useAnnotations?: boolean; useCrop?: boolean }): Promise<string> {\n const canvas = await this.renderMask(options);\n return canvas.toDataURL('image/png');\n }\n\n public async applyInpaintedImage(newDataUrl: string): Promise<void> {\n this.recordHistory();\n await this.loadImage(newDataUrl);\n this.clearAnnotations();\n }\n\n public async applyBackgroundRemovedImage(newDataUrl: string): Promise<void> {\n this.recordHistory();\n await this.loadImage(newDataUrl);\n }\n\n\n // --- Serialization ---\n\n public toJSON(): SerializedImageEditorState {\n return this.serializeState();\n }\n\n public loadJSON(data: SerializedImageEditorState): void {\n this.recordHistory();\n this.applySerializedState(data);\n this.notify();\n }\n\n private serializeState(): SerializedImageEditorState {\n return {\n version: 1,\n crop: this.state.crop ? { ...this.state.crop } : null,\n transform: { ...this.state.transform },\n adjustments: { ...this.state.adjustments },\n depthMask: {\n ...this.state.depthMask,\n depthRange: [...this.state.depthMask.depthRange] as [number, number],\n },\n annotations: JSON.parse(JSON.stringify(this.state.annotations)),\n };\n }\n\n private applySerializedState(data: SerializedImageEditorState): void {\n this.state.crop = data.crop ? { ...data.crop } : null;\n this.state.transform = { ...data.transform };\n this.state.adjustments = { ...data.adjustments };\n this.state.depthMask = {\n ...data.depthMask,\n depthRange: [...data.depthMask.depthRange] as [number, number],\n };\n this.state.annotations = JSON.parse(JSON.stringify(data.annotations || []));\n }\n}\n","import type { CropBox, ImageEditorState, ImagePoint } from './types';\n\nexport type CropHandle = 'nw' | 'ne' | 'se' | 'sw' | 'n' | 's' | 'e' | 'w' | 'inside' | null;\n\nexport interface ViewportMetrics {\n vpWidth: number;\n vpHeight: number;\n centerX: number;\n centerY: number;\n baseW: number;\n baseH: number;\n fitScale: number;\n renderW: number;\n renderH: number;\n totalScale: number;\n imageRect: { x: number; y: number; width: number; height: number };\n cropRect: { x: number; y: number; width: number; height: number } | null;\n}\n\n/**\n * Calculates current viewport projection metrics for canvas <-> image coordinate mapping.\n */\nexport function getViewportMetrics(\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false,\n cropOverride?: CropBox | null\n): ViewportMetrics {\n const { imageDimensions, crop: stateCrop, transform, zoom, pan } = state;\n const naturalWidth = imageDimensions.width || 800;\n const naturalHeight = imageDimensions.height || 600;\n const activeCrop =\n (cropOverride !== undefined && cropOverride !== null)\n ? cropOverride\n : (stateCrop ?? (isActivelyCropping ? { x: 0, y: 0, width: naturalWidth, height: naturalHeight } : null));\n\n const cropW = (!isActivelyCropping && activeCrop) ? activeCrop.width : naturalWidth;\n const cropH = (!isActivelyCropping && activeCrop) ? activeCrop.height : naturalHeight;\n\n const angle = ((transform.rotate % 360) + 360) % 360;\n const isSwapped = angle === 90 || angle === 270;\n const baseW = isSwapped ? cropH : cropW;\n const baseH = isSwapped ? cropW : cropH;\n\n const fitScale = Math.min((vpWidth * 0.85) / baseW, (vpHeight * 0.85) / baseH, 1);\n const renderW = baseW * fitScale;\n const renderH = baseH * fitScale;\n const totalScale = zoom * fitScale;\n\n const centerX = vpWidth / 2 + pan.x;\n const centerY = vpHeight / 2 + pan.y;\n\n const imageRect = {\n x: centerX - (renderW * zoom) / 2,\n y: centerY - (renderH * zoom) / 2,\n width: renderW * zoom,\n height: renderH * zoom,\n };\n\n let cropRect: { x: number; y: number; width: number; height: number } | null = null;\n if (activeCrop) {\n const startX = -renderW / 2 + (activeCrop.x / naturalWidth) * renderW;\n const startY = -renderH / 2 + (activeCrop.y / naturalHeight) * renderH;\n const cropBoxW = (activeCrop.width / naturalWidth) * renderW;\n const cropBoxH = (activeCrop.height / naturalHeight) * renderH;\n\n cropRect = {\n x: centerX + zoom * startX,\n y: centerY + zoom * startY,\n width: zoom * cropBoxW,\n height: zoom * cropBoxH,\n };\n }\n\n return {\n vpWidth,\n vpHeight,\n centerX,\n centerY,\n baseW,\n baseH,\n fitScale,\n renderW,\n renderH,\n totalScale,\n imageRect,\n cropRect,\n };\n}\n\n/**\n * Converts a point on the canvas (in internal canvas pixel space) to image/annotation pixel space.\n */\nexport function canvasToImagePoint(\n canvasPt: ImagePoint,\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false\n): ImagePoint {\n const metrics = getViewportMetrics(state, vpWidth, vpHeight, isActivelyCropping);\n const unzoomedX = (canvasPt.x - metrics.centerX) / state.zoom;\n const unzoomedY = (canvasPt.y - metrics.centerY) / state.zoom;\n\n const x = (unzoomedX + metrics.renderW / 2) / metrics.fitScale;\n const y = (unzoomedY + metrics.renderH / 2) / metrics.fitScale;\n\n return { x: Math.round(x), y: Math.round(y) };\n}\n\n/**\n * Converts a point in image/annotation pixel space to internal canvas pixel space.\n */\nexport function imageToCanvasPoint(\n imgPt: ImagePoint,\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false\n): ImagePoint {\n const metrics = getViewportMetrics(state, vpWidth, vpHeight, isActivelyCropping);\n const unzoomedX = (imgPt.x - metrics.baseW / 2) * metrics.fitScale;\n const unzoomedY = (imgPt.y - metrics.baseH / 2) * metrics.fitScale;\n\n const canvasX = metrics.centerX + unzoomedX * state.zoom;\n const canvasY = metrics.centerY + unzoomedY * state.zoom;\n\n return { x: Math.round(canvasX), y: Math.round(canvasY) };\n}\n\n/**\n * Hit-tests the crop box and its corner/edge handles given a canvas point.\n */\nexport function hitTestCrop(\n canvasPt: ImagePoint,\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n handleThreshold = 14,\n cropOverride?: CropBox | null\n): CropHandle {\n const metrics = getViewportMetrics(state, vpWidth, vpHeight, true, cropOverride);\n if (!metrics.cropRect) return null;\n\n const { x, y, width: w, height: h } = metrics.cropRect;\n const px = canvasPt.x;\n const py = canvasPt.y;\n\n // 1. Corners (highest priority)\n const dist = (x1: number, y1: number, x2: number, y2: number) =>\n Math.hypot(x1 - x2, y1 - y2);\n\n if (dist(px, py, x, y) <= handleThreshold) return 'nw';\n if (dist(px, py, x + w, y) <= handleThreshold) return 'ne';\n if (dist(px, py, x + w, y + h) <= handleThreshold) return 'se';\n if (dist(px, py, x, y + h) <= handleThreshold) return 'sw';\n\n // 2. Edges\n const nearX = px >= x - handleThreshold && px <= x + w + handleThreshold;\n const nearY = py >= y - handleThreshold && py <= y + h + handleThreshold;\n\n if (nearX && Math.abs(py - y) <= handleThreshold) return 'n';\n if (nearX && Math.abs(py - (y + h)) <= handleThreshold) return 's';\n if (nearY && Math.abs(px - x) <= handleThreshold) return 'w';\n if (nearY && Math.abs(px - (x + w)) <= handleThreshold) return 'e';\n\n // 3. Inside crop region\n if (px >= x && px <= x + w && py >= y && py <= y + h) {\n return 'inside';\n }\n\n return null;\n}\n\n/**\n * Returns CSS cursor style corresponding to a crop handle.\n */\nexport function getCropCursor(handle: CropHandle): string {\n switch (handle) {\n case 'nw':\n case 'se':\n return 'nwse-resize';\n case 'ne':\n case 'sw':\n return 'nesw-resize';\n case 'n':\n case 's':\n return 'ns-resize';\n case 'e':\n case 'w':\n return 'ew-resize';\n case 'inside':\n return 'move';\n default:\n return 'crosshair';\n }\n}\n\n/**\n * Calculates updated crop rectangle given a drag interaction.\n */\nexport function calculateCropDrag(\n startCrop: CropBox,\n handle: CropHandle,\n startImgPt: ImagePoint,\n currentImgPt: ImagePoint,\n imgWidth: number,\n imgHeight: number,\n aspectRatio?: number | null\n): CropBox {\n const dx = currentImgPt.x - startImgPt.x;\n const dy = currentImgPt.y - startImgPt.y;\n\n // Whole crop box move\n if (handle === 'inside') {\n const maxX = Math.max(0, imgWidth - startCrop.width);\n const maxY = Math.max(0, imgHeight - startCrop.height);\n const newX = Math.max(0, Math.min(maxX, startCrop.x + dx));\n const newY = Math.max(0, Math.min(maxY, startCrop.y + dy));\n return {\n x: Math.round(newX),\n y: Math.round(newY),\n width: startCrop.width,\n height: startCrop.height,\n };\n }\n\n // Handle resizing\n let left = startCrop.x;\n let top = startCrop.y;\n let right = startCrop.x + startCrop.width;\n let bottom = startCrop.y + startCrop.height;\n\n const minSize = 20;\n\n if (handle === 'e' || handle === 'ne' || handle === 'se') {\n right = Math.min(imgWidth, Math.max(left + minSize, startCrop.x + startCrop.width + dx));\n }\n if (handle === 'w' || handle === 'nw' || handle === 'sw') {\n left = Math.max(0, Math.min(right - minSize, startCrop.x + dx));\n }\n if (handle === 's' || handle === 'se' || handle === 'sw') {\n bottom = Math.min(imgHeight, Math.max(top + minSize, startCrop.y + startCrop.height + dy));\n }\n if (handle === 'n' || handle === 'nw' || handle === 'ne') {\n top = Math.max(0, Math.min(bottom - minSize, startCrop.y + dy));\n }\n\n // Maintain aspect ratio if requested\n if (aspectRatio && aspectRatio > 0) {\n let currentW = right - left;\n let currentH = bottom - top;\n\n if (handle === 'e' || handle === 'w') {\n currentH = Math.min(imgHeight, currentW / aspectRatio);\n bottom = Math.min(imgHeight, top + currentH);\n } else if (handle === 'n' || handle === 's') {\n currentW = Math.min(imgWidth, currentH * aspectRatio);\n right = Math.min(imgWidth, left + currentW);\n } else {\n // Corner resizing\n const targetH = currentW / aspectRatio;\n if (top + targetH <= imgHeight) {\n bottom = top + targetH;\n } else {\n const targetW = currentH * aspectRatio;\n right = Math.min(imgWidth, left + targetW);\n }\n }\n }\n\n return {\n x: Math.round(left),\n y: Math.round(top),\n width: Math.round(Math.max(minSize, right - left)),\n height: Math.round(Math.max(minSize, bottom - top)),\n };\n}\n\n/**\n * Calculates bounding box of an annotation.\n */\nexport function getAnnotationBounds(ann: import('./types').Annotation): { x: number; y: number; width: number; height: number } {\n switch (ann.type) {\n case 'rect':\n return { x: ann.x, y: ann.y, width: ann.width, height: ann.height };\n case 'circle':\n return {\n x: ann.x - ann.radiusX,\n y: ann.y - ann.radiusY,\n width: ann.radiusX * 2,\n height: ann.radiusY * 2,\n };\n case 'arrow':\n case 'line': {\n const minX = Math.min(ann.x, ann.endX);\n const minY = Math.min(ann.y, ann.endY);\n return {\n x: minX,\n y: minY,\n width: Math.max(Math.abs(ann.endX - ann.x), 10),\n height: Math.max(Math.abs(ann.endY - ann.y), 10),\n };\n }\n case 'pen': {\n if (!ann.points.length) return { x: ann.x, y: ann.y, width: 0, height: 0 };\n let minX = ann.points[0].x;\n let maxX = ann.points[0].x;\n let minY = ann.points[0].y;\n let maxY = ann.points[0].y;\n for (const pt of ann.points) {\n if (pt.x < minX) minX = pt.x;\n if (pt.x > maxX) maxX = pt.x;\n if (pt.y < minY) minY = pt.y;\n if (pt.y > maxY) maxY = pt.y;\n }\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n }\n case 'text':\n return {\n x: ann.x,\n y: ann.y,\n width: ann.text.length * ann.fontSize * 0.6,\n height: ann.fontSize * 1.2,\n };\n case 'image':\n return { x: ann.x, y: ann.y, width: ann.width, height: ann.height };\n }\n}\n\n/**\n * Hit tests if a point in image space is within an annotation's bounds.\n */\nexport function hitTestAnnotation(imgPt: ImagePoint, ann: import('./types').Annotation, padding = 10): boolean {\n const b = getAnnotationBounds(ann);\n return (\n imgPt.x >= b.x - padding &&\n imgPt.x <= b.x + b.width + padding &&\n imgPt.y >= b.y - padding &&\n imgPt.y <= b.y + b.height + padding\n );\n}\n\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/image-editor/index.cjs","../../src/image-editor/filters.ts","../../src/image-editor/geometry.ts","../../src/image-editor/renderer.ts","../../src/image-editor/controller.ts"],"names":["x","y"],"mappings":"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","file":"/home/runner/work/editor/editor/packages/core/dist/image-editor/index.cjs","sourcesContent":[null,"import type { DepthMaskOptions, ImageAdjustments } from './types';\n\nfunction clamp(value: number, min = 0, max = 255): number {\n return Math.max(min, Math.min(max, value));\n}\n\n/**\n * Applies color and tone adjustments (brightness, contrast, saturation, exposure, temperature, opacity, blur)\n * directly to the pixel data of an ImageData buffer.\n */\nexport function applyAdjustments(imageData: ImageData, adjustments: ImageAdjustments): void {\n const { brightness, contrast, saturation, exposure, temperature, opacity, blur } = adjustments;\n const data = imageData.data;\n const len = data.length;\n\n const hasBrightness = brightness !== 0;\n const hasContrast = contrast !== 0;\n const hasSaturation = saturation !== 0;\n const hasExposure = exposure !== 0;\n const hasTemperature = temperature !== 0;\n const hasOpacity = opacity < 1;\n\n // Pre-calculate contrast factor\n const contrastFactor = hasContrast\n ? (259 * (contrast + 255)) / (255 * (259 - contrast))\n : 1;\n\n // Pre-calculate exposure multiplier\n const exposureMultiplier = hasExposure ? Math.pow(2, exposure / 50) : 1;\n\n // Pre-calculate saturation multiplier\n const satMultiplier = hasSaturation ? 1 + saturation / 100 : 1;\n\n // Pre-calculate temperature offsets\n const tempOffset = hasTemperature ? temperature * 0.8 : 0;\n\n if (hasBrightness || hasContrast || hasSaturation || hasExposure || hasTemperature || hasOpacity) {\n for (let i = 0; i < len; i += 4) {\n let r = data[i];\n let g = data[i + 1];\n let b = data[i + 2];\n let a = data[i + 3];\n\n // Exposure\n if (hasExposure) {\n r = clamp(r * exposureMultiplier);\n g = clamp(g * exposureMultiplier);\n b = clamp(b * exposureMultiplier);\n }\n\n // Brightness\n if (hasBrightness) {\n const bOffset = brightness * 2.55;\n r = clamp(r + bOffset);\n g = clamp(g + bOffset);\n b = clamp(b + bOffset);\n }\n\n // Contrast\n if (hasContrast) {\n r = clamp(contrastFactor * (r - 128) + 128);\n g = clamp(contrastFactor * (g - 128) + 128);\n b = clamp(contrastFactor * (b - 128) + 128);\n }\n\n // Saturation\n if (hasSaturation) {\n const luminance = 0.2989 * r + 0.587 * g + 0.114 * b;\n r = clamp(luminance + (r - luminance) * satMultiplier);\n g = clamp(luminance + (g - luminance) * satMultiplier);\n b = clamp(luminance + (b - luminance) * satMultiplier);\n }\n\n // Temperature / Warmth\n if (hasTemperature) {\n r = clamp(r + tempOffset);\n b = clamp(b - tempOffset);\n }\n\n // Opacity\n if (hasOpacity) {\n a = clamp(a * opacity);\n }\n\n data[i] = r;\n data[i + 1] = g;\n data[i + 2] = b;\n data[i + 3] = a;\n }\n }\n\n // Apply blur if requested\n if (blur > 0) {\n applyFastBoxBlur(imageData, Math.min(Math.round(blur), 40));\n }\n}\n\n/**\n * Fast separable box blur running horizontally and vertically.\n */\nfunction applyFastBoxBlur(imageData: ImageData, radius: number): void {\n if (radius < 1) return;\n const w = imageData.width;\n const h = imageData.height;\n const src = imageData.data;\n const temp = new Uint8ClampedArray(src.length);\n\n // Horizontal blur pass\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n let r = 0, g = 0, b = 0, a = 0;\n let count = 0;\n for (let k = -radius; k <= radius; k++) {\n const px = Math.min(w - 1, Math.max(0, x + k));\n const idx = (y * w + px) * 4;\n r += src[idx];\n g += src[idx + 1];\n b += src[idx + 2];\n a += src[idx + 3];\n count++;\n }\n const dstIdx = (y * w + x) * 4;\n temp[dstIdx] = Math.round(r / count);\n temp[dstIdx + 1] = Math.round(g / count);\n temp[dstIdx + 2] = Math.round(b / count);\n temp[dstIdx + 3] = Math.round(a / count);\n }\n }\n\n // Vertical blur pass back into src\n for (let x = 0; x < w; x++) {\n for (let y = 0; y < h; y++) {\n let r = 0, g = 0, b = 0, a = 0;\n let count = 0;\n for (let k = -radius; k <= radius; k++) {\n const py = Math.min(h - 1, Math.max(0, y + k));\n const idx = (py * w + x) * 4;\n r += temp[idx];\n g += temp[idx + 1];\n b += temp[idx + 2];\n a += temp[idx + 3];\n count++;\n }\n const dstIdx = (y * w + x) * 4;\n src[dstIdx] = Math.round(r / count);\n src[dstIdx + 1] = Math.round(g / count);\n src[dstIdx + 2] = Math.round(b / count);\n src[dstIdx + 3] = Math.round(a / count);\n }\n }\n}\n\n/**\n * Blends a depth/alpha mask into the main image data based on Gleamforge depth masking.\n * Supports threshold range with softness gradation, brightness mode, and optional inversion.\n */\nexport function applyDepthMask(\n mainImageData: ImageData,\n maskImageData: ImageData,\n options: DepthMaskOptions\n): void {\n const { mode, depthRange, softness, invert } = options;\n const mainData = mainImageData.data;\n const maskData = maskImageData.data;\n const len = Math.min(mainData.length, maskData.length);\n const effectiveSoftness = Math.max(1, softness || 10);\n const minDepth = depthRange[0];\n const maxDepth = depthRange[1];\n\n for (let i = 0; i < len; i += 4) {\n let maskValue = maskData[i]; // Use red channel of mask (or grayscale)\n if (invert) {\n maskValue = 255 - maskValue;\n }\n\n let alphaValue: number;\n\n if (mode === 'threshold') {\n if (maskValue >= maxDepth + effectiveSoftness) {\n alphaValue = 0;\n } else if (maskValue > maxDepth) {\n alphaValue = Math.round(((maxDepth + effectiveSoftness - maskValue) / effectiveSoftness) * 255);\n } else if (maskValue >= minDepth) {\n alphaValue = 255;\n } else if (maskValue < minDepth - effectiveSoftness) {\n alphaValue = 0;\n } else {\n alphaValue = Math.round(((maskValue - (minDepth - effectiveSoftness)) / effectiveSoftness) * 255);\n }\n } else {\n // Brightness mode\n alphaValue = clamp(maskValue + minDepth);\n }\n\n // Multiply existing alpha with mask alpha\n mainData[i + 3] = clamp((mainData[i + 3] * alphaValue) / 255);\n }\n}\n\n/**\n * Removes alpha channel by setting all alpha bytes to 255 (completely opaque).\n * From Gleamforge image-filters.ts.\n */\nexport function removeAlpha(imageData: ImageData): void {\n const data = imageData.data;\n const len = data.length;\n for (let i = 3; i < len; i += 4) {\n data[i] = 255;\n }\n}\n","import type { Annotation, CropBox, ImageEditorState, ImagePoint, ImageTransform } from './types';\n\n/*\n * Coordinate spaces\n * -----------------\n * The crop box and every annotation are stored in ONE canonical space: natural image\n * pixels of the current source image (origin top-left of the un-rotated, un-flipped,\n * un-cropped image). Rotation, flips, crop and the viewport fit/zoom/pan are pure view\n * transforms applied on top of that space by a single forward mapping:\n *\n * target = origin + scale * R(angle) * F(flipH, flipV) * (image - regionCenter)\n *\n * where `regionCenter` is the centre of the visible region (the crop box, or the whole\n * image while the crop tool is active). `projectImagePoint` / `unprojectCanvasPoint`\n * implement the mapping and its exact inverse, and `applyImageProjectionToContext`\n * applies the very same transform to a 2D context, so rendering, hit-testing, viewport\n * metrics, export and mask generation cannot drift apart.\n */\n\nexport type CropHandle = 'nw' | 'ne' | 'se' | 'sw' | 'n' | 's' | 'e' | 'w' | 'inside' | null;\n\nexport interface ViewportMetrics {\n vpWidth: number;\n vpHeight: number;\n centerX: number;\n centerY: number;\n baseW: number;\n baseH: number;\n fitScale: number;\n renderW: number;\n renderH: number;\n totalScale: number;\n imageRect: { x: number; y: number; width: number; height: number };\n cropRect: { x: number; y: number; width: number; height: number } | null;\n}\n\n/**\n * A complete description of the natural-image -> target mapping (see module comment).\n */\nexport interface ImageProjection {\n /** Visible region in natural image pixels (crop box or full image). */\n region: CropBox;\n /** Rotation in degrees, normalised to [0, 360). */\n angle: number;\n flipH: boolean;\n flipV: boolean;\n /** Uniform scale from image pixels to target pixels. */\n scale: number;\n /** Target-space point that the centre of `region` maps to. */\n originX: number;\n originY: number;\n}\n\n/** Normalises any rotation in degrees to [0, 360). */\nexport function normalizeAngle(degrees: number): number {\n const a = ((degrees % 360) + 360) % 360;\n return a === 360 ? 0 : a;\n}\n\nfunction cosSin(angle: number): [number, number] {\n // Exact values for quarter turns so 90deg steps never accumulate float noise.\n switch (angle) {\n case 0:\n return [1, 0];\n case 90:\n return [0, 1];\n case 180:\n return [-1, 0];\n case 270:\n return [0, -1];\n default: {\n const rad = (angle * Math.PI) / 180;\n return [Math.cos(rad), Math.sin(rad)];\n }\n }\n}\n\n/** Whether the rotation swaps the displayed width and height. */\nexport function isQuarterTurnSwapped(transform: Pick<ImageTransform, 'rotate'>): boolean {\n const angle = normalizeAngle(transform.rotate);\n return angle === 90 || angle === 270;\n}\n\n/** Size of a region once rotated (bounding box for arbitrary angles). */\nexport function getRotatedSize(\n width: number,\n height: number,\n transform: Pick<ImageTransform, 'rotate'>\n): { width: number; height: number } {\n const [c, s] = cosSin(normalizeAngle(transform.rotate));\n return {\n width: Math.abs(width * c) + Math.abs(height * s),\n height: Math.abs(width * s) + Math.abs(height * c),\n };\n}\n\n/** Maps a natural-image point to target space. */\nexport function projectImagePoint(pt: ImagePoint, p: ImageProjection): ImagePoint {\n const [c, s] = cosSin(p.angle);\n let x = pt.x - (p.region.x + p.region.width / 2);\n let y = pt.y - (p.region.y + p.region.height / 2);\n if (p.flipH) x = -x;\n if (p.flipV) y = -y;\n const rx = x * c - y * s;\n const ry = x * s + y * c;\n return { x: p.originX + rx * p.scale, y: p.originY + ry * p.scale };\n}\n\n/** Exact inverse of {@link projectImagePoint}. */\nexport function unprojectCanvasPoint(pt: ImagePoint, p: ImageProjection): ImagePoint {\n const [c, s] = cosSin(p.angle);\n const rx = (pt.x - p.originX) / p.scale;\n const ry = (pt.y - p.originY) / p.scale;\n let x = rx * c + ry * s;\n let y = -rx * s + ry * c;\n if (p.flipH) x = -x;\n if (p.flipV) y = -y;\n return { x: x + p.region.x + p.region.width / 2, y: y + p.region.y + p.region.height / 2 };\n}\n\n/**\n * Applies the projection to a 2D context so that subsequent drawing in natural image\n * pixels lands exactly where {@link projectImagePoint} says it does.\n */\nexport function applyImageProjectionToContext(\n ctx: Pick<CanvasRenderingContext2D, 'translate' | 'scale' | 'rotate'>,\n p: ImageProjection\n): void {\n ctx.translate(p.originX, p.originY);\n ctx.scale(p.scale, p.scale);\n if (p.angle !== 0) ctx.rotate((p.angle * Math.PI) / 180);\n if (p.flipH || p.flipV) ctx.scale(p.flipH ? -1 : 1, p.flipV ? -1 : 1);\n ctx.translate(-(p.region.x + p.region.width / 2), -(p.region.y + p.region.height / 2));\n}\n\n/** Projects an image-space rectangle and returns its target-space bounding box. */\nexport function projectImageRect(\n rect: CropBox,\n p: ImageProjection\n): { x: number; y: number; width: number; height: number } {\n const corners = [\n projectImagePoint({ x: rect.x, y: rect.y }, p),\n projectImagePoint({ x: rect.x + rect.width, y: rect.y }, p),\n projectImagePoint({ x: rect.x, y: rect.y + rect.height }, p),\n projectImagePoint({ x: rect.x + rect.width, y: rect.y + rect.height }, p),\n ];\n const xs = corners.map((c) => c.x);\n const ys = corners.map((c) => c.y);\n const minX = Math.min(...xs);\n const minY = Math.min(...ys);\n return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };\n}\n\n/** Clamps a crop box to the image bounds. */\nexport function clampCropToImage(crop: CropBox, width: number, height: number): CropBox {\n const x = Math.max(0, Math.min(width - 1, crop.x));\n const y = Math.max(0, Math.min(height - 1, crop.y));\n return {\n x,\n y,\n width: Math.max(1, Math.min(width - x, crop.width)),\n height: Math.max(1, Math.min(height - y, crop.height)),\n };\n}\n\nfunction resolveViewport(\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping: boolean,\n cropOverride?: CropBox | null\n) {\n const { imageDimensions, crop: stateCrop, transform, zoom, pan } = state;\n const naturalWidth = imageDimensions.width || 800;\n const naturalHeight = imageDimensions.height || 600;\n const full: CropBox = { x: 0, y: 0, width: naturalWidth, height: naturalHeight };\n const activeCrop =\n cropOverride !== undefined && cropOverride !== null\n ? cropOverride\n : (stateCrop ?? (isActivelyCropping ? full : null));\n\n const region =\n !isActivelyCropping && activeCrop ? clampCropToImage(activeCrop, naturalWidth, naturalHeight) : full;\n const { width: baseW, height: baseH } = getRotatedSize(region.width, region.height, transform);\n\n const fitScale = Math.min((vpWidth * 0.85) / baseW, (vpHeight * 0.85) / baseH, 1);\n const centerX = vpWidth / 2 + pan.x;\n const centerY = vpHeight / 2 + pan.y;\n\n const projection: ImageProjection = {\n region,\n angle: normalizeAngle(transform.rotate),\n flipH: transform.flipH,\n flipV: transform.flipV,\n scale: zoom * fitScale,\n originX: centerX,\n originY: centerY,\n };\n\n return { activeCrop, baseW, baseH, fitScale, centerX, centerY, projection };\n}\n\n/**\n * Returns the natural-image -> canvas projection used by the interactive viewport.\n */\nexport function getViewportProjection(\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false,\n cropOverride?: CropBox | null\n): ImageProjection {\n return resolveViewport(state, vpWidth, vpHeight, isActivelyCropping, cropOverride).projection;\n}\n\n/**\n * Calculates current viewport projection metrics for canvas <-> image coordinate mapping.\n * `cropRect` is the on-screen (rotated/flipped) rectangle of the crop box.\n */\nexport function getViewportMetrics(\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false,\n cropOverride?: CropBox | null\n): ViewportMetrics {\n const { zoom } = state;\n const { activeCrop, baseW, baseH, fitScale, centerX, centerY, projection } = resolveViewport(\n state,\n vpWidth,\n vpHeight,\n isActivelyCropping,\n cropOverride\n );\n\n const renderW = baseW * fitScale;\n const renderH = baseH * fitScale;\n\n const imageRect = {\n x: centerX - (renderW * zoom) / 2,\n y: centerY - (renderH * zoom) / 2,\n width: renderW * zoom,\n height: renderH * zoom,\n };\n\n const cropRect = activeCrop ? projectImageRect(activeCrop, projection) : null;\n\n return {\n vpWidth,\n vpHeight,\n centerX,\n centerY,\n baseW,\n baseH,\n fitScale,\n renderW,\n renderH,\n totalScale: zoom * fitScale,\n imageRect,\n cropRect,\n };\n}\n\n/**\n * Converts a point on the canvas (in internal canvas pixel space) to natural image pixel\n * space, accounting for zoom, pan, fit scale, crop offset, rotation and flips.\n */\nexport function canvasToImagePoint(\n canvasPt: ImagePoint,\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false\n): ImagePoint {\n return unprojectCanvasPoint(canvasPt, getViewportProjection(state, vpWidth, vpHeight, isActivelyCropping));\n}\n\n/**\n * Converts a point in natural image pixel space to internal canvas pixel space.\n * Exact inverse of {@link canvasToImagePoint}.\n */\nexport function imageToCanvasPoint(\n imgPt: ImagePoint,\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false\n): ImagePoint {\n return projectImagePoint(imgPt, getViewportProjection(state, vpWidth, vpHeight, isActivelyCropping));\n}\n\nconst HANDLE_VECTORS: Record<Exclude<CropHandle, 'inside' | null>, [number, number]> = {\n nw: [-1, -1],\n n: [0, -1],\n ne: [1, -1],\n e: [1, 0],\n se: [1, 1],\n s: [0, 1],\n sw: [-1, 1],\n w: [-1, 0],\n};\n\nfunction vectorToHandle(x: number, y: number): CropHandle {\n const vx = Math.abs(x) < 0.38 ? 0 : Math.sign(x);\n const vy = Math.abs(y) < 0.38 ? 0 : Math.sign(y);\n for (const [name, [hx, hy]] of Object.entries(HANDLE_VECTORS)) {\n if (hx === vx && hy === vy) return name as CropHandle;\n }\n return null;\n}\n\n/**\n * Converts a crop handle in screen orientation to the equivalent edge/corner of the crop\n * box in natural image space, or the reverse with `toScreen = true`.\n */\nexport function mapCropHandle(handle: CropHandle, transform: ImageTransform, toScreen = false): CropHandle {\n if (handle === null || handle === 'inside') return handle;\n const [hx, hy] = HANDLE_VECTORS[handle];\n const [c, s] = cosSin(normalizeAngle(transform.rotate));\n const fx = transform.flipH ? -1 : 1;\n const fy = transform.flipV ? -1 : 1;\n if (toScreen) {\n const x = hx * fx;\n const y = hy * fy;\n return vectorToHandle(x * c - y * s, x * s + y * c);\n }\n const x = hx * c + hy * s;\n const y = -hx * s + hy * c;\n return vectorToHandle(x * fx, y * fy);\n}\n\n/**\n * Hit-tests the crop box and its corner/edge handles given a canvas point.\n * The returned handle names the edge/corner of the crop box in natural image space\n * (what {@link calculateCropDrag} expects); use `getCropCursor(handle, state.transform)`\n * to obtain the matching on-screen cursor.\n */\nexport function hitTestCrop(\n canvasPt: ImagePoint,\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n handleThreshold = 14,\n cropOverride?: CropBox | null\n): CropHandle {\n const metrics = getViewportMetrics(state, vpWidth, vpHeight, true, cropOverride);\n if (!metrics.cropRect) return null;\n\n const screenHandle = hitTestCropRect(canvasPt, metrics.cropRect, handleThreshold);\n return mapCropHandle(screenHandle, state.transform);\n}\n\nfunction hitTestCropRect(\n canvasPt: ImagePoint,\n rect: { x: number; y: number; width: number; height: number },\n handleThreshold: number\n): CropHandle {\n const { x, y, width: w, height: h } = rect;\n const px = canvasPt.x;\n const py = canvasPt.y;\n\n // 1. Corners (highest priority)\n const dist = (x1: number, y1: number, x2: number, y2: number) =>\n Math.hypot(x1 - x2, y1 - y2);\n\n if (dist(px, py, x, y) <= handleThreshold) return 'nw';\n if (dist(px, py, x + w, y) <= handleThreshold) return 'ne';\n if (dist(px, py, x + w, y + h) <= handleThreshold) return 'se';\n if (dist(px, py, x, y + h) <= handleThreshold) return 'sw';\n\n // 2. Edges\n const nearX = px >= x - handleThreshold && px <= x + w + handleThreshold;\n const nearY = py >= y - handleThreshold && py <= y + h + handleThreshold;\n\n if (nearX && Math.abs(py - y) <= handleThreshold) return 'n';\n if (nearX && Math.abs(py - (y + h)) <= handleThreshold) return 's';\n if (nearY && Math.abs(px - x) <= handleThreshold) return 'w';\n if (nearY && Math.abs(px - (x + w)) <= handleThreshold) return 'e';\n\n // 3. Inside crop region\n if (px >= x && px <= x + w && py >= y && py <= y + h) {\n return 'inside';\n }\n\n return null;\n}\n\n/**\n * Returns CSS cursor style corresponding to a crop handle. Pass the current transform when\n * the handle comes from {@link hitTestCrop} (image space) so the cursor matches the\n * on-screen orientation under rotation/flip.\n */\nexport function getCropCursor(handle: CropHandle, transform?: ImageTransform): string {\n const screenHandle = transform ? mapCropHandle(handle, transform, true) : handle;\n switch (screenHandle) {\n case 'nw':\n case 'se':\n return 'nwse-resize';\n case 'ne':\n case 'sw':\n return 'nesw-resize';\n case 'n':\n case 's':\n return 'ns-resize';\n case 'e':\n case 'w':\n return 'ew-resize';\n case 'inside':\n return 'move';\n default:\n return 'crosshair';\n }\n}\n\n/**\n * Calculates updated crop rectangle given a drag interaction.\n */\nexport function calculateCropDrag(\n startCrop: CropBox,\n handle: CropHandle,\n startImgPt: ImagePoint,\n currentImgPt: ImagePoint,\n imgWidth: number,\n imgHeight: number,\n aspectRatio?: number | null,\n transform?: ImageTransform\n): CropBox {\n // `aspectRatio` is the on-screen ratio; under a quarter turn it is inverted in image space.\n if (aspectRatio && transform && isQuarterTurnSwapped(transform)) {\n aspectRatio = 1 / aspectRatio;\n }\n const dx = currentImgPt.x - startImgPt.x;\n const dy = currentImgPt.y - startImgPt.y;\n\n // Whole crop box move\n if (handle === 'inside') {\n const maxX = Math.max(0, imgWidth - startCrop.width);\n const maxY = Math.max(0, imgHeight - startCrop.height);\n const newX = Math.max(0, Math.min(maxX, startCrop.x + dx));\n const newY = Math.max(0, Math.min(maxY, startCrop.y + dy));\n return {\n x: Math.round(newX),\n y: Math.round(newY),\n width: startCrop.width,\n height: startCrop.height,\n };\n }\n\n // Handle resizing\n let left = startCrop.x;\n let top = startCrop.y;\n let right = startCrop.x + startCrop.width;\n let bottom = startCrop.y + startCrop.height;\n\n const minSize = 20;\n\n if (handle === 'e' || handle === 'ne' || handle === 'se') {\n right = Math.min(imgWidth, Math.max(left + minSize, startCrop.x + startCrop.width + dx));\n }\n if (handle === 'w' || handle === 'nw' || handle === 'sw') {\n left = Math.max(0, Math.min(right - minSize, startCrop.x + dx));\n }\n if (handle === 's' || handle === 'se' || handle === 'sw') {\n bottom = Math.min(imgHeight, Math.max(top + minSize, startCrop.y + startCrop.height + dy));\n }\n if (handle === 'n' || handle === 'nw' || handle === 'ne') {\n top = Math.max(0, Math.min(bottom - minSize, startCrop.y + dy));\n }\n\n // Maintain aspect ratio if requested\n if (aspectRatio && aspectRatio > 0) {\n let currentW = right - left;\n let currentH = bottom - top;\n\n if (handle === 'e' || handle === 'w') {\n currentH = Math.min(imgHeight, currentW / aspectRatio);\n bottom = Math.min(imgHeight, top + currentH);\n } else if (handle === 'n' || handle === 's') {\n currentW = Math.min(imgWidth, currentH * aspectRatio);\n right = Math.min(imgWidth, left + currentW);\n } else {\n // Corner resizing\n const targetH = currentW / aspectRatio;\n if (top + targetH <= imgHeight) {\n bottom = top + targetH;\n } else {\n const targetW = currentH * aspectRatio;\n right = Math.min(imgWidth, left + targetW);\n }\n }\n }\n\n return {\n x: Math.round(left),\n y: Math.round(top),\n width: Math.round(Math.max(minSize, right - left)),\n height: Math.round(Math.max(minSize, bottom - top)),\n };\n}\n\n/**\n * Calculates bounding box of an annotation.\n */\nexport function getAnnotationBounds(ann: Annotation): { x: number; y: number; width: number; height: number } {\n switch (ann.type) {\n case 'rect':\n case 'image':\n return {\n x: Math.min(ann.x, ann.x + ann.width),\n y: Math.min(ann.y, ann.y + ann.height),\n width: Math.abs(ann.width),\n height: Math.abs(ann.height),\n };\n case 'circle':\n return {\n x: ann.x - Math.abs(ann.radiusX),\n y: ann.y - Math.abs(ann.radiusY),\n width: Math.abs(ann.radiusX) * 2,\n height: Math.abs(ann.radiusY) * 2,\n };\n case 'arrow':\n case 'line': {\n const minX = Math.min(ann.x, ann.endX);\n const minY = Math.min(ann.y, ann.endY);\n return {\n x: minX,\n y: minY,\n width: Math.max(Math.abs(ann.endX - ann.x), 10),\n height: Math.max(Math.abs(ann.endY - ann.y), 10),\n };\n }\n case 'pen': {\n if (!ann.points.length) return { x: ann.x, y: ann.y, width: 0, height: 0 };\n let minX = ann.points[0].x;\n let maxX = ann.points[0].x;\n let minY = ann.points[0].y;\n let maxY = ann.points[0].y;\n for (const pt of ann.points) {\n if (pt.x < minX) minX = pt.x;\n if (pt.x > maxX) maxX = pt.x;\n if (pt.y < minY) minY = pt.y;\n if (pt.y > maxY) maxY = pt.y;\n }\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n }\n case 'text':\n return {\n x: ann.x,\n y: ann.y,\n width: ann.text.length * ann.fontSize * 0.6,\n height: ann.fontSize * 1.2,\n };\n }\n}\n\n/**\n * Hit tests if a point in image space is within an annotation's bounds.\n */\nexport function hitTestAnnotation(imgPt: ImagePoint, ann: Annotation, padding = 10): boolean {\n const b = getAnnotationBounds(ann);\n return (\n imgPt.x >= b.x - padding &&\n imgPt.x <= b.x + b.width + padding &&\n imgPt.y >= b.y - padding &&\n imgPt.y <= b.y + b.height + padding\n );\n}\n\n","import type {\n Annotation,\n CropBox,\n ExportOptions,\n ImageEditorState,\n ImageTransform,\n PenAnnotation,\n RectAnnotation,\n CircleAnnotation,\n ArrowAnnotation,\n LineAnnotation,\n TextAnnotation,\n ImageAnnotation,\n} from './types';\nimport { applyAdjustments, applyDepthMask } from './filters';\nimport {\n applyImageProjectionToContext,\n clampCropToImage,\n getAnnotationBounds,\n getRotatedSize,\n getViewportMetrics,\n getViewportProjection,\n normalizeAngle,\n type ImageProjection,\n type ViewportMetrics,\n} from './geometry';\n\nexport interface RenderOptions {\n viewportWidth?: number;\n viewportHeight?: number;\n showCropOverlay?: boolean;\n showSelectionOverlay?: boolean;\n interactive?: boolean;\n draftAnnotation?: Annotation | null;\n draftCrop?: CropBox | null;\n}\n\n/**\n * Image Cache to avoid refetching HTMLImageElement objects repeatedly during re-renders.\n */\nclass ImageCache {\n private cache = new Map<string, HTMLImageElement>();\n\n public async get(src: string): Promise<HTMLImageElement> {\n const existing = this.cache.get(src);\n if (existing && existing.complete) {\n return existing;\n }\n\n return new Promise((resolve, reject) => {\n if (typeof window === 'undefined' || typeof Image === 'undefined') {\n reject(new Error('Image loading is only supported in browser/DOM environments'));\n return;\n }\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n this.cache.set(src, img);\n resolve(img);\n };\n img.onerror = (e) => reject(new Error(`Failed to load image from: ${src}`));\n img.src = src;\n\n // In JSDOM or test environments where Image resource loading is inactive, resolve promptly\n if (typeof navigator !== 'undefined' && /jsdom/i.test(navigator.userAgent)) {\n setTimeout(() => {\n try {\n Object.defineProperty(img, 'naturalWidth', { value: 800, configurable: true });\n Object.defineProperty(img, 'naturalHeight', { value: 600, configurable: true });\n Object.defineProperty(img, 'complete', { value: true, configurable: true });\n } catch {\n // Ignore definition errors in restrictive environments\n }\n this.cache.set(src, img);\n resolve(img);\n }, 10);\n }\n });\n }\n\n public set(src: string, img: HTMLImageElement): void {\n this.cache.set(src, img);\n }\n\n public clear(): void {\n this.cache.clear();\n }\n}\n\nexport const globalImageCache = new ImageCache();\n\n/**\n * Renders an image with all its transformations, filters, depth masks, and annotations\n * to a target HTMLCanvasElement.\n */\nexport class ImageEditorRenderer {\n public imageCache = globalImageCache;\n private cachedBaseCanvas: HTMLCanvasElement | null = null;\n private cachedBaseKey = '';\n private sourceIds = new WeakMap<object, number>();\n private nextSourceId = 0;\n\n /**\n * Helper to create a canvas element safely in browser/JSDOM.\n */\n public createCanvas(width: number, height: number): HTMLCanvasElement {\n if (typeof document !== 'undefined' && document.createElement) {\n const c = document.createElement('canvas');\n c.width = Math.max(1, Math.round(width));\n c.height = Math.max(1, Math.round(height));\n return c;\n }\n // Fallback for non-DOM / headless\n return {\n width: Math.max(1, Math.round(width)),\n height: Math.max(1, Math.round(height)),\n getContext: () => null,\n toDataURL: () => '',\n toBlob: () => {},\n } as unknown as HTMLCanvasElement;\n }\n\n /**\n * Renders the editor state onto a canvas (viewport or preview).\n */\n public async renderToCanvas(\n targetCanvas: HTMLCanvasElement,\n imageSource: CanvasImageSource | string,\n state: ImageEditorState,\n options: RenderOptions = {}\n ): Promise<void> {\n const ctx = targetCanvas.getContext('2d');\n if (!ctx) return;\n\n let img: CanvasImageSource;\n if (typeof imageSource === 'string') {\n img = await this.imageCache.get(imageSource);\n } else {\n img = imageSource;\n }\n\n const { width: naturalWidth, height: naturalHeight } = getSourceSize(img);\n // Geometry helpers read `imageDimensions`; make sure they see the real source size so the\n // viewport drawn here and the hit-testing done by the UI share one projection.\n const geoState: ImageEditorState =\n state.imageDimensions.width === naturalWidth && state.imageDimensions.height === naturalHeight\n ? state\n : { ...state, imageDimensions: { width: naturalWidth, height: naturalHeight } };\n\n const { crop, transform, adjustments, depthMask, annotations, selectedAnnotationId, zoom, pan, isOriginalCompared } = state;\n\n // Viewport dimensions\n const vpWidth = options.viewportWidth || targetCanvas.width;\n const vpHeight = options.viewportHeight || targetCanvas.height;\n\n targetCanvas.width = vpWidth;\n targetCanvas.height = vpHeight;\n\n ctx.clearRect(0, 0, vpWidth, vpHeight);\n\n // If in compare mode, render original image directly and exit\n if (isOriginalCompared) {\n this.drawFittedImage(ctx, img, naturalWidth, naturalHeight, vpWidth, vpHeight, zoom, pan);\n return;\n }\n\n // When actively in crop tool or overlay requested, show full image so user can adjust the crop box\n const isActivelyCropping = Boolean(options.showCropOverlay || state.activeTool === 'crop');\n const projection = getViewportProjection(geoState, vpWidth, vpHeight, isActivelyCropping);\n const { region } = projection;\n const rotated = getRotatedSize(region.width, region.height, transform);\n\n // 1. Offscreen canvas for Base Image + Transform (Flip/Rotate) + Crop + Adjustments + Depth mask\n const baseKey = [\n this.getSourceId(img),\n state.sourceUrl,\n `${naturalWidth}x${naturalHeight}`,\n `${region.x},${region.y},${region.width},${region.height}`,\n `${projection.angle},${transform.flipH},${transform.flipV}`,\n `${adjustments.brightness},${adjustments.contrast},${adjustments.saturation},${adjustments.exposure},${adjustments.temperature},${adjustments.blur},${adjustments.opacity}`,\n `${depthMask.enabled},${depthMask.maskSource},${depthMask.depthRange?.[0]}-${depthMask.depthRange?.[1]},${depthMask.softness},${depthMask.invert},${depthMask.mode}`,\n ].join('|');\n\n let baseCanvas = this.cachedBaseCanvas;\n if (!baseCanvas || this.cachedBaseKey !== baseKey) {\n baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state);\n this.cachedBaseCanvas = baseCanvas;\n this.cachedBaseKey = baseKey;\n }\n\n // 2. Draw the base canvas into the viewport (centred, fitted, zoomed, panned)\n const drawW = rotated.width * projection.scale;\n const drawH = rotated.height * projection.scale;\n ctx.drawImage(baseCanvas, projection.originX - drawW / 2, projection.originY - drawH / 2, drawW, drawH);\n\n // 3. Annotations live in natural image space: draw them through the same projection,\n // clipped to the visible (cropped) region.\n if (annotations.length > 0 || options.draftAnnotation) {\n ctx.save();\n applyImageProjectionToContext(ctx, projection);\n ctx.beginPath();\n ctx.rect(region.x, region.y, region.width, region.height);\n ctx.clip();\n\n for (const ann of annotations) {\n await this.drawAnnotation(ctx, ann);\n if (options.showSelectionOverlay && ann.id === selectedAnnotationId) {\n this.drawAnnotationSelection(ctx, ann);\n }\n }\n\n // Render active live draft annotation\n if (options.draftAnnotation) {\n await this.drawAnnotation(ctx, options.draftAnnotation);\n }\n\n ctx.restore();\n }\n\n // 4. Draw Crop Grid Overlay if crop overlay is requested\n if (isActivelyCropping) {\n const activeCrop =\n options.draftCrop !== undefined && options.draftCrop !== null\n ? options.draftCrop\n : (crop ?? { x: 0, y: 0, width: naturalWidth, height: naturalHeight });\n this.drawCropOverlay(ctx, getViewportMetrics(geoState, vpWidth, vpHeight, true, activeCrop));\n }\n }\n\n /**\n * Draws `region` of the source image with rotation/flip applied, then pixel adjustments\n * and the depth mask (which receives the identical transform so it stays aligned).\n */\n private async renderBaseImage(\n img: CanvasImageSource,\n naturalWidth: number,\n naturalHeight: number,\n region: CropBox,\n transform: ImageTransform,\n state: Pick<ImageEditorState, 'adjustments' | 'depthMask'>\n ): Promise<HTMLCanvasElement> {\n const { adjustments, depthMask } = state;\n const rotated = getRotatedSize(region.width, region.height, transform);\n const baseW = Math.max(1, Math.round(rotated.width));\n const baseH = Math.max(1, Math.round(rotated.height));\n const baseCanvas = this.createCanvas(baseW, baseH);\n const baseCtx = baseCanvas.getContext('2d');\n if (!baseCtx) return baseCanvas;\n\n const projection = getBaseProjection(region, transform, baseW, baseH);\n\n baseCtx.save();\n applyImageProjectionToContext(baseCtx, projection);\n baseCtx.drawImage(\n img,\n region.x, region.y, region.width, region.height,\n region.x, region.y, region.width, region.height\n );\n baseCtx.restore();\n\n const imageData = baseCtx.getImageData(0, 0, baseW, baseH);\n if (imageData) {\n applyAdjustments(imageData, adjustments);\n\n if (depthMask.enabled && depthMask.maskSource) {\n try {\n const maskImg = await this.imageCache.get(depthMask.maskSource);\n const maskSize = getSourceSize(maskImg, naturalWidth, naturalHeight);\n const sx = maskSize.width / naturalWidth;\n const sy = maskSize.height / naturalHeight;\n const maskCanvas = this.createCanvas(baseW, baseH);\n const maskCtx = maskCanvas.getContext('2d');\n if (maskCtx) {\n // The depth map covers the whole source image; scale it to natural pixels and\n // apply the exact same rotate/flip/crop projection as the image itself.\n maskCtx.save();\n applyImageProjectionToContext(maskCtx, projection);\n maskCtx.drawImage(\n maskImg,\n region.x * sx, region.y * sy, region.width * sx, region.height * sy,\n region.x, region.y, region.width, region.height\n );\n maskCtx.restore();\n const maskData = maskCtx.getImageData(0, 0, baseW, baseH);\n if (maskData) {\n applyDepthMask(imageData, maskData, depthMask);\n }\n }\n } catch {\n // Mask failed to load; continue without crashing\n }\n }\n\n baseCtx.putImageData(imageData, 0, 0);\n }\n\n return baseCanvas;\n }\n\n private getSourceId(img: CanvasImageSource): number {\n const key = img as unknown as object;\n let id = this.sourceIds.get(key);\n if (id === undefined) {\n id = ++this.nextSourceId;\n this.sourceIds.set(key, id);\n }\n return id;\n }\n\n /**\n * Draws an individual annotation onto the canvas.\n */\n public async drawAnnotation(ctx: CanvasRenderingContext2D, ann: Annotation): Promise<void> {\n ctx.save();\n if (ann.opacity !== undefined) {\n ctx.globalAlpha = ann.opacity;\n }\n\n if (ann.rotation) {\n ctx.translate(ann.x, ann.y);\n ctx.rotate((ann.rotation * Math.PI) / 180);\n ctx.translate(-ann.x, -ann.y);\n }\n\n switch (ann.type) {\n case 'pen': {\n const pen = ann as PenAnnotation;\n if (pen.points.length > 0) {\n ctx.beginPath();\n ctx.strokeStyle = pen.strokeColor;\n ctx.lineWidth = pen.strokeWidth;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n ctx.moveTo(pen.points[0].x, pen.points[0].y);\n for (let i = 1; i < pen.points.length; i++) {\n ctx.lineTo(pen.points[i].x, pen.points[i].y);\n }\n ctx.stroke();\n }\n break;\n }\n\n case 'rect': {\n const rect = ann as RectAnnotation;\n ctx.strokeStyle = rect.strokeColor;\n ctx.lineWidth = rect.strokeWidth;\n if (rect.fillColor) {\n ctx.fillStyle = rect.fillColor;\n ctx.fillRect(rect.x, rect.y, rect.width, rect.height);\n }\n if (rect.strokeWidth > 0) {\n ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);\n }\n break;\n }\n\n case 'circle': {\n const circle = ann as CircleAnnotation;\n ctx.beginPath();\n ctx.strokeStyle = circle.strokeColor;\n ctx.lineWidth = circle.strokeWidth;\n ctx.ellipse(circle.x, circle.y, Math.abs(circle.radiusX), Math.abs(circle.radiusY), 0, 0, 2 * Math.PI);\n if (circle.fillColor) {\n ctx.fillStyle = circle.fillColor;\n ctx.fill();\n }\n if (circle.strokeWidth > 0) {\n ctx.stroke();\n }\n break;\n }\n\n case 'arrow': {\n const arrow = ann as ArrowAnnotation;\n ctx.strokeStyle = arrow.strokeColor;\n ctx.fillStyle = arrow.strokeColor;\n ctx.lineWidth = arrow.strokeWidth;\n\n // Line body\n ctx.beginPath();\n ctx.moveTo(arrow.x, arrow.y);\n ctx.lineTo(arrow.endX, arrow.endY);\n ctx.stroke();\n\n // Arrow head\n const angle = Math.atan2(arrow.endY - arrow.y, arrow.endX - arrow.x);\n const headLen = Math.max(12, arrow.strokeWidth * 3);\n ctx.beginPath();\n ctx.moveTo(arrow.endX, arrow.endY);\n ctx.lineTo(\n arrow.endX - headLen * Math.cos(angle - Math.PI / 6),\n arrow.endY - headLen * Math.sin(angle - Math.PI / 6)\n );\n ctx.lineTo(\n arrow.endX - headLen * Math.cos(angle + Math.PI / 6),\n arrow.endY - headLen * Math.sin(angle + Math.PI / 6)\n );\n ctx.closePath();\n ctx.fill();\n break;\n }\n\n case 'line': {\n const line = ann as LineAnnotation;\n ctx.beginPath();\n ctx.strokeStyle = line.strokeColor;\n ctx.lineWidth = line.strokeWidth;\n ctx.moveTo(line.x, line.y);\n ctx.lineTo(line.endX, line.endY);\n ctx.stroke();\n break;\n }\n\n case 'text': {\n const textAnn = ann as TextAnnotation;\n ctx.font = `${textAnn.fontWeight || 'normal'} ${textAnn.fontSize}px ${textAnn.fontFamily || 'sans-serif'}`;\n ctx.fillStyle = textAnn.color;\n ctx.textBaseline = 'top';\n ctx.fillText(textAnn.text, textAnn.x, textAnn.y);\n break;\n }\n\n case 'image': {\n const imgAnn = ann as ImageAnnotation;\n try {\n const loadedImg = await this.imageCache.get(imgAnn.src);\n ctx.drawImage(loadedImg, imgAnn.x, imgAnn.y, imgAnn.width, imgAnn.height);\n } catch {\n // Skip if watermark image failed to load\n }\n break;\n }\n }\n\n ctx.restore();\n }\n\n /**\n * Draws selection box around active annotation.\n */\n private drawAnnotationSelection(ctx: CanvasRenderingContext2D, ann: Annotation): void {\n ctx.save();\n ctx.strokeStyle = '#3b82f6'; // Tailwind blue-500\n ctx.lineWidth = 1.5;\n ctx.setLineDash([4, 4]);\n\n const bounds = getAnnotationBounds(ann);\n const pad = 4;\n ctx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.width + pad * 2, bounds.height + pad * 2);\n\n // Draw handles\n ctx.fillStyle = '#ffffff';\n ctx.strokeStyle = '#3b82f6';\n ctx.setLineDash([]);\n const handleSize = 6;\n const corners = [\n { x: bounds.x - pad, y: bounds.y - pad },\n { x: bounds.x + bounds.width + pad, y: bounds.y - pad },\n { x: bounds.x - pad, y: bounds.y + bounds.height + pad },\n { x: bounds.x + bounds.width + pad, y: bounds.y + bounds.height + pad },\n ];\n for (const c of corners) {\n ctx.fillRect(c.x - handleSize / 2, c.y - handleSize / 2, handleSize, handleSize);\n ctx.strokeRect(c.x - handleSize / 2, c.y - handleSize / 2, handleSize, handleSize);\n }\n\n ctx.restore();\n }\n\n /** @deprecated Use the standalone `getAnnotationBounds` from the geometry module. */\n public getAnnotationBounds(ann: Annotation): { x: number; y: number; width: number; height: number } {\n return getAnnotationBounds(ann);\n }\n\n /**\n * Draws a rule-of-thirds crop grid overlay. All rectangles come from\n * `getViewportMetrics`, i.e. the crop box projected through rotation/flip.\n */\n private drawCropOverlay(ctx: CanvasRenderingContext2D, metrics: ViewportMetrics): void {\n const { imageRect, cropRect } = metrics;\n if (!cropRect) return;\n\n const imgL = imageRect.x;\n const imgT = imageRect.y;\n const imgR = imageRect.x + imageRect.width;\n const imgB = imageRect.y + imageRect.height;\n const startX = cropRect.x;\n const startY = cropRect.y;\n const cropBoxW = cropRect.width;\n const cropBoxH = cropRect.height;\n\n ctx.save();\n\n // Dark semi-transparent scrim outside crop box (within the image)\n ctx.fillStyle = 'rgba(0, 0, 0, 0.45)';\n // Top\n ctx.fillRect(imgL, imgT, imageRect.width, Math.max(0, startY - imgT));\n // Bottom\n ctx.fillRect(imgL, startY + cropBoxH, imageRect.width, Math.max(0, imgB - (startY + cropBoxH)));\n // Left\n ctx.fillRect(imgL, startY, Math.max(0, startX - imgL), cropBoxH);\n // Right\n ctx.fillRect(startX + cropBoxW, startY, Math.max(0, imgR - (startX + cropBoxW)), cropBoxH);\n\n // Crop border\n ctx.strokeStyle = '#ffffff';\n ctx.lineWidth = 2;\n ctx.strokeRect(startX, startY, cropBoxW, cropBoxH);\n\n // Rule of thirds lines\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';\n ctx.lineWidth = 1;\n ctx.beginPath();\n // Vertical lines\n ctx.moveTo(startX + cropBoxW / 3, startY);\n ctx.lineTo(startX + cropBoxW / 3, startY + cropBoxH);\n ctx.moveTo(startX + (cropBoxW * 2) / 3, startY);\n ctx.lineTo(startX + (cropBoxW * 2) / 3, startY + cropBoxH);\n // Horizontal lines\n ctx.moveTo(startX, startY + cropBoxH / 3);\n ctx.lineTo(startX + cropBoxW, startY + cropBoxH / 3);\n ctx.moveTo(startX, startY + (cropBoxH * 2) / 3);\n ctx.lineTo(startX + cropBoxW, startY + (cropBoxH * 2) / 3);\n ctx.stroke();\n\n // Corner and edge handles\n ctx.fillStyle = '#ffffff';\n ctx.strokeStyle = '#0284c7';\n ctx.lineWidth = 1.5;\n const handleSize = 10;\n const handles = [\n // Corners\n { x: startX, y: startY },\n { x: startX + cropBoxW, y: startY },\n { x: startX, y: startY + cropBoxH },\n { x: startX + cropBoxW, y: startY + cropBoxH },\n // Edges\n { x: startX + cropBoxW / 2, y: startY },\n { x: startX + cropBoxW / 2, y: startY + cropBoxH },\n { x: startX, y: startY + cropBoxH / 2 },\n { x: startX + cropBoxW, y: startY + cropBoxH / 2 },\n ];\n for (const h of handles) {\n ctx.fillRect(h.x - handleSize / 2, h.y - handleSize / 2, handleSize, handleSize);\n ctx.strokeRect(h.x - handleSize / 2, h.y - handleSize / 2, handleSize, handleSize);\n }\n\n ctx.restore();\n }\n\n private drawFittedImage(\n ctx: CanvasRenderingContext2D,\n img: CanvasImageSource,\n imgW: number,\n imgH: number,\n vpW: number,\n vpH: number,\n zoom: number,\n pan: { x: number; y: number }\n ): void {\n ctx.save();\n ctx.translate(vpW / 2 + pan.x, vpH / 2 + pan.y);\n ctx.scale(zoom, zoom);\n const fitScale = Math.min((vpW * 0.85) / imgW, (vpH * 0.85) / imgH, 1);\n const renderW = imgW * fitScale;\n const renderH = imgH * fitScale;\n ctx.drawImage(img, -renderW / 2, -renderH / 2, renderW, renderH);\n ctx.restore();\n }\n\n /**\n * Performs an export render at exact pixel dimensions without zoom/pan viewport offsets.\n */\n public async renderExport(\n imageSource: CanvasImageSource | string,\n state: ImageEditorState,\n options: ExportOptions = {}\n ): Promise<HTMLCanvasElement> {\n let img: CanvasImageSource;\n if (typeof imageSource === 'string') {\n img = await this.imageCache.get(imageSource);\n } else {\n img = imageSource;\n }\n\n const { width: naturalWidth, height: naturalHeight } = getSourceSize(img);\n const { crop, transform, annotations } = state;\n\n const region = crop\n ? clampCropToImage(crop, naturalWidth, naturalHeight)\n : { x: 0, y: 0, width: naturalWidth, height: naturalHeight };\n const rotated = getRotatedSize(region.width, region.height, transform);\n const baseW = Math.max(1, Math.round(rotated.width));\n const baseH = Math.max(1, Math.round(rotated.height));\n\n const exportW = options.width || baseW;\n const exportH = options.height || baseH;\n\n const exportCanvas = this.createCanvas(exportW, exportH);\n const ctx = exportCanvas.getContext('2d');\n if (!ctx) return exportCanvas;\n\n const baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state);\n\n // Scale to target export dimensions\n ctx.drawImage(baseCanvas, 0, 0, baseW, baseH, 0, 0, exportW, exportH);\n\n // Draw annotations (natural image space) through the same rotate/flip/crop projection\n if (options.includeAnnotations !== false && annotations.length > 0) {\n ctx.save();\n ctx.scale(exportW / baseW, exportH / baseH);\n applyImageProjectionToContext(ctx, getBaseProjection(region, transform, baseW, baseH));\n for (const ann of annotations) {\n await this.drawAnnotation(ctx, ann);\n }\n ctx.restore();\n }\n\n return exportCanvas;\n }\n\n /**\n * Generates a strictly binary mask canvas (white = area to regenerate, black = keep) in\n * natural image pixel space of the current source image — the same space the crop box\n * and annotations are stored in. Pair it with `toSourceDataURL()` for AI inpainting.\n */\n public async renderMask(\n state: ImageEditorState,\n options?: { useAnnotations?: boolean; useCrop?: boolean }\n ): Promise<HTMLCanvasElement> {\n const naturalWidth = Math.max(1, Math.round(state.imageDimensions.width));\n const naturalHeight = Math.max(1, Math.round(state.imageDimensions.height));\n const maskCanvas = this.createCanvas(naturalWidth, naturalHeight);\n const ctx = maskCanvas.getContext('2d');\n if (!ctx) return maskCanvas;\n\n // Background: unmasked (black)\n ctx.fillStyle = MASK_BLACK;\n ctx.fillRect(0, 0, naturalWidth, naturalHeight);\n\n const useCrop = options?.useCrop ?? true;\n const useAnnotations = options?.useAnnotations ?? true;\n\n // Crop box mask (if active)\n if (useCrop && state.crop) {\n ctx.fillStyle = MASK_WHITE;\n ctx.fillRect(state.crop.x, state.crop.y, state.crop.width, state.crop.height);\n }\n\n // Annotation masks (drawn in solid white, never in annotation colours)\n if (useAnnotations && state.annotations.length > 0) {\n for (const ann of state.annotations) {\n this.drawAnnotationMask(ctx, ann);\n }\n }\n\n // Anti-aliased edges produce grey pixels: threshold to pure black/white.\n const imageData = ctx.getImageData?.(0, 0, naturalWidth, naturalHeight);\n if (imageData) {\n const d = imageData.data;\n for (let i = 0; i < d.length; i += 4) {\n const v = d[i] >= 128 ? 255 : 0;\n d[i] = v;\n d[i + 1] = v;\n d[i + 2] = v;\n d[i + 3] = 255;\n }\n ctx.putImageData(imageData, 0, 0);\n }\n\n return maskCanvas;\n }\n\n private drawAnnotationMask(ctx: CanvasRenderingContext2D, ann: Annotation): void {\n ctx.save();\n ctx.globalAlpha = 1;\n ctx.fillStyle = MASK_WHITE;\n ctx.strokeStyle = MASK_WHITE;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n if (ann.rotation) {\n ctx.translate(ann.x, ann.y);\n ctx.rotate((ann.rotation * Math.PI) / 180);\n ctx.translate(-ann.x, -ann.y);\n }\n\n switch (ann.type) {\n case 'pen': {\n if (ann.points.length > 0) {\n ctx.beginPath();\n ctx.lineWidth = Math.max(ann.strokeWidth || 0, 8);\n ctx.moveTo(ann.points[0].x, ann.points[0].y);\n for (let i = 1; i < ann.points.length; i++) {\n ctx.lineTo(ann.points[i].x, ann.points[i].y);\n }\n ctx.stroke();\n }\n break;\n }\n case 'circle': {\n ctx.beginPath();\n ctx.ellipse(ann.x, ann.y, Math.abs(ann.radiusX), Math.abs(ann.radiusY), 0, 0, Math.PI * 2);\n ctx.fill();\n break;\n }\n case 'arrow':\n case 'line': {\n ctx.beginPath();\n ctx.lineWidth = Math.max(ann.strokeWidth || 0, 8);\n ctx.moveTo(ann.x, ann.y);\n ctx.lineTo(ann.endX, ann.endY);\n ctx.stroke();\n break;\n }\n case 'rect':\n case 'image':\n case 'text': {\n const b = getAnnotationBounds(ann);\n ctx.fillRect(b.x, b.y, b.width, b.height);\n break;\n }\n }\n ctx.restore();\n }\n}\n\nconst MASK_WHITE = '#ffffff';\nconst MASK_BLACK = '#000000';\n\nfunction getSourceSize(img: CanvasImageSource, fallbackW = 800, fallbackH = 600): { width: number; height: number } {\n const anyImg = img as any;\n return {\n width: anyImg.naturalWidth || anyImg.videoWidth || anyImg.width || fallbackW,\n height: anyImg.naturalHeight || anyImg.videoHeight || anyImg.height || fallbackH,\n };\n}\n\nfunction getBaseProjection(region: CropBox, transform: ImageTransform, baseW: number, baseH: number): ImageProjection {\n return {\n region,\n angle: normalizeAngle(transform.rotate),\n flipH: transform.flipH,\n flipV: transform.flipV,\n scale: 1,\n originX: baseW / 2,\n originY: baseH / 2,\n };\n}\n","import {\n Annotation,\n CreateAnnotationPayload,\n AspectRatio,\n CropBox,\n DepthMaskOptions,\n ExportOptions,\n ImageAdjustments,\n ImageDimensions,\n ImageEditorOptions,\n ImageEditorState,\n ImageEditorTool,\n ImageTransform,\n ImagePoint,\n SerializedImageEditorState,\n} from './types';\nimport { ImageEditorRenderer, RenderOptions } from './renderer';\nimport { clampCropToImage, isQuarterTurnSwapped } from './geometry';\n\n/** The image a history entry was recorded against. */\ninterface SourceSnapshot {\n sourceUrl: string | null;\n element: CanvasImageSource | null;\n dimensions: ImageDimensions;\n}\n\n/**\n * Undo/redo entry: the serializable edit state plus the source image it applies to, so\n * AI edits that replace the image are restorable.\n */\ninterface HistoryEntry {\n state: SerializedImageEditorState;\n source: SourceSnapshot;\n}\n\nexport const DEFAULT_ADJUSTMENTS: ImageAdjustments = {\n brightness: 0,\n contrast: 0,\n saturation: 0,\n exposure: 0,\n temperature: 0,\n blur: 0,\n opacity: 1,\n};\n\nexport const DEFAULT_TRANSFORM: ImageTransform = {\n rotate: 0,\n flipH: false,\n flipV: false,\n};\n\nexport const DEFAULT_DEPTH_MASK: DepthMaskOptions = {\n enabled: false,\n maskSource: null,\n mode: 'threshold',\n depthRange: [0, 255],\n softness: 10,\n invert: false,\n};\n\n/**\n * ImageEditorController manages the complete lifecycle, canvas rendering pipeline,\n * tool interaction states, undo/redo history, serialization, and image export.\n */\nexport class ImageEditorController {\n private state: ImageEditorState;\n private renderer: ImageEditorRenderer;\n private imageElement: CanvasImageSource | null = null;\n private listeners = new Set<(state: ImageEditorState) => void>();\n private onStateChange?: (state: ImageEditorState) => void;\n\n // History stacks\n private undoStack: HistoryEntry[] = [];\n private redoStack: HistoryEntry[] = [];\n private maxHistoryDepth: number;\n private isApplyingHistory = false;\n private initialSnapshot: HistoryEntry;\n\n constructor(options: ImageEditorOptions = {}) {\n this.maxHistoryDepth = options.maxHistoryDepth ?? 50;\n this.onStateChange = options.onStateChange;\n this.renderer = new ImageEditorRenderer();\n\n const initialAdjustments: ImageAdjustments = {\n ...DEFAULT_ADJUSTMENTS,\n ...options.initialState?.adjustments,\n };\n\n const initialTransform: ImageTransform = {\n ...DEFAULT_TRANSFORM,\n ...options.initialState?.transform,\n };\n\n const initialDepthMask: DepthMaskOptions = {\n ...DEFAULT_DEPTH_MASK,\n ...options.initialState?.depthMask,\n };\n\n this.state = {\n sourceUrl: typeof options.image === 'string' ? options.image : null,\n imageDimensions: { width: 800, height: 600 },\n crop: options.initialState?.crop ?? null,\n transform: initialTransform,\n adjustments: initialAdjustments,\n depthMask: initialDepthMask,\n annotations: options.initialState?.annotations ?? [],\n selectedAnnotationId: null,\n activeTool: 'select',\n zoom: 1,\n pan: { x: 0, y: 0 },\n isOriginalCompared: false,\n };\n\n if (options.image instanceof Object && 'width' in options.image) {\n this.imageElement = options.image as CanvasImageSource;\n this.state.sourceUrl = (options.image as any).src || null;\n this.state.imageDimensions = {\n width: (options.image as any).naturalWidth || (options.image as any).width || 800,\n height: (options.image as any).naturalHeight || (options.image as any).height || 600,\n };\n }\n\n this.initialSnapshot = this.captureHistoryEntry();\n\n if (typeof options.image === 'string') {\n this.loadImage(options.image);\n }\n }\n\n // --- Image Loading ---\n\n /**\n * Loads a new source image and makes it the baseline that `reset()` returns to.\n * Does not record history; use `applyInpaintedImage` / `applyBackgroundRemovedImage`\n * for undoable source replacement.\n */\n public async loadImage(src: string | HTMLImageElement): Promise<void> {\n // Keep the URL visible synchronously (UI bindings compare against it while loading).\n if (typeof src === 'string') this.state.sourceUrl = src;\n const source = await this.resolveSource(src);\n this.imageElement = source.element;\n this.state.sourceUrl = source.sourceUrl;\n this.state.imageDimensions = source.dimensions;\n\n this.initialSnapshot = this.captureHistoryEntry();\n this.notify();\n }\n\n private async resolveSource(src: string | HTMLImageElement): Promise<SourceSnapshot> {\n if (typeof src === 'string') {\n try {\n const img = await this.renderer.imageCache.get(src);\n return {\n sourceUrl: src,\n element: img,\n dimensions: {\n width: img.naturalWidth || img.width || 800,\n height: img.naturalHeight || img.height || 600,\n },\n };\n } catch {\n // In SSR / non-DOM environments, retain current dimensions\n return { sourceUrl: src, element: null, dimensions: { ...this.state.imageDimensions } };\n }\n }\n return {\n sourceUrl: (src as any).src || null,\n element: src,\n dimensions: {\n width: src.naturalWidth || src.width || 800,\n height: src.naturalHeight || src.height || 600,\n },\n };\n }\n\n public getImageElement(): CanvasImageSource | null {\n return this.imageElement;\n }\n\n // --- State & Subscription ---\n\n public getState(): ImageEditorState {\n return this.state;\n }\n\n public subscribe(listener: (state: ImageEditorState) => void): () => void {\n this.listeners.add(listener);\n listener(this.state);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private notify(): void {\n const currentState = { ...this.state };\n this.onStateChange?.(currentState);\n this.listeners.forEach((l) => l(currentState));\n }\n\n // --- History (Undo / Redo / Reset) ---\n\n private captureHistoryEntry(): HistoryEntry {\n return {\n state: this.serializeState(),\n source: {\n sourceUrl: this.state.sourceUrl,\n element: this.imageElement,\n dimensions: { ...this.state.imageDimensions },\n },\n };\n }\n\n private restoreHistoryEntry(entry: HistoryEntry): void {\n this.applySerializedState(entry.state);\n this.imageElement = entry.source.element;\n this.state.sourceUrl = entry.source.sourceUrl;\n this.state.imageDimensions = { ...entry.source.dimensions };\n if (\n this.state.selectedAnnotationId &&\n !this.state.annotations.some((a) => a.id === this.state.selectedAnnotationId)\n ) {\n this.state.selectedAnnotationId = null;\n }\n }\n\n private recordHistory(): void {\n if (this.isApplyingHistory) return;\n this.undoStack.push(this.captureHistoryEntry());\n if (this.undoStack.length > this.maxHistoryDepth) {\n this.undoStack.shift();\n }\n this.redoStack = [];\n }\n\n public get canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n public get canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n public undo(): boolean {\n if (!this.canUndo) return false;\n this.isApplyingHistory = true;\n this.redoStack.push(this.captureHistoryEntry());\n\n const prev = this.undoStack.pop()!;\n this.restoreHistoryEntry(prev);\n this.isApplyingHistory = false;\n this.notify();\n return true;\n }\n\n public redo(): boolean {\n if (!this.canRedo) return false;\n this.isApplyingHistory = true;\n this.undoStack.push(this.captureHistoryEntry());\n\n const next = this.redoStack.pop()!;\n this.restoreHistoryEntry(next);\n this.isApplyingHistory = false;\n this.notify();\n return true;\n }\n\n public reset(): void {\n this.recordHistory();\n this.restoreHistoryEntry(this.initialSnapshot);\n this.state.zoom = 1;\n this.state.pan = { x: 0, y: 0 };\n this.state.activeTool = 'select';\n this.state.selectedAnnotationId = null;\n this.notify();\n }\n\n // --- Active Tool ---\n\n public setTool(tool: ImageEditorTool): void {\n if (this.state.activeTool === tool) return;\n this.state.activeTool = tool;\n if (tool !== 'select') {\n this.state.selectedAnnotationId = null;\n }\n // If switching to crop tool and no crop is defined, initialize crop to full image\n if (tool === 'crop' && !this.state.crop) {\n this.state.crop = {\n x: 0,\n y: 0,\n width: this.state.imageDimensions.width,\n height: this.state.imageDimensions.height,\n };\n }\n this.notify();\n }\n\n // --- Crop ---\n\n public setCrop(crop: CropBox | null, addToHistory = true): void {\n if (addToHistory) {\n this.recordHistory();\n }\n this.state.crop = crop;\n this.notify();\n }\n\n public applyCropPreset(ratio: AspectRatio): void {\n this.recordHistory();\n const { width, height } = this.state.imageDimensions;\n // `ratio` is the on-screen aspect; the crop box lives in natural image space.\n if (ratio && isQuarterTurnSwapped(this.state.transform)) {\n ratio = 1 / ratio;\n }\n if (!ratio) {\n // Freeform: full image\n this.state.crop = { x: 0, y: 0, width, height };\n } else {\n let targetW = width;\n let targetH = targetW / ratio;\n\n if (targetH > height) {\n targetH = height;\n targetW = targetH * ratio;\n }\n\n const x = Math.max(0, Math.round((width - targetW) / 2));\n const y = Math.max(0, Math.round((height - targetH) / 2));\n this.state.crop = {\n x,\n y,\n width: Math.round(targetW),\n height: Math.round(targetH),\n };\n }\n this.notify();\n }\n\n public resetCrop(): void {\n this.recordHistory();\n this.state.crop = null;\n this.notify();\n }\n\n // --- Transformations (Rotate / Flip) ---\n\n public rotate(degrees: number): void {\n this.recordHistory();\n const current = this.state.transform.rotate;\n this.state.transform = { ...this.state.transform, rotate: (current + degrees) % 360 };\n this.notify();\n }\n\n public setRotation(degrees: number): void {\n this.recordHistory();\n this.state.transform = { ...this.state.transform, rotate: degrees % 360 };\n this.notify();\n }\n\n public flipHorizontal(): void {\n this.recordHistory();\n this.state.transform = { ...this.state.transform, flipH: !this.state.transform.flipH };\n this.notify();\n }\n\n public flipVertical(): void {\n this.recordHistory();\n this.state.transform = { ...this.state.transform, flipV: !this.state.transform.flipV };\n this.notify();\n }\n\n // --- Adjustments ---\n\n public setAdjustments(partial: Partial<ImageAdjustments>, commitToHistory = true): void {\n if (commitToHistory) {\n this.recordHistory();\n }\n this.state.adjustments = {\n ...this.state.adjustments,\n ...partial,\n };\n this.notify();\n }\n\n public resetAdjustments(): void {\n this.recordHistory();\n this.state.adjustments = { ...DEFAULT_ADJUSTMENTS };\n this.notify();\n }\n\n // --- Depth Masking (Gleamforge) ---\n\n public setDepthMask(partial: Partial<DepthMaskOptions>): void {\n this.recordHistory();\n this.state.depthMask = {\n ...this.state.depthMask,\n ...partial,\n };\n this.notify();\n }\n\n public resetDepthMask(): void {\n this.recordHistory();\n this.state.depthMask = { ...DEFAULT_DEPTH_MASK };\n this.notify();\n }\n\n // --- Annotations & Layers ---\n\n public addAnnotation(annotation: CreateAnnotationPayload): string {\n this.recordHistory();\n const id = `ann_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\n const fullAnnotation = { ...annotation, id } as Annotation;\n this.state.annotations = [...this.state.annotations, fullAnnotation];\n this.state.selectedAnnotationId = id;\n this.notify();\n return id;\n }\n\n public updateAnnotation(id: string, partial: Partial<Annotation>, commitToHistory = true): void {\n if (commitToHistory) {\n this.recordHistory();\n }\n this.state.annotations = this.state.annotations.map((ann) => {\n if (ann.id === id) {\n return { ...ann, ...partial } as Annotation;\n }\n return ann;\n });\n this.notify();\n }\n\n public removeAnnotation(id: string): void {\n this.recordHistory();\n this.state.annotations = this.state.annotations.filter((ann) => ann.id !== id);\n if (this.state.selectedAnnotationId === id) {\n this.state.selectedAnnotationId = null;\n }\n this.notify();\n }\n\n public clearAnnotations(): void {\n this.recordHistory();\n this.state.annotations = [];\n this.state.selectedAnnotationId = null;\n this.notify();\n }\n\n public selectAnnotation(id: string | null): void {\n this.state.selectedAnnotationId = id;\n this.notify();\n }\n\n // --- Zoom & Pan ---\n\n public setZoom(zoom: number): void {\n this.state.zoom = Math.max(0.1, Math.min(10, zoom));\n this.notify();\n }\n\n public zoomIn(factor = 1.2): void {\n this.setZoom(this.state.zoom * factor);\n }\n\n public zoomOut(factor = 1.2): void {\n this.setZoom(this.state.zoom / factor);\n }\n\n public resetZoom(): void {\n this.state.zoom = 1;\n this.state.pan = { x: 0, y: 0 };\n this.notify();\n }\n\n public setPan(pan: ImagePoint): void {\n this.state.pan = pan;\n this.notify();\n }\n\n public setComparing(comparing: boolean): void {\n if (this.state.isOriginalCompared === comparing) return;\n this.state.isOriginalCompared = comparing;\n this.notify();\n }\n\n // --- Rendering & Export ---\n\n public async render(targetCanvas: HTMLCanvasElement, options?: RenderOptions): Promise<void> {\n const source = this.imageElement || this.state.sourceUrl;\n if (!source) return;\n try {\n await this.renderer.renderToCanvas(targetCanvas, source, this.state, options);\n } catch {\n // In non-DOM or test mock environments without image loading, safely ignore\n }\n }\n\n public async renderExport(options?: ExportOptions): Promise<HTMLCanvasElement> {\n const source = this.imageElement || this.state.sourceUrl;\n if (!source) {\n return this.renderer.createCanvas(1, 1);\n }\n return this.renderer.renderExport(source, this.state, options);\n }\n\n public async toDataURL(options: ExportOptions = {}): Promise<string> {\n const canvas = await this.renderExport(options);\n const format = options.format || 'image/png';\n const quality = options.quality ?? 0.92;\n return canvas.toDataURL(format, quality);\n }\n\n public async toBlob(options: ExportOptions = {}): Promise<Blob> {\n const canvas = await this.renderExport(options);\n const format = options.format || 'image/png';\n const quality = options.quality ?? 0.92;\n\n return new Promise((resolve, reject) => {\n if (canvas.toBlob) {\n canvas.toBlob((blob) => {\n if (blob) resolve(blob);\n else reject(new Error('Failed to create Blob from canvas'));\n }, format, quality);\n } else {\n // Fallback for environments lacking canvas.toBlob\n try {\n const dataUrl = canvas.toDataURL(format, quality);\n const parts = dataUrl.split(',');\n const byteStr = atob(parts[1]);\n const ab = new ArrayBuffer(byteStr.length);\n const ia = new Uint8Array(ab);\n for (let i = 0; i < byteStr.length; i++) {\n ia[i] = byteStr.charCodeAt(i);\n }\n resolve(new Blob([ab], { type: format }));\n } catch (err) {\n reject(err);\n }\n }\n });\n }\n\n // --- AI Masking & Transformation Helpers ---\n\n public async renderMask(options?: { useAnnotations?: boolean; useCrop?: boolean }): Promise<HTMLCanvasElement> {\n return this.renderer.renderMask(this.state, options);\n }\n\n public async toMaskDataURL(options?: { useAnnotations?: boolean; useCrop?: boolean }): Promise<string> {\n const canvas = await this.renderMask(options);\n return canvas.toDataURL('image/png');\n }\n\n /**\n * Renders the current source image at natural size with no crop, rotation, flip,\n * adjustments or annotations. This is the image that `toMaskDataURL()` is aligned with\n * and the space that `applyInpaintedImage` / `applyBackgroundRemovedImage` expect back.\n */\n public async renderSource(): Promise<HTMLCanvasElement> {\n const { width, height } = this.state.imageDimensions;\n const canvas = this.renderer.createCanvas(width, height);\n const source = this.imageElement || this.state.sourceUrl;\n const ctx = canvas.getContext('2d');\n if (!ctx || !source) return canvas;\n const img = typeof source === 'string' ? await this.renderer.imageCache.get(source) : source;\n ctx.drawImage(img, 0, 0, width, height);\n return canvas;\n }\n\n public async toSourceDataURL(options: Pick<ExportOptions, 'format' | 'quality'> = {}): Promise<string> {\n const canvas = await this.renderSource();\n return canvas.toDataURL(options.format || 'image/png', options.quality ?? 0.92);\n }\n\n /**\n * Replaces the source image with an inpainted result (natural image space, see\n * `toSourceDataURL`) and clears the mask annotations, as ONE undoable step.\n */\n public async applyInpaintedImage(newDataUrl: string): Promise<void> {\n await this.replaceSource(newDataUrl, true);\n }\n\n /**\n * Replaces the source image with a background-removed result as ONE undoable step.\n * Crop and annotations are kept (rescaled if the new image has different dimensions).\n */\n public async applyBackgroundRemovedImage(newDataUrl: string): Promise<void> {\n await this.replaceSource(newDataUrl, false);\n }\n\n private async replaceSource(src: string, clearAnnotations: boolean): Promise<void> {\n const next = await this.resolveSource(src);\n this.recordHistory();\n\n const prev = this.state.imageDimensions;\n const sx = next.dimensions.width / (prev.width || next.dimensions.width);\n const sy = next.dimensions.height / (prev.height || next.dimensions.height);\n\n this.imageElement = next.element;\n this.state.sourceUrl = next.sourceUrl;\n this.state.imageDimensions = next.dimensions;\n\n if (sx !== 1 || sy !== 1) {\n if (this.state.crop) {\n this.state.crop = clampCropToImage(\n {\n x: Math.round(this.state.crop.x * sx),\n y: Math.round(this.state.crop.y * sy),\n width: Math.round(this.state.crop.width * sx),\n height: Math.round(this.state.crop.height * sy),\n },\n next.dimensions.width,\n next.dimensions.height\n );\n }\n this.state.annotations = this.state.annotations.map((ann) => scaleAnnotation(ann, sx, sy));\n }\n\n if (clearAnnotations) {\n this.state.annotations = [];\n this.state.selectedAnnotationId = null;\n }\n this.notify();\n }\n\n // --- Serialization ---\n\n public toJSON(): SerializedImageEditorState {\n return this.serializeState();\n }\n\n public loadJSON(data: SerializedImageEditorState): void {\n this.recordHistory();\n this.applySerializedState(data);\n this.notify();\n }\n\n private serializeState(): SerializedImageEditorState {\n return {\n version: 1,\n crop: this.state.crop ? { ...this.state.crop } : null,\n transform: { ...this.state.transform },\n adjustments: { ...this.state.adjustments },\n depthMask: {\n ...this.state.depthMask,\n depthRange: [...this.state.depthMask.depthRange] as [number, number],\n },\n annotations: JSON.parse(JSON.stringify(this.state.annotations)),\n };\n }\n\n private applySerializedState(data: SerializedImageEditorState): void {\n this.state.crop = data.crop ? { ...data.crop } : null;\n this.state.transform = { ...data.transform };\n this.state.adjustments = { ...data.adjustments };\n this.state.depthMask = {\n ...data.depthMask,\n depthRange: [...data.depthMask.depthRange] as [number, number],\n };\n this.state.annotations = JSON.parse(JSON.stringify(data.annotations || []));\n }\n}\n\n/** Scales an annotation's geometry from one image size to another. */\nfunction scaleAnnotation(ann: Annotation, sx: number, sy: number): Annotation {\n const k = Math.sqrt(Math.abs(sx * sy));\n const base = { ...ann, x: ann.x * sx, y: ann.y * sy } as Annotation;\n switch (base.type) {\n case 'pen':\n return { ...base, points: base.points.map((p) => ({ x: p.x * sx, y: p.y * sy })), strokeWidth: base.strokeWidth * k };\n case 'rect':\n return { ...base, width: base.width * sx, height: base.height * sy, strokeWidth: base.strokeWidth * k };\n case 'image':\n return { ...base, width: base.width * sx, height: base.height * sy };\n case 'circle':\n return { ...base, radiusX: base.radiusX * sx, radiusY: base.radiusY * sy, strokeWidth: base.strokeWidth * k };\n case 'arrow':\n case 'line':\n return { ...base, endX: base.endX * sx, endY: base.endY * sy, strokeWidth: base.strokeWidth * k };\n case 'text':\n return { ...base, fontSize: base.fontSize * k };\n }\n}\n"]}
|