blob: c2ae5a5e29e293c82bc360fcc52973932557d06d [file] [log] [blame]
//
// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
//
part of charted.core.utils;
/// Represents a single color for use in visualizations. Currently, supports
/// representing and conversion between RGB, RGBA, HSL, HSLA and hex formats.
class Color {
// Internal representation of RGB
int _r = 0;
int _g = 0;
int _b = 0;
// Internal representation of HSL
int _h = 0; // 0 <= _h <= 360
int _s = 0; // 0 <= _s <= 100
int _l = 0; // 0 <= _l <= 100
// Alpha value for this color
double _a = 0.0;
// Flags to indicate the color-space for which values are readily available.
bool _hasRgbColors = false;
bool _hasHslColors = false;
/// Create an instance from RGB colors.
Color.fromRgba(this._r, this._g, this._b, this._a) : _hasRgbColors = true;
/// Create an instance using a string representing color in RGB color space.
/// The input string, [value], can be one of the following formats:
///
/// #RGB
/// #RRGGBB
///
/// rgb(R, G, B)
/// rgba(R, G, B, A)
///
/// R, G and B represent intensities of Red, Green and Blue channels and A
/// represents the alpha channel (transparency)
///
/// When using these formats:
/// 0 <= R,G,B <= 255
/// 0 <= A <= 1.0
factory Color.fromRgbString(String value) =>
isHexColorString(value) ? _fromHexString(value) : _fromRgbString(value);
/// Create an instance from HSL colors.
Color.fromHsla(this._h, this._s, this._l, this._a) : _hasHslColors = true;
/// Create an instance using a string representing color in HSL color space.
/// The input string, [value], can be in one of the following formats:
///
/// hsl(H, S%, L%)
/// hsla(H, S%, L%, A)
///
/// H, S and L represent Hue, Saturation and Luminosity respectively.
///
/// When using these formats:
/// 0 <= H <= 360
/// 0 <= S,L <= 100
/// 0 <= A <= 1.0
factory Color.fromHslString(String value) => _fromHslString(value);
/// Ensures that the RGB values are available. If they aren't already
/// available, they are computed from the HSA values.
///
/// Based on color.js from Google Closure library
void toRgb() {
if (_hasRgbColors) return;
num _hueToRgb(num v1, num v2, num vH) {
vH %= 1.0;
if ((6 * vH) < 1) {
return (v1 + (v2 - v1) * 6 * vH);
} else if (2 * vH < 1) {
return v2;
} else if (3 * vH < 2) {
return (v1 + (v2 - v1) * ((2 / 3) - vH) * 6);
}
return v1;
}
final h = _h / 360;
if (_s == 0) {
_r = _g = _b = (_l * 255).round();
} else {
var temp1 = 0;
var temp2 = 0;
if (_l < 0.5) {
temp2 = _l * (1 + _s);
} else {
temp2 = _l + _s - (_s * _l);
}
temp1 = 2 * _l - temp2;
_r = (255 * _hueToRgb(temp1, temp2, h + (1 / 3))).round();
_g = (255 * _hueToRgb(temp1, temp2, h)).round();
_b = (255 * _hueToRgb(temp1, temp2, h - (1 / 3))).round();
}
}
/// Ensures that the HSA values are available. If they aren't already
/// available, they are computed from the RGB values.
///
/// Based on color.js in Google Closure library.
void toHsl() {
if (_hasHslColors) return;
final r = _r / 255;
final g = _g / 255;
final b = _b / 255;
final max = math.max(r, math.max(g, b));
final min = math.min(r, math.min(g, b));
double l = (max + min) / 2;
double h = 0.0;
double s = 0.0;
// If max and min are equal, the color is gray (h = s = 0)
if (max != min) {
if (max == r) {
h = 60 * (g - b) / (max - min);
} else if (max == g) {
h = 60 * (b - r) / (max - min) + 120;
} else if (max == b) {
h = 60 * (r - g) / (max - min) + 240;
}
if (0 < l && l <= 0.5) {
s = (max - min) / (2 * l);
} else {
s = (max - min) / (2 - 2 * l);
}
}
_h = (h % 360).floor();
_s = (s * 100).floor();
_l = (l * 100).floor();
}
/// Returns a hex string of the format '#RRGGBB' representing this color.
/// A new string will be returned each time this function is called.
String toHexString() {
toRgb();
return rgbToHexString(_r, _g, _b);
}
/// Returns a string similar to 'rgba(r, g, b, a)' representing this color.
/// A new string will be returned each time this function is called.
String toRgbaString() {
toRgb();
return 'rgba($_r,$_g,$_b,$_a)';
}
/// Returns a string similar to 'hsla(h, s, l, a)' representing this color.
/// A new string will be returned each time this function is called.
String toHslaString() {
toHsl();
return 'hsla($_h,$_s%,$_l%,$_a)';
}
/// Intensity of red from RGB.
/// Computes RGB values if they are not already available.
int get r {
toRgb();
return _r;
}
/// Intensity of green from RGB
/// Computes RGB values if they are not already available.
int get g {
toRgb();
return _g;
}
/// Intensity of blue from RGB.
/// Computes RGB values if they are not already available.
int get b {
toRgb();
return _b;
}
/// Hue value from HSL representation.
/// Computes HSL values if they are not already available.
int get h {
toHsl();
return _h;
}
/// Saturation value from HSL representation.
/// Computes HSL values if they are not already available.
int get s {
toHsl();
return _s;
}
/// Luminosity value from HSL representation.
/// Computes HSL values if they are not already available.
int get l {
toHsl();
return _l;
}
/// Alpha value used by both RGB and HSL representations.
double get a => _a;
@override
String toString() => _hasRgbColors ? toRgbaString() : toHslaString();
@override
int get hashCode => toString().hashCode;
/// Given RGB values create hex string from it.
static String rgbToHexString(int r, int g, int b) {
String _hexify(int v) {
return v < 0x10
? "0" + math.max(0, v).toInt().toRadixString(16)
: math.min(255, v).toInt().toRadixString(16);
}
return '#${_hexify(r)}${_hexify(g)}${_hexify(b)}';
}
/// RegExp to test if a given string is a hex color string
static final RegExp hexColorRegExp =
new RegExp(r'^#([0-9a-f]{3}){1,2}$', caseSensitive: false);
/// Tests if [str] is a hex color
static bool isHexColorString(String str) => hexColorRegExp.hasMatch(str);
/// RegExp to test if a given string is rgb() or rgba() color.
static final RegExp rgbaColorRegExp = new RegExp(
r'^(rgb|rgba)?\(\d+,\s?\d+,\s?\d+(,\s?(0|1)?(\.\d)?\d*)?\)$',
caseSensitive: false);
/// Tests if [str] is a color represented by rgb() or rgba() or hex string
static bool isRgbColorString(String str) =>
isHexColorString(str) || rgbaColorRegExp.hasMatch(str);
/// RegExp to test if a given string is hsl() or hsla() color.
static final RegExp hslaColorRegExp = new RegExp(
r'^(hsl|hsla)?\(\d+,\s?\d+%,\s?\d+%(,\s?(0|1)?(\.\d)?\d*)?\)$',
caseSensitive: false);
/// Tests if [str] is a color represented by hsl() or hsla()
static bool isHslColorString(String str) => hslaColorRegExp.hasMatch(str);
/// Create an instance using the passed RGB string.
static Color _fromRgbString(String value) {
int pos = (value.startsWith('rgb(') || value.startsWith('RGB('))
? 4
: (value.startsWith('rgba(') || value.startsWith('RGBA(')) ? 5 : 0;
if (pos != 0) {
final params = value.substring(pos, value.length - 1).split(',');
int r = int.parse(params[0]),
g = int.parse(params[1]),
b = int.parse(params[2]);
double a = params.length == 3 ? 1.0 : double.parse(params[3]);
return new Color.fromRgba(r, g, b, a);
}
return new Color.fromRgba(0, 0, 0, 0.0);
}
/// Create an instance using the passed HEX string.
/// Assumes that the string starts with a '#' before HEX chars.
static Color _fromHexString(String hex) {
if (isNullOrEmpty(hex) || (hex.length != 4 && hex.length != 7)) {
return new Color.fromRgba(0, 0, 0, 0.0);
}
int rgb = 0;
hex = hex.substring(1);
if (hex.length == 3) {
for (int i = 0; i < hex.length; i++) {
final val = int.parse(hex[i], radix: 16);
rgb = (rgb * 16 + val) * 16 + val;
}
} else if (hex.length == 6) {
rgb = int.parse(hex, radix: 16);
}
return new Color.fromRgba(
(rgb & 0xff0000) >> 0x10, (rgb & 0xff00) >> 8, (rgb & 0xff), 1.0);
}
/// Create an instance using the passed HSL color string.
static Color _fromHslString(String value) {
int pos = (value.startsWith('hsl(') || value.startsWith('HSL('))
? 4
: (value.startsWith('hsla(') || value.startsWith('HSLA(')) ? 5 : 0;
if (pos != 0) {
final params = value.substring(pos, value.length - 1).split(',');
int h = int.parse(params[0]),
s = int.parse(params[1]),
l = int.parse(params[2]);
double a = params.length == 3 ? 1.0 : double.parse(params[3]);
return new Color.fromHsla(h, s, l, a);
}
return new Color.fromHsla(0, 0, 0, 0.0);
}
}