Closed hungtk closed 4 years ago
To draw each frame in a separate file, you can use something like that:
save() async {
var data = await rootBundle.load('assets/HamburgerArrow.json');
var composition = await LottieComposition.fromByteData(data);
var drawable = LottieDrawable(composition);
var size = Size(composition.bounds.width.toDouble(),
composition.bounds.height.toDouble());
for (var i = composition.startFrame; i < composition.endFrame; i += 1) {
drawable.setProgress(i / composition.durationFrames);
var pictureRecorder = PictureRecorder();
var canvas = Canvas(pictureRecorder);
drawable.draw(canvas, Offset.zero & size);
var picture = pictureRecorder.endRecording();
var image = await picture.toImage(size.width.toInt(), size.height.toInt());
var bytes = await image.toByteData(format: ImageByteFormat.png);
await File('output_$i.png')
.writeAsBytes(bytes.buffer.asUint8List());
}
}
To draw all frames in a single file:
save() async {
var data = await rootBundle.load('assets/HamburgerArrow.json');
var composition = await LottieComposition.fromByteData(data);
var drawable = LottieDrawable(composition);
var pictureRecorder = PictureRecorder();
var canvas = Canvas(pictureRecorder);
var size = Size(500, 500);
var columns = 10;
for (var i = composition.startFrame; i < composition.endFrame; i += 1) {
drawable.setProgress(i / composition.durationFrames);
var destRect = Offset(i % columns * 50.0, i ~/ 10 * 80.0) & (size / 5);
drawable.draw(canvas, destRect);
}
var picture = pictureRecorder.endRecording();
var image = await picture.toImage(size.width.toInt(), size.height.toInt());
var bytes = await image.toByteData(format: ImageByteFormat.png);
await File('output.png')
.writeAsBytes(bytes.buffer.asUint8List());
}
You can run the code either as a normal Flutter app or directly on your dev machine using the Flutter test runner
I added this 2 examples here: https://github.com/xvrh/lottie-flutter/blob/master/example/lib/examples/save_frames.dart
Thank you so much @xvrh .
I have a issue. Can you explain for me?
I saved frames to temp directory. When I show 1 frame to UI. I got empty block.
And I converted image to base64 string, and I got:
iVBORw0KGgoAAAANSUhEUgAAEYAAAAnYCAYAAADZA7WJAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAACAASURBVHic7MEBAQAAAICQ/q/uCAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
I am using Image.file()
.
My frames located like: /data/user/0/com.example.test_lottie/cache/images/0070.png
Can you show your full code to help to debug?
Here's my code
Future<String> tempDir(String folderName) async {
final tempDirectory = await getTemporaryDirectory();
Directory dir = Directory(tempDirectory.path + '/' + folderName);
if (await dir.exists()) {
return dir.path;
}
final Directory newDir = await dir.create(recursive: true);
return newDir.path;
}
Future<String> saveAllFrames(ByteData data, String destination) async {
print('saveAllFrames...');
var composition = await LottieComposition.fromByteData(data);
var drawable = LottieDrawable(composition);
var size = Size(composition.bounds.width.toDouble(),
composition.bounds.height.toDouble());
for (var i = composition.startFrame; i < composition.endFrame; i += 1) {
var progress = i / composition.durationFrames;
drawable.setProgress(progress);
var pictureRecorder = PictureRecorder();
var canvas = Canvas(pictureRecorder);
drawable.draw(canvas, Offset.zero & size);
var picture = pictureRecorder.endRecording();
var image =
await picture.toImage(size.width.toInt(), size.height.toInt());
var bytes = await image.toByteData(format: ImageByteFormat.png);
var fileName = (i + 1).toInt().toString().padLeft(4, '0');
var filePath = join(destination, '$fileName.png');
frames.add(filePath.toString());
await File(filePath).writeAsBytes(bytes.buffer.asUint8List());
final bs64 = base64Decode(bytes.buffer.asUint8List().toString());
print((progress * 100).round().toString() + "%");
print(filePath);
}
}
I tried to improve my original example. Now it saves the files in the temp folder and displays the images immediately. To improve the performance, I only export 10 frames from the animation and in a lower resolution.
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:lottie/lottie.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<File> _frames;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
children: [
RaisedButton(
child: Text('Export all frames'),
onPressed: _export,
),
if (_frames != null)
Expanded(
child: Wrap(
children: [..._frames.map((f) => Image.file(f, width: 50))],
))
],
),
),
),
);
}
Future<void> _export() async {
var data = await rootBundle.load('assets/HamburgerArrow.json');
var frames = await exportFrames(
data, await _createTempDirectory('hamburger'),
progresses: [for (var i = 0.0; i <= 1; i += 0.1) i],
size: Size(50, 50));
setState(() {
_frames = frames;
});
}
}
Future<List<File>> exportFrames(ByteData data, String directory,
{@required Size size, @required List<double> progresses}) async {
var composition = await LottieComposition.fromByteData(data);
var drawable = LottieDrawable(composition);
var frames = <File>[];
for (var progress in progresses) {
drawable.setProgress(progress);
var pictureRecorder = PictureRecorder();
var canvas = Canvas(pictureRecorder);
drawable.draw(canvas, Offset.zero & size);
var picture = pictureRecorder.endRecording();
var image = await picture.toImage(size.width.toInt(), size.height.toInt());
var bytes = await image.toByteData(format: ImageByteFormat.png);
var fileName = (progress * 100).round().toString().padLeft(3, '0');
var file = File(p.join(directory, '$fileName.png'));
await file.writeAsBytes(bytes.buffer.asUint8List());
frames.add(file);
}
return frames;
}
Future<String> _createTempDirectory(String folderName) async {
final tempDirectory = await getTemporaryDirectory();
var dir = Directory(p.join(tempDirectory.path, folderName));
if (!dir.existsSync()) {
await dir.create(recursive: true);
}
return dir.path;
}
@xvrh Thank you so much.
When I run with assets/HamburgerArrow.json
with your code everything is perfect.
But with https://github.com/xvrh/lottie-flutter/blob/master/example/assets/lottiefiles/airbnb.json
it save a empty image.
The airbnb
uses a png image to display the logo. So to draw the animation it needs to also load the image after the json.
To do that, you can use the existing AssetLottie
class that has the code to load the images from the assets.
So instead of loading the file directly from the rootBundle
Something like:
var composition = await AssetLottie('assets/lottiefiles/airbnb.json').load();
I updated the example here: https://github.com/xvrh/lottie-flutter/blob/master/example/lib/examples/save_frames.dart
@xvrh wow, awsome. Thank you so much for helping.
How can we draw frame by frame and export these frames to image?