blob: e773ed71e95dd206c15c562a645f8ef647cad397 [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/*
* Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "SkCodecAnimation.h"
#include "SkCodecPriv.h"
#include "SkColorPriv.h"
#include "SkColorTable.h"
#include "SkGifCodec.h"
#include "SkStream.h"
#include "SkSwizzler.h"
#include <algorithm>
#define GIF87_STAMP "GIF87a"
#define GIF89_STAMP "GIF89a"
#define GIF_STAMP_LEN 6
/*
* Checks the start of the stream to see if the image is a gif
*/
bool SkGifCodec::IsGif(const void* buf, size_t bytesRead) {
if (bytesRead >= GIF_STAMP_LEN) {
if (memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0)
{
return true;
}
}
return false;
}
/*
* Error function
*/
static SkCodec::Result gif_error(const char* msg, SkCodec::Result result = SkCodec::kInvalidInput) {
SkCodecPrintf("Gif Error: %s\n", msg);
return result;
}
/*
* Assumes IsGif was called and returned true
* Creates a gif decoder
* Reads enough of the stream to determine the image format
*/
SkCodec* SkGifCodec::NewFromStream(SkStream* stream) {
std::unique_ptr<SkGifImageReader> reader(new SkGifImageReader(stream));
if (!reader->parse(SkGifImageReader::SkGIFSizeQuery)) {
// Not enough data to determine the size.
return nullptr;
}
if (0 == reader->screenWidth() || 0 == reader->screenHeight()) {
return nullptr;
}
const auto alpha = reader->firstFrameHasAlpha() ? SkEncodedInfo::kBinary_Alpha
: SkEncodedInfo::kOpaque_Alpha;
// Use kPalette since Gifs are encoded with a color table.
// FIXME: Gifs can actually be encoded with 4-bits per pixel. Using 8 works, but we could skip
// expanding to 8 bits and take advantage of the SkSwizzler to work from 4.
const auto encodedInfo = SkEncodedInfo::Make(SkEncodedInfo::kPalette_Color, alpha, 8);
// Although the encodedInfo is always kPalette_Color, it is possible that kIndex_8 is
// unsupported if the frame is subset and there is no transparent pixel.
const auto colorType = reader->firstFrameSupportsIndex8() ? kIndex_8_SkColorType
: kN32_SkColorType;
// The choice of unpremul versus premul is arbitrary, since all colors are either fully
// opaque or fully transparent (i.e. kBinary), but we stored the transparent colors as all
// zeroes, which is arguably premultiplied.
const auto alphaType = reader->firstFrameHasAlpha() ? kUnpremul_SkAlphaType
: kOpaque_SkAlphaType;
const auto imageInfo = SkImageInfo::Make(reader->screenWidth(), reader->screenHeight(),
colorType, alphaType,
SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named));
return new SkGifCodec(encodedInfo, imageInfo, reader.release());
}
bool SkGifCodec::onRewind() {
fReader->clearDecodeState();
return true;
}
SkGifCodec::SkGifCodec(const SkEncodedInfo& encodedInfo, const SkImageInfo& imageInfo,
SkGifImageReader* reader)
: INHERITED(encodedInfo, imageInfo, nullptr)
, fReader(reader)
, fTmpBuffer(nullptr)
, fSwizzler(nullptr)
, fCurrColorTable(nullptr)
, fCurrColorTableIsReal(false)
, fFilledBackground(false)
, fFirstCallToIncrementalDecode(false)
, fDst(nullptr)
, fDstRowBytes(0)
, fRowsDecoded(0)
{
reader->setClient(this);
}
std::vector<SkCodec::FrameInfo> SkGifCodec::onGetFrameInfo() {
fReader->parse(SkGifImageReader::SkGIFFrameCountQuery);
const size_t size = fReader->imagesCount();
std::vector<FrameInfo> result(size);
for (size_t i = 0; i < size; i++) {
const SkGIFFrameContext* frameContext = fReader->frameContext(i);
result[i].fDuration = frameContext->delayTime();
result[i].fRequiredFrame = frameContext->getRequiredFrame();
}
return result;
}
int SkGifCodec::onGetRepetitionCount() {
fReader->parse(SkGifImageReader::SkGIFLoopCountQuery);
return fReader->loopCount();
}
void SkGifCodec::initializeColorTable(const SkImageInfo& dstInfo, size_t frameIndex) {
SkColorType colorTableColorType = dstInfo.colorType();
if (this->colorXform()) {
colorTableColorType = kRGBA_8888_SkColorType;
}
sk_sp<SkColorTable> currColorTable = fReader->getColorTable(colorTableColorType, frameIndex);
fCurrColorTableIsReal = currColorTable;
if (!fCurrColorTableIsReal) {
// This is possible for an empty frame. Create a dummy with one value (transparent).
SkPMColor color = SK_ColorTRANSPARENT;
fCurrColorTable.reset(new SkColorTable(&color, 1));
} else if (this->colorXform() && !fXformOnDecode) {
SkPMColor dstColors[256];
SkColorSpaceXform::ColorFormat dstFormat = select_xform_format(dstInfo.colorType());
SkColorSpaceXform::ColorFormat srcFormat = SkColorSpaceXform::kRGBA_8888_ColorFormat;
SkAlphaType xformAlphaType = select_xform_alpha(dstInfo.alphaType(),
this->getInfo().alphaType());
SkAssertResult(this->colorXform()->apply(dstFormat, dstColors, srcFormat,
currColorTable->readColors(),
currColorTable->count(), xformAlphaType));
fCurrColorTable.reset(new SkColorTable(dstColors, currColorTable->count()));
} else {
fCurrColorTable = std::move(currColorTable);
}
}
SkCodec::Result SkGifCodec::prepareToDecode(const SkImageInfo& dstInfo, SkPMColor* inputColorPtr,
int* inputColorCount, const Options& opts) {
// Check for valid input parameters
if (!conversion_possible(dstInfo, this->getInfo()) || !this->initializeColorXform(dstInfo)) {
return gif_error("Cannot convert input type to output type.\n", kInvalidConversion);
}
fXformOnDecode = false;
if (this->colorXform()) {
fXformOnDecode = apply_xform_on_decode(dstInfo.colorType(), this->getEncodedInfo().color());
if (fXformOnDecode) {
fXformBuffer.reset(new uint32_t[dstInfo.width()]);
sk_bzero(fXformBuffer.get(), dstInfo.width() * sizeof(uint32_t));
}
}
if (opts.fSubset) {
return gif_error("Subsets not supported.\n", kUnimplemented);
}
const size_t frameIndex = opts.fFrameIndex;
if (frameIndex > 0) {
switch (dstInfo.colorType()) {
case kIndex_8_SkColorType:
// FIXME: It is possible that a later frame can be decoded to index8, if it does one
// of the following:
// - Covers the entire previous frame
// - Shares a color table (and transparent index) with any prior frames that are
// showing.
// We must support index8 for the first frame to be backwards compatible on Android,
// but we do not (currently) need to support later frames as index8.
return gif_error("Cannot decode multiframe gif (except frame 0) as index 8.\n",
kInvalidConversion);
case kRGB_565_SkColorType:
// FIXME: In theory, we might be able to support this, but it's not clear that it
// is necessary (Chromium does not decode to 565, and Android does not decode
// frames beyond the first). Disabling it because it is somewhat difficult:
// - If there is a transparent pixel, and this frame draws on top of another frame
// (if the frame is independent with a transparent pixel, we should not decode to
// 565 anyway, since it is not opaque), we need to skip drawing the transparent
// pixels (see writeTransparentPixels in haveDecodedRow). We currently do this by
// first swizzling into temporary memory, then copying into the destination. (We
// let the swizzler handle it first because it may need to sample.) After
// swizzling to 565, we do not know which pixels in our temporary memory
// correspond to the transparent pixel, so we do not know what to skip. We could
// special case the non-sampled case (no need to swizzle), but as this is
// currently unused we can just not support it.
return gif_error("Cannot decode multiframe gif (except frame 0) as 565.\n",
kInvalidConversion);
default:
break;
}
}
fReader->parse((SkGifImageReader::SkGIFParseQuery) frameIndex);
if (frameIndex >= fReader->imagesCount()) {
return gif_error("frame index out of range!\n", kIncompleteInput);
}
fTmpBuffer.reset(new uint8_t[dstInfo.minRowBytes()]);
this->initializeColorTable(dstInfo, frameIndex);
this->initializeSwizzler(dstInfo, frameIndex);
SkASSERT(fCurrColorTable);
if (inputColorCount) {
*inputColorCount = fCurrColorTable->count();
}
copy_color_table(dstInfo, fCurrColorTable.get(), inputColorPtr, inputColorCount);
return kSuccess;
}
void SkGifCodec::initializeSwizzler(const SkImageInfo& dstInfo, size_t frameIndex) {
const SkGIFFrameContext* frame = fReader->frameContext(frameIndex);
// This is only called by prepareToDecode, which ensures frameIndex is in range.
SkASSERT(frame);
const int xBegin = frame->xOffset();
const int xEnd = std::min(static_cast<int>(frame->xOffset() + frame->width()),
static_cast<int>(fReader->screenWidth()));
// CreateSwizzler only reads left and right of the frame. We cannot use the frame's raw
// frameRect, since it might extend beyond the edge of the frame.
SkIRect swizzleRect = SkIRect::MakeLTRB(xBegin, 0, xEnd, 0);
SkImageInfo swizzlerInfo = dstInfo;
if (this->colorXform()) {
swizzlerInfo = swizzlerInfo.makeColorType(kRGBA_8888_SkColorType);
if (kPremul_SkAlphaType == dstInfo.alphaType()) {
swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType);
}
}
// The default Options should be fine:
// - we'll ignore if the memory is zero initialized - unless we're the first frame, this won't
// matter anyway.
// - subsets are not supported for gif
// - the swizzler does not need to know about the frame.
// We may not be able to use the real Options anyway, since getPixels does not store it (due to
// a bug).
fSwizzler.reset(SkSwizzler::CreateSwizzler(this->getEncodedInfo(),
fCurrColorTable->readColors(), swizzlerInfo, Options(), &swizzleRect));
SkASSERT(fSwizzler.get());
}
/*
* Initiates the gif decode
*/
SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo,
void* pixels, size_t dstRowBytes,
const Options& opts,
SkPMColor* inputColorPtr,
int* inputColorCount,
int* rowsDecoded) {
Result result = this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, opts);
if (kSuccess != result) {
return result;
}
if (dstInfo.dimensions() != this->getInfo().dimensions()) {
return gif_error("Scaling not supported.\n", kInvalidScale);
}
fDst = pixels;
fDstRowBytes = dstRowBytes;
return this->decodeFrame(true, opts, rowsDecoded);
}
SkCodec::Result SkGifCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
void* pixels, size_t dstRowBytes,
const SkCodec::Options& opts,
SkPMColor* inputColorPtr,
int* inputColorCount) {
Result result = this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, opts);
if (result != kSuccess) {
return result;
}
fDst = pixels;
fDstRowBytes = dstRowBytes;
fFirstCallToIncrementalDecode = true;
return kSuccess;
}
SkCodec::Result SkGifCodec::onIncrementalDecode(int* rowsDecoded) {
// It is possible the client has appended more data. Parse, if needed.
const auto& options = this->options();
const size_t frameIndex = options.fFrameIndex;
fReader->parse((SkGifImageReader::SkGIFParseQuery) frameIndex);
const bool firstCallToIncrementalDecode = fFirstCallToIncrementalDecode;
fFirstCallToIncrementalDecode = false;
return this->decodeFrame(firstCallToIncrementalDecode, options, rowsDecoded);
}
SkCodec::Result SkGifCodec::decodeFrame(bool firstAttempt, const Options& opts, int* rowsDecoded) {
const SkImageInfo& dstInfo = this->dstInfo();
const size_t frameIndex = opts.fFrameIndex;
SkASSERT(frameIndex < fReader->imagesCount());
const SkGIFFrameContext* frameContext = fReader->frameContext(frameIndex);
if (firstAttempt) {
// rowsDecoded reports how many rows have been initialized, so a layer above
// can fill the rest. In some cases, we fill the background before decoding
// (or it is already filled for us), so we report rowsDecoded to be the full
// height.
bool filledBackground = false;
if (frameContext->getRequiredFrame() == kNone) {
// We may need to clear to transparent for one of the following reasons:
// - The frameRect does not cover the full bounds. haveDecodedRow will
// only draw inside the frameRect, so we need to clear the rest.
// - The frame is interlaced. There is no obvious way to fill
// afterwards for an incomplete image. (FIXME: Does the first pass
// cover all rows? If so, we do not have to fill here.)
// - There is no color table for this frame. In that case will not
// draw anything, so we need to fill.
if (frameContext->frameRect() != this->getInfo().bounds()
|| frameContext->interlaced() || !fCurrColorTableIsReal) {
// fill ignores the width (replaces it with the actual, scaled width).
// But we need to scale in Y.
const int scaledHeight = get_scaled_dimension(dstInfo.height(),
fSwizzler->sampleY());
auto fillInfo = dstInfo.makeWH(0, scaledHeight);
fSwizzler->fill(fillInfo, fDst, fDstRowBytes, this->getFillValue(dstInfo),
opts.fZeroInitialized);
filledBackground = true;
}
} else {
// Not independent
if (!opts.fHasPriorFrame) {
// Decode that frame into pixels.
Options prevFrameOpts(opts);
prevFrameOpts.fFrameIndex = frameContext->getRequiredFrame();
prevFrameOpts.fHasPriorFrame = false;
// The prior frame may have a different color table, so update it and the
// swizzler.
this->initializeColorTable(dstInfo, prevFrameOpts.fFrameIndex);
this->initializeSwizzler(dstInfo, prevFrameOpts.fFrameIndex);
const Result prevResult = this->decodeFrame(true, prevFrameOpts, nullptr);
switch (prevResult) {
case kSuccess:
// Prior frame succeeded. Carry on.
break;
case kIncompleteInput:
// Prior frame was incomplete. So this frame cannot be decoded.
return kInvalidInput;
default:
return prevResult;
}
// Go back to using the correct color table for this frame.
this->initializeColorTable(dstInfo, frameIndex);
this->initializeSwizzler(dstInfo, frameIndex);
}
const auto* prevFrame = fReader->frameContext(frameContext->getRequiredFrame());
if (prevFrame->getDisposalMethod() == SkCodecAnimation::RestoreBGColor_DisposalMethod) {
const SkIRect prevRect = prevFrame->frameRect();
auto left = get_scaled_dimension(prevRect.fLeft, fSwizzler->sampleX());
auto top = get_scaled_dimension(prevRect.fTop, fSwizzler->sampleY());
void* const eraseDst = SkTAddOffset<void>(fDst, top * fDstRowBytes
+ left * SkColorTypeBytesPerPixel(dstInfo.colorType()));
auto width = get_scaled_dimension(prevRect.width(), fSwizzler->sampleX());
auto height = get_scaled_dimension(prevRect.height(), fSwizzler->sampleY());
// fSwizzler->fill() would fill to the scaled width of the frame, but we want to
// fill to the scaled with of the width of the PRIOR frame, so we do all the scaling
// ourselves and call the static version.
SkSampler::Fill(dstInfo.makeWH(width, height), eraseDst,
fDstRowBytes, this->getFillValue(dstInfo), kNo_ZeroInitialized);
}
filledBackground = true;
}
fFilledBackground = filledBackground;
if (filledBackground) {
// Report the full (scaled) height, since the client will never need to fill.
fRowsDecoded = get_scaled_dimension(dstInfo.height(), fSwizzler->sampleY());
} else {
// This will be updated by haveDecodedRow.
fRowsDecoded = 0;
}
}
// Note: there is a difference between the following call to SkGifImageReader::decode
// returning false and leaving frameDecoded false:
// - If the method returns false, there was an error in the stream. We still treat this as
// incomplete, since we have already decoded some rows.
// - If frameDecoded is false, that just means that we do not have enough data. If more data
// is supplied, we may be able to continue decoding this frame. We also treat this as
// incomplete.
// FIXME: Ensure that we do not attempt to continue decoding if the method returns false and
// more data is supplied.
bool frameDecoded = false;
if (!fReader->decode(frameIndex, &frameDecoded) || !frameDecoded) {
if (rowsDecoded) {
*rowsDecoded = fRowsDecoded;
}
return kIncompleteInput;
}
return kSuccess;
}
uint64_t SkGifCodec::onGetFillValue(const SkImageInfo& dstInfo) const {
// Note: Using fCurrColorTable relies on having called initializeColorTable already.
// This is (currently) safe because this method is only called when filling, after
// initializeColorTable has been called.
// FIXME: Is there a way to make this less fragile?
if (dstInfo.colorType() == kIndex_8_SkColorType && fCurrColorTableIsReal) {
// We only support index 8 for the first frame, for backwards
// compatibity on Android, so we are using the color table for the first frame.
SkASSERT(this->options().fFrameIndex == 0);
// Use the transparent index for the first frame.
const size_t transPixel = fReader->frameContext(0)->transparentPixel();
if (transPixel < (size_t) fCurrColorTable->count()) {
return transPixel;
}
// Fall through to return SK_ColorTRANSPARENT (i.e. 0). This choice is arbitrary,
// but we have to pick something inside the color table, and this one is as good
// as any.
}
// Using transparent as the fill value matches the behavior in Chromium,
// which ignores the background color.
// If the colorType is kIndex_8, and there was no color table (i.e.
// fCurrColorTableIsReal is false), this value (zero) corresponds to the
// only entry in the dummy color table provided to the client.
return SK_ColorTRANSPARENT;
}
void SkGifCodec::applyXformRow(const SkImageInfo& dstInfo, void* dst, const uint8_t* src) const {
if (this->colorXform() && fXformOnDecode) {
fSwizzler->swizzle(fXformBuffer.get(), src);
const SkColorSpaceXform::ColorFormat dstFormat = select_xform_format(dstInfo.colorType());
const SkColorSpaceXform::ColorFormat srcFormat = SkColorSpaceXform::kRGBA_8888_ColorFormat;
const SkAlphaType xformAlphaType = select_xform_alpha(dstInfo.alphaType(),
this->getInfo().alphaType());
const int xformWidth = get_scaled_dimension(dstInfo.width(), fSwizzler->sampleX());
SkAssertResult(this->colorXform()->apply(dstFormat, dst, srcFormat, fXformBuffer.get(),
xformWidth, xformAlphaType));
} else {
fSwizzler->swizzle(dst, src);
}
}
bool SkGifCodec::haveDecodedRow(size_t frameIndex, const unsigned char* rowBegin,
size_t rowNumber, unsigned repeatCount, bool writeTransparentPixels)
{
const SkGIFFrameContext* frameContext = fReader->frameContext(frameIndex);
// The pixel data and coordinates supplied to us are relative to the frame's
// origin within the entire image size, i.e.
// (frameContext->xOffset, frameContext->yOffset). There is no guarantee
// that width == (size().width() - frameContext->xOffset), so
// we must ensure we don't run off the end of either the source data or the
// row's X-coordinates.
const size_t width = frameContext->width();
const int xBegin = frameContext->xOffset();
const int yBegin = frameContext->yOffset() + rowNumber;
const int xEnd = std::min(static_cast<int>(frameContext->xOffset() + width),
this->getInfo().width());
const int yEnd = std::min(static_cast<int>(frameContext->yOffset() + rowNumber + repeatCount),
this->getInfo().height());
// FIXME: No need to make the checks on width/xBegin/xEnd for every row. We could instead do
// this once in prepareToDecode.
if (!width || (xBegin < 0) || (yBegin < 0) || (xEnd <= xBegin) || (yEnd <= yBegin))
return true;
// yBegin is the first row in the non-sampled image. dstRow will be the row in the output,
// after potentially scaling it.
int dstRow = yBegin;
const int sampleY = fSwizzler->sampleY();
if (sampleY > 1) {
// Check to see whether this row or one that falls in the repeatCount is needed in the
// output.
bool foundNecessaryRow = false;
for (unsigned i = 0; i < repeatCount; i++) {
const int potentialRow = yBegin + i;
if (fSwizzler->rowNeeded(potentialRow)) {
dstRow = potentialRow / sampleY;
const int scaledHeight = get_scaled_dimension(this->dstInfo().height(), sampleY);
if (dstRow >= scaledHeight) {
return true;
}
foundNecessaryRow = true;
repeatCount -= i;
repeatCount = (repeatCount - 1) / sampleY + 1;
// Make sure the repeatCount does not take us beyond the end of the dst
if (dstRow + (int) repeatCount > scaledHeight) {
repeatCount = scaledHeight - dstRow;
SkASSERT(repeatCount >= 1);
}
break;
}
}
if (!foundNecessaryRow) {
return true;
}
} else {
// Make sure the repeatCount does not take us beyond the end of the dst
SkASSERT(this->dstInfo().height() >= yBegin);
repeatCount = SkTMin(repeatCount, (unsigned) (this->dstInfo().height() - yBegin));
}
if (!fFilledBackground) {
// At this point, we are definitely going to write the row, so count it towards the number
// of rows decoded.
// We do not consider the repeatCount, which only happens for interlaced, in which case we
// have already set fRowsDecoded to the proper value (reflecting that we have filled the
// background).
fRowsDecoded++;
}
if (!fCurrColorTableIsReal) {
// No color table, so nothing to draw this frame.
// FIXME: We can abort even earlier - no need to decode this frame.
return true;
}
// The swizzler takes care of offsetting into the dst width-wise.
void* dstLine = SkTAddOffset<void>(fDst, dstRow * fDstRowBytes);
// We may or may not need to write transparent pixels to the buffer.
// If we're compositing against a previous image, it's wrong, but if
// we're decoding an interlaced gif and displaying it "Haeberli"-style,
// we must write these for passes beyond the first, or the initial passes
// will "show through" the later ones.
const auto dstInfo = this->dstInfo();
if (writeTransparentPixels) {
this->applyXformRow(dstInfo, dstLine, rowBegin);
} else {
sk_bzero(fTmpBuffer.get(), dstInfo.minRowBytes());
this->applyXformRow(dstInfo, fTmpBuffer.get(), rowBegin);
const size_t offsetBytes = fSwizzler->swizzleOffsetBytes();
switch (dstInfo.colorType()) {
case kBGRA_8888_SkColorType:
case kRGBA_8888_SkColorType: {
uint32_t* dstPixel = SkTAddOffset<uint32_t>(dstLine, offsetBytes);
uint32_t* srcPixel = SkTAddOffset<uint32_t>(fTmpBuffer.get(), offsetBytes);
for (int i = 0; i < fSwizzler->swizzleWidth(); i++) {
// Technically SK_ColorTRANSPARENT is an SkPMColor, and srcPixel would have
// the opposite swizzle for the non-native swizzle, but TRANSPARENT is all
// zeroes, which is the same either way.
if (*srcPixel != SK_ColorTRANSPARENT) {
*dstPixel = *srcPixel;
}
dstPixel++;
srcPixel++;
}
break;
}
case kRGBA_F16_SkColorType: {
uint64_t* dstPixel = SkTAddOffset<uint64_t>(dstLine, offsetBytes);
uint64_t* srcPixel = SkTAddOffset<uint64_t>(fTmpBuffer.get(), offsetBytes);
for (int i = 0; i < fSwizzler->swizzleWidth(); i++) {
if (*srcPixel != 0) {
*dstPixel = *srcPixel;
}
dstPixel++;
srcPixel++;
}
break;
}
default:
SkASSERT(false);
break;
}
}
// Tell the frame to copy the row data if need be.
if (repeatCount > 1) {
const size_t bytesPerPixel = SkColorTypeBytesPerPixel(this->dstInfo().colorType());
const size_t bytesToCopy = fSwizzler->swizzleWidth() * bytesPerPixel;
void* copiedLine = SkTAddOffset<void>(dstLine, fSwizzler->swizzleOffsetBytes());
void* dst = copiedLine;
for (unsigned i = 1; i < repeatCount; i++) {
dst = SkTAddOffset<void>(dst, fDstRowBytes);
memcpy(dst, copiedLine, bytesToCopy);
}
}
return true;
}