├── .gitignore
├── LICENSE
├── README.md
├── index.html
├── lee.js
├── libs
├── tinycolor.js
└── tinygradient.js
├── package.json
└── spec
├── LeeSpec.js
└── support
└── jasmine.json
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 James Milner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # :runner: Lee Algorithm in JavaScript
2 |
3 | # Demo
4 | Check out a little demo [here](https://jameslmilner.github.io/lee-algorithm-js/) :)
5 |
6 | # What is it?
7 |
8 | A version of the [Lee Algorithm](https://en.wikipedia.org/wiki/Lee_algorithm) implemented in JavaScript. The Lee algorithm is a route finding algorithm that is suited to solving maze problems.
9 |
10 |
11 |
12 |
13 |
14 | # Background
15 |
16 | This implementation came out of the problem set at London Algorithms (which is a fantastic meetup by the way!) and was a lot of fun to try and code.
17 |
18 | # How To
19 |
20 | The implementation takes a matrix n * m, a starting cell and an end cell. It can return the completed matrix with the distances and also a single best path for the matrix.
21 |
22 | ```javascript
23 |
24 | var aMatrix = [
25 | [0, 0, 0, 0, 0],
26 | [0, -1, -1, -1, 0],
27 | [0, 0, -1, 0, -1],
28 | [-1, 0, 0, 0, -1],
29 | [-1, -1, -1, 0, -1]
30 | ];
31 |
32 | console.log("FINAL MATRIX : \n", pathfinder(aMatrix, 0, 0, 4, 3));
33 | console.log("BEST PATH : \n", backtrace(aMatrix, 0, 0, 4, 3));
34 |
35 | ```
36 |
37 | # Tests
38 |
39 | The test suite uses Jasmine. You can use:
40 |
41 | npm install
42 | jasmine
43 |
44 | to test the code.
45 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Maze Path Finding Using the Lee Algorithm
5 |
6 |
66 |
67 |
68 |
69 | Maze Path Finding
70 |
71 |
72 |
73 | Let us imagine the Maze as a matrix made up of 0s (empty space) and -1s (walls/obstacles) like so:
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | var aMatrix = [
82 | [0, 0, 0, 0, 0],
83 | [0, -1, -1, -1, 0],
84 | [0, 0, 0, 0, -1],
85 | [-1, 0, -1, 0, -1],
86 | [-1, -1, -1, 0, -1]
87 | ];
88 |
89 |
90 |
91 |
92 |
93 |
94 | The Lee Algorithm allows us to take an arbitary start and end cell within the matrix and attempt to calculate a viable route
95 | through between the two cells. In this case we are starting at 0,1 and aiming to get to 6,6.
96 | Try it below!
97 |
98 |
99 |
100 |
101 | Generate Maze
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
207 |
208 |
209 |
--------------------------------------------------------------------------------
/lee.js:
--------------------------------------------------------------------------------
1 |
2 | var lee = (function() {
3 |
4 | var pathfinder = function (matrix, x1, y1, x2, y2) {
5 |
6 | var toVisit = [[x1, y1]]; // Initialise at the start square
7 |
8 | while(toVisit.length) { // While there are still squares to visit
9 |
10 | x = toVisit[0][0];
11 | y = toVisit[0][1];
12 |
13 | for (var i = x-1; i < x+2; i++) { // -1, 0, 1
14 | for (var j = y-1; j < y+2; j++) { // -1, 0, 1
15 |
16 | if (neighbourCheck(matrix, i,j, x1, y1, 0)) {
17 |
18 | matrix[i][j] = matrix[x][y] + 1;
19 | toVisit.push([i, j]);
20 |
21 | }
22 |
23 | }
24 | }
25 |
26 | var shift = toVisit.shift();
27 |
28 | }
29 |
30 | var distance = matrix[x2][y2];
31 | return [matrix, distance];
32 |
33 | };
34 |
35 | var backtrace = function(matrix, x1, y1, x2, y2) {
36 |
37 | var previousValue = matrix[x2][y2];
38 | var successfulRoute = [];
39 |
40 | var x = x2;
41 | var y = y2;
42 |
43 | while ( !(x === x1 && y === y1) ) {
44 |
45 | for (var i = x-1; i < x+2; i++) { // -1, 0, 1
46 | for (var j = y-1; j < y+2; j++) { // -1, 0, 1
47 |
48 | if (
49 | matrix[i] && (matrix[i][j] === previousValue -1) && // If array x array defined and the matrix value is 0
50 | !(i === x && j === y) ) {
51 |
52 | previousValue = matrix[i][j];
53 | successfulRoute.push([i, j]);
54 | x = i;
55 | y = j;
56 |
57 | } else if (successfulRoute.length == matrix[x2][y2] - 1) { // If we got to the end of the route
58 | x = x1;
59 | y = y1; // Break the while loop
60 | }
61 |
62 | }
63 | }
64 |
65 | }
66 |
67 | successfulRoute.unshift([x2, y2]); // Add end point
68 | successfulRoute.push([x1, y1]); // Add start point
69 | return successfulRoute.reverse(); // Reverse the array so it's at the start
70 |
71 | };
72 |
73 | var neighbourCheck = function(matrix, i, j, x1, y1, value) {
74 | return matrix[i] && (matrix[i][j] === value) && // If array x array defined and the matrix value is 0
75 | !(i === x && j === y) && // If it's not the center square
76 | !(i === x1 && j === y1); // If it's not the first square
77 | };
78 |
79 | return {
80 | pathfinder : pathfinder,
81 | backtrace : backtrace,
82 | neighbourCheck : neighbourCheck
83 | };
84 |
85 | })();
86 |
--------------------------------------------------------------------------------
/libs/tinycolor.js:
--------------------------------------------------------------------------------
1 | // TinyColor v1.3.0
2 | // https://github.com/bgrins/TinyColor
3 | // Brian Grinstead, MIT License
4 |
5 | (function(Math) {
6 |
7 | var trimLeft = /^\s+/,
8 | trimRight = /\s+$/,
9 | tinyCounter = 0,
10 | mathRound = Math.round,
11 | mathMin = Math.min,
12 | mathMax = Math.max,
13 | mathRandom = Math.random;
14 |
15 | function tinycolor (color, opts) {
16 |
17 | color = (color) ? color : '';
18 | opts = opts || { };
19 |
20 | // If input is already a tinycolor, return itself
21 | if (color instanceof tinycolor) {
22 | return color;
23 | }
24 | // If we are called as a function, call using new instead
25 | if (!(this instanceof tinycolor)) {
26 | return new tinycolor(color, opts);
27 | }
28 |
29 | var rgb = inputToRGB(color);
30 | this._originalInput = color,
31 | this._r = rgb.r,
32 | this._g = rgb.g,
33 | this._b = rgb.b,
34 | this._a = rgb.a,
35 | this._roundA = mathRound(100*this._a) / 100,
36 | this._format = opts.format || rgb.format;
37 | this._gradientType = opts.gradientType;
38 |
39 | // Don't let the range of [0,255] come back in [0,1].
40 | // Potentially lose a little bit of precision here, but will fix issues where
41 | // .5 gets interpreted as half of the total, instead of half of 1
42 | // If it was supposed to be 128, this was already taken care of by `inputToRgb`
43 | if (this._r < 1) { this._r = mathRound(this._r); }
44 | if (this._g < 1) { this._g = mathRound(this._g); }
45 | if (this._b < 1) { this._b = mathRound(this._b); }
46 |
47 | this._ok = rgb.ok;
48 | this._tc_id = tinyCounter++;
49 | }
50 |
51 | tinycolor.prototype = {
52 | isDark: function() {
53 | return this.getBrightness() < 128;
54 | },
55 | isLight: function() {
56 | return !this.isDark();
57 | },
58 | isValid: function() {
59 | return this._ok;
60 | },
61 | getOriginalInput: function() {
62 | return this._originalInput;
63 | },
64 | getFormat: function() {
65 | return this._format;
66 | },
67 | getAlpha: function() {
68 | return this._a;
69 | },
70 | getBrightness: function() {
71 | //http://www.w3.org/TR/AERT#color-contrast
72 | var rgb = this.toRgb();
73 | return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
74 | },
75 | getLuminance: function() {
76 | //http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
77 | var rgb = this.toRgb();
78 | var RsRGB, GsRGB, BsRGB, R, G, B;
79 | RsRGB = rgb.r/255;
80 | GsRGB = rgb.g/255;
81 | BsRGB = rgb.b/255;
82 |
83 | if (RsRGB <= 0.03928) {R = RsRGB / 12.92;} else {R = Math.pow(((RsRGB + 0.055) / 1.055), 2.4);}
84 | if (GsRGB <= 0.03928) {G = GsRGB / 12.92;} else {G = Math.pow(((GsRGB + 0.055) / 1.055), 2.4);}
85 | if (BsRGB <= 0.03928) {B = BsRGB / 12.92;} else {B = Math.pow(((BsRGB + 0.055) / 1.055), 2.4);}
86 | return (0.2126 * R) + (0.7152 * G) + (0.0722 * B);
87 | },
88 | setAlpha: function(value) {
89 | this._a = boundAlpha(value);
90 | this._roundA = mathRound(100*this._a) / 100;
91 | return this;
92 | },
93 | toHsv: function() {
94 | var hsv = rgbToHsv(this._r, this._g, this._b);
95 | return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a };
96 | },
97 | toHsvString: function() {
98 | var hsv = rgbToHsv(this._r, this._g, this._b);
99 | var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
100 | return (this._a == 1) ?
101 | "hsv(" + h + ", " + s + "%, " + v + "%)" :
102 | "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")";
103 | },
104 | toHsl: function() {
105 | var hsl = rgbToHsl(this._r, this._g, this._b);
106 | return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a };
107 | },
108 | toHslString: function() {
109 | var hsl = rgbToHsl(this._r, this._g, this._b);
110 | var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
111 | return (this._a == 1) ?
112 | "hsl(" + h + ", " + s + "%, " + l + "%)" :
113 | "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")";
114 | },
115 | toHex: function(allow3Char) {
116 | return rgbToHex(this._r, this._g, this._b, allow3Char);
117 | },
118 | toHexString: function(allow3Char) {
119 | return '#' + this.toHex(allow3Char);
120 | },
121 | toHex8: function() {
122 | return rgbaToHex(this._r, this._g, this._b, this._a);
123 | },
124 | toHex8String: function() {
125 | return '#' + this.toHex8();
126 | },
127 | toRgb: function() {
128 | return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
129 | },
130 | toRgbString: function() {
131 | return (this._a == 1) ?
132 | "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" :
133 | "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
134 | },
135 | toPercentageRgb: function() {
136 | return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a };
137 | },
138 | toPercentageRgbString: function() {
139 | return (this._a == 1) ?
140 | "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" :
141 | "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
142 | },
143 | toName: function() {
144 | if (this._a === 0) {
145 | return "transparent";
146 | }
147 |
148 | if (this._a < 1) {
149 | return false;
150 | }
151 |
152 | return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
153 | },
154 | toFilter: function(secondColor) {
155 | var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a);
156 | var secondHex8String = hex8String;
157 | var gradientType = this._gradientType ? "GradientType = 1, " : "";
158 |
159 | if (secondColor) {
160 | var s = tinycolor(secondColor);
161 | secondHex8String = s.toHex8String();
162 | }
163 |
164 | return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")";
165 | },
166 | toString: function(format) {
167 | var formatSet = !!format;
168 | format = format || this._format;
169 |
170 | var formattedString = false;
171 | var hasAlpha = this._a < 1 && this._a >= 0;
172 | var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name");
173 |
174 | if (needsAlphaFormat) {
175 | // Special case for "transparent", all other non-alpha formats
176 | // will return rgba when there is transparency.
177 | if (format === "name" && this._a === 0) {
178 | return this.toName();
179 | }
180 | return this.toRgbString();
181 | }
182 | if (format === "rgb") {
183 | formattedString = this.toRgbString();
184 | }
185 | if (format === "prgb") {
186 | formattedString = this.toPercentageRgbString();
187 | }
188 | if (format === "hex" || format === "hex6") {
189 | formattedString = this.toHexString();
190 | }
191 | if (format === "hex3") {
192 | formattedString = this.toHexString(true);
193 | }
194 | if (format === "hex8") {
195 | formattedString = this.toHex8String();
196 | }
197 | if (format === "name") {
198 | formattedString = this.toName();
199 | }
200 | if (format === "hsl") {
201 | formattedString = this.toHslString();
202 | }
203 | if (format === "hsv") {
204 | formattedString = this.toHsvString();
205 | }
206 |
207 | return formattedString || this.toHexString();
208 | },
209 | clone: function() {
210 | return tinycolor(this.toString());
211 | },
212 |
213 | _applyModification: function(fn, args) {
214 | var color = fn.apply(null, [this].concat([].slice.call(args)));
215 | this._r = color._r;
216 | this._g = color._g;
217 | this._b = color._b;
218 | this.setAlpha(color._a);
219 | return this;
220 | },
221 | lighten: function() {
222 | return this._applyModification(lighten, arguments);
223 | },
224 | brighten: function() {
225 | return this._applyModification(brighten, arguments);
226 | },
227 | darken: function() {
228 | return this._applyModification(darken, arguments);
229 | },
230 | desaturate: function() {
231 | return this._applyModification(desaturate, arguments);
232 | },
233 | saturate: function() {
234 | return this._applyModification(saturate, arguments);
235 | },
236 | greyscale: function() {
237 | return this._applyModification(greyscale, arguments);
238 | },
239 | spin: function() {
240 | return this._applyModification(spin, arguments);
241 | },
242 |
243 | _applyCombination: function(fn, args) {
244 | return fn.apply(null, [this].concat([].slice.call(args)));
245 | },
246 | analogous: function() {
247 | return this._applyCombination(analogous, arguments);
248 | },
249 | complement: function() {
250 | return this._applyCombination(complement, arguments);
251 | },
252 | monochromatic: function() {
253 | return this._applyCombination(monochromatic, arguments);
254 | },
255 | splitcomplement: function() {
256 | return this._applyCombination(splitcomplement, arguments);
257 | },
258 | triad: function() {
259 | return this._applyCombination(triad, arguments);
260 | },
261 | tetrad: function() {
262 | return this._applyCombination(tetrad, arguments);
263 | }
264 | };
265 |
266 | // If input is an object, force 1 into "1.0" to handle ratios properly
267 | // String input requires "1.0" as input, so 1 will be treated as 1
268 | tinycolor.fromRatio = function(color, opts) {
269 | if (typeof color == "object") {
270 | var newColor = {};
271 | for (var i in color) {
272 | if (color.hasOwnProperty(i)) {
273 | if (i === "a") {
274 | newColor[i] = color[i];
275 | }
276 | else {
277 | newColor[i] = convertToPercentage(color[i]);
278 | }
279 | }
280 | }
281 | color = newColor;
282 | }
283 |
284 | return tinycolor(color, opts);
285 | };
286 |
287 | // Given a string or object, convert that input to RGB
288 | // Possible string inputs:
289 | //
290 | // "red"
291 | // "#f00" or "f00"
292 | // "#ff0000" or "ff0000"
293 | // "#ff000000" or "ff000000"
294 | // "rgb 255 0 0" or "rgb (255, 0, 0)"
295 | // "rgb 1.0 0 0" or "rgb (1, 0, 0)"
296 | // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
297 | // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
298 | // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
299 | // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
300 | // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
301 | //
302 | function inputToRGB(color) {
303 |
304 | var rgb = { r: 0, g: 0, b: 0 };
305 | var a = 1;
306 | var s = null;
307 | var v = null;
308 | var l = null;
309 | var ok = false;
310 | var format = false;
311 |
312 | if (typeof color == "string") {
313 | color = stringInputToObject(color);
314 | }
315 |
316 | if (typeof color == "object") {
317 | if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
318 | rgb = rgbToRgb(color.r, color.g, color.b);
319 | ok = true;
320 | format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
321 | }
322 | else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
323 | s = convertToPercentage(color.s);
324 | v = convertToPercentage(color.v);
325 | rgb = hsvToRgb(color.h, s, v);
326 | ok = true;
327 | format = "hsv";
328 | }
329 | else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
330 | s = convertToPercentage(color.s);
331 | l = convertToPercentage(color.l);
332 | rgb = hslToRgb(color.h, s, l);
333 | ok = true;
334 | format = "hsl";
335 | }
336 |
337 | if (color.hasOwnProperty("a")) {
338 | a = color.a;
339 | }
340 | }
341 |
342 | a = boundAlpha(a);
343 |
344 | return {
345 | ok: ok,
346 | format: color.format || format,
347 | r: mathMin(255, mathMax(rgb.r, 0)),
348 | g: mathMin(255, mathMax(rgb.g, 0)),
349 | b: mathMin(255, mathMax(rgb.b, 0)),
350 | a: a
351 | };
352 | }
353 |
354 |
355 | // Conversion Functions
356 | // --------------------
357 |
358 | // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
359 | //
360 |
361 | // `rgbToRgb`
362 | // Handle bounds / percentage checking to conform to CSS color spec
363 | //
364 | // *Assumes:* r, g, b in [0, 255] or [0, 1]
365 | // *Returns:* { r, g, b } in [0, 255]
366 | function rgbToRgb(r, g, b){
367 | return {
368 | r: bound01(r, 255) * 255,
369 | g: bound01(g, 255) * 255,
370 | b: bound01(b, 255) * 255
371 | };
372 | }
373 |
374 | // `rgbToHsl`
375 | // Converts an RGB color value to HSL.
376 | // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
377 | // *Returns:* { h, s, l } in [0,1]
378 | function rgbToHsl(r, g, b) {
379 |
380 | r = bound01(r, 255);
381 | g = bound01(g, 255);
382 | b = bound01(b, 255);
383 |
384 | var max = mathMax(r, g, b), min = mathMin(r, g, b);
385 | var h, s, l = (max + min) / 2;
386 |
387 | if(max == min) {
388 | h = s = 0; // achromatic
389 | }
390 | else {
391 | var d = max - min;
392 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
393 | switch(max) {
394 | case r: h = (g - b) / d + (g < b ? 6 : 0); break;
395 | case g: h = (b - r) / d + 2; break;
396 | case b: h = (r - g) / d + 4; break;
397 | }
398 |
399 | h /= 6;
400 | }
401 |
402 | return { h: h, s: s, l: l };
403 | }
404 |
405 | // `hslToRgb`
406 | // Converts an HSL color value to RGB.
407 | // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
408 | // *Returns:* { r, g, b } in the set [0, 255]
409 | function hslToRgb(h, s, l) {
410 | var r, g, b;
411 |
412 | h = bound01(h, 360);
413 | s = bound01(s, 100);
414 | l = bound01(l, 100);
415 |
416 | function hue2rgb(p, q, t) {
417 | if(t < 0) t += 1;
418 | if(t > 1) t -= 1;
419 | if(t < 1/6) return p + (q - p) * 6 * t;
420 | if(t < 1/2) return q;
421 | if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
422 | return p;
423 | }
424 |
425 | if(s === 0) {
426 | r = g = b = l; // achromatic
427 | }
428 | else {
429 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
430 | var p = 2 * l - q;
431 | r = hue2rgb(p, q, h + 1/3);
432 | g = hue2rgb(p, q, h);
433 | b = hue2rgb(p, q, h - 1/3);
434 | }
435 |
436 | return { r: r * 255, g: g * 255, b: b * 255 };
437 | }
438 |
439 | // `rgbToHsv`
440 | // Converts an RGB color value to HSV
441 | // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
442 | // *Returns:* { h, s, v } in [0,1]
443 | function rgbToHsv(r, g, b) {
444 |
445 | r = bound01(r, 255);
446 | g = bound01(g, 255);
447 | b = bound01(b, 255);
448 |
449 | var max = mathMax(r, g, b), min = mathMin(r, g, b);
450 | var h, s, v = max;
451 |
452 | var d = max - min;
453 | s = max === 0 ? 0 : d / max;
454 |
455 | if(max == min) {
456 | h = 0; // achromatic
457 | }
458 | else {
459 | switch(max) {
460 | case r: h = (g - b) / d + (g < b ? 6 : 0); break;
461 | case g: h = (b - r) / d + 2; break;
462 | case b: h = (r - g) / d + 4; break;
463 | }
464 | h /= 6;
465 | }
466 | return { h: h, s: s, v: v };
467 | }
468 |
469 | // `hsvToRgb`
470 | // Converts an HSV color value to RGB.
471 | // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
472 | // *Returns:* { r, g, b } in the set [0, 255]
473 | function hsvToRgb(h, s, v) {
474 |
475 | h = bound01(h, 360) * 6;
476 | s = bound01(s, 100);
477 | v = bound01(v, 100);
478 |
479 | var i = Math.floor(h),
480 | f = h - i,
481 | p = v * (1 - s),
482 | q = v * (1 - f * s),
483 | t = v * (1 - (1 - f) * s),
484 | mod = i % 6,
485 | r = [v, q, p, p, t, v][mod],
486 | g = [t, v, v, q, p, p][mod],
487 | b = [p, p, t, v, v, q][mod];
488 |
489 | return { r: r * 255, g: g * 255, b: b * 255 };
490 | }
491 |
492 | // `rgbToHex`
493 | // Converts an RGB color to hex
494 | // Assumes r, g, and b are contained in the set [0, 255]
495 | // Returns a 3 or 6 character hex
496 | function rgbToHex(r, g, b, allow3Char) {
497 |
498 | var hex = [
499 | pad2(mathRound(r).toString(16)),
500 | pad2(mathRound(g).toString(16)),
501 | pad2(mathRound(b).toString(16))
502 | ];
503 |
504 | // Return a 3 character hex if possible
505 | if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
506 | return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
507 | }
508 |
509 | return hex.join("");
510 | }
511 |
512 | // `rgbaToHex`
513 | // Converts an RGBA color plus alpha transparency to hex
514 | // Assumes r, g, b and a are contained in the set [0, 255]
515 | // Returns an 8 character hex
516 | function rgbaToHex(r, g, b, a) {
517 |
518 | var hex = [
519 | pad2(convertDecimalToHex(a)),
520 | pad2(mathRound(r).toString(16)),
521 | pad2(mathRound(g).toString(16)),
522 | pad2(mathRound(b).toString(16))
523 | ];
524 |
525 | return hex.join("");
526 | }
527 |
528 | // `equals`
529 | // Can be called with any tinycolor input
530 | tinycolor.equals = function (color1, color2) {
531 | if (!color1 || !color2) { return false; }
532 | return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
533 | };
534 |
535 | tinycolor.random = function() {
536 | return tinycolor.fromRatio({
537 | r: mathRandom(),
538 | g: mathRandom(),
539 | b: mathRandom()
540 | });
541 | };
542 |
543 |
544 | // Modification Functions
545 | // ----------------------
546 | // Thanks to less.js for some of the basics here
547 | //
548 |
549 | function desaturate(color, amount) {
550 | amount = (amount === 0) ? 0 : (amount || 10);
551 | var hsl = tinycolor(color).toHsl();
552 | hsl.s -= amount / 100;
553 | hsl.s = clamp01(hsl.s);
554 | return tinycolor(hsl);
555 | }
556 |
557 | function saturate(color, amount) {
558 | amount = (amount === 0) ? 0 : (amount || 10);
559 | var hsl = tinycolor(color).toHsl();
560 | hsl.s += amount / 100;
561 | hsl.s = clamp01(hsl.s);
562 | return tinycolor(hsl);
563 | }
564 |
565 | function greyscale(color) {
566 | return tinycolor(color).desaturate(100);
567 | }
568 |
569 | function lighten (color, amount) {
570 | amount = (amount === 0) ? 0 : (amount || 10);
571 | var hsl = tinycolor(color).toHsl();
572 | hsl.l += amount / 100;
573 | hsl.l = clamp01(hsl.l);
574 | return tinycolor(hsl);
575 | }
576 |
577 | function brighten(color, amount) {
578 | amount = (amount === 0) ? 0 : (amount || 10);
579 | var rgb = tinycolor(color).toRgb();
580 | rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100))));
581 | rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100))));
582 | rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100))));
583 | return tinycolor(rgb);
584 | }
585 |
586 | function darken (color, amount) {
587 | amount = (amount === 0) ? 0 : (amount || 10);
588 | var hsl = tinycolor(color).toHsl();
589 | hsl.l -= amount / 100;
590 | hsl.l = clamp01(hsl.l);
591 | return tinycolor(hsl);
592 | }
593 |
594 | // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
595 | // Values outside of this range will be wrapped into this range.
596 | function spin(color, amount) {
597 | var hsl = tinycolor(color).toHsl();
598 | var hue = (hsl.h + amount) % 360;
599 | hsl.h = hue < 0 ? 360 + hue : hue;
600 | return tinycolor(hsl);
601 | }
602 |
603 | // Combination Functions
604 | // ---------------------
605 | // Thanks to jQuery xColor for some of the ideas behind these
606 | //
607 |
608 | function complement(color) {
609 | var hsl = tinycolor(color).toHsl();
610 | hsl.h = (hsl.h + 180) % 360;
611 | return tinycolor(hsl);
612 | }
613 |
614 | function triad(color) {
615 | var hsl = tinycolor(color).toHsl();
616 | var h = hsl.h;
617 | return [
618 | tinycolor(color),
619 | tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
620 | tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
621 | ];
622 | }
623 |
624 | function tetrad(color) {
625 | var hsl = tinycolor(color).toHsl();
626 | var h = hsl.h;
627 | return [
628 | tinycolor(color),
629 | tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
630 | tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
631 | tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
632 | ];
633 | }
634 |
635 | function splitcomplement(color) {
636 | var hsl = tinycolor(color).toHsl();
637 | var h = hsl.h;
638 | return [
639 | tinycolor(color),
640 | tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}),
641 | tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l})
642 | ];
643 | }
644 |
645 | function analogous(color, results, slices) {
646 | results = results || 6;
647 | slices = slices || 30;
648 |
649 | var hsl = tinycolor(color).toHsl();
650 | var part = 360 / slices;
651 | var ret = [tinycolor(color)];
652 |
653 | for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) {
654 | hsl.h = (hsl.h + part) % 360;
655 | ret.push(tinycolor(hsl));
656 | }
657 | return ret;
658 | }
659 |
660 | function monochromatic(color, results) {
661 | results = results || 6;
662 | var hsv = tinycolor(color).toHsv();
663 | var h = hsv.h, s = hsv.s, v = hsv.v;
664 | var ret = [];
665 | var modification = 1 / results;
666 |
667 | while (results--) {
668 | ret.push(tinycolor({ h: h, s: s, v: v}));
669 | v = (v + modification) % 1;
670 | }
671 |
672 | return ret;
673 | }
674 |
675 | // Utility Functions
676 | // ---------------------
677 |
678 | tinycolor.mix = function(color1, color2, amount) {
679 | amount = (amount === 0) ? 0 : (amount || 50);
680 |
681 | var rgb1 = tinycolor(color1).toRgb();
682 | var rgb2 = tinycolor(color2).toRgb();
683 |
684 | var p = amount / 100;
685 | var w = p * 2 - 1;
686 | var a = rgb2.a - rgb1.a;
687 |
688 | var w1;
689 |
690 | if (w * a == -1) {
691 | w1 = w;
692 | } else {
693 | w1 = (w + a) / (1 + w * a);
694 | }
695 |
696 | w1 = (w1 + 1) / 2;
697 |
698 | var w2 = 1 - w1;
699 |
700 | var rgba = {
701 | r: rgb2.r * w1 + rgb1.r * w2,
702 | g: rgb2.g * w1 + rgb1.g * w2,
703 | b: rgb2.b * w1 + rgb1.b * w2,
704 | a: rgb2.a * p + rgb1.a * (1 - p)
705 | };
706 |
707 | return tinycolor(rgba);
708 | };
709 |
710 |
711 | // Readability Functions
712 | // ---------------------
713 | // false
732 | // tinycolor.isReadable("#000", "#111",{level:"AA",size:"large"}) => false
733 | tinycolor.isReadable = function(color1, color2, wcag2) {
734 | var readability = tinycolor.readability(color1, color2);
735 | var wcag2Parms, out;
736 |
737 | out = false;
738 |
739 | wcag2Parms = validateWCAG2Parms(wcag2);
740 | switch (wcag2Parms.level + wcag2Parms.size) {
741 | case "AAsmall":
742 | case "AAAlarge":
743 | out = readability >= 4.5;
744 | break;
745 | case "AAlarge":
746 | out = readability >= 3;
747 | break;
748 | case "AAAsmall":
749 | out = readability >= 7;
750 | break;
751 | }
752 | return out;
753 |
754 | };
755 |
756 | // `mostReadable`
757 | // Given a base color and a list of possible foreground or background
758 | // colors for that base, returns the most readable color.
759 | // Optionally returns Black or White if the most readable color is unreadable.
760 | // *Example*
761 | // tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:false}).toHexString(); // "#112255"
762 | // tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:true}).toHexString(); // "#ffffff"
763 | // tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"large"}).toHexString(); // "#faf3f3"
764 | // tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"small"}).toHexString(); // "#ffffff"
765 | tinycolor.mostReadable = function(baseColor, colorList, args) {
766 | var bestColor = null;
767 | var bestScore = 0;
768 | var readability;
769 | var includeFallbackColors, level, size ;
770 | args = args || {};
771 | includeFallbackColors = args.includeFallbackColors ;
772 | level = args.level;
773 | size = args.size;
774 |
775 | for (var i= 0; i < colorList.length ; i++) {
776 | readability = tinycolor.readability(baseColor, colorList[i]);
777 | if (readability > bestScore) {
778 | bestScore = readability;
779 | bestColor = tinycolor(colorList[i]);
780 | }
781 | }
782 |
783 | if (tinycolor.isReadable(baseColor, bestColor, {"level":level,"size":size}) || !includeFallbackColors) {
784 | return bestColor;
785 | }
786 | else {
787 | args.includeFallbackColors=false;
788 | return tinycolor.mostReadable(baseColor,["#fff", "#000"],args);
789 | }
790 | };
791 |
792 |
793 | // Big List of Colors
794 | // ------------------
795 | //
796 | var names = tinycolor.names = {
797 | aliceblue: "f0f8ff",
798 | antiquewhite: "faebd7",
799 | aqua: "0ff",
800 | aquamarine: "7fffd4",
801 | azure: "f0ffff",
802 | beige: "f5f5dc",
803 | bisque: "ffe4c4",
804 | black: "000",
805 | blanchedalmond: "ffebcd",
806 | blue: "00f",
807 | blueviolet: "8a2be2",
808 | brown: "a52a2a",
809 | burlywood: "deb887",
810 | burntsienna: "ea7e5d",
811 | cadetblue: "5f9ea0",
812 | chartreuse: "7fff00",
813 | chocolate: "d2691e",
814 | coral: "ff7f50",
815 | cornflowerblue: "6495ed",
816 | cornsilk: "fff8dc",
817 | crimson: "dc143c",
818 | cyan: "0ff",
819 | darkblue: "00008b",
820 | darkcyan: "008b8b",
821 | darkgoldenrod: "b8860b",
822 | darkgray: "a9a9a9",
823 | darkgreen: "006400",
824 | darkgrey: "a9a9a9",
825 | darkkhaki: "bdb76b",
826 | darkmagenta: "8b008b",
827 | darkolivegreen: "556b2f",
828 | darkorange: "ff8c00",
829 | darkorchid: "9932cc",
830 | darkred: "8b0000",
831 | darksalmon: "e9967a",
832 | darkseagreen: "8fbc8f",
833 | darkslateblue: "483d8b",
834 | darkslategray: "2f4f4f",
835 | darkslategrey: "2f4f4f",
836 | darkturquoise: "00ced1",
837 | darkviolet: "9400d3",
838 | deeppink: "ff1493",
839 | deepskyblue: "00bfff",
840 | dimgray: "696969",
841 | dimgrey: "696969",
842 | dodgerblue: "1e90ff",
843 | firebrick: "b22222",
844 | floralwhite: "fffaf0",
845 | forestgreen: "228b22",
846 | fuchsia: "f0f",
847 | gainsboro: "dcdcdc",
848 | ghostwhite: "f8f8ff",
849 | gold: "ffd700",
850 | goldenrod: "daa520",
851 | gray: "808080",
852 | green: "008000",
853 | greenyellow: "adff2f",
854 | grey: "808080",
855 | honeydew: "f0fff0",
856 | hotpink: "ff69b4",
857 | indianred: "cd5c5c",
858 | indigo: "4b0082",
859 | ivory: "fffff0",
860 | khaki: "f0e68c",
861 | lavender: "e6e6fa",
862 | lavenderblush: "fff0f5",
863 | lawngreen: "7cfc00",
864 | lemonchiffon: "fffacd",
865 | lightblue: "add8e6",
866 | lightcoral: "f08080",
867 | lightcyan: "e0ffff",
868 | lightgoldenrodyellow: "fafad2",
869 | lightgray: "d3d3d3",
870 | lightgreen: "90ee90",
871 | lightgrey: "d3d3d3",
872 | lightpink: "ffb6c1",
873 | lightsalmon: "ffa07a",
874 | lightseagreen: "20b2aa",
875 | lightskyblue: "87cefa",
876 | lightslategray: "789",
877 | lightslategrey: "789",
878 | lightsteelblue: "b0c4de",
879 | lightyellow: "ffffe0",
880 | lime: "0f0",
881 | limegreen: "32cd32",
882 | linen: "faf0e6",
883 | magenta: "f0f",
884 | maroon: "800000",
885 | mediumaquamarine: "66cdaa",
886 | mediumblue: "0000cd",
887 | mediumorchid: "ba55d3",
888 | mediumpurple: "9370db",
889 | mediumseagreen: "3cb371",
890 | mediumslateblue: "7b68ee",
891 | mediumspringgreen: "00fa9a",
892 | mediumturquoise: "48d1cc",
893 | mediumvioletred: "c71585",
894 | midnightblue: "191970",
895 | mintcream: "f5fffa",
896 | mistyrose: "ffe4e1",
897 | moccasin: "ffe4b5",
898 | navajowhite: "ffdead",
899 | navy: "000080",
900 | oldlace: "fdf5e6",
901 | olive: "808000",
902 | olivedrab: "6b8e23",
903 | orange: "ffa500",
904 | orangered: "ff4500",
905 | orchid: "da70d6",
906 | palegoldenrod: "eee8aa",
907 | palegreen: "98fb98",
908 | paleturquoise: "afeeee",
909 | palevioletred: "db7093",
910 | papayawhip: "ffefd5",
911 | peachpuff: "ffdab9",
912 | peru: "cd853f",
913 | pink: "ffc0cb",
914 | plum: "dda0dd",
915 | powderblue: "b0e0e6",
916 | purple: "800080",
917 | rebeccapurple: "663399",
918 | red: "f00",
919 | rosybrown: "bc8f8f",
920 | royalblue: "4169e1",
921 | saddlebrown: "8b4513",
922 | salmon: "fa8072",
923 | sandybrown: "f4a460",
924 | seagreen: "2e8b57",
925 | seashell: "fff5ee",
926 | sienna: "a0522d",
927 | silver: "c0c0c0",
928 | skyblue: "87ceeb",
929 | slateblue: "6a5acd",
930 | slategray: "708090",
931 | slategrey: "708090",
932 | snow: "fffafa",
933 | springgreen: "00ff7f",
934 | steelblue: "4682b4",
935 | tan: "d2b48c",
936 | teal: "008080",
937 | thistle: "d8bfd8",
938 | tomato: "ff6347",
939 | turquoise: "40e0d0",
940 | violet: "ee82ee",
941 | wheat: "f5deb3",
942 | white: "fff",
943 | whitesmoke: "f5f5f5",
944 | yellow: "ff0",
945 | yellowgreen: "9acd32"
946 | };
947 |
948 | // Make it easy to access colors via `hexNames[hex]`
949 | var hexNames = tinycolor.hexNames = flip(names);
950 |
951 |
952 | // Utilities
953 | // ---------
954 |
955 | // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
956 | function flip(o) {
957 | var flipped = { };
958 | for (var i in o) {
959 | if (o.hasOwnProperty(i)) {
960 | flipped[o[i]] = i;
961 | }
962 | }
963 | return flipped;
964 | }
965 |
966 | // Return a valid alpha value [0,1] with all invalid values being set to 1
967 | function boundAlpha(a) {
968 | a = parseFloat(a);
969 |
970 | if (isNaN(a) || a < 0 || a > 1) {
971 | a = 1;
972 | }
973 |
974 | return a;
975 | }
976 |
977 | // Take input from [0, n] and return it as [0, 1]
978 | function bound01(n, max) {
979 | if (isOnePointZero(n)) { n = "100%"; }
980 |
981 | var processPercent = isPercentage(n);
982 | n = mathMin(max, mathMax(0, parseFloat(n)));
983 |
984 | // Automatically convert percentage into number
985 | if (processPercent) {
986 | n = parseInt(n * max, 10) / 100;
987 | }
988 |
989 | // Handle floating point rounding errors
990 | if ((Math.abs(n - max) < 0.000001)) {
991 | return 1;
992 | }
993 |
994 | // Convert into [0, 1] range if it isn't already
995 | return (n % max) / parseFloat(max);
996 | }
997 |
998 | // Force a number between 0 and 1
999 | function clamp01(val) {
1000 | return mathMin(1, mathMax(0, val));
1001 | }
1002 |
1003 | // Parse a base-16 hex value into a base-10 integer
1004 | function parseIntFromHex(val) {
1005 | return parseInt(val, 16);
1006 | }
1007 |
1008 | // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
1009 | //
1010 | function isOnePointZero(n) {
1011 | return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
1012 | }
1013 |
1014 | // Check to see if string passed in is a percentage
1015 | function isPercentage(n) {
1016 | return typeof n === "string" && n.indexOf('%') != -1;
1017 | }
1018 |
1019 | // Force a hex value to have 2 characters
1020 | function pad2(c) {
1021 | return c.length == 1 ? '0' + c : '' + c;
1022 | }
1023 |
1024 | // Replace a decimal with it's percentage value
1025 | function convertToPercentage(n) {
1026 | if (n <= 1) {
1027 | n = (n * 100) + "%";
1028 | }
1029 |
1030 | return n;
1031 | }
1032 |
1033 | // Converts a decimal to a hex value
1034 | function convertDecimalToHex(d) {
1035 | return Math.round(parseFloat(d) * 255).toString(16);
1036 | }
1037 | // Converts a hex value to a decimal
1038 | function convertHexToDecimal(h) {
1039 | return (parseIntFromHex(h) / 255);
1040 | }
1041 |
1042 | var matchers = (function() {
1043 |
1044 | //
1045 | var CSS_INTEGER = "[-\\+]?\\d+%?";
1046 |
1047 | //
1048 | var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
1049 |
1050 | // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
1051 | var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
1052 |
1053 | // Actual matching.
1054 | // Parentheses and commas are optional, but not required.
1055 | // Whitespace can take the place of commas or opening paren
1056 | var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
1057 | var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
1058 |
1059 | return {
1060 | CSS_UNIT: new RegExp(CSS_UNIT),
1061 | rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
1062 | rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
1063 | hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
1064 | hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
1065 | hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
1066 | hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
1067 | hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
1068 | hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
1069 | hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
1070 | };
1071 | })();
1072 |
1073 | // `isValidCSSUnit`
1074 | // Take in a single string / number and check to see if it looks like a CSS unit
1075 | // (see `matchers` above for definition).
1076 | function isValidCSSUnit(color) {
1077 | return !!matchers.CSS_UNIT.exec(color);
1078 | }
1079 |
1080 | // `stringInputToObject`
1081 | // Permissive string parsing. Take in a number of formats, and output an object
1082 | // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
1083 | function stringInputToObject(color) {
1084 |
1085 | color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase();
1086 | var named = false;
1087 | if (names[color]) {
1088 | color = names[color];
1089 | named = true;
1090 | }
1091 | else if (color == 'transparent') {
1092 | return { r: 0, g: 0, b: 0, a: 0, format: "name" };
1093 | }
1094 |
1095 | // Try to match string input using regular expressions.
1096 | // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
1097 | // Just return an object and let the conversion functions handle that.
1098 | // This way the result will be the same whether the tinycolor is initialized with string or object.
1099 | var match;
1100 | if ((match = matchers.rgb.exec(color))) {
1101 | return { r: match[1], g: match[2], b: match[3] };
1102 | }
1103 | if ((match = matchers.rgba.exec(color))) {
1104 | return { r: match[1], g: match[2], b: match[3], a: match[4] };
1105 | }
1106 | if ((match = matchers.hsl.exec(color))) {
1107 | return { h: match[1], s: match[2], l: match[3] };
1108 | }
1109 | if ((match = matchers.hsla.exec(color))) {
1110 | return { h: match[1], s: match[2], l: match[3], a: match[4] };
1111 | }
1112 | if ((match = matchers.hsv.exec(color))) {
1113 | return { h: match[1], s: match[2], v: match[3] };
1114 | }
1115 | if ((match = matchers.hsva.exec(color))) {
1116 | return { h: match[1], s: match[2], v: match[3], a: match[4] };
1117 | }
1118 | if ((match = matchers.hex8.exec(color))) {
1119 | return {
1120 | a: convertHexToDecimal(match[1]),
1121 | r: parseIntFromHex(match[2]),
1122 | g: parseIntFromHex(match[3]),
1123 | b: parseIntFromHex(match[4]),
1124 | format: named ? "name" : "hex8"
1125 | };
1126 | }
1127 | if ((match = matchers.hex6.exec(color))) {
1128 | return {
1129 | r: parseIntFromHex(match[1]),
1130 | g: parseIntFromHex(match[2]),
1131 | b: parseIntFromHex(match[3]),
1132 | format: named ? "name" : "hex"
1133 | };
1134 | }
1135 | if ((match = matchers.hex3.exec(color))) {
1136 | return {
1137 | r: parseIntFromHex(match[1] + '' + match[1]),
1138 | g: parseIntFromHex(match[2] + '' + match[2]),
1139 | b: parseIntFromHex(match[3] + '' + match[3]),
1140 | format: named ? "name" : "hex"
1141 | };
1142 | }
1143 |
1144 | return false;
1145 | }
1146 |
1147 | function validateWCAG2Parms(parms) {
1148 | // return valid WCAG2 parms for isReadable.
1149 | // If input parms are invalid, return {"level":"AA", "size":"small"}
1150 | var level, size;
1151 | parms = parms || {"level":"AA", "size":"small"};
1152 | level = (parms.level || "AA").toUpperCase();
1153 | size = (parms.size || "small").toLowerCase();
1154 | if (level !== "AA" && level !== "AAA") {
1155 | level = "AA";
1156 | }
1157 | if (size !== "small" && size !== "large") {
1158 | size = "small";
1159 | }
1160 | return {"level":level, "size":size};
1161 | }
1162 |
1163 | // Node: Export function
1164 | if (typeof module !== "undefined" && module.exports) {
1165 | module.exports = tinycolor;
1166 | }
1167 | // AMD/requirejs: Define the module
1168 | else if (typeof define === 'function' && define.amd) {
1169 | define(function () {return tinycolor;});
1170 | }
1171 | // Browser: Expose to window
1172 | else {
1173 | window.tinycolor = tinycolor;
1174 | }
1175 |
1176 | })(Math);
1177 |
--------------------------------------------------------------------------------
/libs/tinygradient.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * TinyGradient 0.3.1
3 | * Copyright 2014-2015 Damien "Mistic" Sorel (http://www.strangeplanet.fr)
4 | * Licensed under MIT (http://opensource.org/licenses/MIT)
5 | */
6 |
7 | (function(root, factory) {
8 | if (typeof module !== "undefined" && module.exports) {
9 | module.exports = factory(require('tinycolor2'));
10 | }
11 | else if (typeof define === 'function' && define.amd) {
12 | define(['tinycolor'], factory);
13 | }
14 | else {
15 | root.tinygradient = factory(root.tinycolor);
16 | }
17 | }(this, function(tinycolor) {
18 | "use strict";
19 |
20 | var Utils = {
21 | rgba_max: { r: 256, g: 256, b: 256, a: 1 },
22 | hsva_max: { h: 360, s: 1, v: 1, a: 1 },
23 |
24 | /**
25 | * Linearly compute the step size between start and end (not normalized)
26 | * @param {Object} start - rgba or hsva
27 | * @param {Object} end - rgba or hsva
28 | * @param {Integer} steps - number of desired steps
29 | * @return {Object} rgba or hsva
30 | */
31 | stepize: function(start, end, steps) {
32 | var step = {};
33 |
34 | for (var k in start) {
35 | if (start.hasOwnProperty(k)) {
36 | step[k] = (end[k]-start[k]) / steps;
37 | }
38 | }
39 |
40 | return step;
41 | },
42 |
43 | /**
44 | * Compute the final step color
45 | * @param {Object} step - rgba or hsva from `stepize`
46 | * @param {Object} start - rgba or hsva
47 | * @param {Integer} i - color index
48 | * @param {Object} max - rgba or hsva of maximum values for each channel
49 | * @return {Object} rgba or hsva
50 | */
51 | interpolate: function(step, start, i, max) {
52 | var color = {};
53 |
54 | for (var k in start) {
55 | if (start.hasOwnProperty(k)) {
56 | color[k] = step[k] * i + start[k];
57 | color[k] = color[k]<0 ? color[k]+max[k] : ( max[k]!=1 ? color[k]%max[k] : color[k] );
58 | }
59 | }
60 |
61 | return color;
62 | },
63 |
64 | /**
65 | * Generate gradient with RGBa interpolation
66 | * @param {Object} stop1
67 | * @param {Object} stop2
68 | * @param {Integer} steps
69 | * @param {tinycolor[]} color1 included, color2 excluded
70 | */
71 | rgb: function(stop1, stop2, steps) {
72 | var start = stop1.color.toRgb(),
73 | end = stop2.color.toRgb(),
74 | gradient = [stop1.color],
75 | step = Utils.stepize(start, end, steps),
76 | color;
77 |
78 | for (var i=1; i= end.h && trigonometric)) {
103 | diff = end.h-start.h;
104 | }
105 | else if (trigonometric) {
106 | diff = 360-end.h+start.h;
107 | }
108 | else {
109 | diff = 360-start.h+end.h;
110 | }
111 | step.h = Math.pow(-1, trigonometric) * Math.abs(diff)*1.0 / steps;
112 |
113 | for (var i=1; i 1) {
212 | throw new Error('Color stops positions must be between 0 and 1');
213 | }
214 | else if (stop.pos <= p) {
215 | throw new Error('Color stops positions are not ordered');
216 | }
217 | p = stop.pos;
218 | }
219 | else {
220 | stop = {
221 | color: tinycolor(stop),
222 | pos: i/(l-1)
223 | };
224 | }
225 |
226 | return stop;
227 | });
228 |
229 | if (this.stops[0].pos !== 0) {
230 | this.stops.unshift({
231 | color: this.stops[0].color,
232 | pos: 0
233 | });
234 | }
235 | if (this.stops[this.stops.length-1].pos !== 1) {
236 | this.stops.push({
237 | color: this.stops[this.stops.length-1].color,
238 | pos: 1
239 | });
240 | }
241 | };
242 |
243 | /**
244 | * Return new instance with reversed stops
245 | * @return {tinygradient}
246 | */
247 | TinyGradient.prototype.reverse = function() {
248 | var stops = [];
249 |
250 | this.stops.forEach(function(stop) {
251 | stops.push({
252 | color: stop.color,
253 | pos: 1 - stop.pos
254 | });
255 | });
256 |
257 | return new TinyGradient(stops.reverse());
258 | };
259 |
260 | /**
261 | * Generate gradient with RGBa interpolation
262 | * @param {Integer} steps
263 | * @return {tinycolor[]}
264 | */
265 | TinyGradient.prototype.rgb = function(steps) {
266 | var substeps = Utils.substeps(this.stops, steps),
267 | gradient = [];
268 |
269 | for (var i=0, l=this.stops.length; i end.h && start.h-end.h > 180);
301 | }
302 |
303 | // rgb interpolation if one of the steps in grayscale
304 | if (start.s===0 || end.s===0) {
305 | gradient = gradient.concat(Utils.rgb(this.stops[i], this.stops[i+1], substeps[i]));
306 | }
307 | else {
308 | gradient = gradient.concat(Utils.hsv(this.stops[i], this.stops[i+1], substeps[i],
309 | (mode==='long' && trig) || (mode==='short' && !trig) || (!parametrized && trigonometric)
310 | ));
311 | }
312 | }
313 |
314 | gradient.push(this.stops[l-1].color);
315 |
316 | return gradient;
317 | };
318 |
319 | /**
320 | * Generate CSS3 command (no prefix) for this gradient
321 | * @param {String} [mode=linear] - 'linear' or 'radial'
322 | * @param {String} [direction] - default is 'to right' or 'ellipse at center'
323 | * @return {String}
324 | */
325 | TinyGradient.prototype.css = function(mode, direction) {
326 | mode = mode || 'linear';
327 | direction = direction || (mode=='linear' ? 'to right' : 'ellipse at center');
328 |
329 | var css = mode + '-gradient(' + direction;
330 | this.stops.forEach(function(stop) {
331 | css+= ', ' + stop.color.toRgbString() + ' ' + (stop.pos*100) + '%';
332 | });
333 | css+= ')';
334 | return css;
335 | };
336 |
337 |
338 | /**
339 | * Initialize and create gradient with RGBa interpolation
340 | * @see TinyGradient::rgb
341 | */
342 | TinyGradient.rgb = function(colors, steps) {
343 | colors = Array.prototype.slice.call(arguments);
344 | steps = colors.pop();
345 |
346 | return TinyGradient.apply(null, colors).rgb(steps);
347 | };
348 |
349 | /**
350 | * Initialize and create gradient with HSVa interpolation
351 | * @see TinyGradient::hsv
352 | */
353 | TinyGradient.hsv = function(colors, steps, mode) {
354 | colors = Array.prototype.slice.call(arguments);
355 | mode = colors.pop();
356 | steps = colors.pop();
357 |
358 | return TinyGradient.apply(null, colors).hsv(steps, mode);
359 | };
360 |
361 | /**
362 | * Initialize and generate CSS3 command for gradient
363 | * @see TinyGradient::css
364 | */
365 | TinyGradient.css = function(colors, mode, direction) {
366 | colors = Array.prototype.slice.call(arguments);
367 | direction = colors.pop();
368 | mode = colors.pop();
369 |
370 | return TinyGradient.apply(null, colors).css(mode, direction);
371 | };
372 |
373 |
374 | // export
375 | return TinyGradient;
376 | }));
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lee-algorithm",
3 | "version": "1.0.0",
4 | "description": "A JS implementation of the Lee Algorithm",
5 | "main": "lee.js",
6 | "scripts": {
7 | "test": "test"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/JamesMilnerUK/lee-algorithm-js.git"
12 | },
13 | "keywords": [
14 | "Lee",
15 | "Algorithm",
16 | "Maze"
17 | ],
18 | "author": "James Milner",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/JamesMilnerUK/lee-algorithm-js/issues"
22 | },
23 | "homepage": "https://github.com/JamesMilnerUK/lee-algorithm-js#readme",
24 | "devDependencies": {
25 | "jasmine": "^2.4.1"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/spec/LeeSpec.js:
--------------------------------------------------------------------------------
1 |
2 | describe("Lee", function() {
3 |
4 | fs = require('fs');
5 | leeCode = fs.readFileSync('lee.js','utf-8'); // depends on the file encoding
6 | eval(leeCode);
7 |
8 | it("Check pathfinder defined", function() {
9 |
10 | expect(lee.pathfinder).toBeDefined();
11 |
12 | });
13 |
14 | it("Check backtrace defined", function() {
15 |
16 | expect(lee.neighbourCheck).toBeDefined();
17 |
18 | });
19 |
20 | it("Check pathfinder defined", function() {
21 |
22 | expect(lee.backtrace).toBeDefined();
23 |
24 | });
25 |
26 | it("Checks to make sure the returned matrix is correct", function() {
27 |
28 | var aMatrix = [
29 | [0, 0, 0, 0, 0],
30 | [0, -1, -1, -1, 0],
31 | [0, 0, 0, 0, -1],
32 | [-1, 0, -1, 0, -1],
33 | [-1, -1, -1, 0, -1]
34 | ];
35 |
36 | var checkedMatrix = lee.pathfinder(aMatrix, 0, 0, 4, 3);
37 | console.log("Checked Matrix: \n", checkedMatrix);
38 |
39 | // Check the structure of the matrix is correct
40 | expect(checkedMatrix).toBeDefined();
41 | expect(checkedMatrix.length).toBe(2);
42 | expect(checkedMatrix[0].length).toBe(5);
43 | expect(checkedMatrix[0][0].length).toBe(5);
44 |
45 | expect(checkedMatrix[0][0][0]).toBe(0); // Make sure the start is 0
46 | expect(checkedMatrix[1]).toBe(5); // Make sure the end is 5
47 |
48 |
49 | });
50 |
51 | it("Checks to make sure the returned route is correct", function() {
52 |
53 | var aMatrix = [
54 | [0, 0, 0, 0, 0],
55 | [0, -1, -1, -1, 0],
56 | [0, 0, 0, 0, -1],
57 | [-1, 0, -1, 0, -1],
58 | [-1, -1, -1, 0, -1]
59 | ];
60 |
61 | var checkedMatrix = lee.pathfinder(aMatrix, 0, 0, 4, 3);
62 | var route = lee.backtrace(aMatrix, 0, 0, 4, 3);
63 |
64 | expect(route.length).toBe(6); // Make sure the length is correct
65 |
66 | expect(route[0][0]).toBe(0); // Make sure the start is 0, 0
67 | expect(route[0][1]).toBe(0);
68 | expect(route[5][0]).toBe(4); // Make sure the end is 4,3
69 | expect(route[5][1]).toBe(3);
70 | });
71 |
72 | // END OF TESTS
73 |
74 | });
75 |
--------------------------------------------------------------------------------
/spec/support/jasmine.json:
--------------------------------------------------------------------------------
1 | {
2 | "spec_dir": "spec",
3 | "spec_files": [
4 | "**/*[sS]pec.js"
5 | ],
6 | "helpers": [
7 | "helpers/**/*.js"
8 | ],
9 | "stopSpecOnExpectationFailure": false,
10 | "random": false
11 | }
12 |
--------------------------------------------------------------------------------