Skip to content

Commit 24ece08

Browse files
committed
Still buggy
1 parent 3f1e80c commit 24ece08

File tree

5 files changed

+145
-2
lines changed

5 files changed

+145
-2
lines changed

src/pg/inputPixelEditor/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ See usage for each method below.
4646
| `removeLayer(index)` | - | Remove layer. |
4747
| `flattenLayers(layerIndexes)` | - | Flatten layers. |
4848
| `moveLayer(startIndex, endIndex)` | - | Move layer. |
49+
| `getColor()` | - | Get selected color |
4950
| `getColors()` | - | Get colors. |
51+
| `selectColor(index)` | - | Select color. |
5052
| `setColor(index, r, g, b, a)` | - | Set Color. |
5153
| `addColor(r, g, b, a)` | - | Add color. |
5254
| `removeColor(index)` | - | Remove color. |
@@ -57,6 +59,7 @@ See usage for each method below.
5759
| `flipHorizontal()` | - | Flip horizontal. |
5860
| `flipVertical()` | - | Flip vertical. |
5961
| `invert()` | - | Invert layer. |
62+
| `outline()` | - | Outline layer with selected color. |
6063

6164
### `save(options)` Method
6265

@@ -101,7 +104,7 @@ A complete JSON storage for a 10x10 image.
101104
{
102105
"width": 10,
103106
"height": 10,
104-
"transparent": true,
107+
"transparent": [0],
105108
"colors": [
106109
[0, 0, 0, 0],
107110
[0, 0, 0, 1],

src/pg/inputPixelEditor/__examples__/basic/basic.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ <h3>Canvas Tools</h3>
2929
<button part="reset">Reset</button>
3030
<button part="clear">Clear</button>
3131
<button part="invert">Invert</button>
32+
<button part="outline">Outline</button>
3233
|
3334
<button part="save">Save</button>
3435
<button part="open">Open</button>

src/pg/inputPixelEditor/__examples__/basic/basic.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default class XPgInputPixelEditorBasic extends HTMLElement {
3131
@Part() $reset: HTMLButtonElement;
3232
@Part() $clear: HTMLButtonElement;
3333
@Part() $invert: HTMLButtonElement;
34+
@Part() $outline: HTMLButtonElement;
3435
@Part() $modeStamp1: HTMLButtonElement;
3536
@Part() $modeStamp2: HTMLButtonElement;
3637
@Part() $modeStamp3: HTMLButtonElement;
@@ -104,6 +105,9 @@ export default class XPgInputPixelEditorBasic extends HTMLElement {
104105
this.$invert.addEventListener('click', () => {
105106
this.$input.invert();
106107
});
108+
this.$outline.addEventListener('click', () => {
109+
this.$input.outline();
110+
});
107111
this.$save.addEventListener('click', async () => {
108112
const json = await this.$input.save();
109113
this.$output.textContent = JSON.stringify(json, null, 4);

src/pg/inputPixelEditor/inputPixelEditor.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import bitmaskToPath from './utils/bitmapToMask';
1616
import createLayer from './utils/createLayer';
1717
import diffGrid from './utils/diffGrid';
1818
import { getGuides } from './utils/getGuides';
19+
import { getOutline } from './utils/getOutline';
1920

2021
type Color = [number, number, number, number];
2122

@@ -1076,6 +1077,16 @@ export default class PgInputPixelEditor extends HTMLElement {
10761077
this.#data.splice(index, 1);
10771078
}
10781079

1080+
/**
1081+
* Outline.
1082+
*/
1083+
outline() {
1084+
const pixels = getOutline(this.#data[this.#layer]);
1085+
pixels.forEach(([x, y]) => {
1086+
this.#setPixel(x, y, this.#color);
1087+
});
1088+
}
1089+
10791090
/**
10801091
* Flatten layers, first index determines the color
10811092
*/

src/pg/inputPixelEditor/utils/getOutline.ts

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function getOutline(grid) {
1+
/*export function getOutline(grid) {
22
const outline: number[][] = [];
33
const rows = grid.length;
44
const cols = grid[0].length;
@@ -35,5 +35,129 @@ function getOutline(grid) {
3535
}
3636
}
3737
38+
return outline;
39+
} */
40+
41+
export function getOutline(grid) {
42+
const rows = grid.length;
43+
const cols = grid[0].length;
44+
45+
// ------------------------------------------------------------
46+
// 1. Flood-fill background reachable from outside
47+
// ------------------------------------------------------------
48+
const outside = Array.from({ length: rows }, () => Array(cols).fill(false));
49+
const queue: number[][] = [];
50+
51+
// Add border 0-cells
52+
for (let x = 0; x < cols; x++) {
53+
if (grid[0][x] === 0) queue.push([x, 0]);
54+
if (grid[rows - 1][x] === 0) queue.push([x, rows - 1]);
55+
}
56+
for (let y = 0; y < rows; y++) {
57+
if (grid[y][0] === 0) queue.push([0, y]);
58+
if (grid[y][cols - 1] === 0) queue.push([cols - 1, y]);
59+
}
60+
61+
// BFS flood fill
62+
while (queue.length) {
63+
const [x, y] = queue.shift() as number[];
64+
if (outside[y][x]) continue;
65+
outside[y][x] = true;
66+
67+
const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
68+
for (const [dx, dy] of dirs) {
69+
const nx = x + dx, ny = y + dy;
70+
if (
71+
nx >= 0 && nx < cols &&
72+
ny >= 0 && ny < rows &&
73+
grid[ny][nx] === 0 &&
74+
!outside[ny][nx]
75+
) {
76+
queue.push([nx, ny]);
77+
}
78+
}
79+
}
80+
81+
// ------------------------------------------------------------
82+
// 2. Collect boundary cells (unordered)
83+
// ------------------------------------------------------------
84+
const boundary = new Set();
85+
const key = (x, y) => `${x},${y}`;
86+
87+
for (let y = 0; y < rows; y++) {
88+
for (let x = 0; x < cols; x++) {
89+
if (grid[y][x] !== 1) continue;
90+
91+
const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
92+
for (const [dx, dy] of dirs) {
93+
const nx = x + dx, ny = y + dy;
94+
95+
if (
96+
nx < 0 || nx >= cols ||
97+
ny < 0 || ny >= rows ||
98+
(grid[ny][nx] === 0 && outside[ny][nx])
99+
) {
100+
boundary.add(key(x, y));
101+
break;
102+
}
103+
}
104+
}
105+
}
106+
107+
if (boundary.size === 0) return [];
108+
109+
// ------------------------------------------------------------
110+
// 3. SAFE OUTLINE WALK (no infinite loops)
111+
// ------------------------------------------------------------
112+
113+
// Convert boundary set to a fast lookup
114+
const isBoundary = (x, y) => boundary.has(key(x, y));
115+
116+
// Find a starting boundary cell
117+
const [startKey] = boundary;
118+
// @ts-ignore
119+
const [sx, sy] = startKey.split(',').map(Number);
120+
121+
const outline: number[][] = [];
122+
const visited = new Set();
123+
let current = { x: sx, y: sy };
124+
125+
// Moore-neighborhood directions (8 directions)
126+
const dirs8 = [
127+
[1,0], [1,1], [0,1], [-1,1],
128+
[-1,0], [-1,-1], [0,-1], [1,-1]
129+
];
130+
131+
const maxSteps = rows * cols * 8;
132+
133+
for (let step = 0; step < maxSteps; step++) {
134+
outline.push([current.x, current.y]);
135+
visited.add(key(current.x, current.y));
136+
137+
// Try to find next boundary neighbor
138+
let next = null;
139+
for (const [dx, dy] of dirs8) {
140+
const nx = current.x + dx;
141+
const ny = current.y + dy;
142+
if (isBoundary(nx, ny) && !visited.has(key(nx, ny))) {
143+
// @ts-ignore
144+
next = { x: nx, y: ny };
145+
break;
146+
}
147+
}
148+
149+
// If no next step → open contour (C-shape)
150+
if (!next) break;
151+
152+
// If next is start → closed loop
153+
// @ts-ignore
154+
if (next.x === sx && next.y === sy) {
155+
outline.push([sx, sy]);
156+
break;
157+
}
158+
159+
current = next;
160+
}
161+
38162
return outline;
39163
}

0 commit comments

Comments
 (0)