Adding HSLColor and color 'within' matchers for HSVColor and HSLColor (#18294)
This adds an HSLColor class which uses a perceptual color space based upon human perception of colored light (as opposed to HSV, which is based on pigment colors). You can see the difference in the color spaces here: https://en.wikipedia.org/wiki/HSL_and_HSV I also added a "within" matcher for both HSLColor and HSVColor that will check if the (floating point) color components are within a certain error. And tests.
This commit is contained in:
parent
bd4cf62821
commit
efa2a474ea
@ -167,7 +167,7 @@ class _ChipDemoState extends State<ChipDemo> {
|
||||
Color _nameToColor(String name) {
|
||||
assert(name.length > 1);
|
||||
final int hash = name.hashCode & 0xffff;
|
||||
final double hue = 360.0 * hash / (1 << 15);
|
||||
final double hue = (360.0 * hash / (1 << 15)) % 360.0;
|
||||
return new HSVColor.fromAHSV(1.0, hue, 0.4, 0.90).toColor();
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,81 @@ import 'dart:ui' show Color, lerpDouble, hashValues;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
double _getHue(double red, double green, double blue, double max, double delta) {
|
||||
double hue;
|
||||
if (max == 0.0) {
|
||||
hue = 0.0;
|
||||
} else if (max == red) {
|
||||
hue = 60.0 * (((green - blue) / delta) % 6);
|
||||
} else if (max == green) {
|
||||
hue = 60.0 * (((blue - red) / delta) + 2);
|
||||
} else if (max == blue) {
|
||||
hue = 60.0 * (((red - green) / delta) + 4);
|
||||
}
|
||||
|
||||
/// Set hue to 0.0 when red == green == blue.
|
||||
hue = hue.isNaN ? 0.0 : hue;
|
||||
return hue;
|
||||
}
|
||||
|
||||
Color _colorFromHue(
|
||||
double alpha,
|
||||
double hue,
|
||||
double chroma,
|
||||
double secondary,
|
||||
double match,
|
||||
) {
|
||||
double red;
|
||||
double green;
|
||||
double blue;
|
||||
if (hue < 60.0) {
|
||||
red = chroma;
|
||||
green = secondary;
|
||||
blue = 0.0;
|
||||
} else if (hue < 120.0) {
|
||||
red = secondary;
|
||||
green = chroma;
|
||||
blue = 0.0;
|
||||
} else if (hue < 180.0) {
|
||||
red = 0.0;
|
||||
green = chroma;
|
||||
blue = secondary;
|
||||
} else if (hue < 240.0) {
|
||||
red = 0.0;
|
||||
green = secondary;
|
||||
blue = chroma;
|
||||
} else if (hue < 300.0) {
|
||||
red = secondary;
|
||||
green = 0.0;
|
||||
blue = chroma;
|
||||
} else {
|
||||
red = chroma;
|
||||
green = 0.0;
|
||||
blue = secondary;
|
||||
}
|
||||
return new Color.fromARGB((alpha * 0xFF).round(), ((red + match) * 0xFF).round(), ((green + match) * 0xFF).round(), ((blue + match) * 0xFF).round());
|
||||
}
|
||||
|
||||
/// A color represented using [alpha], [hue], [saturation], and [value].
|
||||
///
|
||||
/// An [HSVColor] is represented in a parameter space that's motivated by human
|
||||
/// perception. The representation is useful for some color computations (e.g.,
|
||||
/// rotating the hue through the colors of the rainbow).
|
||||
/// An [HSVColor] is represented in a parameter space that's based on human
|
||||
/// perception of color in pigments (e.g. paint and printer's ink). The
|
||||
/// representation is useful for some color computations (e.g. rotating the hue
|
||||
/// through the colors), because interpolation and picking of
|
||||
/// colors as red, green, and blue channels doesn't always produce intuitive
|
||||
/// results.
|
||||
///
|
||||
/// The HSV color space models the way that different pigments are perceived
|
||||
/// when mixed. The hue describes which pigment is used, the saturation
|
||||
/// describes which shade of the pigment, and the value resembles mixing the
|
||||
/// pigment with different amounts of black or white pigment.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [HSLColor], a color that uses a color space based on human perception of
|
||||
/// colored light.
|
||||
/// * [HSV and HSL](https://en.wikipedia.org/wiki/HSL_and_HSV) Wikipedia
|
||||
/// article, which this implementation is based upon.
|
||||
@immutable
|
||||
class HSVColor {
|
||||
/// Creates a color.
|
||||
@ -22,14 +92,21 @@ class HSVColor {
|
||||
: assert(alpha != null),
|
||||
assert(hue != null),
|
||||
assert(saturation != null),
|
||||
assert(value != null);
|
||||
assert(value != null),
|
||||
assert(alpha >= 0.0),
|
||||
assert(alpha <= 1.0),
|
||||
assert(hue >= 0.0),
|
||||
assert(hue <= 360.0),
|
||||
assert(saturation >= 0.0),
|
||||
assert(saturation <= 1.0),
|
||||
assert(value >= 0.0),
|
||||
assert(value <= 1.0);
|
||||
|
||||
/// Creates an [HSVColor] from an RGB [Color].
|
||||
///
|
||||
/// This constructor does not necessarily round-trip with [toColor] because
|
||||
/// of floating point imprecision.
|
||||
factory HSVColor.fromColor(Color color) {
|
||||
final double alpha = color.alpha / 0xFF;
|
||||
final double red = color.red / 0xFF;
|
||||
final double green = color.green / 0xFF;
|
||||
final double blue = color.blue / 0xFF;
|
||||
@ -38,98 +115,66 @@ class HSVColor {
|
||||
final double min = math.min(red, math.min(green, blue));
|
||||
final double delta = max - min;
|
||||
|
||||
double hue = 0.0;
|
||||
|
||||
if (max == 0.0) {
|
||||
hue = 0.0;
|
||||
} else if (max == red) {
|
||||
hue = 60.0 * (((green - blue) / delta) % 6);
|
||||
} else if (max == green) {
|
||||
hue = 60.0 * (((blue - red) / delta) + 2);
|
||||
} else if (max == blue) {
|
||||
hue = 60.0 * (((red - green) / delta) + 4);
|
||||
}
|
||||
|
||||
/// fix hue to 0.0 when red == green == blue.
|
||||
hue = hue.isNaN ? 0.0 : hue;
|
||||
final double alpha = color.alpha / 0xFF;
|
||||
final double hue = _getHue(red, green, blue, max, delta);
|
||||
final double saturation = max == 0.0 ? 0.0 : delta / max;
|
||||
|
||||
return new HSVColor.fromAHSV(alpha, hue, saturation, max);
|
||||
}
|
||||
|
||||
/// Alpha, from 0.0 to 1.0.
|
||||
/// Alpha, from 0.0 to 1.0. The describes the transparency of the color.
|
||||
/// A value of 0.0 is fully transparent, and 1.0 is fully opaque.
|
||||
final double alpha;
|
||||
|
||||
/// Hue, from 0.0 to 360.0.
|
||||
/// Hue, from 0.0 to 360.0. Describes which color of the spectrum is
|
||||
/// represented. A value of 0.0 represents red, as does 360.0. Values in
|
||||
/// between go through all the hues representable in RGB. You can think of
|
||||
/// this as selecting which pigment will be added to a color.
|
||||
final double hue;
|
||||
|
||||
/// Saturation, from 0.0 to 1.0.
|
||||
/// Saturation, from 0.0 to 1.0. This describes how colorful the color is.
|
||||
/// 0.0 implies a shade of grey (i.e. no pigment), and 1.0 implies a color as
|
||||
/// vibrant as that hue gets. You can think of this as the equivalent of
|
||||
/// how much of a pigment is added.
|
||||
final double saturation;
|
||||
|
||||
/// Value, from 0.0 to 1.0.
|
||||
/// Value, from 0.0 to 1.0. The "value" of a color that, in this context,
|
||||
/// describes how bright a color is. A value of 0.0 indicates black, and 1.0
|
||||
/// indicates full intensity color. You can think of this as the equivalent of
|
||||
/// removing black from the color as value increases.
|
||||
final double value;
|
||||
|
||||
/// Returns a copy of this color with the alpha parameter replaced with the given value.
|
||||
/// Returns a copy of this color with the [alpha] parameter replaced with the
|
||||
/// given value.
|
||||
HSVColor withAlpha(double alpha) {
|
||||
return new HSVColor.fromAHSV(alpha, hue, saturation, value);
|
||||
}
|
||||
|
||||
/// Returns a copy of this color with the hue parameter replaced with the given value.
|
||||
/// Returns a copy of this color with the [hue] parameter replaced with the
|
||||
/// given value.
|
||||
HSVColor withHue(double hue) {
|
||||
return new HSVColor.fromAHSV(alpha, hue, saturation, value);
|
||||
}
|
||||
|
||||
/// Returns a copy of this color with the saturation parameter replaced with the given value.
|
||||
/// Returns a copy of this color with the [saturation] parameter replaced with
|
||||
/// the given value.
|
||||
HSVColor withSaturation(double saturation) {
|
||||
return new HSVColor.fromAHSV(alpha, hue, saturation, value);
|
||||
}
|
||||
|
||||
/// Returns a copy of this color with the value parameter replaced with the given value.
|
||||
/// Returns a copy of this color with the [value] parameter replaced with the
|
||||
/// given value.
|
||||
HSVColor withValue(double value) {
|
||||
return new HSVColor.fromAHSV(alpha, hue, saturation, value);
|
||||
}
|
||||
|
||||
/// Returns this color in RGB.
|
||||
Color toColor() {
|
||||
final double h = hue % 360;
|
||||
final double c = saturation * value;
|
||||
final double x = c * (1 - (((h / 60.0) % 2) - 1).abs());
|
||||
final double m = value - c;
|
||||
final double chroma = saturation * value;
|
||||
final double secondary = chroma * (1.0 - (((hue / 60.0) % 2.0) - 1.0).abs());
|
||||
final double match = value - chroma;
|
||||
|
||||
double r;
|
||||
double g;
|
||||
double b;
|
||||
if (h < 60.0) {
|
||||
r = c;
|
||||
g = x;
|
||||
b = 0.0;
|
||||
} else if (h < 120.0) {
|
||||
r = x;
|
||||
g = c;
|
||||
b = 0.0;
|
||||
} else if (h < 180.0) {
|
||||
r = 0.0;
|
||||
g = c;
|
||||
b = x;
|
||||
} else if (h < 240.0) {
|
||||
r = 0.0;
|
||||
g = x;
|
||||
b = c;
|
||||
} else if (h < 300.0) {
|
||||
r = x;
|
||||
g = 0.0;
|
||||
b = c;
|
||||
} else {
|
||||
r = c;
|
||||
g = 0.0;
|
||||
b = x;
|
||||
}
|
||||
return new Color.fromARGB(
|
||||
(alpha * 0xFF).round(),
|
||||
((r + m) * 0xFF).round(),
|
||||
((g + m) * 0xFF).round(),
|
||||
((b + m) * 0xFF).round()
|
||||
);
|
||||
return _colorFromHue(alpha, hue, chroma, secondary, match);
|
||||
}
|
||||
|
||||
HSVColor _scaleAlpha(double factor) {
|
||||
@ -158,6 +203,8 @@ class HSVColor {
|
||||
/// 1.0, so negative values and values greater than 1.0 are valid (and can
|
||||
/// easily be generated by curves such as [Curves.elasticInOut]).
|
||||
///
|
||||
/// Values outside of the valid range for each channel will be clamped.
|
||||
///
|
||||
/// Values for `t` are usually obtained from an [Animation<double>], such as
|
||||
/// an [AnimationController].
|
||||
static HSVColor lerp(HSVColor a, HSVColor b, double t) {
|
||||
@ -169,10 +216,10 @@ class HSVColor {
|
||||
if (b == null)
|
||||
return a._scaleAlpha(1.0 - t);
|
||||
return new HSVColor.fromAHSV(
|
||||
lerpDouble(a.alpha, b.alpha, t),
|
||||
lerpDouble(a.hue, b.hue, t),
|
||||
lerpDouble(a.saturation, b.saturation, t),
|
||||
lerpDouble(a.value, b.value, t),
|
||||
lerpDouble(a.alpha, b.alpha, t).clamp(0.0, 1.0),
|
||||
lerpDouble(a.hue, b.hue, t) % 360.0,
|
||||
lerpDouble(a.saturation, b.saturation, t).clamp(0.0, 1.0),
|
||||
lerpDouble(a.value, b.value, t).clamp(0.0, 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
@ -193,7 +240,193 @@ class HSVColor {
|
||||
int get hashCode => hashValues(alpha, hue, saturation, value);
|
||||
|
||||
@override
|
||||
String toString() => 'HSVColor($alpha, $hue, $saturation, $value)';
|
||||
String toString() => '$runtimeType($alpha, $hue, $saturation, $value)';
|
||||
}
|
||||
|
||||
/// A color represented using [alpha], [hue], [saturation], and [lightness].
|
||||
///
|
||||
/// An [HSLColor] is represented in a parameter space that's based up human
|
||||
/// perception of colored light. The representation is useful for some color
|
||||
/// computations (e.g., combining colors of light), because interpolation and
|
||||
/// picking of colors as red, green, and blue channels doesn't always produce
|
||||
/// intuitive results.
|
||||
///
|
||||
/// HSL is a perceptual color model, placing fully saturated colors around a
|
||||
/// circle (conceptually) at a lightness of 0.5, with a lightness of 0.0 being
|
||||
/// completely black, and a lightness of 1.0 being completely white. As the
|
||||
/// lightness increases or decreases from 0.5, the apparent saturation decreases
|
||||
/// proportionally (even though the [saturation] parameter hasn't changed).
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [HSVColor], a color that uses a color space based on human perception of
|
||||
/// pigments (e.g. paint and printer's ink).
|
||||
/// * [HSV and HSL](https://en.wikipedia.org/wiki/HSL_and_HSV) Wikipedia
|
||||
/// article, which this implementation is based upon.
|
||||
@immutable
|
||||
class HSLColor {
|
||||
/// Creates a color.
|
||||
///
|
||||
/// All the arguments must not be null and be in their respective ranges. See
|
||||
/// the fields for each parameter for a description of their ranges.
|
||||
const HSLColor.fromAHSL(this.alpha, this.hue, this.saturation, this.lightness)
|
||||
: assert(alpha != null),
|
||||
assert(hue != null),
|
||||
assert(saturation != null),
|
||||
assert(lightness != null),
|
||||
assert(alpha >= 0.0),
|
||||
assert(alpha <= 1.0),
|
||||
assert(hue >= 0.0),
|
||||
assert(hue <= 360.0),
|
||||
assert(saturation >= 0.0),
|
||||
assert(saturation <= 1.0),
|
||||
assert(lightness >= 0.0),
|
||||
assert(lightness <= 1.0);
|
||||
|
||||
/// Creates an [HSLColor] from an RGB [Color].
|
||||
///
|
||||
/// This constructor does not necessarily round-trip with [toColor] because
|
||||
/// of floating point imprecision.
|
||||
factory HSLColor.fromColor(Color color) {
|
||||
final double red = color.red / 0xFF;
|
||||
final double green = color.green / 0xFF;
|
||||
final double blue = color.blue / 0xFF;
|
||||
|
||||
final double max = math.max(red, math.max(green, blue));
|
||||
final double min = math.min(red, math.min(green, blue));
|
||||
final double delta = max - min;
|
||||
|
||||
final double alpha = color.alpha / 0xFF;
|
||||
final double hue = _getHue(red, green, blue, max, delta);
|
||||
final double lightness = (max + min) / 2.0;
|
||||
// Saturation can exceed 1.0 with rounding errors, so clamp it.
|
||||
final double saturation = lightness == 1.0
|
||||
? 0.0
|
||||
: (delta / (1.0 - (2.0 * lightness - 1.0).abs())).clamp(0.0, 1.0);
|
||||
return new HSLColor.fromAHSL(alpha, hue, saturation, lightness);
|
||||
}
|
||||
|
||||
/// Alpha, from 0.0 to 1.0. The describes the transparency of the color.
|
||||
/// A value of 0.0 is fully transparent, and 1.0 is fully opaque.
|
||||
final double alpha;
|
||||
|
||||
/// Hue, from 0.0 to 360.0. Describes which color of the spectrum is
|
||||
/// represented. A value of 0.0 represents red, as does 360.0. Values in
|
||||
/// between go through all the hues representable in RGB. You can think of
|
||||
/// this as selecting which color filter is placed over a light.
|
||||
final double hue;
|
||||
|
||||
/// Saturation, from 0.0 to 1.0. This describes how colorful the color is.
|
||||
/// 0.0 implies a shade of grey (i.e. no pigment), and 1.0 implies a color as
|
||||
/// vibrant as that hue gets. You can think of this as the purity of the
|
||||
/// color filter over the light.
|
||||
final double saturation;
|
||||
|
||||
/// Lightness, from 0.0 to 1.0. The lightness of a color describes how bright
|
||||
/// a color is. A value of 0.0 indicates black, and 1.0 indicates white. You
|
||||
/// can think of this as the intensity of the light behind the filter. As the
|
||||
/// lightness approaches 0.5, the colors get brighter and appear more
|
||||
/// saturated, and over 0.5, the colors start to become less saturated and
|
||||
/// approach white at 1.0.
|
||||
final double lightness;
|
||||
|
||||
/// Returns a copy of this color with the alpha parameter replaced with the
|
||||
/// given value.
|
||||
HSLColor withAlpha(double alpha) {
|
||||
return new HSLColor.fromAHSL(alpha, hue, saturation, lightness);
|
||||
}
|
||||
|
||||
/// Returns a copy of this color with the [hue] parameter replaced with the
|
||||
/// given value.
|
||||
HSLColor withHue(double hue) {
|
||||
return new HSLColor.fromAHSL(alpha, hue, saturation, lightness);
|
||||
}
|
||||
|
||||
/// Returns a copy of this color with the [saturation] parameter replaced with
|
||||
/// the given value.
|
||||
HSLColor withSaturation(double saturation) {
|
||||
return new HSLColor.fromAHSL(alpha, hue, saturation, lightness);
|
||||
}
|
||||
|
||||
/// Returns a copy of this color with the [lightness] parameter replaced with
|
||||
/// the given value.
|
||||
HSLColor withLightness(double lightness) {
|
||||
return new HSLColor.fromAHSL(alpha, hue, saturation, lightness);
|
||||
}
|
||||
|
||||
/// Returns this HSL color in RGB.
|
||||
Color toColor() {
|
||||
final double chroma = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
|
||||
final double secondary = chroma * (1.0 - (((hue / 60.0) % 2.0) - 1.0).abs());
|
||||
final double match = lightness - chroma / 2.0;
|
||||
|
||||
return _colorFromHue(alpha, hue, chroma, secondary, match);
|
||||
}
|
||||
|
||||
HSLColor _scaleAlpha(double factor) {
|
||||
return withAlpha(alpha * factor);
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two HSLColors.
|
||||
///
|
||||
/// The colors are interpolated by interpolating the [alpha], [hue],
|
||||
/// [saturation], and [lightness] channels separately, which usually leads to
|
||||
/// a more pleasing effect than [Color.lerp] (which interpolates the red,
|
||||
/// green, and blue channels separately).
|
||||
///
|
||||
/// If either color is null, this function linearly interpolates from a
|
||||
/// transparent instance of the other color. This is usually preferable to
|
||||
/// interpolating from [Colors.transparent] (`const Color(0x00000000)`) since
|
||||
/// that will interpolate from a transparent red and cycle through the hues to
|
||||
/// match the target color, regardless of what that color's hue is.
|
||||
///
|
||||
/// The `t` argument represents position on the timeline, with 0.0 meaning
|
||||
/// that the interpolation has not started, returning `a` (or something
|
||||
/// equivalent to `a`), 1.0 meaning that the interpolation has finished,
|
||||
/// returning `b` (or something equivalent to `b`), and values between them
|
||||
/// meaning that the interpolation is at the relevant point on the timeline
|
||||
/// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
|
||||
/// 1.0, so negative values and values greater than 1.0 are valid
|
||||
/// (and can easily be generated by curves such as [Curves.elasticInOut]).
|
||||
///
|
||||
/// Values outside of the valid range for each channel will be clamped.
|
||||
///
|
||||
/// Values for `t` are usually obtained from an [Animation<double>], such as
|
||||
/// an [AnimationController].
|
||||
static HSLColor lerp(HSLColor a, HSLColor b, double t) {
|
||||
assert(t != null);
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
if (a == null)
|
||||
return b._scaleAlpha(t);
|
||||
if (b == null)
|
||||
return a._scaleAlpha(1.0 - t);
|
||||
return new HSLColor.fromAHSL(
|
||||
lerpDouble(a.alpha, b.alpha, t).clamp(0.0, 1.0),
|
||||
lerpDouble(a.hue, b.hue, t) % 360.0,
|
||||
lerpDouble(a.saturation, b.saturation, t).clamp(0.0, 1.0),
|
||||
lerpDouble(a.lightness, b.lightness, t).clamp(0.0, 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (identical(this, other))
|
||||
return true;
|
||||
if (other is! HSLColor)
|
||||
return false;
|
||||
final HSLColor typedOther = other;
|
||||
return typedOther.alpha == alpha
|
||||
&& typedOther.hue == hue
|
||||
&& typedOther.saturation == saturation
|
||||
&& typedOther.lightness == lightness;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(alpha, hue, saturation, lightness);
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType($alpha, $hue, $saturation, $lightness)';
|
||||
}
|
||||
|
||||
/// A color that has a small table of related colors called a "swatch".
|
||||
@ -204,7 +437,8 @@ class HSVColor {
|
||||
///
|
||||
/// * [MaterialColor] and [MaterialAccentColor], which define material design
|
||||
/// primary and accent color swatches.
|
||||
/// * [material.Colors], which defines all of the standard material design colors.
|
||||
/// * [material.Colors], which defines all of the standard material design
|
||||
/// colors.
|
||||
class ColorSwatch<T> extends Color {
|
||||
/// Creates a color that has a small table of related colors called a "swatch".
|
||||
///
|
||||
|
@ -5,6 +5,8 @@
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
const double _doubleColorPrecision = 0.01;
|
||||
|
||||
void main() {
|
||||
test('HSVColor control test', () {
|
||||
const HSVColor color = const HSVColor.fromAHSV(0.7, 28.0, 0.3, 0.6);
|
||||
@ -27,24 +29,381 @@ void main() {
|
||||
expect(result.value, 0.5);
|
||||
});
|
||||
|
||||
test('HSVColor.fromColor test', () {
|
||||
final HSVColor black = new HSVColor.fromColor(const Color.fromARGB(0xFF, 0x00, 0x00, 0x00));
|
||||
final HSVColor red = new HSVColor.fromColor(const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00));
|
||||
final HSVColor green = new HSVColor.fromColor(const Color.fromARGB(0xFF, 0x00, 0xFF, 0x00));
|
||||
final HSVColor blue = new HSVColor.fromColor(const Color.fromARGB(0xFF, 0x00, 0x00, 0xFF));
|
||||
final HSVColor grey = new HSVColor.fromColor(const Color.fromARGB(0xFF, 0x80, 0x80, 0x80));
|
||||
test('HSVColor hue sweep test', () {
|
||||
final List<Color> output = <Color>[];
|
||||
for (double hue = 0.0; hue <= 360.0; hue += 36.0) {
|
||||
final HSVColor hsvColor = new HSVColor.fromAHSV(1.0, hue, 1.0, 1.0);
|
||||
final Color color = hsvColor.toColor();
|
||||
output.add(color);
|
||||
if (hue != 360.0) {
|
||||
// Check that it's reversible.
|
||||
expect(
|
||||
new HSVColor.fromColor(color),
|
||||
within<HSVColor>(distance: _doubleColorPrecision, from: hsvColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xffff0000),
|
||||
const Color(0xffff9900),
|
||||
const Color(0xffccff00),
|
||||
const Color(0xff33ff00),
|
||||
const Color(0xff00ff66),
|
||||
const Color(0xff00ffff),
|
||||
const Color(0xff0066ff),
|
||||
const Color(0xff3300ff),
|
||||
const Color(0xffcc00ff),
|
||||
const Color(0xffff0099),
|
||||
const Color(0xffff0000),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
expect(black.toColor(), equals(const Color.fromARGB(0xFF, 0x00, 0x00, 0x00)));
|
||||
expect(red.toColor(), equals(const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00)));
|
||||
expect(green.toColor(), equals(const Color.fromARGB(0xFF, 0x00, 0xFF, 0x00)));
|
||||
expect(blue.toColor(), equals(const Color.fromARGB(0xFF, 0x00, 0x00, 0xFF)));
|
||||
expect(grey.toColor(), equals(const Color.fromARGB(0xFF, 0x80, 0x80, 0x80)));
|
||||
test('HSVColor saturation sweep test', () {
|
||||
final List<Color> output = <Color>[];
|
||||
for (double saturation = 0.0; saturation < 1.0; saturation += 0.1) {
|
||||
final HSVColor hslColor = new HSVColor.fromAHSV(1.0, 0.0, saturation, 1.0);
|
||||
final Color color = hslColor.toColor();
|
||||
output.add(color);
|
||||
// Check that it's reversible.
|
||||
expect(
|
||||
new HSVColor.fromColor(color),
|
||||
within<HSVColor>(distance: _doubleColorPrecision, from: hslColor),
|
||||
);
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xffffffff),
|
||||
const Color(0xffffe6e6),
|
||||
const Color(0xffffcccc),
|
||||
const Color(0xffffb3b3),
|
||||
const Color(0xffff9999),
|
||||
const Color(0xffff8080),
|
||||
const Color(0xffff6666),
|
||||
const Color(0xffff4d4d),
|
||||
const Color(0xffff3333),
|
||||
const Color(0xffff1a1a),
|
||||
const Color(0xffff0000),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('HSVColor value sweep test', () {
|
||||
final List<Color> output = <Color>[];
|
||||
for (double value = 0.0; value < 1.0; value += 0.1) {
|
||||
final HSVColor hsvColor = new HSVColor.fromAHSV(1.0, 0.0, 1.0, value);
|
||||
final Color color = hsvColor.toColor();
|
||||
output.add(color);
|
||||
// Check that it's reversible. Discontinuities at the ends for saturation,
|
||||
// so we skip those.
|
||||
if (value >= _doubleColorPrecision && value <= (1.0 - _doubleColorPrecision)) {
|
||||
expect(
|
||||
new HSVColor.fromColor(color),
|
||||
within<HSVColor>(distance: _doubleColorPrecision, from: hsvColor),
|
||||
);
|
||||
}
|
||||
// output.add(new HSVColor.fromAHSV(1.0, 0.0, 1.0, value).toColor());
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xff000000),
|
||||
const Color(0xff1a0000),
|
||||
const Color(0xff330000),
|
||||
const Color(0xff4d0000),
|
||||
const Color(0xff660000),
|
||||
const Color(0xff800000),
|
||||
const Color(0xff990000),
|
||||
const Color(0xffb30000),
|
||||
const Color(0xffcc0000),
|
||||
const Color(0xffe50000),
|
||||
const Color(0xffff0000),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('HSVColor lerps hue correctly.', () {
|
||||
final List<Color> output = <Color>[];
|
||||
const HSVColor startColor = const HSVColor.fromAHSV(1.0, 0.0, 1.0, 1.0);
|
||||
const HSVColor endColor = const HSVColor.fromAHSV(1.0, 360.0, 1.0, 1.0);
|
||||
|
||||
for (double t = -0.5; t < 1.5; t += 0.1) {
|
||||
output.add(HSVColor.lerp(startColor, endColor, t).toColor());
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xff00ffff),
|
||||
const Color(0xff0066ff),
|
||||
const Color(0xff3300ff),
|
||||
const Color(0xffcc00ff),
|
||||
const Color(0xffff0099),
|
||||
const Color(0xffff0000),
|
||||
const Color(0xffff9900),
|
||||
const Color(0xffccff00),
|
||||
const Color(0xff33ff00),
|
||||
const Color(0xff00ff66),
|
||||
const Color(0xff00ffff),
|
||||
const Color(0xff0066ff),
|
||||
const Color(0xff3300ff),
|
||||
const Color(0xffcc00ff),
|
||||
const Color(0xffff0099),
|
||||
const Color(0xffff0000),
|
||||
const Color(0xffff9900),
|
||||
const Color(0xffccff00),
|
||||
const Color(0xff33ff00),
|
||||
const Color(0xff00ff66),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('HSVColor lerps saturation correctly.', () {
|
||||
final List<Color> output = <Color>[];
|
||||
const HSVColor startColor = const HSVColor.fromAHSV(1.0, 0.0, 0.0, 1.0);
|
||||
const HSVColor endColor = const HSVColor.fromAHSV(1.0, 0.0, 1.0, 1.0);
|
||||
|
||||
for (double t = -0.1; t < 1.1; t += 0.1) {
|
||||
output.add(HSVColor.lerp(startColor, endColor, t).toColor());
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xffffffff),
|
||||
const Color(0xffffffff),
|
||||
const Color(0xffffe6e6),
|
||||
const Color(0xffffcccc),
|
||||
const Color(0xffffb3b3),
|
||||
const Color(0xffff9999),
|
||||
const Color(0xffff8080),
|
||||
const Color(0xffff6666),
|
||||
const Color(0xffff4d4d),
|
||||
const Color(0xffff3333),
|
||||
const Color(0xffff1a1a),
|
||||
const Color(0xffff0000),
|
||||
const Color(0xffff0000),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('HSVColor lerps value correctly.', () {
|
||||
final List<Color> output = <Color>[];
|
||||
const HSVColor startColor = const HSVColor.fromAHSV(1.0, 0.0, 1.0, 0.0);
|
||||
const HSVColor endColor = const HSVColor.fromAHSV(1.0, 0.0, 1.0, 1.0);
|
||||
|
||||
for (double t = -0.1; t < 1.1; t += 0.1) {
|
||||
output.add(HSVColor.lerp(startColor, endColor, t).toColor());
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xff000000),
|
||||
const Color(0xff000000),
|
||||
const Color(0xff1a0000),
|
||||
const Color(0xff330000),
|
||||
const Color(0xff4d0000),
|
||||
const Color(0xff660000),
|
||||
const Color(0xff800000),
|
||||
const Color(0xff990000),
|
||||
const Color(0xffb30000),
|
||||
const Color(0xffcc0000),
|
||||
const Color(0xffe50000),
|
||||
const Color(0xffff0000),
|
||||
const Color(0xffff0000),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('HSLColor control test', () {
|
||||
const HSLColor color = const HSLColor.fromAHSL(0.7, 28.0, 0.3, 0.6);
|
||||
|
||||
expect(color, hasOneLineDescription);
|
||||
expect(color.hashCode, equals(const HSLColor.fromAHSL(0.7, 28.0, 0.3, 0.6).hashCode));
|
||||
|
||||
expect(color.withAlpha(0.8), const HSLColor.fromAHSL(0.8, 28.0, 0.3, 0.6));
|
||||
expect(color.withHue(123.0), const HSLColor.fromAHSL(0.7, 123.0, 0.3, 0.6));
|
||||
expect(color.withSaturation(0.9), const HSLColor.fromAHSL(0.7, 28.0, 0.9, 0.6));
|
||||
expect(color.withLightness(0.1), const HSLColor.fromAHSL(0.7, 28.0, 0.3, 0.1));
|
||||
|
||||
expect(color.toColor(), const Color(0xb3b8977a));
|
||||
|
||||
final HSLColor result = HSLColor.lerp(color, const HSLColor.fromAHSL(0.3, 128.0, 0.7, 0.2), 0.25);
|
||||
expect(result.alpha, 0.6);
|
||||
expect(result.hue, 53.0);
|
||||
expect(result.saturation, greaterThan(0.3999));
|
||||
expect(result.saturation, lessThan(0.4001));
|
||||
expect(result.lightness, 0.5);
|
||||
});
|
||||
|
||||
test('HSLColor hue sweep test', () {
|
||||
final List<Color> output = <Color>[];
|
||||
for (double hue = 0.0; hue <= 360.0; hue += 36.0) {
|
||||
final HSLColor hslColor = new HSLColor.fromAHSL(1.0, hue, 0.5, 0.5);
|
||||
final Color color = hslColor.toColor();
|
||||
output.add(color);
|
||||
if (hue != 360.0) {
|
||||
// Check that it's reversible.
|
||||
expect(
|
||||
new HSLColor.fromColor(color),
|
||||
within<HSLColor>(distance: _doubleColorPrecision, from: hslColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xffbf4040),
|
||||
const Color(0xffbf8c40),
|
||||
const Color(0xffa6bf40),
|
||||
const Color(0xff59bf40),
|
||||
const Color(0xff40bf73),
|
||||
const Color(0xff40bfbf),
|
||||
const Color(0xff4073bf),
|
||||
const Color(0xff5940bf),
|
||||
const Color(0xffa640bf),
|
||||
const Color(0xffbf408c),
|
||||
const Color(0xffbf4040),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('HSLColor saturation sweep test', () {
|
||||
final List<Color> output = <Color>[];
|
||||
for (double saturation = 0.0; saturation < 1.0; saturation += 0.1) {
|
||||
final HSLColor hslColor = new HSLColor.fromAHSL(1.0, 0.0, saturation, 0.5);
|
||||
final Color color = hslColor.toColor();
|
||||
output.add(color);
|
||||
// Check that it's reversible.
|
||||
expect(
|
||||
new HSLColor.fromColor(color),
|
||||
within<HSLColor>(distance: _doubleColorPrecision, from: hslColor),
|
||||
);
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xff808080),
|
||||
const Color(0xff8c7373),
|
||||
const Color(0xff996666),
|
||||
const Color(0xffa65959),
|
||||
const Color(0xffb34d4d),
|
||||
const Color(0xffbf4040),
|
||||
const Color(0xffcc3333),
|
||||
const Color(0xffd92626),
|
||||
const Color(0xffe51a1a),
|
||||
const Color(0xfff20d0d),
|
||||
const Color(0xffff0000),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('HSLColor lightness sweep test', () {
|
||||
final List<Color> output = <Color>[];
|
||||
for (double lightness = 0.0; lightness < 1.0; lightness += 0.1) {
|
||||
final HSLColor hslColor = new HSLColor.fromAHSL(1.0, 0.0, 0.5, lightness);
|
||||
final Color color = hslColor.toColor();
|
||||
output.add(color);
|
||||
// Check that it's reversible. Discontinuities at the ends for saturation,
|
||||
// so we skip those.
|
||||
if (lightness >= _doubleColorPrecision && lightness <= (1.0 - _doubleColorPrecision)) {
|
||||
expect(
|
||||
new HSLColor.fromColor(color),
|
||||
within<HSLColor>(distance: _doubleColorPrecision, from: hslColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xff000000),
|
||||
const Color(0xff260d0d),
|
||||
const Color(0xff4d1a1a),
|
||||
const Color(0xff732626),
|
||||
const Color(0xff993333),
|
||||
const Color(0xffbf4040),
|
||||
const Color(0xffcc6666),
|
||||
const Color(0xffd98c8c),
|
||||
const Color(0xffe6b3b3),
|
||||
const Color(0xfff2d9d9),
|
||||
const Color(0xffffffff),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('HSLColor lerps hue correctly.', () {
|
||||
final List<Color> output = <Color>[];
|
||||
const HSLColor startColor = const HSLColor.fromAHSL(1.0, 0.0, 0.5, 0.5);
|
||||
const HSLColor endColor = const HSLColor.fromAHSL(1.0, 360.0, 0.5, 0.5);
|
||||
|
||||
for (double t = -0.5; t < 1.5; t += 0.1) {
|
||||
output.add(HSLColor.lerp(startColor, endColor, t).toColor());
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xff40bfbf),
|
||||
const Color(0xff4073bf),
|
||||
const Color(0xff5940bf),
|
||||
const Color(0xffa640bf),
|
||||
const Color(0xffbf408c),
|
||||
const Color(0xffbf4040),
|
||||
const Color(0xffbf8c40),
|
||||
const Color(0xffa6bf40),
|
||||
const Color(0xff59bf40),
|
||||
const Color(0xff40bf73),
|
||||
const Color(0xff40bfbf),
|
||||
const Color(0xff4073bf),
|
||||
const Color(0xff5940bf),
|
||||
const Color(0xffa640bf),
|
||||
const Color(0xffbf408c),
|
||||
const Color(0xffbf4040),
|
||||
const Color(0xffbf8c40),
|
||||
const Color(0xffa6bf40),
|
||||
const Color(0xff59bf40),
|
||||
const Color(0xff40bf73),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('HSLColor lerps saturation correctly.', () {
|
||||
final List<Color> output = <Color>[];
|
||||
const HSLColor startColor = const HSLColor.fromAHSL(1.0, 0.0, 0.0, 0.5);
|
||||
const HSLColor endColor = const HSLColor.fromAHSL(1.0, 0.0, 1.0, 0.5);
|
||||
|
||||
for (double t = -0.1; t < 1.1; t += 0.1) {
|
||||
output.add(HSLColor.lerp(startColor, endColor, t).toColor());
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xff808080),
|
||||
const Color(0xff808080),
|
||||
const Color(0xff8c7373),
|
||||
const Color(0xff996666),
|
||||
const Color(0xffa65959),
|
||||
const Color(0xffb34d4d),
|
||||
const Color(0xffbf4040),
|
||||
const Color(0xffcc3333),
|
||||
const Color(0xffd92626),
|
||||
const Color(0xffe51a1a),
|
||||
const Color(0xfff20d0d),
|
||||
const Color(0xffff0000),
|
||||
const Color(0xffff0000),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('HSLColor lerps lightness correctly.', () {
|
||||
final List<Color> output = <Color>[];
|
||||
const HSLColor startColor = const HSLColor.fromAHSL(1.0, 0.0, 0.5, 0.0);
|
||||
const HSLColor endColor = const HSLColor.fromAHSL(1.0, 0.0, 0.5, 1.0);
|
||||
|
||||
for (double t = -0.1; t < 1.1; t += 0.1) {
|
||||
output.add(HSLColor.lerp(startColor, endColor, t).toColor());
|
||||
}
|
||||
final List<Color> expectedColors = <Color>[
|
||||
const Color(0xff000000),
|
||||
const Color(0xff000000),
|
||||
const Color(0xff260d0d),
|
||||
const Color(0xff4d1a1a),
|
||||
const Color(0xff732626),
|
||||
const Color(0xff993333),
|
||||
const Color(0xffbf4040),
|
||||
const Color(0xffcc6666),
|
||||
const Color(0xffd98c8c),
|
||||
const Color(0xffe6b3b3),
|
||||
const Color(0xfff2d9d9),
|
||||
const Color(0xffffffff),
|
||||
const Color(0xffffffff),
|
||||
];
|
||||
expect(output, equals(expectedColors));
|
||||
});
|
||||
|
||||
test('ColorSwatch test', () {
|
||||
final int color = nonconst(0xFF027223);
|
||||
final ColorSwatch<String> greens1 = new ColorSwatch<String>(
|
||||
color, const <String, Color>{
|
||||
color,
|
||||
const <String, Color>{
|
||||
'2259 C': const Color(0xFF027223),
|
||||
'2273 C': const Color(0xFF257226),
|
||||
'2426 XGC': const Color(0xFF00932F),
|
||||
@ -52,7 +411,8 @@ void main() {
|
||||
},
|
||||
);
|
||||
final ColorSwatch<String> greens2 = new ColorSwatch<String>(
|
||||
color, const <String, Color>{
|
||||
color,
|
||||
const <String, Color>{
|
||||
'2259 C': const Color(0xFF027223),
|
||||
'2273 C': const Color(0xFF257226),
|
||||
'2426 XGC': const Color(0xFF00932F),
|
||||
|
@ -653,6 +653,8 @@ typedef num AnyDistanceFunction(Null a, Null b);
|
||||
|
||||
const Map<Type, AnyDistanceFunction> _kStandardDistanceFunctions = const <Type, AnyDistanceFunction>{
|
||||
Color: _maxComponentColorDistance,
|
||||
HSVColor: _maxComponentHSVColorDistance,
|
||||
HSLColor: _maxComponentHSLColorDistance,
|
||||
Offset: _offsetDistance,
|
||||
int: _intDistance,
|
||||
double: _doubleDistance,
|
||||
@ -671,6 +673,22 @@ double _maxComponentColorDistance(Color a, Color b) {
|
||||
return delta.toDouble();
|
||||
}
|
||||
|
||||
// Compares hue by converting it to a 0.0 - 1.0 range, so that the comparison
|
||||
// can be a similar error percentage per component.
|
||||
double _maxComponentHSVColorDistance(HSVColor a, HSVColor b) {
|
||||
double delta = math.max<double>((a.saturation - b.saturation).abs(), (a.value - b.value).abs());
|
||||
delta = math.max<double>(delta, ((a.hue - b.hue) / 360.0).abs());
|
||||
return math.max<double>(delta, (a.alpha - b.alpha).abs());
|
||||
}
|
||||
|
||||
// Compares hue by converting it to a 0.0 - 1.0 range, so that the comparison
|
||||
// can be a similar error percentage per component.
|
||||
double _maxComponentHSLColorDistance(HSLColor a, HSLColor b) {
|
||||
double delta = math.max<double>((a.saturation - b.saturation).abs(), (a.lightness - b.lightness).abs());
|
||||
delta = math.max<double>(delta, ((a.hue - b.hue) / 360.0).abs());
|
||||
return math.max<double>(delta, (a.alpha - b.alpha).abs());
|
||||
}
|
||||
|
||||
double _rectDistance(Rect a, Rect b) {
|
||||
double delta = math.max<double>((a.left - b.left).abs(), (a.top - b.top).abs());
|
||||
delta = math.max<double>(delta, (a.right - b.right).abs());
|
||||
|
Loading…
x
Reference in New Issue
Block a user