UNPKG

color-classifier

Version:

Classify the color along the reference color. using algorithm the CIEDE2000, RGB, HSV.

575 lines (469 loc) 16.1 kB
/*! * color-classifier * Classify the color along the reference color. * * @author tsuyoshiwada * @license MIT * @version 0.0.1 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.ColorClassifier = factory()); }(this, function () { 'use strict'; var babelHelpers = {}; babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; babelHelpers.classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; babelHelpers.createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); babelHelpers.defineProperty = function (obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }; babelHelpers.slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); babelHelpers.toConsumableArray = function (arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }; babelHelpers; function minBy(array, key) { var sortedArray = [].concat(babelHelpers.toConsumableArray(array)); sortedArray.sort(function (a, b) { if (a[key] < b[key]) return -1; if (a[key] > b[key]) return 1; return 0; }); return sortedArray.shift(); } var HEX_SHORT = /^#([a-fA-F0-9]{3})$/; var HEX = /^#([a-fA-F0-9]{6})$/; function roundColors(obj, round) { if (!round) return obj; var o = {}; for (var k in obj) { o[k] = Math.round(obj[k]); } return o; } function hasProp(obj, key) { return obj.hasOwnProperty(key); } function isRgb(obj) { return hasProp(obj, "r") && hasProp(obj, "g") && hasProp(obj, "b"); } var Color = function () { babelHelpers.createClass(Color, null, [{ key: "normalizeHex", value: function normalizeHex(hex) { if (HEX.test(hex)) { return hex; } else if (HEX_SHORT.test(hex)) { var r = hex.slice(1, 2); var g = hex.slice(2, 3); var b = hex.slice(3, 4); return "#" + (r + r) + (g + g) + (b + b); } return null; } }, { key: "hexToRgb", value: function hexToRgb(hex) { var normalizedHex = this.normalizeHex(hex); if (normalizedHex == null) { return null; } var m = normalizedHex.match(HEX); var i = parseInt(m[1], 16); var r = i >> 16 & 0xFF; var g = i >> 8 & 0xFF; var b = i & 0xFF; return { r: r, g: g, b: b }; } }, { key: "rgbToHex", value: function rgbToHex(rgb) { var r = rgb.r; var g = rgb.g; var b = rgb.b; var i = ((Math.round(r) & 0xFF) << 16) + ((Math.round(g) & 0xFF) << 8) + (Math.round(b) & 0xFF); var s = i.toString(16).toLowerCase(); return "#" + ("000000".substring(s.length) + s); } }, { key: "rgbToHsv", value: function rgbToHsv(rgb) { var round = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; var r = rgb.r; var g = rgb.g; var b = rgb.b; var min = Math.min(r, g, b); var max = Math.max(r, g, b); var delta = max - min; var hsv = {}; if (max === 0) { hsv.s = 0; } else { hsv.s = delta / max * 1000 / 10; } if (max === min) { hsv.h = 0; } else if (r === max) { hsv.h = (g - b) / delta; } else if (g === max) { hsv.h = 2 + (b - r) / delta; } else { hsv.h = 4 + (r - g) / delta; } hsv.h = Math.min(hsv.h * 60, 360); hsv.h = hsv.h < 0 ? hsv.h + 360 : hsv.h; hsv.v = max / 255 * 1000 / 10; return roundColors(hsv, round); } }, { key: "rgbToXyz", value: function rgbToXyz(rgb) { var round = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; var r = rgb.r / 255; var g = rgb.g / 255; var b = rgb.b / 255; var rr = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92; var gg = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92; var bb = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; var x = (rr * 0.4124 + gg * 0.3576 + bb * 0.1805) * 100; var y = (rr * 0.2126 + gg * 0.7152 + bb * 0.0722) * 100; var z = (rr * 0.0193 + gg * 0.1192 + bb * 0.9505) * 100; return roundColors({ x: x, y: y, z: z }, round); } }, { key: "rgbToLab", value: function rgbToLab(rgb) { var round = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; var xyz = Color.rgbToXyz(rgb, false); var x = xyz.x; var y = xyz.y; var z = xyz.z; x /= 95.047; y /= 100; z /= 108.883; x = x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787 * x + 16 / 116; y = y > 0.008856 ? Math.pow(y, 1 / 3) : 7.787 * y + 16 / 116; z = z > 0.008856 ? Math.pow(z, 1 / 3) : 7.787 * z + 16 / 116; var l = 116 * y - 16; var a = 500 * (x - y); var b = 200 * (y - z); return roundColors({ l: l, a: a, b: b }, round); } }]); function Color(value) { babelHelpers.classCallCheck(this, Color); this.original = value; if (isRgb(value)) { this.rgb = value; this.hex = Color.rgbToHex(value); } else { this.hex = Color.normalizeHex(value); this.rgb = Color.hexToRgb(this.hex); } this.hsv = Color.rgbToHsv(this.rgb); } return Color; }(); function radians(n) { return n * Math.PI / 180; } function degrees(n) { return n * (180 / Math.PI); } function hypot(a, b) { return Math.sqrt(a * a + b * b); } function pow2(n) { return n * n; } function pow7(n) { return n * n * n * n * n * n * n; } var _diffMethodMap; var abs = Math.abs; var atan2 = Math.atan2; var cos = Math.cos; var min = Math.min; var exp = Math.exp; var sqrt = Math.sqrt; var sin = Math.sin; var AlgorithmTypes = { CIEDE2000: "CIEDE2000", HSV: "HSV", RGB: "RGB" }; var diffMethodMap = (_diffMethodMap = {}, babelHelpers.defineProperty(_diffMethodMap, AlgorithmTypes.CIEDE2000, "ciede2000"), babelHelpers.defineProperty(_diffMethodMap, AlgorithmTypes.HSV, "hsv"), babelHelpers.defineProperty(_diffMethodMap, AlgorithmTypes.RGB, "rgb"), _diffMethodMap); var kl = 1; var kc = 1; var kh = 1; var pow7_25 = pow7(25); var ColorDiff = function () { function ColorDiff() { babelHelpers.classCallCheck(this, ColorDiff); } babelHelpers.createClass(ColorDiff, null, [{ key: "diff", value: function diff(algorithmType, color1, color2) { var method = diffMethodMap[algorithmType]; return ColorDiff[method](color1, color2); } }, { key: "ciede2000", value: function ciede2000(color1, color2) { var a = Color.rgbToLab(color1.rgb); var b = Color.rgbToLab(color2.rgb); return ColorDiff._ciede2000(a.l, a.a, a.b, b.l, b.a, b.b); } }, { key: "_ciede2000", value: function _ciede2000(l1, a1, b1, l2, a2, b2) { var c1 = hypot(a1, b1); var c2 = hypot(a2, b2); var ac1c2 = (c1 + c2) / 2; var g = 0.5 * (1 - sqrt(pow7(ac1c2) / (pow7(ac1c2) + pow7_25))); var a1p = (1 + g) * a1; var a2p = (1 + g) * a2; var c1p = sqrt(pow2(a1p) + pow2(b1)); var c2p = sqrt(pow2(a2p) + pow2(b2)); var h1pd = degrees(atan2(b1, a1p)); var h1p = b1 === 0 && a1p === 0 ? 0 : h1pd > 0 ? h1pd : h1pd + 360; var h2pd = degrees(atan2(b2, a2p)); var h2p = b2 === 0 && a2p === 0 ? 0 : h2pd > 0 ? h2pd : h2pd + 360; var dlp = l2 - l1; var dcp = c2p - c1p; var dhp = 2 * sqrt(c1p * c2p) * sin(radians(c1 * c2 === 0 ? 0 : abs(h2p - h1p) <= 180 ? h2p - h1p : h2p - h1p > 180 ? h2p - h1p - 360 : h2p - h1p + 360) / 2); var al = (l1 + l2) / 2; var acp = (c1p + c2p) / 2; var ahp = void 0; if (c1 * c2 === 0) { ahp = h1p + h2p; } else if (abs(h1p - h2p) <= 180) { ahp = (h1p + h2p) / 2; } else if (abs(h1p - h2p) > 180 && h1p + h2p < 360) { ahp = (h1p + h2p + 360) / 2; } else { ahp = (h1p + h2p - 360) / 2; } var t = 1 - 0.17 * cos(radians(ahp - 30)) + 0.24 * cos(radians(2 * ahp)) + 0.32 * cos(radians(3 * ahp + 6)) - 0.20 * cos(radians(4 * ahp - 63)); var dro = 30 * exp(-pow2((ahp - 275) / 25)); var rc = sqrt(pow7(acp) / (pow7(acp) + pow7_25)); var sl = 1 + 0.015 * pow2(al - 50) / sqrt(20 + pow2(al - 50)); var sc = 1 + 0.045 * acp; var sh = 1 + 0.015 * acp * t; var rt = -2 * rc * sin(radians(2 * dro)); return sqrt(pow2(dlp / (sl * kl)) + pow2(dcp / (sc * kc)) + pow2(dhp / (sh * kh)) + rt * (dcp / (sc * kc)) * (dhp / (sh * kh))); } }, { key: "hsv", value: function hsv(color1, color2) { var a = color1.hsv; var b = color2.hsv; var h = 0; if (a.h > b.h) { h = min(a.h - b.h, b.h - a.h + 360); } else { h = min(b.h - a.h, a.h - b.h + 360); } return sqrt(pow2(h) + pow2(a.s - b.s) + pow2(a.v - b.v)); } }, { key: "rgb", value: function rgb(color1, color2) { var a = color1.rgb; var b = color2.rgb; return sqrt(pow2(a.r - b.r) + pow2(a.g - b.g) + pow2(a.b - b.b)); } }]); return ColorDiff; }(); // https://www.w3.org/TR/css3-color/#html4 var W3C = ["#000000", "#808080", "#c0c0c0", "#ffffff", "#800000", "#ff0000", "#008000", "#00ff00", "#808000", "#ffff00", "#008080", "#00ffff", "#000080", "#0000ff", "#800080", "#ff00ff"]; var RAINBOW = ["#000000", "#808080", "#ffffff", "#ff0000", "#ffa500", "#ffff00", "#008000", "#00ffff", "#0000ff", "#800080"]; var Palette = Object.freeze({ W3C: W3C, RAINBOW: RAINBOW }); function equal(a, b) { if (a === b) return true; var ka = void 0; var kb = void 0; try { ka = Object.keys(a); kb = Object.keys(b); } catch (e) { return false; } if (ka.length !== kb.length) return false; for (var i = 0; ka.length - 1; i++) { var key = ka[i]; if (a[key] !== b[key]) return false; } return (typeof a === "undefined" ? "undefined" : babelHelpers.typeof(a)) === (typeof b === "undefined" ? "undefined" : babelHelpers.typeof(b)); } var ColorClassifier = function () { babelHelpers.createClass(ColorClassifier, null, [{ key: "throwError", value: function throwError(msg) { throw new Error("[ColorClassifier] " + msg); } }]); function ColorClassifier() { var palette = arguments.length <= 0 || arguments[0] === undefined ? W3C : arguments[0]; var algorithmType = arguments.length <= 1 || arguments[1] === undefined ? AlgorithmTypes.CIEDE2000 : arguments[1]; babelHelpers.classCallCheck(this, ColorClassifier); this.setPalette(palette); this.setAlgorithmType(algorithmType); } babelHelpers.createClass(ColorClassifier, [{ key: "setPalette", value: function setPalette(palette) { if (!Array.isArray(palette)) { ColorClassifier.throwError("palette is should be a Array."); } this.palette = palette.map(function (c) { return new Color(c); }); } }, { key: "getPalette", value: function getPalette() { return this.palette; } }, { key: "setAlgorithmType", value: function setAlgorithmType(algorithmType) { if (!AlgorithmTypes.hasOwnProperty(algorithmType)) { ColorClassifier.throwError(algorithmType + " is an undefined algorithm type."); } this.algorithmType = algorithmType; } }, { key: "getAlgorithmType", value: function getAlgorithmType() { return this.algorithmType; } }, { key: "classify", value: function classify(value) { var format = arguments.length <= 1 || arguments[1] === undefined ? "rgb" : arguments[1]; var palette = this.palette; var algorithmType = this.algorithmType; var color = new Color(value); var array = []; palette.forEach(function (paletteColor) { array.push({ distance: ColorDiff.diff(algorithmType, paletteColor, color), color: format === "raw" ? paletteColor : paletteColor[format] }); }); return minBy(array, "distance").color; } }, { key: "classifyFromArray", value: function classifyFromArray(colors) { var _this = this; var format = arguments.length <= 1 || arguments[1] === undefined ? "rgb" : arguments[1]; var results = []; var array = []; colors.forEach(function (value) { var color = new Color(value); var palette = _this.classify(color.rgb, "raw"); array.push({ palette: palette, color: color }); }); array.forEach(function (obj) { var palette = obj.palette; var color = obj.color; var _results$filter = results.filter(function (o) { return equal(o.palette, palette[format]); }); var _results$filter2 = babelHelpers.slicedToArray(_results$filter, 1); var paletteColor = _results$filter2[0]; if (!paletteColor) { results.push({ palette: palette[format], colors: [color[format]] }); } else { paletteColor.colors.push(color[format]); } }); return results; } }]); return ColorClassifier; }(); ColorClassifier.Palette = Palette; ColorClassifier.AlgorithmTypes = AlgorithmTypes; return ColorClassifier; }));