export interface DeSepAIOptions { strength?: number notchWhiten?: boolean shiftBase?: [number, number, number] preserveDark?: boolean } export interface ImageData { data: Uint8ClampedArray width: number height: number } export function detectShiftBase(imageData: ImageData): [number, number, number] { const { data } = imageData const lum: number[] = [] for (let i = 0; i < data.length; i += 4) lum.push(0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2]) const sorted = lum.slice().sort((a, b) => a - b) const threshold = sorted[Math.floor(sorted.length * 0.9)] let sum = [0, 0, 0] let count = 0 for (let i = 0; i < lum.length; i++) { if (lum[i] >= threshold) { const idx = i * 4 sum[0] += data[idx] sum[1] += data[idx + 1] sum[2] += data[idx + 2] count++ } } const avg: [number, number, number] = count ? [sum[0] / count, sum[1] / count, sum[2] / count] : [255, 255, 255] return [255 - avg[0], 255 - avg[1], 255 - avg[2]] } export function processPixelData( imageData: ImageData, options: DeSepAIOptions = {} ): ImageData { const { strength = 100, notchWhiten = false, shiftBase = [9, 15, 27], preserveDark = strength > 50 } = options // Create new pixel data array (don't modify original) const d = new Uint8ClampedArray(imageData.data) const scale = strength / 100 if (notchWhiten) { const refR = 255 - shiftBase[0] const refG = 255 - shiftBase[1] const refB = 255 - shiftBase[2] const notch = 8 for (let i = 0; i < d.length; i += 4) { const r = d[i], g = d[i + 1], b = d[i + 2] const bri = 0.2126 * r + 0.7152 * g + 0.0722 * b const att = preserveDark ? Math.min(1, bri / 128) : 1 if ( r >= refR - notch && r <= refR + notch && g >= refG - notch && g <= refG + notch && b >= refB - notch && b <= refB + notch ) { d[i] = Math.min(255, r + (255 - r) * scale * att) d[i + 1] = Math.min(255, g + (255 - g) * scale * att) d[i + 2] = Math.min(255, b + (255 - b) * scale * att) } else { d[i] = Math.min(255, r + shiftBase[0] * scale * att) d[i + 1] = Math.min(255, g + shiftBase[1] * scale * att) d[i + 2] = Math.min(255, b + shiftBase[2] * scale * att) } } } else { for (let i = 0; i < d.length; i += 4) { const r = d[i], g = d[i + 1], b = d[i + 2] const bri = 0.2126 * r + 0.7152 * g + 0.0722 * b const att = preserveDark ? Math.min(1, bri / 128) : 1 d[i] = Math.min(255, r + shiftBase[0] * scale * att) d[i + 1] = Math.min(255, g + shiftBase[1] * scale * att) d[i + 2] = Math.min(255, b + shiftBase[2] * scale * att) } } return { data: d, width: imageData.width, height: imageData.height } }