diff --git a/src/app/page.tsx b/src/app/page.tsx index fc497d3..c0fb212 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -121,6 +121,24 @@ import GridTooltip from '../components/GridTooltip'; import CustomPaletteEditor from '../components/CustomPaletteEditor'; import { loadPaletteSelections, savePaletteSelections, presetToSelections, PaletteSelections } from '../utils/localStorageUtils'; +// ++ 添加/定义网格下载选项类型 ++ +type GridDownloadOptions = { + showGrid: boolean; + gridInterval: number; + showCoordinates: boolean; + gridLineColor: string; // 新增网格线颜色字段 +}; + +// ++ 定义可选的网格线颜色 ++ +const gridLineColorOptions = [ + { name: '深灰色', value: '#555555' }, + { name: '红色', value: '#FF0000' }, + { name: '蓝色', value: '#0000FF' }, + { name: '绿色', value: '#008000' }, + { name: '紫色', value: '#800080' }, + { name: '橙色', value: '#FFA500' }, +]; + export default function Home() { const [originalImageSrc, setOriginalImageSrc] = useState(null); const [granularity, setGranularity] = useState(50); @@ -153,6 +171,15 @@ export default function Home() { const [customPaletteSelections, setCustomPaletteSelections] = useState({}); const [isCustomPaletteEditorOpen, setIsCustomPaletteEditorOpen] = useState(false); const [isCustomPalette, setIsCustomPalette] = useState(false); + + // ++ 新增:下载设置相关状态 ++ + const [isDownloadSettingsOpen, setIsDownloadSettingsOpen] = useState(false); + const [downloadOptions, setDownloadOptions] = useState({ + showGrid: true, + gridInterval: 10, + showCoordinates: true, + gridLineColor: gridLineColorOptions[0].value, // 默认使用第一个颜色 + }); const originalCanvasRef = useRef(null); const pixelatedCanvasRef = useRef(null); @@ -657,18 +684,24 @@ export default function Home() { }, [originalImageSrc, granularity, similarityThreshold, customPaletteSelections, pixelationMode, remapTrigger]); // --- Download function (ensure filename includes palette) --- - const handleDownloadImage = () => { + const handleDownloadImage = (options?: GridDownloadOptions) => { if (!mappedPixelData || !gridDimensions || gridDimensions.N === 0 || gridDimensions.M === 0 || activeBeadPalette.length === 0) { console.error("下载失败: 映射数据或尺寸无效。"); alert("无法下载图纸,数据未生成或无效。"); return; } const { N, M } = gridDimensions; const downloadCellSize = 30; - const axisLabelSize = 20; // Space for axis labels - const gridLineColor = '#DDDDDD'; // Light grid lines - const thickGridLineColor = '#AAAAAA'; // Thicker grid lines - const gridInterval = 10; // For thicker grid lines (e.g., 10x10) - - // Adjust canvas size to include axis labels + + // 使用传入的options或者当前的downloadOptions + const currentOptions = options || downloadOptions; + + // 根据当前下载选项确定是否需要绘制网格和坐标 + const { showGrid, gridInterval, showCoordinates, gridLineColor } = currentOptions; + + // 设置边距空间用于坐标轴标注(如果需要) + const axisLabelSize = showCoordinates ? 20 : 0; + // const gridLineColor = '#555555'; // 深色网格线 - 由 downloadOptions.gridLineColor 替代 + + // 调整画布大小 const downloadWidth = N * downloadCellSize + axisLabelSize; const downloadHeight = M * downloadCellSize + axisLabelSize; @@ -679,7 +712,7 @@ export default function Home() { if (!ctx) { console.error("下载失败: 无法创建临时 Canvas Context。"); alert("无法下载图纸。"); return; } ctx.imageSmoothingEnabled = false; - // Set a default background color for the entire canvas + // 设置背景色 ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, downloadWidth, downloadHeight); @@ -689,37 +722,40 @@ export default function Home() { ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - // Draw Axis Labels (Numbers) - ctx.fillStyle = '#333333'; // Text color for axis labels - const axisFontSize = Math.max(10, Math.floor(axisLabelSize * 0.6)); - ctx.font = `${axisFontSize}px sans-serif`; + // 如果需要,绘制坐标轴数字 + if (showCoordinates) { + ctx.fillStyle = '#333333'; // 坐标数字颜色 + const axisFontSize = Math.max(10, Math.floor(axisLabelSize * 0.6)); + ctx.font = `${axisFontSize}px sans-serif`; - // Top axis (X-axis numbers) - for (let i = 0; i < N; i++) { - if ((i + 1) % gridInterval === 0 || i === 0 || i === N -1) { // Label at intervals, and first/last - ctx.fillText((i + 1).toString(), axisLabelSize + (i * downloadCellSize) + (downloadCellSize / 2), axisLabelSize / 2); + // X轴(顶部)数字 + for (let i = 0; i < N; i++) { + if ((i + 1) % gridInterval === 0 || i === 0 || i === N - 1) { // 在间隔处、起始处和结束处标注 + ctx.fillText((i + 1).toString(), axisLabelSize + (i * downloadCellSize) + (downloadCellSize / 2), axisLabelSize / 2); + } } - } - // Left axis (Y-axis numbers) - for (let j = 0; j < M; j++) { - if ((j + 1) % gridInterval === 0 || j === 0 || j === M-1 ) { // Label at intervals, and first/last - ctx.fillText((j + 1).toString(), axisLabelSize / 2, axisLabelSize + (j * downloadCellSize) + (downloadCellSize / 2)); + // Y轴(左侧)数字 + for (let j = 0; j < M; j++) { + if ((j + 1) % gridInterval === 0 || j === 0 || j === M - 1) { // 在间隔处、起始处和结束处标注 + ctx.fillText((j + 1).toString(), axisLabelSize / 2, axisLabelSize + (j * downloadCellSize) + (downloadCellSize / 2)); + } } + + // 重设字体以绘制单元格内的内容 + ctx.font = `bold ${fontSize}px sans-serif`; } - // Reset font for cell keys - ctx.font = `bold ${fontSize}px sans-serif`; - + // 绘制所有单元格 for (let j = 0; j < M; j++) { for (let i = 0; i < N; i++) { const cellData = mappedPixelData[j][i]; - // Offset drawing by axisLabelSize + // 由于坐标轴的存在,需要偏移绘制位置 const drawX = i * downloadCellSize + axisLabelSize; const drawY = j * downloadCellSize + axisLabelSize; - // Determine fill color based on whether it's external background + // 根据是否是外部背景确定填充颜色 if (cellData && !cellData.isExternal) { - // Internal cell: fill with bead color and draw text + // 内部单元格:使用珠子颜色填充并绘制文本 const cellColor = cellData.color || '#FFFFFF'; const cellKey = cellData.key || '?'; @@ -728,36 +764,52 @@ export default function Home() { ctx.fillStyle = getContrastColor(cellColor); ctx.fillText(cellKey, drawX + downloadCellSize / 2, drawY + downloadCellSize / 2); - } else { - // External cell: fill with white (or leave transparent if background wasn't filled) - // No text needed for external background - ctx.fillStyle = '#FFFFFF'; // Ensure background cells are white + // 外部背景:填充白色 + ctx.fillStyle = '#FFFFFF'; ctx.fillRect(drawX, drawY, downloadCellSize, downloadCellSize); } - // Draw border for ALL cells - // Determine grid line color - ctx.strokeStyle = ((i + 1) % gridInterval === 0 || (j + 1) % gridInterval === 0) && (i < N && j < M) - ? thickGridLineColor - : gridLineColor; - ctx.lineWidth = ((i + 1) % gridInterval === 0 || (j + 1) % gridInterval === 0) ? 1.5 : 1; - - // Use precise coordinates for sharp lines + // 绘制所有单元格的边框 + ctx.strokeStyle = '#DDDDDD'; // 浅色线条作为基础网格 + ctx.lineWidth = 0.5; ctx.strokeRect(drawX + 0.5, drawY + 0.5, downloadCellSize, downloadCellSize); } } - // Draw main border around the grid area - ctx.strokeStyle = '#000000'; // Black border for the main grid - ctx.lineWidth = 1; - ctx.strokeRect(axisLabelSize + 0.5, axisLabelSize + 0.5, N * downloadCellSize, M * downloadCellSize); + // 如果需要,绘制分隔网格线 + if (showGrid) { + ctx.strokeStyle = gridLineColor; // 使用用户选择的颜色 + ctx.lineWidth = 1.5; + + // 绘制垂直分隔线 - 在单元格之间而不是边框上 + for (let i = gridInterval; i < N; i += gridInterval) { + const lineX = i * downloadCellSize + axisLabelSize; + ctx.beginPath(); + ctx.moveTo(lineX, axisLabelSize); + ctx.lineTo(lineX, axisLabelSize + M * downloadCellSize); + ctx.stroke(); + } + + // 绘制水平分隔线 - 在单元格之间而不是边框上 + for (let j = gridInterval; j < M; j += gridInterval) { + const lineY = j * downloadCellSize + axisLabelSize; + ctx.beginPath(); + ctx.moveTo(axisLabelSize, lineY); + ctx.lineTo(axisLabelSize + N * downloadCellSize, lineY); + ctx.stroke(); + } + } + // 绘制整个网格区域的主边框 + ctx.strokeStyle = '#000000'; // 黑色边框 + ctx.lineWidth = 1.5; + ctx.strokeRect(axisLabelSize + 0.5, axisLabelSize + 0.5, N * downloadCellSize, M * downloadCellSize); try { const dataURL = downloadCanvas.toDataURL('image/png'); const link = document.createElement('a'); - link.download = `bead-grid-${N}x${M}-keys-palette_${selectedPaletteKeySet}.png`; // Filename includes palette + link.download = `bead-grid-${N}x${M}-keys-palette_${selectedPaletteKeySet}.png`; // 文件名包含调色板 link.href = dataURL; document.body.appendChild(link); link.click(); document.body.removeChild(link); console.log("Grid image download initiated."); @@ -1204,6 +1256,166 @@ export default function Home() { importPaletteInputRef.current?.click(); }; + // ++ 添加下载设置弹窗组件 ++ + const DownloadSettingsModal = ({ + isOpen, + onClose, + options, + onOptionsChange, + onDownload + }: { + isOpen: boolean, + onClose: () => void, + options: GridDownloadOptions, + onOptionsChange: (options: GridDownloadOptions) => void, + onDownload: (opts?: GridDownloadOptions) => void // 修改onDownload类型以接受可选参数 + }) => { + if (!isOpen) return null; + + // 临时状态用于表单输入 + const [tempOptions, setTempOptions] = useState({...options}); + + // 处理选项变更 + const handleOptionChange = (key: keyof GridDownloadOptions, value: any) => { + setTempOptions(prev => ({ + ...prev, + [key]: value + })); + }; + + // 保存选项并立即使用新设置下载 + const handleSave = () => { + // 更新父组件中的设置状态(虽然下载时不依赖这个更新) + onOptionsChange(tempOptions); + + // 直接使用当前临时设置下载,不依赖状态更新 + onDownload(tempOptions); + + onClose(); + }; + + return ( +
+
+
+
+

下载图纸设置

+ +
+ +
+ {/* 显示网格线选项 */} +
+ + +
+ + {/* 网格线设置 (仅当显示网格线时) */} + {tempOptions.showGrid && ( +
+ {/* 网格线间隔选项 */} +
+ +
+ handleOptionChange('gridInterval', parseInt(e.target.value))} + className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700" + /> + + {tempOptions.gridInterval} + +
+
+ + {/* 网格线颜色选择 */} +
+ +
+ {gridLineColorOptions.map(colorOpt => ( + + ))} +
+
+
+ )} + + {/* 显示坐标选项 */} +
+ + +
+
+ +
+ + +
+
+
+
+ ); + }; + return ( <> {/* 添加自定义动画样式 */} @@ -1736,9 +1948,9 @@ export default function Home() { {/* ++ HIDE Download Buttons in manual mode ++ */} {!isManualColoringMode && originalImageSrc && mappedPixelData && (
- {/* Download Grid Button - Keeping styles bright */} + {/* Download Grid Button - 现在打开设置弹窗而不是直接下载 */}
)} + + {/* 添加下载设置弹窗 */} + setIsDownloadSettingsOpen(false)} + options={downloadOptions} + onOptionsChange={setDownloadOptions} + onDownload={handleDownloadImage} + /> );