Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added lib/ui/fixtures/four_frame_with_reuse.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added lib/ui/fixtures/four_frame_with_reuse_end.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added lib/ui/fixtures/heart.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added lib/ui/fixtures/heart_end.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 7 additions & 9 deletions lib/ui/painting/multi_frame_codec.cc
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,6 @@ sk_sp<DlImage> MultiFrameCodec::State::GetNextFrameImage(
<< "Frame " << nextFrameIndex_ << " depends on frame "
<< requiredFrameIndex
<< " and no required frames are cached. Using blank slate instead.";
} else if (lastRequiredFrameIndex_ != requiredFrameIndex) {
FML_DLOG(INFO) << "Required frame " << requiredFrameIndex
<< " is not cached. Using blank slate instead.";
} else {
// Copy the previous frame's output buffer into the current frame as the
// starting point.
Expand All @@ -138,7 +135,8 @@ sk_sp<DlImage> MultiFrameCodec::State::GetNextFrameImage(
}

// Hold onto this if we need it to decode future frames.
if (frameInfo.disposal_method == SkCodecAnimation::DisposalMethod::kKeep) {
if (frameInfo.disposal_method == SkCodecAnimation::DisposalMethod::kKeep ||
lastRequiredFrame_) {
lastRequiredFrame_ = std::make_unique<SkBitmap>(bitmap);
lastRequiredFrameIndex_ = nextFrameIndex_;
}
Expand All @@ -161,9 +159,9 @@ sk_sp<DlImage> MultiFrameCodec::State::GetNextFrameImage(
gpu_disable_sync_switch->Execute(
fml::SyncSwitch::Handlers()
.SetIfTrue([&skImage, &bitmap] {
// Defer decoding until time of draw later on the raster thread. Can
// happen when GL operations are currently forbidden such as in the
// background on iOS.
// Defer decoding until time of draw later on the raster thread.
// Can happen when GL operations are currently forbidden such as
// in the background on iOS.
skImage = SkImage::MakeFromBitmap(bitmap);
})
.SetIfFalse([&skImage, &resourceContext, &bitmap] {
Expand Down Expand Up @@ -257,8 +255,8 @@ Dart_Handle MultiFrameCodec::getNextFrame(Dart_Handle callback_handle) {
}));

return Dart_Null();
// The static leak checker gets confused by the control flow, unique pointers
// and closures in this function.
// The static leak checker gets confused by the control flow, unique
// pointers and closures in this function.
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
}

Expand Down
51 changes: 51 additions & 0 deletions testing/dart/codec_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,57 @@ void main() {
expect(e.toString(), contains('Decoded image has been disposed'));
}
});

test('Animated gif can reuse across multiple frames', () async {
// Regression test for b/271947267 and https://github.com/flutter/flutter/issues/122134

final Uint8List data = File(
path.join('flutter', 'lib', 'ui', 'fixtures', 'four_frame_with_reuse.gif'),
).readAsBytesSync();
final ui.Codec codec = await ui.instantiateImageCodec(data);

// Capture the final frame of animation. If we have not composited
// correctly, it will be clipped strangely.
late ui.FrameInfo frameInfo;
for (int i = 0; i < 4; i++) {
frameInfo = await codec.getNextFrame();
}

final ui.Image image = frameInfo.image;
final ByteData imageData = (await image.toByteData(format: ui.ImageByteFormat.png))!;

final Uint8List goldenData = File(
path.join('flutter', 'lib', 'ui', 'fixtures', 'four_frame_with_reuse_end.png'),
).readAsBytesSync();

expect(imageData.buffer.asUint8List(), goldenData);
});

test('Animated webp can reuse across multiple frames', () async {
// Regression test for https://github.com/flutter/flutter/issues/61150#issuecomment-679055858

final Uint8List data = File(
path.join('flutter', 'lib', 'ui', 'fixtures', 'heart.webp'),
).readAsBytesSync();
final ui.Codec codec = await ui.instantiateImageCodec(data);

// Capture the final frame of animation. If we have not composited
// correctly, the hearts will be incorrectly repeated in the image.
late ui.FrameInfo frameInfo;
for (int i = 0; i < 69; i++) {
frameInfo = await codec.getNextFrame();
}

final ui.Image image = frameInfo.image;
final ByteData imageData = (await image.toByteData(format: ui.ImageByteFormat.png))!;

final Uint8List goldenData = File(
path.join('flutter', 'lib', 'ui', 'fixtures', 'heart_end.png'),
).readAsBytesSync();

expect(imageData.buffer.asUint8List(), goldenData);

});
}

/// Returns a File handle to a file in the skia/resources directory.
Expand Down