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 if (
reinterpret_cast<const uint8_t*
>(chunk) <
330 static_cast<const uint8_t*
>(buffer)) {
335 if (
reinterpret_cast<const uint8_t*
>(chunk) +
sizeof(ChunkHeader) >
336 static_cast<const uint8_t*
>(buffer) + size) {
342 const uint8_t* chunk_end =
343 reinterpret_cast<const uint8_t*
>(chunk) + GetChunkSize(chunk);
344 if (chunk_end >
static_cast<const uint8_t*
>(buffer) +
size) {
349 uint32_t
type = chunk->get_type();
350 for (
int i = 0;
i < 4;
i++) {
351 uint8_t
c =
type >>
i * 8 & 0xFF;
352 if (!((
c >=
'A' &&
c <=
'Z') || (
c >=
'a' &&
c <=
'z'))) {
360const APNGImageGenerator::ChunkHeader* APNGImageGenerator::GetNextChunk(
363 const ChunkHeader* current_chunk) {
364 FML_DCHECK((uint8_t*)current_chunk +
sizeof(ChunkHeader) <=
365 (uint8_t*)buffer + size);
367 const ChunkHeader* next_chunk =
reinterpret_cast<const ChunkHeader*
>(
368 reinterpret_cast<const uint8_t*
>(current_chunk) +
369 GetChunkSize(current_chunk));
370 if (!IsValidChunkHeader(buffer, size, next_chunk)) {
377std::pair<std::optional<std::vector<uint8_t>>,
const void*>
378APNGImageGenerator::ExtractHeader(
const void* buffer_p,
size_t buffer_size) {
379 std::vector<uint8_t> result(kPngSignature.begin(), kPngSignature.end());
381 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(
382 static_cast<const uint8_t*
>(buffer_p) +
sizeof(kPngSignature));
384 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
385 return std::make_pair(std::nullopt,
nullptr);
391 if (chunk->get_type() != kAnimationControlChunkType) {
392 size_t chunk_size = GetChunkSize(chunk);
393 result.resize(result.size() + chunk_size);
394 memcpy(result.data() + result.size() - chunk_size, chunk, chunk_size);
397 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
398 }
while (chunk !=
nullptr && chunk->get_type() != kFrameControlChunkType &&
399 chunk->get_type() != kImageDataChunkType &&
400 chunk->get_type() != kFrameDataChunkType);
405 if (chunk ==
nullptr) {
406 return std::make_pair(std::nullopt,
nullptr);
409 return std::make_pair(result, chunk);
412std::pair<std::optional<APNGImageGenerator::APNGImage>,
const void*>
413APNGImageGenerator::DemuxNextImage(
const void* buffer_p,
415 const std::vector<uint8_t>& header,
416 const void* chunk_p) {
417 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(chunk_p);
419 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
420 return std::make_pair(std::nullopt,
nullptr);
424 if (chunk->get_type() != kFrameControlChunkType &&
425 chunk->get_type() != kImageDataChunkType) {
426 return std::make_pair(std::nullopt,
nullptr);
430 const FrameControlChunkData* control_data =
nullptr;
434 if (chunk->get_type() == kFrameControlChunkType) {
435 if (chunk->get_data_length() <
sizeof(FrameControlChunkData)) {
436 return std::make_pair(std::nullopt,
nullptr);
438 control_data = CastChunkData<FrameControlChunkData>(chunk);
440 ImageGenerator::FrameInfo frame_info;
441 switch (control_data->get_blend_op()) {
443 frame_info.blend_mode = SkCodecAnimation::Blend::kSrc;
446 frame_info.blend_mode = SkCodecAnimation::Blend::kSrcOver;
449 return std::make_pair(std::nullopt,
nullptr);
452 SkIRect frame_rect = SkIRect::MakeXYWH(
453 control_data->get_x_offset(), control_data->get_y_offset(),
454 control_data->get_width(), control_data->get_height());
455 switch (control_data->get_dispose_op()) {
457 frame_info.disposal_method = SkCodecAnimation::DisposalMethod::kKeep;
460 frame_info.disposal_method =
461 SkCodecAnimation::DisposalMethod::kRestoreBGColor;
462 frame_info.disposal_rect = frame_rect;
465 frame_info.disposal_method =
466 SkCodecAnimation::DisposalMethod::kRestorePrevious;
469 return std::make_pair(std::nullopt,
nullptr);
471 uint16_t denominator = control_data->get_delay_den() == 0
473 : control_data->get_delay_den();
474 frame_info.duration =
475 static_cast<int>(control_data->get_delay_num() * 1000.f / denominator);
477 result.frame_info = frame_info;
478 result.x_offset = control_data->get_x_offset();
479 result.y_offset = control_data->get_y_offset();
482 std::vector<const ChunkHeader*> image_chunks;
483 size_t chunk_space = 0;
490 if (chunk->get_type() != kFrameControlChunkType) {
491 image_chunks.push_back(chunk);
492 chunk_space += GetChunkSize(chunk);
498 if (chunk->get_type() == kFrameDataChunkType) {
499 if (chunk->get_data_length() < kFrameDataSequenceNumberSize) {
500 return std::make_pair(std::nullopt,
nullptr);
502 chunk_space -= kFrameDataSequenceNumberSize;
506 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
507 }
while (chunk !=
nullptr && chunk->get_type() != kFrameControlChunkType &&
508 chunk->get_type() != kImageTrailerChunkType);
510 const uint8_t end_chunk[] = {0, 0, 0, 0,
'I',
'E',
511 'N',
'D', 0xAE, 0x42, 0x60, 0x82};
514 sk_sp<SkData> new_png_buffer = SkData::MakeUninitialized(
515 header.size() + chunk_space +
sizeof(end_chunk));
518 uint8_t* write_cursor =
519 static_cast<uint8_t*
>(new_png_buffer->writable_data());
522 memcpy(write_cursor, header.data(), header.size());
525 ChunkHeader* ihdr_header =
526 reinterpret_cast<ChunkHeader*
>(write_cursor +
sizeof(kPngSignature));
527 ImageHeaderChunkData* ihdr_data =
const_cast<ImageHeaderChunkData*
>(
528 CastChunkData<ImageHeaderChunkData>(ihdr_header));
529 ihdr_data->set_width(control_data->get_width());
530 ihdr_data->set_height(control_data->get_height());
531 ihdr_header->UpdateChunkCrc32();
533 write_cursor += header.size();
536 for (
const ChunkHeader*
c : image_chunks) {
537 if (
c->get_type() == kFrameDataChunkType) {
538 FML_DCHECK(
c->get_data_length() >= kFrameDataSequenceNumberSize);
541 ChunkHeader* write_header =
542 reinterpret_cast<ChunkHeader*
>(write_cursor);
543 write_header->set_data_length(
c->get_data_length() -
544 kFrameDataSequenceNumberSize);
545 write_header->set_type(kImageDataChunkType);
546 write_cursor +=
sizeof(ChunkHeader);
551 reinterpret_cast<const uint8_t*
>(
c) +
sizeof(ChunkHeader) +
552 kFrameDataSequenceNumberSize,
553 write_header->get_data_length());
554 write_cursor += write_header->get_data_length();
557 write_header->UpdateChunkCrc32();
560 size_t chunk_size = GetChunkSize(
c);
561 memcpy(write_cursor,
c, chunk_size);
562 write_cursor += chunk_size;
567 memcpy(write_cursor, &end_chunk,
sizeof(end_chunk));
570 SkCodec::Result header_parse_result;
571 result.codec = SkCodec::MakeFromStream(SkMemoryStream::Make(new_png_buffer),
572 &header_parse_result);
573 if (header_parse_result != SkCodec::Result::kSuccess) {
575 <<
"Failed to parse image header during APNG demux. SkCodec::Result: "
576 << header_parse_result;
577 return std::make_pair(std::nullopt,
nullptr);
580 if (chunk->get_type() == kImageTrailerChunkType) {
584 return std::make_pair(std::optional<APNGImage>{std::move(result)}, chunk);
587bool APNGImageGenerator::DemuxNextImageInternal() {
588 if (next_chunk_p_ ==
nullptr) {
592 std::optional<APNGImage>
image;
593 const void* data_p =
const_cast<void*
>(data_.get()->data());
594 std::tie(
image, next_chunk_p_) =
595 DemuxNextImage(data_p, data_->size(), header_, next_chunk_p_);
596 if (!
image.has_value() || !
image->frame_info.has_value()) {
600 auto last_frame_info = images_.back().frame_info;
601 if (!last_frame_info.has_value()) {
605 if (images_.size() > first_frame_index_ &&
606 (last_frame_info->disposal_method ==
607 SkCodecAnimation::DisposalMethod::kKeep ||
608 last_frame_info->disposal_method ==
609 SkCodecAnimation::DisposalMethod::kRestoreBGColor)) {
611 image->frame_info->required_frame = images_.size() - 1;
612 }
else if (images_.size() > (first_frame_index_ + 1) &&
613 last_frame_info->disposal_method ==
614 SkCodecAnimation::DisposalMethod::kRestorePrevious) {
618 image->frame_info->required_frame = images_.size() - 2;
622 SkImageInfo info =
image.value().codec->getInfo();
623 FML_DCHECK(info.colorInfo() == image_info_.colorInfo());
625 images_.push_back(std::move(
image.value()));
627 auto default_info = images_[0].codec->getInfo();
628 if (info.colorType() != default_info.colorType()) {
634bool APNGImageGenerator::DemuxToImageIndex(
unsigned int image_index) {
637 if (image_index >= images_.size()) {
638 while (DemuxNextImageInternal() && image_index >= images_.size()) {
641 if (image_index >= images_.size()) {
650void APNGImageGenerator::ChunkHeader::UpdateChunkCrc32() {
652 reinterpret_cast<uint32_t*
>(
reinterpret_cast<uint8_t*
>(
this) +
653 sizeof(ChunkHeader) + get_data_length());
657uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
659 size_t length =
sizeof(ChunkHeader) - 4 + get_data_length();
660 const uint8_t* chunk_data =
reinterpret_cast<const uint8_t*
>(
this) + 4;
661 return ComputeCrc32(chunk_data,
length);
664uint32_t APNGImageGenerator::ComputeCrc32(
const uint8_t* data,
size_t length) {
666 const uint8_t* data_p =
data;
673 uint16_t length16 =
length;
674 if (length16 == 0 &&
length > 0) {
675 length16 = std::numeric_limits<uint16_t>::max();
678 crc = crc32(crc, data_p, length16);
686bool APNGImageGenerator::RenderDefaultImage(
const SkImageInfo& info,
689 APNGImage& frame = images_[0];
690 SkImageInfo frame_info = frame.codec->getInfo();
691 if (frame_info.width() > info.width() ||
692 frame_info.height() > info.height()) {
694 <<
"Default image rejected because the destination region (width: "
695 << frame_info.width() <<
", height: " << frame_info.height()
696 <<
") is not entirely within the destination surface (width: "
697 << info.width() <<
", height: " << info.height() <<
").";
701 SkCodec::Result result = frame.codec->getPixels(info, pixels, row_bytes);
702 if (result != SkCodec::kSuccess) {
703 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)
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
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.