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/dispose_op_background.apng
Binary file not shown.
3 changes: 3 additions & 0 deletions lib/ui/painting/image_generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class ImageGenerator {
/// How this frame should be modified before decoding the next one.
SkCodecAnimation::DisposalMethod disposal_method;

/// The region of the frame that is affected by the disposal method.
std::optional<SkIRect> disposal_rect;

/// How this frame should be blended with the previous frame.
SkCodecAnimation::Blend blend_mode;
};
Expand Down
11 changes: 9 additions & 2 deletions lib/ui/painting/image_generator_apng.cc
Original file line number Diff line number Diff line change
Expand Up @@ -404,13 +404,18 @@ APNGImageGenerator::DemuxNextImage(const void* buffer_p,
default:
return std::make_pair(std::nullopt, nullptr);
}

SkIRect frame_rect = SkIRect::MakeXYWH(
control_data->get_x_offset(), control_data->get_y_offset(),
control_data->get_width(), control_data->get_height());
switch (control_data->get_dispose_op()) {
case 0: // APNG_DISPOSE_OP_NONE
frame_info.disposal_method = SkCodecAnimation::DisposalMethod::kKeep;
break;
case 1: // APNG_DISPOSE_OP_BACKGROUND
frame_info.disposal_method =
SkCodecAnimation::DisposalMethod::kRestoreBGColor;
frame_info.disposal_rect = frame_rect;
break;
case 2: // APNG_DISPOSE_OP_PREVIOUS
frame_info.disposal_method =
Expand Down Expand Up @@ -547,8 +552,10 @@ bool APNGImageGenerator::DemuxNextImageInternal() {
}

if (images_.size() > first_frame_index_ &&
last_frame_info->disposal_method ==
SkCodecAnimation::DisposalMethod::kKeep) {
(last_frame_info->disposal_method ==
SkCodecAnimation::DisposalMethod::kKeep ||
last_frame_info->disposal_method ==
SkCodecAnimation::DisposalMethod::kRestoreBGColor)) {
// Mark the required frame as the previous frame in all cases.
image->frame_info->required_frame = images_.size() - 1;
} else if (images_.size() > (first_frame_index_ + 1) &&
Expand Down
10 changes: 10 additions & 0 deletions lib/ui/painting/multi_frame_codec.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ MultiFrameCodec::State::GetNextFrameImage(
// Copy the previous frame's output buffer into the current frame as the
// starting point.
bitmap.writePixels(lastRequiredFrame_->pixmap());
if (restoreBGColorRect_.has_value()) {
bitmap.erase(SK_ColorTRANSPARENT, restoreBGColorRect_.value());
}
}
}

Expand Down Expand Up @@ -133,6 +136,13 @@ MultiFrameCodec::State::GetNextFrameImage(
lastRequiredFrameIndex_ = nextFrameIndex_;
}

if (frameInfo.disposal_method ==
SkCodecAnimation::DisposalMethod::kRestoreBGColor) {
restoreBGColorRect_ = frameInfo.disposal_rect;
} else {
restoreBGColorRect_.reset();
}

#if IMPELLER_SUPPORTS_RENDERING
if (is_impeller_enabled_) {
// This is safe regardless of whether the GPU is available or not because
Expand Down
5 changes: 4 additions & 1 deletion lib/ui/painting/multi_frame_codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,13 @@ class MultiFrameCodec : public Codec {
int nextFrameIndex_;
// The last decoded frame that's required to decode any subsequent frames.
std::optional<SkBitmap> lastRequiredFrame_;

// The index of the last decoded required frame.
int lastRequiredFrameIndex_ = -1;

// The rectangle that should be cleared if the previous frame's disposal
// method was kRestoreBGColor.
std::optional<SkIRect> restoreBGColorRect_;

std::pair<sk_sp<DlImage>, std::string> GetNextFrameImage(
fml::WeakPtr<GrDirectContext> resourceContext,
const std::shared_ptr<const fml::SyncSwitch>& gpu_disable_sync_switch,
Expand Down
25 changes: 23 additions & 2 deletions testing/dart/codec_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,6 @@ void main() {
});

test('Animated apng alpha type handling', () async {
// https://github.com/flutter/engine/pull/42153

final Uint8List data = File(
path.join('flutter', 'lib', 'ui', 'fixtures', 'alpha_animated.apng'),
).readAsBytesSync();
Expand All @@ -220,6 +218,29 @@ void main() {
imageData = (await image.toByteData())!;
expect(imageData.getUint32(0), 0x99000099);
});

test('Animated apng background color restore', () async {
final Uint8List data = File(
path.join('flutter', 'lib', 'ui', 'fixtures', 'dispose_op_background.apng'),
).readAsBytesSync();
final ui.Codec codec = await ui.instantiateImageCodec(data);

// First frame is solid red
ui.Image image = (await codec.getNextFrame()).image;
ByteData imageData = (await image.toByteData())!;
expect(imageData.getUint32(0), 0xFF0000FF);

// Third frame is blue in the lower right corner.
await codec.getNextFrame();
image = (await codec.getNextFrame()).image;
imageData = (await image.toByteData())!;
expect(imageData.getUint32(imageData.lengthInBytes - 4), 0x0000FFFF);

// Fourth frame is transparent in the lower right corner
image = (await codec.getNextFrame()).image;
imageData = (await image.toByteData())!;
expect(imageData.getUint32(imageData.lengthInBytes - 4), 0x00000000);
});
}

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