Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 4bee37e

Browse files
authored
Support DisposalMethod::kRestorePrevious in MultiFrameCodec and fix the apng problem. (#42153)
Support DisposalMethod::kRestorePrevious in MultiFrameCodec and fix the apng problem. ![image](https://github.com/flutter/engine/assets/5031712/48bb95c1-10b3-4736-a42e-46281d355cd3) [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 1e145cb commit 4bee37e

File tree

7 files changed

+56
-13
lines changed

7 files changed

+56
-13
lines changed
189 KB
Binary file not shown.
22.8 KB
Loading
22.7 KB
Loading
22.9 KB
Loading

lib/ui/painting/image_generator_apng.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -538,19 +538,19 @@ bool APNGImageGenerator::DemuxNextImageInternal() {
538538
if (!last_frame_info.has_value()) {
539539
return false;
540540
}
541-
if (last_frame_info->disposal_method ==
542-
SkCodecAnimation::DisposalMethod::kRestorePrevious) {
543-
FML_DLOG(INFO)
544-
<< "DisposalMethod::kRestorePrevious is not supported by the "
545-
"MultiFrameCodec. Falling back to DisposalMethod::kRestoreBGColor "
546-
" behavior instead.";
547-
}
548541

549542
if (images_.size() > first_frame_index_ &&
550543
last_frame_info->disposal_method ==
551544
SkCodecAnimation::DisposalMethod::kKeep) {
552545
// Mark the required frame as the previous frame in all cases.
553546
image->frame_info->required_frame = images_.size() - 1;
547+
} else if (images_.size() > (first_frame_index_ + 1) &&
548+
last_frame_info->disposal_method ==
549+
SkCodecAnimation::DisposalMethod::kRestorePrevious) {
550+
// Mark the required frame as the last previous frame
551+
// It is not valid if there are 2 or above frames set |disposal_method| to
552+
// |kRestorePrevious|. But it also works in MultiFrameCodec.
553+
image->frame_info->required_frame = images_.size() - 2;
554554
}
555555

556556
// Calling SkCodec::getInfo at least once prior to decoding is mandatory.

lib/ui/painting/multi_frame_codec.cc

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@ MultiFrameCodec::State::GetNextFrameImage(
116116
std::optional<unsigned int> prior_frame_index = std::nullopt;
117117

118118
if (requiredFrameIndex != SkCodec::kNoFrame) {
119-
// We currently assume that frames can only ever depend on the immediately
120-
// previous frame, if any. This means that
121-
// `DisposalMethod::kRestorePrevious` is not supported.
119+
// We are here when the frame said |disposal_method| is
120+
// `DisposalMethod::kKeep` or `DisposalMethod::kRestorePrevious` and
121+
// |requiredFrameIndex| is set to ex-frame or ex-ex-frame.
122122
if (lastRequiredFrame_ == nullptr) {
123123
FML_DLOG(INFO)
124124
<< "Frame " << nextFrameIndex_ << " depends on frame "
@@ -146,9 +146,27 @@ MultiFrameCodec::State::GetNextFrameImage(
146146
return std::make_pair(nullptr, decode_error);
147147
}
148148

149-
// Hold onto this if we need it to decode future frames.
150-
if (frameInfo.disposal_method == SkCodecAnimation::DisposalMethod::kKeep ||
151-
lastRequiredFrame_) {
149+
const bool keep_current_frame =
150+
frameInfo.disposal_method == SkCodecAnimation::DisposalMethod::kKeep;
151+
const bool restore_previous_frame =
152+
frameInfo.disposal_method ==
153+
SkCodecAnimation::DisposalMethod::kRestorePrevious;
154+
const bool previous_frame_available = !!lastRequiredFrame_;
155+
156+
// Store the current frame in `lastRequiredFrame_` if the frame's disposal
157+
// method indicates we should do so.
158+
// * When the disposal method is "Keep", the stored frame should always be
159+
// overwritten with the new frame we just crafted.
160+
// * When the disposal method is "RestorePrevious", the previously stored
161+
// frame should be retained and used as the backdrop for the next frame
162+
// again. If there isn't already a stored frame, that means we haven't
163+
// rendered any frames yet! When this happens, we just fall back to "Keep"
164+
// behavior and store the current frame as the backdrop of the next frame.
165+
166+
if (keep_current_frame ||
167+
(previous_frame_available && !restore_previous_frame)) {
168+
// Replace the stored frame. The `lastRequiredFrame_` will get used as the
169+
// starting backdrop for the next frame.
152170
lastRequiredFrame_ = std::make_unique<SkBitmap>(bitmap);
153171
lastRequiredFrameIndex_ = nextFrameIndex_;
154172
}

testing/dart/codec_test.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,31 @@ void main() {
177177
expect(imageData.buffer.asUint8List(), goldenData);
178178

179179
});
180+
181+
test('Animated apng can reuse pre-pre-frame', () async {
182+
// https://github.com/flutter/engine/pull/42153
183+
184+
final Uint8List data = File(
185+
path.join('flutter', 'lib', 'ui', 'fixtures', '2_dispose_op_restore_previous.apng'),
186+
).readAsBytesSync();
187+
final ui.Codec codec = await ui.instantiateImageCodec(data);
188+
189+
// Capture the 67,68,69 frames of animation and then compare the pixels.
190+
late ui.FrameInfo frameInfo;
191+
for (int i = 0; i < 70; i++) {
192+
frameInfo = await codec.getNextFrame();
193+
if (i >= 67) {
194+
final ui.Image image = frameInfo.image;
195+
final ByteData imageData = (await image.toByteData(format: ui.ImageByteFormat.png))!;
196+
197+
final Uint8List goldenData = File(
198+
path.join('flutter', 'lib', 'ui', 'fixtures', '2_dispose_op_restore_previous.apng.$i.png'),
199+
).readAsBytesSync();
200+
201+
expect(imageData.buffer.asUint8List(), goldenData);
202+
}
203+
}
204+
});
180205
}
181206

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

0 commit comments

Comments
 (0)