Initial commit — Bradly branding editor platform
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Chroma Key Utilities
|
||||
*
|
||||
* Core pixel-processing algorithm for removing solid-color backgrounds
|
||||
* from images and video frames. Operates on Canvas 2D ImageData.
|
||||
*/
|
||||
|
||||
export interface ChromaKeyParams {
|
||||
/** RGB tuple of the key color to remove */
|
||||
keyColor: [number, number, number];
|
||||
/** Tolerance in color distance (0-255 range, mapped from 0-100%) */
|
||||
tolerance: number;
|
||||
/** Edge softness in color distance units */
|
||||
softness: number;
|
||||
}
|
||||
|
||||
/** Preset colors for quick selection — optimized for common use cases */
|
||||
export const CHROMA_KEY_PRESETS = [
|
||||
{ color: '#ffffff', label: 'Blanco', icon: '⬜' },
|
||||
{ color: '#000000', label: 'Negro', icon: '⬛' },
|
||||
{ color: '#00ff00', label: 'Verde', icon: '🟩' },
|
||||
{ color: '#0000ff', label: 'Azul', icon: '🟦' },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Parse a hex color string to an RGB tuple.
|
||||
* Supports #RGB, #RRGGBB, and plain RRGGBB formats.
|
||||
*/
|
||||
export function hexToRgb(hex: string): [number, number, number] {
|
||||
let clean = hex.replace('#', '');
|
||||
if (clean.length === 3) {
|
||||
clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
|
||||
}
|
||||
const num = parseInt(clean, 16);
|
||||
return [
|
||||
(num >> 16) & 0xff,
|
||||
(num >> 8) & 0xff,
|
||||
num & 0xff,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert RGB to a hex color string (#RRGGBB).
|
||||
*/
|
||||
export function rgbToHex(r: number, g: number, b: number): string {
|
||||
return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply chroma key to canvas ImageData in-place.
|
||||
*
|
||||
* Uses Euclidean distance in RGB color space to determine how close
|
||||
* each pixel is to the key color. Pixels within tolerance become fully
|
||||
* transparent; pixels in the softness zone get partial transparency for
|
||||
* smooth edges.
|
||||
*
|
||||
* @param imageData - The ImageData to process (modified in-place)
|
||||
* @param params - Chroma key configuration
|
||||
*/
|
||||
export function applyChromaKey(
|
||||
imageData: ImageData,
|
||||
params: ChromaKeyParams
|
||||
): void {
|
||||
const { keyColor, tolerance, softness } = params;
|
||||
const data = imageData.data;
|
||||
const [kr, kg, kb] = keyColor;
|
||||
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
const r = data[i];
|
||||
const g = data[i + 1];
|
||||
const b = data[i + 2];
|
||||
|
||||
// Euclidean distance in RGB space
|
||||
const dr = r - kr;
|
||||
const dg = g - kg;
|
||||
const db = b - kb;
|
||||
const distance = Math.sqrt(dr * dr + dg * dg + db * db);
|
||||
|
||||
if (distance < tolerance) {
|
||||
// Fully transparent — within the key color range
|
||||
data[i + 3] = 0;
|
||||
} else if (softness > 0 && distance < tolerance + softness) {
|
||||
// Partial transparency — smooth edge transition
|
||||
const alpha = Math.round(255 * ((distance - tolerance) / softness));
|
||||
data[i + 3] = Math.min(data[i + 3], alpha);
|
||||
|
||||
// Spill suppression: reduce the key color influence on edge pixels
|
||||
// This prevents a colored "halo" around the subject
|
||||
const spillFactor = 1 - ((tolerance + softness - distance) / softness) * 0.5;
|
||||
data[i] = Math.round(r + (r - kr) * (1 - spillFactor));
|
||||
data[i + 1] = Math.round(g + (g - kg) * (1 - spillFactor));
|
||||
data[i + 2] = Math.round(b + (b - kb) * (1 - spillFactor));
|
||||
|
||||
// Clamp values
|
||||
data[i] = Math.max(0, Math.min(255, data[i]));
|
||||
data[i + 1] = Math.max(0, Math.min(255, data[i + 1]));
|
||||
data[i + 2] = Math.max(0, Math.min(255, data[i + 2]));
|
||||
}
|
||||
// else: pixel is outside the range, keep as-is
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map user-facing percentage values (0-100) to internal distance values.
|
||||
* The max RGB distance is ~441 (sqrt(255² + 255² + 255²)).
|
||||
*/
|
||||
export function mapToleranceToDistance(tolerancePercent: number): number {
|
||||
return (tolerancePercent / 100) * 441;
|
||||
}
|
||||
|
||||
export function mapSoftnessToDistance(softnessPercent: number): number {
|
||||
return (softnessPercent / 100) * 150;
|
||||
}
|
||||
Reference in New Issue
Block a user