编辑、去除杂色

This commit is contained in:
zihanjian
2025-06-30 20:40:34 +08:00
parent b8c7559548
commit bfc1daa55e
4 changed files with 234 additions and 43 deletions

View File

@@ -31,7 +31,8 @@ import PreviewToolbar from '../../components/PreviewToolbar';
import EditToolbar from '../../components/EditToolbar';
import ColorSystemPanel from '../../components/ColorSystemPanel';
import CanvasColorPanel from '../../components/CanvasColorPanel';
import { getColorKeyByHex, ColorSystem, getMardToHexMapping, getAllHexValues } from '../../utils/colorSystemUtils';
import ManualColoringPanel from '../../components/ManualColoringPanel';
import { getColorKeyByHex, ColorSystem, getAllHexValues } from '../../utils/colorSystemUtils';
import DownloadSettingsModal from '../../components/DownloadSettingsModal';
import { downloadImage } from '../../utils/imageDownloader';
import { GridDownloadOptions } from '../../types/downloadTypes';
@@ -137,7 +138,7 @@ export default function FocusMode() {
const clickedColorTimerRef = useRef<NodeJS.Timeout | null>(null);
// 编辑模式状态
const [editTool, setEditTool] = useState<'select' | 'wand'>('select');
const [editTool] = useState<'select' | 'wand'>('select');
const [selectedCells, setSelectedCells] = useState<Set<string>>(new Set());
const [isSelecting, setIsSelecting] = useState(false);
const [selectionStart, setSelectionStart] = useState<{ row: number; col: number } | null>(null);
@@ -149,6 +150,11 @@ export default function FocusMode() {
const [canvasPalette, setCanvasPalette] = useState<Set<string>>(new Set());
const [removedColors, setRemovedColors] = useState<string[]>([]);
// 手动上色模式状态
const [showManualColoringPanel, setShowManualColoringPanel] = useState(false);
const [manualColoringTool, setManualColoringTool] = useState<'brush' | 'eraser'>('brush');
const [manualColoringColor, setManualColoringColor] = useState<string>('');
// 下载相关状态
const [showDownloadModal, setShowDownloadModal] = useState(false);
const [downloadOptions, setDownloadOptions] = useState<GridDownloadOptions>({
@@ -161,9 +167,9 @@ export default function FocusMode() {
});
// 计算状态
const hasSelection = selectedCells.size > 0;
const canUndo = historyIndex > 0;
const canRedo = historyIndex < history.length - 1;
// const hasSelection = selectedCells.size > 0;
// const canUndo = historyIndex > 0;
// const canRedo = historyIndex < history.length - 1;
// 在客户端加载保存的设置
useEffect(() => {
@@ -447,6 +453,28 @@ export default function FocusMode() {
}));
}, [calculateRecommendedRegion]);
// 编辑模式:保存历史记录
const saveToHistory = useCallback(() => {
if (!mappedPixelData) return;
// 如果不在历史末尾,删除后面的历史
const newHistory = history.slice(0, historyIndex + 1);
// 深拷贝当前状态
const currentState = mappedPixelData.map(row => row.map(pixel => ({ ...pixel })));
// 添加到历史
newHistory.push(currentState);
// 限制历史记录数量
if (newHistory.length > 50) {
newHistory.shift();
}
setHistory(newHistory);
setHistoryIndex(newHistory.length - 1);
}, [mappedPixelData, history, historyIndex]);
// 处理格子点击 - 根据不同模式执行不同操作
const handleCellClick = useCallback((row: number, col: number) => {
if (!mappedPixelData) return;
@@ -567,6 +595,52 @@ export default function FocusMode() {
return;
}
// 手动上色模式:直接修改颜色
if (showManualColoringPanel) {
// 保存历史(内联逻辑)
if (mappedPixelData) {
// 如果不在历史末尾,删除后面的历史
const newHistory = history.slice(0, historyIndex + 1);
// 深拷贝当前状态
const currentState = mappedPixelData.map(row => row.map(pixel => ({ ...pixel })));
// 添加到历史
newHistory.push(currentState);
// 限制历史记录数量
if (newHistory.length > 50) {
newHistory.shift();
}
setHistory(newHistory);
setHistoryIndex(newHistory.length - 1);
}
const newPixelData = mappedPixelData.map(r => r.map(pixel => ({ ...pixel })));
if (manualColoringTool === 'brush' && manualColoringColor) {
// 画笔模式:使用选中的颜色
newPixelData[row][col] = {
...newPixelData[row][col],
color: manualColoringColor,
key: manualColoringColor,
isExternal: false
};
} else if (manualColoringTool === 'eraser') {
// 橡皮擦模式:设为透明
newPixelData[row][col] = {
...newPixelData[row][col],
color: 'transparent',
key: 'transparent',
isExternal: true
};
}
setMappedPixelData(newPixelData);
return;
}
// 编辑模式:处理选择
if (focusState.editMode === 'edit') {
if (editTool === 'select') {
@@ -603,7 +677,7 @@ export default function FocusMode() {
setSelectedCells(newSelection);
}
}
}, [mappedPixelData, focusState, editTool, isSelecting, selectedColorSystem]);
}, [mappedPixelData, focusState, editTool, isSelecting, selectedColorSystem, showManualColoringPanel, manualColoringTool, manualColoringColor, saveToHistory]);
// 处理颜色切换
const handleColorChange = useCallback((color: string) => {
@@ -993,28 +1067,6 @@ export default function FocusMode() {
img.src = imageSrc;
}, [gridWidth, pixelationMode, colorMergeThreshold, removeBackground, selectedColor, selectedColorSystem, customPalette]);
// 编辑模式:保存历史记录
const saveToHistory = useCallback(() => {
if (!mappedPixelData) return;
// 如果不在历史末尾,删除后面的历史
const newHistory = history.slice(0, historyIndex + 1);
// 深拷贝当前状态
const currentState = mappedPixelData.map(row => row.map(pixel => ({ ...pixel })));
// 添加到历史
newHistory.push(currentState);
// 限制历史记录数量
if (newHistory.length > 50) {
newHistory.shift();
}
setHistory(newHistory);
setHistoryIndex(newHistory.length - 1);
}, [mappedPixelData, history, historyIndex]);
// 编辑模式:撤销
const handleUndo = useCallback(() => {
if (historyIndex > 0) {
@@ -1063,6 +1115,21 @@ export default function FocusMode() {
setShowCanvasColorPanel(true);
}, [mappedPixelData, getCanvasColors]);
// 手动上色:启动模式
const handleManualColoring = useCallback(() => {
if (!mappedPixelData) return;
// 获取当前画布颜色
const canvasColors = getCanvasColors();
// 设置默认选中第一个颜色(如果有的话)
if (canvasColors.length > 0 && !manualColoringColor) {
setManualColoringColor(canvasColors[0].hex);
}
setShowManualColoringPanel(true);
}, [mappedPixelData, getCanvasColors, manualColoringColor]);
// 去杂色:移除颜色
const handleRemoveCanvasColor = useCallback((hexToRemove: string) => {
if (!mappedPixelData) return;
@@ -1679,16 +1746,13 @@ export default function FocusMode() {
/>
)}
{/* 编辑模式工具栏 - 仅在非去杂色模式时显示 */}
{focusState.editMode === 'edit' && !showCanvasColorPanel && (
{/* 编辑模式工具栏 - 仅在非去杂色和非手动上色模式时显示 */}
{focusState.editMode === 'edit' && !showCanvasColorPanel && !showManualColoringPanel && (
<EditToolbar
selectedColor={selectedColor}
availableColors={availableColors}
onRemoveNoise={handleRemoveNoise}
onManualColoring={() => {
// TODO: 实现手动上色功能
console.log('手动上色功能');
}}
onManualColoring={handleManualColoring}
onColorSelect={setSelectedColor}
onShowColorPanel={() => setFocusState(prev => ({ ...prev, showColorPanel: true }))}
/>
@@ -1706,8 +1770,21 @@ export default function FocusMode() {
/>
)}
{/* 底部模式切换栏 - 去杂色模式时隐藏 */}
{!showCanvasColorPanel && (
{/* 手动上色面板 */}
{showManualColoringPanel && (
<ManualColoringPanel
canvasColors={getCanvasColors()}
selectedColorSystem={selectedColorSystem}
selectedTool={manualColoringTool}
selectedColor={manualColoringColor}
onToolChange={setManualColoringTool}
onColorSelect={setManualColoringColor}
onClose={() => setShowManualColoringPanel(false)}
/>
)}
{/* 底部模式切换栏 - 去杂色和手动上色模式时隐藏 */}
{!showCanvasColorPanel && !showManualColoringPanel && (
<div className="bg-white border-t border-gray-200 px-4 py-3">
<div className="flex bg-gray-100 rounded-lg p-1 max-w-md mx-auto">
<button

View File

@@ -8,12 +8,12 @@ interface ColorInfo {
}
interface EditToolbarProps {
selectedColor: string;
availableColors: ColorInfo[];
selectedColor?: string;
availableColors?: ColorInfo[];
onRemoveNoise: () => void;
onManualColoring: () => void;
onColorSelect: (color: string) => void;
onShowColorPanel: () => void;
onColorSelect?: (color: string) => void;
onShowColorPanel?: () => void;
}
const EditToolbar: React.FC<EditToolbarProps> = ({

View File

@@ -0,0 +1,114 @@
import React from 'react';
import { getColorKeyByHex, ColorSystem } from '../utils/colorSystemUtils';
interface CanvasColorInfo {
hex: string;
key: string;
count: number;
}
interface ManualColoringPanelProps {
canvasColors: CanvasColorInfo[];
selectedColorSystem: ColorSystem;
selectedTool: 'brush' | 'eraser';
selectedColor: string;
onToolChange: (tool: 'brush' | 'eraser') => void;
onColorSelect: (color: string) => void;
onClose: () => void;
}
const ManualColoringPanel: React.FC<ManualColoringPanelProps> = ({
canvasColors,
selectedColorSystem,
selectedTool,
selectedColor,
onToolChange,
onColorSelect,
onClose
}) => {
return (
<div className="fixed inset-x-0 bottom-0 bg-white border-t border-gray-200 shadow-lg z-40 flex flex-col">
{/* 工具栏 */}
<div className="flex items-center justify-between px-4 py-2 border-b border-gray-100">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
<h3 className="text-sm font-medium text-gray-800"></h3>
</div>
{/* 工具选择 */}
<div className="flex items-center gap-1 bg-gray-100 rounded-lg p-1">
<button
onClick={() => onToolChange('brush')}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm transition-colors ${
selectedTool === 'brush'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-800'
}`}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
</button>
<button
onClick={() => onToolChange('eraser')}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm transition-colors ${
selectedTool === 'eraser'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-800'
}`}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
<button
onClick={onClose}
className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors"
>
<svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* 颜色选择区域 - 仅在画笔模式下显示 */}
{selectedTool === 'brush' && (
<div className="px-3 py-2 max-h-32 overflow-y-auto">
<div className="flex flex-wrap gap-1">
{canvasColors.map(color => {
const isSelected = selectedColor === color.hex;
return (
<button
key={color.hex}
onClick={() => onColorSelect(color.hex)}
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-md text-xs transition-all ${
isSelected
? 'bg-blue-100 border-blue-300 border-2'
: 'bg-gray-50 border border-gray-200 hover:bg-gray-100'
}`}
>
{/* 颜色块 */}
<div
className="w-3 h-3 rounded border border-gray-300"
style={{ backgroundColor: color.hex }}
/>
{/* 色号 */}
<span className="font-mono text-gray-700">
{getColorKeyByHex(color.hex, selectedColorSystem)}
</span>
</button>
);
})}
</div>
</div>
)}
</div>
);
};
export default ManualColoringPanel;

View File

@@ -13,9 +13,9 @@ interface PreviewToolbarProps {
colorMergeThreshold: number;
pixelationMode: PixelationMode;
removeBackground: boolean;
availableColors: ColorInfo[];
mappedPixelData: MappedPixel[][] | null;
isProcessing: boolean;
availableColors?: ColorInfo[];
mappedPixelData?: MappedPixel[][] | null;
isProcessing?: boolean;
onGridWidthChange: (width: number) => void;
onColorMergeThresholdChange: (threshold: number) => void;
onPixelationModeChange: (mode: PixelationMode) => void;