Commit d27f5390 authored by Sander Kersten's avatar Sander Kersten Committed by Michael Goderbauer

Add lerping between Gradients with arbitrary number of colors and stops (#27435)

parent 70c8b63e
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:collection';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui' as ui show Gradient, lerpDouble; import 'dart:ui' as ui show Gradient, lerpDouble;
...@@ -16,22 +17,43 @@ class _ColorsAndStops { ...@@ -16,22 +17,43 @@ class _ColorsAndStops {
final List<double> stops; final List<double> stops;
} }
_ColorsAndStops _interpolateColorsAndStops(List<Color> aColors, List<double> aStops, List<Color> bColors, List<double> bStops, double t) { /// Calculate the color at position [t] of the gradient defined by [colors] and [stops].
assert(aColors.length == bColors.length, 'Cannot interpolate between two gradients with a different number of colors.'); // TODO(ianh): remove limitation Color _sample(List<Color> colors, List<double> stops, double t) {
assert((aStops == null && aColors.length == 2) || (aStops != null && aStops.length == aColors.length)); assert(colors != null);
assert((bStops == null && bColors.length == 2) || (bStops != null && bStops.length == bColors.length)); assert(colors.isNotEmpty);
final List<Color> interpolatedColors = <Color>[]; assert(stops != null);
for (int i = 0; i < aColors.length; i += 1) assert(stops.isNotEmpty);
interpolatedColors.add(Color.lerp(aColors[i], bColors[i], t)); assert(t != null);
List<double> interpolatedStops; if (t <= stops.first)
if (aStops != null || bStops != null) { return colors.first;
aStops ??= const <double>[0.0, 1.0]; if (t >= stops.last)
bStops ??= const <double>[0.0, 1.0]; return colors.last;
assert(aStops.length == bStops.length); final int index = stops.lastIndexWhere((double s) => s <= t);
interpolatedStops = <double>[]; assert(index != -1);
for (int i = 0; i < aStops.length; i += 1) return Color.lerp(
interpolatedStops.add(ui.lerpDouble(aStops[i], bStops[i], t).clamp(0.0, 1.0)); colors[index], colors[index + 1],
} (t - stops[index]) / (stops[index + 1] - stops[index])
);
}
_ColorsAndStops _interpolateColorsAndStops(
List<Color> aColors,
List<double> aStops,
List<Color> bColors,
List<double> bStops,
double t,
) {
assert(aColors.length >= 2);
assert(bColors.length >= 2);
assert(aStops.length == aColors.length);
assert(bStops.length == bColors.length);
final SplayTreeSet<double> stops = SplayTreeSet<double>()
..addAll(aStops)
..addAll(bStops);
final List<double> interpolatedStops = stops.toList(growable: false);
final List<Color> interpolatedColors = interpolatedStops.map<Color>(
(double stop) => Color.lerp(_sample(aColors, aStops, stop), _sample(bColors, bStops, stop), t)
).toList(growable: false);
return _ColorsAndStops(interpolatedColors, interpolatedStops); return _ColorsAndStops(interpolatedColors, interpolatedStops);
} }
...@@ -88,8 +110,6 @@ abstract class Gradient { ...@@ -88,8 +110,6 @@ abstract class Gradient {
List<double> _impliedStops() { List<double> _impliedStops() {
if (stops != null) if (stops != null)
return stops; return stops;
if (colors.length == 2)
return null;
assert(colors.length >= 2, 'colors list must have at least two colors'); assert(colors.length >= 2, 'colors list must have at least two colors');
final double separation = 1.0 / (colors.length - 1); final double separation = 1.0 / (colors.length - 1);
return List<double>.generate( return List<double>.generate(
...@@ -335,14 +355,14 @@ class LinearGradient extends Gradient { ...@@ -335,14 +355,14 @@ class LinearGradient extends Gradient {
@override @override
Gradient lerpFrom(Gradient a, double t) { Gradient lerpFrom(Gradient a, double t) {
if (a == null || (a is LinearGradient && a.colors.length == colors.length)) // TODO(ianh): remove limitation if (a == null || (a is LinearGradient))
return LinearGradient.lerp(a, this, t); return LinearGradient.lerp(a, this, t);
return super.lerpFrom(a, t); return super.lerpFrom(a, t);
} }
@override @override
Gradient lerpTo(Gradient b, double t) { Gradient lerpTo(Gradient b, double t) {
if (b == null || (b is LinearGradient && b.colors.length == colors.length)) // TODO(ianh): remove limitation if (b == null || (b is LinearGradient))
return LinearGradient.lerp(this, b, t); return LinearGradient.lerp(this, b, t);
return super.lerpTo(b, t); return super.lerpTo(b, t);
} }
...@@ -374,7 +394,13 @@ class LinearGradient extends Gradient { ...@@ -374,7 +394,13 @@ class LinearGradient extends Gradient {
return b.scale(t); return b.scale(t);
if (b == null) if (b == null)
return a.scale(1.0 - t); return a.scale(1.0 - t);
final _ColorsAndStops interpolated = _interpolateColorsAndStops(a.colors, a.stops, b.colors, b.stops, t); final _ColorsAndStops interpolated = _interpolateColorsAndStops(
a.colors,
a._impliedStops(),
b.colors,
b._impliedStops(),
t,
);
return LinearGradient( return LinearGradient(
begin: AlignmentGeometry.lerp(a.begin, b.begin, t), begin: AlignmentGeometry.lerp(a.begin, b.begin, t),
end: AlignmentGeometry.lerp(a.end, b.end, t), end: AlignmentGeometry.lerp(a.end, b.end, t),
...@@ -604,14 +630,14 @@ class RadialGradient extends Gradient { ...@@ -604,14 +630,14 @@ class RadialGradient extends Gradient {
@override @override
Gradient lerpFrom(Gradient a, double t) { Gradient lerpFrom(Gradient a, double t) {
if (a == null || (a is RadialGradient && a.colors.length == colors.length)) // TODO(ianh): remove limitation if (a == null || (a is RadialGradient))
return RadialGradient.lerp(a, this, t); return RadialGradient.lerp(a, this, t);
return super.lerpFrom(a, t); return super.lerpFrom(a, t);
} }
@override @override
Gradient lerpTo(Gradient b, double t) { Gradient lerpTo(Gradient b, double t) {
if (b == null || (b is RadialGradient && b.colors.length == colors.length)) // TODO(ianh): remove limitation if (b == null || (b is RadialGradient))
return RadialGradient.lerp(this, b, t); return RadialGradient.lerp(this, b, t);
return super.lerpTo(b, t); return super.lerpTo(b, t);
} }
...@@ -643,7 +669,13 @@ class RadialGradient extends Gradient { ...@@ -643,7 +669,13 @@ class RadialGradient extends Gradient {
return b.scale(t); return b.scale(t);
if (b == null) if (b == null)
return a.scale(1.0 - t); return a.scale(1.0 - t);
final _ColorsAndStops interpolated = _interpolateColorsAndStops(a.colors, a.stops, b.colors, b.stops, t); final _ColorsAndStops interpolated = _interpolateColorsAndStops(
a.colors,
a._impliedStops(),
b.colors,
b._impliedStops(),
t,
);
return RadialGradient( return RadialGradient(
center: AlignmentGeometry.lerp(a.center, b.center, t), center: AlignmentGeometry.lerp(a.center, b.center, t),
radius: math.max(0.0, ui.lerpDouble(a.radius, b.radius, t)), radius: math.max(0.0, ui.lerpDouble(a.radius, b.radius, t)),
...@@ -835,14 +867,14 @@ class SweepGradient extends Gradient { ...@@ -835,14 +867,14 @@ class SweepGradient extends Gradient {
@override @override
Gradient lerpFrom(Gradient a, double t) { Gradient lerpFrom(Gradient a, double t) {
if (a == null || (a is SweepGradient && a.colors.length == colors.length)) // TODO(ianh): remove limitation if (a == null || (a is SweepGradient))
return SweepGradient.lerp(a, this, t); return SweepGradient.lerp(a, this, t);
return super.lerpFrom(a, t); return super.lerpFrom(a, t);
} }
@override @override
Gradient lerpTo(Gradient b, double t) { Gradient lerpTo(Gradient b, double t) {
if (b == null || (b is SweepGradient && b.colors.length == colors.length)) // TODO(ianh): remove limitation if (b == null || (b is SweepGradient))
return SweepGradient.lerp(this, b, t); return SweepGradient.lerp(this, b, t);
return super.lerpTo(b, t); return super.lerpTo(b, t);
} }
...@@ -873,7 +905,13 @@ class SweepGradient extends Gradient { ...@@ -873,7 +905,13 @@ class SweepGradient extends Gradient {
return b.scale(t); return b.scale(t);
if (b == null) if (b == null)
return a.scale(1.0 - t); return a.scale(1.0 - t);
final _ColorsAndStops interpolated = _interpolateColorsAndStops(a.colors, a.stops, b.colors, b.stops, t); final _ColorsAndStops interpolated = _interpolateColorsAndStops(
a.colors,
a._impliedStops(),
b.colors,
b._impliedStops(),
t,
);
return SweepGradient( return SweepGradient(
center: AlignmentGeometry.lerp(a.center, b.center, t), center: AlignmentGeometry.lerp(a.center, b.center, t),
startAngle: math.max(0.0, ui.lerpDouble(a.startAngle, b.startAngle, t)), startAngle: math.max(0.0, ui.lerpDouble(a.startAngle, b.startAngle, t)),
......
...@@ -56,6 +56,7 @@ void main() { ...@@ -56,6 +56,7 @@ void main() {
Color(0x3B3B3B3B), Color(0x3B3B3B3B),
Color(0x77777777), Color(0x77777777),
], ],
stops: <double>[0, 1],
)); ));
}); });
...@@ -91,11 +92,84 @@ void main() { ...@@ -91,11 +92,84 @@ void main() {
end: Alignment(-1.0, 0.0), end: Alignment(-1.0, 0.0),
colors: <Color>[ colors: <Color>[
Color(0x3B3B3B3B), Color(0x3B3B3B3B),
Color(0x55555555),
Color(0x77777777), Color(0x77777777),
], ],
stops: <double>[ stops: <double>[
0.25, 0.0,
0.75, 0.5,
1.0,
],
));
});
test('LinearGradient lerp test with unequal number of colors', () {
const LinearGradient testGradient1 = LinearGradient(
colors: <Color>[
Color(0x22222222),
Color(0x66666666),
],
);
const LinearGradient testGradient2 = LinearGradient(
colors: <Color>[
Color(0x44444444),
Color(0x66666666),
Color(0x88888888),
],
);
final LinearGradient actual = LinearGradient.lerp(testGradient1, testGradient2, 0.5);
expect(actual, const LinearGradient(
colors: <Color>[
Color(0x33333333),
Color(0x55555555),
Color(0x77777777),
],
stops: <double>[
0.0,
0.5,
1.0,
],
));
});
test('LinearGradient lerp test with stops and unequal number of colors', () {
const LinearGradient testGradient1 = LinearGradient(
colors: <Color>[
Color(0x33333333),
Color(0x66666666),
],
stops: <double>[
0.0,
0.5,
],
);
const LinearGradient testGradient2 = LinearGradient(
colors: <Color>[
Color(0x44444444),
Color(0x48484848),
Color(0x88888888),
],
stops: <double>[
0.5,
0.7,
1.0,
],
);
final LinearGradient actual = LinearGradient.lerp(testGradient1, testGradient2, 0.5);
expect(actual, const LinearGradient(
colors: <Color>[
Color(0x3B3B3B3B),
Color(0x55555555),
Color(0x57575757),
Color(0x77777777),
],
stops: <double>[
0.0,
0.5,
0.7,
1.0,
], ],
)); ));
}); });
...@@ -221,6 +295,10 @@ void main() { ...@@ -221,6 +295,10 @@ void main() {
Color(0x3B3B3B3B), Color(0x3B3B3B3B),
Color(0x77777777), Color(0x77777777),
], ],
stops: <double>[
0.0,
1.0,
],
)); ));
}); });
...@@ -259,11 +337,84 @@ void main() { ...@@ -259,11 +337,84 @@ void main() {
radius: 15.0, radius: 15.0,
colors: <Color>[ colors: <Color>[
Color(0x3B3B3B3B), Color(0x3B3B3B3B),
Color(0x55555555),
Color(0x77777777),
],
stops: <double>[
0.0,
0.5,
1.0
],
));
});
test('RadialGradient lerp test with unequal number of colors', () {
const RadialGradient testGradient1 = RadialGradient(
colors: <Color>[
Color(0x22222222),
Color(0x66666666),
],
);
const RadialGradient testGradient2 = RadialGradient(
colors: <Color>[
Color(0x44444444),
Color(0x66666666),
Color(0x88888888),
],
);
final RadialGradient actual = RadialGradient.lerp(testGradient1, testGradient2, 0.5);
expect(actual, const RadialGradient(
colors: <Color>[
Color(0x33333333),
Color(0x55555555),
Color(0x77777777), Color(0x77777777),
], ],
stops: <double>[ stops: <double>[
0.25, 0.0,
0.75, 0.5,
1.0,
],
));
});
test('RadialGradient lerp test with stops and unequal number of colors', () {
const RadialGradient testGradient1 = RadialGradient(
colors: <Color>[
Color(0x33333333),
Color(0x66666666),
],
stops: <double>[
0.0,
0.5,
],
);
const RadialGradient testGradient2 = RadialGradient(
colors: <Color>[
Color(0x44444444),
Color(0x48484848),
Color(0x88888888),
],
stops: <double>[
0.5,
0.7,
1.0,
],
);
final RadialGradient actual = RadialGradient.lerp(testGradient1, testGradient2, 0.5);
expect(actual, const RadialGradient(
colors: <Color>[
Color(0x3B3B3B3B),
Color(0x55555555),
Color(0x57575757),
Color(0x77777777),
],
stops: <double>[
0.0,
0.5,
0.7,
1.0,
], ],
)); ));
}); });
...@@ -308,6 +459,10 @@ void main() { ...@@ -308,6 +459,10 @@ void main() {
Color(0x3B3B3B3B), Color(0x3B3B3B3B),
Color(0x77777777), Color(0x77777777),
], ],
stops: <double>[
0.0,
1.0,
],
)); ));
final RadialGradient actual2 = RadialGradient.lerp(testGradient1, testGradient3, 0.5); final RadialGradient actual2 = RadialGradient.lerp(testGradient1, testGradient3, 0.5);
...@@ -320,6 +475,10 @@ void main() { ...@@ -320,6 +475,10 @@ void main() {
Color(0x3B3B3B3B), Color(0x3B3B3B3B),
Color(0x77777777), Color(0x77777777),
], ],
stops: <double>[
0.0,
1.0,
],
)); ));
}); });
...@@ -352,6 +511,10 @@ void main() { ...@@ -352,6 +511,10 @@ void main() {
Color(0x3B3B3B3B), Color(0x3B3B3B3B),
Color(0x77777777), Color(0x77777777),
], ],
stops: <double>[
0.0,
1.0,
],
)); ));
}); });
...@@ -390,11 +553,84 @@ void main() { ...@@ -390,11 +553,84 @@ void main() {
endAngle: math.pi * 3/4, endAngle: math.pi * 3/4,
colors: <Color>[ colors: <Color>[
Color(0x3B3B3B3B), Color(0x3B3B3B3B),
Color(0x55555555),
Color(0x77777777),
],
stops: <double>[
0.0,
0.5,
1.0,
],
));
});
test('SweepGradient lerp test with unequal number of colors', () {
const SweepGradient testGradient1 = SweepGradient(
colors: <Color>[
Color(0x22222222),
Color(0x66666666),
],
);
const SweepGradient testGradient2 = SweepGradient(
colors: <Color>[
Color(0x44444444),
Color(0x66666666),
Color(0x88888888),
],
);
final SweepGradient actual = SweepGradient.lerp(testGradient1, testGradient2, 0.5);
expect(actual, const SweepGradient(
colors: <Color>[
Color(0x33333333),
Color(0x55555555),
Color(0x77777777),
],
stops: <double>[
0.0,
0.5,
1.0,
],
));
});
test('SweepGradient lerp test with stops and unequal number of colors', () {
const SweepGradient testGradient1 = SweepGradient(
colors: <Color>[
Color(0x33333333),
Color(0x66666666),
],
stops: <double>[
0.0,
0.5,
],
);
const SweepGradient testGradient2 = SweepGradient(
colors: <Color>[
Color(0x44444444),
Color(0x48484848),
Color(0x88888888),
],
stops: <double>[
0.5,
0.7,
1.0,
],
);
final SweepGradient actual = SweepGradient.lerp(testGradient1, testGradient2, 0.5);
expect(actual, const SweepGradient(
colors: <Color>[
Color(0x3B3B3B3B),
Color(0x55555555),
Color(0x57575757),
Color(0x77777777), Color(0x77777777),
], ],
stops: <double>[ stops: <double>[
0.25, 0.0,
0.75, 0.5,
0.7,
1.0,
], ],
)); ));
}); });
...@@ -431,6 +667,10 @@ void main() { ...@@ -431,6 +667,10 @@ void main() {
Color(0x33333333), Color(0x33333333),
Color(0x66666666), Color(0x66666666),
], ],
stops: <double>[
0.0,
1.0,
],
); );
const RadialGradient testGradient2 = RadialGradient( const RadialGradient testGradient2 = RadialGradient(
center: Alignment(0.0, -1.0), center: Alignment(0.0, -1.0),
...@@ -439,6 +679,10 @@ void main() { ...@@ -439,6 +679,10 @@ void main() {
Color(0x3B3B3B3B), Color(0x3B3B3B3B),
Color(0x77777777), Color(0x77777777),
], ],
stops: <double>[
0.0,
1.0,
],
); );
const RadialGradient testGradient3 = RadialGradient( const RadialGradient testGradient3 = RadialGradient(
center: Alignment.topRight, center: Alignment.topRight,
...@@ -447,6 +691,10 @@ void main() { ...@@ -447,6 +691,10 @@ void main() {
Color(0x44444444), Color(0x44444444),
Color(0x88888888), Color(0x88888888),
], ],
stops: <double>[
0.0,
1.0,
],
); );
expect(Gradient.lerp(testGradient1, testGradient3, 0.0), testGradient1); expect(Gradient.lerp(testGradient1, testGradient3, 0.0), testGradient1);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment