import { transparentColorData } from './pixelEditingUtils'; // 定义像素化模式 export enum PixelationMode { Dominant = 'dominant', // 卡通模式(主色) Average = 'average', // 真实模式(平均色) } // 定义色号系统类型 export type ColorSystem = 'MARD' | 'COCO' | '漫漫' | '盼盼' | '咪小窝'; // --- 必要的类型定义 --- export interface RgbColor { r: number; g: number; b: number; } export interface PaletteColor { key: string; hex: string; rgb: RgbColor; } export interface MappedPixel { key: string; color: string; isExternal?: boolean; } // --- 辅助函数 --- // 转换 Hex 到 RGB export function hexToRgb(hex: string): RgbColor | null { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } // 计算颜色距离 export function colorDistance(rgb1: RgbColor, rgb2: RgbColor): number { const dr = rgb1.r - rgb2.r; const dg = rgb1.g - rgb2.g; const db = rgb1.b - rgb2.b; return Math.sqrt(dr * dr + dg * dg + db * db); } // 查找最接近的颜色 export function findClosestPaletteColor( targetRgb: RgbColor, palette: PaletteColor[] ): PaletteColor { if (!palette || palette.length === 0) { console.error("findClosestPaletteColor: Palette is empty or invalid!"); // 提供一个健壮的回退 return { key: 'ERR', hex: '#000000', rgb: { r: 0, g: 0, b: 0 } }; } let minDistance = Infinity; let closestColor = palette[0]; for (const paletteColor of palette) { const distance = colorDistance(targetRgb, paletteColor.rgb); if (distance < minDistance) { minDistance = distance; closestColor = paletteColor; } if (distance === 0) break; // 完全匹配,提前退出 } return closestColor; } // --- 核心像素化计算逻辑 --- /** * 计算图像指定区域的代表色(根据所选模式) * @param imageData 包含像素数据的 ImageData 对象 * @param startX 区域起始 X 坐标 * @param startY 区域起始 Y 坐标 * @param width 区域宽度 * @param height 区域高度 * @param mode 计算模式 ('dominant' 或 'average') * @returns 代表色的 RGB 对象,或 null(如果区域无效或全透明) */ function calculateCellRepresentativeColor( imageData: ImageData, startX: number, startY: number, width: number, height: number, mode: PixelationMode ): RgbColor | null { const data = imageData.data; const imgWidth = imageData.width; let rSum = 0, gSum = 0, bSum = 0; let pixelCount = 0; const colorCountsInCell: { [key: string]: number } = {}; let dominantColorRgb: RgbColor | null = null; let maxCount = 0; const endX = startX + width; const endY = startY + height; for (let y = startY; y < endY; y++) { for (let x = startX; x < endX; x++) { const index = (y * imgWidth + x) * 4; // 检查 alpha 通道,忽略完全透明的像素 if (data[index + 3] < 128) continue; const r = data[index]; const g = data[index + 1]; const b = data[index + 2]; pixelCount++; if (mode === PixelationMode.Average) { rSum += r; gSum += g; bSum += b; } else { // Dominant mode const colorKey = `${r},${g},${b}`; colorCountsInCell[colorKey] = (colorCountsInCell[colorKey] || 0) + 1; if (colorCountsInCell[colorKey] > maxCount) { maxCount = colorCountsInCell[colorKey]; dominantColorRgb = { r, g, b }; } } } } if (pixelCount === 0) { return null; // 区域内没有不透明像素 } if (mode === PixelationMode.Average) { return { r: Math.round(rSum / pixelCount), g: Math.round(gSum / pixelCount), b: Math.round(bSum / pixelCount), }; } else { // Dominant mode return dominantColorRgb; // 可能为 null 如果只有一个透明像素 } } /** * 根据原始图像数据、网格尺寸、调色板和模式计算像素化网格数据。 * @param originalCtx 原始图像的 Canvas 2D Context * @param imgWidth 原始图像宽度 * @param imgHeight 原始图像高度 * @param N 网格横向数量 * @param M 网格纵向数量 * @param palette 当前使用的调色板 * @param mode 像素化模式 (Dominant/Average) * @param t1FallbackColor T1 或其他备用颜色数据 * @returns 计算后的 MappedPixel 网格数据 */ export function calculatePixelGrid( originalCtx: CanvasRenderingContext2D, imgWidth: number, imgHeight: number, N: number, M: number, palette: PaletteColor[], mode: PixelationMode, t1FallbackColor: PaletteColor // 传入备用色 ): MappedPixel[][] { console.log(`Calculating pixel grid with mode: ${mode}`); const mappedData: MappedPixel[][] = Array(M).fill(null).map(() => Array(N).fill({ key: t1FallbackColor.key, color: t1FallbackColor.hex })); const cellWidthOriginal = imgWidth / N; const cellHeightOriginal = imgHeight / M; let fullImageData: ImageData | null = null; try { fullImageData = originalCtx.getImageData(0, 0, imgWidth, imgHeight); } catch (e) { console.error("Failed to get full image data:", e); // 如果无法获取图像数据,返回一个空的或默认的网格 return mappedData; } for (let j = 0; j < M; j++) { for (let i = 0; i < N; i++) { const startXOriginal = Math.floor(i * cellWidthOriginal); const startYOriginal = Math.floor(j * cellHeightOriginal); // 计算精确的单元格结束位置,避免超出图像边界 const endXOriginal = Math.min(imgWidth, Math.ceil((i + 1) * cellWidthOriginal)); const endYOriginal = Math.min(imgHeight, Math.ceil((j + 1) * cellHeightOriginal)); // 计算实际的单元格宽高 const currentCellWidth = Math.max(1, endXOriginal - startXOriginal); const currentCellHeight = Math.max(1, endYOriginal - startYOriginal); // 使用提取的函数计算代表色 const representativeRgb = calculateCellRepresentativeColor( fullImageData, startXOriginal, startYOriginal, currentCellWidth, currentCellHeight, mode ); let finalCellColorData: MappedPixel; if (representativeRgb) { const closestBead = findClosestPaletteColor(representativeRgb, palette); finalCellColorData = { key: closestBead.key, color: closestBead.hex }; } else { // 如果单元格为空或全透明,标记为透明/外部 finalCellColorData = { ...transparentColorData }; } mappedData[j][i] = finalCellColorData; } } console.log(`Pixel grid calculation complete for mode: ${mode}`); return mappedData; }