• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 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 
8 #include "SkBmpCodec.h"
9 #include "SkCodec.h"
10 #include "SkCodecPriv.h"
11 #include "SkColorSpace.h"
12 #include "SkColorSpaceXform_Base.h"
13 #include "SkData.h"
14 #include "SkFrameHolder.h"
15 #include "SkGifCodec.h"
16 #include "SkHalf.h"
17 #ifdef SK_HAS_HEIF_LIBRARY
18 #include "SkHeifCodec.h"
19 #endif
20 #include "SkIcoCodec.h"
21 #include "SkJpegCodec.h"
22 #ifdef SK_HAS_PNG_LIBRARY
23 #include "SkPngCodec.h"
24 #endif
25 #include "SkRawCodec.h"
26 #include "SkStream.h"
27 #include "SkWbmpCodec.h"
28 #include "SkWebpCodec.h"
29 
30 struct DecoderProc {
31     bool (*IsFormat)(const void*, size_t);
32     SkCodec* (*NewFromStream)(SkStream*, SkCodec::Result*);
33 };
34 
35 static const DecoderProc gDecoderProcs[] = {
36 #ifdef SK_HAS_JPEG_LIBRARY
37     { SkJpegCodec::IsJpeg, SkJpegCodec::NewFromStream },
38 #endif
39 #ifdef SK_HAS_WEBP_LIBRARY
40     { SkWebpCodec::IsWebp, SkWebpCodec::NewFromStream },
41 #endif
42     { SkGifCodec::IsGif, SkGifCodec::NewFromStream },
43 #ifdef SK_HAS_PNG_LIBRARY
44     { SkIcoCodec::IsIco, SkIcoCodec::NewFromStream },
45 #endif
46     { SkBmpCodec::IsBmp, SkBmpCodec::NewFromStream },
47     { SkWbmpCodec::IsWbmp, SkWbmpCodec::NewFromStream },
48 #ifdef SK_HAS_HEIF_LIBRARY
49     { SkHeifCodec::IsHeif, SkHeifCodec::NewFromStream },
50 #endif
51 };
52 
NewFromStream(SkStream * stream,Result * outResult,SkPngChunkReader * chunkReader)53 SkCodec* SkCodec::NewFromStream(SkStream* stream,
54         Result* outResult, SkPngChunkReader* chunkReader) {
55     Result resultStorage;
56     if (!outResult) {
57         outResult = &resultStorage;
58     }
59 
60     if (!stream) {
61         *outResult = kInvalidInput;
62         return nullptr;
63     }
64 
65     std::unique_ptr<SkStream> streamDeleter(stream);
66 
67     constexpr size_t bytesToRead = MinBufferedBytesNeeded();
68 
69     char buffer[bytesToRead];
70     size_t bytesRead = stream->peek(buffer, bytesToRead);
71 
72     // It is also possible to have a complete image less than bytesToRead bytes
73     // (e.g. a 1 x 1 wbmp), meaning peek() would return less than bytesToRead.
74     // Assume that if bytesRead < bytesToRead, but > 0, the stream is shorter
75     // than bytesToRead, so pass that directly to the decoder.
76     // It also is possible the stream uses too small a buffer for peeking, but
77     // we trust the caller to use a large enough buffer.
78 
79     if (0 == bytesRead) {
80         // TODO: After implementing peek in CreateJavaOutputStreamAdaptor.cpp, this
81         // printf could be useful to notice failures.
82         // SkCodecPrintf("Encoded image data failed to peek!\n");
83 
84         // It is possible the stream does not support peeking, but does support
85         // rewinding.
86         // Attempt to read() and pass the actual amount read to the decoder.
87         bytesRead = stream->read(buffer, bytesToRead);
88         if (!stream->rewind()) {
89             SkCodecPrintf("Encoded image data could not peek or rewind to determine format!\n");
90             *outResult = kCouldNotRewind;
91             return nullptr;
92         }
93     }
94 
95     // PNG is special, since we want to be able to supply an SkPngChunkReader.
96     // But this code follows the same pattern as the loop.
97 #ifdef SK_HAS_PNG_LIBRARY
98     if (SkPngCodec::IsPng(buffer, bytesRead)) {
99         return SkPngCodec::NewFromStream(streamDeleter.release(), outResult, chunkReader);
100     } else
101 #endif
102     {
103         for (DecoderProc proc : gDecoderProcs) {
104             if (proc.IsFormat(buffer, bytesRead)) {
105                 return proc.NewFromStream(streamDeleter.release(), outResult);
106             }
107         }
108 
109 #ifdef SK_CODEC_DECODES_RAW
110         // Try to treat the input as RAW if all the other checks failed.
111         return SkRawCodec::NewFromStream(streamDeleter.release(), outResult);
112 #endif
113     }
114 
115     if (bytesRead < bytesToRead) {
116         *outResult = kIncompleteInput;
117     } else {
118         *outResult = kUnimplemented;
119     }
120 
121     return nullptr;
122 }
123 
NewFromData(sk_sp<SkData> data,SkPngChunkReader * reader)124 SkCodec* SkCodec::NewFromData(sk_sp<SkData> data, SkPngChunkReader* reader) {
125     if (!data) {
126         return nullptr;
127     }
128     return NewFromStream(new SkMemoryStream(data), nullptr, reader);
129 }
130 
SkCodec(int width,int height,const SkEncodedInfo & info,XformFormat srcFormat,SkStream * stream,sk_sp<SkColorSpace> colorSpace,Origin origin)131 SkCodec::SkCodec(int width, int height, const SkEncodedInfo& info,
132         XformFormat srcFormat, SkStream* stream,
133         sk_sp<SkColorSpace> colorSpace, Origin origin)
134     : fEncodedInfo(info)
135     , fSrcInfo(info.makeImageInfo(width, height, std::move(colorSpace)))
136     , fSrcXformFormat(srcFormat)
137     , fStream(stream)
138     , fNeedsRewind(false)
139     , fOrigin(origin)
140     , fDstInfo()
141     , fOptions()
142     , fCurrScanline(-1)
143 {}
144 
SkCodec(const SkEncodedInfo & info,const SkImageInfo & imageInfo,XformFormat srcFormat,SkStream * stream,Origin origin)145 SkCodec::SkCodec(const SkEncodedInfo& info, const SkImageInfo& imageInfo,
146         XformFormat srcFormat, SkStream* stream, Origin origin)
147     : fEncodedInfo(info)
148     , fSrcInfo(imageInfo)
149     , fSrcXformFormat(srcFormat)
150     , fStream(stream)
151     , fNeedsRewind(false)
152     , fOrigin(origin)
153     , fDstInfo()
154     , fOptions()
155     , fCurrScanline(-1)
156 {}
157 
~SkCodec()158 SkCodec::~SkCodec() {}
159 
rewindIfNeeded()160 bool SkCodec::rewindIfNeeded() {
161     // Store the value of fNeedsRewind so we can update it. Next read will
162     // require a rewind.
163     const bool needsRewind = fNeedsRewind;
164     fNeedsRewind = true;
165     if (!needsRewind) {
166         return true;
167     }
168 
169     // startScanlineDecode will need to be called before decoding scanlines.
170     fCurrScanline = -1;
171     // startIncrementalDecode will need to be called before incrementalDecode.
172     fStartedIncrementalDecode = false;
173 
174     // Some codecs do not have a stream.  They may hold onto their own data or another codec.
175     // They must handle rewinding themselves.
176     if (fStream && !fStream->rewind()) {
177         return false;
178     }
179 
180     return this->onRewind();
181 }
182 
zero_rect(const SkImageInfo & dstInfo,void * pixels,size_t rowBytes,SkIRect frameRect)183 static void zero_rect(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes,
184                       SkIRect frameRect) {
185     if (!frameRect.intersect(dstInfo.bounds())) {
186         return;
187     }
188     const auto info = dstInfo.makeWH(frameRect.width(), frameRect.height());
189     const size_t bpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
190     const size_t offset = frameRect.x() * bpp + frameRect.y() * rowBytes;
191     auto* eraseDst = SkTAddOffset<void>(pixels, offset);
192     SkSampler::Fill(info, eraseDst, rowBytes, 0, SkCodec::kNo_ZeroInitialized);
193 }
194 
handleFrameIndex(const SkImageInfo & info,void * pixels,size_t rowBytes,const Options & options)195 SkCodec::Result SkCodec::handleFrameIndex(const SkImageInfo& info, void* pixels, size_t rowBytes,
196                                           const Options& options) {
197     const int index = options.fFrameIndex;
198     if (0 == index) {
199         return kSuccess;
200     }
201 
202     if (options.fSubset || info.dimensions() != fSrcInfo.dimensions()) {
203         // If we add support for these, we need to update the code that zeroes
204         // a kRestoreBGColor frame.
205         return kInvalidParameters;
206     }
207 
208     if (index >= this->onGetFrameCount()) {
209         return kIncompleteInput;
210     }
211 
212     const auto* frameHolder = this->getFrameHolder();
213     SkASSERT(frameHolder);
214 
215     const auto* frame = frameHolder->getFrame(index);
216     SkASSERT(frame);
217 
218     const int requiredFrame = frame->getRequiredFrame();
219     if (requiredFrame == kNone) {
220         return kSuccess;
221     }
222 
223     if (options.fPriorFrame != kNone) {
224         // Check for a valid frame as a starting point. Alternatively, we could
225         // treat an invalid frame as not providing one, but rejecting it will
226         // make it easier to catch the mistake.
227         if (options.fPriorFrame < requiredFrame || options.fPriorFrame >= index) {
228             return kInvalidParameters;
229         }
230         const auto* prevFrame = frameHolder->getFrame(options.fPriorFrame);
231         switch (prevFrame->getDisposalMethod()) {
232             case SkCodecAnimation::DisposalMethod::kRestorePrevious:
233                 return kInvalidParameters;
234             case SkCodecAnimation::DisposalMethod::kRestoreBGColor:
235                 // If a frame after the required frame is provided, there is no
236                 // need to clear, since it must be covered by the desired frame.
237                 if (options.fPriorFrame == requiredFrame) {
238                     zero_rect(info, pixels, rowBytes, prevFrame->frameRect());
239                 }
240                 break;
241             default:
242                 break;
243         }
244         return kSuccess;
245     }
246 
247     Options prevFrameOptions(options);
248     prevFrameOptions.fFrameIndex = requiredFrame;
249     prevFrameOptions.fZeroInitialized = kNo_ZeroInitialized;
250     const Result result = this->getPixels(info, pixels, rowBytes, &prevFrameOptions);
251     if (result == kSuccess) {
252         const auto* prevFrame = frameHolder->getFrame(requiredFrame);
253         const auto disposalMethod = prevFrame->getDisposalMethod();
254         if (disposalMethod == SkCodecAnimation::DisposalMethod::kRestoreBGColor) {
255             zero_rect(info, pixels, rowBytes, prevFrame->frameRect());
256         }
257     }
258 
259     return result;
260 }
261 
getPixels(const SkImageInfo & info,void * pixels,size_t rowBytes,const Options * options)262 SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
263                                    const Options* options) {
264     if (kUnknown_SkColorType == info.colorType()) {
265         return kInvalidConversion;
266     }
267     if (nullptr == pixels) {
268         return kInvalidParameters;
269     }
270     if (rowBytes < info.minRowBytes()) {
271         return kInvalidParameters;
272     }
273 
274     if (!this->rewindIfNeeded()) {
275         return kCouldNotRewind;
276     }
277 
278     // Default options.
279     Options optsStorage;
280     if (nullptr == options) {
281         options = &optsStorage;
282     } else {
283         const Result frameIndexResult = this->handleFrameIndex(info, pixels, rowBytes, *options);
284         if (frameIndexResult != kSuccess) {
285             return frameIndexResult;
286         }
287         if (options->fSubset) {
288             SkIRect subset(*options->fSubset);
289             if (!this->onGetValidSubset(&subset) || subset != *options->fSubset) {
290                 // FIXME: How to differentiate between not supporting subset at all
291                 // and not supporting this particular subset?
292                 return kUnimplemented;
293             }
294         }
295     }
296 
297     // FIXME: Support subsets somehow? Note that this works for SkWebpCodec
298     // because it supports arbitrary scaling/subset combinations.
299     if (!this->dimensionsSupported(info.dimensions())) {
300         return kInvalidScale;
301     }
302 
303     fDstInfo = info;
304     fOptions = *options;
305 
306     // On an incomplete decode, the subclass will specify the number of scanlines that it decoded
307     // successfully.
308     int rowsDecoded = 0;
309     const Result result = this->onGetPixels(info, pixels, rowBytes, *options, &rowsDecoded);
310 
311     // A return value of kIncompleteInput indicates a truncated image stream.
312     // In this case, we will fill any uninitialized memory with a default value.
313     // Some subclasses will take care of filling any uninitialized memory on
314     // their own.  They indicate that all of the memory has been filled by
315     // setting rowsDecoded equal to the height.
316     if ((kIncompleteInput == result || kErrorInInput == result) && rowsDecoded != info.height()) {
317         // FIXME: (skbug.com/5772) fillIncompleteImage will fill using the swizzler's width, unless
318         // there is a subset. In that case, it will use the width of the subset. From here, the
319         // subset will only be non-null in the case of SkWebpCodec, but it treats the subset
320         // differenty from the other codecs, and it needs to use the width specified by the info.
321         // Set the subset to null so SkWebpCodec uses the correct width.
322         fOptions.fSubset = nullptr;
323         this->fillIncompleteImage(info, pixels, rowBytes, options->fZeroInitialized, info.height(),
324                 rowsDecoded);
325     }
326 
327     return result;
328 }
329 
startIncrementalDecode(const SkImageInfo & info,void * pixels,size_t rowBytes,const SkCodec::Options * options)330 SkCodec::Result SkCodec::startIncrementalDecode(const SkImageInfo& info, void* pixels,
331         size_t rowBytes, const SkCodec::Options* options) {
332     fStartedIncrementalDecode = false;
333 
334     if (kUnknown_SkColorType == info.colorType()) {
335         return kInvalidConversion;
336     }
337     if (nullptr == pixels) {
338         return kInvalidParameters;
339     }
340 
341     // FIXME: If the rows come after the rows of a previous incremental decode,
342     // we might be able to skip the rewind, but only the implementation knows
343     // that. (e.g. PNG will always need to rewind, since we called longjmp, but
344     // a bottom-up BMP could skip rewinding if the new rows are above the old
345     // rows.)
346     if (!this->rewindIfNeeded()) {
347         return kCouldNotRewind;
348     }
349 
350     // Set options.
351     Options optsStorage;
352     if (nullptr == options) {
353         options = &optsStorage;
354     } else {
355         const Result frameIndexResult = this->handleFrameIndex(info, pixels, rowBytes, *options);
356         if (frameIndexResult != kSuccess) {
357             return frameIndexResult;
358         }
359         if (options->fSubset) {
360             SkIRect size = SkIRect::MakeSize(info.dimensions());
361             if (!size.contains(*options->fSubset)) {
362                 return kInvalidParameters;
363             }
364 
365             const int top = options->fSubset->top();
366             const int bottom = options->fSubset->bottom();
367             if (top < 0 || top >= info.height() || top >= bottom || bottom > info.height()) {
368                 return kInvalidParameters;
369             }
370         }
371     }
372 
373     if (!this->dimensionsSupported(info.dimensions())) {
374         return kInvalidScale;
375     }
376 
377     fDstInfo = info;
378     fOptions = *options;
379 
380     const Result result = this->onStartIncrementalDecode(info, pixels, rowBytes, fOptions);
381     if (kSuccess == result) {
382         fStartedIncrementalDecode = true;
383     } else if (kUnimplemented == result) {
384         // FIXME: This is temporarily necessary, until we transition SkCodec
385         // implementations from scanline decoding to incremental decoding.
386         // SkAndroidCodec will first attempt to use incremental decoding, but
387         // will fall back to scanline decoding if incremental returns
388         // kUnimplemented. rewindIfNeeded(), above, set fNeedsRewind to true
389         // (after potentially rewinding), but we do not want the next call to
390         // startScanlineDecode() to do a rewind.
391         fNeedsRewind = false;
392     }
393     return result;
394 }
395 
396 
startScanlineDecode(const SkImageInfo & info,const SkCodec::Options * options)397 SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& info,
398         const SkCodec::Options* options) {
399     // Reset fCurrScanline in case of failure.
400     fCurrScanline = -1;
401 
402     if (!this->rewindIfNeeded()) {
403         return kCouldNotRewind;
404     }
405 
406     // Set options.
407     Options optsStorage;
408     if (nullptr == options) {
409         options = &optsStorage;
410     } else if (options->fSubset) {
411         SkIRect size = SkIRect::MakeSize(info.dimensions());
412         if (!size.contains(*options->fSubset)) {
413             return kInvalidInput;
414         }
415 
416         // We only support subsetting in the x-dimension for scanline decoder.
417         // Subsetting in the y-dimension can be accomplished using skipScanlines().
418         if (options->fSubset->top() != 0 || options->fSubset->height() != info.height()) {
419             return kInvalidInput;
420         }
421     }
422 
423     // FIXME: Support subsets somehow?
424     if (!this->dimensionsSupported(info.dimensions())) {
425         return kInvalidScale;
426     }
427 
428     const Result result = this->onStartScanlineDecode(info, *options);
429     if (result != SkCodec::kSuccess) {
430         return result;
431     }
432 
433     fCurrScanline = 0;
434     fDstInfo = info;
435     fOptions = *options;
436     return kSuccess;
437 }
438 
getScanlines(void * dst,int countLines,size_t rowBytes)439 int SkCodec::getScanlines(void* dst, int countLines, size_t rowBytes) {
440     if (fCurrScanline < 0) {
441         return 0;
442     }
443 
444     SkASSERT(!fDstInfo.isEmpty());
445     if (countLines <= 0 || fCurrScanline + countLines > fDstInfo.height()) {
446         return 0;
447     }
448 
449     const int linesDecoded = this->onGetScanlines(dst, countLines, rowBytes);
450     if (linesDecoded < countLines) {
451         this->fillIncompleteImage(this->dstInfo(), dst, rowBytes, this->options().fZeroInitialized,
452                 countLines, linesDecoded);
453     }
454     fCurrScanline += countLines;
455     return linesDecoded;
456 }
457 
skipScanlines(int countLines)458 bool SkCodec::skipScanlines(int countLines) {
459     if (fCurrScanline < 0) {
460         return false;
461     }
462 
463     SkASSERT(!fDstInfo.isEmpty());
464     if (countLines < 0 || fCurrScanline + countLines > fDstInfo.height()) {
465         // Arguably, we could just skip the scanlines which are remaining,
466         // and return true. We choose to return false so the client
467         // can catch their bug.
468         return false;
469     }
470 
471     bool result = this->onSkipScanlines(countLines);
472     fCurrScanline += countLines;
473     return result;
474 }
475 
outputScanline(int inputScanline) const476 int SkCodec::outputScanline(int inputScanline) const {
477     SkASSERT(0 <= inputScanline && inputScanline < this->getInfo().height());
478     return this->onOutputScanline(inputScanline);
479 }
480 
onOutputScanline(int inputScanline) const481 int SkCodec::onOutputScanline(int inputScanline) const {
482     switch (this->getScanlineOrder()) {
483         case kTopDown_SkScanlineOrder:
484             return inputScanline;
485         case kBottomUp_SkScanlineOrder:
486             return this->getInfo().height() - inputScanline - 1;
487         default:
488             // This case indicates an interlaced gif and is implemented by SkGifCodec.
489             SkASSERT(false);
490             return 0;
491     }
492 }
493 
onGetFillValue(const SkImageInfo & dstInfo) const494 uint64_t SkCodec::onGetFillValue(const SkImageInfo& dstInfo) const {
495     switch (dstInfo.colorType()) {
496         case kRGBA_F16_SkColorType: {
497             static constexpr uint64_t transparentColor = 0;
498             static constexpr uint64_t opaqueColor = ((uint64_t) SK_Half1) << 48;
499             return (kOpaque_SkAlphaType == fSrcInfo.alphaType()) ? opaqueColor : transparentColor;
500         }
501         default: {
502             // This not only handles the kN32 case, but also k565, kGray8, since
503             // the low bits are zeros.
504             return (kOpaque_SkAlphaType == fSrcInfo.alphaType()) ?
505                     SK_ColorBLACK : SK_ColorTRANSPARENT;
506         }
507     }
508 }
509 
fill_proc(const SkImageInfo & info,void * dst,size_t rowBytes,uint64_t colorOrIndex,SkCodec::ZeroInitialized zeroInit,SkSampler * sampler)510 static void fill_proc(const SkImageInfo& info, void* dst, size_t rowBytes,
511         uint64_t colorOrIndex, SkCodec::ZeroInitialized zeroInit, SkSampler* sampler) {
512     if (sampler) {
513         sampler->fill(info, dst, rowBytes, colorOrIndex, zeroInit);
514     } else {
515         SkSampler::Fill(info, dst, rowBytes, colorOrIndex, zeroInit);
516     }
517 }
518 
fillIncompleteImage(const SkImageInfo & info,void * dst,size_t rowBytes,ZeroInitialized zeroInit,int linesRequested,int linesDecoded)519 void SkCodec::fillIncompleteImage(const SkImageInfo& info, void* dst, size_t rowBytes,
520         ZeroInitialized zeroInit, int linesRequested, int linesDecoded) {
521 
522     void* fillDst;
523     const uint64_t fillValue = this->getFillValue(info);
524     const int linesRemaining = linesRequested - linesDecoded;
525     SkSampler* sampler = this->getSampler(false);
526 
527     int fillWidth = info.width();
528     if (fOptions.fSubset) {
529         fillWidth = fOptions.fSubset->width();
530     }
531 
532     switch (this->getScanlineOrder()) {
533         case kTopDown_SkScanlineOrder: {
534             const SkImageInfo fillInfo = info.makeWH(fillWidth, linesRemaining);
535             fillDst = SkTAddOffset<void>(dst, linesDecoded * rowBytes);
536             fill_proc(fillInfo, fillDst, rowBytes, fillValue, zeroInit, sampler);
537             break;
538         }
539         case kBottomUp_SkScanlineOrder: {
540             fillDst = dst;
541             const SkImageInfo fillInfo = info.makeWH(fillWidth, linesRemaining);
542             fill_proc(fillInfo, fillDst, rowBytes, fillValue, zeroInit, sampler);
543             break;
544         }
545     }
546 }
547 
select_xform_format_ct(SkColorType colorType)548 static inline SkColorSpaceXform::ColorFormat select_xform_format_ct(SkColorType colorType) {
549     switch (colorType) {
550         case kRGBA_8888_SkColorType:
551             return SkColorSpaceXform::kRGBA_8888_ColorFormat;
552         case kBGRA_8888_SkColorType:
553             return SkColorSpaceXform::kBGRA_8888_ColorFormat;
554         case kRGB_565_SkColorType:
555 #ifdef SK_PMCOLOR_IS_RGBA
556             return SkColorSpaceXform::kRGBA_8888_ColorFormat;
557 #else
558             return SkColorSpaceXform::kBGRA_8888_ColorFormat;
559 #endif
560         default:
561             SkASSERT(false);
562             return SkColorSpaceXform::kRGBA_8888_ColorFormat;
563     }
564 }
565 
initializeColorXform(const SkImageInfo & dstInfo,SkTransferFunctionBehavior premulBehavior)566 bool SkCodec::initializeColorXform(const SkImageInfo& dstInfo,
567                                    SkTransferFunctionBehavior premulBehavior) {
568     fColorXform = nullptr;
569     fXformOnDecode = false;
570     bool needsColorCorrectPremul = needs_premul(dstInfo, fEncodedInfo) &&
571                                    SkTransferFunctionBehavior::kRespect == premulBehavior;
572     if (needs_color_xform(dstInfo, fSrcInfo, needsColorCorrectPremul)) {
573         fColorXform = SkColorSpaceXform_Base::New(fSrcInfo.colorSpace(), dstInfo.colorSpace(),
574                                                   premulBehavior);
575         if (!fColorXform) {
576             return false;
577         }
578 
579         // We will apply the color xform when reading the color table unless F16 is requested.
580         fXformOnDecode = SkEncodedInfo::kPalette_Color != fEncodedInfo.color()
581             || kRGBA_F16_SkColorType == dstInfo.colorType();
582         if (fXformOnDecode) {
583             fDstXformFormat = select_xform_format(dstInfo.colorType());
584         } else {
585             fDstXformFormat = select_xform_format_ct(dstInfo.colorType());
586         }
587     }
588 
589     return true;
590 }
591 
applyColorXform(void * dst,const void * src,int count,SkAlphaType at) const592 void SkCodec::applyColorXform(void* dst, const void* src, int count, SkAlphaType at) const {
593     SkASSERT(fColorXform);
594     SkAssertResult(fColorXform->apply(fDstXformFormat, dst,
595                                       fSrcXformFormat, src,
596                                       count, at));
597 }
598 
applyColorXform(void * dst,const void * src,int count) const599 void SkCodec::applyColorXform(void* dst, const void* src, int count) const {
600     auto alphaType = select_xform_alpha(fDstInfo.alphaType(), fSrcInfo.alphaType());
601     this->applyColorXform(dst, src, count, alphaType);
602 }
603 
getFrameInfo()604 std::vector<SkCodec::FrameInfo> SkCodec::getFrameInfo() {
605     const int frameCount = this->getFrameCount();
606     SkASSERT(frameCount >= 0);
607     if (frameCount <= 0) {
608         return std::vector<FrameInfo>{};
609     }
610 
611     if (frameCount == 1 && !this->onGetFrameInfo(0, nullptr)) {
612         // Not animated.
613         return std::vector<FrameInfo>{};
614     }
615 
616     std::vector<FrameInfo> result(frameCount);
617     for (int i = 0; i < frameCount; ++i) {
618         SkAssertResult(this->onGetFrameInfo(i, &result[i]));
619     }
620     return result;
621 }
622