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() < kPngSignature.size() + 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.data(), kPngSignature.size())) {
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 that the chunk starts within the bounds of the buffer.
329 const uint8_t* chunk_ptr = reinterpret_cast<const uint8_t*>(chunk);
330 const uint8_t* buffer_ptr = static_cast<const uint8_t*>(buffer);
331 if (chunk_ptr < buffer_ptr || chunk_ptr >= buffer_ptr + size) {
332 return false;
333 }
334
335 // Ensure that the buffer has enough space for the chunk header before using
336 // any fields in the header.
337 size_t buffer_bytes_remaining = size - (chunk_ptr - buffer_ptr);
338 if (buffer_bytes_remaining < sizeof(ChunkHeader)) {
339 return false;
340 }
341 // Ensure that the buffer has enough space for the chunk data and CRC.
342 // Do not use the chunk data length in pointer arithmetic until it is known to
343 // be valid.
344 size_t data_length = chunk->get_data_length();
345 if (buffer_bytes_remaining - sizeof(ChunkHeader) < data_length) {
346 return false;
347 }
348 if (buffer_bytes_remaining - sizeof(ChunkHeader) - data_length <
349 kChunkCrcSize) {
350 return false;
351 }
352
353 // Ensure the 4-byte type only contains ISO 646 letters.
354 uint32_t type = chunk->get_type();
355 for (int i = 0; i < 4; i++) {
356 uint8_t c = type >> i * 8 & 0xFF;
357 if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) {
358 return false;
359 }
360 }
361
362 return true;
363}
364
365const APNGImageGenerator::ChunkHeader* APNGImageGenerator::GetNextChunk(
366 const void* buffer,
367 size_t size,
368 const ChunkHeader* current_chunk) {
369 FML_DCHECK((uint8_t*)current_chunk + sizeof(ChunkHeader) <=
370 (uint8_t*)buffer + size);
371
372 const ChunkHeader* next_chunk = reinterpret_cast<const ChunkHeader*>(
373 reinterpret_cast<const uint8_t*>(current_chunk) +
374 GetChunkSize(current_chunk));
375 if (!IsValidChunkHeader(buffer, size, next_chunk)) {
376 return nullptr;
377 }
378
379 return next_chunk;
380}
381
382std::pair<std::optional<std::vector<uint8_t>>, const void*>
383APNGImageGenerator::ExtractHeader(const void* buffer_p, size_t buffer_size) {
384 std::vector<uint8_t> result(kPngSignature.begin(), kPngSignature.end());
385
386 const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(
387 static_cast<const uint8_t*>(buffer_p) + sizeof(kPngSignature));
388 // Validate the first chunk to ensure it's safe to read.
389 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
390 return std::make_pair(std::nullopt, nullptr);
391 }
392
393 // Walk the chunks and copy in the non-APNG chunks until we come across a
394 // frame or image chunk.
395 do {
396 if (chunk->get_type() != kAnimationControlChunkType) {
397 size_t chunk_size = GetChunkSize(chunk);
398 result.resize(result.size() + chunk_size);
399 memcpy(result.data() + result.size() - chunk_size, chunk, chunk_size);
400 }
401
402 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
403 } while (chunk != nullptr && chunk->get_type() != kFrameControlChunkType &&
404 chunk->get_type() != kImageDataChunkType &&
405 chunk->get_type() != kFrameDataChunkType);
406
407 // nullptr means the end of the buffer was reached, which means there's no
408 // frame or image data, so just return nothing because the PNG isn't even
409 // valid.
410 if (chunk == nullptr) {
411 return std::make_pair(std::nullopt, nullptr);
412 }
413
414 return std::make_pair(result, chunk);
415}
416
417std::pair<std::optional<APNGImageGenerator::APNGImage>, const void*>
418APNGImageGenerator::DemuxNextImage(const void* buffer_p,
419 size_t buffer_size,
420 const std::vector<uint8_t>& header,
421 const void* chunk_p) {
422 const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(chunk_p);
423 // Validate the given chunk to ensure it's safe to read.
424 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
425 return std::make_pair(std::nullopt, nullptr);
426 }
427
428 // Expect frame data to begin at fdAT or IDAT
429 if (chunk->get_type() != kFrameControlChunkType &&
430 chunk->get_type() != kImageDataChunkType) {
431 return std::make_pair(std::nullopt, nullptr);
432 }
433
434 APNGImage result;
435 const FrameControlChunkData* control_data = nullptr;
436
437 // The presence of an fcTL chunk is optional for the first (default) image
438 // of a PNG. Both cases are handled in APNGImage.
439 if (chunk->get_type() == kFrameControlChunkType) {
440 if (chunk->get_data_length() < sizeof(FrameControlChunkData)) {
441 return std::make_pair(std::nullopt, nullptr);
442 }
443 control_data = CastChunkData<FrameControlChunkData>(chunk);
444
445 ImageGenerator::FrameInfo frame_info;
446 switch (control_data->get_blend_op()) {
447 case 0: // APNG_BLEND_OP_SOURCE
448 frame_info.blend_mode = SkCodecAnimation::Blend::kSrc;
449 break;
450 case 1: // APNG_BLEND_OP_OVER
451 frame_info.blend_mode = SkCodecAnimation::Blend::kSrcOver;
452 break;
453 default:
454 return std::make_pair(std::nullopt, nullptr);
455 }
456
457 SkIRect frame_rect = SkIRect::MakeXYWH(
458 control_data->get_x_offset(), control_data->get_y_offset(),
459 control_data->get_width(), control_data->get_height());
460 switch (control_data->get_dispose_op()) {
461 case 0: // APNG_DISPOSE_OP_NONE
462 frame_info.disposal_method = SkCodecAnimation::DisposalMethod::kKeep;
463 break;
464 case 1: // APNG_DISPOSE_OP_BACKGROUND
465 frame_info.disposal_method =
466 SkCodecAnimation::DisposalMethod::kRestoreBGColor;
467 frame_info.disposal_rect = frame_rect;
468 break;
469 case 2: // APNG_DISPOSE_OP_PREVIOUS
470 frame_info.disposal_method =
471 SkCodecAnimation::DisposalMethod::kRestorePrevious;
472 break;
473 default:
474 return std::make_pair(std::nullopt, nullptr);
475 }
476 uint16_t denominator = control_data->get_delay_den() == 0
477 ? 100
478 : control_data->get_delay_den();
479 frame_info.duration =
480 static_cast<int>(control_data->get_delay_num() * 1000.f / denominator);
481
482 result.frame_info = frame_info;
483 result.x_offset = control_data->get_x_offset();
484 result.y_offset = control_data->get_y_offset();
485 }
486
487 std::vector<const ChunkHeader*> image_chunks;
488 size_t chunk_space = 0;
489
490 // Walk the chunks until the next frame, end chunk, or an invalid chunk is
491 // reached, recording the chunks to copy along with their required space.
492 // TODO(bdero): Validate that IDAT/fdAT chunks are contiguous.
493 // TODO(bdero): Validate the acTL/fcTL/fdAT sequence number ordering.
494 do {
495 if (chunk->get_type() != kFrameControlChunkType) {
496 image_chunks.push_back(chunk);
497 chunk_space += GetChunkSize(chunk);
498
499 // fdAT chunks are converted into IDAT chunks when demuxed. The only
500 // difference between these chunk types is that fdAT has a 4 byte
501 // sequence number prepended to its data, so subtract that space from
502 // the buffer.
503 if (chunk->get_type() == kFrameDataChunkType) {
504 if (chunk->get_data_length() < kFrameDataSequenceNumberSize) {
505 return std::make_pair(std::nullopt, nullptr);
506 }
507 chunk_space -= kFrameDataSequenceNumberSize;
508 }
509 }
510
511 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
512 } while (chunk != nullptr && chunk->get_type() != kFrameControlChunkType &&
513 chunk->get_type() != kImageTrailerChunkType);
514
515 const uint8_t end_chunk[] = {0, 0, 0, 0, 'I', 'E',
516 'N', 'D', 0xAE, 0x42, 0x60, 0x82};
517
518 // Form a buffer for the new encoded PNG and copy the chunks in.
519 sk_sp<SkData> new_png_buffer = SkData::MakeUninitialized(
520 header.size() + chunk_space + sizeof(end_chunk));
521
522 {
523 uint8_t* write_cursor =
524 static_cast<uint8_t*>(new_png_buffer->writable_data());
525
526 // Copy the signature/header chunks
527 memcpy(write_cursor, header.data(), header.size());
528 // If this is a frame, override the width/height in the IHDR chunk.
529 if (control_data) {
530 ChunkHeader* ihdr_header =
531 reinterpret_cast<ChunkHeader*>(write_cursor + sizeof(kPngSignature));
532 ImageHeaderChunkData* ihdr_data = const_cast<ImageHeaderChunkData*>(
533 CastChunkData<ImageHeaderChunkData>(ihdr_header));
534 ihdr_data->set_width(control_data->get_width());
535 ihdr_data->set_height(control_data->get_height());
536 ihdr_header->UpdateChunkCrc32();
537 }
538 write_cursor += header.size();
539
540 // Copy the image data/ancillary chunks.
541 for (const ChunkHeader* c : image_chunks) {
542 if (c->get_type() == kFrameDataChunkType) {
543 FML_DCHECK(c->get_data_length() >= kFrameDataSequenceNumberSize);
544
545 // Write a new IDAT chunk header.
546 ChunkHeader* write_header =
547 reinterpret_cast<ChunkHeader*>(write_cursor);
548 write_header->set_data_length(c->get_data_length() -
549 kFrameDataSequenceNumberSize);
550 write_header->set_type(kImageDataChunkType);
551 write_cursor += sizeof(ChunkHeader);
552
553 // Copy all of the data except for the 4 byte sequence number at the
554 // beginning of the fdAT data.
555 memcpy(write_cursor,
556 reinterpret_cast<const uint8_t*>(c) + sizeof(ChunkHeader) +
557 kFrameDataSequenceNumberSize,
558 write_header->get_data_length());
559 write_cursor += write_header->get_data_length();
560
561 // Recompute the chunk CRC.
562 write_header->UpdateChunkCrc32();
563 write_cursor += 4;
564 } else {
565 size_t chunk_size = GetChunkSize(c);
566 memcpy(write_cursor, c, chunk_size);
567 write_cursor += chunk_size;
568 }
569 }
570
571 // Copy the trailer chunk.
572 memcpy(write_cursor, &end_chunk, sizeof(end_chunk));
573 }
574
575 SkCodec::Result header_parse_result;
576 result.codec = SkCodec::MakeFromStream(SkMemoryStream::Make(new_png_buffer),
577 &header_parse_result);
578 if (header_parse_result != SkCodec::Result::kSuccess) {
579 FML_DLOG(ERROR)
580 << "Failed to parse image header during APNG demux. SkCodec::Result: "
581 << header_parse_result;
582 return std::make_pair(std::nullopt, nullptr);
583 }
584
585 if (chunk->get_type() == kImageTrailerChunkType) {
586 chunk = nullptr;
587 }
588
589 return std::make_pair(std::optional<APNGImage>{std::move(result)}, chunk);
590}
591
592bool APNGImageGenerator::DemuxNextImageInternal() {
593 if (next_chunk_p_ == nullptr) {
594 return false;
595 }
596
597 std::optional<APNGImage> image;
598 const void* data_p = const_cast<void*>(data_.get()->data());
599 std::tie(image, next_chunk_p_) =
600 DemuxNextImage(data_p, data_->size(), header_, next_chunk_p_);
601 if (!image.has_value() || !image->frame_info.has_value()) {
602 return false;
603 }
604
605 auto last_frame_info = images_.back().frame_info;
606 if (!last_frame_info.has_value()) {
607 return false;
608 }
609
610 if (images_.size() > first_frame_index_ &&
611 (last_frame_info->disposal_method ==
612 SkCodecAnimation::DisposalMethod::kKeep ||
613 last_frame_info->disposal_method ==
614 SkCodecAnimation::DisposalMethod::kRestoreBGColor)) {
615 // Mark the required frame as the previous frame in all cases.
616 image->frame_info->required_frame = images_.size() - 1;
617 } else if (images_.size() > (first_frame_index_ + 1) &&
618 last_frame_info->disposal_method ==
619 SkCodecAnimation::DisposalMethod::kRestorePrevious) {
620 // Mark the required frame as the last previous frame
621 // It is not valid if there are 2 or above frames set |disposal_method| to
622 // |kRestorePrevious|. But it also works in MultiFrameCodec.
623 image->frame_info->required_frame = images_.size() - 2;
624 }
625
626 // Calling SkCodec::getInfo at least once prior to decoding is mandatory.
627 SkImageInfo info = image.value().codec->getInfo();
628 FML_DCHECK(info.colorInfo() == image_info_.colorInfo());
629
630 images_.push_back(std::move(image.value()));
631
632 auto default_info = images_[0].codec->getInfo();
633 if (info.colorType() != default_info.colorType()) {
634 return false;
635 }
636 return true;
637}
638
639bool APNGImageGenerator::DemuxToImageIndex(unsigned int image_index) {
640 // If the requested image doesn't exist yet, demux more frames from the APNG
641 // stream.
642 if (image_index >= images_.size()) {
643 while (DemuxNextImageInternal() && image_index >= images_.size()) {
644 }
645
646 if (image_index >= images_.size()) {
647 // The chunk stream was exhausted before the image was found.
648 return false;
649 }
650 }
651
652 return true;
653}
654
655void APNGImageGenerator::ChunkHeader::UpdateChunkCrc32() {
656 uint32_t* crc_p =
657 reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(this) +
658 sizeof(ChunkHeader) + get_data_length());
659 *crc_p = fml::BigEndianToArch(ComputeChunkCrc32());
660}
661
662uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
663 // Exclude the length field at the beginning of the chunk header.
664 size_t length = sizeof(ChunkHeader) - 4 + get_data_length();
665 const uint8_t* chunk_data = reinterpret_cast<const uint8_t*>(this) + 4;
666 return ComputeCrc32(chunk_data, length);
667}
668
669uint32_t APNGImageGenerator::ComputeCrc32(const uint8_t* data, size_t length) {
670 uint32_t crc = 0;
671 const uint8_t* data_p = data;
672
673 // zlib's crc32 can only take 16 bits at a time for the length, but PNG
674 // supports a 32 bit chunk length, so looping is necessary here.
675 // Note that crc32 is always called at least once, even if the chunk has an
676 // empty data section.
677 do {
678 uint16_t length16 = length;
679 if (length16 == 0 && length > 0) {
680 length16 = std::numeric_limits<uint16_t>::max();
681 }
682
683 crc = crc32(crc, data_p, length16);
684 length -= length16;
685 data_p += length16;
686 } while (length > 0);
687
688 return crc;
689}
690
691bool APNGImageGenerator::RenderDefaultImage(const SkImageInfo& info,
692 void* pixels,
693 size_t row_bytes) {
694 APNGImage& frame = images_[0];
695 SkImageInfo frame_info = frame.codec->getInfo();
696 if (frame_info.width() > info.width() ||
697 frame_info.height() > info.height()) {
698 FML_DLOG(ERROR)
699 << "Default image rejected because the destination region (width: "
700 << frame_info.width() << ", height: " << frame_info.height()
701 << ") is not entirely within the destination surface (width: "
702 << info.width() << ", height: " << info.height() << ").";
703 return false;
704 }
705
706 SkCodec::Result result = frame.codec->getPixels(info, pixels, row_bytes);
707 if (result != SkCodec::kSuccess) {
708 FML_DLOG(ERROR) << "Failed to decode the APNG's default/fallback image. "
709 "SkCodec::Result: "
710 << result;
711 return false;
712 }
713 return true;
714}
715
716} // 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
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
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 disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set profile Make the profiler discard new samples once the profiler sample buffer is full When this flag is not the profiler sample buffer is used as a ring buffer
Definition switch_defs.h:98
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.