react-native-mask-segment-c.../dist/utils/compositePaintedImage.js
a1518 56738b1f06 feat: add MaskSegmentApp source code and config
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 00:23:52 -07:00

146 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Buffer } from 'buffer';
import RNFS from 'react-native-fs';
// upng-js: used for PNG encode of CPU recolor.
import UPNG from 'upng-js';
import { renderPaintedImageOffscreen } from './paintShaderRuntime';
import { writePngBase64ToFile, writePngBytesToFile } from './exportUtils';
/**
* CPU recolor: directly map pick codes to painted BGR colors (or copy origin).
* Produces RGBA PNG bytes via upng-js. This is the *fallback* path when rich shader offscreen
* is not available or fails. It produces flat colors without the editor's lighting + freq texture.
*/
function cpuRecolorToPngBytes(originBgr, pickBuffer, paintedRegions, cols, rows) {
const pixelCount = cols * rows;
const rgba = new Uint8Array(pixelCount * 4);
const colorByPickCode = new Map();
for (const [regionId, color] of paintedRegions) {
colorByPickCode.set(regionId + 1, color);
}
for (let i = 0; i < pixelCount; i++) {
const code = pickBuffer[i];
const color = code > 0 ? colorByPickCode.get(code) : undefined;
const d = i * 4;
if (color) {
rgba[d] = color.r;
rgba[d + 1] = color.g;
rgba[d + 2] = color.b;
rgba[d + 3] = 255;
}
else {
const s = i * 3;
rgba[d] = originBgr[s + 2]; // RGB <- BGR
rgba[d + 1] = originBgr[s + 1];
rgba[d + 2] = originBgr[s];
rgba[d + 3] = 255;
}
}
const png = UPNG.encode([rgba.buffer], cols, rows, 0);
return new Uint8Array(png);
}
/** 将上色区域导出为 recolored PNG。
* 优先级(从好到保底):
* 1. exportPngBytes调用方用 makeImageSnapshot 在高分辨率 Canvas 上捕获的完整 shader 结果)—— 推荐的“保存快照”路径,无 CPU 逐像素,无二次 drawAsImage。
* 2. shaderTextures + render*(通过 renderPaintedImageOffscreen / drawAsImage 重建同一套 PaintShaderLayer + SkSL
* 3. CPU 逐像素 recolorflat无光照/纹理,仅作最后兜底,保证保存不中断)。
*/
export async function compositePaintedImage(input) {
const { originBuffer, cols, rows, pickBuffer, paintedRegions, destDir, exportPngBase64, exportPngBytes, shaderTextures, renderWidth, renderHeight, } = input;
if (paintedRegions.size === 0) {
throw new Error('No painted regions, cannot save');
}
if (pickBuffer.length !== cols * rows) {
const msg = 'pickMap size does not match image';
console.error('[VIZ-SAVE] composite will throw:', msg, { pickLen: pickBuffer.length, expected: cols * rows, cols, rows });
throw new Error(msg);
}
if (originBuffer.length !== cols * rows * 3) {
const msg = 'Original buffer size does not match image';
console.error('[VIZ-SAVE] composite will throw:', msg, { originLen: originBuffer.length, expected: cols * rows * 3, cols, rows });
throw new Error(msg);
}
let pngBytesForWrite;
let pngBase64ForWrite;
let usedSnapshot = false;
let usedRichShader = false;
// 1) Highest priority: pre-encoded PNG base64 from makeImageSnapshot (no extra conversion).
if (exportPngBase64 && exportPngBase64.length > 0) {
pngBase64ForWrite = exportPngBase64;
usedSnapshot = true;
}
// 1b) Snapshot bytes fallback (legacy callers).
else if (exportPngBytes && exportPngBytes.length > 0) {
pngBytesForWrite = exportPngBytes;
usedSnapshot = true;
}
// 2) 回退 rich用 live 纹理 + drawAsImage 重建与编辑器一致的 shader 结果(带光照 + 频率纹理)。
else if (shaderTextures && renderWidth && renderHeight) {
try {
const offImg = await renderPaintedImageOffscreen({
originImage: shaderTextures.originImage,
paintColorMap: shaderTextures.paintColorMap,
lowFreqImage: shaderTextures.lowFreqImage,
highFreqImage: shaderTextures.highFreqImage,
width: renderWidth,
height: renderHeight,
showOrigin: false,
});
if (offImg) {
let b64 = '';
try {
const enc = offImg.encodeToBase64;
if (typeof enc === 'function') {
b64 = enc.call(offImg) || '';
}
}
catch (encErr) {
console.warn('[VIZ-SAVE] offscreen encodeToBase64 failed:', encErr);
}
if (b64 && b64.length > 0) {
pngBytesForWrite = new Uint8Array(Buffer.from(b64, 'base64'));
usedRichShader = true;
}
try {
const disp = offImg.dispose;
if (typeof disp === 'function')
disp.call(offImg);
}
catch { }
}
}
catch (e) {
console.warn('[VIZ-SAVE] rich shader offscreen for export failed (will fallback):', e);
}
}
// 3) 最后兜底CPU 逐像素flat 颜色,无 editor 质感)。
if (!pngBase64ForWrite && !pngBytesForWrite) {
try {
pngBytesForWrite = cpuRecolorToPngBytes(originBuffer, pickBuffer, paintedRegions, cols, rows);
}
catch (e) {
throw new Error('CPU recolor PNG decoding failed');
}
}
const dir = destDir ?? RNFS.CachesDirectoryPath;
const filePath = `${dir}/painted_${Date.now()}.png`;
try {
if (pngBase64ForWrite) {
await writePngBase64ToFile(filePath, pngBase64ForWrite);
}
else {
await writePngBytesToFile(filePath, pngBytesForWrite);
}
void usedSnapshot;
void usedRichShader;
}
catch (e) {
console.error('[VIZ-SAVE] composite writeFile threw:', e, { filePath, dir });
throw e;
}
return {
filePath,
width: cols,
height: rows,
paintedCount: paintedRegions.size,
};
}
//# sourceMappingURL=compositePaintedImage.js.map