Skip to content

Commit c5651bd

Browse files
committed
Move drawing related code into draw.js
1 parent 31036a1 commit c5651bd

File tree

2 files changed

+185
-195
lines changed

2 files changed

+185
-195
lines changed

internal/frontend/resources/draw.js

Lines changed: 177 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
//Notice for core code of the floodfill, which has since then been heavily
2+
//changed.
13
//Copyright(c) Max Irwin - 2011, 2015, 2016
24
//Repo: https://github.com/binarymax/floodfill.js
35
//MIT License
46

57
function floodfillData(data, x, y, fillcolor, width, height) {
6-
var length = data.length;
7-
var i = (x + y * width) * 4;
8+
const length = data.length;
9+
let i = (x + y * width) * 4;
810

911
//Fill coordinates are out of bounds
1012
if (i < 0 || i >= length) {
@@ -13,25 +15,25 @@ function floodfillData(data, x, y, fillcolor, width, height) {
1315

1416
//We check whether the target pixel is already the desired color, since
1517
//filling wouldn't change any of the pixels in this case.
16-
var targetcolor = [data[i], data[i + 1], data[i + 2]];
18+
const targetcolor = [data[i], data[i + 1], data[i + 2]];
1719
if (
1820
targetcolor[0] === fillcolor.r &&
1921
targetcolor[1] === fillcolor.g &&
2022
targetcolor[2] === fillcolor.b) {
2123
return false;
2224
}
2325

24-
var e = i, w = i, me, mw, w2 = width * 4;
25-
var j;
26+
let e = i, w = i, me, mw, w2 = width * 4;
27+
let j;
2628

2729
//Previously we used Array.push and Array.pop here, with which the method
2830
//took between 70ms and 80ms on a rather strong machine with a FULL HD monitor.
2931
//Since Q can never be required to be bigger than the amount of maximum
3032
//pixels (width*height), we preallocate Q with that size. While not all of
3133
//the space might be needed, this is cheaper than reallocating multiple times.
3234
//This improved the time from 70ms-80ms to 50ms-60ms.
33-
var Q = new Array(width * height);
34-
var nextQIndex = 0;
35+
const Q = new Array(width * height);
36+
let nextQIndex = 0;
3537
Q[nextQIndex++] = i;
3638

3739
while (nextQIndex > 0) {
@@ -83,3 +85,171 @@ function floodfillUint8ClampedArray(data, x, y, color, width, height) {
8385

8486
return floodfillData(data, xi, yi, color, width, height);
8587
};
88+
89+
// Code for line drawing, not related to the floodfill repo.
90+
// Hence it's all BSD licensed.
91+
92+
function drawLine(context, imageData, x1, y1, x2, y2, color, width) {
93+
const coords = prepareDrawLineCoords(context, x1, y1, x2, y2, width);
94+
_drawLineNoPut(imageData, coords, color, width);
95+
context.putImageData(imageData, 0, 0, 0, 0, coords.right, coords.bottom);
96+
};
97+
98+
// This implementation directly access the canvas data and does not
99+
// put it back into the canvas context directly. This saved us not
100+
// only from calling put, which is relatively cheap, but also from
101+
// calling getImageData all the time.
102+
function drawLineNoPut(context, imageData, x1, y1, x2, y2, color, width) {
103+
_drawLineNoPut(imageData, prepareDrawLineCoords(context, x1, y1, x2, y2, width), color, width);
104+
};
105+
106+
function _drawLineNoPut(imageData, coords, color, width) {
107+
const { x1, y1, x2, y2, left, top, right, bottom } = coords;
108+
109+
// off canvas, so don't draw anything
110+
if (right - left === 0 || bottom - top === 0) {
111+
return;
112+
}
113+
114+
const circleMap = generateCircleMap(Math.floor(width / 2));
115+
const offset = Math.floor(circleMap.length / 2);
116+
117+
for (let ix = 0; ix < circleMap.length; ix++) {
118+
for (let iy = 0; iy < circleMap[ix].length; iy++) {
119+
if (circleMap[ix][iy] === 1 || (x1 === x2 && y1 === y2 && circleMap[ix][iy] === 2)) {
120+
const newX1 = x1 + ix - offset;
121+
const newY1 = y1 + iy - offset;
122+
const newX2 = x2 + ix - offset;
123+
const newY2 = y2 + iy - offset;
124+
drawBresenhamLine(imageData, newX1, newY1, newX2, newY2, color);
125+
}
126+
}
127+
}
128+
}
129+
130+
function prepareDrawLineCoords(context, x1, y1, x2, y2, width) {
131+
// the coordinates must be whole numbers to improve performance.
132+
// also, decimals as coordinates is not making sense.
133+
x1 = Math.floor(x1);
134+
y1 = Math.floor(y1);
135+
x2 = Math.floor(x2);
136+
y2 = Math.floor(y2);
137+
138+
// calculate bounding box
139+
const left = Math.max(0, Math.min(context.canvas.width, Math.min(x1, x2) - width));
140+
const top = Math.max(0, Math.min(context.canvas.height, Math.min(y1, y2) - width));
141+
const right = Math.max(0, Math.min(context.canvas.width, Math.max(x1, x2) + width));
142+
const bottom = Math.max(0, Math.min(context.canvas.height, Math.max(y1, y2) + width));
143+
144+
return {
145+
x1: x1,
146+
y1: y1,
147+
x2: x2,
148+
y2: y2,
149+
left: left,
150+
top: top,
151+
right: right,
152+
bottom: bottom,
153+
};
154+
}
155+
function drawBresenhamLine(imageData, x1, y1, x2, y2, color) {
156+
const dx = Math.abs(x2 - x1);
157+
const dy = Math.abs(y2 - y1);
158+
const sx = (x1 < x2) ? 1 : -1;
159+
const sy = (y1 < y2) ? 1 : -1;
160+
let err = dx - dy;
161+
162+
while (true) {
163+
//check if pixel is inside the canvas
164+
if (!(x1 < 0 || x1 >= imageData.width || y1 < 0 || y1 >= imageData.height)) {
165+
setPixel(imageData, x1, y1, color);
166+
}
167+
168+
if ((x1 === x2) && (y1 === y2)) break;
169+
const e2 = 2 * err;
170+
if (e2 > -dy) {
171+
err -= dy;
172+
x1 += sx;
173+
}
174+
if (e2 < dx) {
175+
err += dx;
176+
y1 += sy;
177+
}
178+
}
179+
}
180+
181+
function generateCircleMap(radius) {
182+
const diameter = 2 * radius;
183+
const circleData = new Array(diameter);
184+
185+
for (let x = 0; x < diameter; x++) {
186+
circleData[x] = new Array(diameter);
187+
for (let y = 0; y < diameter; y++) {
188+
const distanceToRadius = Math.sqrt(Math.pow(radius - x, 2) + Math.pow(radius - y, 2));
189+
if (distanceToRadius > radius) {
190+
circleData[x][y] = 0;
191+
} else if (distanceToRadius < radius - 2) {
192+
circleData[x][y] = 2;
193+
} else {
194+
circleData[x][y] = 1;
195+
}
196+
}
197+
}
198+
199+
return circleData;
200+
}
201+
202+
function setPixel(imageData, x, y, color) {
203+
const offset = (y * imageData.width + x) * 4;
204+
imageData.data[offset] = color.r;
205+
imageData.data[offset + 1] = color.g;
206+
imageData.data[offset + 2] = color.b;
207+
}
208+
209+
//We accept both #RRGGBB and RRGGBB. Both are treated case insensitive.
210+
function hexStringToRgbColorObject(hexString) {
211+
if (!hexString) {
212+
return { r: 0, g: 0, b: 0 };
213+
}
214+
const hexColorsRegex = /#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})/i;
215+
const match = hexString.match(hexColorsRegex)
216+
return { r: parseInt(match[1], 16), g: parseInt(match[2], 16), b: parseInt(match[3], 16) };
217+
}
218+
219+
const colorMap = [
220+
{ hex: '#ffffff', rgb: hexStringToRgbColorObject('#ffffff') },
221+
{ hex: '#c1c1c1', rgb: hexStringToRgbColorObject('#c1c1c1') },
222+
{ hex: '#ef130b', rgb: hexStringToRgbColorObject('#ef130b') },
223+
{ hex: '#ff7100', rgb: hexStringToRgbColorObject('#ff7100') },
224+
{ hex: '#ffe400', rgb: hexStringToRgbColorObject('#ffe400') },
225+
{ hex: '#00cc00', rgb: hexStringToRgbColorObject('#00cc00') },
226+
{ hex: '#00b2ff', rgb: hexStringToRgbColorObject('#00b2ff') },
227+
{ hex: '#231fd3', rgb: hexStringToRgbColorObject('#231fd3') },
228+
{ hex: '#a300ba', rgb: hexStringToRgbColorObject('#a300ba') },
229+
{ hex: '#d37caa', rgb: hexStringToRgbColorObject('#d37caa') },
230+
{ hex: '#a0522d', rgb: hexStringToRgbColorObject('#a0522d') },
231+
{ hex: '#592f2a', rgb: hexStringToRgbColorObject('#592f2a') },
232+
{ hex: '#ecbcb4', rgb: hexStringToRgbColorObject('#ecbcb4') },
233+
{ hex: '#000000', rgb: hexStringToRgbColorObject('#000000') },
234+
{ hex: '#4c4c4c', rgb: hexStringToRgbColorObject('#4c4c4c') },
235+
{ hex: '#740b07', rgb: hexStringToRgbColorObject('#740b07') },
236+
{ hex: '#c23800', rgb: hexStringToRgbColorObject('#c23800') },
237+
{ hex: '#e8a200', rgb: hexStringToRgbColorObject('#e8a200') },
238+
{ hex: '#005510', rgb: hexStringToRgbColorObject('#005510') },
239+
{ hex: '#00569e', rgb: hexStringToRgbColorObject('#00569e') },
240+
{ hex: '#0e0865', rgb: hexStringToRgbColorObject('#0e0865') },
241+
{ hex: '#550069', rgb: hexStringToRgbColorObject('#550069') },
242+
{ hex: '#a75574', rgb: hexStringToRgbColorObject('#a75574') },
243+
{ hex: '#63300d', rgb: hexStringToRgbColorObject('#63300d') },
244+
{ hex: '#492f31', rgb: hexStringToRgbColorObject('#492f31') },
245+
{ hex: '#d1a3a4', rgb: hexStringToRgbColorObject('#d1a3a4') }
246+
];
247+
248+
function indexToHexColor(index) {
249+
return colorMap[index].hex;
250+
}
251+
252+
function indexToRgbColor(index) {
253+
return colorMap[index].rgb;
254+
}
255+

0 commit comments

Comments
 (0)