9#include "flutter/fml/logging.h"
16#include "third_party/zlib/zlib.h"
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));
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();
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];
96 if (
frame.pixels.empty()) {
97 frame.pixels.resize(frame_row_bytes * frame_info.
height());
99 frame.codec->getInfo(),
frame.pixels.data(), frame_row_bytes);
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).";
118 if (
info.colorType() != kN32_SkColorType) {
119 FML_DLOG(
ERROR) <<
"Failed to composite image at index " << image_index
120 <<
" (frame index: " << frame_index
121 <<
") of APNG due to the destination surface having an "
122 "unsupported color type.";
125 if (frame_info.
colorType() != kN32_SkColorType) {
127 <<
"Failed to composite image at index " << image_index
128 <<
" (frame index: " << frame_index
129 <<
") of APNG due to the frame having an unsupported color type.";
138 uint8_t GetAlpha() {
return channel[3]; }
141 for (
int i = 0;
i < 3;
i++) {
142 channel[
i] = channel[
i] * GetAlpha() / 0xFF;
146 void Unpremultiply() {
147 if (GetAlpha() == 0) {
148 channel[0] = channel[1] = channel[2] = 0;
151 for (
int i = 0;
i < 3;
i++) {
152 channel[
i] = channel[
i] * 0xFF / GetAlpha();
162 SkPixmap src_pixmap(frame_info,
frame.pixels.data(), frame_row_bytes);
163 uint8_t* dst_pixels =
static_cast<uint8_t*
>(pixels) +
164 frame.y_offset * row_bytes +
168 FML_DLOG(
ERROR) <<
"Failed to copy pixels at index " << image_index
169 <<
" (frame index: " << frame_index <<
") of APNG.";
171 }
else if (
frame.frame_info->blend_mode ==
173 for (
int y = 0;
y < frame_info.
height();
y++) {
174 auto src_row =
frame.pixels.data() +
y * frame_row_bytes;
175 auto dst_row =
static_cast<uint8_t*
>(pixels) +
176 (
y +
frame.y_offset) * row_bytes +
179 for (
int x = 0;
x < frame_info.
width();
x++) {
182 Pixel
src = *
reinterpret_cast<Pixel*
>(src_row + x_offset_bytes);
183 Pixel* dst_p =
reinterpret_cast<Pixel*
>(dst_row + x_offset_bytes);
194 for (
int i = 0;
i < 4;
i++) {
196 src.channel[
i] +
dst.channel[
i] * (0xFF -
src.GetAlpha()) / 0xFF;
213std::unique_ptr<ImageGenerator> APNGImageGenerator::MakeFromData(
217 if (
data->size() <
sizeof(kPngSignature) +
sizeof(ChunkHeader)) {
221 const uint8_t* data_p =
static_cast<const uint8_t*
>(
data.get()->data());
222 if (memcmp(data_p, kPngSignature,
sizeof(kPngSignature))) {
227 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(data_p + 8);
228 if (!IsValidChunkHeader(data_p,
data->size(), chunk) ||
229 chunk->get_data_length() !=
sizeof(ImageHeaderChunkData) ||
230 chunk->get_type() != kImageHeaderChunkType) {
237 chunk = GetNextChunk(data_p,
data->size(), chunk);
239 if (chunk ==
nullptr) {
242 if (chunk->get_type() == kImageDataChunkType) {
245 if (chunk->get_type() == kAnimationControlChunkType) {
250 const AnimationControlChunkData* animation_data =
251 CastChunkData<AnimationControlChunkData>(chunk);
254 std::optional<std::vector<uint8_t>>
header;
255 const void* first_chunk_p;
256 std::tie(
header, first_chunk_p) = ExtractHeader(data_p,
data->size());
257 if (!
header.has_value()) {
263 std::optional<APNGImage> default_image;
264 const void* next_chunk_p;
265 std::tie(default_image, next_chunk_p) =
266 DemuxNextImage(data_p,
data->size(),
header.value(), first_chunk_p);
267 if (!default_image.has_value()) {
271 unsigned int play_count = animation_data->get_num_plays();
272 if (play_count == 0) {
273 play_count = kInfinitePlayCount;
276 SkImageInfo image_info = default_image.value().codec->getInfo();
277 return std::unique_ptr<APNGImageGenerator>(
279 animation_data->get_num_frames(), play_count,
280 next_chunk_p,
header.value()));
283bool APNGImageGenerator::IsValidChunkHeader(
const void*
buffer,
285 const ChunkHeader* chunk) {
287 if (
reinterpret_cast<const uint8_t*
>(chunk) <
288 static_cast<const uint8_t*
>(
buffer)) {
293 if (
reinterpret_cast<const uint8_t*
>(chunk) +
sizeof(ChunkHeader) >
300 const uint8_t* chunk_end =
301 reinterpret_cast<const uint8_t*
>(chunk) + GetChunkSize(chunk);
302 if (chunk_end >
static_cast<const uint8_t*
>(
buffer) +
size) {
307 uint32_t
type = chunk->get_type();
308 for (
int i = 0;
i < 4;
i++) {
309 uint8_t c =
type >>
i * 8 & 0xFF;
310 if (!((c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z'))) {
318const APNGImageGenerator::ChunkHeader* APNGImageGenerator::GetNextChunk(
321 const ChunkHeader* current_chunk) {
322 FML_DCHECK((uint8_t*)current_chunk +
sizeof(ChunkHeader) <=
325 const ChunkHeader* next_chunk =
reinterpret_cast<const ChunkHeader*
>(
326 reinterpret_cast<const uint8_t*
>(current_chunk) +
327 GetChunkSize(current_chunk));
328 if (!IsValidChunkHeader(
buffer,
size, next_chunk)) {
335std::pair<std::optional<std::vector<uint8_t>>,
const void*>
336APNGImageGenerator::ExtractHeader(
const void* buffer_p,
size_t buffer_size) {
337 std::vector<uint8_t>
result(
sizeof(kPngSignature));
338 memcpy(
result.data(), kPngSignature,
sizeof(kPngSignature));
340 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(
341 static_cast<const uint8_t*
>(buffer_p) +
sizeof(kPngSignature));
343 if (!IsValidChunkHeader(buffer_p,
buffer_size, chunk)) {
344 return std::make_pair(std::nullopt,
nullptr);
350 if (chunk->get_type() != kAnimationControlChunkType) {
356 chunk = GetNextChunk(buffer_p,
buffer_size, chunk);
357 }
while (chunk !=
nullptr && chunk->get_type() != kFrameControlChunkType &&
358 chunk->get_type() != kImageDataChunkType &&
359 chunk->get_type() != kFrameDataChunkType);
364 if (chunk ==
nullptr) {
365 return std::make_pair(std::nullopt,
nullptr);
368 return std::make_pair(
result, chunk);
371std::pair<std::optional<APNGImageGenerator::APNGImage>,
const void*>
372APNGImageGenerator::DemuxNextImage(
const void* buffer_p,
374 const std::vector<uint8_t>&
header,
375 const void* chunk_p) {
376 const ChunkHeader* chunk =
reinterpret_cast<const ChunkHeader*
>(chunk_p);
378 if (!IsValidChunkHeader(buffer_p,
buffer_size, chunk)) {
379 return std::make_pair(std::nullopt,
nullptr);
383 if (chunk->get_type() != kFrameControlChunkType &&
384 chunk->get_type() != kImageDataChunkType) {
385 return std::make_pair(std::nullopt,
nullptr);
389 const FrameControlChunkData* control_data =
nullptr;
393 if (chunk->get_type() == kFrameControlChunkType) {
394 control_data = CastChunkData<FrameControlChunkData>(chunk);
396 ImageGenerator::FrameInfo frame_info;
397 switch (control_data->get_blend_op()) {
405 return std::make_pair(std::nullopt,
nullptr);
409 control_data->get_x_offset(), control_data->get_y_offset(),
410 control_data->get_width(), control_data->get_height());
411 switch (control_data->get_dispose_op()) {
416 frame_info.disposal_method =
418 frame_info.disposal_rect = frame_rect;
421 frame_info.disposal_method =
425 return std::make_pair(std::nullopt,
nullptr);
427 uint16_t denominator = control_data->get_delay_den() == 0
429 : control_data->get_delay_den();
430 frame_info.duration =
431 static_cast<int>(control_data->get_delay_num() * 1000.f / denominator);
433 result.frame_info = frame_info;
434 result.x_offset = control_data->get_x_offset();
435 result.y_offset = control_data->get_y_offset();
438 std::vector<const ChunkHeader*> image_chunks;
439 size_t chunk_space = 0;
446 if (chunk->get_type() != kFrameControlChunkType) {
447 image_chunks.push_back(chunk);
448 chunk_space += GetChunkSize(chunk);
454 if (chunk->get_type() == kFrameDataChunkType) {
459 chunk = GetNextChunk(buffer_p,
buffer_size, chunk);
460 }
while (chunk !=
nullptr && chunk->get_type() != kFrameControlChunkType &&
461 chunk->get_type() != kImageTrailerChunkType);
463 const uint8_t end_chunk[] = {0, 0, 0, 0,
'I',
'E',
464 'N',
'D', 0xAE, 0x42, 0x60, 0x82};
468 header.size() + chunk_space +
sizeof(end_chunk));
471 uint8_t* write_cursor =
478 ChunkHeader* ihdr_header =
479 reinterpret_cast<ChunkHeader*
>(write_cursor +
sizeof(kPngSignature));
480 ImageHeaderChunkData* ihdr_data =
const_cast<ImageHeaderChunkData*
>(
481 CastChunkData<ImageHeaderChunkData>(ihdr_header));
482 ihdr_data->set_width(control_data->get_width());
483 ihdr_data->set_height(control_data->get_height());
484 ihdr_header->UpdateChunkCrc32();
486 write_cursor +=
header.size();
489 for (
const ChunkHeader* c : image_chunks) {
490 if (c->get_type() == kFrameDataChunkType) {
493 reinterpret_cast<ChunkHeader*
>(write_cursor);
494 write_header->set_data_length(c->get_data_length() - 4);
496 write_cursor +=
sizeof(ChunkHeader);
501 reinterpret_cast<const uint8_t*
>(c) +
sizeof(ChunkHeader) + 4,
516 memcpy(write_cursor, &end_chunk,
sizeof(end_chunk));
521 &header_parse_result);
524 <<
"Failed to parse image header during APNG demux. SkCodec::Result: "
525 << header_parse_result;
526 return std::make_pair(std::nullopt,
nullptr);
529 if (chunk->get_type() == kImageTrailerChunkType) {
533 return std::make_pair(std::optional<APNGImage>{std::move(
result)}, chunk);
536bool APNGImageGenerator::DemuxNextImageInternal() {
537 if (next_chunk_p_ ==
nullptr) {
541 std::optional<APNGImage>
image;
542 const void* data_p =
const_cast<void*
>(data_.get()->data());
543 std::tie(
image, next_chunk_p_) =
544 DemuxNextImage(data_p, data_->size(), header_, next_chunk_p_);
545 if (!
image.has_value() || !
image->frame_info.has_value()) {
549 auto last_frame_info = images_.back().frame_info;
550 if (!last_frame_info.has_value()) {
554 if (images_.size() > first_frame_index_ &&
555 (last_frame_info->disposal_method ==
557 last_frame_info->disposal_method ==
560 image->frame_info->required_frame = images_.size() - 1;
561 }
else if (images_.size() > (first_frame_index_ + 1) &&
562 last_frame_info->disposal_method ==
567 image->frame_info->required_frame = images_.size() - 2;
574 images_.push_back(std::move(
image.value()));
576 auto default_info = images_[0].codec->getInfo();
577 if (
info.colorType() != default_info.colorType()) {
583bool APNGImageGenerator::DemuxToImageIndex(
unsigned int image_index) {
586 if (image_index >= images_.size()) {
587 while (DemuxNextImageInternal() && image_index >= images_.size()) {
590 if (image_index >= images_.size()) {
599void APNGImageGenerator::ChunkHeader::UpdateChunkCrc32() {
601 reinterpret_cast<uint32_t*
>(
reinterpret_cast<uint8_t*
>(
this) +
602 sizeof(ChunkHeader) + get_data_length());
606uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
608 size_t length =
sizeof(ChunkHeader) - 4 + get_data_length();
609 uint8_t* chunk_data_p =
reinterpret_cast<uint8_t*
>(
this) + 4;
617 uint16_t length16 =
length;
618 if (length16 == 0 &&
length > 0) {
622 crc = crc32(crc, chunk_data_p, length16);
624 chunk_data_p += length16;
635 FML_DLOG(
ERROR) <<
"Failed to decode the APNG's default/fallback image. "
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
static uint32_t buffer_size(uint32_t offset, uint32_t maxAlignment)
static std::unique_ptr< SkCodec > MakeFromStream(std::unique_ptr< SkStream >, SkSpan< const SkCodecs::Decoder > decoders, Result *=nullptr, SkPngChunkReader *=nullptr, SelectionPolicy selectionPolicy=SelectionPolicy::kPreferStillImage)
static sk_sp< SkData > MakeUninitialized(size_t length)
static std::unique_ptr< SkMemoryStream > Make(sk_sp< SkData > data)
bool readPixels(const SkImageInfo &dstInfo, void *dstPixels, size_t dstRowBytes) const
#define FML_DLOG(severity)
#define FML_DCHECK(condition)
static float max(float r, float g, float b)
sk_sp< const SkImage > image
static intptr_t chunk_size(intptr_t bytes_left)
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 vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
it will be possible to load the file into Perfetto s trace viewer 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
constexpr T BigEndianToArch(T n)
Convert a known big endian value to match the endianness of the current architecture....
static const char header[]
static constexpr SkIRect MakeXYWH(int32_t x, int32_t y, int32_t w, int32_t h)
int bytesPerPixel() const
SkAlphaType alphaType() const
SkColorType colorType() const
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