forked from flutter/flutter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
colors.dart
498 lines (445 loc) · 17.9 KB
/
colors.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' show Color, lerpDouble, hashValues;
import 'package:flutter/foundation.dart';
double _getHue(double red, double green, double blue, double max, double delta) {
late 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 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 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.
///
/// 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 HSVColor.fromAHSV(this.alpha, this.hue, this.saturation, this.value)
: assert(alpha != null),
assert(hue != null),
assert(saturation != 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 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 saturation = max == 0.0 ? 0.0 : delta / max;
return HSVColor.fromAHSV(alpha, hue, saturation, max);
}
/// 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 pigment will be added to a color.
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 equivalent of
/// how much of a pigment is added.
final double saturation;
/// 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.
HSVColor withAlpha(double alpha) {
return HSVColor.fromAHSV(alpha, hue, saturation, value);
}
/// Returns a copy of this color with the [hue] parameter replaced with the
/// given value.
HSVColor withHue(double hue) {
return HSVColor.fromAHSV(alpha, hue, saturation, value);
}
/// Returns a copy of this color with the [saturation] parameter replaced with
/// the given value.
HSVColor withSaturation(double saturation) {
return HSVColor.fromAHSV(alpha, hue, saturation, value);
}
/// Returns a copy of this color with the [value] parameter replaced with the
/// given value.
HSVColor withValue(double value) {
return HSVColor.fromAHSV(alpha, hue, saturation, value);
}
/// Returns this color in RGB.
Color toColor() {
final double chroma = saturation * value;
final double secondary = chroma * (1.0 - (((hue / 60.0) % 2.0) - 1.0).abs());
final double match = value - chroma;
return _colorFromHue(alpha, hue, chroma, secondary, match);
}
HSVColor _scaleAlpha(double factor) {
return withAlpha(alpha * factor);
}
/// Linearly interpolate between two HSVColors.
///
/// The colors are interpolated by interpolating the [alpha], [hue],
/// [saturation], and [value] 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.
///
/// {@macro dart.ui.shadow.lerp}
///
/// Values outside of the valid range for each channel will be clamped.
static HSVColor? lerp(HSVColor? a, HSVColor? 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 HSVColor.fromAHSV(
lerpDouble(a.alpha, b.alpha, t)!.clamp(0.0, 1.0) as double,
lerpDouble(a.hue, b.hue, t)! % 360.0,
lerpDouble(a.saturation, b.saturation, t)!.clamp(0.0, 1.0) as double,
lerpDouble(a.value, b.value, t)!.clamp(0.0, 1.0) as double,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
return other is HSVColor
&& other.alpha == alpha
&& other.hue == hue
&& other.saturation == saturation
&& other.value == value;
}
@override
int get hashCode => hashValues(alpha, hue, saturation, value);
@override
String toString() => '${objectRuntimeType(this, 'HSVColor')}($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) as double);
return 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 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 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 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 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 HSLColor.fromAHSL(
lerpDouble(a.alpha, b.alpha, t)!.clamp(0.0, 1.0) as double,
lerpDouble(a.hue, b.hue, t)! % 360.0,
lerpDouble(a.saturation, b.saturation, t)!.clamp(0.0, 1.0) as double,
lerpDouble(a.lightness, b.lightness, t)!.clamp(0.0, 1.0) as double,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
return other is HSLColor
&& other.alpha == alpha
&& other.hue == hue
&& other.saturation == saturation
&& other.lightness == lightness;
}
@override
int get hashCode => hashValues(alpha, hue, saturation, lightness);
@override
String toString() => '${objectRuntimeType(this, 'HSLColor')}($alpha, $hue, $saturation, $lightness)';
}
/// A color that has a small table of related colors called a "swatch".
///
/// The table is indexed by values of type `T`.
///
/// See also:
///
/// * [MaterialColor] and [MaterialAccentColor], which define material design
/// primary and accent color swatches.
/// * [material.Colors], which defines all of the standard material design
/// colors.
@immutable
class ColorSwatch<T> extends Color {
/// Creates a color that has a small table of related colors called a "swatch".
///
/// The `primary` argument should be the 32 bit ARGB value of one of the
/// values in the swatch, as would be passed to the [new Color] constructor
/// for that same color, and as is exposed by [value]. (This is distinct from
/// the specific index of the color in the swatch.)
const ColorSwatch(int primary, this._swatch) : super(primary);
@protected
final Map<T, Color> _swatch;
/// Returns an element of the swatch table.
Color? operator [](T index) => _swatch[index];
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return super == other
&& other is ColorSwatch<T>
&& other._swatch == _swatch;
}
@override
int get hashCode => hashValues(runtimeType, value, _swatch);
@override
String toString() => '${objectRuntimeType(this, 'ColorSwatch')}(primary value: ${super.toString()})';
}
/// [DiagnosticsProperty] that has an [Color] as value.
class ColorProperty extends DiagnosticsProperty<Color> {
/// Create a diagnostics property for [Color].
///
/// The [showName], [style], and [level] arguments must not be null.
ColorProperty(
String name,
Color? value, {
bool showName = true,
Object? defaultValue = kNoDefaultValue,
DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
DiagnosticLevel level = DiagnosticLevel.info,
}) : assert(showName != null),
assert(style != null),
assert(level != null),
super(name, value,
defaultValue: defaultValue,
showName: showName,
style: style,
level: level,
);
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
final Map<String, Object?> json = super.toJsonMap(delegate);
if (value != null) {
json['valueProperties'] = <String, Object>{
'red': value!.red,
'green': value!.green,
'blue': value!.blue,
'alpha': value!.alpha,
};
}
return json;
}
}