1 |
|
2 | (function (global, factory) {
|
3 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
4 | typeof define === 'function' && define.amd ? define(factory) :
|
5 | (global.FastAverageColor = factory());
|
6 | }(this, (function () { 'use strict';
|
7 |
|
8 | function _classCallCheck(instance, Constructor) {
|
9 | if (!(instance instanceof Constructor)) {
|
10 | throw new TypeError("Cannot call a class as a function");
|
11 | }
|
12 | }
|
13 |
|
14 | function _defineProperties(target, props) {
|
15 | for (var i = 0; i < props.length; i++) {
|
16 | var descriptor = props[i];
|
17 | descriptor.enumerable = descriptor.enumerable || false;
|
18 | descriptor.configurable = true;
|
19 | if ("value" in descriptor) descriptor.writable = true;
|
20 | Object.defineProperty(target, descriptor.key, descriptor);
|
21 | }
|
22 | }
|
23 |
|
24 | function _createClass(Constructor, protoProps, staticProps) {
|
25 | if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
26 | if (staticProps) _defineProperties(Constructor, staticProps);
|
27 | return Constructor;
|
28 | }
|
29 |
|
30 | function dominantAlgorithm(arr, len, preparedStep) {
|
31 | var colorHash = {},
|
32 | divider = 24;
|
33 |
|
34 | for (var i = 0; i < len; i += preparedStep) {
|
35 | var red = arr[i],
|
36 | green = arr[i + 1],
|
37 | blue = arr[i + 2],
|
38 | alpha = arr[i + 3],
|
39 | key = Math.round(red / divider) + ',' + Math.round(green / divider) + ',' + Math.round(blue / divider);
|
40 |
|
41 | if (colorHash[key]) {
|
42 | colorHash[key] = [colorHash[key][0] + red * alpha, colorHash[key][1] + green * alpha, colorHash[key][2] + blue * alpha, colorHash[key][3] + alpha, colorHash[key][4] + 1];
|
43 | } else {
|
44 | colorHash[key] = [red * alpha, green * alpha, blue * alpha, alpha, 1];
|
45 | }
|
46 | }
|
47 |
|
48 | var buffer = Object.keys(colorHash).map(function (key) {
|
49 | return colorHash[key];
|
50 | }).sort(function (a, b) {
|
51 | var countA = a[4],
|
52 | countB = b[4];
|
53 | return countA > countB ? -1 : countA === countB ? 0 : 1;
|
54 | });
|
55 | var max = buffer[0];
|
56 | var redTotal = max[0];
|
57 | var greenTotal = max[1];
|
58 | var blueTotal = max[2];
|
59 | var alphaTotal = max[3];
|
60 | var count = max[4];
|
61 | return alphaTotal ? [Math.round(redTotal / alphaTotal), Math.round(greenTotal / alphaTotal), Math.round(blueTotal / alphaTotal), Math.round(alphaTotal / count)] : [0, 0, 0, 0];
|
62 | }
|
63 |
|
64 | function simpleAlgorithm(arr, len, preparedStep) {
|
65 | var redTotal = 0,
|
66 | greenTotal = 0,
|
67 | blueTotal = 0,
|
68 | alphaTotal = 0,
|
69 | count = 0;
|
70 |
|
71 | for (var i = 0; i < len; i += preparedStep) {
|
72 | var alpha = arr[i + 3],
|
73 | red = arr[i] * alpha,
|
74 | green = arr[i + 1] * alpha,
|
75 | blue = arr[i + 2] * alpha;
|
76 | redTotal += red;
|
77 | greenTotal += green;
|
78 | blueTotal += blue;
|
79 | alphaTotal += alpha;
|
80 | count++;
|
81 | }
|
82 |
|
83 | return alphaTotal ? [Math.round(redTotal / alphaTotal), Math.round(greenTotal / alphaTotal), Math.round(blueTotal / alphaTotal), Math.round(alphaTotal / count)] : [0, 0, 0, 0];
|
84 | }
|
85 |
|
86 | function sqrtAlgorithm(arr, len, preparedStep) {
|
87 | var redTotal = 0,
|
88 | greenTotal = 0,
|
89 | blueTotal = 0,
|
90 | alphaTotal = 0,
|
91 | count = 0;
|
92 |
|
93 | for (var i = 0; i < len; i += preparedStep) {
|
94 | var red = arr[i],
|
95 | green = arr[i + 1],
|
96 | blue = arr[i + 2],
|
97 | alpha = arr[i + 3];
|
98 | redTotal += red * red * alpha;
|
99 | greenTotal += green * green * alpha;
|
100 | blueTotal += blue * blue * alpha;
|
101 | alphaTotal += alpha;
|
102 | count++;
|
103 | }
|
104 |
|
105 | return alphaTotal ? [Math.round(Math.sqrt(redTotal / alphaTotal)), Math.round(Math.sqrt(greenTotal / alphaTotal)), Math.round(Math.sqrt(blueTotal / alphaTotal)), Math.round(alphaTotal / count)] : [0, 0, 0, 0];
|
106 | }
|
107 |
|
108 | var ERROR_PREFIX = 'FastAverageColor: ';
|
109 |
|
110 | var FastAverageColor =
|
111 |
|
112 | function () {
|
113 | function FastAverageColor() {
|
114 | _classCallCheck(this, FastAverageColor);
|
115 | }
|
116 |
|
117 | _createClass(FastAverageColor, [{
|
118 | key: "getColorAsync",
|
119 |
|
120 | |
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | value: function getColorAsync(resource, options) {
|
138 | if (!resource) {
|
139 | return Promise.reject(Error('Call .getColorAsync(null) without resource.'));
|
140 | } else if (resource.complete) {
|
141 | var result = this.getColor(resource, options);
|
142 | return result.error ? Promise.reject(result.error) : Promise.resolve(result);
|
143 | } else {
|
144 | return this._bindImageEvents(resource, options);
|
145 | }
|
146 | }
|
147 | |
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 | }, {
|
166 | key: "getColor",
|
167 | value: function getColor(resource, options) {
|
168 | options = options || {};
|
169 |
|
170 | var defaultColor = this._getDefaultColor(options);
|
171 |
|
172 | var value = defaultColor;
|
173 |
|
174 | if (!resource) {
|
175 | this._outputError(options, 'call .getColor(null) without resource.');
|
176 |
|
177 | return this._prepareResult(defaultColor);
|
178 | }
|
179 |
|
180 | var originalSize = this._getOriginalSize(resource),
|
181 | size = this._prepareSizeAndPosition(originalSize, options);
|
182 |
|
183 | if (!size.srcWidth || !size.srcHeight || !size.destWidth || !size.destHeight) {
|
184 | this._outputError(options, "incorrect sizes for resource \"".concat(resource.src, "\"."));
|
185 |
|
186 | return this._prepareResult(defaultColor);
|
187 | }
|
188 |
|
189 | if (!this._ctx) {
|
190 | this._canvas = this._makeCanvas();
|
191 | this._ctx = this._canvas.getContext && this._canvas.getContext('2d');
|
192 |
|
193 | if (!this._ctx) {
|
194 | this._outputError(options, 'Canvas Context 2D is not supported in this browser.');
|
195 |
|
196 | return this._prepareResult(defaultColor);
|
197 | }
|
198 | }
|
199 |
|
200 | this._canvas.width = size.destWidth;
|
201 | this._canvas.height = size.destHeight;
|
202 |
|
203 | try {
|
204 | this._ctx.clearRect(0, 0, size.destWidth, size.destHeight);
|
205 |
|
206 | this._ctx.drawImage(resource, size.srcLeft, size.srcTop, size.srcWidth, size.srcHeight, 0, 0, size.destWidth, size.destHeight);
|
207 |
|
208 | var bitmapData = this._ctx.getImageData(0, 0, size.destWidth, size.destHeight).data;
|
209 |
|
210 | value = this.getColorFromArray4(bitmapData, options);
|
211 | } catch (e) {
|
212 | this._outputError(options, "security error (CORS) for resource ".concat(resource.src, ".\nDetails: https://developer.mozilla.org/en/docs/Web/HTML/CORS_enabled_image"), e);
|
213 | }
|
214 |
|
215 | return this._prepareResult(value);
|
216 | }
|
217 | |
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 | }, {
|
230 | key: "getColorFromArray4",
|
231 | value: function getColorFromArray4(arr, options) {
|
232 | options = options || {};
|
233 | var bytesPerPixel = 4,
|
234 | arrLength = arr.length;
|
235 |
|
236 | if (arrLength < bytesPerPixel) {
|
237 | return this._getDefaultColor(options);
|
238 | }
|
239 |
|
240 | var len = arrLength - arrLength % bytesPerPixel,
|
241 | preparedStep = (options.step || 1) * bytesPerPixel;
|
242 | var algorithm;
|
243 |
|
244 | switch (options.algorithm || 'sqrt') {
|
245 | case 'simple':
|
246 | algorithm = simpleAlgorithm;
|
247 | break;
|
248 |
|
249 | case 'sqrt':
|
250 | algorithm = sqrtAlgorithm;
|
251 | break;
|
252 |
|
253 | case 'dominant':
|
254 | algorithm = dominantAlgorithm;
|
255 | break;
|
256 |
|
257 | default:
|
258 | throw new Error("".concat(ERROR_PREFIX).concat(options.algorithm, " is unknown algorithm."));
|
259 | }
|
260 |
|
261 | return algorithm(arr, len, preparedStep);
|
262 | }
|
263 | |
264 |
|
265 |
|
266 |
|
267 | }, {
|
268 | key: "destroy",
|
269 | value: function destroy() {
|
270 | delete this._canvas;
|
271 | delete this._ctx;
|
272 | }
|
273 | }, {
|
274 | key: "_getDefaultColor",
|
275 | value: function _getDefaultColor(options) {
|
276 | return this._getOption(options, 'defaultColor', [255, 255, 255, 255]);
|
277 | }
|
278 | }, {
|
279 | key: "_getOption",
|
280 | value: function _getOption(options, name, defaultValue) {
|
281 | return typeof options[name] === 'undefined' ? defaultValue : options[name];
|
282 | }
|
283 | }, {
|
284 | key: "_prepareSizeAndPosition",
|
285 | value: function _prepareSizeAndPosition(originalSize, options) {
|
286 | var srcLeft = this._getOption(options, 'left', 0),
|
287 | srcTop = this._getOption(options, 'top', 0),
|
288 | srcWidth = this._getOption(options, 'width', originalSize.width),
|
289 | srcHeight = this._getOption(options, 'height', originalSize.height),
|
290 | destWidth = srcWidth,
|
291 | destHeight = srcHeight;
|
292 |
|
293 | if (options.mode === 'precision') {
|
294 | return {
|
295 | srcLeft: srcLeft,
|
296 | srcTop: srcTop,
|
297 | srcWidth: srcWidth,
|
298 | srcHeight: srcHeight,
|
299 | destWidth: destWidth,
|
300 | destHeight: destHeight
|
301 | };
|
302 | }
|
303 |
|
304 | var maxSize = 100,
|
305 | minSize = 10;
|
306 | var factor;
|
307 |
|
308 | if (srcWidth > srcHeight) {
|
309 | factor = srcWidth / srcHeight;
|
310 | destWidth = maxSize;
|
311 | destHeight = Math.round(destWidth / factor);
|
312 | } else {
|
313 | factor = srcHeight / srcWidth;
|
314 | destHeight = maxSize;
|
315 | destWidth = Math.round(destHeight / factor);
|
316 | }
|
317 |
|
318 | if (destWidth > srcWidth || destHeight > srcHeight || destWidth < minSize || destHeight < minSize) {
|
319 | destWidth = srcWidth;
|
320 | destHeight = srcHeight;
|
321 | }
|
322 |
|
323 | return {
|
324 | srcLeft: srcLeft,
|
325 | srcTop: srcTop,
|
326 | srcWidth: srcWidth,
|
327 | srcHeight: srcHeight,
|
328 | destWidth: destWidth,
|
329 | destHeight: destHeight
|
330 | };
|
331 | }
|
332 | }, {
|
333 | key: "_bindImageEvents",
|
334 | value: function _bindImageEvents(resource, options) {
|
335 | var _this = this;
|
336 |
|
337 | return new Promise(function (resolve, reject) {
|
338 | var onload = function onload() {
|
339 | unbindEvents();
|
340 |
|
341 | var result = _this.getColor(resource, options);
|
342 |
|
343 | if (result.error) {
|
344 | reject(result.error);
|
345 | } else {
|
346 | resolve(result);
|
347 | }
|
348 | },
|
349 | onerror = function onerror() {
|
350 | unbindEvents();
|
351 | reject(new Error("".concat(ERROR_PREFIX, "Error loading image ").concat(resource.src, ".")));
|
352 | },
|
353 | onabort = function onabort() {
|
354 | unbindEvents();
|
355 | reject(new Error("".concat(ERROR_PREFIX, "Image \"").concat(resource.src, "\" loading aborted.")));
|
356 | },
|
357 | unbindEvents = function unbindEvents() {
|
358 | resource.removeEventListener('load', onload);
|
359 | resource.removeEventListener('error', onerror);
|
360 | resource.removeEventListener('abort', onabort);
|
361 | };
|
362 |
|
363 | resource.addEventListener('load', onload);
|
364 | resource.addEventListener('error', onerror);
|
365 | resource.addEventListener('abort', onabort);
|
366 | });
|
367 | }
|
368 | }, {
|
369 | key: "_prepareResult",
|
370 | value: function _prepareResult(value) {
|
371 | var rgb = value.slice(0, 3),
|
372 | rgba = [].concat(rgb, value[3] / 255),
|
373 | isDark = this._isDark(value);
|
374 |
|
375 | return {
|
376 | value: value,
|
377 | rgb: 'rgb(' + rgb.join(',') + ')',
|
378 | rgba: 'rgba(' + rgba.join(',') + ')',
|
379 | hex: this._arrayToHex(rgb),
|
380 | hexa: this._arrayToHex(value),
|
381 | isDark: isDark,
|
382 | isLight: !isDark
|
383 | };
|
384 | }
|
385 | }, {
|
386 | key: "_getOriginalSize",
|
387 | value: function _getOriginalSize(resource) {
|
388 | if (resource instanceof HTMLImageElement) {
|
389 | return {
|
390 | width: resource.naturalWidth,
|
391 | height: resource.naturalHeight
|
392 | };
|
393 | }
|
394 |
|
395 | if (resource instanceof HTMLVideoElement) {
|
396 | return {
|
397 | width: resource.videoWidth,
|
398 | height: resource.videoHeight
|
399 | };
|
400 | }
|
401 |
|
402 | return {
|
403 | width: resource.width,
|
404 | height: resource.height
|
405 | };
|
406 | }
|
407 | }, {
|
408 | key: "_toHex",
|
409 | value: function _toHex(num) {
|
410 | var str = num.toString(16);
|
411 | return str.length === 1 ? '0' + str : str;
|
412 | }
|
413 | }, {
|
414 | key: "_arrayToHex",
|
415 | value: function _arrayToHex(arr) {
|
416 | return '#' + arr.map(this._toHex).join('');
|
417 | }
|
418 | }, {
|
419 | key: "_isDark",
|
420 | value: function _isDark(color) {
|
421 |
|
422 | var result = (color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000;
|
423 | return result < 128;
|
424 | }
|
425 | }, {
|
426 | key: "_makeCanvas",
|
427 | value: function _makeCanvas() {
|
428 | return typeof window === 'undefined' ? new OffscreenCanvas(1, 1) : document.createElement('canvas');
|
429 | }
|
430 | }, {
|
431 | key: "_outputError",
|
432 | value: function _outputError(options, error, details) {
|
433 | if (!options.silent) {
|
434 |
|
435 | console.error("".concat(ERROR_PREFIX).concat(error));
|
436 |
|
437 | if (details) {
|
438 |
|
439 | console.error(details);
|
440 | }
|
441 | }
|
442 | }
|
443 | }]);
|
444 |
|
445 | return FastAverageColor;
|
446 | }();
|
447 |
|
448 | return FastAverageColor;
|
449 |
|
450 | })));
|