Skip to content

Commit e1c7c0f

Browse files
committed
Add grid customization options to Pixel2CPP and PixelCanvas
Implemented grid size, line width, opacity, and offset controls in Pixel2CPP. Enhanced PixelCanvas to render the grid with improved visibility and sectioning based on user-defined parameters. Updated grid drawing logic to support both standard and section grids, ensuring better usability for pixel art creation.
1 parent 33f8219 commit e1c7c0f

File tree

2 files changed

+167
-20
lines changed

2 files changed

+167
-20
lines changed

src/Pixel2CPP.jsx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export default function Pixel2CPP() {
1414
const [h, setH] = useState(64);
1515
const [zoom, setZoom] = useState(8); // pixel size in CSS px
1616
const [showGrid, setShowGrid] = useState(true);
17+
const [gridSize, setGridSize] = useState(1); // Grid spacing (every N pixels)
18+
const [gridLineWidth, setGridLineWidth] = useState(1); // Grid line thickness
19+
const [gridOpacity, setGridOpacity] = useState(0.15); // Grid opacity
20+
const [gridOffset, setGridOffset] = useState({ x: 0, y: 0 }); // Grid offset
1721
const [mirrorX, setMirrorX] = useState(false);
1822
const [mirrorY, setMirrorY] = useState(false);
1923
const [tool, setTool] = useState("pen"); // pen | erase | fill | eyedropper
@@ -1012,6 +1016,109 @@ const GFXfont ${safeName} PROGMEM = {
10121016
<label className="flex items-center gap-1"><input type="checkbox" checked={mirrorX} onChange={(e) => setMirrorX(e.target.checked)} />Mirror X</label>
10131017
<label className="flex items-center gap-1"><input type="checkbox" checked={mirrorY} onChange={(e) => setMirrorY(e.target.checked)} />Mirror Y</label>
10141018
</div>
1019+
1020+
{/* Grid customization controls */}
1021+
{showGrid && (
1022+
<div className="space-y-2 w-full border-t border-neutral-700 pt-2">
1023+
<div className="flex items-center justify-between">
1024+
<label className="block text-xs font-medium text-neutral-300">Grid Settings</label>
1025+
<button
1026+
onClick={() => {
1027+
setGridSize(1);
1028+
setGridLineWidth(1);
1029+
setGridOpacity(0.15);
1030+
setGridOffset({ x: 0, y: 0 });
1031+
}}
1032+
className="px-2 py-1 rounded bg-neutral-800 hover:bg-neutral-700 text-xs"
1033+
title="Reset grid settings to defaults"
1034+
>
1035+
Reset
1036+
</button>
1037+
</div>
1038+
<div className="grid grid-cols-2 gap-2 text-sm">
1039+
<label className="flex items-center gap-1">
1040+
Spacing
1041+
<input
1042+
type="number"
1043+
min={1}
1044+
max={16}
1045+
value={gridSize}
1046+
onChange={(e) => setGridSize(clamp(parseInt(e.target.value) || 1, 1, 16))}
1047+
className="w-12 bg-neutral-800 rounded px-1 py-0.5 text-xs"
1048+
title="Grid line spacing (pixels between lines)"
1049+
/>
1050+
</label>
1051+
<div className="flex items-center gap-1">
1052+
<label className="flex items-center gap-1">
1053+
Opacity
1054+
<input
1055+
type="range"
1056+
min={0.01}
1057+
max={0.3}
1058+
step={0.01}
1059+
value={gridOpacity}
1060+
onChange={(e) => setGridOpacity(parseFloat(e.target.value))}
1061+
className="w-16"
1062+
title="Grid line opacity"
1063+
/>
1064+
</label>
1065+
<span className="text-xs text-neutral-400">{(gridOpacity * 100).toFixed(0)}%</span>
1066+
</div>
1067+
</div>
1068+
<div className="grid grid-cols-2 gap-2 text-sm">
1069+
<label className="flex items-center gap-1">
1070+
Offset X
1071+
<input
1072+
type="number"
1073+
min={-16}
1074+
max={16}
1075+
value={gridOffset.x}
1076+
onChange={(e) => setGridOffset(prev => ({ ...prev, x: clamp(parseInt(e.target.value) || 0, -16, 16) }))}
1077+
className="w-12 bg-neutral-800 rounded px-1 py-0.5 text-xs"
1078+
title="Horizontal grid offset"
1079+
/>
1080+
</label>
1081+
<label className="flex items-center gap-1">
1082+
Offset Y
1083+
<input
1084+
type="number"
1085+
min={-16}
1086+
max={16}
1087+
value={gridOffset.y}
1088+
onChange={(e) => setGridOffset(prev => ({ ...prev, y: clamp(parseInt(e.target.value) || 0, -16, 16) }))}
1089+
className="w-12 bg-neutral-800 rounded px-1 py-0.5 text-xs"
1090+
title="Vertical grid offset"
1091+
/>
1092+
</label>
1093+
</div>
1094+
<div className="flex gap-1 flex-wrap">
1095+
<button
1096+
onClick={() => { setGridSize(1); setGridOpacity(0.15); }}
1097+
className="px-2 py-1 rounded bg-neutral-800 hover:bg-neutral-700 text-xs"
1098+
title="Standard 1x1 pixel grid"
1099+
>
1100+
1x1
1101+
</button>
1102+
<button
1103+
onClick={() => { setGridSize(8); setGridOpacity(0.25); }}
1104+
className="px-2 py-1 rounded bg-neutral-800 hover:bg-neutral-700 text-xs"
1105+
title="8x8 pixel grid (common for 8-bit sprites)"
1106+
>
1107+
8x8
1108+
</button>
1109+
<button
1110+
onClick={() => { setGridSize(16); setGridOpacity(0.3); }}
1111+
className="px-2 py-1 rounded bg-neutral-800 hover:bg-neutral-700 text-xs"
1112+
title="16x16 pixel grid (common for tiles)"
1113+
>
1114+
16x16
1115+
</button>
1116+
</div>
1117+
<div className="text-xs text-neutral-500">
1118+
Tip: Use spacing &gt; 1 to create grid sections. Offsets shift the grid position.
1119+
</div>
1120+
</div>
1121+
)}
10151122
<div className="space-y-2 w-full">
10161123
<label className="block text-sm font-medium">Draw mode:</label>
10171124
<select value={drawMode} onChange={(e) => setDrawMode(e.target.value)} className="w-full bg-neutral-800 rounded px-2 py-1 text-sm">
@@ -1083,6 +1190,10 @@ const GFXfont ${safeName} PROGMEM = {
10831190
zoom={zoom}
10841191
pixels={data}
10851192
showGrid={showGrid}
1193+
gridSize={gridSize}
1194+
gridLineWidth={gridLineWidth}
1195+
gridOpacity={gridOpacity}
1196+
gridOffset={gridOffset}
10861197
cursor={tool === "eyedropper" ? "crosshair" : "pointer"}
10871198
onPointerDown={handleMouseDown}
10881199
onPointerMove={handleMouseMove}

src/components/PixelCanvas.jsx

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ export default function PixelCanvas({
77
zoom,
88
pixels,
99
showGrid,
10+
gridSize = 1,
11+
gridLineWidth = 0.5,
12+
gridOpacity = 0.3,
13+
gridOffset = { x: 0, y: 0 },
1014
cursor,
1115
onPointerDown,
1216
onPointerMove,
@@ -59,31 +63,63 @@ export default function PixelCanvas({
5963
ctx.putImageData(imageData, 0, 0);
6064
ctx.imageSmoothingEnabled = false;
6165

62-
// Draw grid in logical pixels (will scale with CSS)
66+
// Draw grid to clearly show individual pixels
6367
if (showGrid) {
64-
ctx.strokeStyle = "rgba(255,255,255,0.06)";
65-
ctx.lineWidth = 1;
66-
for (let x = 1; x < width; x++) {
67-
ctx.beginPath();
68-
ctx.moveTo(x + 0.5, 0);
69-
ctx.lineTo(x + 0.5, height);
70-
ctx.stroke();
68+
// Use a more visible grid with better contrast
69+
ctx.strokeStyle = `rgba(255,255,255,${Math.max(gridOpacity, 0.3)})`;
70+
ctx.lineWidth = Math.max(gridLineWidth, 0.5); // Make lines more visible
71+
72+
if (gridSize === 1) {
73+
// Standard pixel grid - draw lines to clearly separate each pixel
74+
// Draw vertical lines at pixel boundaries
75+
for (let x = 0; x <= width; x++) {
76+
ctx.beginPath();
77+
ctx.moveTo(x, 0);
78+
ctx.lineTo(x, height);
79+
ctx.stroke();
80+
}
81+
82+
// Draw horizontal lines at pixel boundaries
83+
for (let y = 0; y <= height; y++) {
84+
ctx.beginPath();
85+
ctx.moveTo(0, y);
86+
ctx.lineTo(width, y);
87+
ctx.stroke();
88+
}
89+
} else {
90+
// Section grid - draw lines at section boundaries
91+
for (let x = 0; x <= width; x += gridSize) {
92+
const xPos = x + gridOffset.x;
93+
if (xPos >= 0 && xPos <= width) {
94+
ctx.beginPath();
95+
ctx.moveTo(xPos, 0);
96+
ctx.lineTo(xPos, height);
97+
ctx.stroke();
98+
}
99+
}
100+
101+
for (let y = 0; y <= height; y += gridSize) {
102+
const yPos = y + gridOffset.y;
103+
if (yPos >= 0 && yPos <= height) {
104+
ctx.beginPath();
105+
ctx.moveTo(0, yPos);
106+
ctx.lineTo(width, yPos);
107+
ctx.stroke();
108+
}
109+
}
71110
}
72-
for (let y = 1; y < height; y++) {
73-
ctx.beginPath();
74-
ctx.moveTo(0, y + 0.5);
75-
ctx.lineTo(width, y + 0.5);
76-
ctx.stroke();
77-
}
78-
ctx.strokeStyle = "rgba(255,255,255,0.08)";
79-
ctx.strokeRect(0.5, 0.5, width - 1, height - 1);
111+
112+
// Draw border around the entire canvas
113+
ctx.strokeStyle = `rgba(255,255,255,${Math.min(gridOpacity * 1.5, 0.4)})`;
114+
ctx.lineWidth = Math.max(gridLineWidth * 1.5, 0.75); // Slightly thicker border
115+
ctx.strokeRect(0, 0, width, height);
80116
} else {
81-
const prev = ctx.strokeStyle;
117+
// Draw only the border when grid is disabled
82118
ctx.strokeStyle = "rgba(255,255,255,0.08)";
83-
ctx.strokeRect(0.5, 0.5, width - 1, height - 1);
84-
ctx.strokeStyle = prev;
119+
ctx.lineWidth = 0.05;
120+
ctx.strokeRect(0, 0, width, height);
85121
}
86-
}, [pixels, width, height, zoom, showGrid]);
122+
}, [pixels, width, height, zoom, showGrid, gridSize, gridLineWidth, gridOpacity, gridOffset]);
87123

88124
// Also ensure the canvas style updates are applied correctly
89125
useEffect(() => {

0 commit comments

Comments
 (0)