mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add lerping between Gradients with arbitrary number of colors and stops (#27435)
This commit is contained in:
parent
70c8b63e15
commit
d27f53904b
@ -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.25,
|
||||
0.75,
|
||||
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.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.25,
|
||||
0.75,
|
||||
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.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);
|
||||
|
Loading…
Reference in New Issue
Block a user