Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkWuffsCodec.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
16#include "include/core/SkData.h"
21#include "include/core/SkRect.h"
24#include "include/core/SkSize.h"
30#include "modules/skcms/skcms.h"
33#include "src/codec/SkSampler.h"
35#include "src/core/SkDraw.h"
38
39#include <climits>
40#include <cstdint>
41#include <cstring>
42#include <memory>
43#include <utility>
44#include <vector>
45
46// Documentation on the Wuffs language and standard library (in general) and
47// its image decoding API (in particular) is at:
48//
49// - https://github.com/google/wuffs/tree/master/doc
50// - https://github.com/google/wuffs/blob/master/doc/std/image-decoders.md
51
52// Wuffs ships as a "single file C library" or "header file library" as per
53// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
54//
55// As we have not #define'd WUFFS_IMPLEMENTATION, the #include here is
56// including a header file, even though that file name ends in ".c".
57#if defined(WUFFS_IMPLEMENTATION)
58#error "SkWuffsCodec should not #define WUFFS_IMPLEMENTATION"
59#endif
60#include "wuffs-v0.3.c" // NO_G3_REWRITE
61// Commit count 2514 is Wuffs 0.3.0-alpha.4.
62#if WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT < 2514
63#error "Wuffs version is too old. Upgrade to the latest version."
64#endif
65
66#define SK_WUFFS_CODEC_BUFFER_SIZE 4096
67
68// Configuring a Skia build with
69// SK_WUFFS_FAVORS_PERFORMANCE_OVER_ADDITIONAL_MEMORY_SAFETY can improve decode
70// performance by some fixed amount (independent of the image size), which can
71// be a noticeable proportional improvement if the input is relatively small.
72//
73// The Wuffs library is still memory-safe either way, in that there are no
74// out-of-bounds reads or writes, and the library endeavours not to read
75// uninitialized memory. There are just fewer compiler-enforced guarantees
76// against reading uninitialized memory. For more detail, see
77// https://github.com/google/wuffs/blob/master/doc/note/initialization.md#partial-zero-initialization
78#if defined(SK_WUFFS_FAVORS_PERFORMANCE_OVER_ADDITIONAL_MEMORY_SAFETY)
79#define SK_WUFFS_INITIALIZE_FLAGS WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED
80#else
81#define SK_WUFFS_INITIALIZE_FLAGS WUFFS_INITIALIZE__DEFAULT_OPTIONS
82#endif
83
84static bool fill_buffer(wuffs_base__io_buffer* b, SkStream* s) {
85 b->compact();
86 size_t num_read = s->read(b->data.ptr + b->meta.wi, b->data.len - b->meta.wi);
87 b->meta.wi += num_read;
88 // We hard-code false instead of s->isAtEnd(). In theory, Skia's
89 // SkStream::isAtEnd() method has the same semantics as Wuffs'
90 // wuffs_base__io_buffer_meta::closed field. Specifically, both are false
91 // when reading from a network socket when all bytes *available right now*
92 // have been read but there might be more later.
93 //
94 // However, SkStream is designed around synchronous I/O. The SkStream::read
95 // method does not take a callback and, per its documentation comments, a
96 // read request for N bytes should block until a full N bytes are
97 // available. In practice, Blink's SkStream subclass builds on top of async
98 // I/O and cannot afford to block. While it satisfies "the letter of the
99 // law", in terms of what the C++ compiler needs, it does not satisfy "the
100 // spirit of the law". Its read() can return short without blocking and its
101 // isAtEnd() can return false positives.
102 //
103 // When closed is true, Wuffs treats incomplete input as a fatal error
104 // instead of a recoverable "short read" suspension. We therefore hard-code
105 // false and return kIncompleteInput (instead of kErrorInInput) up the call
106 // stack even if the SkStream isAtEnd. The caller usually has more context
107 // (more than what's in the SkStream) to differentiate the two, like this:
108 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.cc;l=115;drc=277dcc4d810ae4c0286d8af96d270ed9b686c5ff
109 b->meta.closed = false;
110 return num_read > 0;
111}
112
113static bool seek_buffer(wuffs_base__io_buffer* b, SkStream* s, uint64_t pos) {
114 // Try to re-position the io_buffer's meta.ri read-index first, which is
115 // cheaper than seeking in the backing SkStream.
116 if ((pos >= b->meta.pos) && (pos - b->meta.pos <= b->meta.wi)) {
117 b->meta.ri = pos - b->meta.pos;
118 return true;
119 }
120 // Seek in the backing SkStream.
121 if ((pos > SIZE_MAX) || (!s->seek(pos))) {
122 return false;
123 }
124 b->meta.wi = 0;
125 b->meta.ri = 0;
126 b->meta.pos = pos;
127 b->meta.closed = false;
128 return true;
129}
130
132 wuffs_base__animation_disposal w) {
133 switch (w) {
134 case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND:
136 case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS:
138 default:
140 }
141}
142
143static SkAlphaType to_alpha_type(bool opaque) {
145}
146
147static SkCodec::Result reset_and_decode_image_config(wuffs_gif__decoder* decoder,
148 wuffs_base__image_config* imgcfg,
149 wuffs_base__io_buffer* b,
150 SkStream* s) {
151 // Calling decoder->initialize will memset most or all of it to zero,
152 // depending on SK_WUFFS_INITIALIZE_FLAGS.
153 wuffs_base__status status =
154 decoder->initialize(sizeof__wuffs_gif__decoder(), WUFFS_VERSION, SK_WUFFS_INITIALIZE_FLAGS);
155 if (status.repr != nullptr) {
156 SkCodecPrintf("initialize: %s", status.message());
158 }
159
160 // See https://bugs.chromium.org/p/skia/issues/detail?id=12055
161 decoder->set_quirk_enabled(WUFFS_GIF__QUIRK_IGNORE_TOO_MUCH_PIXEL_DATA, true);
162
163 while (true) {
164 status = decoder->decode_image_config(imgcfg, b);
165 if (status.repr == nullptr) {
166 break;
167 } else if (status.repr != wuffs_base__suspension__short_read) {
168 SkCodecPrintf("decode_image_config: %s", status.message());
170 } else if (!fill_buffer(b, s)) {
172 }
173 }
174
175 // A GIF image's natural color model is indexed color: 1 byte per pixel,
176 // indexing a 256-element palette.
177 //
178 // For Skia, we override that to decode to 4 bytes per pixel, BGRA or RGBA.
179 uint32_t pixfmt = WUFFS_BASE__PIXEL_FORMAT__INVALID;
180 switch (kN32_SkColorType) {
182 pixfmt = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL;
183 break;
185 pixfmt = WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL;
186 break;
187 default:
189 }
190 if (imgcfg) {
191 imgcfg->pixcfg.set(pixfmt, WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, imgcfg->pixcfg.width(),
192 imgcfg->pixcfg.height());
193 }
194
195 return SkCodec::kSuccess;
196}
197
198// -------------------------------- Class definitions
199
200class SkWuffsCodec;
201
202class SkWuffsFrame final : public SkFrame {
203public:
204 SkWuffsFrame(wuffs_base__frame_config* fc);
205
206 uint64_t ioPosition() const;
207
208 // SkFrame overrides.
209 SkEncodedInfo::Alpha onReportedAlpha() const override;
210
211private:
212 uint64_t fIOPosition;
213 SkEncodedInfo::Alpha fReportedAlpha;
214
215 using INHERITED = SkFrame;
216};
217
218// SkWuffsFrameHolder is a trivial indirector that forwards its calls onto a
219// SkWuffsCodec. It is a separate class as SkWuffsCodec would otherwise
220// inherit from both SkCodec and SkFrameHolder, and Skia style discourages
221// multiple inheritance (e.g. with its "typedef Foo INHERITED" convention).
222class SkWuffsFrameHolder final : public SkFrameHolder {
223public:
225
226 void init(SkWuffsCodec* codec, int width, int height);
227
228 // SkFrameHolder overrides.
229 const SkFrame* onGetFrame(int i) const override;
230
231private:
232 const SkWuffsCodec* fCodec;
233
234 using INHERITED = SkFrameHolder;
235};
236
237class SkWuffsCodec final : public SkScalingCodec {
238public:
239 SkWuffsCodec(SkEncodedInfo&& encodedInfo,
240 std::unique_ptr<SkStream> stream,
241 bool canSeek,
242 std::unique_ptr<wuffs_gif__decoder, decltype(&sk_free)> dec,
243 std::unique_ptr<uint8_t, decltype(&sk_free)> workbuf_ptr,
244 size_t workbuf_len,
245 wuffs_base__image_config imgcfg,
246 wuffs_base__io_buffer iobuf);
247
248 const SkWuffsFrame* frame(int i) const;
249
250 std::unique_ptr<SkStream> getEncodedData() const override;
251
252private:
253 // SkCodec overrides.
255 Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, int*) override;
256 const SkFrameHolder* getFrameHolder() const override;
258 void* dst,
259 size_t rowBytes,
260 const SkCodec::Options& options) override;
261 Result onIncrementalDecode(int* rowsDecoded) override;
262 int onGetFrameCount() override;
263 bool onGetFrameInfo(int, FrameInfo*) const override;
264 int onGetRepetitionCount() override;
265
266 // Two separate implementations of onStartIncrementalDecode and
267 // onIncrementalDecode, named "one pass" and "two pass" decoding. One pass
268 // decoding writes directly from the Wuffs image decoder to the dst buffer
269 // (the dst argument to onStartIncrementalDecode). Two pass decoding first
270 // writes into an intermediate buffer, and then composites and transforms
271 // the intermediate buffer into the dst buffer.
272 //
273 // In the general case, we need the two pass decoder, because of Skia API
274 // features that Wuffs doesn't support (e.g. color correction, scaling,
275 // RGB565). But as an optimization, we use one pass decoding (it's faster
276 // and uses less memory) if applicable (see the assignment to
277 // fIncrDecOnePass that calculates when we can do so).
278 Result onStartIncrementalDecodeOnePass(const SkImageInfo& dstInfo,
279 uint8_t* dst,
280 size_t rowBytes,
282 uint32_t pixelFormat,
283 size_t bytesPerPixel);
284 Result onStartIncrementalDecodeTwoPass();
285 Result onIncrementalDecodeOnePass();
286 Result onIncrementalDecodeTwoPass();
287
288 void onGetFrameCountInternal();
289 Result seekFrame(int frameIndex);
290 Result resetDecoder();
291 const char* decodeFrameConfig();
292 const char* decodeFrame();
293 void updateNumFullyReceivedFrames();
294
295 SkWuffsFrameHolder fFrameHolder;
296 std::unique_ptr<SkStream> fPrivStream;
297 std::unique_ptr<uint8_t, decltype(&sk_free)> fWorkbufPtr;
298 size_t fWorkbufLen;
299
300 std::unique_ptr<wuffs_gif__decoder, decltype(&sk_free)> fDecoder;
301
302 const uint64_t fFirstFrameIOPosition;
303 wuffs_base__frame_config fFrameConfig;
304 wuffs_base__pixel_config fPixelConfig;
305 wuffs_base__pixel_buffer fPixelBuffer;
306 wuffs_base__io_buffer fIOBuffer;
307
308 // Incremental decoding state.
309 uint8_t* fIncrDecDst;
310 size_t fIncrDecRowBytes;
311 wuffs_base__pixel_blend fIncrDecPixelBlend;
312 bool fIncrDecOnePass;
313 bool fFirstCallToIncrementalDecode;
314
315 // Lazily allocated intermediate pixel buffer, for two pass decoding.
316 std::unique_ptr<uint8_t, decltype(&sk_free)> fTwoPassPixbufPtr;
317 size_t fTwoPassPixbufLen;
318
319 uint64_t fNumFullyReceivedFrames;
320 std::vector<SkWuffsFrame> fFrames;
321 bool fFramesComplete;
322
323 // If calling an fDecoder method returns an incomplete status, then
324 // fDecoder is suspended in a coroutine (i.e. waiting on I/O or halted on a
325 // non-recoverable error). To keep its internal proof-of-safety invariants
326 // consistent, there's only two things you can safely do with a suspended
327 // Wuffs object: resume the coroutine, or reset all state (memset to zero
328 // and start again).
329 //
330 // If fDecoderIsSuspended, and we aren't sure that we're going to resume
331 // the coroutine, then we will need to call this->resetDecoder before
332 // calling other fDecoder methods.
333 bool fDecoderIsSuspended;
334
335 uint8_t fBuffer[SK_WUFFS_CODEC_BUFFER_SIZE];
336
337 const bool fCanSeek;
338
340};
341
342// -------------------------------- SkWuffsFrame implementation
343
344SkWuffsFrame::SkWuffsFrame(wuffs_base__frame_config* fc)
345 : INHERITED(fc->index()),
346 fIOPosition(fc->io_position()),
347 fReportedAlpha(fc->opaque_within_bounds() ? SkEncodedInfo::kOpaque_Alpha
348 : SkEncodedInfo::kUnpremul_Alpha) {
349 wuffs_base__rect_ie_u32 r = fc->bounds();
350 this->setXYWH(r.min_incl_x, r.min_incl_y, r.width(), r.height());
352 this->setDuration(fc->duration() / WUFFS_BASE__FLICKS_PER_MILLISECOND);
353 this->setBlend(fc->overwrite_instead_of_blend() ? SkCodecAnimation::Blend::kSrc
355}
356
357uint64_t SkWuffsFrame::ioPosition() const {
358 return fIOPosition;
359}
360
362 return fReportedAlpha;
363}
364
365// -------------------------------- SkWuffsFrameHolder implementation
366
368 fCodec = codec;
369 // Initialize SkFrameHolder's (the superclass) fields.
372}
373
375 return fCodec->frame(i);
376}
377
378// -------------------------------- SkWuffsCodec implementation
379
381 std::unique_ptr<SkStream> stream,
382 bool canSeek,
383 std::unique_ptr<wuffs_gif__decoder, decltype(&sk_free)> dec,
384 std::unique_ptr<uint8_t, decltype(&sk_free)> workbuf_ptr,
385 size_t workbuf_len,
386 wuffs_base__image_config imgcfg,
387 wuffs_base__io_buffer iobuf)
388 : INHERITED(std::move(encodedInfo),
390 // Pass a nullptr SkStream to the SkCodec constructor. We
391 // manage the stream ourselves, as the default SkCodec behavior
392 // is too trigger-happy on rewinding the stream.
393 nullptr),
394 fFrameHolder(),
395 fPrivStream(std::move(stream)),
396 fWorkbufPtr(std::move(workbuf_ptr)),
397 fWorkbufLen(workbuf_len),
398 fDecoder(std::move(dec)),
399 fFirstFrameIOPosition(imgcfg.first_frame_io_position()),
400 fFrameConfig(wuffs_base__null_frame_config()),
401 fPixelConfig(imgcfg.pixcfg),
402 fPixelBuffer(wuffs_base__null_pixel_buffer()),
403 fIOBuffer(wuffs_base__empty_io_buffer()),
404 fIncrDecDst(nullptr),
405 fIncrDecRowBytes(0),
406 fIncrDecPixelBlend(WUFFS_BASE__PIXEL_BLEND__SRC),
407 fIncrDecOnePass(false),
408 fFirstCallToIncrementalDecode(false),
409 fTwoPassPixbufPtr(nullptr, &sk_free),
410 fTwoPassPixbufLen(0),
411 fNumFullyReceivedFrames(0),
412 fFramesComplete(false),
413 fDecoderIsSuspended(false),
414 fCanSeek(canSeek) {
415 fFrameHolder.init(this, imgcfg.pixcfg.width(), imgcfg.pixcfg.height());
416
417 // Initialize fIOBuffer's fields, copying any outstanding data from iobuf to
418 // fIOBuffer, as iobuf's backing array may not be valid for the lifetime of
419 // this SkWuffsCodec object, but fIOBuffer's backing array (fBuffer) is.
420 SkASSERT(iobuf.data.len == SK_WUFFS_CODEC_BUFFER_SIZE);
421 memmove(fBuffer, iobuf.data.ptr, iobuf.meta.wi);
422 fIOBuffer.data = wuffs_base__make_slice_u8(fBuffer, SK_WUFFS_CODEC_BUFFER_SIZE);
423 fIOBuffer.meta = iobuf.meta;
424}
425
427 if ((0 <= i) && (static_cast<size_t>(i) < fFrames.size())) {
428 return &fFrames[i];
429 }
430 return nullptr;
431}
432
436
438 void* dst,
439 size_t rowBytes,
440 const Options& options,
441 int* rowsDecoded) {
442 SkCodec::Result result = this->onStartIncrementalDecode(dstInfo, dst, rowBytes, options);
443 if (result != kSuccess) {
444 return result;
445 }
446 return this->onIncrementalDecode(rowsDecoded);
447}
448
450 return &fFrameHolder;
451}
452
454 void* dst,
455 size_t rowBytes,
456 const SkCodec::Options& options) {
457 if (!dst) {
459 }
460 if (options.fSubset) {
462 }
463 SkCodec::Result result = this->seekFrame(options.fFrameIndex);
464 if (result != SkCodec::kSuccess) {
465 return result;
466 }
467
468 const char* status = this->decodeFrameConfig();
469 if (status == wuffs_base__suspension__short_read) {
471 } else if (status != nullptr) {
472 SkCodecPrintf("decodeFrameConfig: %s", status);
474 }
475
476 uint32_t pixelFormat = WUFFS_BASE__PIXEL_FORMAT__INVALID;
477 size_t bytesPerPixel = 0;
478
479 switch (dstInfo.colorType()) {
481 pixelFormat = WUFFS_BASE__PIXEL_FORMAT__BGR_565;
482 bytesPerPixel = 2;
483 break;
485 pixelFormat = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL;
486 bytesPerPixel = 4;
487 break;
489 pixelFormat = WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL;
490 bytesPerPixel = 4;
491 break;
492 default:
493 break;
494 }
495
496 // We can use "one pass" decoding if we have a Skia pixel format that Wuffs
497 // supports...
498 fIncrDecOnePass = (pixelFormat != WUFFS_BASE__PIXEL_FORMAT__INVALID) &&
499 // ...and no color profile (as Wuffs does not support them)...
500 (!getEncodedInfo().profile()) &&
501 // ...and we use the identity transform (as Wuffs does
502 // not support scaling).
503 (this->dimensions() == dstInfo.dimensions());
504
505 result = fIncrDecOnePass ? this->onStartIncrementalDecodeOnePass(
506 dstInfo, static_cast<uint8_t*>(dst), rowBytes, options,
507 pixelFormat, bytesPerPixel)
508 : this->onStartIncrementalDecodeTwoPass();
509 if (result != SkCodec::kSuccess) {
510 return result;
511 }
512
513 fIncrDecDst = static_cast<uint8_t*>(dst);
514 fIncrDecRowBytes = rowBytes;
515 fFirstCallToIncrementalDecode = true;
516 return SkCodec::kSuccess;
517}
518
519SkCodec::Result SkWuffsCodec::onStartIncrementalDecodeOnePass(const SkImageInfo& dstInfo,
520 uint8_t* dst,
521 size_t rowBytes,
523 uint32_t pixelFormat,
524 size_t bytesPerPixel) {
525 wuffs_base__pixel_config pixelConfig;
526 pixelConfig.set(pixelFormat, WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, dstInfo.width(),
527 dstInfo.height());
528
529 wuffs_base__table_u8 table;
530 table.ptr = dst;
531 table.width = static_cast<size_t>(dstInfo.width()) * bytesPerPixel;
532 table.height = dstInfo.height();
533 table.stride = rowBytes;
534
535 wuffs_base__status status = fPixelBuffer.set_from_table(&pixelConfig, table);
536 if (status.repr != nullptr) {
537 SkCodecPrintf("set_from_table: %s", status.message());
539 }
540
541 // SRC is usually faster than SRC_OVER, but for a dependent frame, dst is
542 // assumed to hold the previous frame's pixels (after processing the
543 // DisposalMethod). For one-pass decoding, we therefore use SRC_OVER.
544 if ((options.fFrameIndex != 0) &&
545 (this->frame(options.fFrameIndex)->getRequiredFrame() != SkCodec::kNoFrame)) {
546 fIncrDecPixelBlend = WUFFS_BASE__PIXEL_BLEND__SRC_OVER;
547 } else {
549 fIncrDecPixelBlend = WUFFS_BASE__PIXEL_BLEND__SRC;
550 }
551
552 return SkCodec::kSuccess;
553}
554
555SkCodec::Result SkWuffsCodec::onStartIncrementalDecodeTwoPass() {
556 // Either re-use the previously allocated "two pass" pixel buffer (and
557 // memset to zero), or allocate (and zero initialize) a new one.
558 bool already_zeroed = false;
559
560 if (!fTwoPassPixbufPtr) {
561 uint64_t pixbuf_len = fPixelConfig.pixbuf_len();
562 void* pixbuf_ptr_raw = (pixbuf_len <= SIZE_MAX)
564 : nullptr;
565 if (!pixbuf_ptr_raw) {
567 }
568 fTwoPassPixbufPtr.reset(reinterpret_cast<uint8_t*>(pixbuf_ptr_raw));
569 fTwoPassPixbufLen = SkToSizeT(pixbuf_len);
570 already_zeroed = true;
571 }
572
573 wuffs_base__status status = fPixelBuffer.set_from_slice(
574 &fPixelConfig, wuffs_base__make_slice_u8(fTwoPassPixbufPtr.get(), fTwoPassPixbufLen));
575 if (status.repr != nullptr) {
576 SkCodecPrintf("set_from_slice: %s", status.message());
578 }
579
580 if (!already_zeroed) {
581 uint32_t src_bits_per_pixel = fPixelConfig.pixel_format().bits_per_pixel();
582 if ((src_bits_per_pixel == 0) || (src_bits_per_pixel % 8 != 0)) {
584 }
585 size_t src_bytes_per_pixel = src_bits_per_pixel / 8;
586
587 wuffs_base__rect_ie_u32 frame_rect = fFrameConfig.bounds();
588 wuffs_base__table_u8 pixels = fPixelBuffer.plane(0);
589
590 uint8_t* ptr = pixels.ptr + (frame_rect.min_incl_y * pixels.stride) +
591 (frame_rect.min_incl_x * src_bytes_per_pixel);
592 size_t len = frame_rect.width() * src_bytes_per_pixel;
593
594 // As an optimization, issue a single sk_bzero call, if possible.
595 // Otherwise, zero out each row separately.
596 if ((len == pixels.stride) && (frame_rect.min_incl_y < frame_rect.max_excl_y)) {
597 sk_bzero(ptr, len * (frame_rect.max_excl_y - frame_rect.min_incl_y));
598 } else {
599 for (uint32_t y = frame_rect.min_incl_y; y < frame_rect.max_excl_y; y++) {
600 sk_bzero(ptr, len);
601 ptr += pixels.stride;
602 }
603 }
604 }
605
606 fIncrDecPixelBlend = WUFFS_BASE__PIXEL_BLEND__SRC;
607 return SkCodec::kSuccess;
608}
609
611 if (!fIncrDecDst) {
613 }
614
615 if (rowsDecoded) {
616 *rowsDecoded = dstInfo().height();
617 }
618
620 fIncrDecOnePass ? this->onIncrementalDecodeOnePass() : this->onIncrementalDecodeTwoPass();
621 if (result == SkCodec::kSuccess) {
622 fIncrDecDst = nullptr;
623 fIncrDecRowBytes = 0;
624 fIncrDecPixelBlend = WUFFS_BASE__PIXEL_BLEND__SRC;
625 fIncrDecOnePass = false;
626 }
627 return result;
628}
629
630SkCodec::Result SkWuffsCodec::onIncrementalDecodeOnePass() {
631 const char* status = this->decodeFrame();
632 if (status != nullptr) {
633 if (status == wuffs_base__suspension__short_read) {
635 } else {
636 SkCodecPrintf("decodeFrame: %s", status);
638 }
639 }
640 return SkCodec::kSuccess;
641}
642
643SkCodec::Result SkWuffsCodec::onIncrementalDecodeTwoPass() {
645 const char* status = this->decodeFrame();
646 bool independent;
647 SkAlphaType alphaType;
648 const int index = options().fFrameIndex;
649 if (index == 0) {
650 independent = true;
651 alphaType = to_alpha_type(getEncodedInfo().opaque());
652 } else {
653 const SkWuffsFrame* f = this->frame(index);
654 independent = f->getRequiredFrame() == SkCodec::kNoFrame;
655 alphaType = to_alpha_type(f->reportedAlpha() == SkEncodedInfo::kOpaque_Alpha);
656 }
657 if (status != nullptr) {
658 if (status == wuffs_base__suspension__short_read) {
660 } else {
661 SkCodecPrintf("decodeFrame: %s", status);
663 }
664
665 if (!independent) {
666 // For a dependent frame, we cannot blend the partial result, since
667 // that will overwrite the contribution from prior frames.
668 return result;
669 }
670 }
671
672 uint32_t src_bits_per_pixel = fPixelBuffer.pixcfg.pixel_format().bits_per_pixel();
673 if ((src_bits_per_pixel == 0) || (src_bits_per_pixel % 8 != 0)) {
675 }
676 size_t src_bytes_per_pixel = src_bits_per_pixel / 8;
677
678 wuffs_base__rect_ie_u32 frame_rect = fFrameConfig.bounds();
679 if (fFirstCallToIncrementalDecode) {
680 if (frame_rect.width() > (SIZE_MAX / src_bytes_per_pixel)) {
682 }
683
684 auto bounds = SkIRect::MakeLTRB(frame_rect.min_incl_x, frame_rect.min_incl_y,
685 frame_rect.max_excl_x, frame_rect.max_excl_y);
686
687 // If the frame rect does not fill the output, ensure that those pixels are not
688 // left uninitialized.
689 if (independent && (bounds != this->bounds() || result != kSuccess)) {
690 SkSampler::Fill(dstInfo(), fIncrDecDst, fIncrDecRowBytes, options().fZeroInitialized);
691 }
692 fFirstCallToIncrementalDecode = false;
693 } else {
694 // Existing clients intend to only show frames beyond the first if they
695 // are complete (based on FrameInfo::fFullyReceived), since it might
696 // look jarring to draw a partial frame over an existing frame. If they
697 // changed their behavior and expected to continue decoding a partial
698 // frame after the first one, we'll need to update our blending code.
699 // Otherwise, if the frame were interlaced and not independent, the
700 // second pass may have an overlapping dirty_rect with the first,
701 // resulting in blending with the first pass.
702 SkASSERT(index == 0);
703 }
704
705 // If the frame's dirty rect is empty, no need to swizzle.
706 wuffs_base__rect_ie_u32 dirty_rect = fDecoder->frame_dirty_rect();
707 if (!dirty_rect.is_empty()) {
708 wuffs_base__table_u8 pixels = fPixelBuffer.plane(0);
709
710 // The Wuffs model is that the dst buffer is the image, not the frame.
711 // The expectation is that you allocate the buffer once, but re-use it
712 // for the N frames, regardless of each frame's top-left co-ordinate.
713 //
714 // To get from the start (in the X-direction) of the image to the start
715 // of the dirty_rect, we adjust s by (dirty_rect.min_incl_x * src_bytes_per_pixel).
716 uint8_t* s = pixels.ptr + (dirty_rect.min_incl_y * pixels.stride) +
717 (dirty_rect.min_incl_x * src_bytes_per_pixel);
718
719 // Currently, this is only used for GIF, which will never have an ICC profile. When it is
720 // used for other formats that might have one, we will need to transform from profiles that
721 // do not have corresponding SkColorSpaces.
723
724 auto srcInfo =
725 getInfo().makeWH(dirty_rect.width(), dirty_rect.height()).makeAlphaType(alphaType);
727 src.installPixels(srcInfo, s, pixels.stride);
729 if (independent) {
730 paint.setBlendMode(SkBlendMode::kSrc);
731 }
732
733 SkDraw draw;
734 draw.fDst.reset(dstInfo(), fIncrDecDst, fIncrDecRowBytes);
736 SkRect::Make(this->dstInfo().dimensions()));
737 draw.fCTM = &matrix;
739 draw.fRC = &rc;
740
741 SkMatrix translate = SkMatrix::Translate(dirty_rect.min_incl_x, dirty_rect.min_incl_y);
742 draw.drawBitmap(src, translate, nullptr, SkSamplingOptions(), paint);
743 }
744
745 if (result == SkCodec::kSuccess) {
746 // On success, we are done using the "two pass" pixel buffer for this
747 // frame. We have the option of releasing its memory, but there is a
748 // trade-off. If decoding a subsequent frame will also need "two pass"
749 // decoding, it would have to re-allocate the buffer instead of just
750 // re-using it. On the other hand, if there is no subsequent frame, and
751 // the SkWuffsCodec object isn't deleted soon, then we are holding
752 // megabytes of memory longer than we need to.
753 //
754 // For example, when the Chromium web browser decodes the <img> tags in
755 // a HTML page, the SkCodec object can live until navigating away from
756 // the page, which can be much longer than when the pixels are fully
757 // decoded, especially for a still (non-animated) image. Even for
758 // looping animations, caching the decoded frames (at the higher HTML
759 // renderer layer) may mean that each frame is only decoded once (at
760 // the lower SkCodec layer), in sequence.
761 //
762 // The heuristic we use here is to free the memory if we have decoded
763 // the last frame of the animation (or, for still images, the only
764 // frame). The output of the next decode request (if any) should be the
765 // same either way, but the steady state memory use should hopefully be
766 // lower than always keeping the fTwoPassPixbufPtr buffer up until the
767 // SkWuffsCodec destructor runs.
768 //
769 // This only applies to "two pass" decoding. "One pass" decoding does
770 // not allocate, free or otherwise use fTwoPassPixbufPtr.
771 if (fFramesComplete && (static_cast<size_t>(options().fFrameIndex) == fFrames.size() - 1)) {
772 fTwoPassPixbufPtr.reset(nullptr);
773 fTwoPassPixbufLen = 0;
774 }
775 }
776
777 return result;
778}
779
781 if (!fCanSeek) {
782 return 1;
783 }
784
785 // It is valid, in terms of the SkCodec API, to call SkCodec::getFrameCount
786 // while in an incremental decode (after onStartIncrementalDecode returns
787 // and before onIncrementalDecode returns kSuccess).
788 //
789 // We should not advance the SkWuffsCodec' stream while doing so, even
790 // though other SkCodec implementations can return increasing values from
791 // onGetFrameCount when given more data. If we tried to do so, the
792 // subsequent resume of the incremental decode would continue reading from
793 // a different position in the I/O stream, leading to an incorrect error.
794 //
795 // Other SkCodec implementations can move the stream forward during
796 // onGetFrameCount because they assume that the stream is rewindable /
797 // seekable. For example, an alternative GIF implementation may choose to
798 // store, for each frame walked past when merely counting the number of
799 // frames, the I/O position of each of the frame's GIF data blocks. (A GIF
800 // frame's compressed data can have multiple data blocks, each at most 255
801 // bytes in length). Obviously, this can require O(numberOfFrames) extra
802 // memory to store these I/O positions. The constant factor is small, but
803 // it's still O(N), not O(1).
804 //
805 // Wuffs and SkWuffsCodec try to minimize relying on the rewindable /
806 // seekable assumption. By design, Wuffs per se aims for O(1) memory use
807 // (after any pixel buffers are allocated) instead of O(N), and its I/O
808 // type, wuffs_base__io_buffer, is not necessarily rewindable or seekable.
809 //
810 // The Wuffs API provides a limited, optional form of seeking, to the start
811 // of an animation frame's data, but does not provide arbitrary save and
812 // load of its internal state whilst in the middle of an animation frame.
813 bool incrementalDecodeIsInProgress = fIncrDecDst != nullptr;
814
815 if (!fFramesComplete && !incrementalDecodeIsInProgress) {
816 this->onGetFrameCountInternal();
817 this->updateNumFullyReceivedFrames();
818 }
819 return fFrames.size();
820}
821
822void SkWuffsCodec::onGetFrameCountInternal() {
823 size_t n = fFrames.size();
824 int i = n ? n - 1 : 0;
825 if (this->seekFrame(i) != SkCodec::kSuccess) {
826 return;
827 }
828
829 // Iterate through the frames, converting from Wuffs'
830 // wuffs_base__frame_config type to Skia's SkWuffsFrame type.
831 for (; i < INT_MAX; i++) {
832 const char* status = this->decodeFrameConfig();
833 if (status == nullptr) {
834 // No-op.
835 } else if (status == wuffs_base__note__end_of_data) {
836 break;
837 } else {
838 return;
839 }
840
841 if (static_cast<size_t>(i) < fFrames.size()) {
842 continue;
843 }
844 fFrames.emplace_back(&fFrameConfig);
845 SkWuffsFrame* f = &fFrames[fFrames.size() - 1];
846 fFrameHolder.setAlphaAndRequiredFrame(f);
847 }
848
849 fFramesComplete = true;
850}
851
853 if (!fCanSeek) {
854 // We haven't read forward in the stream, so this info isn't available.
855 return false;
856 }
857
858 const SkWuffsFrame* f = this->frame(i);
859 if (!f) {
860 return false;
861 }
862 if (frameInfo) {
863 f->fillIn(frameInfo, static_cast<uint64_t>(i) < this->fNumFullyReceivedFrames);
864 }
865 return true;
866}
867
869 // Convert from Wuffs's loop count to Skia's repeat count. Wuffs' uint32_t
870 // number is how many times to play the loop. Skia's int number is how many
871 // times to play the loop *after the first play*. Wuffs and Skia use 0 and
872 // kRepetitionCountInfinite respectively to mean loop forever.
873 uint32_t n = fDecoder->num_animation_loops();
874 if (n == 0) {
876 }
877 n--;
878 return n < INT_MAX ? n : INT_MAX;
879}
880
881SkCodec::Result SkWuffsCodec::seekFrame(int frameIndex) {
882 if (fDecoderIsSuspended) {
883 SkCodec::Result res = this->resetDecoder();
884 if (res != SkCodec::kSuccess) {
885 return res;
886 }
887 }
888
889 uint64_t pos = 0;
890 if (frameIndex < 0) {
892 } else if (frameIndex == 0) {
893 pos = fFirstFrameIOPosition;
894 } else if (static_cast<size_t>(frameIndex) < fFrames.size()) {
895 pos = fFrames[frameIndex].ioPosition();
896 } else {
898 }
899
900 if (!seek_buffer(&fIOBuffer, fPrivStream.get(), pos)) {
902 }
903 wuffs_base__status status =
904 fDecoder->restart_frame(frameIndex, fIOBuffer.reader_io_position());
905 if (status.repr != nullptr) {
907 }
908 return SkCodec::kSuccess;
909}
910
911SkCodec::Result SkWuffsCodec::resetDecoder() {
912 if (!fPrivStream->rewind()) {
914 }
915 fIOBuffer.meta = wuffs_base__empty_io_buffer_meta();
916
918 reset_and_decode_image_config(fDecoder.get(), nullptr, &fIOBuffer, fPrivStream.get());
921 } else if (result != SkCodec::kSuccess) {
922 return result;
923 }
924
925 fDecoderIsSuspended = false;
926 return SkCodec::kSuccess;
927}
928
929const char* SkWuffsCodec::decodeFrameConfig() {
930 while (true) {
931 wuffs_base__status status =
932 fDecoder->decode_frame_config(&fFrameConfig, &fIOBuffer);
933 if ((status.repr == wuffs_base__suspension__short_read) &&
934 fill_buffer(&fIOBuffer, fPrivStream.get())) {
935 continue;
936 }
937 fDecoderIsSuspended = !status.is_complete();
938 this->updateNumFullyReceivedFrames();
939 return status.repr;
940 }
941}
942
943const char* SkWuffsCodec::decodeFrame() {
944 while (true) {
945 wuffs_base__status status = fDecoder->decode_frame(
946 &fPixelBuffer, &fIOBuffer, fIncrDecPixelBlend,
947 wuffs_base__make_slice_u8(fWorkbufPtr.get(), fWorkbufLen), nullptr);
948 if ((status.repr == wuffs_base__suspension__short_read) &&
949 fill_buffer(&fIOBuffer, fPrivStream.get())) {
950 continue;
951 }
952 fDecoderIsSuspended = !status.is_complete();
953 this->updateNumFullyReceivedFrames();
954 return status.repr;
955 }
956}
957
958void SkWuffsCodec::updateNumFullyReceivedFrames() {
959 // num_decoded_frames's return value, n, can change over time, both up and
960 // down, as we seek back and forth in the underlying stream.
961 // fNumFullyReceivedFrames is the highest n we've seen.
962 uint64_t n = fDecoder->num_decoded_frames();
963 if (fNumFullyReceivedFrames < n) {
964 fNumFullyReceivedFrames = n;
965 }
966}
967
968// We cannot use the SkCodec implementation since we pass nullptr to the superclass out of
969// an abundance of caution w/r to rewinding the stream.
970std::unique_ptr<SkStream> SkWuffsCodec::getEncodedData() const {
971 SkASSERT(fPrivStream);
972 return fPrivStream->duplicate();
973}
974
975namespace SkGifDecoder {
976
977bool IsGif(const void* buf, size_t bytesRead) {
978 constexpr const char* gif_ptr = "GIF8";
979 constexpr size_t gif_len = 4;
980 return (bytesRead >= gif_len) && (memcmp(buf, gif_ptr, gif_len) == 0);
981}
982
983std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream> stream,
984 SkCodec::SelectionPolicy selectionPolicy,
987 if (!stream) {
989 return nullptr;
990 }
991
992 bool canSeek = stream->hasPosition() && stream->hasLength();
993
994 if (selectionPolicy != SkCodec::SelectionPolicy::kPreferStillImage) {
995 // Some clients (e.g. Android) need to be able to seek the stream, but may
996 // not provide a seekable stream. Copy the stream to one that can seek.
997 if (!canSeek) {
998 auto data = SkCopyStreamToData(stream.get());
999 stream = std::make_unique<SkMemoryStream>(std::move(data));
1000 canSeek = true;
1001 }
1002 }
1003
1005 wuffs_base__io_buffer iobuf =
1006 wuffs_base__make_io_buffer(wuffs_base__make_slice_u8(buffer, SK_WUFFS_CODEC_BUFFER_SIZE),
1007 wuffs_base__empty_io_buffer_meta());
1008 wuffs_base__image_config imgcfg = wuffs_base__null_image_config();
1009
1010 // Wuffs is primarily a C library, not a C++ one. Furthermore, outside of
1011 // the wuffs_base__etc types, the sizeof a file format specific type like
1012 // GIF's wuffs_gif__decoder can vary between Wuffs versions. If p is of
1013 // type wuffs_gif__decoder*, then the supported API treats p as a pointer
1014 // to an opaque type: a private implementation detail. The API is always
1015 // "set_foo(p, etc)" and not "p->foo = etc".
1016 //
1017 // See https://en.wikipedia.org/wiki/Opaque_pointer#C
1018 //
1019 // Thus, we don't use C++'s new operator (which requires knowing the sizeof
1020 // the struct at compile time). Instead, we use sk_malloc_canfail, with
1021 // sizeof__wuffs_gif__decoder returning the appropriate value for the
1022 // (statically or dynamically) linked version of the Wuffs library.
1023 //
1024 // As a C (not C++) library, none of the Wuffs types have constructors or
1025 // destructors.
1026 //
1027 // In RAII style, we can still use std::unique_ptr with these pointers, but
1028 // we pair the pointer with sk_free instead of C++'s delete.
1029 void* decoder_raw = sk_malloc_canfail(sizeof__wuffs_gif__decoder());
1030 if (!decoder_raw) {
1032 return nullptr;
1033 }
1034 std::unique_ptr<wuffs_gif__decoder, decltype(&sk_free)> decoder(
1035 reinterpret_cast<wuffs_gif__decoder*>(decoder_raw), &sk_free);
1036
1037 SkCodec::Result reset_result =
1038 reset_and_decode_image_config(decoder.get(), &imgcfg, &iobuf, stream.get());
1039 if (reset_result != SkCodec::kSuccess) {
1040 *result = reset_result;
1041 return nullptr;
1042 }
1043
1044 uint32_t width = imgcfg.pixcfg.width();
1045 uint32_t height = imgcfg.pixcfg.height();
1046 if ((width == 0) || (width > INT_MAX) || (height == 0) || (height > INT_MAX)) {
1048 return nullptr;
1049 }
1050
1051 uint64_t workbuf_len = decoder->workbuf_len().max_incl;
1052 void* workbuf_ptr_raw = nullptr;
1053 if (workbuf_len) {
1054 workbuf_ptr_raw = workbuf_len <= SIZE_MAX ? sk_malloc_canfail(workbuf_len) : nullptr;
1055 if (!workbuf_ptr_raw) {
1057 return nullptr;
1058 }
1059 }
1060 std::unique_ptr<uint8_t, decltype(&sk_free)> workbuf_ptr(
1061 reinterpret_cast<uint8_t*>(workbuf_ptr_raw), &sk_free);
1062
1064 (imgcfg.pixcfg.pixel_format().repr == WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL)
1066 : SkEncodedInfo::kRGBA_Color;
1067
1068 // In Skia's API, the alpha we calculate here and return is only for the
1069 // first frame.
1070 SkEncodedInfo::Alpha alpha = imgcfg.first_frame_is_opaque() ? SkEncodedInfo::kOpaque_Alpha
1072
1073 SkEncodedInfo encodedInfo = SkEncodedInfo::Make(width, height, color, alpha, 8);
1074
1076 return std::unique_ptr<SkCodec>(new SkWuffsCodec(std::move(encodedInfo), std::move(stream),
1077 canSeek,
1078 std::move(decoder), std::move(workbuf_ptr),
1079 workbuf_len, imgcfg, iobuf));
1080}
1081
1082std::unique_ptr<SkCodec> Decode(std::unique_ptr<SkStream> stream,
1083 SkCodec::Result* outResult,
1084 SkCodecs::DecodeContext ctx) {
1085 SkCodec::Result resultStorage;
1086 if (!outResult) {
1087 outResult = &resultStorage;
1088 }
1090 if (ctx) {
1091 policy = *static_cast<SkCodec::SelectionPolicy*>(ctx);
1092 }
1093 return MakeFromStream(std::move(stream), policy, outResult);
1094}
1095
1096std::unique_ptr<SkCodec> Decode(sk_sp<SkData> data,
1097 SkCodec::Result* outResult,
1098 SkCodecs::DecodeContext ctx) {
1099 if (!data) {
1100 if (outResult) {
1101 *outResult = SkCodec::kInvalidInput;
1102 }
1103 return nullptr;
1104 }
1105 return Decode(SkMemoryStream::Make(std::move(data)), outResult, ctx);
1106}
1107} // namespace SkGifDecoder
1108
const char * options
SkPoint pos
SkColor4f color
SkAlphaType
Definition SkAlphaType.h:26
@ kOpaque_SkAlphaType
pixel is opaque
Definition SkAlphaType.h:28
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkCodecPrintf(...)
Definition SkCodecPriv.h:23
static bool independent(const SkFrame &frame)
Definition SkCodec.cpp:922
@ kBGRA_8888_SkColorType
pixel with 8 bits for blue, green, red, alpha; in 32-bit word
Definition SkColorType.h:26
@ kRGB_565_SkColorType
pixel with 5 bits red, 6 bits green, 5 bits blue, in 16-bit word
Definition SkColorType.h:22
@ kRGBA_8888_SkColorType
pixel with 8 bits for red, green, blue, alpha; in 32-bit word
Definition SkColorType.h:24
SkEncodedImageFormat
SK_API void * sk_malloc_flags(size_t size, unsigned flags)
static void sk_bzero(void *buffer, size_t size)
Definition SkMalloc.h:105
SK_API void sk_free(void *)
static void * sk_malloc_canfail(size_t size)
Definition SkMalloc.h:93
@ SK_MALLOC_ZERO_INITIALIZE
Definition SkMalloc.h:34
sk_sp< SkData > SkCopyStreamToData(SkStream *stream)
Definition SkStream.cpp:937
constexpr size_t SkToSizeT(S x)
Definition SkTo.h:31
static SkCodecAnimation::DisposalMethod wuffs_disposal_to_skia_disposal(wuffs_base__animation_disposal w)
static SkCodec::Result reset_and_decode_image_config(wuffs_gif__decoder *decoder, wuffs_base__image_config *imgcfg, wuffs_base__io_buffer *b, SkStream *s)
static SkAlphaType to_alpha_type(bool opaque)
static bool seek_buffer(wuffs_base__io_buffer *b, SkStream *s, uint64_t pos)
#define SK_WUFFS_CODEC_BUFFER_SIZE
#define SK_WUFFS_INITIALIZE_FLAGS
static bool fill_buffer(wuffs_base__io_buffer *b, SkStream *s)
SI F table(const skcms_Curve *curve, F v)
static void draw(SkCanvas *canvas, SkRect &target, int x, int y)
Definition aaclip.cpp:27
SkISize dimensions() const
Definition SkCodec.h:230
const SkImageInfo & dstInfo() const
Definition SkCodec.h:878
SkStream * stream()
Definition SkCodec.h:865
SkIRect bounds() const
Definition SkCodec.h:231
const SkEncodedInfo & getEncodedInfo() const
Definition SkCodec.h:788
@ kIncompleteInput
Definition SkCodec.h:84
@ kInvalidInput
Definition SkCodec.h:109
@ kInvalidParameters
Definition SkCodec.h:105
@ kInternalError
Definition SkCodec.h:118
@ kUnimplemented
Definition SkCodec.h:123
@ kSuccess
Definition SkCodec.h:80
@ kErrorInInput
Definition SkCodec.h:91
SelectionPolicy
Definition SkCodec.h:136
static constexpr int kRepetitionCountInfinite
Definition SkCodec.h:759
static constexpr int kNoFrame
Definition SkCodec.h:650
SkImageInfo getInfo() const
Definition SkCodec.h:228
const Options & options() const
Definition SkCodec.h:880
const SkRasterClip * fRC
Definition SkDrawBase.h:154
SkPixmap fDst
Definition SkDrawBase.h:151
const SkMatrix * fCTM
Definition SkDrawBase.h:153
void setAlphaAndRequiredFrame(SkFrame *)
Definition SkCodec.cpp:972
void setBlend(SkCodecAnimation::Blend blend)
void setDuration(int duration)
void setDisposalMethod(SkCodecAnimation::DisposalMethod disposalMethod)
void setXYWH(int x, int y, int width, int height)
static SkMatrix RectToRect(const SkRect &src, const SkRect &dst, ScaleToFit mode=kFill_ScaleToFit)
Definition SkMatrix.h:157
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition SkMatrix.h:91
static std::unique_ptr< SkMemoryStream > Make(sk_sp< SkData > data)
Definition SkStream.cpp:314
void reset()
Definition SkPixmap.cpp:32
static void Fill(const SkImageInfo &info, void *dst, size_t rowBytes, SkCodec::ZeroInitialized zeroInit)
Definition SkSampler.cpp:20
const SkWuffsFrame * frame(int i) const
int onGetFrameCount() override
Result onIncrementalDecode(int *rowsDecoded) override
bool onGetFrameInfo(int, FrameInfo *) const override
Result onStartIncrementalDecode(const SkImageInfo &dstInfo, void *dst, size_t rowBytes, const SkCodec::Options &options) override
int onGetRepetitionCount() override
Result onGetPixels(const SkImageInfo &, void *, size_t, const Options &, int *) override
SkEncodedImageFormat onGetEncodedFormat() const override
SkWuffsCodec(SkEncodedInfo &&encodedInfo, std::unique_ptr< SkStream > stream, bool canSeek, std::unique_ptr< wuffs_gif__decoder, decltype(&sk_free)> dec, std::unique_ptr< uint8_t, decltype(&sk_free)> workbuf_ptr, size_t workbuf_len, wuffs_base__image_config imgcfg, wuffs_base__io_buffer iobuf)
std::unique_ptr< SkStream > getEncodedData() const override
const SkFrameHolder * getFrameHolder() const override
const SkFrame * onGetFrame(int i) const override
void init(SkWuffsCodec *codec, int width, int height)
SkWuffsFrame(wuffs_base__frame_config *fc)
SkEncodedInfo::Alpha onReportedAlpha() const override
uint64_t ioPosition() const
const Paint & paint
double frame
Definition examples.cpp:31
static bool b
struct MyStruct s
static const uint8_t buffer[]
GAsyncResult * result
double y
SK_API std::unique_ptr< SkCodec > Decode(std::unique_ptr< SkStream >, SkCodec::Result *, SkCodecs::DecodeContext=nullptr)
SK_API bool IsGif(const void *, size_t)
unsigned useCenter Optional< SkMatrix > matrix
Definition SkRecords.h:258
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
Definition ref_ptr.h:256
SkScalar w
int32_t height
int32_t width
@ skcms_PixelFormat_RGBA_8888
const SkIRect * fSubset
Definition SkCodec.h:347
ZeroInitialized fZeroInitialized
Definition SkCodec.h:329
const skcms_ICCProfile * profile() const
static SkEncodedInfo Make(int width, int height, Color color, Alpha alpha, int bitsPerComponent)
static constexpr SkIRect MakeLTRB(int32_t l, int32_t t, int32_t r, int32_t b)
Definition SkRect.h:91
static constexpr SkIRect MakeSize(const SkISize &size)
Definition SkRect.h:66
SkImageInfo makeWH(int newWidth, int newHeight) const
SkImageInfo makeAlphaType(SkAlphaType newAlphaType) const
SkISize dimensions() const
int width() const
SkColorType colorType() const
int height() const
static SkRect Make(const SkISize &size)
Definition SkRect.h:669