From a1ea5dfffd8e5a1d6f0463e7c7a2e45ae4955b46 Mon Sep 17 00:00:00 2001 From: zihanjian Date: Mon, 26 May 2025 14:53:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=A2=9C=E8=89=B2=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E6=BA=90=E9=A2=9C=E8=89=B2=E5=92=8C=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E7=9A=84=E6=9B=BF=E6=8D=A2=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=E8=B0=83=E8=89=B2=E6=9D=BF=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=BB=A5=E9=9B=86=E6=88=90=E9=A2=9C=E8=89=B2=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E7=8A=B6=E6=80=81=E5=92=8C=E4=BA=A4=E4=BA=92=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=E5=92=8C?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=8F=AF=E8=AF=BB=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/page.tsx | 158 +++++++++++++++++++++++++++++++- src/components/ColorPalette.tsx | 125 ++++++++++++++++++++++++- 2 files changed, 281 insertions(+), 2 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index c2404f9..89e47c5 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -147,6 +147,16 @@ export default function Home() { // 新增:完整色板切换状态 const [showFullPalette, setShowFullPalette] = useState(false); + + // 新增:颜色替换相关状态 + const [colorReplaceState, setColorReplaceState] = useState<{ + isActive: boolean; + step: 'select-source' | 'select-target'; + sourceColor?: { key: string; color: string }; + }>({ + isActive: false, + step: 'select-source' + }); const originalCanvasRef = useRef(null); const pixelatedCanvasRef = useRef(null); @@ -337,6 +347,15 @@ export default function Home() { return; } + // 如果当前在颜色替换模式,先退出替换模式 + if (colorReplaceState.isActive) { + setColorReplaceState({ + isActive: false, + step: 'select-source' + }); + setHighlightColorKey(null); + } + setIsEraseMode(!isEraseMode); // 如果开启擦除模式,取消选中的颜色 if (!isEraseMode) { @@ -913,6 +932,19 @@ export default function Home() { if (i >= 0 && i < N && j >= 0 && j < M) { const cellData = mappedPixelData[j][i]; + // 颜色替换模式逻辑 - 选择源颜色 + if (isClick && colorReplaceState.isActive && colorReplaceState.step === 'select-source') { + if (cellData && !cellData.isExternal && cellData.key && cellData.key !== TRANSPARENT_KEY) { + // 执行选择源颜色 + handleCanvasColorSelect({ + key: cellData.key, + color: cellData.color + }); + setTooltipData(null); + } + return; + } + // 一键擦除模式逻辑 if (isClick && isEraseMode) { if (cellData && !cellData.isExternal && cellData.key && cellData.key !== TRANSPARENT_KEY) { @@ -1197,6 +1229,127 @@ export default function Home() { setShowFullPalette(!showFullPalette); }; + // 新增:处理颜色选择,同时管理模式切换 + const handleColorSelect = (colorData: { key: string; color: string; isExternal?: boolean }) => { + // 如果选择的是橡皮擦(透明色)且当前在颜色替换模式,退出替换模式 + if (colorData.key === TRANSPARENT_KEY && colorReplaceState.isActive) { + setColorReplaceState({ + isActive: false, + step: 'select-source' + }); + setHighlightColorKey(null); + } + + // 选择任何颜色(包括橡皮擦)时,都应该退出一键擦除模式 + if (isEraseMode) { + setIsEraseMode(false); + } + + // 设置选中的颜色 + setSelectedColor(colorData); + }; + + // 新增:颜色替换相关处理函数 + const handleColorReplaceToggle = () => { + setColorReplaceState(prev => { + if (prev.isActive) { + // 退出替换模式 + return { + isActive: false, + step: 'select-source' + }; + } else { + // 进入替换模式 + // 只退出冲突的模式,但保持在手动上色模式下 + setIsEraseMode(false); + setSelectedColor(null); + return { + isActive: true, + step: 'select-source' + }; + } + }); + }; + + // 新增:处理从画布选择源颜色 + const handleCanvasColorSelect = (colorData: { key: string; color: string }) => { + if (colorReplaceState.isActive && colorReplaceState.step === 'select-source') { + // 高亮显示选中的颜色 + setHighlightColorKey(colorData.color); + // 进入第二步:选择目标颜色 + setColorReplaceState({ + isActive: true, + step: 'select-target', + sourceColor: colorData + }); + } + }; + + // 新增:执行颜色替换 + const handleColorReplace = (sourceColor: { key: string; color: string }, targetColor: { key: string; color: string }) => { + if (!mappedPixelData || !gridDimensions) return; + + const { N, M } = gridDimensions; + const newPixelData = mappedPixelData.map(row => row.map(cell => ({ ...cell }))); + let replaceCount = 0; + + // 遍历所有像素,替换匹配的颜色 + for (let j = 0; j < M; j++) { + for (let i = 0; i < N; i++) { + const currentCell = newPixelData[j][i]; + if (currentCell && !currentCell.isExternal && + currentCell.color.toUpperCase() === sourceColor.color.toUpperCase()) { + // 替换颜色 + newPixelData[j][i] = { + key: targetColor.key, + color: targetColor.color, + isExternal: false + }; + replaceCount++; + } + } + } + + if (replaceCount > 0) { + // 更新像素数据 + setMappedPixelData(newPixelData); + + // 重新计算颜色统计 + if (colorCounts) { + const newColorCounts: { [hexKey: string]: { count: number; color: string } } = {}; + let newTotalCount = 0; + + newPixelData.flat().forEach(cell => { + if (cell && !cell.isExternal && cell.key !== TRANSPARENT_KEY) { + const cellHex = cell.color.toUpperCase(); + if (!newColorCounts[cellHex]) { + newColorCounts[cellHex] = { + count: 0, + color: cellHex + }; + } + newColorCounts[cellHex].count++; + newTotalCount++; + } + }); + + setColorCounts(newColorCounts); + setTotalBeadCount(newTotalCount); + } + + console.log(`颜色替换完成:将 ${replaceCount} 个 ${sourceColor.key} 替换为 ${targetColor.key}`); + } + + // 退出替换模式 + setColorReplaceState({ + isActive: false, + step: 'select-source' + }); + + // 清除高亮 + setHighlightColorKey(null); + }; + // 生成完整色板数据(用户自定义色板中选中的所有颜色) const fullPaletteColors = useMemo(() => { const selectedColors: { key: string; color: string }[] = []; @@ -1568,7 +1721,7 @@ export default function Home() { diff --git a/src/components/ColorPalette.tsx b/src/components/ColorPalette.tsx index db41ee3..63220a5 100644 --- a/src/components/ColorPalette.tsx +++ b/src/components/ColorPalette.tsx @@ -10,6 +10,13 @@ interface ColorData { isExternal?: boolean; // 添加 isExternal 属性以支持透明/橡皮擦功能 } +// 新增:颜色替换相关接口 +interface ColorReplaceState { + isActive: boolean; + step: 'select-source' | 'select-target'; // 替换步骤:选择源颜色 | 选择目标颜色 + sourceColor?: ColorData; // 被替换的颜色 +} + interface ColorPaletteProps { colors: ColorData[]; selectedColor: ColorData | null; @@ -25,6 +32,10 @@ interface ColorPaletteProps { fullPaletteColors?: ColorData[]; // 用户自定义色板中的所有颜色 showFullPalette?: boolean; // 是否显示完整色板 onToggleFullPalette?: () => void; // 切换完整色板显示 + // 新增:颜色替换相关props + colorReplaceState?: ColorReplaceState; // 颜色替换状态 + onColorReplaceToggle?: () => void; // 切换颜色替换模式 + onColorReplace?: (sourceColor: ColorData, targetColor: ColorData) => void; // 执行颜色替换 } const ColorPalette: React.FC = ({ @@ -38,7 +49,10 @@ const ColorPalette: React.FC = ({ onHighlightColor, fullPaletteColors, showFullPalette, - onToggleFullPalette + onToggleFullPalette, + colorReplaceState, + onColorReplaceToggle, + onColorReplace }) => { if (!colors || colors.length === 0) { // Apply dark mode text color @@ -84,6 +98,81 @@ const ColorPalette: React.FC = ({ )} + {/* 颜色替换状态提示 */} + {colorReplaceState?.isActive && ( +
+
+
+ + + + 颜色替换模式 +
+ + {colorReplaceState.step === 'select-source' ? ( +
+

步骤 1/2:点击图中要被替换的颜色

+

选择后将高亮显示该颜色的所有位置

+
+ ) : ( +
+

步骤 2/2:从下方色板选择替换成的颜色

+
+ 被替换的颜色: +
+ + + {selectedColorSystem ? getDisplayColorKey(colorReplaceState.sourceColor?.color || '', selectedColorSystem) : colorReplaceState.sourceColor?.key} + +
+
+
+ )} +
+
+ )} + + {/* 一键擦除状态提示 */} + {isEraseMode && ( +
+
+
+ + + + 背景擦除模式 +
+ +
+

点击图中任意颜色,删除整个颜色块

+

使用洪水填充算法,一次性擦除连通的相同颜色区域

+
+
+
+ )} + + {/* 橡皮擦选中状态提示 */} + {selectedColor?.key === transparentKey && !isEraseMode && !colorReplaceState?.isActive && ( +
+
+
+ + + + 橡皮擦模式 +
+ +
+

点击图中任意位置清除单个格子

+

逐个删除不需要的颜色,不会影响其他格子

+
+
+
+ )} + {/* 颜色按钮区域 */}
{/* 一键擦除按钮 */} @@ -111,6 +200,31 @@ const ColorPalette: React.FC = ({ )} + {/* 颜色替换按钮 */} + {onColorReplaceToggle && ( + + )} + {colorsToShow.map((colorData) => { // 检查当前颜色是否是透明/橡皮擦 const isTransparent = transparentKey && colorData.key === transparentKey; @@ -137,7 +251,16 @@ const ColorPalette: React.FC = ({