import type { ImageDocument } from "@core/document"; import type { GenerationCandidate } from "@editor/state"; import { loadImageCanvas, maskValueFromRgba } from "../mask/maskRaster"; export async function createMaskedPixelReplacementSource(document: ImageDocument, candidate: GenerationCandidate): Promise { if (!candidate.inpaint) throw new Error("Only inpaint candidates can replace masked pixels."); const targetAsset = document.assets.find((asset) => asset.id === candidate.inpaint?.sourceAssetId); if (!targetAsset) throw new Error("The source layer for this candidate no longer exists."); const targetCanvas = await loadImageCanvas(targetAsset.source, targetAsset.intrinsicSize.w, targetAsset.intrinsicSize.h); const generatedCanvas = await loadImageCanvas(candidate.source, candidate.width, candidate.height); const maskCanvas = await loadImageCanvas(candidate.inpaint.maskImage, candidate.width, candidate.height); const targetContext = require2dContext(targetCanvas); const generatedContext = require2dContext(generatedCanvas); const maskContext = require2dContext(maskCanvas); const targetData = targetContext.getImageData(0, 0, targetCanvas.width, targetCanvas.height); const generatedData = generatedContext.getImageData(0, 0, generatedCanvas.width, generatedCanvas.height); const maskData = maskContext.getImageData(0, 0, maskCanvas.width, maskCanvas.height); const crop = candidate.inpaint.crop.assetBounds; for (let y = 0; y < candidate.height; y += 1) { for (let x = 0; x < candidate.width; x += 1) { const targetX = Math.round(crop.x) + x; const targetY = Math.round(crop.y) + y; if (targetX < 0 || targetY < 0 || targetX >= targetCanvas.width || targetY >= targetCanvas.height) continue; const generatedIndex = (y * generatedCanvas.width + x) * 4; const targetIndex = (targetY * targetCanvas.width + targetX) * 4; const mask = maskValueFromRgba(maskData.data, generatedIndex) / 255; if (mask <= 0) continue; for (let channel = 0; channel < 4; channel += 1) { const previous = targetData.data[targetIndex + channel] ?? 0; const next = generatedData.data[generatedIndex + channel] ?? previous; targetData.data[targetIndex + channel] = Math.round(previous * (1 - mask) + next * mask); } } } targetContext.putImageData(targetData, 0, 0); return targetCanvas.toDataURL("image/png"); } function require2dContext(canvas: HTMLCanvasElement): CanvasRenderingContext2D { const context = canvas.getContext("2d"); if (!context) throw new Error("Unable to prepare generated candidate"); return context; }