Open doubleA411 opened 1 year ago
Had a similar problem.
GifDecoder
seems to return the (relative) frames as they are stored in the GIF, not as they are rendered. To get the absolute frames, the code below works for Flutter.
static Future<(List<Uint8List>, List<int>)> extractGifFrames(
Uint8List data) async {
final List<int> durations = [];
final List<Uint8List> frames = <Uint8List>[];
final ui.Codec codec = await ui.instantiateImageCodec(data);
final int frameCount = codec.frameCount;
print('Total frameCount: $frameCount');
for (int i = 0; i < frameCount; i++) {
final ui.FrameInfo fi = await codec.getNextFrame();
final frame = await _loadImage(fi.image);
if (frame != null) {
print(' -- extracted frame $i');
frames.add(frame);
durations.add(fi.duration.inMilliseconds);
}
}
return (frames, durations);
}
These absolute frames can be easily combined with the GifEncoder
:
static Future<Uint8List> pngBytesToGifBytes(
List<Uint8List> pngBytes, List<int> durationsMillis) async {
return compute(_pngBytesToGifBytes,
{'pngBytes': pngBytes, 'durationsMillis': durationsMillis});
}
static Uint8List _pngBytesToGifBytes(Map<String, dynamic> param) {
final pngBytes = param['pngBytes']!;
final durationsMillis = param['durationsMillis']!;
final encode = img.GifEncoder();
for (var i = 0; i < pngBytes.length; i++) {
final dur = (durationsMillis.elementAt(i)) ~/ 10;
print(' - adding frame $i with duration $dur/100 s');
encode.addFrame(img.decodePng(pngBytes.elementAt(i))!, duration: dur);
}
return encode.finish()!;
}
static Future<Uint8List?> _loadImage(ui.Image image) async {
final ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
return byteData?.buffer.asUint8List();
}
This needs more testing, a GIF inspector can be found here:
@xErik Thanks for that code snippet. I was transforming to animated PNG and needed to tweak the pngBytesToGifBytes
method slightly. When building the apng
it was necessary to add the frames to the first image, rather than the encoder
.
For reference, here's the code I used:
Uint8List _imageFramesToPngIsolate((List<Uint8List> pngBytes, List<int> durationsMillis) param) {
final (pngBytes, durationsMillis) = param;
final encoder = PngEncoder()
..repeat = -1
..isAnimated = pngBytes.length > 1;
final imageToBeEncoded = decodePng(pngBytes.first);
if (imageToBeEncoded == null) {
throw Exception('First image cannot be decoded');
}
for (var i = 1; i < pngBytes.length; i++) {
final dur = durationsMillis.elementAt(i);
print(' - adding frame $i with duration ${dur}ms');
imageToBeEncoded.addFrame(
decodePng(pngBytes.elementAt(i))!
..frameDuration = dur
..frameIndex = i
..frameType = FrameType.animation,
);
}
return encoder.encode(imageToBeEncoded);
}
I'm trying to converting a list of frames captured from the RepaintBoundary of Flutter to a small Animated GiF. But the docs only has an example for single Image conversion and Also the guide in the Animated Image was not clear.
Can you help by providing a clear guide or something ?