Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
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
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" // For crc32
18
19namespace flutter {
20
22
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)
30 : data_(data),
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),
36 header_(header) {
37 images_.push_back(std::move(default_image));
38}
39
40const SkImageInfo& APNGImageGenerator::GetInfo() {
41 return image_info_;
42}
43
44unsigned int APNGImageGenerator::GetFrameCount() const {
45 return frame_count_;
46}
47
48unsigned int APNGImageGenerator::GetPlayCount() const {
49 return frame_count_ > 1 ? play_count_ : 1;
50}
51
52const ImageGenerator::FrameInfo APNGImageGenerator::GetFrameInfo(
53 unsigned int frame_index) {
54 unsigned int image_index = first_frame_index_ + frame_index;
55 if (!DemuxToImageIndex(image_index)) {
56 return {};
57 }
58
59 auto frame_info = images_[image_index].frame_info;
60 if (frame_info.has_value()) {
61 return frame_info.value();
62 }
63 return {};
64}
65
66SkISize APNGImageGenerator::GetScaledDimensions(float desired_scale) {
67 return image_info_.dimensions();
68}
69
70bool APNGImageGenerator::GetPixels(const SkImageInfo& info,
71 void* pixels,
72 size_t row_bytes,
73 unsigned int frame_index,
74 std::optional<unsigned int> prior_frame) {
75 FML_DCHECK(images_.size() > 0);
76 unsigned int image_index = first_frame_index_ + frame_index;
77
78 //----------------------------------------------------------------------------
79 /// 1. Demux the frame from the APNG stream.
80 ///
81
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);
87 }
88
89 //----------------------------------------------------------------------------
90 /// 2. Decode the frame.
91 ///
92
93 APNGImage& frame = images_[image_index];
94 SkImageInfo frame_info = frame.codec->getInfo();
95
96 fml::SafeMath safe;
97 size_t frame_row_bytes =
98 safe.mul(frame_info.bytesPerPixel(), frame_info.width());
99 if (safe.overflow_detected()) {
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.";
103 return false;
104 }
105
106 if (frame.pixels.empty()) {
107 size_t pixels_bytes = safe.mul(frame_row_bytes, frame_info.height());
108 if (safe.overflow_detected()) {
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.";
112 return false;
113 }
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);
122 }
123 }
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).";
128 return false;
129 }
130 if (
131 // Check for unsigned integer wrapping for
132 // frame.{x|y}_offset + frame_info.{width|height}().
133 frame.x_offset >
134 std::numeric_limits<uint32_t>::max() - frame_info.width() ||
135 frame.y_offset >
136 std::numeric_limits<uint32_t>::max() - frame_info.height() ||
137
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())) {
142 FML_DLOG(ERROR)
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() << ").";
150 return false;
151 }
152
153 //----------------------------------------------------------------------------
154 /// 3. Composite the frame onto the canvas.
155 ///
156
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.";
162 return false;
163 }
164 if (frame_info.colorType() != kN32_SkColorType) {
165 FML_DLOG(ERROR)
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.";
169 return false;
170 }
171
172 // Regardless of the byte order (RGBA vs BGRA), the blending operations are
173 // the same.
174 struct Pixel {
175 uint8_t channel[4];
176
177 uint8_t GetAlpha() { return channel[3]; }
178
179 void Premultiply() {
180 for (int i = 0; i < 3; i++) {
181 channel[i] = channel[i] * GetAlpha() / 0xFF;
182 }
183 }
184
185 void Unpremultiply() {
186 if (GetAlpha() == 0) {
187 channel[0] = channel[1] = channel[2] = 0;
188 return;
189 }
190 for (int i = 0; i < 3; i++) {
191 channel[i] = channel[i] * 0xFF / GetAlpha();
192 }
193 }
194 };
195
196 FML_DCHECK(frame_info.bytesPerPixel() == sizeof(Pixel));
197
198 bool result = true;
199
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);
206 if (!result) {
207 FML_DLOG(ERROR) << "Failed to copy pixels at index " << image_index
208 << " (frame index: " << frame_index << ") of APNG.";
209 }
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();
217
218 for (int x = 0; x < frame_info.width(); x++) {
219 auto x_offset_bytes = x * frame_info.bytesPerPixel();
220
221 Pixel src = *reinterpret_cast<Pixel*>(src_row + x_offset_bytes);
222 Pixel* dst_p = reinterpret_cast<Pixel*>(dst_row + x_offset_bytes);
223 Pixel dst = *dst_p;
224
225 // Ensure both colors are premultiplied for the blending operation.
226 if (info.alphaType() == kUnpremul_SkAlphaType) {
227 dst.Premultiply();
228 }
229 if (frame_info.alphaType() == kUnpremul_SkAlphaType) {
230 src.Premultiply();
231 }
232
233 for (int i = 0; i < 4; i++) {
234 dst.channel[i] =
235 src.channel[i] + dst.channel[i] * (0xFF - src.GetAlpha()) / 0xFF;
236 }
237
238 // The final color is premultiplied. Unpremultiply to match the
239 // backdrop surface if necessary.
240 if (info.alphaType() == kUnpremul_SkAlphaType) {
241 dst.Unpremultiply();
242 }
243
244 *dst_p = dst;
245 }
246 }
247 }
248
249 return result;
250}
251
252std::unique_ptr<ImageGenerator> APNGImageGenerator::MakeFromData(
253 sk_sp<SkData> data) {
254 // Ensure the buffer is large enough to at least contain the PNG signature
255 // and a chunk header.
256 if (data->size() < sizeof(kPngSignature) + sizeof(ChunkHeader)) {
257 return nullptr;
258 }
259 // Validate the full PNG signature.
260 const uint8_t* data_p = static_cast<const uint8_t*>(data.get()->data());
261 if (memcmp(data_p, kPngSignature, sizeof(kPngSignature))) {
262 return nullptr;
263 }
264
265 // Validate the header chunk.
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) {
270 return nullptr;
271 }
272
273 // Walk the chunks to find the "animation control" chunk. If an "image data"
274 // chunk is found first, this PNG is not animated.
275 while (true) {
276 chunk = GetNextChunk(data_p, data->size(), chunk);
277
278 if (chunk == nullptr) {
279 return nullptr;
280 }
281 if (chunk->get_type() == kImageDataChunkType) {
282 return nullptr;
283 }
284 if (chunk->get_type() == kAnimationControlChunkType) {
285 break;
286 }
287 }
288
289 if (chunk->get_data_length() < sizeof(AnimationControlChunkData)) {
290 return nullptr;
291 }
292 const AnimationControlChunkData* animation_data =
293 CastChunkData<AnimationControlChunkData>(chunk);
294
295 // Extract the header signature and chunks to prepend when demuxing images.
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()) {
300 return nullptr;
301 }
302
303 // Demux the first image in the APNG chunk stream in order to interpret
304 // extent and blending info immediately.
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()) {
310 return nullptr;
311 }
312
313 unsigned int play_count = animation_data->get_num_plays();
314 if (play_count == 0) {
315 play_count = kInfinitePlayCount;
316 }
317
318 SkImageInfo image_info = default_image.value().codec->getInfo();
319 return std::unique_ptr<APNGImageGenerator>(
320 new APNGImageGenerator(data, image_info, std::move(default_image.value()),
321 animation_data->get_num_frames(), play_count,
322 next_chunk_p, header.value()));
323}
324
325bool APNGImageGenerator::IsValidChunkHeader(const void* buffer,
326 size_t size,
327 const ChunkHeader* chunk) {
328 // Ensure the chunk doesn't start before the beginning of the buffer.
329 if (reinterpret_cast<const uint8_t*>(chunk) <
330 static_cast<const uint8_t*>(buffer)) {
331 return false;
332 }
333
334 // Ensure the buffer is large enough to contain at least the chunk header.
335 if (reinterpret_cast<const uint8_t*>(chunk) + sizeof(ChunkHeader) >
336 static_cast<const uint8_t*>(buffer) + size) {
337 return false;
338 }
339
340 // Ensure the buffer is large enough to contain the chunk's given data size
341 // and CRC.
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) {
345 return false;
346 }
347
348 // Ensure the 4-byte type only contains ISO 646 letters.
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'))) {
353 return false;
354 }
355 }
356
357 return true;
358}
359
360const APNGImageGenerator::ChunkHeader* APNGImageGenerator::GetNextChunk(
361 const void* buffer,
362 size_t size,
363 const ChunkHeader* current_chunk) {
364 FML_DCHECK((uint8_t*)current_chunk + sizeof(ChunkHeader) <=
365 (uint8_t*)buffer + size);
366
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)) {
371 return nullptr;
372 }
373
374 return next_chunk;
375}
376
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));
381
382 const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(
383 static_cast<const uint8_t*>(buffer_p) + sizeof(kPngSignature));
384 // Validate the first chunk to ensure it's safe to read.
385 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
386 return std::make_pair(std::nullopt, nullptr);
387 }
388
389 // Walk the chunks and copy in the non-APNG chunks until we come across a
390 // frame or image chunk.
391 do {
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);
396 }
397
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);
402
403 // nullptr means the end of the buffer was reached, which means there's no
404 // frame or image data, so just return nothing because the PNG isn't even
405 // valid.
406 if (chunk == nullptr) {
407 return std::make_pair(std::nullopt, nullptr);
408 }
409
410 return std::make_pair(result, chunk);
411}
412
413std::pair<std::optional<APNGImageGenerator::APNGImage>, const void*>
414APNGImageGenerator::DemuxNextImage(const void* buffer_p,
415 size_t buffer_size,
416 const std::vector<uint8_t>& header,
417 const void* chunk_p) {
418 const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(chunk_p);
419 // Validate the given chunk to ensure it's safe to read.
420 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
421 return std::make_pair(std::nullopt, nullptr);
422 }
423
424 // Expect frame data to begin at fdAT or IDAT
425 if (chunk->get_type() != kFrameControlChunkType &&
426 chunk->get_type() != kImageDataChunkType) {
427 return std::make_pair(std::nullopt, nullptr);
428 }
429
430 APNGImage result;
431 const FrameControlChunkData* control_data = nullptr;
432
433 // The presence of an fcTL chunk is optional for the first (default) image
434 // of a PNG. Both cases are handled in APNGImage.
435 if (chunk->get_type() == kFrameControlChunkType) {
436 if (chunk->get_data_length() < sizeof(FrameControlChunkData)) {
437 return std::make_pair(std::nullopt, nullptr);
438 }
439 control_data = CastChunkData<FrameControlChunkData>(chunk);
440
441 ImageGenerator::FrameInfo frame_info;
442 switch (control_data->get_blend_op()) {
443 case 0: // APNG_BLEND_OP_SOURCE
444 frame_info.blend_mode = SkCodecAnimation::Blend::kSrc;
445 break;
446 case 1: // APNG_BLEND_OP_OVER
447 frame_info.blend_mode = SkCodecAnimation::Blend::kSrcOver;
448 break;
449 default:
450 return std::make_pair(std::nullopt, nullptr);
451 }
452
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()) {
457 case 0: // APNG_DISPOSE_OP_NONE
458 frame_info.disposal_method = SkCodecAnimation::DisposalMethod::kKeep;
459 break;
460 case 1: // APNG_DISPOSE_OP_BACKGROUND
461 frame_info.disposal_method =
462 SkCodecAnimation::DisposalMethod::kRestoreBGColor;
463 frame_info.disposal_rect = frame_rect;
464 break;
465 case 2: // APNG_DISPOSE_OP_PREVIOUS
466 frame_info.disposal_method =
467 SkCodecAnimation::DisposalMethod::kRestorePrevious;
468 break;
469 default:
470 return std::make_pair(std::nullopt, nullptr);
471 }
472 uint16_t denominator = control_data->get_delay_den() == 0
473 ? 100
474 : control_data->get_delay_den();
475 frame_info.duration =
476 static_cast<int>(control_data->get_delay_num() * 1000.f / denominator);
477
478 result.frame_info = frame_info;
479 result.x_offset = control_data->get_x_offset();
480 result.y_offset = control_data->get_y_offset();
481 }
482
483 std::vector<const ChunkHeader*> image_chunks;
484 size_t chunk_space = 0;
485
486 // Walk the chunks until the next frame, end chunk, or an invalid chunk is
487 // reached, recording the chunks to copy along with their required space.
488 // TODO(bdero): Validate that IDAT/fdAT chunks are contiguous.
489 // TODO(bdero): Validate the acTL/fcTL/fdAT sequence number ordering.
490 do {
491 if (chunk->get_type() != kFrameControlChunkType) {
492 image_chunks.push_back(chunk);
493 chunk_space += GetChunkSize(chunk);
494
495 // fdAT chunks are converted into IDAT chunks when demuxed. The only
496 // difference between these chunk types is that fdAT has a 4 byte
497 // sequence number prepended to its data, so subtract that space from
498 // the buffer.
499 if (chunk->get_type() == kFrameDataChunkType) {
500 if (chunk->get_data_length() < kFrameDataSequenceNumberSize) {
501 return std::make_pair(std::nullopt, nullptr);
502 }
503 chunk_space -= kFrameDataSequenceNumberSize;
504 }
505 }
506
507 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
508 } while (chunk != nullptr && chunk->get_type() != kFrameControlChunkType &&
509 chunk->get_type() != kImageTrailerChunkType);
510
511 const uint8_t end_chunk[] = {0, 0, 0, 0, 'I', 'E',
512 'N', 'D', 0xAE, 0x42, 0x60, 0x82};
513
514 // Form a buffer for the new encoded PNG and copy the chunks in.
515 sk_sp<SkData> new_png_buffer = SkData::MakeUninitialized(
516 header.size() + chunk_space + sizeof(end_chunk));
517
518 {
519 uint8_t* write_cursor =
520 static_cast<uint8_t*>(new_png_buffer->writable_data());
521
522 // Copy the signature/header chunks
523 memcpy(write_cursor, header.data(), header.size());
524 // If this is a frame, override the width/height in the IHDR chunk.
525 if (control_data) {
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();
533 }
534 write_cursor += header.size();
535
536 // Copy the image data/ancillary chunks.
537 for (const ChunkHeader* c : image_chunks) {
538 if (c->get_type() == kFrameDataChunkType) {
539 FML_DCHECK(c->get_data_length() >= kFrameDataSequenceNumberSize);
540
541 // Write a new IDAT chunk header.
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);
548
549 // Copy all of the data except for the 4 byte sequence number at the
550 // beginning of the fdAT data.
551 memcpy(write_cursor,
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();
556
557 // Recompute the chunk CRC.
558 write_header->UpdateChunkCrc32();
559 write_cursor += 4;
560 } else {
561 size_t chunk_size = GetChunkSize(c);
562 memcpy(write_cursor, c, chunk_size);
563 write_cursor += chunk_size;
564 }
565 }
566
567 // Copy the trailer chunk.
568 memcpy(write_cursor, &end_chunk, sizeof(end_chunk));
569 }
570
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) {
575 FML_DLOG(ERROR)
576 << "Failed to parse image header during APNG demux. SkCodec::Result: "
577 << header_parse_result;
578 return std::make_pair(std::nullopt, nullptr);
579 }
580
581 if (chunk->get_type() == kImageTrailerChunkType) {
582 chunk = nullptr;
583 }
584
585 return std::make_pair(std::optional<APNGImage>{std::move(result)}, chunk);
586}
587
588bool APNGImageGenerator::DemuxNextImageInternal() {
589 if (next_chunk_p_ == nullptr) {
590 return false;
591 }
592
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()) {
598 return false;
599 }
600
601 auto last_frame_info = images_.back().frame_info;
602 if (!last_frame_info.has_value()) {
603 return false;
604 }
605
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)) {
611 // Mark the required frame as the previous frame in all cases.
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) {
616 // Mark the required frame as the last previous frame
617 // It is not valid if there are 2 or above frames set |disposal_method| to
618 // |kRestorePrevious|. But it also works in MultiFrameCodec.
619 image->frame_info->required_frame = images_.size() - 2;
620 }
621
622 // Calling SkCodec::getInfo at least once prior to decoding is mandatory.
623 SkImageInfo info = image.value().codec->getInfo();
624 FML_DCHECK(info.colorInfo() == image_info_.colorInfo());
625
626 images_.push_back(std::move(image.value()));
627
628 auto default_info = images_[0].codec->getInfo();
629 if (info.colorType() != default_info.colorType()) {
630 return false;
631 }
632 return true;
633}
634
635bool APNGImageGenerator::DemuxToImageIndex(unsigned int image_index) {
636 // If the requested image doesn't exist yet, demux more frames from the APNG
637 // stream.
638 if (image_index >= images_.size()) {
639 while (DemuxNextImageInternal() && image_index >= images_.size()) {
640 }
641
642 if (image_index >= images_.size()) {
643 // The chunk stream was exhausted before the image was found.
644 return false;
645 }
646 }
647
648 return true;
649}
650
651void APNGImageGenerator::ChunkHeader::UpdateChunkCrc32() {
652 uint32_t* crc_p =
653 reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(this) +
654 sizeof(ChunkHeader) + get_data_length());
655 *crc_p = fml::BigEndianToArch(ComputeChunkCrc32());
656}
657
658uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
659 // Exclude the length field at the beginning of the chunk header.
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);
663}
664
665uint32_t APNGImageGenerator::ComputeCrc32(const uint8_t* data, size_t length) {
666 uint32_t crc = 0;
667 const uint8_t* data_p = data;
668
669 // zlib's crc32 can only take 16 bits at a time for the length, but PNG
670 // supports a 32 bit chunk length, so looping is necessary here.
671 // Note that crc32 is always called at least once, even if the chunk has an
672 // empty data section.
673 do {
674 uint16_t length16 = length;
675 if (length16 == 0 && length > 0) {
676 length16 = std::numeric_limits<uint16_t>::max();
677 }
678
679 crc = crc32(crc, data_p, length16);
680 length -= length16;
681 data_p += length16;
682 } while (length > 0);
683
684 return crc;
685}
686
687bool APNGImageGenerator::RenderDefaultImage(const SkImageInfo& info,
688 void* pixels,
689 size_t row_bytes) {
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()) {
694 FML_DLOG(ERROR)
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() << ").";
699 return false;
700 }
701
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. "
705 "SkCodec::Result: "
706 << result;
707 return false;
708 }
709 return true;
710}
711
712} // namespace flutter
size_t mul(size_t x, size_t y)
Definition safe_math.cc:12
bool overflow_detected() const
Definition safe_math.h:17
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
impeller::ShaderType type
Info about a single frame in the context of a multi-frame image, useful for animation and blending.