11#include "third_party/skia/include/codec/SkCodec.h"
12#include "third_party/skia/include/codec/SkCodecAnimation.h"
13#include "third_party/skia/include/core/SkAlphaType.h"
14#include "third_party/skia/include/core/SkColorType.h"
15#include "third_party/skia/include/core/SkImageInfo.h"
16#include "third_party/skia/include/core/SkStream.h"
17#include "third_party/zlib/zlib.h"
23APNGImageGenerator::APNGImageGenerator(sk_sp<SkData>&
data,
24 SkImageInfo& image_info,
25 APNGImage&& default_image,
26 unsigned int frame_count,
27 unsigned int play_count,
28 const void* next_chunk_p,
29 const std::vector<uint8_t>& header)
31 image_info_(image_info),
32 frame_count_(frame_count),
33 play_count_(play_count),
34 first_frame_index_(default_image.frame_info.has_value() ? 0 : 1),
35 next_chunk_p_(next_chunk_p),
37 images_.push_back(std::move(default_image));
40const SkImageInfo& APNGImageGenerator::GetInfo() {
44unsigned int APNGImageGenerator::GetFrameCount()
const {
48unsigned int APNGImageGenerator::GetPlayCount()
const {
49 return frame_count_ > 1 ? play_count_ : 1;
53 unsigned int frame_index) {
54 unsigned int image_index = first_frame_index_ + frame_index;
55 if (!DemuxToImageIndex(image_index)) {
59 auto frame_info = images_[image_index].frame_info;
60 if (frame_info.has_value()) {
61 return frame_info.value();
66SkISize APNGImageGenerator::GetScaledDimensions(
float desired_scale) {
67 return image_info_.dimensions();
70bool APNGImageGenerator::GetPixels(
const SkImageInfo& info,
73 unsigned int frame_index,
74 std::optional<unsigned int> prior_frame) {
76 unsigned int image_index = first_frame_index_ + frame_index;
82 if (!DemuxToImageIndex(image_index)) {
83 FML_DLOG(ERROR) <<
"Couldn't demux image at index " << image_index
84 <<
" (frame index: " << frame_index
85 <<
") from APNG stream.";
86 return RenderDefaultImage(info, pixels, row_bytes);
93 APNGImage& frame = images_[image_index];
94 SkImageInfo frame_info = frame.codec->getInfo();
97 size_t frame_row_bytes =
98 safe.
mul(frame_info.bytesPerPixel(), frame_info.width());
100 FML_DLOG(ERROR) <<
"Failed to decode image at index " << image_index
101 <<
" (frame index: " << frame_index
102 <<
") of APNG due to frame row bytes overflow.";
106 if (frame.pixels.empty()) {
107 size_t pixels_bytes = safe.
mul(frame_row_bytes, frame_info.height());
109 FML_DLOG(ERROR) <<
"Failed to decode image at index " << image_index
110 <<
" (frame index: " << frame_index
111 <<
") of APNG due to pixel buffer size overflow.";
114 frame.pixels.resize(pixels_bytes);
115 SkCodec::Result result = frame.codec->getPixels(
116 frame.codec->getInfo(), frame.pixels.data(), frame_row_bytes);
117 if (result != SkCodec::kSuccess) {
118 FML_DLOG(ERROR) <<
"Failed to decode image at index " << image_index
119 <<
" (frame index: " << frame_index
120 <<
") of APNG. SkCodec::Result: " << result;
121 return RenderDefaultImage(info, pixels, row_bytes);
124 if (!frame.frame_info.has_value()) {
125 FML_DLOG(ERROR) <<
"Failed to decode image at index " << image_index
126 <<
" (frame index: " << frame_index
127 <<
") of APNG due to the frame missing data (frame_info).";
134 std::numeric_limits<uint32_t>::max() - frame_info.width() ||
136 std::numeric_limits<uint32_t>::max() - frame_info.height() ||
138 frame.x_offset + frame_info.width() >
139 static_cast<unsigned int>(info.width()) ||
140 frame.y_offset + frame_info.height() >
141 static_cast<unsigned int>(info.height())) {
143 <<
"Decoded image at index " << image_index
144 <<
" (frame index: " << frame_index
145 <<
") rejected because the destination region (x: " << frame.x_offset
146 <<
", y: " << frame.y_offset <<
", width: " << frame_info.width()
147 <<
", height: " << frame_info.height()
148 <<
") is not entirely within the destination surface (width: "
149 << info.width() <<
", height: " << info.height() <<
").";
157 if (info.colorType() != kN32_SkColorType) {
158 FML_DLOG(ERROR) <<
"Failed to composite image at index " << image_index
159 <<
" (frame index: " << frame_index
160 <<
") of APNG due to the destination surface having an "
161 "unsupported color type.";
164 if (frame_info.colorType() != kN32_SkColorType) {
166 <<
"Failed to composite image at index " << image_index
167 <<
" (frame index: " << frame_index
168 <<
") of APNG due to the frame having an unsupported color type.";
177 uint8_t GetAlpha() {
return channel[3]; }
180 for (
int i = 0;
i < 3;
i++) {
185 void Unpremultiply() {
186 if (GetAlpha() == 0) {
190 for (
int i = 0;
i < 3;
i++) {
196 FML_DCHECK(frame_info.bytesPerPixel() ==
sizeof(Pixel));
200 if (frame.frame_info->blend_mode == SkCodecAnimation::Blend::kSrc) {
201 SkPixmap src_pixmap(frame_info, frame.pixels.data(), frame_row_bytes);
202 uint8_t* dst_pixels =
static_cast<uint8_t*
>(pixels) +
203 frame.y_offset * row_bytes +
204 frame.x_offset * frame_info.bytesPerPixel();
205 result = src_pixmap.readPixels(info, dst_pixels, row_bytes);
207 FML_DLOG(ERROR) <<
"Failed to copy pixels at index " << image_index
208 <<
" (frame index: " << frame_index <<
") of APNG.";
210 }
else if (frame.frame_info->blend_mode ==
211 SkCodecAnimation::Blend::kSrcOver) {
212 for (
int y = 0;
y < frame_info.height();
y++) {
213 auto src_row = frame.pixels.data() +
y * frame_row_bytes;
214 auto dst_row =
static_cast<uint8_t*
>(pixels) +
215 (
y + frame.y_offset) * row_bytes +
216 frame.x_offset * frame_info.bytesPerPixel();
218 for (
int x = 0;
x < frame_info.width();
x++) {
219 auto x_offset_bytes =
x * frame_info.bytesPerPixel();
221 Pixel src = *
reinterpret_cast<Pixel*
>(src_row + x_offset_bytes);
222 Pixel* dst_p =
reinterpret_cast<Pixel*
>(dst_row + x_offset_bytes);
226 if (info.alphaType() == kUnpremul_SkAlphaType) {
229 if (frame_info.alphaType() == kUnpremul_SkAlphaType) {
233 for (
int i = 0;
i < 4;
i++) {
235 src.channel[
i] + dst.channel[
i] * (0xFF - src.GetAlpha()) / 0xFF;
240 if (info.alphaType() == kUnpremul_SkAlphaType) {
252std::unique_ptr<ImageGenerator> APNGImageGenerator::MakeFromData(
253 sk_sp<SkData> data) {
256 if (
data->size() < kPngSignature.size() +
sizeof(ChunkHeader)) {
260 const uint8_t* data_p =
static_cast<const uint8_t*
>(
data.get()->data());
261 if (memcmp(data_p, kPngSignature.data(), kPngSignature.size())) {
266 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(data_p + 8);
267 if (!IsValidChunkHeader(data_p,
data->size(), chunk) ||
268 chunk->get_data_length() !=
sizeof(ImageHeaderChunkData) ||
269 chunk->get_type() != kImageHeaderChunkType) {
276 chunk = GetNextChunk(data_p,
data->size(), chunk);
278 if (chunk ==
nullptr) {
281 if (chunk->get_type() == kImageDataChunkType) {
284 if (chunk->get_type() == kAnimationControlChunkType) {
289 if (chunk->get_data_length() <
sizeof(AnimationControlChunkData)) {
292 const AnimationControlChunkData* animation_data =
293 CastChunkData<AnimationControlChunkData>(chunk);
296 std::optional<std::vector<uint8_t>> header;
297 const void* first_chunk_p;
298 std::tie(header, first_chunk_p) = ExtractHeader(data_p,
data->size());
299 if (!header.has_value()) {
305 std::optional<APNGImage> default_image;
306 const void* next_chunk_p;
307 std::tie(default_image, next_chunk_p) =
308 DemuxNextImage(data_p,
data->size(), header.value(), first_chunk_p);
309 if (!default_image.has_value()) {
313 unsigned int play_count = animation_data->get_num_plays();
314 if (play_count == 0) {
315 play_count = kInfinitePlayCount;
318 SkImageInfo image_info = default_image.value().codec->getInfo();
319 return std::unique_ptr<APNGImageGenerator>(
321 animation_data->get_num_frames(), play_count,
322 next_chunk_p, header.value()));
325bool APNGImageGenerator::IsValidChunkHeader(
const void* buffer,
327 const ChunkHeader* chunk) {
329 const uint8_t* chunk_ptr =
reinterpret_cast<const uint8_t*
>(chunk);
330 const uint8_t* buffer_ptr =
static_cast<const uint8_t*
>(
buffer);
331 if (chunk_ptr < buffer_ptr || chunk_ptr >= buffer_ptr + size) {
337 size_t buffer_bytes_remaining = size - (chunk_ptr - buffer_ptr);
338 if (buffer_bytes_remaining <
sizeof(ChunkHeader)) {
344 size_t data_length = chunk->get_data_length();
345 if (buffer_bytes_remaining -
sizeof(ChunkHeader) < data_length) {
348 if (buffer_bytes_remaining -
sizeof(ChunkHeader) - data_length <
354 uint32_t
type = chunk->get_type();
355 for (
int i = 0;
i < 4;
i++) {
356 uint8_t
c =
type >>
i * 8 & 0xFF;
357 if (!((
c >=
'A' &&
c <=
'Z') || (
c >=
'a' &&
c <=
'z'))) {
365const APNGImageGenerator::ChunkHeader* APNGImageGenerator::GetNextChunk(
368 const ChunkHeader* current_chunk) {
369 FML_DCHECK((uint8_t*)current_chunk +
sizeof(ChunkHeader) <=
370 (uint8_t*)buffer + size);
372 const ChunkHeader* next_chunk =
reinterpret_cast<const ChunkHeader*
>(
373 reinterpret_cast<const uint8_t*
>(current_chunk) +
374 GetChunkSize(current_chunk));
375 if (!IsValidChunkHeader(buffer, size, next_chunk)) {
382std::pair<std::optional<std::vector<uint8_t>>,
const void*>
383APNGImageGenerator::ExtractHeader(
const void* buffer_p,
size_t buffer_size) {
384 std::vector<uint8_t> result(kPngSignature.begin(), kPngSignature.end());
386 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(
387 static_cast<const uint8_t*
>(buffer_p) +
sizeof(kPngSignature));
389 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
390 return std::make_pair(std::nullopt,
nullptr);
396 if (chunk->get_type() != kAnimationControlChunkType) {
397 size_t chunk_size = GetChunkSize(chunk);
398 result.resize(result.size() + chunk_size);
399 memcpy(result.data() + result.size() - chunk_size, chunk, chunk_size);
402 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
403 }
while (chunk !=
nullptr && chunk->get_type() != kFrameControlChunkType &&
404 chunk->get_type() != kImageDataChunkType &&
405 chunk->get_type() != kFrameDataChunkType);
410 if (chunk ==
nullptr) {
411 return std::make_pair(std::nullopt,
nullptr);
414 return std::make_pair(result, chunk);
417std::pair<std::optional<APNGImageGenerator::APNGImage>,
const void*>
418APNGImageGenerator::DemuxNextImage(
const void* buffer_p,
420 const std::vector<uint8_t>& header,
421 const void* chunk_p) {
422 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(chunk_p);
424 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
425 return std::make_pair(std::nullopt,
nullptr);
429 if (chunk->get_type() != kFrameControlChunkType &&
430 chunk->get_type() != kImageDataChunkType) {
431 return std::make_pair(std::nullopt,
nullptr);
435 const FrameControlChunkData* control_data =
nullptr;
439 if (chunk->get_type() == kFrameControlChunkType) {
440 if (chunk->get_data_length() <
sizeof(FrameControlChunkData)) {
441 return std::make_pair(std::nullopt,
nullptr);
443 control_data = CastChunkData<FrameControlChunkData>(chunk);
445 ImageGenerator::FrameInfo frame_info;
446 switch (control_data->get_blend_op()) {
448 frame_info.blend_mode = SkCodecAnimation::Blend::kSrc;
451 frame_info.blend_mode = SkCodecAnimation::Blend::kSrcOver;
454 return std::make_pair(std::nullopt,
nullptr);
457 SkIRect frame_rect = SkIRect::MakeXYWH(
458 control_data->get_x_offset(), control_data->get_y_offset(),
459 control_data->get_width(), control_data->get_height());
460 switch (control_data->get_dispose_op()) {
462 frame_info.disposal_method = SkCodecAnimation::DisposalMethod::kKeep;
465 frame_info.disposal_method =
466 SkCodecAnimation::DisposalMethod::kRestoreBGColor;
467 frame_info.disposal_rect = frame_rect;
470 frame_info.disposal_method =
471 SkCodecAnimation::DisposalMethod::kRestorePrevious;
474 return std::make_pair(std::nullopt,
nullptr);
476 uint16_t denominator = control_data->get_delay_den() == 0
478 : control_data->get_delay_den();
479 frame_info.duration =
480 static_cast<int>(control_data->get_delay_num() * 1000.f / denominator);
482 result.frame_info = frame_info;
483 result.x_offset = control_data->get_x_offset();
484 result.y_offset = control_data->get_y_offset();
487 std::vector<const ChunkHeader*> image_chunks;
488 size_t chunk_space = 0;
495 if (chunk->get_type() != kFrameControlChunkType) {
496 image_chunks.push_back(chunk);
497 chunk_space += GetChunkSize(chunk);
503 if (chunk->get_type() == kFrameDataChunkType) {
504 if (chunk->get_data_length() < kFrameDataSequenceNumberSize) {
505 return std::make_pair(std::nullopt,
nullptr);
507 chunk_space -= kFrameDataSequenceNumberSize;
511 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
512 }
while (chunk !=
nullptr && chunk->get_type() != kFrameControlChunkType &&
513 chunk->get_type() != kImageTrailerChunkType);
515 const uint8_t end_chunk[] = {0, 0, 0, 0,
'I',
'E',
516 'N',
'D', 0xAE, 0x42, 0x60, 0x82};
519 sk_sp<SkData> new_png_buffer = SkData::MakeUninitialized(
520 header.size() + chunk_space +
sizeof(end_chunk));
523 uint8_t* write_cursor =
524 static_cast<uint8_t*
>(new_png_buffer->writable_data());
527 memcpy(write_cursor, header.data(), header.size());
530 ChunkHeader* ihdr_header =
531 reinterpret_cast<ChunkHeader*
>(write_cursor +
sizeof(kPngSignature));
532 ImageHeaderChunkData* ihdr_data =
const_cast<ImageHeaderChunkData*
>(
533 CastChunkData<ImageHeaderChunkData>(ihdr_header));
534 ihdr_data->set_width(control_data->get_width());
535 ihdr_data->set_height(control_data->get_height());
536 ihdr_header->UpdateChunkCrc32();
538 write_cursor += header.size();
541 for (
const ChunkHeader*
c : image_chunks) {
542 if (
c->get_type() == kFrameDataChunkType) {
543 FML_DCHECK(
c->get_data_length() >= kFrameDataSequenceNumberSize);
546 ChunkHeader* write_header =
547 reinterpret_cast<ChunkHeader*
>(write_cursor);
548 write_header->set_data_length(
c->get_data_length() -
549 kFrameDataSequenceNumberSize);
550 write_header->set_type(kImageDataChunkType);
551 write_cursor +=
sizeof(ChunkHeader);
556 reinterpret_cast<const uint8_t*
>(
c) +
sizeof(ChunkHeader) +
557 kFrameDataSequenceNumberSize,
558 write_header->get_data_length());
559 write_cursor += write_header->get_data_length();
562 write_header->UpdateChunkCrc32();
565 size_t chunk_size = GetChunkSize(
c);
566 memcpy(write_cursor,
c, chunk_size);
567 write_cursor += chunk_size;
572 memcpy(write_cursor, &end_chunk,
sizeof(end_chunk));
575 SkCodec::Result header_parse_result;
576 result.codec = SkCodec::MakeFromStream(SkMemoryStream::Make(new_png_buffer),
577 &header_parse_result);
578 if (header_parse_result != SkCodec::Result::kSuccess) {
580 <<
"Failed to parse image header during APNG demux. SkCodec::Result: "
581 << header_parse_result;
582 return std::make_pair(std::nullopt,
nullptr);
585 if (chunk->get_type() == kImageTrailerChunkType) {
589 return std::make_pair(std::optional<APNGImage>{std::move(result)}, chunk);
592bool APNGImageGenerator::DemuxNextImageInternal() {
593 if (next_chunk_p_ ==
nullptr) {
597 std::optional<APNGImage>
image;
598 const void* data_p =
const_cast<void*
>(data_.get()->data());
599 std::tie(
image, next_chunk_p_) =
600 DemuxNextImage(data_p, data_->size(), header_, next_chunk_p_);
601 if (!
image.has_value() || !
image->frame_info.has_value()) {
605 auto last_frame_info = images_.back().frame_info;
606 if (!last_frame_info.has_value()) {
610 if (images_.size() > first_frame_index_ &&
611 (last_frame_info->disposal_method ==
612 SkCodecAnimation::DisposalMethod::kKeep ||
613 last_frame_info->disposal_method ==
614 SkCodecAnimation::DisposalMethod::kRestoreBGColor)) {
616 image->frame_info->required_frame = images_.size() - 1;
617 }
else if (images_.size() > (first_frame_index_ + 1) &&
618 last_frame_info->disposal_method ==
619 SkCodecAnimation::DisposalMethod::kRestorePrevious) {
623 image->frame_info->required_frame = images_.size() - 2;
627 SkImageInfo info =
image.value().codec->getInfo();
628 FML_DCHECK(info.colorInfo() == image_info_.colorInfo());
630 images_.push_back(std::move(
image.value()));
632 auto default_info = images_[0].codec->getInfo();
633 if (info.colorType() != default_info.colorType()) {
639bool APNGImageGenerator::DemuxToImageIndex(
unsigned int image_index) {
642 if (image_index >= images_.size()) {
643 while (DemuxNextImageInternal() && image_index >= images_.size()) {
646 if (image_index >= images_.size()) {
655void APNGImageGenerator::ChunkHeader::UpdateChunkCrc32() {
657 reinterpret_cast<uint32_t*
>(
reinterpret_cast<uint8_t*
>(
this) +
658 sizeof(ChunkHeader) + get_data_length());
662uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
664 size_t length =
sizeof(ChunkHeader) - 4 + get_data_length();
665 const uint8_t* chunk_data =
reinterpret_cast<const uint8_t*
>(
this) + 4;
666 return ComputeCrc32(chunk_data,
length);
669uint32_t APNGImageGenerator::ComputeCrc32(
const uint8_t* data,
size_t length) {
671 const uint8_t* data_p =
data;
678 uint16_t length16 =
length;
679 if (length16 == 0 &&
length > 0) {
680 length16 = std::numeric_limits<uint16_t>::max();
683 crc = crc32(crc, data_p, length16);
691bool APNGImageGenerator::RenderDefaultImage(
const SkImageInfo& info,
694 APNGImage& frame = images_[0];
695 SkImageInfo frame_info = frame.codec->getInfo();
696 if (frame_info.width() > info.width() ||
697 frame_info.height() > info.height()) {
699 <<
"Default image rejected because the destination region (width: "
700 << frame_info.width() <<
", height: " << frame_info.height()
701 <<
") is not entirely within the destination surface (width: "
702 << info.width() <<
", height: " << info.height() <<
").";
706 SkCodec::Result result = frame.codec->getPixels(info, pixels, row_bytes);
707 if (result != SkCodec::kSuccess) {
708 FML_DLOG(ERROR) <<
"Failed to decode the APNG's default/fallback image. "
size_t mul(size_t x, size_t y)
bool overflow_detected() const
FlutterVulkanImage * image
#define FML_DLOG(severity)
#define FML_DCHECK(condition)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set profile Make the profiler discard new samples once the profiler sample buffer is full When this flag is not the profiler sample buffer is used as a ring buffer
constexpr T BigEndianToArch(T n)
Convert a known big endian value to match the endianness of the current architecture....
impeller::ShaderType type
Info about a single frame in the context of a multi-frame image, useful for animation and blending.