Flutter Engine
 
Loading...
Searching...
No Matches
image_generator_apng.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6#include <cstddef>
7#include <cstring>
8
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" // For crc32
17
18namespace flutter {
19
21
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)
29 : data_(data),
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),
35 header_(header) {
36 images_.push_back(std::move(default_image));
37}
38
39const SkImageInfo& APNGImageGenerator::GetInfo() {
40 return image_info_;
41}
42
43unsigned int APNGImageGenerator::GetFrameCount() const {
44 return frame_count_;
45}
46
47unsigned int APNGImageGenerator::GetPlayCount() const {
48 return frame_count_ > 1 ? play_count_ : 1;
49}
50
51const ImageGenerator::FrameInfo APNGImageGenerator::GetFrameInfo(
52 unsigned int frame_index) {
53 unsigned int image_index = first_frame_index_ + frame_index;
54 if (!DemuxToImageIndex(image_index)) {
55 return {};
56 }
57
58 auto frame_info = images_[image_index].frame_info;
59 if (frame_info.has_value()) {
60 return frame_info.value();
61 }
62 return {};
63}
64
65SkISize APNGImageGenerator::GetScaledDimensions(float desired_scale) {
66 return image_info_.dimensions();
67}
68
69bool APNGImageGenerator::GetPixels(const SkImageInfo& info,
70 void* pixels,
71 size_t row_bytes,
72 unsigned int frame_index,
73 std::optional<unsigned int> prior_frame) {
74 FML_DCHECK(images_.size() > 0);
75 unsigned int image_index = first_frame_index_ + frame_index;
76
77 //----------------------------------------------------------------------------
78 /// 1. Demux the frame from the APNG stream.
79 ///
80
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);
86 }
87
88 //----------------------------------------------------------------------------
89 /// 2. Decode the frame.
90 ///
91
92 APNGImage& frame = images_[image_index];
93 SkImageInfo frame_info = frame.codec->getInfo();
94 auto frame_row_bytes = frame_info.bytesPerPixel() * frame_info.width();
95
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);
105 }
106 }
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).";
111 return false;
112 }
113 if (
114 // Check for unsigned integer wrapping for
115 // frame.{x|y}_offset + frame_info.{width|height}().
116 frame.x_offset >
117 std::numeric_limits<uint32_t>::max() - frame_info.width() ||
118 frame.y_offset >
119 std::numeric_limits<uint32_t>::max() - frame_info.height() ||
120
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())) {
125 FML_DLOG(ERROR)
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() << ").";
133 return false;
134 }
135
136 //----------------------------------------------------------------------------
137 /// 3. Composite the frame onto the canvas.
138 ///
139
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.";
145 return false;
146 }
147 if (frame_info.colorType() != kN32_SkColorType) {
148 FML_DLOG(ERROR)
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.";
152 return false;
153 }
154
155 // Regardless of the byte order (RGBA vs BGRA), the blending operations are
156 // the same.
157 struct Pixel {
158 uint8_t channel[4];
159
160 uint8_t GetAlpha() { return channel[3]; }
161
162 void Premultiply() {
163 for (int i = 0; i < 3; i++) {
164 channel[i] = channel[i] * GetAlpha() / 0xFF;
165 }
166 }
167
168 void Unpremultiply() {
169 if (GetAlpha() == 0) {
170 channel[0] = channel[1] = channel[2] = 0;
171 return;
172 }
173 for (int i = 0; i < 3; i++) {
174 channel[i] = channel[i] * 0xFF / GetAlpha();
175 }
176 }
177 };
178
179 FML_DCHECK(frame_info.bytesPerPixel() == sizeof(Pixel));
180
181 bool result = true;
182
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);
189 if (!result) {
190 FML_DLOG(ERROR) << "Failed to copy pixels at index " << image_index
191 << " (frame index: " << frame_index << ") of APNG.";
192 }
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();
200
201 for (int x = 0; x < frame_info.width(); x++) {
202 auto x_offset_bytes = x * frame_info.bytesPerPixel();
203
204 Pixel src = *reinterpret_cast<Pixel*>(src_row + x_offset_bytes);
205 Pixel* dst_p = reinterpret_cast<Pixel*>(dst_row + x_offset_bytes);
206 Pixel dst = *dst_p;
207
208 // Ensure both colors are premultiplied for the blending operation.
209 if (info.alphaType() == kUnpremul_SkAlphaType) {
210 dst.Premultiply();
211 }
212 if (frame_info.alphaType() == kUnpremul_SkAlphaType) {
213 src.Premultiply();
214 }
215
216 for (int i = 0; i < 4; i++) {
217 dst.channel[i] =
218 src.channel[i] + dst.channel[i] * (0xFF - src.GetAlpha()) / 0xFF;
219 }
220
221 // The final color is premultiplied. Unpremultiply to match the
222 // backdrop surface if necessary.
223 if (info.alphaType() == kUnpremul_SkAlphaType) {
224 dst.Unpremultiply();
225 }
226
227 *dst_p = dst;
228 }
229 }
230 }
231
232 return result;
233}
234
235std::unique_ptr<ImageGenerator> APNGImageGenerator::MakeFromData(
236 sk_sp<SkData> data) {
237 // Ensure the buffer is large enough to at least contain the PNG signature
238 // and a chunk header.
239 if (data->size() < sizeof(kPngSignature) + sizeof(ChunkHeader)) {
240 return nullptr;
241 }
242 // Validate the full PNG signature.
243 const uint8_t* data_p = static_cast<const uint8_t*>(data.get()->data());
244 if (memcmp(data_p, kPngSignature, sizeof(kPngSignature))) {
245 return nullptr;
246 }
247
248 // Validate the header chunk.
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) {
253 return nullptr;
254 }
255
256 // Walk the chunks to find the "animation control" chunk. If an "image data"
257 // chunk is found first, this PNG is not animated.
258 while (true) {
259 chunk = GetNextChunk(data_p, data->size(), chunk);
260
261 if (chunk == nullptr) {
262 return nullptr;
263 }
264 if (chunk->get_type() == kImageDataChunkType) {
265 return nullptr;
266 }
267 if (chunk->get_type() == kAnimationControlChunkType) {
268 break;
269 }
270 }
271
272 const AnimationControlChunkData* animation_data =
273 CastChunkData<AnimationControlChunkData>(chunk);
274
275 // Extract the header signature and chunks to prepend when demuxing images.
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()) {
280 return nullptr;
281 }
282
283 // Demux the first image in the APNG chunk stream in order to interpret
284 // extent and blending info immediately.
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()) {
290 return nullptr;
291 }
292
293 unsigned int play_count = animation_data->get_num_plays();
294 if (play_count == 0) {
295 play_count = kInfinitePlayCount;
296 }
297
298 SkImageInfo image_info = default_image.value().codec->getInfo();
299 return std::unique_ptr<APNGImageGenerator>(
300 new APNGImageGenerator(data, image_info, std::move(default_image.value()),
301 animation_data->get_num_frames(), play_count,
302 next_chunk_p, header.value()));
303}
304
305bool APNGImageGenerator::IsValidChunkHeader(const void* buffer,
306 size_t size,
307 const ChunkHeader* chunk) {
308 // Ensure the chunk doesn't start before the beginning of the buffer.
309 if (reinterpret_cast<const uint8_t*>(chunk) <
310 static_cast<const uint8_t*>(buffer)) {
311 return false;
312 }
313
314 // Ensure the buffer is large enough to contain at least the chunk header.
315 if (reinterpret_cast<const uint8_t*>(chunk) + sizeof(ChunkHeader) >
316 static_cast<const uint8_t*>(buffer) + size) {
317 return false;
318 }
319
320 // Ensure the buffer is large enough to contain the chunk's given data size
321 // and CRC.
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) {
325 return false;
326 }
327
328 // Ensure the 4-byte type only contains ISO 646 letters.
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'))) {
333 return false;
334 }
335 }
336
337 return true;
338}
339
340const APNGImageGenerator::ChunkHeader* APNGImageGenerator::GetNextChunk(
341 const void* buffer,
342 size_t size,
343 const ChunkHeader* current_chunk) {
344 FML_DCHECK((uint8_t*)current_chunk + sizeof(ChunkHeader) <=
345 (uint8_t*)buffer + size);
346
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)) {
351 return nullptr;
352 }
353
354 return next_chunk;
355}
356
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));
361
362 const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(
363 static_cast<const uint8_t*>(buffer_p) + sizeof(kPngSignature));
364 // Validate the first chunk to ensure it's safe to read.
365 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
366 return std::make_pair(std::nullopt, nullptr);
367 }
368
369 // Walk the chunks and copy in the non-APNG chunks until we come across a
370 // frame or image chunk.
371 do {
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);
376 }
377
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);
382
383 // nullptr means the end of the buffer was reached, which means there's no
384 // frame or image data, so just return nothing because the PNG isn't even
385 // valid.
386 if (chunk == nullptr) {
387 return std::make_pair(std::nullopt, nullptr);
388 }
389
390 return std::make_pair(result, chunk);
391}
392
393std::pair<std::optional<APNGImageGenerator::APNGImage>, const void*>
394APNGImageGenerator::DemuxNextImage(const void* buffer_p,
395 size_t buffer_size,
396 const std::vector<uint8_t>& header,
397 const void* chunk_p) {
398 const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(chunk_p);
399 // Validate the given chunk to ensure it's safe to read.
400 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
401 return std::make_pair(std::nullopt, nullptr);
402 }
403
404 // Expect frame data to begin at fdAT or IDAT
405 if (chunk->get_type() != kFrameControlChunkType &&
406 chunk->get_type() != kImageDataChunkType) {
407 return std::make_pair(std::nullopt, nullptr);
408 }
409
410 APNGImage result;
411 const FrameControlChunkData* control_data = nullptr;
412
413 // The presence of an fcTL chunk is optional for the first (default) image
414 // of a PNG. Both cases are handled in APNGImage.
415 if (chunk->get_type() == kFrameControlChunkType) {
416 control_data = CastChunkData<FrameControlChunkData>(chunk);
417
418 ImageGenerator::FrameInfo frame_info;
419 switch (control_data->get_blend_op()) {
420 case 0: // APNG_BLEND_OP_SOURCE
421 frame_info.blend_mode = SkCodecAnimation::Blend::kSrc;
422 break;
423 case 1: // APNG_BLEND_OP_OVER
424 frame_info.blend_mode = SkCodecAnimation::Blend::kSrcOver;
425 break;
426 default:
427 return std::make_pair(std::nullopt, nullptr);
428 }
429
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()) {
434 case 0: // APNG_DISPOSE_OP_NONE
435 frame_info.disposal_method = SkCodecAnimation::DisposalMethod::kKeep;
436 break;
437 case 1: // APNG_DISPOSE_OP_BACKGROUND
438 frame_info.disposal_method =
439 SkCodecAnimation::DisposalMethod::kRestoreBGColor;
440 frame_info.disposal_rect = frame_rect;
441 break;
442 case 2: // APNG_DISPOSE_OP_PREVIOUS
443 frame_info.disposal_method =
444 SkCodecAnimation::DisposalMethod::kRestorePrevious;
445 break;
446 default:
447 return std::make_pair(std::nullopt, nullptr);
448 }
449 uint16_t denominator = control_data->get_delay_den() == 0
450 ? 100
451 : control_data->get_delay_den();
452 frame_info.duration =
453 static_cast<int>(control_data->get_delay_num() * 1000.f / denominator);
454
455 result.frame_info = frame_info;
456 result.x_offset = control_data->get_x_offset();
457 result.y_offset = control_data->get_y_offset();
458 }
459
460 std::vector<const ChunkHeader*> image_chunks;
461 size_t chunk_space = 0;
462
463 // Walk the chunks until the next frame, end chunk, or an invalid chunk is
464 // reached, recording the chunks to copy along with their required space.
465 // TODO(bdero): Validate that IDAT/fdAT chunks are contiguous.
466 // TODO(bdero): Validate the acTL/fcTL/fdAT sequence number ordering.
467 do {
468 if (chunk->get_type() != kFrameControlChunkType) {
469 image_chunks.push_back(chunk);
470 chunk_space += GetChunkSize(chunk);
471
472 // fdAT chunks are converted into IDAT chunks when demuxed. The only
473 // difference between these chunk types is that fdAT has a 4 byte
474 // sequence number prepended to its data, so subtract that space from
475 // the buffer.
476 if (chunk->get_type() == kFrameDataChunkType) {
477 chunk_space -= 4;
478 }
479 }
480
481 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
482 } while (chunk != nullptr && chunk->get_type() != kFrameControlChunkType &&
483 chunk->get_type() != kImageTrailerChunkType);
484
485 const uint8_t end_chunk[] = {0, 0, 0, 0, 'I', 'E',
486 'N', 'D', 0xAE, 0x42, 0x60, 0x82};
487
488 // Form a buffer for the new encoded PNG and copy the chunks in.
489 sk_sp<SkData> new_png_buffer = SkData::MakeUninitialized(
490 header.size() + chunk_space + sizeof(end_chunk));
491
492 {
493 uint8_t* write_cursor =
494 static_cast<uint8_t*>(new_png_buffer->writable_data());
495
496 // Copy the signature/header chunks
497 memcpy(write_cursor, header.data(), header.size());
498 // If this is a frame, override the width/height in the IHDR chunk.
499 if (control_data) {
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();
507 }
508 write_cursor += header.size();
509
510 // Copy the image data/ancillary chunks.
511 for (const ChunkHeader* c : image_chunks) {
512 if (c->get_type() == kFrameDataChunkType) {
513 // Write a new IDAT chunk header.
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);
519
520 // Copy all of the data except for the 4 byte sequence number at the
521 // beginning of the fdAT data.
522 memcpy(write_cursor,
523 reinterpret_cast<const uint8_t*>(c) + sizeof(ChunkHeader) + 4,
524 write_header->get_data_length());
525 write_cursor += write_header->get_data_length();
526
527 // Recompute the chunk CRC.
528 write_header->UpdateChunkCrc32();
529 write_cursor += 4;
530 } else {
531 size_t chunk_size = GetChunkSize(c);
532 memcpy(write_cursor, c, chunk_size);
533 write_cursor += chunk_size;
534 }
535 }
536
537 // Copy the trailer chunk.
538 memcpy(write_cursor, &end_chunk, sizeof(end_chunk));
539 }
540
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) {
545 FML_DLOG(ERROR)
546 << "Failed to parse image header during APNG demux. SkCodec::Result: "
547 << header_parse_result;
548 return std::make_pair(std::nullopt, nullptr);
549 }
550
551 if (chunk->get_type() == kImageTrailerChunkType) {
552 chunk = nullptr;
553 }
554
555 return std::make_pair(std::optional<APNGImage>{std::move(result)}, chunk);
556}
557
558bool APNGImageGenerator::DemuxNextImageInternal() {
559 if (next_chunk_p_ == nullptr) {
560 return false;
561 }
562
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()) {
568 return false;
569 }
570
571 auto last_frame_info = images_.back().frame_info;
572 if (!last_frame_info.has_value()) {
573 return false;
574 }
575
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)) {
581 // Mark the required frame as the previous frame in all cases.
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) {
586 // Mark the required frame as the last previous frame
587 // It is not valid if there are 2 or above frames set |disposal_method| to
588 // |kRestorePrevious|. But it also works in MultiFrameCodec.
589 image->frame_info->required_frame = images_.size() - 2;
590 }
591
592 // Calling SkCodec::getInfo at least once prior to decoding is mandatory.
593 SkImageInfo info = image.value().codec->getInfo();
594 FML_DCHECK(info.colorInfo() == image_info_.colorInfo());
595
596 images_.push_back(std::move(image.value()));
597
598 auto default_info = images_[0].codec->getInfo();
599 if (info.colorType() != default_info.colorType()) {
600 return false;
601 }
602 return true;
603}
604
605bool APNGImageGenerator::DemuxToImageIndex(unsigned int image_index) {
606 // If the requested image doesn't exist yet, demux more frames from the APNG
607 // stream.
608 if (image_index >= images_.size()) {
609 while (DemuxNextImageInternal() && image_index >= images_.size()) {
610 }
611
612 if (image_index >= images_.size()) {
613 // The chunk stream was exhausted before the image was found.
614 return false;
615 }
616 }
617
618 return true;
619}
620
621void APNGImageGenerator::ChunkHeader::UpdateChunkCrc32() {
622 uint32_t* crc_p =
623 reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(this) +
624 sizeof(ChunkHeader) + get_data_length());
625 *crc_p = fml::BigEndianToArch(ComputeChunkCrc32());
626}
627
628uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
629 // Exclude the length field at the beginning of the chunk header.
630 size_t length = sizeof(ChunkHeader) - 4 + get_data_length();
631 uint8_t* chunk_data_p = reinterpret_cast<uint8_t*>(this) + 4;
632 uint32_t crc = 0;
633
634 // zlib's crc32 can only take 16 bits at a time for the length, but PNG
635 // supports a 32 bit chunk length, so looping is necessary here.
636 // Note that crc32 is always called at least once, even if the chunk has an
637 // empty data section.
638 do {
639 uint16_t length16 = length;
640 if (length16 == 0 && length > 0) {
641 length16 = std::numeric_limits<uint16_t>::max();
642 }
643
644 crc = crc32(crc, chunk_data_p, length16);
645 length -= length16;
646 chunk_data_p += length16;
647 } while (length > 0);
648
649 return crc;
650}
651
652bool APNGImageGenerator::RenderDefaultImage(const SkImageInfo& info,
653 void* pixels,
654 size_t row_bytes) {
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()) {
659 FML_DLOG(ERROR)
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() << ").";
664 return false;
665 }
666
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. "
670 "SkCodec::Result: "
671 << result;
672 return false;
673 }
674 return true;
675}
676
677} // namespace flutter
GLenum type
int32_t x
FlutterVulkanImage * image
const gchar * channel
#define FML_DLOG(severity)
Definition logging.h:121
#define FML_DCHECK(condition)
Definition logging.h:122
size_t length
double y
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
Definition switch_defs.h:36
constexpr T BigEndianToArch(T n)
Convert a known big endian value to match the endianness of the current architecture....
Definition endianness.h:59
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