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