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() <
sizeof(kPngSignature) +
sizeof(ChunkHeader)) {
260 const uint8_t* data_p =
static_cast<const uint8_t*
>(
data.get()->data());
261 if (memcmp(data_p, kPngSignature,
sizeof(kPngSignature))) {
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(
sizeof(kPngSignature));
380 memcpy(result.data(), kPngSignature,
sizeof(kPngSignature));
382 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(
383 static_cast<const uint8_t*
>(buffer_p) +
sizeof(kPngSignature));
385 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
386 return std::make_pair(std::nullopt,
nullptr);
392 if (chunk->get_type() != kAnimationControlChunkType) {
393 size_t chunk_size = GetChunkSize(chunk);
394 result.resize(result.size() + chunk_size);
395 memcpy(result.data() + result.size() - chunk_size, chunk, chunk_size);
398 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
399 }
while (chunk !=
nullptr && chunk->get_type() != kFrameControlChunkType &&
400 chunk->get_type() != kImageDataChunkType &&
401 chunk->get_type() != kFrameDataChunkType);
406 if (chunk ==
nullptr) {
407 return std::make_pair(std::nullopt,
nullptr);
410 return std::make_pair(result, chunk);
413std::pair<std::optional<APNGImageGenerator::APNGImage>,
const void*>
414APNGImageGenerator::DemuxNextImage(
const void* buffer_p,
416 const std::vector<uint8_t>& header,
417 const void* chunk_p) {
418 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(chunk_p);
420 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
421 return std::make_pair(std::nullopt,
nullptr);
425 if (chunk->get_type() != kFrameControlChunkType &&
426 chunk->get_type() != kImageDataChunkType) {
427 return std::make_pair(std::nullopt,
nullptr);
431 const FrameControlChunkData* control_data =
nullptr;
435 if (chunk->get_type() == kFrameControlChunkType) {
436 if (chunk->get_data_length() <
sizeof(FrameControlChunkData)) {
437 return std::make_pair(std::nullopt,
nullptr);
439 control_data = CastChunkData<FrameControlChunkData>(chunk);
441 ImageGenerator::FrameInfo frame_info;
442 switch (control_data->get_blend_op()) {
444 frame_info.blend_mode = SkCodecAnimation::Blend::kSrc;
447 frame_info.blend_mode = SkCodecAnimation::Blend::kSrcOver;
450 return std::make_pair(std::nullopt,
nullptr);
453 SkIRect frame_rect = SkIRect::MakeXYWH(
454 control_data->get_x_offset(), control_data->get_y_offset(),
455 control_data->get_width(), control_data->get_height());
456 switch (control_data->get_dispose_op()) {
458 frame_info.disposal_method = SkCodecAnimation::DisposalMethod::kKeep;
461 frame_info.disposal_method =
462 SkCodecAnimation::DisposalMethod::kRestoreBGColor;
463 frame_info.disposal_rect = frame_rect;
466 frame_info.disposal_method =
467 SkCodecAnimation::DisposalMethod::kRestorePrevious;
470 return std::make_pair(std::nullopt,
nullptr);
472 uint16_t denominator = control_data->get_delay_den() == 0
474 : control_data->get_delay_den();
475 frame_info.duration =
476 static_cast<int>(control_data->get_delay_num() * 1000.f / denominator);
478 result.frame_info = frame_info;
479 result.x_offset = control_data->get_x_offset();
480 result.y_offset = control_data->get_y_offset();
483 std::vector<const ChunkHeader*> image_chunks;
484 size_t chunk_space = 0;
491 if (chunk->get_type() != kFrameControlChunkType) {
492 image_chunks.push_back(chunk);
493 chunk_space += GetChunkSize(chunk);
499 if (chunk->get_type() == kFrameDataChunkType) {
500 if (chunk->get_data_length() < kFrameDataSequenceNumberSize) {
501 return std::make_pair(std::nullopt,
nullptr);
503 chunk_space -= kFrameDataSequenceNumberSize;
507 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
508 }
while (chunk !=
nullptr && chunk->get_type() != kFrameControlChunkType &&
509 chunk->get_type() != kImageTrailerChunkType);
511 const uint8_t end_chunk[] = {0, 0, 0, 0,
'I',
'E',
512 'N',
'D', 0xAE, 0x42, 0x60, 0x82};
515 sk_sp<SkData> new_png_buffer = SkData::MakeUninitialized(
516 header.size() + chunk_space +
sizeof(end_chunk));
519 uint8_t* write_cursor =
520 static_cast<uint8_t*
>(new_png_buffer->writable_data());
523 memcpy(write_cursor, header.data(), header.size());
526 ChunkHeader* ihdr_header =
527 reinterpret_cast<ChunkHeader*
>(write_cursor +
sizeof(kPngSignature));
528 ImageHeaderChunkData* ihdr_data =
const_cast<ImageHeaderChunkData*
>(
529 CastChunkData<ImageHeaderChunkData>(ihdr_header));
530 ihdr_data->set_width(control_data->get_width());
531 ihdr_data->set_height(control_data->get_height());
532 ihdr_header->UpdateChunkCrc32();
534 write_cursor += header.size();
537 for (
const ChunkHeader*
c : image_chunks) {
538 if (
c->get_type() == kFrameDataChunkType) {
539 FML_DCHECK(
c->get_data_length() >= kFrameDataSequenceNumberSize);
542 ChunkHeader* write_header =
543 reinterpret_cast<ChunkHeader*
>(write_cursor);
544 write_header->set_data_length(
c->get_data_length() -
545 kFrameDataSequenceNumberSize);
546 write_header->set_type(kImageDataChunkType);
547 write_cursor +=
sizeof(ChunkHeader);
552 reinterpret_cast<const uint8_t*
>(
c) +
sizeof(ChunkHeader) +
553 kFrameDataSequenceNumberSize,
554 write_header->get_data_length());
555 write_cursor += write_header->get_data_length();
558 write_header->UpdateChunkCrc32();
561 size_t chunk_size = GetChunkSize(
c);
562 memcpy(write_cursor,
c, chunk_size);
563 write_cursor += chunk_size;
568 memcpy(write_cursor, &end_chunk,
sizeof(end_chunk));
571 SkCodec::Result header_parse_result;
572 result.codec = SkCodec::MakeFromStream(SkMemoryStream::Make(new_png_buffer),
573 &header_parse_result);
574 if (header_parse_result != SkCodec::Result::kSuccess) {
576 <<
"Failed to parse image header during APNG demux. SkCodec::Result: "
577 << header_parse_result;
578 return std::make_pair(std::nullopt,
nullptr);
581 if (chunk->get_type() == kImageTrailerChunkType) {
585 return std::make_pair(std::optional<APNGImage>{std::move(result)}, chunk);
588bool APNGImageGenerator::DemuxNextImageInternal() {
589 if (next_chunk_p_ ==
nullptr) {
593 std::optional<APNGImage>
image;
594 const void* data_p =
const_cast<void*
>(data_.get()->data());
595 std::tie(
image, next_chunk_p_) =
596 DemuxNextImage(data_p, data_->size(), header_, next_chunk_p_);
597 if (!
image.has_value() || !
image->frame_info.has_value()) {
601 auto last_frame_info = images_.back().frame_info;
602 if (!last_frame_info.has_value()) {
606 if (images_.size() > first_frame_index_ &&
607 (last_frame_info->disposal_method ==
608 SkCodecAnimation::DisposalMethod::kKeep ||
609 last_frame_info->disposal_method ==
610 SkCodecAnimation::DisposalMethod::kRestoreBGColor)) {
612 image->frame_info->required_frame = images_.size() - 1;
613 }
else if (images_.size() > (first_frame_index_ + 1) &&
614 last_frame_info->disposal_method ==
615 SkCodecAnimation::DisposalMethod::kRestorePrevious) {
619 image->frame_info->required_frame = images_.size() - 2;
623 SkImageInfo info =
image.value().codec->getInfo();
624 FML_DCHECK(info.colorInfo() == image_info_.colorInfo());
626 images_.push_back(std::move(
image.value()));
628 auto default_info = images_[0].codec->getInfo();
629 if (info.colorType() != default_info.colorType()) {
635bool APNGImageGenerator::DemuxToImageIndex(
unsigned int image_index) {
638 if (image_index >= images_.size()) {
639 while (DemuxNextImageInternal() && image_index >= images_.size()) {
642 if (image_index >= images_.size()) {
651void APNGImageGenerator::ChunkHeader::UpdateChunkCrc32() {
653 reinterpret_cast<uint32_t*
>(
reinterpret_cast<uint8_t*
>(
this) +
654 sizeof(ChunkHeader) + get_data_length());
658uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
660 size_t length =
sizeof(ChunkHeader) - 4 + get_data_length();
661 const uint8_t* chunk_data =
reinterpret_cast<const uint8_t*
>(
this) + 4;
662 return ComputeCrc32(chunk_data,
length);
665uint32_t APNGImageGenerator::ComputeCrc32(
const uint8_t* data,
size_t length) {
667 const uint8_t* data_p =
data;
674 uint16_t length16 =
length;
675 if (length16 == 0 &&
length > 0) {
676 length16 = std::numeric_limits<uint16_t>::max();
679 crc = crc32(crc, data_p, length16);
687bool APNGImageGenerator::RenderDefaultImage(
const SkImageInfo& info,
690 APNGImage& frame = images_[0];
691 SkImageInfo frame_info = frame.codec->getInfo();
692 if (frame_info.width() > info.width() ||
693 frame_info.height() > info.height()) {
695 <<
"Default image rejected because the destination region (width: "
696 << frame_info.width() <<
", height: " << frame_info.height()
697 <<
") is not entirely within the destination surface (width: "
698 << info.width() <<
", height: " << info.height() <<
").";
702 SkCodec::Result result = frame.codec->getPixels(info, pixels, row_bytes);
703 if (result != SkCodec::kSuccess) {
704 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.