Clamp overflows in Color.lerp. (flutter/engine#4141)

Previously, cases like:

```dart
Color.lerp(const Color(0xFF00FF7F), const Color(0xFF00FFFF), 1.1)
```

...would result in unexpected effects (in this instance, lerping
between these colors with a curve that overshoots would take what
should be a simple animation from pale green to blue and add some
flickering bright green whenever it overshoots).
This commit is contained in:
Ian Hickson 2017-09-26 11:11:09 -07:00 committed by GitHub
parent de14cec698
commit 56b4eb63d3
3 changed files with 62 additions and 21 deletions

View File

@ -8,3 +8,6 @@ script:
- ./travis/build.sh
- ./travis/test.sh
- ./travis/format.sh
# We don't build the engine or run the tests for the engine on Travis
# See testing/run_tests.sh if that's what you're looking for though.

View File

@ -35,7 +35,7 @@ bool _offsetIsValid(Offset offset) {
}
Color _scaleAlpha(Color a, double factor) {
return a.withAlpha((a.alpha * factor).round());
return a.withAlpha((a.alpha * factor).round().clamp(0, 255));
}
/// An immutable 32 bit color value in ARGB format.
@ -100,10 +100,10 @@ class Color {
/// See also [fromARGB], which takes the alpha value as a floating point
/// value.
const Color.fromARGB(int a, int r, int g, int b) :
value = ((((a & 0xff) << 24) |
((r & 0xff) << 16) |
((g & 0xff) << 8) |
((b & 0xff) << 0)) & 0xFFFFFFFF);
value = (((a & 0xff) << 24) |
((r & 0xff) << 16) |
((g & 0xff) << 8) |
((b & 0xff) << 0)) & 0xFFFFFFFF;
/// Create a color from red, green, blue, and opacity, similar to `rgba()` in CSS.
///
@ -117,10 +117,10 @@ class Color {
///
/// See also [fromARGB], which takes the opacity as an integer value.
const Color.fromRGBO(int r, int g, int b, double opacity) :
value = (((((opacity * 0xff ~/ 1) & 0xff) << 24) |
((r & 0xff) << 16) |
((g & 0xff) << 8) |
((b & 0xff) << 0)) & 0xFFFFFFFF);
value = ((((opacity * 0xff ~/ 1) & 0xff) << 24) |
((r & 0xff) << 16) |
((g & 0xff) << 8) |
((b & 0xff) << 0)) & 0xFFFFFFFF;
/// A 32 bit value representing this color.
///
@ -155,31 +155,41 @@ class Color {
/// Returns a new color that matches this color with the alpha channel
/// replaced with `a` (which ranges from 0 to 255).
///
/// Out of range values will have unexpected effects.
Color withAlpha(int a) {
return new Color.fromARGB(a, red, green, blue);
}
/// Returns a new color that matches this color with the alpha channel
/// replaced with the given `opacity` (which ranges from 0.0 to 1.0).
///
/// Out of range values will have unexpected effects.
Color withOpacity(double opacity) {
assert(opacity >= 0.0 && opacity <= 1.0);
return withAlpha((255.0 * opacity).round());
}
/// Returns a new color that matches this color with the red channel replaced
/// with `r`.
/// with `r` (which ranges from 0 to 255).
///
/// Out of range values will have unexpected effects.
Color withRed(int r) {
return new Color.fromARGB(alpha, r, green, blue);
}
/// Returns a new color that matches this color with the green channel
/// replaced with `g`.
/// replaced with `g` (which ranges from 0 to 255).
///
/// Out of range values will have unexpected effects.
Color withGreen(int g) {
return new Color.fromARGB(alpha, red, g, blue);
}
/// Returns a new color that matches this color with the blue channel replaced
/// with `b`.
/// with `b` (which ranges from 0 to 255).
///
/// Out of range values will have unexpected effects.
Color withBlue(int b) {
return new Color.fromARGB(alpha, red, green, b);
}
@ -188,6 +198,12 @@ class Color {
///
/// If either color is null, this function linearly interpolates from a
/// transparent instance of the other color.
///
/// Values of `t` less that 0.0 or greater than 1.0 are supported. Each
/// channel will be clamped to the range 0 to 255.
///
/// This is intended to be fast but as a result may be ugly. Consider
/// [HSVColor] or writing custom logic for interpolating colors.
static Color lerp(Color a, Color b, double t) {
if (a == null && b == null)
return null;
@ -196,10 +212,10 @@ class Color {
if (b == null)
return _scaleAlpha(a, 1.0 - t);
return new Color.fromARGB(
lerpDouble(a.alpha, b.alpha, t).toInt(),
lerpDouble(a.red, b.red, t).toInt(),
lerpDouble(a.green, b.green, t).toInt(),
lerpDouble(a.blue, b.blue, t).toInt()
lerpDouble(a.alpha, b.alpha, t).toInt().clamp(0, 255),
lerpDouble(a.red, b.red, t).toInt().clamp(0, 255),
lerpDouble(a.green, b.green, t).toInt().clamp(0, 255),
lerpDouble(a.blue, b.blue, t).toInt().clamp(0, 255),
);
}

View File

@ -11,7 +11,7 @@ class NotAColor extends Color {
}
void main() {
test("color accessors should work", () {
test('color accessors should work', () {
Color foo = const Color(0x12345678);
expect(foo.alpha, equals(0x12));
expect(foo.red, equals(0x34));
@ -19,14 +19,14 @@ void main() {
expect(foo.blue, equals(0x78));
});
test("paint set to black", () {
test('paint set to black', () {
Color c = const Color(0x00000000);
Paint p = new Paint();
p.color = c;
expect(c.toString(), equals('Color(0x00000000)'));
});
test("color created with out of bounds value", () {
test('color created with out of bounds value', () {
try {
Color c = const Color(0x100 << 24);
Paint p = new Paint();
@ -36,7 +36,7 @@ void main() {
}
});
test("color created with wildly out of bounds value", () {
test('color created with wildly out of bounds value', () {
try {
Color c = const Color(1 << 1000000);
Paint p = new Paint();
@ -46,7 +46,7 @@ void main() {
}
});
test("two colors are only == if they have the same runtime type", () {
test('two colors are only == if they have the same runtime type', () {
expect(const Color(123), equals(const Color(123)));
expect(const Color(123), equals(new Color(123)));
expect(const Color(123), isNot(equals(const Color(321))));
@ -55,4 +55,26 @@ void main() {
expect(const NotAColor(123), equals(const NotAColor(123)));
});
test('Color.lerp', () {
expect(
Color.lerp(const Color(0x00000000), const Color(0xFFFFFFFF), 0.0),
const Color(0x00000000),
);
expect(
Color.lerp(const Color(0x00000000), const Color(0xFFFFFFFF), 0.5),
const Color(0x7F7F7F7F),
);
expect(
Color.lerp(const Color(0x00000000), const Color(0xFFFFFFFF), 1.0),
const Color(0xFFFFFFFF),
);
expect(
Color.lerp(const Color(0x00000000), const Color(0xFFFFFFFF), -0.1),
const Color(0x00000000),
);
expect(
Color.lerp(const Color(0x00000000), const Color(0xFFFFFFFF), 1.1),
const Color(0xFFFFFFFF),
);
});
}