PatchHub

โ† patchhub/pixel-paint

Fill bucket tool for the pixel art app

Published โœ“ Safe by @maya 0 users1 vote 2 files changed (+46 โˆ’8)

Adds a ๐Ÿชฃ Fill button to the toolbar. Click it, then click a square to color that square and every connected square of the same color with your selected color. Right-clicking with Fill empties the area instead.

What it does

- Add a toolbar button labeled "๐Ÿชฃ Fill" (id fillBtn, class "tool") placed between the Brush and Eraser buttons. - Track a "filling" mode alongside the existing brush/eraser modes. Clicking Fill sets filling on and erasing off. Clicking Brush or Eraser turns filling off. The Fill button shows the "active" style only when filling and not erasing; Brush is active only when neither erasing nor filling. - Keep an in-memory array of 32ร—32 pixel values (a color string such as "#ff0000" or the ERASE/empty value), kept in sync with every cell's displayed background. All painting, erasing and clearing must update this array through a single set-pixel helper. Clear sets every pixel to empty. - Normalize selected colors to lowercase so that color comparisons are consistent. - On pointer down in Fill mode (and not Eraser mode): flood-fill starting at the clicked cell. Replace the clicked cell's current value and all 4-directionally connected cells (up, down, left, right; diagonals do NOT connect) that hold the same value with the fill value. Do nothing if the clicked cell already has the fill value. Do not start a drag-paint; Fill acts on a single click only. - The fill value is the selected color for a left click, and empty for a right click (right-click empties the area, as right-click erases with the Brush). - Choosing a palette or custom color keeps Fill mode on, because it only turns off erasing. - There is no undo. - Change the hint under the grid to: "Click or drag to paint. Right-click (or hold the Eraser) to erase. Use Fill to color a whole area at once." - Add a README bullet: "Fill: click the Fill button, then click a square to fill it and every touching square of the same color with your chosen color. Right-click with Fill to empty an area instead. Click Brush to go back to painting."

Safety check โœ“ Safe

The changes are limited to the page's own drawing logic, the toolbar button, the hint text and the README. There are no network requests, new dependencies, or access to files or personal data.

Conversation that created this patch
@maya
Add a fill bucket tool that fills an area with the selected color.
AI
I've added a **๐Ÿชฃ Fill** button to the toolbar, between Brush and Eraser. To use it, click Fill and then click a square on the grid. That square and every touching square of the same color change to your selected color. Lines of a different color act as borders, so you can draw an outline with the Brush and fill it in with one click. Squares that only touch at the corners don't count as connected, so a diagonal line still holds the color in. A few things to know: - **Right-clicking** with Fill empties an area instead of coloring it, the same way right-click erases with the Brush. - **Picking a new color** keeps you in Fill mode, so you can fill several areas in different colors one after another. Click Brush to go back to normal painting. - **There's no undo** (the app didn't have one before), so a click on the wrong spot can't be taken back. I checked it with a quick automated test that filled one side of a drawn line, and the color stopped at the line as it should. I haven't tried it in a real browser, so it's worth a quick go yourself. I also added a sentence about Fill to the hint under the grid and to the README instructions.
Code changes (for the curious, 2 files) ยท raw
README.md +1 โˆ’0
@@ -11,6 +11,7 @@ A small pixel art drawing app that runs in your web browser. Everything lives in
- **Pick a color** by clicking one of the squares in the palette, or choose any color with **Custom**.
- **Paint** by clicking a square on the 32ร—32 grid, or click and drag to paint many squares at once.
+- **Fill**: click the Fill button, then click a square to fill it and every touching square of the same color with your chosen color. Right-click with Fill to empty an area instead. Click **Brush** to go back to painting.
- **Eraser**: click the Eraser button, then click or drag to erase. Clicking with the right mouse button also erases. Click **Brush** to go back to painting.
- **Grid lines**: turns the faint lines between squares on or off.
- **Clear**: wipes the whole drawing. It asks you first, just in case.
index.html +45 โˆ’8
@@ -127,6 +127,7 @@
Custom: <input type="color" id="customColor" value="#ff8800">
</label>
<button class="tool" id="brushBtn" type="button">โœ๏ธ Brush</button>
+ <button class="tool" id="fillBtn" type="button">๐Ÿชฃ Fill</button>
<button class="tool" id="eraserBtn" type="button">๐Ÿงฝ Eraser</button>
<button class="tool" id="linesBtn" type="button">โ–ฆ Grid lines</button>
<button class="tool" id="clearBtn" type="button">๐Ÿ—‘๏ธ Clear</button>
@@ -134,7 +135,7 @@
<div id="grid" class="show-lines"></div>
โˆ’ <p class="hint">Click or drag to paint. Right-click (or hold the Eraser) to erase.</p>
+ <p class="hint">Click or drag to paint. Right-click (or hold the Eraser) to erase. Use Fill to color a whole area at once.</p>
<script>
(function () {
@@ -156,14 +157,18 @@
const customColor = document.getElementById("customColor");
const brushBtn = document.getElementById("brushBtn");
const eraserBtn = document.getElementById("eraserBtn");
+ const fillBtn = document.getElementById("fillBtn");
const linesBtn = document.getElementById("linesBtn");
const clearBtn = document.getElementById("clearBtn");
let color = COLORS[0];
let erasing = false;
+ let filling = false;
let painting = false;
let paintValue = color;
const cells = [];
+ // What each square holds: a color like "#ff0000", or ERASE for empty
+ const pixels = new Array(SIZE * SIZE).fill(ERASE);
// Build the grid
for (let i = 0; i < SIZE * SIZE; i++) {
@@ -190,7 +195,8 @@
swatches.forEach(function (s, i) {
s.classList.toggle("selected", !erasing && COLORS[i] === color);
});
โˆ’ brushBtn.classList.toggle("active", !erasing);
+ brushBtn.classList.toggle("active", !erasing && !filling);
+ fillBtn.classList.toggle("active", !erasing && filling);
eraserBtn.classList.toggle("active", erasing);
if (erasing) {
currentBox.style.background = "";
@@ -202,14 +208,40 @@
}
function selectColor(c) {
โˆ’ color = c;
+ color = c.toLowerCase();
erasing = false;
updateUI();
}
+ function setPixel(i, value) {
+ pixels[i] = value;
+ cells[i].style.background = value === ERASE ? "" : value;
+ }
+
function paintCell(cell) {
if (!cell || !cell.classList.contains("cell")) return;
โˆ’ cell.style.background = paintValue === ERASE ? "" : paintValue;
+ setPixel(Number(cell.dataset.index), paintValue);
+ }
+
+ // Fill the connected area of same-colored squares (touching up, down,
+ // left or right) starting from the clicked square.
+ function fillFrom(cell, value) {
+ if (!cell || !cell.classList.contains("cell")) return;
+ const start = Number(cell.dataset.index);
+ const target = pixels[start];
+ if (target === value) return;
+ const stack = [start];
+ while (stack.length) {
+ const i = stack.pop();
+ if (pixels[i] !== target) continue;
+ setPixel(i, value);
+ const x = i % SIZE;
+ const y = (i - x) / SIZE;
+ if (x > 0) stack.push(i - 1);
+ if (x < SIZE - 1) stack.push(i + 1);
+ if (y > 0) stack.push(i - SIZE);
+ if (y < SIZE - 1) stack.push(i + SIZE);
+ }
}
function cellAt(x, y) {
@@ -220,9 +252,13 @@
// Painting with mouse, pen or touch
gridEl.addEventListener("pointerdown", function (e) {
e.preventDefault();
โˆ’ painting = true;
// Right mouse button always erases
paintValue = (erasing || e.button === 2) ? ERASE : color;
+ if (filling && !erasing) {
+ fillFrom(cellAt(e.clientX, e.clientY), paintValue);
+ return;
+ }
+ painting = true;
paintCell(cellAt(e.clientX, e.clientY));
});
@@ -244,15 +280,16 @@
// Tools
customColor.addEventListener("input", function () { selectColor(customColor.value); });
โˆ’ brushBtn.addEventListener("click", function () { erasing = false; updateUI(); });
โˆ’ eraserBtn.addEventListener("click", function () { erasing = true; updateUI(); });
+ brushBtn.addEventListener("click", function () { erasing = false; filling = false; updateUI(); });
+ fillBtn.addEventListener("click", function () { erasing = false; filling = true; updateUI(); });
+ eraserBtn.addEventListener("click", function () { erasing = true; filling = false; updateUI(); });
linesBtn.addEventListener("click", function () {
gridEl.classList.toggle("show-lines");
linesBtn.classList.toggle("active", gridEl.classList.contains("show-lines"));
});
clearBtn.addEventListener("click", function () {
if (confirm("Clear the whole drawing?")) {
โˆ’ cells.forEach(function (c) { c.style.background = ""; });
+ cells.forEach(function (c, i) { setPixel(i, ERASE); });
}
});