Skip to content

Commit f9b8820

Browse files
committed
Add ellipse filled code.
1 parent 8d99590 commit f9b8820

File tree

2 files changed

+134
-21
lines changed

2 files changed

+134
-21
lines changed

src/pg/inputPixelEditor/inputPixelEditor.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import template from './inputPixelEditor.html';
44
import style from './inputPixelEditor.css';
55
import { InputMode } from './utils/inputMode';
66
import cloneGrid from './utils/cloneGrid';
7+
import getEllipsePixels from './utils/getEllipsePixels';
78
import getEllipseOutlinePixels from './utils/getEllipseOutlinePixels';
89
import { WHITE } from './utils/constants';
910
import getLinePixels from './utils/getLinePixels';
@@ -489,7 +490,7 @@ export default class PgInputPixelEditor extends HTMLElement {
489490
});
490491
break;
491492
case InputMode.Ellipse:
492-
getEllipseOutlinePixels(this.#startX, this.#startY, newX, newY).forEach(({ x, y }) => {
493+
getEllipsePixels(this.#startX, this.#startY, newX, newY).forEach(({ x, y }) => {
493494
this.#setPixel(x, y, 1);
494495
});
495496
break;
@@ -571,7 +572,7 @@ export default class PgInputPixelEditor extends HTMLElement {
571572
this.#setPreview(getRectangleOutlinePixels(startX, startY, lastX, lastY), x, y);
572573
break;
573574
case InputMode.Ellipse:
574-
this.#setPreview(getEllipseOutlinePixels(startX, startY, lastX, lastY), x, y);
575+
this.#setPreview(getEllipsePixels(startX, startY, lastX, lastY), x, y);
575576
break;
576577
case InputMode.EllipseOutline:
577578
this.#setPreview(getEllipseOutlinePixels(startX, startY, lastX, lastY), x, y);
Lines changed: 131 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,140 @@
1-
export default function getEllipsePixels(x0: number, y0: number, x1: number, y1: number) {
2-
let a = Math.abs(x1 - x0), b = Math.abs(y1 - y0), b1 = b & 1; /* values of diameter */
3-
let dx = 4 * (1 - a) * b * b, dy = 4 * (b1 + 1) * a * a; /* error increment */
4-
let err = dx + dy + b1 * a * a, e2; /* error of 1.step */
5-
6-
if (x0 > x1) { x0 = x1; x1 += a; } /* if called with swapped points */
7-
if (y0 > y1) y0 = y1; /* .. exchange them */
8-
y0 += (b + 1) / 2; y1 = y0 - b1; /* starting pixel */
9-
a *= 8 * a; b1 = 8 * b * b;
1+
export function distance(x: number, y: number, ratio: number): number {
2+
return Math.sqrt((Math.pow(y * ratio, 2)) + Math.pow(x, 2));
3+
}
4+
5+
function filled(x: number, y: number, radius: number, ratio: number): boolean {
6+
return distance(x, y, ratio) <= radius;
7+
}
8+
9+
function thinfilled(x: number, y: number, radius: number, ratio: number): boolean {
10+
return filled(x, y, radius, ratio) && !(
11+
filled(x + 1, y, radius, ratio) &&
12+
filled(x - 1, y, radius, ratio) &&
13+
filled(x, y + 1, radius, ratio) &&
14+
filled(x, y - 1, radius, ratio)
15+
);
16+
}
17+
18+
function isFilled(x: number, y: number, width: number, height: number): boolean {
19+
const bounds = {
20+
minX: 0,
21+
maxX: width,
22+
minY: 0,
23+
maxY: height,
24+
};
25+
26+
x = -.5 * (bounds.maxX - 2 * (x + .5));
27+
y = -.5 * (bounds.maxY - 2 * (y + .5));
28+
29+
return thinfilled(x, y, (bounds.maxX / 2), bounds.maxX / bounds.maxY);
30+
}
31+
32+
33+
function betterCircle(x0: number, y0: number, x1: number, y1: number) {
34+
const width = Math.abs(x0 - x1);
35+
const height = Math.abs(y0 - y1);
36+
const minX = Math.min(x0, x1);
37+
const minY = Math.min(y0, y1);
38+
1039
const pixels: { x: number, y: number }[] = [];
40+
41+
// Loop through bounding box
42+
for (let y = 0; y < height; y++) {
43+
for (let x = 0; x < width; x++) {
44+
// Center coordinates relative to ellipse
45+
const cx = -.5 * (width - 2 * (x + 0.5));
46+
const cy = -.5 * (height - 2 * (y + 0.5));
47+
48+
// Use filled() instead of thinfilled() to fill the ellipse
49+
if (filled(cx, cy, width / 2, width / height)) {
50+
pixels.push({ x: x + minX, y: y + minY });
51+
}
52+
}
53+
}
54+
55+
return pixels;
56+
}
57+
58+
59+
function ellipse(x0: number, y0: number, x1: number, y1: number) {
60+
const pixels: { x: number, y: number }[] = [];
61+
62+
let a = Math.abs(x1 - x0),
63+
b = Math.abs(y1 - y0),
64+
b1 = b & 1;
65+
66+
let dx = 4 * (1.0 - a) * b * b,
67+
dy = 4 * (b1 + 1) * a * a;
68+
69+
let err = dx + dy + b1 * a * a,
70+
e2;
71+
72+
if (x0 > x1) {
73+
x0 = x1;
74+
x1 += a;
75+
}
76+
if (y0 > y1) y0 = y1;
77+
y0 += (b + 1) >> 1;
78+
y1 = y0 - b1;
79+
80+
a = 8 * a * a;
81+
b1 = 8 * b * b;
82+
83+
// Outline drawing
1184
do {
12-
pixels.push({ x: x1, y: y0 }); /* I. Quadrant */
13-
pixels.push({ x: x0, y: y0 }); /* II. Quadrant */
14-
pixels.push({ x: x0, y: y1 }); /* III. Quadrant */
15-
pixels.push({ x: x1, y: y1 }); /* IV. Quadrant */
85+
pixels.push({ x: x1, y: y0 }); // I Quadrant
86+
pixels.push({ x: x0, y: y0 }); // II Quadrant
87+
pixels.push({ x: x0, y: y1 }); // III Quadrant
88+
pixels.push({ x: x1, y: y1 }); // IV Quadrant
89+
1690
e2 = 2 * err;
17-
if (e2 <= dy) { y0++; y1--; err += dy += a; } /* y step */
18-
if (e2 >= dx || 2 * err > dy) { x0++; x1--; err += dx += b1; } /* x step */
91+
if (e2 <= dy) {
92+
y0++;
93+
y1--;
94+
err += dy += a;
95+
}
96+
if (e2 >= dx || 2 * err > dy) {
97+
x0++;
98+
x1--;
99+
err += dx += b1;
100+
}
19101
} while (x0 <= x1);
20102

21-
while (y0 - y1 < b) { /* too early stop of flat ellipses a=1 */
22-
pixels.push({ x: x0 - 1, y: y0 }); /* -> finish tip of ellipse */
103+
while (y0 - y1 <= b) {
104+
pixels.push({ x: x0 - 1, y: y0 });
23105
pixels.push({ x: x1 + 1, y: y0++ });
24106
pixels.push({ x: x0 - 1, y: y1 });
25107
pixels.push({ x: x1 + 1, y: y1-- });
26108
}
27-
return pixels;
28-
}
109+
110+
// --- Fill interior ---
111+
// Group pixels by row (y), then fill between minX and maxX
112+
const rows: Record<number, { minX: number; maxX: number }> = {};
113+
for (const p of pixels) {
114+
if (!(p.y in rows)) {
115+
rows[p.y] = { minX: p.x, maxX: p.x };
116+
} else {
117+
rows[p.y].minX = Math.min(rows[p.y].minX, p.x);
118+
rows[p.y].maxX = Math.max(rows[p.y].maxX, p.x);
119+
}
120+
}
121+
122+
const filledPixels: { x: number; y: number }[] = [...pixels];
123+
for (const y in rows) {
124+
const { minX, maxX } = rows[y];
125+
for (let x = minX; x <= maxX; x++) {
126+
filledPixels.push({ x, y: parseInt(y) });
127+
}
128+
}
129+
130+
return filledPixels;
131+
}
132+
133+
134+
export default function getEllipsePixels(x0: number, y0: number, x1: number, y1: number) {
135+
if (Math.abs(x0 - x1) === Math.abs(y0 - y1) && Math.abs(x0 - x1)) {
136+
console.log('circle', Math.abs(x0 - x1), Math.abs(y0 - y1))
137+
return betterCircle(x0, y0, x1 + 1, y1 + 1);
138+
}
139+
return ellipse(x0, y0, x1, y1);
140+
}

0 commit comments

Comments
 (0)