10#include "third_party/skia/include/codec/SkCodec.h"
11#include "third_party/skia/include/codec/SkCodecAnimation.h"
12#include "third_party/skia/include/core/SkAlphaType.h"
13#include "third_party/skia/include/core/SkColorType.h"
14#include "third_party/skia/include/core/SkImageInfo.h"
15#include "third_party/skia/include/core/SkStream.h"
16#include "third_party/zlib/zlib.h"
22APNGImageGenerator::APNGImageGenerator(sk_sp<SkData>&
data,
23 SkImageInfo& image_info,
24 APNGImage&& default_image,
25 unsigned int frame_count,
26 unsigned int play_count,
27 const void* next_chunk_p,
28 const std::vector<uint8_t>& header)
30 image_info_(image_info),
31 frame_count_(frame_count),
32 play_count_(play_count),
33 first_frame_index_(default_image.frame_info.has_value() ? 0 : 1),
34 next_chunk_p_(next_chunk_p),
36 images_.push_back(std::move(default_image));
39const SkImageInfo& APNGImageGenerator::GetInfo() {
43unsigned int APNGImageGenerator::GetFrameCount()
const {
47unsigned int APNGImageGenerator::GetPlayCount()
const {
48 return frame_count_ > 1 ? play_count_ : 1;
52 unsigned int frame_index) {
53 unsigned int image_index = first_frame_index_ + frame_index;
54 if (!DemuxToImageIndex(image_index)) {
58 auto frame_info = images_[image_index].frame_info;
59 if (frame_info.has_value()) {
60 return frame_info.value();
65SkISize APNGImageGenerator::GetScaledDimensions(
float desired_scale) {
66 return image_info_.dimensions();
69bool APNGImageGenerator::GetPixels(
const SkImageInfo& info,
72 unsigned int frame_index,
73 std::optional<unsigned int> prior_frame) {
75 unsigned int image_index = first_frame_index_ + frame_index;
81 if (!DemuxToImageIndex(image_index)) {
82 FML_DLOG(ERROR) <<
"Couldn't demux image at index " << image_index
83 <<
" (frame index: " << frame_index
84 <<
") from APNG stream.";
85 return RenderDefaultImage(info, pixels, row_bytes);
92 APNGImage& frame = images_[image_index];
93 SkImageInfo frame_info = frame.codec->getInfo();
94 auto frame_row_bytes = frame_info.bytesPerPixel() * frame_info.width();
96 if (frame.pixels.empty()) {
97 frame.pixels.resize(frame_row_bytes * frame_info.height());
98 SkCodec::Result result = frame.codec->getPixels(
99 frame.codec->getInfo(), frame.pixels.data(), frame_row_bytes);
100 if (result != SkCodec::kSuccess) {
101 FML_DLOG(ERROR) <<
"Failed to decode image at index " << image_index
102 <<
" (frame index: " << frame_index
103 <<
") of APNG. SkCodec::Result: " << result;
104 return RenderDefaultImage(info, pixels, row_bytes);
107 if (!frame.frame_info.has_value()) {
108 FML_DLOG(ERROR) <<
"Failed to decode image at index " << image_index
109 <<
" (frame index: " << frame_index
110 <<
") of APNG due to the frame missing data (frame_info).";
117 std::numeric_limits<uint32_t>::max() - frame_info.width() ||
119 std::numeric_limits<uint32_t>::max() - frame_info.height() ||
121 frame.x_offset + frame_info.width() >
122 static_cast<unsigned int>(info.width()) ||
123 frame.y_offset + frame_info.height() >
124 static_cast<unsigned int>(info.height())) {
126 <<
"Decoded image at index " << image_index
127 <<
" (frame index: " << frame_index
128 <<
") rejected because the destination region (x: " << frame.x_offset
129 <<
", y: " << frame.y_offset <<
", width: " << frame_info.width()
130 <<
", height: " << frame_info.height()
131 <<
") is not entirely within the destination surface (width: "
132 << info.width() <<
", height: " << info.height() <<
").";
140 if (info.colorType() != kN32_SkColorType) {
141 FML_DLOG(ERROR) <<
"Failed to composite image at index " << image_index
142 <<
" (frame index: " << frame_index
143 <<
") of APNG due to the destination surface having an "
144 "unsupported color type.";
147 if (frame_info.colorType() != kN32_SkColorType) {
149 <<
"Failed to composite image at index " << image_index
150 <<
" (frame index: " << frame_index
151 <<
") of APNG due to the frame having an unsupported color type.";
160 uint8_t GetAlpha() {
return channel[3]; }
163 for (
int i = 0;
i < 3;
i++) {
168 void Unpremultiply() {
169 if (GetAlpha() == 0) {
173 for (
int i = 0;
i < 3;
i++) {
179 FML_DCHECK(frame_info.bytesPerPixel() ==
sizeof(Pixel));
183 if (frame.frame_info->blend_mode == SkCodecAnimation::Blend::kSrc) {
184 SkPixmap src_pixmap(frame_info, frame.pixels.data(), frame_row_bytes);
185 uint8_t* dst_pixels =
static_cast<uint8_t*
>(pixels) +
186 frame.y_offset * row_bytes +
187 frame.x_offset * frame_info.bytesPerPixel();
188 result = src_pixmap.readPixels(info, dst_pixels, row_bytes);
190 FML_DLOG(ERROR) <<
"Failed to copy pixels at index " << image_index
191 <<
" (frame index: " << frame_index <<
") of APNG.";
193 }
else if (frame.frame_info->blend_mode ==
194 SkCodecAnimation::Blend::kSrcOver) {
195 for (
int y = 0;
y < frame_info.height();
y++) {
196 auto src_row = frame.pixels.data() +
y * frame_row_bytes;
197 auto dst_row =
static_cast<uint8_t*
>(pixels) +
198 (
y + frame.y_offset) * row_bytes +
199 frame.x_offset * frame_info.bytesPerPixel();
201 for (
int x = 0;
x < frame_info.width();
x++) {
202 auto x_offset_bytes =
x * frame_info.bytesPerPixel();
204 Pixel src = *
reinterpret_cast<Pixel*
>(src_row + x_offset_bytes);
205 Pixel* dst_p =
reinterpret_cast<Pixel*
>(dst_row + x_offset_bytes);
209 if (info.alphaType() == kUnpremul_SkAlphaType) {
212 if (frame_info.alphaType() == kUnpremul_SkAlphaType) {
216 for (
int i = 0;
i < 4;
i++) {
218 src.channel[
i] + dst.channel[
i] * (0xFF - src.GetAlpha()) / 0xFF;
223 if (info.alphaType() == kUnpremul_SkAlphaType) {
235std::unique_ptr<ImageGenerator> APNGImageGenerator::MakeFromData(
236 sk_sp<SkData>
data) {
239 if (
data->size() <
sizeof(kPngSignature) +
sizeof(ChunkHeader)) {
243 const uint8_t* data_p =
static_cast<const uint8_t*
>(
data.get()->data());
244 if (memcmp(data_p, kPngSignature,
sizeof(kPngSignature))) {
249 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(data_p + 8);
250 if (!IsValidChunkHeader(data_p,
data->size(), chunk) ||
251 chunk->get_data_length() !=
sizeof(ImageHeaderChunkData) ||
252 chunk->get_type() != kImageHeaderChunkType) {
259 chunk = GetNextChunk(data_p,
data->size(), chunk);
261 if (chunk ==
nullptr) {
264 if (chunk->get_type() == kImageDataChunkType) {
267 if (chunk->get_type() == kAnimationControlChunkType) {
272 const AnimationControlChunkData* animation_data =
273 CastChunkData<AnimationControlChunkData>(chunk);
276 std::optional<std::vector<uint8_t>> header;
277 const void* first_chunk_p;
278 std::tie(header, first_chunk_p) = ExtractHeader(data_p,
data->size());
279 if (!header.has_value()) {
285 std::optional<APNGImage> default_image;
286 const void* next_chunk_p;
287 std::tie(default_image, next_chunk_p) =
288 DemuxNextImage(data_p,
data->size(), header.value(), first_chunk_p);
289 if (!default_image.has_value()) {
293 unsigned int play_count = animation_data->get_num_plays();
294 if (play_count == 0) {
295 play_count = kInfinitePlayCount;
298 SkImageInfo image_info = default_image.value().codec->getInfo();
299 return std::unique_ptr<APNGImageGenerator>(
301 animation_data->get_num_frames(), play_count,
302 next_chunk_p, header.value()));
305bool APNGImageGenerator::IsValidChunkHeader(
const void* buffer,
307 const ChunkHeader* chunk) {
309 if (
reinterpret_cast<const uint8_t*
>(chunk) <
310 static_cast<const uint8_t*
>(buffer)) {
315 if (
reinterpret_cast<const uint8_t*
>(chunk) +
sizeof(ChunkHeader) >
316 static_cast<const uint8_t*
>(buffer) + size) {
322 const uint8_t* chunk_end =
323 reinterpret_cast<const uint8_t*
>(chunk) + GetChunkSize(chunk);
324 if (chunk_end >
static_cast<const uint8_t*
>(buffer) +
size) {
329 uint32_t
type = chunk->get_type();
330 for (
int i = 0;
i < 4;
i++) {
331 uint8_t c =
type >>
i * 8 & 0xFF;
332 if (!((c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z'))) {
340const APNGImageGenerator::ChunkHeader* APNGImageGenerator::GetNextChunk(
343 const ChunkHeader* current_chunk) {
344 FML_DCHECK((uint8_t*)current_chunk +
sizeof(ChunkHeader) <=
345 (uint8_t*)buffer + size);
347 const ChunkHeader* next_chunk =
reinterpret_cast<const ChunkHeader*
>(
348 reinterpret_cast<const uint8_t*
>(current_chunk) +
349 GetChunkSize(current_chunk));
350 if (!IsValidChunkHeader(buffer, size, next_chunk)) {
357std::pair<std::optional<std::vector<uint8_t>>,
const void*>
358APNGImageGenerator::ExtractHeader(
const void* buffer_p,
size_t buffer_size) {
359 std::vector<uint8_t> result(
sizeof(kPngSignature));
360 memcpy(result.data(), kPngSignature,
sizeof(kPngSignature));
362 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(
363 static_cast<const uint8_t*
>(buffer_p) +
sizeof(kPngSignature));
365 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
366 return std::make_pair(std::nullopt,
nullptr);
372 if (chunk->get_type() != kAnimationControlChunkType) {
373 size_t chunk_size = GetChunkSize(chunk);
374 result.resize(result.size() + chunk_size);
375 memcpy(result.data() + result.size() - chunk_size, chunk, chunk_size);
378 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
379 }
while (chunk !=
nullptr && chunk->get_type() != kFrameControlChunkType &&
380 chunk->get_type() != kImageDataChunkType &&
381 chunk->get_type() != kFrameDataChunkType);
386 if (chunk ==
nullptr) {
387 return std::make_pair(std::nullopt,
nullptr);
390 return std::make_pair(result, chunk);
393std::pair<std::optional<APNGImageGenerator::APNGImage>,
const void*>
394APNGImageGenerator::DemuxNextImage(
const void* buffer_p,
396 const std::vector<uint8_t>& header,
397 const void* chunk_p) {
398 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(chunk_p);
400 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
401 return std::make_pair(std::nullopt,
nullptr);
405 if (chunk->get_type() != kFrameControlChunkType &&
406 chunk->get_type() != kImageDataChunkType) {
407 return std::make_pair(std::nullopt,
nullptr);
411 const FrameControlChunkData* control_data =
nullptr;
415 if (chunk->get_type() == kFrameControlChunkType) {
416 control_data = CastChunkData<FrameControlChunkData>(chunk);
418 ImageGenerator::FrameInfo frame_info;
419 switch (control_data->get_blend_op()) {
421 frame_info.blend_mode = SkCodecAnimation::Blend::kSrc;
424 frame_info.blend_mode = SkCodecAnimation::Blend::kSrcOver;
427 return std::make_pair(std::nullopt,
nullptr);
430 SkIRect frame_rect = SkIRect::MakeXYWH(
431 control_data->get_x_offset(), control_data->get_y_offset(),
432 control_data->get_width(), control_data->get_height());
433 switch (control_data->get_dispose_op()) {
435 frame_info.disposal_method = SkCodecAnimation::DisposalMethod::kKeep;
438 frame_info.disposal_method =
439 SkCodecAnimation::DisposalMethod::kRestoreBGColor;
440 frame_info.disposal_rect = frame_rect;
443 frame_info.disposal_method =
444 SkCodecAnimation::DisposalMethod::kRestorePrevious;
447 return std::make_pair(std::nullopt,
nullptr);
449 uint16_t denominator = control_data->get_delay_den() == 0
451 : control_data->get_delay_den();
452 frame_info.duration =
453 static_cast<int>(control_data->get_delay_num() * 1000.f / denominator);
455 result.frame_info = frame_info;
456 result.x_offset = control_data->get_x_offset();
457 result.y_offset = control_data->get_y_offset();
460 std::vector<const ChunkHeader*> image_chunks;
461 size_t chunk_space = 0;
468 if (chunk->get_type() != kFrameControlChunkType) {
469 image_chunks.push_back(chunk);
470 chunk_space += GetChunkSize(chunk);
476 if (chunk->get_type() == kFrameDataChunkType) {
481 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
482 }
while (chunk !=
nullptr && chunk->get_type() != kFrameControlChunkType &&
483 chunk->get_type() != kImageTrailerChunkType);
485 const uint8_t end_chunk[] = {0, 0, 0, 0,
'I',
'E',
486 'N',
'D', 0xAE, 0x42, 0x60, 0x82};
489 sk_sp<SkData> new_png_buffer = SkData::MakeUninitialized(
490 header.size() + chunk_space +
sizeof(end_chunk));
493 uint8_t* write_cursor =
494 static_cast<uint8_t*
>(new_png_buffer->writable_data());
497 memcpy(write_cursor, header.data(), header.size());
500 ChunkHeader* ihdr_header =
501 reinterpret_cast<ChunkHeader*
>(write_cursor +
sizeof(kPngSignature));
502 ImageHeaderChunkData* ihdr_data =
const_cast<ImageHeaderChunkData*
>(
503 CastChunkData<ImageHeaderChunkData>(ihdr_header));
504 ihdr_data->set_width(control_data->get_width());
505 ihdr_data->set_height(control_data->get_height());
506 ihdr_header->UpdateChunkCrc32();
508 write_cursor += header.size();
511 for (
const ChunkHeader* c : image_chunks) {
512 if (c->get_type() == kFrameDataChunkType) {
514 ChunkHeader* write_header =
515 reinterpret_cast<ChunkHeader*
>(write_cursor);
516 write_header->set_data_length(c->get_data_length() - 4);
517 write_header->set_type(kImageDataChunkType);
518 write_cursor +=
sizeof(ChunkHeader);
523 reinterpret_cast<const uint8_t*
>(c) +
sizeof(ChunkHeader) + 4,
524 write_header->get_data_length());
525 write_cursor += write_header->get_data_length();
528 write_header->UpdateChunkCrc32();
531 size_t chunk_size = GetChunkSize(c);
532 memcpy(write_cursor, c, chunk_size);
533 write_cursor += chunk_size;
538 memcpy(write_cursor, &end_chunk,
sizeof(end_chunk));
541 SkCodec::Result header_parse_result;
542 result.codec = SkCodec::MakeFromStream(SkMemoryStream::Make(new_png_buffer),
543 &header_parse_result);
544 if (header_parse_result != SkCodec::Result::kSuccess) {
546 <<
"Failed to parse image header during APNG demux. SkCodec::Result: "
547 << header_parse_result;
548 return std::make_pair(std::nullopt,
nullptr);
551 if (chunk->get_type() == kImageTrailerChunkType) {
555 return std::make_pair(std::optional<APNGImage>{std::move(result)}, chunk);
558bool APNGImageGenerator::DemuxNextImageInternal() {
559 if (next_chunk_p_ ==
nullptr) {
563 std::optional<APNGImage>
image;
564 const void* data_p =
const_cast<void*
>(data_.get()->data());
565 std::tie(
image, next_chunk_p_) =
566 DemuxNextImage(data_p, data_->size(), header_, next_chunk_p_);
567 if (!
image.has_value() || !
image->frame_info.has_value()) {
571 auto last_frame_info = images_.back().frame_info;
572 if (!last_frame_info.has_value()) {
576 if (images_.size() > first_frame_index_ &&
577 (last_frame_info->disposal_method ==
578 SkCodecAnimation::DisposalMethod::kKeep ||
579 last_frame_info->disposal_method ==
580 SkCodecAnimation::DisposalMethod::kRestoreBGColor)) {
582 image->frame_info->required_frame = images_.size() - 1;
583 }
else if (images_.size() > (first_frame_index_ + 1) &&
584 last_frame_info->disposal_method ==
585 SkCodecAnimation::DisposalMethod::kRestorePrevious) {
589 image->frame_info->required_frame = images_.size() - 2;
593 SkImageInfo info =
image.value().codec->getInfo();
594 FML_DCHECK(info.colorInfo() == image_info_.colorInfo());
596 images_.push_back(std::move(
image.value()));
598 auto default_info = images_[0].codec->getInfo();
599 if (info.colorType() != default_info.colorType()) {
605bool APNGImageGenerator::DemuxToImageIndex(
unsigned int image_index) {
608 if (image_index >= images_.size()) {
609 while (DemuxNextImageInternal() && image_index >= images_.size()) {
612 if (image_index >= images_.size()) {
621void APNGImageGenerator::ChunkHeader::UpdateChunkCrc32() {
623 reinterpret_cast<uint32_t*
>(
reinterpret_cast<uint8_t*
>(
this) +
624 sizeof(ChunkHeader) + get_data_length());
628uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
630 size_t length =
sizeof(ChunkHeader) - 4 + get_data_length();
631 uint8_t* chunk_data_p =
reinterpret_cast<uint8_t*
>(
this) + 4;
639 uint16_t length16 =
length;
640 if (length16 == 0 &&
length > 0) {
641 length16 = std::numeric_limits<uint16_t>::max();
644 crc = crc32(crc, chunk_data_p, length16);
646 chunk_data_p += length16;
652bool APNGImageGenerator::RenderDefaultImage(
const SkImageInfo& info,
655 APNGImage& frame = images_[0];
656 SkImageInfo frame_info = frame.codec->getInfo();
657 if (frame_info.width() > info.width() ||
658 frame_info.height() > info.height()) {
660 <<
"Default image rejected because the destination region (width: "
661 << frame_info.width() <<
", height: " << frame_info.height()
662 <<
") is not entirely within the destination surface (width: "
663 << info.width() <<
", height: " << info.height() <<
").";
667 SkCodec::Result result = frame.codec->getPixels(info, pixels, row_bytes);
668 if (result != SkCodec::kSuccess) {
669 FML_DLOG(ERROR) <<
"Failed to decode the APNG's default/fallback image. "
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....
Info about a single frame in the context of a multi-frame image, useful for animation and blending.
std::shared_ptr< const fml::Mapping > data