Beispiel #1
0
/*
 * Initiates the bitmap decode
 */
SkCodec::Result SkBmpMaskCodec::onGetPixels(const SkImageInfo& dstInfo,
                                            void* dst, size_t dstRowBytes,
                                            const Options& opts,
                                            SkPMColor* inputColorPtr,
                                            int* inputColorCount,
                                            int* rowsDecoded) {
    if (opts.fSubset) {
        // Subsets are not supported.
        return kUnimplemented;
    }
    if (dstInfo.dimensions() != this->getInfo().dimensions()) {
        SkCodecPrintf("Error: scaling not supported.\n");
        return kInvalidScale;
    }

    if (!conversion_possible(dstInfo, this->getInfo())) {
        SkCodecPrintf("Error: cannot convert input type to output type.\n");
        return kInvalidConversion;
    }

    Result result = this->prepareToDecode(dstInfo, opts, inputColorPtr, inputColorCount);
    if (kSuccess != result) {
        return result;
    }

    int rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts);
    if (rows != dstInfo.height()) {
        *rowsDecoded = rows;
        return kIncompleteInput;
    }
    return kSuccess;
}
Beispiel #2
0
/*
 * Initiates the bitmap decode
 */
SkCodec::Result SkBmpMaskCodec::onGetPixels(const SkImageInfo& dstInfo,
                                            void* dst, size_t dstRowBytes,
                                            const Options& opts,
                                            SkPMColor* inputColorPtr,
                                            int* inputColorCount) {
    if (!this->rewindIfNeeded()) {
        return kCouldNotRewind;
    }
    if (opts.fSubset) {
        // Subsets are not supported.
        return kUnimplemented;
    }
    if (dstInfo.dimensions() != this->getInfo().dimensions()) {
        SkCodecPrintf("Error: scaling not supported.\n");
        return kInvalidScale;
    }

    if (!conversion_possible(dstInfo, this->getInfo())) {
        SkCodecPrintf("Error: cannot convert input type to output type.\n");
        return kInvalidConversion;
    }

    // Initialize a the mask swizzler
    if (!this->initializeSwizzler(dstInfo)) {
        SkCodecPrintf("Error: cannot initialize swizzler.\n");
        return kInvalidConversion;
    }

    return this->decode(dstInfo, dst, dstRowBytes, opts);
}
Beispiel #3
0
/*
 * Initiates the bitmap decode
 */
SkCodec::Result SkBmpRLECodec::onGetPixels(const SkImageInfo& dstInfo,
                                           void* dst, size_t dstRowBytes,
                                           const Options& opts,
                                           SkPMColor* inputColorPtr,
                                           int* inputColorCount,
                                           int* rowsDecoded) {
    if (opts.fSubset) {
        // Subsets are not supported.
        return kUnimplemented;
    }
    if (!conversion_possible(dstInfo, this->getInfo())) {
        SkCodecPrintf("Error: cannot convert input type to output type.\n");
        return kInvalidConversion;
    }

    Result result = this->prepareToDecode(dstInfo, opts, inputColorPtr, inputColorCount);
    if (kSuccess != result) {
        return result;
    }

    // Perform the decode
    int rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts);
    if (rows != dstInfo.height()) {
        // We set rowsDecoded equal to the height because the background has already
        // been filled.  RLE encodings sometimes skip pixels, so we always start by
        // filling the background.
        *rowsDecoded = dstInfo.height();
        return kIncompleteInput;
    }

    return kSuccess;
}
Beispiel #4
0
SkCodec::Result SkRawCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst,
                                        size_t dstRowBytes, const Options& options,
                                        SkPMColor ctable[], int* ctableCount,
                                        int* rowsDecoded) {
    if (!conversion_possible(requestedInfo, this->getInfo())) {
        SkCodecPrintf("Error: cannot convert input type to output type.\n");
        return kInvalidConversion;
    }

    SkAutoTDelete<SkSwizzler> swizzler(SkSwizzler::CreateSwizzler(
            SkSwizzler::kRGB, nullptr, requestedInfo, options));
    SkASSERT(swizzler);

    const int width = requestedInfo.width();
    const int height = requestedInfo.height();
    SkAutoTDelete<dng_image> image(fDngImage->render(width, height));
    if (!image) {
        return kInvalidInput;
    }

    // Because the DNG SDK can not guarantee to render to requested size, we allow a small
    // difference. Only the overlapping region will be converted.
    const float maxDiffRatio = 1.03f;
    const dng_point& imageSize = image->Size();
    if (imageSize.h / width > maxDiffRatio || imageSize.h < width ||
        imageSize.v / height > maxDiffRatio || imageSize.v < height) {
        return SkCodec::kInvalidScale;
    }

    void* dstRow = dst;
    SkAutoTMalloc<uint8_t> srcRow(width * 3);

    dng_pixel_buffer buffer;
    buffer.fData = &srcRow[0];
    buffer.fPlane = 0;
    buffer.fPlanes = 3;
    buffer.fColStep = buffer.fPlanes;
    buffer.fPlaneStep = 1;
    buffer.fPixelType = ttByte;
    buffer.fPixelSize = sizeof(uint8_t);
    buffer.fRowStep = width * 3;

    for (int i = 0; i < height; ++i) {
        buffer.fArea = dng_rect(i, 0, i + 1, width);

        try {
            image->Get(buffer, dng_image::edge_zero);
        } catch (...) {
            *rowsDecoded = i;
            return kIncompleteInput; 
        }

        swizzler->swizzle(dstRow, &srcRow[0]);
        dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
    }
    return kSuccess;
}
Beispiel #5
0
SkCodec::Result SkBmpCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
        const SkCodec::Options& options, SkPMColor inputColorPtr[], int* inputColorCount) {
    if (!conversion_possible(dstInfo, this->getInfo())) {
        SkCodecPrintf("Error: cannot convert input type to output type.\n");
        return kInvalidConversion;
    }

    return prepareToDecode(dstInfo, options, inputColorPtr, inputColorCount);
}
Beispiel #6
0
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())) {
        return gif_error("Cannot convert input type to output type.\n",
                kInvalidConversion);
    }

    // Initialize color table and copy to the client if necessary
    this->initializeColorTable(dstInfo, inputColorPtr, inputColorCount);
    return kSuccess;
}
    Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options,
            SkPMColor ctable[], int* ctableCount) override {
        if (!conversion_possible(dstInfo, this->getInfo())) {
            return kInvalidConversion;
        }

        const Result result = this->initializeSwizzler(dstInfo, options, ctable,
                                                       ctableCount);
        if (result != kSuccess) {
            return result;
        }

        fStorage.reset(this->getInfo().width() *
                (bytes_per_pixel(this->getEncodedInfo().bitsPerPixel())));
        fSrcRow = fStorage.get();

        return kSuccess;
    }
Beispiel #8
0
SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst,
                                        size_t dstRowBytes, const Options& options,
                                        SkPMColor ctable[], int* ctableCount,
                                        int* rowsDecoded) {
    if (!conversion_possible(requestedInfo, this->getInfo())) {
        return kInvalidConversion;
    }
    if (options.fSubset) {
        // Subsets are not supported.
        return kUnimplemented;
    }

    // Note that ctable and ctableCount may be modified if there is a color table
    if (!this->initializeSwizzler(requestedInfo, options, ctable, ctableCount)) {
        return kInvalidInput;   // or parameters?
    }

    return this->decodeAllRows(dst, dstRowBytes, rowsDecoded);
}
Beispiel #9
0
SkScanlineDecoder* SkJpegCodec::onGetScanlineDecoder(const SkImageInfo& dstInfo,
        const Options& options, SkPMColor ctable[], int* ctableCount) {

    // Rewind the stream if needed
    if (!this->handleRewind()) {
        SkCodecPrintf("Could not rewind\n");
        return NULL;
    }

    // Set the jump location for libjpeg errors
    if (setjmp(fDecoderMgr->getJmpBuf())) {
        SkCodecPrintf("setjmp: Error from libjpeg\n");
        return NULL;
    }

    // Check if we can decode to the requested destination
    if (!conversion_possible(dstInfo, this->getInfo())) {
        SkCodecPrintf("Cannot convert to output type\n");
        return NULL;
    }

    // Perform the necessary scaling
    if (!this->scaleToDimensions(dstInfo.width(), dstInfo.height())) {
        SkCodecPrintf("Cannot scale ot output dimensions\n");
        return NULL;
    }

    // Now, given valid output dimensions, we can start the decompress
    if (!jpeg_start_decompress(fDecoderMgr->dinfo())) {
        SkCodecPrintf("start decompress failed\n");
        return NULL;
    }

    // Create the swizzler
    this->initializeSwizzler(dstInfo, NULL, dstInfo.minRowBytes(), options);
    if (NULL == fSwizzler) {
        SkCodecPrintf("Could not create swizzler\n");
        return NULL;
    }

    // Return the new scanline decoder
    return SkNEW_ARGS(SkJpegScanlineDecoder, (dstInfo, this));
}
Beispiel #10
0
    SkCodec::Result onStart(const SkImageInfo& dstInfo, const SkCodec::Options& options,
                            SkPMColor inputColorPtr[], int* inputColorCount) override {
        if (!fCodec->rewindIfNeeded()) {
            return SkCodec::kCouldNotRewind;
        }
        if (options.fSubset) {
            // Subsets are not supported.
            return SkCodec::kUnimplemented;
        }
        if (dstInfo.dimensions() != this->getInfo().dimensions()) {
            if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
                return SkCodec::kInvalidScale;
            }
        }
        if (!conversion_possible(dstInfo, this->getInfo())) {
            SkCodecPrintf("Error: cannot convert input type to output type.\n");
            return SkCodec::kInvalidConversion;
        }

        return fCodec->prepareToDecode(dstInfo, options, inputColorPtr, inputColorCount);
    }
/*
 * Initiates the bitmap decode
 */
SkCodec::Result SkBmpRLECodec::onGetPixels(const SkImageInfo& dstInfo,
                                        void* dst, size_t dstRowBytes,
                                        const Options& opts,
                                        SkPMColor* inputColorPtr,
                                        int* inputColorCount) {
    if (!this->rewindIfNeeded()) {
        return kCouldNotRewind;
    }
    if (opts.fSubset) {
        // Subsets are not supported.
        return kUnimplemented;
    }
    if (dstInfo.dimensions() != this->getInfo().dimensions()) {
        SkCodecPrintf("Error: scaling not supported.\n");
        return kInvalidScale;
    }
    if (!conversion_possible(dstInfo, this->getInfo())) {
        SkCodecPrintf("Error: cannot convert input type to output type.\n");
        return kInvalidConversion;
    }

    // Create the color table if necessary and prepare the stream for decode
    // Note that if it is non-NULL, inputColorCount will be modified
    if (!this->createColorTable(inputColorCount)) {
        SkCodecPrintf("Error: could not create color table.\n");
        return kInvalidInput;
    }

    // Copy the color table to the client if necessary
    copy_color_table(dstInfo, fColorTable, inputColorPtr, inputColorCount);

    // Initialize a swizzler if necessary
    if (!this->initializeStreamBuffer()) {
        SkCodecPrintf("Error: cannot initialize swizzler.\n");
        return kInvalidConversion;
    }

    // Perform the decode
    return decode(dstInfo, dst, dstRowBytes, opts);
}
Beispiel #12
0
SkCodec::Result SkPngCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
        void* dst, size_t rowBytes, const SkCodec::Options& options,
        SkPMColor* ctable, int* ctableCount) {
    if (!conversion_possible(dstInfo, this->getInfo())) {
        return kInvalidConversion;
    }

    if (!this->initializeSwizzler(dstInfo, options, ctable, ctableCount)) {
        return kInvalidInput;
    }

    int firstRow, lastRow;
    if (options.fSubset) {
        firstRow = options.fSubset->top();
        lastRow = options.fSubset->bottom() - 1;
    } else {
        firstRow = 0;
        lastRow = dstInfo.height() - 1;
    }
    this->setRange(firstRow, lastRow, dst, rowBytes);
    return kSuccess;
}
    Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options,
            SkPMColor ctable[], int* ctableCount) override {
        if (!conversion_possible(dstInfo, this->getInfo())) {
            return kInvalidConversion;
        }

        const Result result = this->initializeSwizzler(dstInfo, options, ctable,
                                                       ctableCount);
        if (result != kSuccess) {
            return result;
        }

        fHeight = dstInfo.height();
        // FIXME: This need not be called on a second call to onStartScanlineDecode.
        fSrcRowBytes = this->getInfo().width() *
                (bytes_per_pixel(this->getEncodedInfo().bitsPerPixel()));
        fGarbageRow.reset(fSrcRowBytes);
        fGarbageRowPtr = static_cast<uint8_t*>(fGarbageRow.get());
        fCanSkipRewind = true;

        return SkCodec::kSuccess;
    }
Beispiel #14
0
bool SkBitmapRegionCodec::conversionSupported(SkColorType colorType) {
    return conversion_possible(fCodec->getInfo().makeColorType(colorType), fCodec->getInfo());
}
SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst,
                                        size_t dstRowBytes, const Options& options,
                                        SkPMColor ctable[], int* ctableCount,
                                        int* rowsDecoded) {
    if (!conversion_possible(requestedInfo, this->getInfo())) {
        return kInvalidConversion;
    }
    if (options.fSubset) {
        // Subsets are not supported.
        return kUnimplemented;
    }

    // Note that ctable and ctableCount may be modified if there is a color table
    const Result result = this->initializeSwizzler(requestedInfo, options, ctable, ctableCount);
    if (result != kSuccess) {
        return result;
    }

    const int width = requestedInfo.width();
    const int height = requestedInfo.height();
    const int bpp = bytes_per_pixel(this->getEncodedInfo().bitsPerPixel());
    const size_t srcRowBytes = width * bpp;

    // FIXME: Could we use the return value of setjmp to specify the type of
    // error?
    int row = 0;
    // This must be declared above the call to setjmp to avoid memory leaks on incomplete images.
    SkAutoTMalloc<uint8_t> storage;
    if (setjmp(png_jmpbuf(fPng_ptr))) {
        // Assume that any error that occurs while reading rows is caused by an incomplete input.
        if (fNumberPasses > 1) {
            // FIXME (msarett): Handle incomplete interlaced pngs.
            return (row == height) ? kSuccess : kInvalidInput;
        }
        // FIXME: We do a poor job on incomplete pngs compared to other decoders (ex: Chromium,
        // Ubuntu Image Viewer).  This is because we use the default buffer size in libpng (8192
        // bytes), and if we can't fill the buffer, we immediately fail.
        // For example, if we try to read 8192 bytes, and the image (incorrectly) only contains
        // half that, which may have been enough to contain a non-zero number of lines, we fail
        // when we could have decoded a few more lines and then failed.
        // The read function that we provide for libpng has no way of indicating that we have
        // made a partial read.
        // Making our buffer size smaller improves our incomplete decodes, but what impact does
        // it have on regular decode performance?  Should we investigate using a different API
        // instead of png_read_row?  Chromium uses png_process_data.
        *rowsDecoded = row;
        return (row == height) ? kSuccess : kIncompleteInput;
    }

    // FIXME: We could split these out based on subclass.
    void* dstRow = dst;
    if (fNumberPasses > 1) {
        storage.reset(height * srcRowBytes);
        uint8_t* const base = storage.get();

        for (int i = 0; i < fNumberPasses; i++) {
            uint8_t* srcRow = base;
            for (int y = 0; y < height; y++) {
                png_read_row(fPng_ptr, srcRow, nullptr);
                srcRow += srcRowBytes;
            }
        }

        // Now swizzle it.
        uint8_t* srcRow = base;
        for (; row < height; row++) {
            fSwizzler->swizzle(dstRow, srcRow);
            dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
            srcRow += srcRowBytes;
        }
    } else {
        storage.reset(srcRowBytes);
        uint8_t* srcRow = storage.get();
        for (; row < height; row++) {
            png_read_row(fPng_ptr, srcRow, nullptr);
            fSwizzler->swizzle(dstRow, srcRow);
            dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
        }
    }

    // read rest of file, and get additional comment and time chunks in info_ptr
    png_read_end(fPng_ptr, fInfo_ptr);

    return kSuccess;
}
Beispiel #16
0
bool SkBitmapRegionCodec::conversionSupported(SkColorType colorType) {
    // FIXME: Call virtual function when it lands.
    SkImageInfo info = SkImageInfo::Make(0, 0, colorType, fCodec->getInfo().alphaType(),
            fCodec->getInfo().profileType());
    return conversion_possible(info, fCodec->getInfo());
}
Beispiel #17
0
/*
 * Performs the jpeg decode
 */
SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
                                         void* dst, size_t dstRowBytes,
                                         const Options& options, SkPMColor*, int*) {

    // Rewind the stream if needed
    if (!this->handleRewind()) {
        fDecoderMgr->returnFailure("could not rewind stream", kCouldNotRewind);
    }

    // Get a pointer to the decompress info since we will use it quite frequently
    jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo();

    // Set the jump location for libjpeg errors
    if (setjmp(fDecoderMgr->getJmpBuf())) {
        return fDecoderMgr->returnFailure("setjmp", kInvalidInput);
    }

    // Check if we can decode to the requested destination
    if (!conversion_possible(dstInfo, this->getInfo())) {
        return fDecoderMgr->returnFailure("conversion_possible", kInvalidConversion);
    }

    // Perform the necessary scaling
    if (!this->scaleToDimensions(dstInfo.width(), dstInfo.height())) {
        fDecoderMgr->returnFailure("cannot scale to requested dims", kInvalidScale);
    }

    // Now, given valid output dimensions, we can start the decompress
    if (!jpeg_start_decompress(dinfo)) {
        return fDecoderMgr->returnFailure("startDecompress", kInvalidInput);
    }

    // Create the swizzler
    this->initializeSwizzler(dstInfo, dst, dstRowBytes, options);
    if (NULL == fSwizzler) {
        return fDecoderMgr->returnFailure("getSwizzler", kUnimplemented);
    }

    // This is usually 1, but can also be 2 or 4.
    // If we wanted to always read one row at a time, we could, but we will save space and time
    // by using the recommendation from libjpeg.
    const uint32_t rowsPerDecode = dinfo->rec_outbuf_height;
    SkASSERT(rowsPerDecode <= 4);

    // Create a buffer to contain decoded rows (libjpeg requires a 2D array)
    SkASSERT(0 != fSrcRowBytes);
    SkAutoTDeleteArray<uint8_t> srcBuffer(SkNEW_ARRAY(uint8_t, fSrcRowBytes * rowsPerDecode));
    JSAMPLE* srcRows[4];
    uint8_t* srcPtr = srcBuffer.get();
    for (uint8_t i = 0; i < rowsPerDecode; i++) {
        srcRows[i] = (JSAMPLE*) srcPtr;
        srcPtr += fSrcRowBytes;
    }

    // Ensure that we loop enough times to decode all of the rows
    // libjpeg will prevent us from reading past the bottom of the image
    uint32_t dstHeight = dstInfo.height();
    for (uint32_t y = 0; y < dstHeight + rowsPerDecode - 1; y += rowsPerDecode) {
        // Read rows of the image
        uint32_t rowsDecoded = jpeg_read_scanlines(dinfo, srcRows, rowsPerDecode);

        // Convert to RGB if necessary
        if (JCS_CMYK == dinfo->out_color_space) {
            convert_CMYK_to_RGB(srcRows[0], dstInfo.width() * rowsDecoded);
        }

        // Swizzle to output destination
        for (uint32_t i = 0; i < rowsDecoded; i++) {
            fSwizzler->next(srcRows[i]);
        }

        // If we cannot read enough rows, assume the input is incomplete
        if (rowsDecoded < rowsPerDecode && y + rowsDecoded < dstHeight) {
            // Fill the remainder of the image with black. This error handling
            // behavior is unspecified but SkCodec consistently uses black as
            // the fill color for opaque images.  If the destination is kGray,
            // the low 8 bits of SK_ColorBLACK will be used.  Conveniently,
            // these are zeros, which is the representation for black in kGray.
            SkSwizzler::Fill(fSwizzler->getDstRow(), dstInfo, dstRowBytes,
                    dstHeight - y - rowsDecoded, SK_ColorBLACK, NULL);

            // Prevent libjpeg from failing on incomplete decode
            dinfo->output_scanline = dstHeight;

            // Finish the decode and indicate that the input was incomplete.
            jpeg_finish_decompress(dinfo);
            return fDecoderMgr->returnFailure("Incomplete image data", kIncompleteInput);
        }
    }
    jpeg_finish_decompress(dinfo);

    return kSuccess;
}
Beispiel #18
0
SkCodec::Result SkRawCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
                                        size_t dstRowBytes, const Options& options,
                                        SkPMColor ctable[], int* ctableCount,
                                        int* rowsDecoded) {
    if (!conversion_possible(dstInfo, this->getInfo()) ||
        !this->initializeColorXform(dstInfo, options.fPremulBehavior))
    {
        SkCodecPrintf("Error: cannot convert input type to output type.\n");
        return kInvalidConversion;
    }

    static const SkColorType kXformSrcColorType = kRGBA_8888_SkColorType;
    SkImageInfo swizzlerInfo = dstInfo;
    std::unique_ptr<uint32_t[]> xformBuffer = nullptr;
    if (this->colorXform()) {
        swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType);
        xformBuffer.reset(new uint32_t[dstInfo.width()]);
    }

    std::unique_ptr<SkSwizzler> swizzler(SkSwizzler::CreateSwizzler(
            this->getEncodedInfo(), nullptr, swizzlerInfo, options));
    SkASSERT(swizzler);

    const int width = dstInfo.width();
    const int height = dstInfo.height();
    std::unique_ptr<dng_image> image(fDngImage->render(width, height));
    if (!image) {
        return kInvalidInput;
    }

    // Because the DNG SDK can not guarantee to render to requested size, we allow a small
    // difference. Only the overlapping region will be converted.
    const float maxDiffRatio = 1.03f;
    const dng_point& imageSize = image->Size();
    if (imageSize.h / (float) width > maxDiffRatio || imageSize.h < width ||
        imageSize.v / (float) height > maxDiffRatio || imageSize.v < height) {
        return SkCodec::kInvalidScale;
    }

    void* dstRow = dst;
    SkAutoTMalloc<uint8_t> srcRow(width * 3);

    dng_pixel_buffer buffer;
    buffer.fData = &srcRow[0];
    buffer.fPlane = 0;
    buffer.fPlanes = 3;
    buffer.fColStep = buffer.fPlanes;
    buffer.fPlaneStep = 1;
    buffer.fPixelType = ttByte;
    buffer.fPixelSize = sizeof(uint8_t);
    buffer.fRowStep = width * 3;

    for (int i = 0; i < height; ++i) {
        buffer.fArea = dng_rect(i, 0, i + 1, width);

        try {
            image->Get(buffer, dng_image::edge_zero);
        } catch (...) {
            *rowsDecoded = i;
            return kIncompleteInput; 
        }

        if (this->colorXform()) {
            swizzler->swizzle(xformBuffer.get(), &srcRow[0]);

            const SkColorSpaceXform::ColorFormat srcFormat =
                    select_xform_format(kXformSrcColorType);
            const SkColorSpaceXform::ColorFormat dstFormat =
                    select_xform_format(dstInfo.colorType());
            this->colorXform()->apply(dstFormat, dstRow, srcFormat, xformBuffer.get(),
                                      dstInfo.width(), kOpaque_SkAlphaType);
            dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
        } else {
            swizzler->swizzle(dstRow, &srcRow[0]);
            dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
        }
    }
    return kSuccess;
}