# Source: src/ol/color/color.js

    // We can't use goog.color or goog.color.alpha because they interally use a hex
    // string representation that encodes each channel in a single byte.  This
    // causes occasional loss of precision and rounding errors, especially in the
    // alpha channel.

    goog.provide('ol.color');

    goog.require('goog.asserts');
    goog.require('goog.color');
    goog.require('goog.color.names');
    goog.require('goog.math');
    goog.require('goog.vec.Mat4');
    goog.require('ol');

    /**
     * A color represented as a short array [red, green, blue, alpha].
     * red, green, and blue should be integers in the range 0..255 inclusive.
     * alpha should be a float in the range 0..1 inclusive.
     * @typedef {Array.<number>}
     */
    ol.Color;

    /**
     * This RegExp matches # followed by 3 or 6 hex digits.
     * @const
     * @type {RegExp}
     * @private
     */
    ol.color.hexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i;

    /**
     * @see goog.color.rgbColorRe_
     * @const
     * @type {RegExp}
     * @private
     */
    ol.color.rgbColorRe_ =
        /^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;

    /**
     * @see goog.color.alpha.rgbaColorRe_
     * @const
     * @type {RegExp}
     * @private
     */
    ol.color.rgbaColorRe_ =
        /^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i;

    /**
     * @param {ol.Color} dst Destination.
     * @param {ol.Color} src Source.
     * @param {ol.Color=} opt_color Color.
     * @return {ol.Color} Color.
     */
    ol.color.blend = function(dst, src, opt_color) {
      // http://en.wikipedia.org/wiki/Alpha_compositing
      // FIXME do we need to scale by 255?
      var out = goog.isDef(opt_color) ? opt_color : [];
      var dstA = dst[3];
      var srcA = dst[3];
      if (dstA == 1) {
        out[0] = (src[0] * srcA + dst[0] * (1 - srcA) + 0.5) | 0;
        out[1] = (src[1] * srcA + dst[1] * (1 - srcA) + 0.5) | 0;
        out[2] = (src[2] * srcA + dst[2] * (1 - srcA) + 0.5) | 0;
        out[4] = 1;
      } else if (srcA === 0) {
        out[0] = dst[0];
        out[1] = dst[1];
        out[2] = dst[2];
        out[3] = dstA;
      } else {
        var outA = srcA + dstA * (1 - srcA);
        if (outA === 0) {
          out[0] = 0;
          out[1] = 0;
          out[2] = 0;
          out[3] = 0;
        } else {
          out[0] = ((src[0] * srcA + dst[0] * dstA * (1 - srcA)) / outA + 0.5) | 0;
          out[1] = ((src[1] * srcA + dst[1] * dstA * (1 - srcA)) / outA + 0.5) | 0;
          out[2] = ((src[2] * srcA + dst[2] * dstA * (1 - srcA)) / outA + 0.5) | 0;
          out[3] = outA;
        }
      }
      goog.asserts.assert(ol.color.isValid(out));
      return out;
    };

    /**
     * @param {ol.Color|string} color Color.
     * @return {ol.Color} Color.
     * @todo api
     */
    ol.color.asArray = function(color) {
      if (goog.isArray(color)) {
        return color;
      } else {
        goog.asserts.assert(goog.isString(color));
        return ol.color.fromString(color);
      }
    };

    /**
     * @param {ol.Color|string} color Color.
     * @return {string} String.
     * @todo api
     */
    ol.color.asString = function(color) {
      if (goog.isString(color)) {
        return color;
      } else {
        goog.asserts.assert(goog.isArray(color));
        return ol.color.toString(color);
      }
    };

    /**
     * @param {ol.Color} color1 Color1.
     * @param {ol.Color} color2 Color2.
     * @return {boolean} Equals.
     */
    ol.color.equals = function(color1, color2) {
      return color1 === color2 || (
          color1[0] == color2[0] && color1[1] == color2[1] &&
          color1[2] == color2[2] && color1[3] == color2[3]);
    };

    /**
     * @param {string} s String.
     * @param {ol.Color=} opt_color Color.
     * @return {ol.Color} Color.
     */
    ol.color.fromString = (
        /**
         * @return {function(string, ol.Color=): ol.Color}
         */
        function() {

          // We maintain a small cache of parsed strings.  To provide cheap LRU-like
          // semantics, whenever the cache grows too large we simply delete an
          // arbitrary 25% of the entries.

          /**
           * @const
           * @type {number}
           */
          var MAX_CACHE_SIZE = 1024;

          /**
           * @type {Object.<string, ol.Color>}
           */
          var cache = {};

          /**
           * @type {number}
           */
          var cacheSize = 0;

          return (
              /**
               * @param {string} s String.
               * @param {ol.Color=} opt_color Color.
               * @return {ol.Color} Color.
               */
              function(s, opt_color) {
                var color;
                if (cache.hasOwnProperty(s)) {
                  color = cache[s];
                } else {
                  if (cacheSize >= MAX_CACHE_SIZE) {
                    var i = 0;
                    var key;
                    for (key in cache) {
                      if ((i++ & 3) === 0) {
                        delete cache[key];
                        --cacheSize;
                      }
                    }
                  }
                  color = ol.color.fromStringInternal_(s);
                  cache[s] = color;
                  ++cacheSize;
                }
                return ol.color.returnOrUpdate(color, opt_color);
              });

        })();

    /**
     * @param {string} s String.
     * @private
     * @return {ol.Color} Color.
     */
    ol.color.fromStringInternal_ = function(s) {

      var isHex = false;
      if (ol.ENABLE_NAMED_COLORS && goog.color.names.hasOwnProperty(s)) {
        // goog.color.names does not have a type declaration, so add a typecast
        s = /** @type {string} */ (goog.color.names[s]);
        isHex = true;
      }

      var r, g, b, a, color, match;
      if (isHex || (match = ol.color.hexColorRe_.exec(s))) { // hex
        var n = s.length - 1; // number of hex digits
        goog.asserts.assert(n == 3 || n == 6);
        var d = n == 3 ? 1 : 2; // number of digits per channel
        r = parseInt(s.substr(1 + 0 * d, d), 16);
        g = parseInt(s.substr(1 + 1 * d, d), 16);
        b = parseInt(s.substr(1 + 2 * d, d), 16);
        if (d == 1) {
          r = (r << 4) + r;
          g = (g << 4) + g;
          b = (b << 4) + b;
        }
        a = 1;
        color = [r, g, b, a];
        goog.asserts.assert(ol.color.isValid(color));
        return color;
      } else if ((match = ol.color.rgbaColorRe_.exec(s))) { // rgba()
        r = Number(match[1]);
        g = Number(match[2]);
        b = Number(match[3]);
        a = Number(match[4]);
        color = [r, g, b, a];
        return ol.color.normalize(color, color);
      } else if ((match = ol.color.rgbColorRe_.exec(s))) { // rgb()
        r = Number(match[1]);
        g = Number(match[2]);
        b = Number(match[3]);
        color = [r, g, b, 1];
        return ol.color.normalize(color, color);
      } else {
        throw new Error(s + ' is not a valid color');
      }

    };

    /**
     * @param {ol.Color} color Color.
     * @return {boolean} Is valid.
     */
    ol.color.isValid = function(color) {
      return 0 <= color[0] && color[0] < 256 &&
          0 <= color[1] && color[1] < 256 &&
          0 <= color[2] && color[2] < 256 &&
          0 <= color[3] && color[3] <= 1;
    };

    /**
     * @param {ol.Color} color Color.
     * @param {ol.Color=} opt_color Color.
     * @return {ol.Color} Clamped color.
     */
    ol.color.normalize = function(color, opt_color) {
      var result = goog.isDef(opt_color) ? opt_color : [];
      result[0] = goog.math.clamp((color[0] + 0.5) | 0, 0, 255);
      result[1] = goog.math.clamp((color[1] + 0.5) | 0, 0, 255);
      result[2] = goog.math.clamp((color[2] + 0.5) | 0, 0, 255);
      result[3] = goog.math.clamp(color[3], 0, 1);
      return result;
    };

    /**
     * @param {ol.Color} color Color.
     * @param {ol.Color=} opt_color Color.
     * @return {ol.Color} Color.
     */
    ol.color.returnOrUpdate = function(color, opt_color) {
      if (goog.isDef(opt_color)) {
        opt_color[0] = color[0];
        opt_color[1] = color[1];
        opt_color[2] = color[2];
        opt_color[3] = color[3];
        return opt_color;
      } else {
        return color;
      }
    };

    /**
     * @param {ol.Color} color Color.
     * @return {string} String.
     */
    ol.color.toString = function(color) {
      var r = color[0];
      if (r != (r | 0)) {
        r = (r + 0.5) | 0;
      }
      var g = color[1];
      if (g != (g | 0)) {
        g = (g + 0.5) | 0;
      }
      var b = color[2];
      if (b != (b | 0)) {
        b = (b + 0.5) | 0;
      }
      var a = color[3];
      return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
    };

    /**
     * @param {ol.Color} color Color.
     * @param {goog.vec.Mat4.Number} transform Transform.
     * @param {ol.Color=} opt_color Color.
     * @return {ol.Color} Transformed color.
     */
    ol.color.transform = function(color, transform, opt_color) {
      var result = goog.isDef(opt_color) ? opt_color : [];
      result = goog.vec.Mat4.multVec3(transform, color, result);
      goog.asserts.assert(goog.isArray(result));
      result[3] = color[3];
      return ol.color.normalize(result, result);
    };

    /**
     * @param {ol.Color|string} color1 Color2.
     * @param {ol.Color|string} color2 Color2.
     * @return {boolean} Equals.
     */
    ol.color.stringOrColorEquals = function(color1, color2) {
      if (color1 === color2 || color1 == color2) {
        return true;
      }
      if (goog.isString(color1)) {
        color1 = ol.color.fromString(color1);
      }
      if (goog.isString(color2)) {
        color2 = ol.color.fromString(color2);
      }
      return ol.color.equals(color1, color2);
    };