diff --git a/src/ol/color.js b/src/ol/color.js index f8bfc08b8e..39d7edbf93 100644 --- a/src/ol/color.js +++ b/src/ol/color.js @@ -1,15 +1,16 @@ -// ol.color is based on goog.color and goog.color.alpha -// goog.color and goog.color.alpha use a hex string representation that encodes -// each channel as a byte (a two character hex string). This causes occasional -// loss of precision and rounding errors, especially in the alpha channel. -// FIXME don't use goog.color or goog.color.alpha +// 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. + // FIXME move the color matrix code from ol.renderer.webgl.Layer to here goog.provide('ol.color'); +goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.color'); -goog.require('goog.color.alpha'); +goog.require('goog.color.names'); goog.require('goog.math'); goog.require('goog.vec.Mat4'); @@ -23,6 +24,32 @@ goog.require('goog.vec.Mat4'); ol.Color; +/** + * @type {RegExp} + * @private + * This RegExp matches # followed by 3, 4, 6, or 8 hex digits. + */ +ol.color.hexColorRe_ = /^#(?:[0-9a-f]{3,4}){1,2}$/i; + + +/** + * @type {RegExp} + * @private + * @see goog.color.rgbColorRe_ + */ +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; + + +/** + * @type {RegExp} + * @private + * @see goog.color.alpha.rgbaColorRe_ + */ +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 {string} s String. * @param {ol.Color=} opt_color Color. @@ -87,17 +114,50 @@ ol.color.fromString = (function() { */ ol.color.fromStringInternal_ = function(s) { - /** @preserveTry */ - try { - var rgba = goog.color.alpha.parse(s); - return goog.color.alpha.hexToRgba(rgba.hex); - } catch (e) { - // goog.color.alpha.parse throws an Error on named and rgb-style colors. - var rgb = goog.color.parse(s); - var result = goog.color.hexToRgb(rgb.hex); - result.push(1); - return result; + var isHex = false; + if (goog.color.names.hasOwnProperty(s)) { + s = 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(goog.array.indexOf([3, 4, 6, 8], n) != -1); + var d = n < 6 ? 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; + } + if ((n >> 1) & 1) { + a = 1; + } else { // has alpha channel + a = parseInt(s.substr(1 + 3 * d, d), 16) / (d == 1 ? 15 : 255); + } + 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'); + } + }; diff --git a/test/spec/ol/color.test.js b/test/spec/ol/color.test.js index db3bfc87a9..8cb37f7397 100644 --- a/test/spec/ol/color.test.js +++ b/test/spec/ol/color.test.js @@ -17,8 +17,21 @@ describe('ol.color', function() { expect(ol.color.fromString('red')).to.eql([255, 0, 0, 1]); }); - it('can parse hex colors', function() { - expect(ol.color.fromString('#00ff0080')).to.eql([0, 255, 0, 128 / 255]); + it('can parse 3-digit hex colors', function() { + expect(ol.color.fromString('#087')).to.eql([0, 136, 119, 1]); + }); + + it('can parse 4-digit hex colors', function() { + expect(ol.color.fromString('#1234')).to.eql([17, 34, 51, 68 / 255]); + }); + + it('can parse 6-digit hex colors', function() { + expect(ol.color.fromString('#56789a')).to.eql([86, 120, 154, 1]); + }); + + it('can parse 8-digit hex colors', function() { + expect(ol.color.fromString('#bcdef012')).to.eql( + [188, 222, 240, 18 / 255]); }); it('can parse rgb colors', function() { @@ -27,7 +40,7 @@ describe('ol.color', function() { it('can parse rgba colors', function() { expect(ol.color.fromString('rgba(255, 255, 0, 0.1)')).to.eql( - [255, 255, 0, 25 / 255]); + [255, 255, 0, 0.1]); }); it('caches parsed values', function() { @@ -38,6 +51,16 @@ describe('ol.color', function() { expect(ol.color.fromStringInternal_.callCount).to.be(count + 1); }); + it('throws an error on invalid colors', function() { + var invalidColors = ['tuesday', '#1234567', 'rgb(255.0,0,0)']; + var i, ii; + for (i = 0, ii < invalidColors.length; i < ii; ++i) { + expect(function() { + ol.color.fromString(invalidColors[i]); + }).to.throwException(); + } + }); + }); describe('ol.color.isValid', function() {