@pixerate/editor 0.22.0 → 0.23.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/README.md +80 -2
- package/THIRD_PARTY_NOTICES.md +88 -0
- package/dist/agent/index.cjs +83 -108
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +69 -211
- package/dist/agent/index.d.ts +69 -211
- package/dist/agent/index.js +76 -101
- package/dist/agent/index.js.map +1 -1
- package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
- package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
- package/dist/canvas/index.cjs +879 -83
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +291 -186
- package/dist/canvas/index.d.ts +291 -186
- package/dist/canvas/index.js +865 -69
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-26BVFKHU.cjs +453 -0
- package/dist/chunk-26BVFKHU.cjs.map +1 -0
- package/dist/chunk-2H7ZHPEJ.js +762 -0
- package/dist/chunk-2H7ZHPEJ.js.map +1 -0
- package/dist/chunk-7DWMJCAT.js +453 -0
- package/dist/chunk-7DWMJCAT.js.map +1 -0
- package/dist/chunk-ARRTP33I.js +101 -0
- package/dist/chunk-ARRTP33I.js.map +1 -0
- package/dist/chunk-CPOKFNBG.cjs +101 -0
- package/dist/chunk-CPOKFNBG.cjs.map +1 -0
- package/dist/chunk-G2I4QJOT.cjs +762 -0
- package/dist/chunk-G2I4QJOT.cjs.map +1 -0
- package/dist/{chunk-RU52I7QO.js → chunk-K3R5NG2D.js} +147 -42
- package/dist/chunk-K3R5NG2D.js.map +1 -0
- package/dist/{chunk-DENSCKFC.js → chunk-QBJ22YZR.js} +423 -30
- package/dist/chunk-QBJ22YZR.js.map +1 -0
- package/dist/{chunk-SAZRENEZ.cjs → chunk-RIKBJLS2.cjs} +424 -31
- package/dist/chunk-RIKBJLS2.cjs.map +1 -0
- package/dist/{chunk-6M3BCTBD.cjs → chunk-XDMXXC4Z.cjs} +230 -125
- package/dist/chunk-XDMXXC4Z.cjs.map +1 -0
- package/dist/commands/index.cjs +860 -0
- package/dist/commands/index.cjs.map +1 -0
- package/dist/commands/index.d.cts +592 -0
- package/dist/commands/index.d.ts +592 -0
- package/dist/commands/index.js +860 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/{controller-B7GIybc4.d.cts → controller-pePCJLT1.d.cts} +78 -5
- package/dist/{controller-B7GIybc4.d.ts → controller-pePCJLT1.d.ts} +78 -5
- package/dist/history/index.cjs +12 -2
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +211 -7
- package/dist/history/index.d.ts +211 -7
- package/dist/history/index.js +13 -3
- package/dist/image-editor/index.cjs +418 -13
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +154 -3
- package/dist/image-editor/index.d.ts +154 -3
- package/dist/image-editor/index.js +417 -12
- package/dist/image-editor/index.js.map +1 -1
- package/dist/index.cjs +34 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -42
- package/dist/index.d.ts +5 -42
- package/dist/index.js +27 -69
- package/dist/index.js.map +1 -1
- package/dist/registry-D5-R90p0.d.cts +209 -0
- package/dist/registry-D5-R90p0.d.ts +209 -0
- package/dist/spreadsheet/index.cjs +19 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +67 -9
- package/dist/spreadsheet/index.d.ts +67 -9
- package/dist/spreadsheet/index.js +18 -2
- package/dist/{types-CJ4U-HHI.d.cts → types-DX4Mh4SL.d.cts} +1 -1
- package/dist/{types-CJ4U-HHI.d.ts → types-DX4Mh4SL.d.ts} +1 -1
- package/dist/types-GT5a5YAX.d.cts +86 -0
- package/dist/types-GT5a5YAX.d.ts +86 -0
- package/package.json +8 -2
- package/dist/chunk-2TWGLC5K.cjs +0 -262
- package/dist/chunk-2TWGLC5K.cjs.map +0 -1
- package/dist/chunk-6M3BCTBD.cjs.map +0 -1
- package/dist/chunk-DENSCKFC.js.map +0 -1
- package/dist/chunk-KAADNAPF.js +0 -262
- package/dist/chunk-KAADNAPF.js.map +0 -1
- package/dist/chunk-RU52I7QO.js.map +0 -1
- package/dist/chunk-SAZRENEZ.cjs.map +0 -1
- package/dist/types-BZgcDsC5.d.cts +0 -42
- package/dist/types-BZgcDsC5.d.ts +0 -42
|
@@ -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/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"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/image-editor/index.cjs","../../src/image-editor/color.ts","../../src/image-editor/filters.ts","../../src/image-editor/geometry.ts","../../src/image-editor/renderer.ts","../../src/image-editor/controller.ts"],"names":["lu","lv","clamp"],"mappings":"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K,IAAA;AACL,MAAA;AACK,QAAA;AACD,UAAA;AACC,UAAA;AACJ,QAAA;AACE,MAAA;AAED,QAAA;AACF,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACK,YAAA;AACL,UAAA;AACA,UAAA;AACF,QAAA;AACE,UAAA;AACF,QAAA;AACF,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAIa,EAAA;AACJ,IAAA;AACT,EAAA;AAEa,EAAA;AACL,IAAA;AACC,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOa,EAAA;AACH,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACG,IAAA;AACT,EAAA;AAEa,EAAA;AACL,IAAA;AACC,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAMa,EAAA;AACA,IAAA;AACb,EAAA;AAAA;AAAA;AAAA;AAAA;AAMa,EAAA;AACA,IAAA;AACb,EAAA;AAEc,EAAA;AACN,IAAA;AACD,IAAA;AAEC,IAAA;AACK,IAAA;AACA,IAAA;AAEN,IAAA;AACM,IAAA;AACA,IAAA;AAEA,IAAA;AACA,MAAA;AACF,QAAA;AACH,UAAA;AACK,YAAA;AACA,YAAA;AACH,YAAA;AACA,YAAA;AACF,UAAA;AACK,UAAA;AACA,UAAA;AACP,QAAA;AACF,MAAA;AACK,MAAA;AACP,IAAA;AAEI,IAAA;AACG,MAAA;AACA,MAAA;AACP,IAAA;AACK,IAAA;AACP,EAAA;AAAA;AAI4C,EAAA;AACnC,IAAA;AACT,EAAA;AAEgB,EAAA;AACT,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AAEQ,EAAA;AACC,IAAA;AACI,MAAA;AACH,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACK,QAAA;AACH,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEQ,EAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,MAAA;AACR,MAAA;AACF,IAAA;AACW,IAAA;AACb,EAAA;AACF;AAMS;AACuB,EAAA;AAC1B,EAAA;AACS,IAAA;AACF,IAAA;AACX,EAAA;AACO,EAAA;AACT;AAGS;AACG,EAAA;AACG,EAAA;AACA,EAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACA,IAAA;AACM,MAAA;AACN,IAAA;AACM,MAAA;AACb,EAAA;AACF;ALquCe;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/image-editor/index.cjs","sourcesContent":[null,"// Portions ported from lightcraft-main/crates/color/src/cct.rs, lightcraft-main/crates/color/src/lib.rs,\n// lightcraft-main/crates/pipeline/src/tone.rs, lightcraft-main/crates/pipeline/src/local.rs and\n// lightcraft-main/crates/develop/src/settings.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors).\n// See THIRD_PARTY_NOTICES.md.\n\n/**\n * Colour science for the image editor's process 2 rendering: sRGB transfer functions,\n * correlated colour temperature / tint (Kim et al. Planckian locus, tint as Duv), Bradford\n * white-balance adaptation relative to \"as shot\", and the display-referred tone curve.\n */\n\n/** A CIE 1931 chromaticity. */\nexport interface Chromaticity {\n x: number;\n y: number;\n}\n\n/** A row-major 3×3 matrix (`[m00, m01, m02, m10, …, m22]`). */\nexport type Matrix3 = [number, number, number, number, number, number, number, number, number];\n\n// ---------------------------------------------------------------------------\n// Process versions\n// ---------------------------------------------------------------------------\n\n/** The rendering of adjustments from before process versions existed. Frozen. */\nexport const LEGACY_PROCESS_VERSION = 1 as const;\n/** The process new edits and Reset get. */\nexport const LATEST_PROCESS_VERSION = 2 as const;\nexport type ImageProcessVersion = 1 | 2;\n\n/**\n * Reads a stored process number leniently: a missing or damaged value (not a whole number) is\n * the legacy process, so a saved edit from before process versions renders exactly as it did. A\n * whole number newer than this build knows renders with the latest process.\n */\nexport function normalizeProcessVersion(value: unknown): ImageProcessVersion {\n if (typeof value !== 'number' || !Number.isInteger(value)) return LEGACY_PROCESS_VERSION;\n return value >= LATEST_PROCESS_VERSION ? LATEST_PROCESS_VERSION : LEGACY_PROCESS_VERSION;\n}\n\n// ---------------------------------------------------------------------------\n// sRGB transfer\n// ---------------------------------------------------------------------------\n\nexport function srgbToLinear(v: number): number {\n return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);\n}\n\nexport function linearToSrgb(v: number): number {\n return v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055;\n}\n\n/** Rec.709 / sRGB luminance weights (linear light). */\nexport const LUMA_709: readonly [number, number, number] = [0.2126, 0.7152, 0.0722];\n\n// ---------------------------------------------------------------------------\n// 3×3 matrices\n// ---------------------------------------------------------------------------\n\nexport const IDENTITY_MATRIX3: Matrix3 = [1, 0, 0, 0, 1, 0, 0, 0, 1];\n\nexport function mulMatrix3(a: Matrix3, b: Matrix3): Matrix3 {\n const out = new Array(9).fill(0) as Matrix3;\n for (let r = 0; r < 3; r++) {\n for (let c = 0; c < 3; c++) {\n out[r * 3 + c] = a[r * 3] * b[c] + a[r * 3 + 1] * b[3 + c] + a[r * 3 + 2] * b[6 + c];\n }\n }\n return out;\n}\n\nexport function applyMatrix3(m: Matrix3, v: readonly [number, number, number]): [number, number, number] {\n return [\n m[0] * v[0] + m[1] * v[1] + m[2] * v[2],\n m[3] * v[0] + m[4] * v[1] + m[5] * v[2],\n m[6] * v[0] + m[7] * v[1] + m[8] * v[2],\n ];\n}\n\nexport function invertMatrix3(m: Matrix3): Matrix3 | null {\n const [a, b, c, d, e, f, g, h, i] = m;\n const A = e * i - f * h;\n const B = -(d * i - f * g);\n const C = d * h - e * g;\n const det = a * A + b * B + c * C;\n if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return null;\n const k = 1 / det;\n return [\n A * k, -(b * i - c * h) * k, (b * f - c * e) * k,\n B * k, (a * i - c * g) * k, -(a * f - c * d) * k,\n C * k, -(a * h - b * g) * k, (a * e - b * d) * k,\n ];\n}\n\nfunction diag3(x: number, y: number, z: number): Matrix3 {\n return [x, 0, 0, 0, y, 0, 0, 0, z];\n}\n\n// ---------------------------------------------------------------------------\n// Chromaticity, sRGB primaries, Bradford adaptation\n// ---------------------------------------------------------------------------\n\nexport const D65: Chromaticity = { x: 0.3127, y: 0.329 };\n\nfunction xyToXyz(p: Chromaticity): [number, number, number] {\n return [p.x / p.y, 1, (1 - p.x - p.y) / p.y];\n}\n\nfunction xyzToXy(v: readonly [number, number, number]): Chromaticity {\n const s = v[0] + v[1] + v[2];\n return s <= 0 ? { ...D65 } : { x: v[0] / s, y: v[1] / s };\n}\n\n/** Linear sRGB → XYZ (D65 white, Y = 1), derived from the primaries. */\nconst SRGB_TO_XYZ: Matrix3 = (() => {\n const p = [{ x: 0.64, y: 0.33 }, { x: 0.3, y: 0.6 }, { x: 0.15, y: 0.06 }].map(xyToXyz);\n const m: Matrix3 = [p[0][0], p[1][0], p[2][0], p[0][1], p[1][1], p[2][1], p[0][2], p[1][2], p[2][2]];\n const inv = invertMatrix3(m);\n const s = inv ? applyMatrix3(inv, xyToXyz(D65)) : [1, 1, 1];\n return mulMatrix3(m, diag3(s[0], s[1], s[2]));\n})();\nconst XYZ_TO_SRGB: Matrix3 = invertMatrix3(SRGB_TO_XYZ) ?? IDENTITY_MATRIX3;\n\n/** The Bradford cone-response matrix (XYZ → sharpened cones). */\nconst BRADFORD: Matrix3 = [0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296];\nconst BRADFORD_INV: Matrix3 = invertMatrix3(BRADFORD) ?? IDENTITY_MATRIX3;\n\n/** Bradford chromatic adaptation (XYZ → XYZ) from white `src` to white `dst`. */\nexport function bradfordAdaptation(src: Chromaticity, dst: Chromaticity): Matrix3 {\n if (src.x === dst.x && src.y === dst.y) return [...IDENTITY_MATRIX3];\n const s = applyMatrix3(BRADFORD, xyToXyz(src));\n const d = applyMatrix3(BRADFORD, xyToXyz(dst));\n return mulMatrix3(BRADFORD_INV, mulMatrix3(diag3(d[0] / s[0], d[1] / s[1], d[2] / s[2]), BRADFORD));\n}\n\n/**\n * Matrix (linear sRGB → linear sRGB) that white-balances a scene lit by `src` so that it\n * renders as if lit by D65.\n */\nexport function wbMatrix(src: Chromaticity): Matrix3 {\n return mulMatrix3(XYZ_TO_SRGB, mulMatrix3(bradfordAdaptation(src, D65), SRGB_TO_XYZ));\n}\n\n/** The chromaticity of a linear sRGB colour. */\nexport function linearSrgbToXy(rgb: readonly [number, number, number]): Chromaticity {\n return xyzToXy(applyMatrix3(SRGB_TO_XYZ, rgb));\n}\n\n// ---------------------------------------------------------------------------\n// Correlated colour temperature and tint\n// ---------------------------------------------------------------------------\n\n/** Tint units per unit of Duv. */\nexport const TINT_SCALE = 3000;\nexport const MIN_KELVIN = 1667;\nexport const MAX_KELVIN = 25000;\n\nfunction kimXy(tIn: number): Chromaticity {\n const t = Math.min(MAX_KELVIN, Math.max(MIN_KELVIN, tIn));\n const t2 = t * t;\n const t3 = t2 * t;\n const x =\n t <= 4000\n ? -0.2661239e9 / t3 - 0.2343589e6 / t2 + 0.8776956e3 / t + 0.17991\n : -3.0258469e9 / t3 + 2.1070379e6 / t2 + 0.2226347e3 / t + 0.24039;\n const x2 = x * x;\n const x3 = x2 * x;\n const y =\n t <= 2222\n ? -1.1063814 * x3 - 1.3481102 * x2 + 2.18555832 * x - 0.20219683\n : t <= 4000\n ? -0.9549476 * x3 - 1.37418593 * x2 + 2.09137015 * x - 0.16748867\n : 3.081758 * x3 - 5.8733867 * x2 + 3.75112997 * x - 0.37001483;\n return { x, y };\n}\n\n/** CIE 1931 xy → CIE 1960 uv. */\nexport function xyToUv(p: Chromaticity): [number, number] {\n const d = -2 * p.x + 12 * p.y + 3;\n return [(4 * p.x) / d, (6 * p.y) / d];\n}\n\n/** CIE 1960 uv → CIE 1931 xy. */\nexport function uvToXy(u: number, v: number): Chromaticity {\n const d = 2 * u - 8 * v + 4;\n return { x: (3 * u) / d, y: (2 * v) / d };\n}\n\n/** Planckian locus in (u, v), extended linearly in mired space outside the approximation's range. */\nfunction locusUv(tIn: number): [number, number] {\n const t = Math.max(1000, tIn);\n if (t >= MIN_KELVIN && t <= MAX_KELVIN) return xyToUv(kimXy(t));\n const [a, b] = t < MIN_KELVIN ? [MIN_KELVIN, MIN_KELVIN + 50] : [MAX_KELVIN - 500, MAX_KELVIN];\n const [ua, va] = xyToUv(kimXy(a));\n const [ub, vb] = xyToUv(kimXy(b));\n const ma = 1e6 / a;\n const mb = 1e6 / b;\n const s = (1e6 / t - ma) / (mb - ma);\n return [ua + (ub - ua) * s, va + (vb - va) * s];\n}\n\n/** Unit normal of the locus at `t`, pointing towards green (increasing v). */\nfunction locusNormal(t: number): [number, number] {\n const m = 1e6 / t;\n const [u0, v0] = locusUv(1e6 / (m + 1));\n const [u1, v1] = locusUv(1e6 / Math.max(m - 1, 1));\n const du = u1 - u0;\n const dv = v1 - v0;\n const l = Math.max(Math.hypot(du, dv), 1e-12);\n const nx = -dv / l;\n const ny = du / l;\n return ny < 0 ? [-nx, -ny] : [nx, ny];\n}\n\n/**\n * Source illuminant chromaticity for temperature `kelvin` and correction `tint`.\n * Positive tint means a green source white, whose correction adds magenta to the image.\n */\nexport function tempTintToXy(kelvin: number, tint: number): Chromaticity {\n const [u, v] = locusUv(kelvin);\n const [nu, nv] = locusNormal(kelvin);\n const duv = tint / TINT_SCALE;\n return uvToXy(u + nu * duv, v + nv * duv);\n}\n\n/** Inverse of {@link tempTintToXy}: nearest locus temperature (searched in mired space) and tint. */\nexport function xyToTempTint(p: Chromaticity): { kelvin: number; tint: number } {\n const [u, v] = xyToUv(p);\n const dist = (m: number) => {\n const [lu, lv] = locusUv(1e6 / m);\n return (u - lu) ** 2 + (v - lv) ** 2;\n };\n // Coarse scan, then golden-section refine over mired 20..1000 (50000 K .. 1000 K).\n let best = 20;\n let bd = Infinity;\n for (let m = 20; m <= 1000; m += 5) {\n const d = dist(m);\n if (d < bd) {\n bd = d;\n best = m;\n }\n }\n let a = Math.max(best - 5, 20);\n let b = Math.min(best + 5, 1000);\n const g = 0.61803398875;\n for (let i = 0; i < 60; i++) {\n const c = b - g * (b - a);\n const d = a + g * (b - a);\n if (dist(c) < dist(d)) b = d;\n else a = c;\n }\n const kelvin = 1e6 / ((a + b) / 2);\n const [lu, lv] = locusUv(kelvin);\n const [nu, nv] = locusNormal(kelvin);\n const duv = (u - lu) * nu + (v - lv) * nv;\n return { kelvin, tint: duv * TINT_SCALE };\n}\n\n// ---------------------------------------------------------------------------\n// Relative white balance for rendered (JPEG / PNG / WebP) sources\n// ---------------------------------------------------------------------------\n\n/** Rendered images carry no as-shot white: \"as shot\" is 6500 K / tint 0 and maps to the identity. */\nexport const AS_SHOT_KELVIN = 6500;\n/** Mireds per unit of the `temperature` slider (−100…100). */\nexport const MIRED_PER_TEMPERATURE_UNIT = 1;\nconst AS_SHOT_MIRED = 1e6 / AS_SHOT_KELVIN;\n\n/**\n * Maps the `temperature` slider (−100 cool … +100 warm) to the Kelvin white-balance setting:\n * one mired per unit around 6500 K, so 0 → 6500 K, +100 → ~18 570 K (a strong warming\n * correction), −100 → ~3 940 K (a strong cooling correction). Mireds are perceptually even,\n * so equal slider steps look like equal changes.\n */\nexport function temperatureToKelvin(temperature: number): number {\n return 1e6 / (AS_SHOT_MIRED - temperature * MIRED_PER_TEMPERATURE_UNIT);\n}\n\n/** Inverse of {@link temperatureToKelvin}. */\nexport function kelvinToTemperature(kelvin: number): number {\n return (AS_SHOT_MIRED - 1e6 / kelvin) / MIRED_PER_TEMPERATURE_UNIT;\n}\n\nconst AS_SHOT_WB_INV: Matrix3 = invertMatrix3(wbMatrix(tempTintToXy(AS_SHOT_KELVIN, 0))) ?? IDENTITY_MATRIX3;\n\n/**\n * White-balance matrix (linear sRGB) for the `temperature` / `tint` sliders, relative to\n * \"as shot\": the identity at 0 / 0. Normalised so neutral luminance is preserved (white\n * balance doesn't change exposure).\n */\nexport function whiteBalanceMatrix(temperature: number, tint = 0): Matrix3 {\n if (!temperature && !tint) return [...IDENTITY_MATRIX3];\n const set = wbMatrix(tempTintToXy(temperatureToKelvin(temperature), tint));\n const m = mulMatrix3(set, AS_SHOT_WB_INV);\n const g = applyMatrix3(m, [1, 1, 1]);\n const y = g[0] * LUMA_709[0] + g[1] * LUMA_709[1] + g[2] * LUMA_709[2];\n return mulMatrix3(m, diag3(1 / y, 1 / y, 1 / y));\n}\n\n/**\n * Neutral-point eyedropper: the `temperature` / `tint` that render the sampled sRGB colour\n * (0–255 per channel, as it appears in the unadjusted image) neutral. Results are clamped to\n * the slider range −100…100.\n */\nexport function whiteBalanceFromSample(\n rgb: readonly [number, number, number] | { r: number; g: number; b: number }\n): { temperature: number; tint: number } {\n const [r, g, b] = Array.isArray(rgb) ? rgb : [(rgb as any).r, (rgb as any).g, (rgb as any).b];\n const lin: [number, number, number] = [r, g, b].map((c) => srgbToLinear(Math.min(255, Math.max(0, c)) / 255)) as [\n number,\n number,\n number,\n ];\n if (lin[0] + lin[1] + lin[2] <= 1e-6) return { temperature: 0, tint: 0 };\n // Under relative WB the matrix is wb(set) · wb(asShot)⁻¹, so the sample is neutral when the\n // chosen source white is the chromaticity of wb(asShot)⁻¹ · sample.\n const src = linearSrgbToXy(applyMatrix3(AS_SHOT_WB_INV, lin));\n const { kelvin, tint } = xyToTempTint(src);\n const clamp = (v: number) => Math.max(-100, Math.min(100, v));\n return { temperature: clamp(kelvinToTemperature(kelvin)), tint: clamp(tint) };\n}\n\n// ---------------------------------------------------------------------------\n// Tone curve (display-referred, identity at neutral)\n// ---------------------------------------------------------------------------\n\n/** Number of intervals in a tone LUT (the table has `TONE_LUT_SIZE + 1` entries). */\nexport const TONE_LUT_SIZE = 4096;\nconst GREY = 0.18;\nconst GREY_P = Math.pow(GREY, 1 / 2.2);\n/** Linear value where the highlight shoulder starts when there is headroom above 1. */\nconst SHOULDER_KNEE = 0.6;\n\n/**\n * The process 2 tone curve for one linear input value in `[0, headroom]`:\n *\n * 1. A highlight shoulder (extended Reinhard with white point `headroom`) above a knee of 0.6\n * rolls values above 1 (after exposure / white balance) back into range instead of clipping.\n * With no headroom (`headroom = 1`) it is exactly the identity.\n * 2. In a gamma-2.2 perceptual domain, contrast is an S-curve anchored at 0, middle grey and 1\n * (as `ToneMap::display`), and brightness a midtone lift anchored at 0 and 1.\n *\n * `contrast` and `brightness` are −100…100; both 0 with `headroom = 1` is the identity.\n */\nexport function toneCurve(y: number, contrast: number, brightness: number, headroom = 1): number {\n if (!(y > 0)) return 0;\n const h = Math.max(1, headroom);\n let o = Math.min(y, h);\n if (h > 1 && o > SHOULDER_KNEE) {\n const span = 1 - SHOULDER_KNEE;\n const x = (o - SHOULDER_KNEE) / span;\n const X = (h - SHOULDER_KNEE) / span;\n o = SHOULDER_KNEE + span * ((x * (1 + x / (X * X))) / (1 + x));\n }\n if (contrast === 0 && brightness === 0) return Math.min(1, o);\n let p = Math.pow(Math.min(1, o), 1 / 2.2);\n const c = contrast / 100;\n if (c !== 0) {\n p += c * 0.4 * (p - GREY_P) * (1 - (2 * p - 1) ** 2);\n }\n const b = brightness / 100;\n if (b !== 0) {\n // Smooth midtone lift that leaves black and white in place (slope stays positive).\n p += b * 0.8 * p * (1 - p);\n }\n return Math.pow(Math.min(1, Math.max(0, p)), 2.2);\n}\n\n/** Tabulates {@link toneCurve} over `[0, headroom]` in {@link TONE_LUT_SIZE} steps. */\nexport function buildToneLut(contrast: number, brightness: number, headroom = 1): Float32Array {\n const h = Math.max(1, headroom);\n const lut = new Float32Array(TONE_LUT_SIZE + 1);\n for (let i = 0; i <= TONE_LUT_SIZE; i++) {\n lut[i] = toneCurve((h * i) / TONE_LUT_SIZE, contrast, brightness, h);\n }\n return lut;\n}\n\n/** Linearly interpolated lookup into a LUT from {@link buildToneLut}. */\nexport function sampleToneLut(lut: Float32Array, headroom: number, y: number): number {\n if (!(y > 0)) return lut[0];\n const n = lut.length - 1;\n const f = (y * n) / Math.max(1, headroom);\n if (f >= n) return lut[n];\n const i = f | 0;\n return lut[i] + (lut[i + 1] - lut[i]) * (f - i);\n}\n","import type { DepthMaskOptions, ImageAdjustments } from './types';\nimport {\n LUMA_709,\n LATEST_PROCESS_VERSION,\n TONE_LUT_SIZE,\n buildToneLut,\n linearToSrgb,\n normalizeProcessVersion,\n srgbToLinear,\n whiteBalanceMatrix,\n} from './color';\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, tint,\n * opacity, blur) directly to the pixel data of an ImageData buffer.\n *\n * `adjustments.process` selects the rendering: `2` is the linear-light pipeline\n * ({@link applyProcess2Adjustments}); anything else (including a missing value, as in edits saved\n * before process versions existed) is the frozen legacy rendering ({@link applyLegacyAdjustments}).\n */\nexport function applyAdjustments(imageData: ImageData, adjustments: ImageAdjustments): void {\n if (normalizeProcessVersion(adjustments.process) === LATEST_PROCESS_VERSION) {\n applyProcess2Adjustments(imageData, adjustments);\n } else {\n applyLegacyAdjustments(imageData, adjustments);\n }\n\n // Apply blur if requested\n if (adjustments.blur > 0) {\n applyFastBoxBlur(imageData, Math.min(Math.round(adjustments.blur), 40));\n }\n}\n\n/**\n * Process 1: the original 8-bit gamma-space rendering, clamping after every step. Frozen so\n * that edits saved without a `process` look exactly as they always have. `tint` is ignored.\n */\nexport function applyLegacyAdjustments(imageData: ImageData, adjustments: ImageAdjustments): void {\n const { brightness, contrast, saturation, exposure, temperature, opacity } = 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\n/** sRGB byte → linear light (exact; shared by every process 2 render). */\nconst DECODE_LUT: Float32Array = (() => {\n const lut = new Float32Array(256);\n for (let i = 0; i < 256; i++) lut[i] = srgbToLinear(i / 255);\n return lut;\n})();\n\n/** Intervals of the linear → sRGB byte encode table over [0, 1]. */\nconst ENCODE_LUT_SIZE = 65535;\nlet encodeLut: Uint8Array | null = null;\nfunction getEncodeLut(): Uint8Array {\n if (!encodeLut) {\n encodeLut = new Uint8Array(ENCODE_LUT_SIZE + 1);\n for (let i = 0; i <= ENCODE_LUT_SIZE; i++) {\n encodeLut[i] = Math.round(linearToSrgb(i / ENCODE_LUT_SIZE) * 255);\n }\n }\n return encodeLut;\n}\n\ninterface Process2Plan {\n key: string;\n /** Exposure gain folded into the white-balance matrix (row-major). */\n m: Float64Array;\n /** Per-channel tone LUTs over [0, headroom[c]]. */\n luts: [Float32Array, Float32Array, Float32Array];\n /** LUT index scale per channel: TONE_LUT_SIZE / headroom[c]. */\n scale: [number, number, number];\n saturation: number;\n}\n\nlet cachedPlan: Process2Plan | null = null;\n\n/**\n * Precomputes everything per parameter change: the 3×3 colour matrix (exposure gain × white\n * balance) and one tone LUT per channel whose domain is that channel's largest possible value,\n * so the highlight shoulder absorbs it instead of clipping.\n */\nfunction getProcess2Plan(adjustments: ImageAdjustments): Process2Plan {\n const exposure = adjustments.exposure || 0;\n const temperature = adjustments.temperature || 0;\n const tint = adjustments.tint || 0;\n const contrast = adjustments.contrast || 0;\n const brightness = adjustments.brightness || 0;\n const saturation = 1 + (adjustments.saturation || 0) / 100;\n const key = `${exposure},${temperature},${tint},${contrast},${brightness},${saturation}`;\n if (cachedPlan && cachedPlan.key === key) return cachedPlan;\n\n const gain = Math.pow(2, exposure / 50);\n const wb = whiteBalanceMatrix(temperature, tint);\n const m = new Float64Array(9);\n for (let i = 0; i < 9; i++) m[i] = wb[i] * gain;\n const luts = [] as unknown as [Float32Array, Float32Array, Float32Array];\n const scale = [0, 0, 0] as [number, number, number];\n for (let c = 0; c < 3; c++) {\n // Largest value this channel can reach from inputs in [0, 1].\n let reach = 0;\n for (let k = 0; k < 3; k++) reach += Math.max(0, m[c * 3 + k]);\n const headroom = Math.max(1, reach);\n luts[c] = buildToneLut(contrast, brightness, headroom);\n scale[c] = TONE_LUT_SIZE / headroom;\n }\n cachedPlan = { key, m, luts, scale, saturation };\n return cachedPlan;\n}\n\n/**\n * Process 2: decode sRGB to linear float once; apply exposure (gain) and white balance (a\n * Bradford matrix relative to as-shot) in linear light; a per-channel tone LUT for the\n * highlight shoulder, contrast and brightness; luminance-preserving saturation in linear light;\n * then encode back to sRGB through a LUT. Nothing is clamped between stages, only at the final\n * encode. Neutral settings are the identity.\n */\nexport function applyProcess2Adjustments(imageData: ImageData, adjustments: ImageAdjustments): void {\n const data = imageData.data;\n const len = data.length;\n const opacity = adjustments.opacity ?? 1;\n const hasOpacity = opacity < 1;\n const hasColor =\n !!adjustments.brightness ||\n !!adjustments.contrast ||\n !!adjustments.saturation ||\n !!adjustments.exposure ||\n !!adjustments.temperature ||\n !!adjustments.tint;\n\n if (!hasColor) {\n if (hasOpacity) {\n for (let i = 3; i < len; i += 4) data[i] = clamp(data[i] * opacity);\n }\n return;\n }\n\n const plan = getProcess2Plan(adjustments);\n const dec = DECODE_LUT;\n const enc = getEncodeLut();\n const { m, luts, scale, saturation } = plan;\n const m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3], m4 = m[4], m5 = m[5], m6 = m[6], m7 = m[7], m8 = m[8];\n const lutR = luts[0], lutG = luts[1], lutB = luts[2];\n const sR = scale[0], sG = scale[1], sB = scale[2];\n const N = TONE_LUT_SIZE;\n const hasSaturation = saturation !== 1;\n const wr = LUMA_709[0], wg = LUMA_709[1], wb = LUMA_709[2];\n\n for (let i = 0; i < len; i += 4) {\n const r0 = dec[data[i]];\n const g0 = dec[data[i + 1]];\n const b0 = dec[data[i + 2]];\n\n // Exposure × white balance (linear light, unclamped).\n let r = m0 * r0 + m1 * g0 + m2 * b0;\n let g = m3 * r0 + m4 * g0 + m5 * b0;\n let b = m6 * r0 + m7 * g0 + m8 * b0;\n\n // Tone LUTs (linear interpolation).\n let f: number;\n let k: number;\n if (r <= 0) r = lutR[0];\n else if ((f = r * sR) >= N) r = lutR[N];\n else { k = f | 0; r = lutR[k] + (lutR[k + 1] - lutR[k]) * (f - k); }\n if (g <= 0) g = lutG[0];\n else if ((f = g * sG) >= N) g = lutG[N];\n else { k = f | 0; g = lutG[k] + (lutG[k + 1] - lutG[k]) * (f - k); }\n if (b <= 0) b = lutB[0];\n else if ((f = b * sB) >= N) b = lutB[N];\n else { k = f | 0; b = lutB[k] + (lutB[k + 1] - lutB[k]) * (f - k); }\n\n // Luminance-preserving saturation in linear light.\n if (hasSaturation) {\n const y = wr * r + wg * g + wb * b;\n r = y + (r - y) * saturation;\n g = y + (g - y) * saturation;\n b = y + (b - y) * saturation;\n }\n\n // Encode (the only clamp).\n data[i] = enc[r <= 0 ? 0 : r >= 1 ? ENCODE_LUT_SIZE : (r * ENCODE_LUT_SIZE + 0.5) | 0];\n data[i + 1] = enc[g <= 0 ? 0 : g >= 1 ? ENCODE_LUT_SIZE : (g * ENCODE_LUT_SIZE + 0.5) | 0];\n data[i + 2] = enc[b <= 0 ? 0 : b >= 1 ? ENCODE_LUT_SIZE : (b * ENCODE_LUT_SIZE + 0.5) | 0];\n if (hasOpacity) data[i + 3] = clamp(data[i + 3] * opacity);\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 { normalizeProcessVersion } from './color';\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.tint ?? 0},${adjustments.blur},${adjustments.opacity},p${normalizeProcessVersion(adjustments.process)}`,\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';\nimport { LATEST_PROCESS_VERSION, LEGACY_PROCESS_VERSION, normalizeProcessVersion } from './color';\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 tint: 0,\n blur: 0,\n opacity: 1,\n process: LATEST_PROCESS_VERSION,\n};\n\n/**\n * Fills in defaults for stored adjustments. A stored object without `process` was saved before\n * process versions existed and keeps the legacy rendering.\n */\nexport function normalizeStoredAdjustments(stored: Partial<ImageAdjustments> | undefined | null): ImageAdjustments {\n if (!stored) return { ...DEFAULT_ADJUSTMENTS };\n return {\n ...DEFAULT_ADJUSTMENTS,\n ...stored,\n process: normalizeProcessVersion(stored.process),\n };\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 = normalizeStoredAdjustments(options.initialState?.adjustments);\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 /** True when the adjustments use an older rendering process than {@link LATEST_PROCESS_VERSION}. */\n public get isProcessOutdated(): boolean {\n return normalizeProcessVersion(this.state.adjustments.process) < LATEST_PROCESS_VERSION;\n }\n\n /**\n * Update to Current Process: re-interprets the current adjustment values with the latest\n * rendering process (one undo step). Returns false when already current.\n */\n public upgradeProcess(): boolean {\n if (!this.isProcessOutdated) return false;\n this.setAdjustments({ process: LATEST_PROCESS_VERSION });\n return true;\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: serializeAdjustments(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 = normalizeStoredAdjustments(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/**\n * Legacy-process adjustments are saved without `process` (and without an unused zero `tint`), so\n * they serialize exactly as they did before process versions existed.\n */\nfunction serializeAdjustments(adjustments: ImageAdjustments): ImageAdjustments {\n const out: ImageAdjustments = { ...adjustments };\n if (normalizeProcessVersion(out.process) === LEGACY_PROCESS_VERSION) {\n delete out.process;\n if (!out.tint) delete out.tint;\n }\n return out;\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"]}
|