mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Implement the APNG_DISPOSE_OP_BACKGROUND disposal method in the animated PNG decoder (flutter/engine#42933)
This commit is contained in:
parent
7f3bed1778
commit
c4e22ba089
BIN
engine/src/flutter/lib/ui/fixtures/dispose_op_background.apng
Normal file
BIN
engine/src/flutter/lib/ui/fixtures/dispose_op_background.apng
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 494 B |
@ -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;
|
||||
};
|
||||
|
||||
@ -404,6 +404,10 @@ 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;
|
||||
@ -411,6 +415,7 @@ APNGImageGenerator::DemuxNextImage(const void* buffer_p,
|
||||
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 =
|
||||
@ -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) &&
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user