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
57function 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