diff --git a/src/app/editor/page.tsx b/src/app/editor/page.tsx deleted file mode 100644 index 8de3bf3..0000000 --- a/src/app/editor/page.tsx +++ /dev/null @@ -1,248 +0,0 @@ -'use client'; - -import React, { useState, useEffect, useMemo, useCallback } from 'react'; -import { useRouter } from 'next/navigation'; -import CanvasContainer from '../../components/CanvasContainer'; - -// 导入像素化工具和类型 -import { - PixelationMode, - calculatePixelGrid, - PaletteColor, - MappedPixel, - hexToRgb, -} from '../../utils/pixelation'; - -import { - getColorKeyByHex, - getMardToHexMapping, - sortColorsByHue, - ColorSystem -} from '../../utils/colorSystemUtils'; - -// 获取完整色板 -const mardToHexMapping = getMardToHexMapping(); -const fullBeadPalette: PaletteColor[] = Object.entries(mardToHexMapping) - .map(([, hex]) => { - const rgb = hexToRgb(hex); - if (!rgb) return null; - return { key: hex, hex, rgb }; - }) - .filter((item): item is PaletteColor => item !== null); - -export default function EditorPage() { - const router = useRouter(); - - // 基础状态 - const [originalImageSrc, setOriginalImageSrc] = useState(null); - const [granularity, setGranularity] = useState(50); - const [pixelationMode, setPixelationMode] = useState(PixelationMode.Dominant); - const [selectedColorSystem] = useState('MARD'); - const [activeBeadPalette] = useState(fullBeadPalette); - - // 像素数据 - const [mappedPixelData, setMappedPixelData] = useState(null); - const [colorCounts, setColorCounts] = useState<{ [key: string]: { count: number; color: string } } | null>(null); - const [isProcessing, setIsProcessing] = useState(false); - - // 从 localStorage 加载图片 - useEffect(() => { - const imageSrc = localStorage.getItem('uploadedImage'); - if (imageSrc) { - setOriginalImageSrc(imageSrc); - localStorage.removeItem('uploadedImage'); // 清除以避免重复加载 - } else { - // 如果没有图片,返回首页 - router.push('/'); - } - }, [router]); - - // 生成像素画 - const generatePixelArt = useCallback(() => { - if (!originalImageSrc) return; - - setIsProcessing(true); - - const img = new Image(); - img.onload = () => { - // 创建临时 canvas 获取图像数据 - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - if (!ctx) { - setIsProcessing(false); - return; - } - - canvas.width = img.width; - canvas.height = img.height; - ctx.drawImage(img, 0, 0); - - // 计算网格尺寸 - const N = Math.round(img.height / granularity); - const M = Math.round(img.width / granularity); - - // 获取备用颜色(第一个颜色) - const fallbackColor = activeBeadPalette[0] || { key: '#000000', hex: '#000000', rgb: { r: 0, g: 0, b: 0 } }; - - // 调用新的 API - const mappedPixelData = calculatePixelGrid( - ctx, - img.width, - img.height, - N, - M, - activeBeadPalette, - pixelationMode, - fallbackColor - ); - - // 计算颜色统计 - const colorCounts: { [key: string]: { count: number; color: string } } = {}; - - mappedPixelData.forEach(row => { - row.forEach(pixel => { - if (pixel.key && pixel.key !== 'transparent' && !pixel.isExternal) { - if (!colorCounts[pixel.key]) { - colorCounts[pixel.key] = { count: 0, color: pixel.color }; - } - colorCounts[pixel.key].count++; - } - }); - }); - - setMappedPixelData(mappedPixelData); - setColorCounts(colorCounts); - setIsProcessing(false); - }; - img.src = originalImageSrc; - }, [originalImageSrc, granularity, activeBeadPalette, pixelationMode]); - - // 首次加载时自动生成 - useEffect(() => { - if (originalImageSrc && !mappedPixelData) { - generatePixelArt(); - } - }, [originalImageSrc, mappedPixelData, generatePixelArt]); - - // 处理像素网格更新 - const handlePixelGridUpdate = useCallback((newGrid: MappedPixel[][]) => { - setMappedPixelData(newGrid); - - // 重新计算颜色统计 - const counts: { [key: string]: { count: number; color: string } } = {}; - - newGrid.forEach(row => { - row.forEach(pixel => { - if (pixel.key && pixel.key !== 'transparent' && !pixel.isExternal) { - if (!counts[pixel.key]) { - counts[pixel.key] = { count: 0, color: pixel.color }; - } - counts[pixel.key].count++; - } - }); - }); - - setColorCounts(counts); - }, []); - - // 计算当前色板 - const currentColorPalette = useMemo(() => { - if (!mappedPixelData) return []; - - const uniqueColors = new Set(); - mappedPixelData.forEach(row => { - row.forEach(pixel => { - if (pixel.key && pixel.key !== 'transparent' && !pixel.isExternal) { - uniqueColors.add(pixel.key); - } - }); - }); - - const colorArray = Array.from(uniqueColors).map(key => { - const colorInfo = colorCounts?.[key]; - return { - key, - hex: colorInfo?.color || key, - color: colorInfo?.color || key, - name: getColorKeyByHex(key, selectedColorSystem) || key - }; - }); - - return sortColorsByHue(colorArray); - }, [mappedPixelData, colorCounts, selectedColorSystem]); - - return ( -
- {/* 顶部工具栏 */} -
-
-
- - -

拼豆底稿编辑器

-
- - {/* 参数调整 */} -
-
- - setGranularity(Number(e.target.value))} - className="w-24" - /> - {granularity} -
- - - - -
-
-
- - {/* 主内容区 */} -
- {mappedPixelData ? ( -
- -
- ) : ( -
-
加载中...
-
- )} -
-
- ); -} \ No newline at end of file diff --git a/src/app/studio/page.tsx b/src/app/studio/page.tsx index 9b48374..cdc7fd5 100644 --- a/src/app/studio/page.tsx +++ b/src/app/studio/page.tsx @@ -757,8 +757,117 @@ export default function FocusMode() { }); } - // TODO: 实现去背景功能 - // if (removeBackground) { ... } + // 实现去背景功能 + if (removeBackground) { + const rows = pixelData.length; + const cols = pixelData[0]?.length || 0; + + // 统计边缘颜色 + const edgeColorCounts: { [key: string]: number } = {}; + + // 统计上边缘 + for (let x = 0; x < cols; x++) { + const color = pixelData[0][x].color; + if (color && color !== 'transparent') { + edgeColorCounts[color] = (edgeColorCounts[color] || 0) + 1; + } + } + + // 统计下边缘 + for (let x = 0; x < cols; x++) { + const color = pixelData[rows - 1][x].color; + if (color && color !== 'transparent') { + edgeColorCounts[color] = (edgeColorCounts[color] || 0) + 1; + } + } + + // 统计左边缘(排除角落避免重复计数) + for (let y = 1; y < rows - 1; y++) { + const color = pixelData[y][0].color; + if (color && color !== 'transparent') { + edgeColorCounts[color] = (edgeColorCounts[color] || 0) + 1; + } + } + + // 统计右边缘(排除角落避免重复计数) + for (let y = 1; y < rows - 1; y++) { + const color = pixelData[y][cols - 1].color; + if (color && color !== 'transparent') { + edgeColorCounts[color] = (edgeColorCounts[color] || 0) + 1; + } + } + + // 找出边缘最多的颜色 + let mostCommonEdgeColor = ''; + let maxCount = 0; + for (const [color, count] of Object.entries(edgeColorCounts)) { + if (count > maxCount) { + maxCount = count; + mostCommonEdgeColor = color; + } + } + + // 如果找到了最常见的边缘颜色,进行洪水填充 + if (mostCommonEdgeColor) { + // 洪水填充函数 + const floodFill = (startY: number, startX: number, targetColor: string) => { + const visited = new Set(); + const queue: [number, number][] = [[startY, startX]]; + + while (queue.length > 0) { + const [y, x] = queue.shift()!; + const key = `${y},${x}`; + + // 检查边界和是否已访问 + if (y < 0 || y >= rows || x < 0 || x >= cols || visited.has(key)) { + continue; + } + + visited.add(key); + + // 检查颜色是否匹配 + if (pixelData[y][x].color !== targetColor) { + continue; + } + + // 标记为外部(背景) + pixelData[y][x].isExternal = true; + + // 添加相邻像素到队列 + queue.push([y - 1, x], [y + 1, x], [y, x - 1], [y, x + 1]); + } + }; + + // 从所有边缘开始洪水填充 + // 上边缘 + for (let x = 0; x < cols; x++) { + if (pixelData[0][x].color === mostCommonEdgeColor && !pixelData[0][x].isExternal) { + floodFill(0, x, mostCommonEdgeColor); + } + } + + // 下边缘 + for (let x = 0; x < cols; x++) { + if (pixelData[rows - 1][x].color === mostCommonEdgeColor && !pixelData[rows - 1][x].isExternal) { + floodFill(rows - 1, x, mostCommonEdgeColor); + } + } + + // 左边缘 + for (let y = 0; y < rows; y++) { + if (pixelData[y][0].color === mostCommonEdgeColor && !pixelData[y][0].isExternal) { + floodFill(y, 0, mostCommonEdgeColor); + } + } + + // 右边缘 + for (let y = 0; y < rows; y++) { + if (pixelData[y][cols - 1].color === mostCommonEdgeColor && !pixelData[y][cols - 1].isExternal) { + floodFill(y, cols - 1, mostCommonEdgeColor); + } + } + } + } // 计算颜色统计 const counts: { [key: string]: { count: number; color: string } } = {}; @@ -919,6 +1028,14 @@ export default function FocusMode() { regeneratePixelArt(); } }, [pixelationMode]); // 只监听 pixelationMode 的变化 + + // 监听 removeBackground 变化并重新生成 + useEffect(() => { + // 只有在有图片数据时才重新生成 + if (mappedPixelData && focusState.editMode === 'preview') { + regeneratePixelArt(); + } + }, [removeBackground]); // 只监听 removeBackground 的变化 if (!mappedPixelData || !gridDimensions) { return ( diff --git a/src/components/CanvasContainer.tsx b/src/components/CanvasContainer.tsx deleted file mode 100644 index 40f1554..0000000 --- a/src/components/CanvasContainer.tsx +++ /dev/null @@ -1,357 +0,0 @@ -'use client'; - -import React, { useState, useCallback, useRef } from 'react'; -import UnifiedCanvas, { CanvasMode } from './UnifiedCanvas'; -import { MappedPixel } from '../utils/pixelation'; -import { useEditMode } from '../hooks/useEditMode'; -import { usePreviewMode } from '../hooks/usePreviewMode'; -import { downloadImage } from '../utils/imageDownloader'; -import { GridDownloadOptions } from '../types/downloadTypes'; -import DownloadSettingsModal from './DownloadSettingsModal'; -import { ColorSystem } from '../utils/colorSystemUtils'; - -interface CanvasContainerProps { - pixelGrid: MappedPixel[][]; - cellSize: number; - onPixelGridUpdate: (newGrid: MappedPixel[][]) => void; - colorPalette: Array<{ key: string; hex: string; name?: string }>; - selectedColorSystem: string; -} - -export default function CanvasContainer({ - pixelGrid, - cellSize, - onPixelGridUpdate, - colorPalette, - selectedColorSystem, -}: CanvasContainerProps) { - const [mode, setMode] = useState('preview'); - const [showDownloadModal, setShowDownloadModal] = useState(false); - const [isExporting, setIsExporting] = useState(false); - const [downloadOptions, setDownloadOptions] = useState({ - showGrid: true, - gridInterval: 10, - showCoordinates: true, - gridLineColor: '#000000', - includeStats: true, - exportCsv: false - }); - const canvasRef = useRef(null); - - // 预览模式钩子 - const { - selectedTool, - selectedColorKey, - highlightColorKey, - replaceModeState, - setSelectedTool, - setSelectedColorKey, - handleCellClick, - toggleHighlight, - resetToolState, - } = usePreviewMode(pixelGrid, onPixelGridUpdate); - - // 编辑模式钩子 - const { - currentColorKey, - markedCells, - recommendedRegion, - recommendMode, - isTimerRunning, - elapsedTime, - markCell, - switchColor, - setRecommendMode, - calculateProgress, - resetEditMode, - toggleTimer, - } = useEditMode(pixelGrid); - - // 处理画布准备完成 - const handleCanvasReady = useCallback((canvas: HTMLCanvasElement) => { - canvasRef.current = canvas; - }, []); - - // 切换模式 - const toggleMode = useCallback(() => { - if (mode === 'preview') { - // 切换到编辑模式 - resetToolState(); - setMode('edit'); - // 自动选择第一个颜色 - if (colorPalette.length > 0 && !currentColorKey) { - switchColor(colorPalette[0].key); - } - } else { - // 切换到预览模式 - resetEditMode(); - setMode('preview'); - } - }, [mode, resetToolState, resetEditMode, colorPalette, currentColorKey, switchColor]); - - // 处理下载 - const handleDownload = useCallback(async (options: GridDownloadOptions) => { - if (!canvasRef.current) return; - - setIsExporting(true); - try { - // 计算颜色统计和尺寸 - const colorCounts: { [key: string]: { count: number; color: string } } = {}; - let totalBeadCount = 0; - - pixelGrid.forEach(row => { - row.forEach(pixel => { - if (pixel.key && pixel.key !== 'transparent' && !pixel.isExternal) { - if (!colorCounts[pixel.key]) { - colorCounts[pixel.key] = { count: 0, color: pixel.color }; - } - colorCounts[pixel.key].count++; - totalBeadCount++; - } - }); - }); - - // 创建一个简单的色板数组 - const activeBeadPalette = colorPalette.map(c => ({ - key: c.key, - hex: c.hex, - rgb: { r: 0, g: 0, b: 0 } // 简化处理 - })); - - await downloadImage({ - mappedPixelData: pixelGrid, - gridDimensions: { N: pixelGrid.length, M: pixelGrid[0]?.length || 0 }, - colorCounts, - totalBeadCount, - options, - activeBeadPalette, - selectedColorSystem: selectedColorSystem as ColorSystem, - }); - } finally { - setIsExporting(false); - setShowDownloadModal(false); - } - }, [pixelGrid, selectedColorSystem, colorPalette]); - - // 格式化时间 - const formatTime = (seconds: number) => { - const mins = Math.floor(seconds / 60); - const secs = seconds % 60; - return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; - }; - - // 编辑模式的进度信息 - const progress = mode === 'edit' ? calculateProgress() : null; - - return ( -
- {/* 顶部工具栏 */} -
-
- {/* 模式切换 */} -
- - - {/* 预览模式工具 */} - {mode === 'preview' && ( - <> -
- - - - -
- - {replaceModeState.isActive && ( -
- 选择目标颜色进行替换 -
- )} - - )} - - {/* 编辑模式信息 */} - {mode === 'edit' && progress && ( - <> -
- - 当前颜色: - -
-
- -
- - 进度:{progress.completed}/{progress.total} ({progress.percentage}%) - -
-
-
-
- -
- - 用时:{formatTime(elapsedTime)} - - -
- - - - )} -
- - {/* 导出按钮 */} - -
-
- - {/* 画布区域 */} -
- {/* 处理悬停 */} : undefined} - highlightColorKey={mode === 'preview' ? highlightColorKey : undefined} - currentColorKey={mode === 'edit' ? currentColorKey : undefined} - markedCells={mode === 'edit' ? markedCells : undefined} - onMarkCell={mode === 'edit' ? markCell : undefined} - recommendedRegion={mode === 'edit' ? recommendedRegion : undefined} - onCanvasReady={handleCanvasReady} - className="w-full h-full" - /> -
- - {/* 底部颜色选择器 */} -
-
- {colorPalette.map((color) => ( -
-
- - {/* 下载设置模态框 */} - {showDownloadModal && ( - setShowDownloadModal(false)} - options={downloadOptions} - onOptionsChange={setDownloadOptions} - onDownload={() => handleDownload(downloadOptions)} - /> - )} -
- ); -} \ No newline at end of file diff --git a/src/components/FocusCanvas.tsx b/src/components/FocusCanvas.tsx index c5dd191..d5d5aef 100644 --- a/src/components/FocusCanvas.tsx +++ b/src/components/FocusCanvas.tsx @@ -81,6 +81,9 @@ const FocusCanvas: React.FC = ({ const y = row * cellSize; const cellKey = `${row},${col}`; + // 跳过外部或透明像素 + if (pixel.isExternal || pixel.key === 'transparent') continue; + // 确定格子颜色 let fillColor = pixel.color; @@ -191,7 +194,7 @@ const FocusCanvas: React.FC = ({ ctx.stroke(); } } - }, [mappedPixelData, gridDimensions, cellSize, currentColor, completedCells, recommendedCell, recommendedRegion, gridSectionInterval, showSectionLines, sectionLineColor, highlightColor, editMode, selectedCells]); + }, [mappedPixelData, gridDimensions, cellSize, currentColor, completedCells, recommendedCell, recommendedRegion, gridSectionInterval, showSectionLines, sectionLineColor, highlightColor, editMode, selectedCells, canvasScale]); // 处理触摸/鼠标事件 const getEventPosition = useCallback((event: React.MouseEvent | React.TouchEvent) => { @@ -385,8 +388,13 @@ const FocusCanvas: React.FC = ({ return (
= ({ = ({ - - -
- - {/* 模式指示器 */} -
- - {mode === 'preview' ? '预览模式' : '编辑模式'} - -
-
- ); -} \ No newline at end of file diff --git a/src/hooks/useEditMode.ts b/src/hooks/useEditMode.ts deleted file mode 100644 index bad0efb..0000000 --- a/src/hooks/useEditMode.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { useState, useCallback, useRef, useEffect } from 'react'; -import { MappedPixel } from '../utils/pixelation'; - -export type RecommendMode = 'nearest' | 'largest' | 'edge-first'; - -interface Region { - cells: Array<{ x: number; y: number }>; - bounds: { x: number; y: number; width: number; height: number }; -} - -export function useEditMode(pixelGrid: MappedPixel[][]) { - const [currentColorKey, setCurrentColorKey] = useState(''); - const [markedCells, setMarkedCells] = useState>(new Set()); - const [recommendedRegion, setRecommendedRegion] = useState(null); - const [recommendMode, setRecommendMode] = useState('nearest'); - const [isTimerRunning, setIsTimerRunning] = useState(false); - const [elapsedTime, setElapsedTime] = useState(0); - const [lastMarkedPosition, setLastMarkedPosition] = useState<{ x: number; y: number } | null>(null); - const timerRef = useRef(null); - - // 启动/停止计时器 - useEffect(() => { - if (isTimerRunning) { - timerRef.current = setInterval(() => { - setElapsedTime(prev => prev + 1); - }, 1000); - } else { - if (timerRef.current) { - clearInterval(timerRef.current); - timerRef.current = null; - } - } - - return () => { - if (timerRef.current) { - clearInterval(timerRef.current); - } - }; - }, [isTimerRunning]); - - // 获取连通区域 - const getConnectedRegion = useCallback((startX: number, startY: number, targetKey: string): Array<{ x: number; y: number }> => { - const visited = new Set(); - const region: Array<{ x: number; y: number }> = []; - const stack = [{ x: startX, y: startY }]; - - while (stack.length > 0) { - const { x, y } = stack.pop()!; - const key = `${x},${y}`; - - if (visited.has(key)) continue; - visited.add(key); - - if ( - x < 0 || x >= pixelGrid[0].length || - y < 0 || y >= pixelGrid.length || - pixelGrid[y][x].key !== targetKey || - pixelGrid[y][x].isExternal - ) { - continue; - } - - region.push({ x, y }); - - // 添加相邻格子 - stack.push({ x: x + 1, y }); - stack.push({ x: x - 1, y }); - stack.push({ x, y: y + 1 }); - stack.push({ x, y: y - 1 }); - } - - return region; - }, [pixelGrid]); - - // 查找所有未标记的区域 - const findUnmarkedRegions = useCallback((): Region[] => { - const visited = new Set(); - const regions: Region[] = []; - - for (let y = 0; y < pixelGrid.length; y++) { - for (let x = 0; x < pixelGrid[0].length; x++) { - const key = `${x},${y}`; - const pixel = pixelGrid[y][x]; - - if ( - !visited.has(key) && - pixel.key === currentColorKey && - !pixel.isExternal && - !markedCells.has(key) - ) { - const cells = getConnectedRegion(x, y, currentColorKey); - cells.forEach(cell => visited.add(`${cell.x},${cell.y}`)); - - if (cells.length > 0) { - const bounds = { - x: Math.min(...cells.map(c => c.x)), - y: Math.min(...cells.map(c => c.y)), - width: 0, - height: 0 - }; - bounds.width = Math.max(...cells.map(c => c.x)) - bounds.x + 1; - bounds.height = Math.max(...cells.map(c => c.y)) - bounds.y + 1; - - regions.push({ cells, bounds }); - } - } - } - } - - return regions; - }, [pixelGrid, currentColorKey, markedCells, getConnectedRegion]); - - // 推荐下一个区域 - const recommendNextRegion = useCallback(() => { - const regions = findUnmarkedRegions(); - if (regions.length === 0) { - setRecommendedRegion(null); - return; - } - - let selectedRegion: Region | null = null; - - switch (recommendMode) { - case 'largest': - selectedRegion = regions.reduce((largest, region) => - region.cells.length > largest.cells.length ? region : largest - ); - break; - - case 'nearest': - if (lastMarkedPosition) { - selectedRegion = regions.reduce((nearest, region) => { - const nearestDist = Math.min(...nearest.cells.map(cell => - Math.hypot(cell.x - lastMarkedPosition.x, cell.y - lastMarkedPosition.y) - )); - const regionDist = Math.min(...region.cells.map(cell => - Math.hypot(cell.x - lastMarkedPosition.x, cell.y - lastMarkedPosition.y) - )); - return regionDist < nearestDist ? region : nearest; - }); - } else { - selectedRegion = regions[0]; - } - break; - - case 'edge-first': - selectedRegion = regions.reduce((edgiest, region) => { - const edgeCount = region.cells.filter(cell => { - const x = cell.x; - const y = cell.y; - return x === 0 || y === 0 || - x === pixelGrid[0].length - 1 || - y === pixelGrid.length - 1; - }).length; - const edgiestCount = edgiest.cells.filter(cell => { - const x = cell.x; - const y = cell.y; - return x === 0 || y === 0 || - x === pixelGrid[0].length - 1 || - y === pixelGrid.length - 1; - }).length; - return edgeCount > edgiestCount ? region : edgiest; - }); - break; - } - - if (selectedRegion) { - setRecommendedRegion(selectedRegion.bounds); - } - }, [findUnmarkedRegions, recommendMode, lastMarkedPosition, pixelGrid]); - - // 标记单元格 - const markCell = useCallback((x: number, y: number) => { - const pixel = pixelGrid[y]?.[x]; - if (!pixel || pixel.key !== currentColorKey || pixel.isExternal) return; - - const region = getConnectedRegion(x, y, currentColorKey); - const newMarkedCells = new Set(markedCells); - let hasChanges = false; - - region.forEach(cell => { - const key = `${cell.x},${cell.y}`; - if (!newMarkedCells.has(key)) { - newMarkedCells.add(key); - hasChanges = true; - } - }); - - if (hasChanges) { - setMarkedCells(newMarkedCells); - setLastMarkedPosition({ x, y }); - - // 开始计时 - if (!isTimerRunning) { - setIsTimerRunning(true); - } - } - }, [pixelGrid, currentColorKey, markedCells, getConnectedRegion, isTimerRunning]); - - // 计算进度 - const calculateProgress = useCallback(() => { - if (!currentColorKey) return { completed: 0, total: 0, percentage: 0 }; - - let total = 0; - let completed = 0; - - for (let y = 0; y < pixelGrid.length; y++) { - for (let x = 0; x < pixelGrid[0].length; x++) { - const pixel = pixelGrid[y][x]; - if (pixel.key === currentColorKey && !pixel.isExternal) { - total++; - if (markedCells.has(`${x},${y}`)) { - completed++; - } - } - } - } - - const percentage = total > 0 ? Math.round((completed / total) * 100) : 0; - return { completed, total, percentage }; - }, [pixelGrid, currentColorKey, markedCells]); - - // 切换颜色 - const switchColor = useCallback((colorKey: string) => { - setCurrentColorKey(colorKey); - setMarkedCells(new Set()); - setRecommendedRegion(null); - setLastMarkedPosition(null); - }, []); - - // 重置编辑状态 - const resetEditMode = useCallback(() => { - setCurrentColorKey(''); - setMarkedCells(new Set()); - setRecommendedRegion(null); - setLastMarkedPosition(null); - setElapsedTime(0); - setIsTimerRunning(false); - }, []); - - // 自动推荐下一个区域 - useEffect(() => { - if (currentColorKey) { - recommendNextRegion(); - } - }, [currentColorKey, markedCells, recommendNextRegion]); - - return { - // 状态 - currentColorKey, - markedCells, - recommendedRegion, - recommendMode, - isTimerRunning, - elapsedTime, - - // 方法 - markCell, - switchColor, - setRecommendMode, - calculateProgress, - resetEditMode, - toggleTimer: () => setIsTimerRunning(prev => !prev), - }; -} \ No newline at end of file diff --git a/src/hooks/usePreviewMode.ts b/src/hooks/usePreviewMode.ts deleted file mode 100644 index a5b2ead..0000000 --- a/src/hooks/usePreviewMode.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { useState, useCallback } from 'react'; -import { MappedPixel } from '../utils/pixelation'; - -export type PreviewTool = 'view' | 'paint' | 'erase' | 'replace'; - -export function usePreviewMode( - pixelGrid: MappedPixel[][], - onPixelGridUpdate: (newGrid: MappedPixel[][]) => void -) { - const [selectedTool, setSelectedTool] = useState('view'); - const [selectedColorKey, setSelectedColorKey] = useState(''); - const [highlightColorKey, setHighlightColorKey] = useState(null); - const [replaceModeState, setReplaceModeState] = useState<{ - isActive: boolean; - sourceColor?: string; - }>({ isActive: false }); - - // 获取连通区域(用于洪水填充) - const getConnectedRegion = useCallback(( - startX: number, - startY: number, - targetKey: string - ): Array<{ x: number; y: number }> => { - const visited = new Set(); - const region: Array<{ x: number; y: number }> = []; - const stack = [{ x: startX, y: startY }]; - - while (stack.length > 0) { - const { x, y } = stack.pop()!; - const key = `${x},${y}`; - - if (visited.has(key)) continue; - visited.add(key); - - if ( - x < 0 || x >= pixelGrid[0].length || - y < 0 || y >= pixelGrid.length || - pixelGrid[y][x].key !== targetKey || - pixelGrid[y][x].isExternal - ) { - continue; - } - - region.push({ x, y }); - - // 添加相邻格子 - stack.push({ x: x + 1, y }); - stack.push({ x: x - 1, y }); - stack.push({ x, y: y + 1 }); - stack.push({ x, y: y - 1 }); - } - - return region; - }, [pixelGrid]); - - // 处理单元格点击 - const handleCellClick = useCallback((pixel: MappedPixel, x: number, y: number) => { - if (!pixel || pixel.isExternal) return; - - switch (selectedTool) { - case 'view': - // 仅查看,不做操作 - break; - - case 'paint': - if (selectedColorKey && pixel.key !== selectedColorKey) { - const newGrid = pixelGrid.map(row => [...row]); - newGrid[y][x] = { - ...pixel, - key: selectedColorKey, - color: selectedColorKey - }; - onPixelGridUpdate(newGrid); - } - break; - - case 'erase': - // 洪水填充擦除 - const region = getConnectedRegion(x, y, pixel.key); - if (region.length > 0) { - const newGrid = pixelGrid.map(row => [...row]); - region.forEach(cell => { - newGrid[cell.y][cell.x] = { - ...newGrid[cell.y][cell.x], - key: 'transparent', - color: '#ffffff' - }; - }); - onPixelGridUpdate(newGrid); - } - break; - - case 'replace': - if (replaceModeState.isActive && replaceModeState.sourceColor) { - // 执行颜色替换 - if (selectedColorKey && pixel.key !== selectedColorKey) { - const newGrid = pixelGrid.map(row => [...row]); - for (let y = 0; y < newGrid.length; y++) { - for (let x = 0; x < newGrid[0].length; x++) { - if (newGrid[y][x].key === replaceModeState.sourceColor) { - newGrid[y][x] = { - ...newGrid[y][x], - key: selectedColorKey, - color: selectedColorKey - }; - } - } - } - onPixelGridUpdate(newGrid); - setReplaceModeState({ isActive: false }); - } - } else { - // 选择源颜色 - setReplaceModeState({ - isActive: true, - sourceColor: pixel.key - }); - } - break; - } - }, [selectedTool, selectedColorKey, replaceModeState, pixelGrid, getConnectedRegion, onPixelGridUpdate]); - - // 切换高亮颜色 - const toggleHighlight = useCallback((colorKey: string) => { - setHighlightColorKey(prev => prev === colorKey ? null : colorKey); - }, []); - - // 重置工具状态 - const resetToolState = useCallback(() => { - setReplaceModeState({ isActive: false }); - }, []); - - return { - // 状态 - selectedTool, - selectedColorKey, - highlightColorKey, - replaceModeState, - - // 方法 - setSelectedTool, - setSelectedColorKey, - handleCellClick, - toggleHighlight, - resetToolState, - }; -} \ No newline at end of file