- Implemented ComfyGenerateRequest type and associated functions for generating images using various architectures and modes. - Added functions for listing generation options and handling image uploads. - Created workflows for different generation modes including SDXL, Z-Image, Z-Image Turbo, and Anima. - Introduced GenerationJobStatus component to display the status of ongoing generation jobs. - Developed MaskControls for managing mask operations and displaying mask analysis. - Created palette items for tool selection, layer management, and generation settings.
67 lines
3.6 KiB
TypeScript
67 lines
3.6 KiB
TypeScript
import { blurMaskValues, despeckleMaskValues, dilateMaskValues, erodeMaskValues } from "./maskRaster";
|
|
|
|
type ChromaKeySettings = { color: string; tolerance: number; softness: number; feather: number; choke: number; despeckle: number; spill: number };
|
|
|
|
export async function createChromaKeyPreview(source: string, width: number, height: number, settings: ChromaKeySettings) {
|
|
return render(source, width, height, settings, false);
|
|
}
|
|
|
|
export async function createChromaKeyMask(source: string, width: number, height: number, settings: ChromaKeySettings) {
|
|
return render(source, width, height, settings, true);
|
|
}
|
|
|
|
async function render(source: string, width: number, height: number, settings: ChromaKeySettings, maskOnly: boolean) {
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = Math.max(1, Math.round(width));
|
|
canvas.height = Math.max(1, Math.round(height));
|
|
const context = canvas.getContext("2d");
|
|
if (!context) return source;
|
|
context.drawImage(await loadImage(source), 0, 0, canvas.width, canvas.height);
|
|
const data = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
const alpha = chromaKeyAlpha(data, canvas.width, canvas.height, settings);
|
|
for (let pixel = 0; pixel < alpha.length; pixel++) {
|
|
const index = pixel * 4;
|
|
if (maskOnly) data.data[index] = data.data[index + 1] = data.data[index + 2] = 255;
|
|
data.data[index + 3] = alpha[pixel] ?? 255;
|
|
}
|
|
context.putImageData(data, 0, 0);
|
|
return canvas.toDataURL("image/png");
|
|
}
|
|
|
|
function chromaKeyAlpha(data: ImageData, width: number, height: number, settings: ChromaKeySettings) {
|
|
const hex = settings.color.replace("#", "");
|
|
const key = { r: Number.parseInt(hex.slice(0, 2), 16), g: Number.parseInt(hex.slice(2, 4), 16), b: Number.parseInt(hex.slice(4, 6), 16) };
|
|
const alpha = new Uint8ClampedArray(width * height);
|
|
for (let pixel = 0; pixel < alpha.length; pixel++) {
|
|
const index = pixel * 4;
|
|
const red = data.data[index] ?? 0;
|
|
const green = data.data[index + 1] ?? 0;
|
|
const blue = data.data[index + 2] ?? 0;
|
|
const distance = Math.hypot(red - key.r, green - key.g, blue - key.b);
|
|
const tolerance = Math.max(0, Math.min(255, settings.tolerance));
|
|
const softness = Math.max(0, Math.min(255, settings.softness));
|
|
const edgeKeep = distance <= tolerance ? 0 : softness > 0 && distance < tolerance + softness ? (distance - tolerance) / softness : 1;
|
|
const dominant = key.g >= key.r && key.g >= key.b ? green : key.r >= key.b ? red : blue;
|
|
const neutral = key.g >= key.r && key.g >= key.b ? Math.max(red, blue) : key.r >= key.b ? Math.max(green, blue) : Math.max(red, green);
|
|
const spillKeep = 1 - Math.max(0, dominant - neutral) / 255 * Math.max(0, Math.min(100, settings.spill)) / 100;
|
|
alpha[pixel] = Math.round((data.data[index + 3] ?? 255) * Math.max(0, Math.min(edgeKeep, spillKeep)));
|
|
}
|
|
let next: Uint8ClampedArray<ArrayBufferLike> = alpha;
|
|
const despeckle = Math.round(Math.max(0, Math.min(20, settings.despeckle)));
|
|
const choke = Math.round(Math.max(-20, Math.min(20, settings.choke)));
|
|
const feather = Math.round(Math.max(0, Math.min(20, settings.feather)));
|
|
if (despeckle > 0) next = despeckleMaskValues(next, width, height, despeckle);
|
|
if (choke > 0) next = erodeMaskValues(next, width, height, choke);
|
|
if (choke < 0) next = dilateMaskValues(next, width, height, -choke);
|
|
return feather > 0 ? blurMaskValues(next, width, height, feather) : next;
|
|
}
|
|
|
|
function loadImage(source: string) {
|
|
return new Promise<HTMLImageElement>((resolve, reject) => {
|
|
const image = new Image();
|
|
image.onload = () => resolve(image);
|
|
image.onerror = () => reject(new Error("Failed to load image"));
|
|
image.src = source;
|
|
});
|
|
}
|