import cv from './opencvAdapter'; import { getMaskSegmentRuntimeConfig } from './maskSegmentRuntime'; /** OpenCV 8-bit Lab L 通道(BGR 输入,供单测与近似对照) */ export function bgrToLabL(b, g, r) { return bgrToLab(b, g, r).l; } /** BGR → 8-bit Lab(L/a/b 均映射到 0–255) */ export function bgrToLab(b, g, r) { let rf = r / 255; let gf = g / 255; let bf = b / 255; rf = rf > 0.04045 ? Math.pow((rf + 0.055) / 1.055, 2.4) : rf / 12.92; gf = gf > 0.04045 ? Math.pow((gf + 0.055) / 1.055, 2.4) : gf / 12.92; bf = bf > 0.04045 ? Math.pow((bf + 0.055) / 1.055, 2.4) : bf / 12.92; const x = rf * 0.412453 + gf * 0.35758 + bf * 0.180423; const y = rf * 0.212671 + gf * 0.71516 + bf * 0.072169; const z = rf * 0.019334 + gf * 0.119193 + bf * 0.950227; const xn = 0.950456; const yn = 1; const zn = 1.088754; const delta = 6 / 29; const delta3 = delta * delta * delta; let fx = x / xn; let fy = y / yn; let fz = z / zn; fx = fx > delta3 ? Math.cbrt(fx) : fx / (3 * delta * delta) + 4 / 29; fy = fy > delta3 ? Math.cbrt(fy) : fy / (3 * delta * delta) + 4 / 29; fz = fz > delta3 ? Math.cbrt(fz) : fz / (3 * delta * delta) + 4 / 29; const L = fy * 116 - 16; const a = 500 * (fx - fy); const bLab = 200 * (fy - fz); return { l: Math.max(0, Math.min(255, Math.round((L * 255) / 100))), a: Math.max(0, Math.min(255, Math.round(a + 128))), b: Math.max(0, Math.min(255, Math.round(bLab + 128))), }; } export function bgrBufferToRgbaBuffer(bgr, cols, rows) { const pixelCount = cols * rows; const rgba = new Uint8Array(pixelCount * 4); for (let i = 0; i < pixelCount; i++) { const s = i * 3; const d = i * 4; rgba[d] = bgr[s + 2]; rgba[d + 1] = bgr[s + 1]; rgba[d + 2] = bgr[s]; rgba[d + 3] = 255; } return rgba; } export function releaseFreqLayerImages(layers) { layers?.lowFreqImage.dispose(); layers?.highFreqImage.dispose(); } /** 16-bit 有符号差分 → 8-bit 高频层(detail * gain + 128) */ async function buildHighFreqMatNative(lMat, lLowMat, cols, rows, gain) { const l16 = cv.createMat(cols, rows, 1); const lLow16 = cv.createMat(cols, rows, 1); const diff16 = cv.createMat(cols, rows, 1); const high8 = cv.createMat(cols, rows, 1); try { cv.convertTo(lMat, l16, cv.CV_16SC1); cv.convertTo(lLowMat, lLow16, cv.CV_16SC1); await cv.subtract(l16, lLow16, diff16); await cv.addWeighted(diff16, gain, null, 0, 128, diff16); cv.convertTo(diff16, high8, cv.CV_8UC1); return high8; } finally { l16.release(); lLow16.release(); diff16.release(); } } async function downscaleMatForFreq(workMat, cols, rows) { const maxLongSide = getMaskSegmentRuntimeConfig().pipeline.paintFreqMaxLongSide; const longSide = Math.max(cols, rows); if (longSide <= maxLongSide) { return { mat: workMat, cols, rows, owned: false }; } const scale = maxLongSide / longSide; const freqCols = Math.floor(cols * scale); const freqRows = Math.floor(rows * scale); const resized = cv.createMat(freqCols, freqRows, 3); await cv.resize(workMat, resized, { width: freqCols, height: freqRows }, cv.INTER_LINEAR); return { mat: resized, cols: freqCols, rows: freqRows, owned: true }; } /** 复用已上传的 BGR Mat,避免重复 bufferToMat + JS↔原生往返 */ export async function prepareFreqLayersFromWorkMat(workMat, cols, rows) { const paintCfg = getMaskSegmentRuntimeConfig().paint; const blurStart = __DEV__ ? performance.now() : 0; const scaled = await downscaleMatForFreq(workMat, cols, rows); const freqMat = scaled.mat; const freqCols = scaled.cols; const freqRows = scaled.rows; let labMat = null; let lMat = null; let lLowMat = null; let lHighMat = null; try { labMat = cv.cvtColorBgr(freqMat, cv.COLOR_BGR2Lab); lMat = cv.createMat(freqCols, freqRows, 1); cv.extractChannel(labMat, lMat, 0); labMat.release(); labMat = null; lLowMat = cv.createMat(freqCols, freqRows, 1); const kernel = paintCfg.lLowBlurKernel; await cv.GaussianBlur(lMat, lLowMat, { width: kernel, height: kernel }, 0); lHighMat = await buildHighFreqMatNative(lMat, lLowMat, freqCols, freqRows, paintCfg.lHighGain); await cv.addWeighted(lLowMat, paintCfg.lLowContrast, null, 0, 128 * (1 - paintCfg.lLowContrast), lLowMat); await cv.addWeighted(lLowMat, paintCfg.lLowBrightness, null, 0, 128 * (1 - paintCfg.lLowBrightness), lLowMat); const lowFreqImage = cv.grayMatToSkiaImage(lLowMat); const highFreqImage = cv.grayMatToSkiaImage(lHighMat); if (!lowFreqImage || !highFreqImage) { lowFreqImage?.dispose(); highFreqImage?.dispose(); return null; } return { lowFreqImage, highFreqImage }; } finally { if (scaled.owned) { freqMat.release(); } labMat?.release(); lMat?.release(); lLowMat?.release(); lHighMat?.release(); } } /** 单次 Mat 上传 → 高低频 + 原图 Skia(并行,高低频先就绪时可回调) */ export async function preparePaintResourcesFromWorkBuffer(bgrBuffer, cols, rows, onFreqLayersReady) { const pixelCount = cols * rows; if (bgrBuffer.length !== pixelCount * 3) { return null; } const prepStart = __DEV__ ? performance.now() : 0; const workMat = cv.bgrBufferToMat(bgrBuffer, cols, rows); try { const originPromise = cv.matToSkiaImage(workMat); const freqPromise = prepareFreqLayersFromWorkMat(workMat, cols, rows); const layers = await freqPromise; if (!layers) { const originImage = await originPromise; originImage?.dispose(); return null; } onFreqLayersReady?.(layers); const originImage = await originPromise; if (!originImage) { releaseFreqLayerImages(layers); return null; } return { originImage, layers }; } finally { workMat.release(); } } /** @deprecated 测试兼容;生产路径请用 preparePaintResourcesFromWorkBuffer */ export async function prepareFreqLayersFromBgrBuffer(bgrBuffer, cols, rows) { const pixelCount = cols * rows; if (bgrBuffer.length !== pixelCount * 3) { return null; } const workMat = cv.bgrBufferToMat(bgrBuffer, cols, rows); try { return await prepareFreqLayersFromWorkMat(workMat, cols, rows); } finally { workMat.release(); } } /** 原图 BGR → Skia RGBA(OpenCV cvtColor,与 freq 并行) */ export async function originBgrBufferToSkiaImage(bgrBuffer, cols, rows) { return cv.bgrBufferToSkiaImage(bgrBuffer, cols, rows); } //# sourceMappingURL=freqLayerPrep.js.map