1
+ //Notice for core code of the floodfill, which has since then been heavily
2
+ //changed.
1
3
//Copyright(c) Max Irwin - 2011, 2015, 2016
2
4
//Repo: https://github.com/binarymax/floodfill.js
3
5
//MIT License
4
6
5
7
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 ;
8
10
9
11
//Fill coordinates are out of bounds
10
12
if ( i < 0 || i >= length ) {
@@ -13,25 +15,25 @@ function floodfillData(data, x, y, fillcolor, width, height) {
13
15
14
16
//We check whether the target pixel is already the desired color, since
15
17
//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 ] ] ;
17
19
if (
18
20
targetcolor [ 0 ] === fillcolor . r &&
19
21
targetcolor [ 1 ] === fillcolor . g &&
20
22
targetcolor [ 2 ] === fillcolor . b ) {
21
23
return false ;
22
24
}
23
25
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 ;
26
28
27
29
//Previously we used Array.push and Array.pop here, with which the method
28
30
//took between 70ms and 80ms on a rather strong machine with a FULL HD monitor.
29
31
//Since Q can never be required to be bigger than the amount of maximum
30
32
//pixels (width*height), we preallocate Q with that size. While not all of
31
33
//the space might be needed, this is cheaper than reallocating multiple times.
32
34
//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 ;
35
37
Q [ nextQIndex ++ ] = i ;
36
38
37
39
while ( nextQIndex > 0 ) {
@@ -83,3 +85,171 @@ function floodfillUint8ClampedArray(data, x, y, color, width, height) {
83
85
84
86
return floodfillData ( data , xi , yi , color , width , height ) ;
85
87
} ;
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