Flutter Engine
The 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
9#include "flutter/fml/logging.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
114 //----------------------------------------------------------------------------
115 /// 3. Composite the frame onto the canvas.
116 ///
117
118 if (info.colorType() != kN32_SkColorType) {
119 FML_DLOG(ERROR) << "Failed to composite image at index " << image_index
120 << " (frame index: " << frame_index
121 << ") of APNG due to the destination surface having an "
122 "unsupported color type.";
123 return false;
124 }
125 if (frame_info.colorType() != kN32_SkColorType) {
127 << "Failed to composite image at index " << image_index
128 << " (frame index: " << frame_index
129 << ") of APNG due to the frame having an unsupported color type.";
130 return false;
131 }
132
133 // Regardless of the byte order (RGBA vs BGRA), the blending operations are
134 // the same.
135 struct Pixel {
136 uint8_t channel[4];
137
138 uint8_t GetAlpha() { return channel[3]; }
139
140 void Premultiply() {
141 for (int i = 0; i < 3; i++) {
142 channel[i] = channel[i] * GetAlpha() / 0xFF;
143 }
144 }
145
146 void Unpremultiply() {
147 if (GetAlpha() == 0) {
148 channel[0] = channel[1] = channel[2] = 0;
149 return;
150 }
151 for (int i = 0; i < 3; i++) {
152 channel[i] = channel[i] * 0xFF / GetAlpha();
153 }
154 }
155 };
156
157 FML_DCHECK(frame_info.bytesPerPixel() == sizeof(Pixel));
158
159 bool result = true;
160
161 if (frame.frame_info->blend_mode == SkCodecAnimation::Blend::kSrc) {
162 SkPixmap src_pixmap(frame_info, frame.pixels.data(), frame_row_bytes);
163 uint8_t* dst_pixels = static_cast<uint8_t*>(pixels) +
164 frame.y_offset * row_bytes +
165 frame.x_offset * frame_info.bytesPerPixel();
166 result = src_pixmap.readPixels(info, dst_pixels, row_bytes);
167 if (!result) {
168 FML_DLOG(ERROR) << "Failed to copy pixels at index " << image_index
169 << " (frame index: " << frame_index << ") of APNG.";
170 }
171 } else if (frame.frame_info->blend_mode ==
173 for (int y = 0; y < frame_info.height(); y++) {
174 auto src_row = frame.pixels.data() + y * frame_row_bytes;
175 auto dst_row = static_cast<uint8_t*>(pixels) +
176 (y + frame.y_offset) * row_bytes +
177 frame.x_offset * frame_info.bytesPerPixel();
178
179 for (int x = 0; x < frame_info.width(); x++) {
180 auto x_offset_bytes = x * frame_info.bytesPerPixel();
181
182 Pixel src = *reinterpret_cast<Pixel*>(src_row + x_offset_bytes);
183 Pixel* dst_p = reinterpret_cast<Pixel*>(dst_row + x_offset_bytes);
184 Pixel dst = *dst_p;
185
186 // Ensure both colors are premultiplied for the blending operation.
187 if (info.alphaType() == kUnpremul_SkAlphaType) {
188 dst.Premultiply();
189 }
190 if (frame_info.alphaType() == kUnpremul_SkAlphaType) {
191 src.Premultiply();
192 }
193
194 for (int i = 0; i < 4; i++) {
195 dst.channel[i] =
196 src.channel[i] + dst.channel[i] * (0xFF - src.GetAlpha()) / 0xFF;
197 }
198
199 // The final color is premultiplied. Unpremultiply to match the
200 // backdrop surface if necessary.
201 if (info.alphaType() == kUnpremul_SkAlphaType) {
202 dst.Unpremultiply();
203 }
204
205 *dst_p = dst;
206 }
207 }
208 }
209
210 return result;
211}
212
213std::unique_ptr<ImageGenerator> APNGImageGenerator::MakeFromData(
214 sk_sp<SkData> data) {
215 // Ensure the buffer is large enough to at least contain the PNG signature
216 // and a chunk header.
217 if (data->size() < sizeof(kPngSignature) + sizeof(ChunkHeader)) {
218 return nullptr;
219 }
220 // Validate the full PNG signature.
221 const uint8_t* data_p = static_cast<const uint8_t*>(data.get()->data());
222 if (memcmp(data_p, kPngSignature, sizeof(kPngSignature))) {
223 return nullptr;
224 }
225
226 // Validate the header chunk.
227 const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(data_p + 8);
228 if (!IsValidChunkHeader(data_p, data->size(), chunk) ||
229 chunk->get_data_length() != sizeof(ImageHeaderChunkData) ||
230 chunk->get_type() != kImageHeaderChunkType) {
231 return nullptr;
232 }
233
234 // Walk the chunks to find the "animation control" chunk. If an "image data"
235 // chunk is found first, this PNG is not animated.
236 while (true) {
237 chunk = GetNextChunk(data_p, data->size(), chunk);
238
239 if (chunk == nullptr) {
240 return nullptr;
241 }
242 if (chunk->get_type() == kImageDataChunkType) {
243 return nullptr;
244 }
245 if (chunk->get_type() == kAnimationControlChunkType) {
246 break;
247 }
248 }
249
250 const AnimationControlChunkData* animation_data =
251 CastChunkData<AnimationControlChunkData>(chunk);
252
253 // Extract the header signature and chunks to prepend when demuxing images.
254 std::optional<std::vector<uint8_t>> header;
255 const void* first_chunk_p;
256 std::tie(header, first_chunk_p) = ExtractHeader(data_p, data->size());
257 if (!header.has_value()) {
258 return nullptr;
259 }
260
261 // Demux the first image in the APNG chunk stream in order to interpret
262 // extent and blending info immediately.
263 std::optional<APNGImage> default_image;
264 const void* next_chunk_p;
265 std::tie(default_image, next_chunk_p) =
266 DemuxNextImage(data_p, data->size(), header.value(), first_chunk_p);
267 if (!default_image.has_value()) {
268 return nullptr;
269 }
270
271 unsigned int play_count = animation_data->get_num_plays();
272 if (play_count == 0) {
273 play_count = kInfinitePlayCount;
274 }
275
276 SkImageInfo image_info = default_image.value().codec->getInfo();
277 return std::unique_ptr<APNGImageGenerator>(
278 new APNGImageGenerator(data, image_info, std::move(default_image.value()),
279 animation_data->get_num_frames(), play_count,
280 next_chunk_p, header.value()));
281}
282
283bool APNGImageGenerator::IsValidChunkHeader(const void* buffer,
284 size_t size,
285 const ChunkHeader* chunk) {
286 // Ensure the chunk doesn't start before the beginning of the buffer.
287 if (reinterpret_cast<const uint8_t*>(chunk) <
288 static_cast<const uint8_t*>(buffer)) {
289 return false;
290 }
291
292 // Ensure the buffer is large enough to contain at least the chunk header.
293 if (reinterpret_cast<const uint8_t*>(chunk) + sizeof(ChunkHeader) >
294 static_cast<const uint8_t*>(buffer) + size) {
295 return false;
296 }
297
298 // Ensure the buffer is large enough to contain the chunk's given data size
299 // and CRC.
300 const uint8_t* chunk_end =
301 reinterpret_cast<const uint8_t*>(chunk) + GetChunkSize(chunk);
302 if (chunk_end > static_cast<const uint8_t*>(buffer) + size) {
303 return false;
304 }
305
306 // Ensure the 4-byte type only contains ISO 646 letters.
307 uint32_t type = chunk->get_type();
308 for (int i = 0; i < 4; i++) {
309 uint8_t c = type >> i * 8 & 0xFF;
310 if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) {
311 return false;
312 }
313 }
314
315 return true;
316}
317
318const APNGImageGenerator::ChunkHeader* APNGImageGenerator::GetNextChunk(
319 const void* buffer,
320 size_t size,
321 const ChunkHeader* current_chunk) {
322 FML_DCHECK((uint8_t*)current_chunk + sizeof(ChunkHeader) <=
323 (uint8_t*)buffer + size);
324
325 const ChunkHeader* next_chunk = reinterpret_cast<const ChunkHeader*>(
326 reinterpret_cast<const uint8_t*>(current_chunk) +
327 GetChunkSize(current_chunk));
328 if (!IsValidChunkHeader(buffer, size, next_chunk)) {
329 return nullptr;
330 }
331
332 return next_chunk;
333}
334
335std::pair<std::optional<std::vector<uint8_t>>, const void*>
336APNGImageGenerator::ExtractHeader(const void* buffer_p, size_t buffer_size) {
337 std::vector<uint8_t> result(sizeof(kPngSignature));
338 memcpy(result.data(), kPngSignature, sizeof(kPngSignature));
339
340 const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(
341 static_cast<const uint8_t*>(buffer_p) + sizeof(kPngSignature));
342 // Validate the first chunk to ensure it's safe to read.
343 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
344 return std::make_pair(std::nullopt, nullptr);
345 }
346
347 // Walk the chunks and copy in the non-APNG chunks until we come across a
348 // frame or image chunk.
349 do {
350 if (chunk->get_type() != kAnimationControlChunkType) {
351 size_t chunk_size = GetChunkSize(chunk);
352 result.resize(result.size() + chunk_size);
353 memcpy(result.data() + result.size() - chunk_size, chunk, chunk_size);
354 }
355
356 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
357 } while (chunk != nullptr && chunk->get_type() != kFrameControlChunkType &&
358 chunk->get_type() != kImageDataChunkType &&
359 chunk->get_type() != kFrameDataChunkType);
360
361 // nullptr means the end of the buffer was reached, which means there's no
362 // frame or image data, so just return nothing because the PNG isn't even
363 // valid.
364 if (chunk == nullptr) {
365 return std::make_pair(std::nullopt, nullptr);
366 }
367
368 return std::make_pair(result, chunk);
369}
370
371std::pair<std::optional<APNGImageGenerator::APNGImage>, const void*>
372APNGImageGenerator::DemuxNextImage(const void* buffer_p,
373 size_t buffer_size,
374 const std::vector<uint8_t>& header,
375 const void* chunk_p) {
376 const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(chunk_p);
377 // Validate the given chunk to ensure it's safe to read.
378 if (!IsValidChunkHeader(buffer_p, buffer_size, chunk)) {
379 return std::make_pair(std::nullopt, nullptr);
380 }
381
382 // Expect frame data to begin at fdAT or IDAT
383 if (chunk->get_type() != kFrameControlChunkType &&
384 chunk->get_type() != kImageDataChunkType) {
385 return std::make_pair(std::nullopt, nullptr);
386 }
387
388 APNGImage result;
389 const FrameControlChunkData* control_data = nullptr;
390
391 // The presence of an fcTL chunk is optional for the first (default) image
392 // of a PNG. Both cases are handled in APNGImage.
393 if (chunk->get_type() == kFrameControlChunkType) {
394 control_data = CastChunkData<FrameControlChunkData>(chunk);
395
396 ImageGenerator::FrameInfo frame_info;
397 switch (control_data->get_blend_op()) {
398 case 0: // APNG_BLEND_OP_SOURCE
399 frame_info.blend_mode = SkCodecAnimation::Blend::kSrc;
400 break;
401 case 1: // APNG_BLEND_OP_OVER
402 frame_info.blend_mode = SkCodecAnimation::Blend::kSrcOver;
403 break;
404 default:
405 return std::make_pair(std::nullopt, nullptr);
406 }
407
408 SkIRect frame_rect = SkIRect::MakeXYWH(
409 control_data->get_x_offset(), control_data->get_y_offset(),
410 control_data->get_width(), control_data->get_height());
411 switch (control_data->get_dispose_op()) {
412 case 0: // APNG_DISPOSE_OP_NONE
413 frame_info.disposal_method = SkCodecAnimation::DisposalMethod::kKeep;
414 break;
415 case 1: // APNG_DISPOSE_OP_BACKGROUND
416 frame_info.disposal_method =
418 frame_info.disposal_rect = frame_rect;
419 break;
420 case 2: // APNG_DISPOSE_OP_PREVIOUS
421 frame_info.disposal_method =
423 break;
424 default:
425 return std::make_pair(std::nullopt, nullptr);
426 }
427 uint16_t denominator = control_data->get_delay_den() == 0
428 ? 100
429 : control_data->get_delay_den();
430 frame_info.duration =
431 static_cast<int>(control_data->get_delay_num() * 1000.f / denominator);
432
433 result.frame_info = frame_info;
434 result.x_offset = control_data->get_x_offset();
435 result.y_offset = control_data->get_y_offset();
436 }
437
438 std::vector<const ChunkHeader*> image_chunks;
439 size_t chunk_space = 0;
440
441 // Walk the chunks until the next frame, end chunk, or an invalid chunk is
442 // reached, recording the chunks to copy along with their required space.
443 // TODO(bdero): Validate that IDAT/fdAT chunks are contiguous.
444 // TODO(bdero): Validate the acTL/fcTL/fdAT sequence number ordering.
445 do {
446 if (chunk->get_type() != kFrameControlChunkType) {
447 image_chunks.push_back(chunk);
448 chunk_space += GetChunkSize(chunk);
449
450 // fdAT chunks are converted into IDAT chunks when demuxed. The only
451 // difference between these chunk types is that fdAT has a 4 byte
452 // sequence number prepended to its data, so subtract that space from
453 // the buffer.
454 if (chunk->get_type() == kFrameDataChunkType) {
455 chunk_space -= 4;
456 }
457 }
458
459 chunk = GetNextChunk(buffer_p, buffer_size, chunk);
460 } while (chunk != nullptr && chunk->get_type() != kFrameControlChunkType &&
461 chunk->get_type() != kImageTrailerChunkType);
462
463 const uint8_t end_chunk[] = {0, 0, 0, 0, 'I', 'E',
464 'N', 'D', 0xAE, 0x42, 0x60, 0x82};
465
466 // Form a buffer for the new encoded PNG and copy the chunks in.
468 header.size() + chunk_space + sizeof(end_chunk));
469
470 {
471 uint8_t* write_cursor =
472 static_cast<uint8_t*>(new_png_buffer->writable_data());
473
474 // Copy the signature/header chunks
475 memcpy(write_cursor, header.data(), header.size());
476 // If this is a frame, override the width/height in the IHDR chunk.
477 if (control_data) {
478 ChunkHeader* ihdr_header =
479 reinterpret_cast<ChunkHeader*>(write_cursor + sizeof(kPngSignature));
480 ImageHeaderChunkData* ihdr_data = const_cast<ImageHeaderChunkData*>(
481 CastChunkData<ImageHeaderChunkData>(ihdr_header));
482 ihdr_data->set_width(control_data->get_width());
483 ihdr_data->set_height(control_data->get_height());
484 ihdr_header->UpdateChunkCrc32();
485 }
486 write_cursor += header.size();
487
488 // Copy the image data/ancillary chunks.
489 for (const ChunkHeader* c : image_chunks) {
490 if (c->get_type() == kFrameDataChunkType) {
491 // Write a new IDAT chunk header.
492 ChunkHeader* write_header =
493 reinterpret_cast<ChunkHeader*>(write_cursor);
494 write_header->set_data_length(c->get_data_length() - 4);
495 write_header->set_type(kImageDataChunkType);
496 write_cursor += sizeof(ChunkHeader);
497
498 // Copy all of the data except for the 4 byte sequence number at the
499 // beginning of the fdAT data.
500 memcpy(write_cursor,
501 reinterpret_cast<const uint8_t*>(c) + sizeof(ChunkHeader) + 4,
502 write_header->get_data_length());
503 write_cursor += write_header->get_data_length();
504
505 // Recompute the chunk CRC.
506 write_header->UpdateChunkCrc32();
507 write_cursor += 4;
508 } else {
509 size_t chunk_size = GetChunkSize(c);
510 memcpy(write_cursor, c, chunk_size);
511 write_cursor += chunk_size;
512 }
513 }
514
515 // Copy the trailer chunk.
516 memcpy(write_cursor, &end_chunk, sizeof(end_chunk));
517 }
518
519 SkCodec::Result header_parse_result;
521 &header_parse_result);
522 if (header_parse_result != SkCodec::Result::kSuccess) {
524 << "Failed to parse image header during APNG demux. SkCodec::Result: "
525 << header_parse_result;
526 return std::make_pair(std::nullopt, nullptr);
527 }
528
529 if (chunk->get_type() == kImageTrailerChunkType) {
530 chunk = nullptr;
531 }
532
533 return std::make_pair(std::optional<APNGImage>{std::move(result)}, chunk);
534}
535
536bool APNGImageGenerator::DemuxNextImageInternal() {
537 if (next_chunk_p_ == nullptr) {
538 return false;
539 }
540
541 std::optional<APNGImage> image;
542 const void* data_p = const_cast<void*>(data_.get()->data());
543 std::tie(image, next_chunk_p_) =
544 DemuxNextImage(data_p, data_->size(), header_, next_chunk_p_);
545 if (!image.has_value() || !image->frame_info.has_value()) {
546 return false;
547 }
548
549 auto last_frame_info = images_.back().frame_info;
550 if (!last_frame_info.has_value()) {
551 return false;
552 }
553
554 if (images_.size() > first_frame_index_ &&
555 (last_frame_info->disposal_method ==
557 last_frame_info->disposal_method ==
559 // Mark the required frame as the previous frame in all cases.
560 image->frame_info->required_frame = images_.size() - 1;
561 } else if (images_.size() > (first_frame_index_ + 1) &&
562 last_frame_info->disposal_method ==
564 // Mark the required frame as the last previous frame
565 // It is not valid if there are 2 or above frames set |disposal_method| to
566 // |kRestorePrevious|. But it also works in MultiFrameCodec.
567 image->frame_info->required_frame = images_.size() - 2;
568 }
569
570 // Calling SkCodec::getInfo at least once prior to decoding is mandatory.
571 SkImageInfo info = image.value().codec->getInfo();
572 FML_DCHECK(info.colorInfo() == image_info_.colorInfo());
573
574 images_.push_back(std::move(image.value()));
575
576 auto default_info = images_[0].codec->getInfo();
577 if (info.colorType() != default_info.colorType()) {
578 return false;
579 }
580 return true;
581}
582
583bool APNGImageGenerator::DemuxToImageIndex(unsigned int image_index) {
584 // If the requested image doesn't exist yet, demux more frames from the APNG
585 // stream.
586 if (image_index >= images_.size()) {
587 while (DemuxNextImageInternal() && image_index >= images_.size()) {
588 }
589
590 if (image_index >= images_.size()) {
591 // The chunk stream was exhausted before the image was found.
592 return false;
593 }
594 }
595
596 return true;
597}
598
599void APNGImageGenerator::ChunkHeader::UpdateChunkCrc32() {
600 uint32_t* crc_p =
601 reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(this) +
602 sizeof(ChunkHeader) + get_data_length());
603 *crc_p = fml::BigEndianToArch(ComputeChunkCrc32());
604}
605
606uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
607 // Exclude the length field at the beginning of the chunk header.
608 size_t length = sizeof(ChunkHeader) - 4 + get_data_length();
609 uint8_t* chunk_data_p = reinterpret_cast<uint8_t*>(this) + 4;
610 uint32_t crc = 0;
611
612 // zlib's crc32 can only take 16 bits at a time for the length, but PNG
613 // supports a 32 bit chunk length, so looping is necessary here.
614 // Note that crc32 is always called at least once, even if the chunk has an
615 // empty data section.
616 do {
617 uint16_t length16 = length;
618 if (length16 == 0 && length > 0) {
619 length16 = std::numeric_limits<uint16_t>::max();
620 }
621
622 crc = crc32(crc, chunk_data_p, length16);
623 length -= length16;
624 chunk_data_p += length16;
625 } while (length > 0);
626
627 return crc;
628}
629
630bool APNGImageGenerator::RenderDefaultImage(const SkImageInfo& info,
631 void* pixels,
632 size_t row_bytes) {
633 SkCodec::Result result = images_[0].codec->getPixels(info, pixels, row_bytes);
634 if (result != SkCodec::kSuccess) {
635 FML_DLOG(ERROR) << "Failed to decode the APNG's default/fallback image. "
636 "SkCodec::Result: "
637 << result;
638 return false;
639 }
640 return true;
641}
642
643} // namespace flutter
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
static uint32_t buffer_size(uint32_t offset, uint32_t maxAlignment)
kUnpremul_SkAlphaType
static std::unique_ptr< SkCodec > MakeFromStream(std::unique_ptr< SkStream >, SkSpan< const SkCodecs::Decoder > decoders, Result *=nullptr, SkPngChunkReader *=nullptr, SelectionPolicy selectionPolicy=SelectionPolicy::kPreferStillImage)
Definition SkCodec.cpp:163
@ kSuccess
Definition SkCodec.h:80
static sk_sp< SkData > MakeUninitialized(size_t length)
Definition SkData.cpp:116
static std::unique_ptr< SkMemoryStream > Make(sk_sp< SkData > data)
Definition SkStream.cpp:314
bool readPixels(const SkImageInfo &dstInfo, void *dstPixels, size_t dstRowBytes) const
Definition SkPixmap.h:592
sk_sp< SkImage > image
Definition examples.cpp:29
double frame
Definition examples.cpp:31
static const uint8_t buffer[]
GAsyncResult * result
#define FML_DLOG(severity)
Definition logging.h:102
#define FML_DCHECK(condition)
Definition logging.h:103
size_t length
double y
double x
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 switches.h:41
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition switches.h:259
constexpr T BigEndianToArch(T n)
Convert a known big endian value to match the endianness of the current architecture....
Definition endianness.h:59
static const char header[]
Definition skpbench.cpp:88
static constexpr SkIRect MakeXYWH(int32_t x, int32_t y, int32_t w, int32_t h)
Definition SkRect.h:104
int bytesPerPixel() const
int width() const
SkAlphaType alphaType() const
SkColorType colorType() const
int height() const
Info about a single frame in the context of a multi-frame image, useful for animation and blending.
#define ERROR(message)