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