Skip to content

Commit

Permalink
Fix gradients (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
xvrh authored Mar 5, 2020
1 parent 2914caf commit d525de8
Show file tree
Hide file tree
Showing 20 changed files with 135 additions and 81 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/analyze-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- run: flutter pub run tool/prepare_submit.dart
- name: "check for uncommitted changes"
run: |
git diff --exit-code --stat -- . \
git diff --exit-code --stat -- . ':(exclude)*pubspec.lock' \
|| (echo "##[error] found changed files after build. please run 'dart tool/prepare_submit.dart'" \
"and check in all changes" \
&& exit 1)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## [0.3.0] - 2020-03-02
- Add `LottieDelegates` a group of options to customize the lottie animation at runtime.
ie: Dynamically modify color, position, size, text... of every elements of the animation.
- Correctly display Linear and Radial Gradients
- Integrate latest changes from Lottie-android

## [0.2.2] - 2020-02-21
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.11"
version: "0.2.15"
typed_data:
dependency: transitive
description:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions example/test/gradient_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:lottie/lottie.dart';

void main() {
void testGradient(String name, ValueDelegate valueDelegate) {
testWidgets(name, (tester) async {
var composition = await LottieComposition.fromBytes(
File('assets/Tests/DynamicGradient.json').readAsBytesSync());

var animation =
AnimationController(vsync: tester, duration: composition.duration);

await tester.pumpWidget(
Lottie(
composition: composition,
controller: animation,
delegates: LottieDelegates(values: [valueDelegate]),
),
);

var screenshotName = name
.toLowerCase()
.replaceAll(RegExp('[^a-z0-9 ]'), '')
.replaceAll(' ', '_');

await expectLater(find.byType(Lottie),
matchesGoldenFile('goldens/gradients/$screenshotName.png'));
});
}

testGradient(
'Linear Gradient Fill',
ValueDelegate.gradientColor(['Linear', 'Rectangle', 'Gradient Fill'],
value: [Color(0xFFFFFF00), Color(0xFF00FF00)]));

testGradient(
'Radial Gradient Fill',
ValueDelegate.gradientColor(['Radial', 'Rectangle', 'Gradient Fill'],
value: [Color(0xFFFFFF00), Color(0xFF00FF00)]));

testGradient(
'Linear Gradient Stroke',
ValueDelegate.gradientColor(['Linear', 'Rectangle', 'Gradient Stroke'],
value: [Color(0xFFFFFF00), Color(0xFF00FF00)]));

testGradient(
'Radial Gradient Stroke',
ValueDelegate.gradientColor(['Radial', 'Rectangle', 'Gradient Stroke'],
value: [Color(0xFFFFFF00), Color(0xFF00FF00)]));

testGradient(
'Opacity Linear Gradient Fill',
ValueDelegate.opacity(['Linear', 'Rectangle', 'Gradient Fill'],
value: 50));
}
45 changes: 18 additions & 27 deletions lib/src/animation/content/gradient_fill_content.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:ui';
import 'package:flutter/painting.dart';
import 'package:vector_math/vector_math_64.dart';
import '../../l.dart';
import '../../lottie_drawable.dart';
Expand All @@ -24,8 +23,8 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
static final int _cacheStepsMs = 32;
final BaseLayer layer;
final GradientFill _fill;
final _linearGradientCache = <int, LinearGradient>{};
final _radialGradientCache = <int, RadialGradient>{};
final _linearGradientCache = <int, Gradient>{};
final _radialGradientCache = <int, Gradient>{};
final _path = Path();
final _paint = Paint();
final _paths = <PathContent>[];
Expand Down Expand Up @@ -90,20 +89,14 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
matrix4: parentMatrix.storage);
}

var boundsRect = _path.getBounds();

Gradient gradient;
if (_fill.gradientType == GradientType.linear) {
gradient = _getLinearGradient();
gradient = _getLinearGradient(parentMatrix);
} else {
gradient = _getRadialGradient();
gradient = _getRadialGradient(parentMatrix);
}

//TODO(xha)
//gradient.setLocalMatrix(parentMatrix);

//TODO(xha): cache the shader
_paint.shader = gradient.createShader(boundsRect);
_paint.shader = gradient;

if (_colorFilterAnimation != null) {
_paint.colorFilter = _colorFilterAnimation.value;
Expand Down Expand Up @@ -131,8 +124,8 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
outBounds.right + 1, outBounds.bottom + 1);
}

LinearGradient _getLinearGradient() {
var gradientHash = _getGradientHash();
Gradient _getLinearGradient(Matrix4 parentMatrix) {
var gradientHash = _getGradientHash(parentMatrix);
var gradient = _linearGradientCache[gradientHash];
if (gradient != null) {
return gradient;
Expand All @@ -142,17 +135,14 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
var gradientColor = _colorAnimation.value;
var colors = _applyDynamicColorsIfNeeded(gradientColor.colors);
var positions = gradientColor.positions;
gradient = LinearGradient(
begin: Alignment(startPoint.dx, startPoint.dy),
end: Alignment(endPoint.dx, endPoint.dy),
colors: colors,
stops: positions);
gradient = Gradient.linear(startPoint, endPoint, colors, positions,
TileMode.clamp, parentMatrix.storage);
_linearGradientCache[gradientHash] = gradient;
return gradient;
}

RadialGradient _getRadialGradient() {
var gradientHash = _getGradientHash();
Gradient _getRadialGradient(Matrix4 parentMatrix) {
var gradientHash = _getGradientHash(parentMatrix);
var gradient = _radialGradientCache[gradientHash];
if (gradient != null) {
return gradient;
Expand All @@ -166,17 +156,17 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
var y0 = startPoint.dy;
var x1 = endPoint.dx;
var y1 = endPoint.dy;
var r = hypot(x1 - x0, y1 - y0).toDouble();
if (r <= 0) {
r = 0.001;
var radius = hypot(x1 - x0, y1 - y0).toDouble();
if (radius <= 0) {
radius = 0.001;
}
gradient = RadialGradient(
center: Alignment(x0, y0), radius: r, colors: colors, stops: positions);
gradient = Gradient.radial(startPoint, radius, colors, positions,
TileMode.clamp, parentMatrix.storage);
_radialGradientCache[gradientHash] = gradient;
return gradient;
}

int _getGradientHash() {
int _getGradientHash(Matrix4 parentMatrix) {
var startPointProgress =
(_startPointAnimation.progress * _cacheSteps).round();
var endPointProgress = (_endPointAnimation.progress * _cacheSteps).round();
Expand All @@ -191,6 +181,7 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
if (colorProgress != 0) {
hash = hash * 31 * colorProgress;
}
hash *= 31 * parentMatrix.hashCode;
return hash;
}

Expand Down
47 changes: 20 additions & 27 deletions lib/src/animation/content/gradient_stroke_content.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:ui';
import 'package:flutter/painting.dart';
import 'package:vector_math/vector_math_64.dart';
import '../../lottie_drawable.dart';
import '../../lottie_property.dart';
Expand All @@ -21,8 +20,8 @@ class GradientStrokeContent extends BaseStrokeContent {
@override
final String name;
final bool _hidden;
final _linearGradientCache = <int, LinearGradient>{};
final _radialGradientCache = <int, RadialGradient>{};
final _linearGradientCache = <int, Gradient>{};
final _radialGradientCache = <int, Gradient>{};

final GradientType _type;
final int _cacheSteps;
Expand Down Expand Up @@ -66,25 +65,21 @@ class GradientStrokeContent extends BaseStrokeContent {
if (_hidden) {
return;
}
var boundsRect = getBounds(parentMatrix, applyParents: false);

Gradient gradient;
if (_type == GradientType.linear) {
gradient = _getLinearGradient();
gradient = _getLinearGradient(parentMatrix);
} else {
gradient = _getRadialGradient();
gradient = _getRadialGradient(parentMatrix);
}

//TODO(xha): transform the gradient with the matrix.
//shader.setLocalMatrix(parentMatrix);
//TODO(xha): cache the shader
paint.shader = gradient.createShader(boundsRect);
paint.shader = gradient;

super.draw(canvas, size, parentMatrix, parentAlpha: parentAlpha);
}

LinearGradient _getLinearGradient() {
var gradientHash = _getGradientHash();
Gradient _getLinearGradient(Matrix4 parentMatrix) {
var gradientHash = _getGradientHash(parentMatrix);
var gradient = _linearGradientCache[gradientHash];
if (gradient != null) {
return gradient;
Expand All @@ -94,21 +89,15 @@ class GradientStrokeContent extends BaseStrokeContent {
var gradientColor = _colorAnimation.value;
var colors = _applyDynamicColorsIfNeeded(gradientColor.colors);
var positions = gradientColor.positions;
var x0 = startPoint.dx;
var y0 = startPoint.dy;
var x1 = endPoint.dx;
var y1 = endPoint.dy;
gradient = LinearGradient(
begin: Alignment(x0, y0),
end: Alignment(x1, y1),
colors: colors,
stops: positions);

gradient = Gradient.linear(startPoint, endPoint, colors, positions,
TileMode.clamp, parentMatrix.storage);
_linearGradientCache[gradientHash] = gradient;
return gradient;
}

RadialGradient _getRadialGradient() {
var gradientHash = _getGradientHash();
Gradient _getRadialGradient(Matrix4 parentMatrix) {
var gradientHash = _getGradientHash(parentMatrix);
var gradient = _radialGradientCache[gradientHash];
if (gradient != null) {
return gradient;
Expand All @@ -122,14 +111,17 @@ class GradientStrokeContent extends BaseStrokeContent {
var y0 = startPoint.dy;
var x1 = endPoint.dx;
var y1 = endPoint.dy;
var r = hypot(x1 - x0, y1 - y0).toDouble();
gradient = RadialGradient(
center: Alignment(x0, y0), radius: r, colors: colors, stops: positions);
var radius = hypot(x1 - x0, y1 - y0).toDouble();
gradient = Gradient.radial(startPoint, radius, colors, positions,
TileMode.clamp, parentMatrix.storage);
_radialGradientCache[gradientHash] = gradient;
return gradient;
}

int _getGradientHash() {
//TODO(xha): cache the shader based on the input parameters and not the animation
// progress.
// At first, log when there is too many cache miss.
int _getGradientHash(Matrix4 parentMatrix) {
var startPointProgress =
(_startPointAnimation.progress * _cacheSteps).round();
var endPointProgress = (_endPointAnimation.progress * _cacheSteps).round();
Expand All @@ -144,6 +136,7 @@ class GradientStrokeContent extends BaseStrokeContent {
if (colorProgress != 0) {
hash = hash * 31 * colorProgress;
}
hash *= 31 * parentMatrix.hashCode;
return hash;
}

Expand Down
19 changes: 10 additions & 9 deletions lib/src/composition.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import 'parser/moshi/json_reader.dart';
import 'performance_tracker.dart';
import 'providers/load_image.dart';

LottieComposition internalCreateComposition() => LottieComposition._();

void internalInit(
LottieComposition composition,
Rectangle<int> bounds,
Expand Down Expand Up @@ -46,11 +44,12 @@ void internalInit(
}

class LottieComposition {
static Future<LottieComposition> fromByteData(ByteData data) {
return fromBytes(data.buffer.asUint8List());
static Future<LottieComposition> fromByteData(ByteData data, {String name}) {
return fromBytes(data.buffer.asUint8List(), name: name);
}

static Future<LottieComposition> fromBytes(Uint8List bytes) async {
static Future<LottieComposition> fromBytes(Uint8List bytes,
{String name}) async {
Archive archive;
if (bytes[0] == 0x50 && bytes[1] == 0x4B) {
archive = ZipDecoder().decodeBytes(bytes);
Expand All @@ -59,8 +58,8 @@ class LottieComposition {
}

//TODO(xha): try to decode using the "compute" function to release the UI thread?
var composition =
LottieCompositionParser.parse(JsonReader.fromBytes(bytes));
var composition = LottieCompositionParser.parse(
LottieComposition._(name), JsonReader.fromBytes(bytes));

if (archive != null) {
for (var image in composition.images.values) {
Expand All @@ -78,8 +77,9 @@ class LottieComposition {
return composition;
}

LottieComposition._();
LottieComposition._(this.name);

final String name;
final _performanceTracker = PerformanceTracker();
// This is stored as a set to avoid duplicates.
final _warnings = <String>{};
Expand All @@ -106,7 +106,8 @@ class LottieComposition {
int _maskAndMatteCount = 0;

void addWarning(String warning) {
logger.warning(warning);
var prefix = name != null && name.isNotEmpty ? '$name: ' : '';
logger.warning('$prefix$warning');
_warnings.add(warning);
}

Expand Down
3 changes: 1 addition & 2 deletions lib/src/parser/content_model_parser.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import '../composition.dart';
import '../logger.dart';
import '../model/content/content_model.dart';
import 'animatable_transform_parser.dart';
import 'circle_shape_parser.dart';
Expand Down Expand Up @@ -91,7 +90,7 @@ class ContentModelParser {
model = RepeaterParser.parse(reader, composition);
break;
default:
logger.warning('Unknown shape type $type');
composition.addWarning('Unknown shape type $type');
}

while (reader.hasNext()) {
Expand Down
Loading

0 comments on commit d525de8

Please sign in to comment.