TEST_F(DeferredImageDecoderTest, frameOpacity)
{
    OwnPtr<ImageDecoder> actualDecoder = ImageDecoder::create(*m_data,
        ImageDecoder::AlphaPremultiplied, ImageDecoder::GammaAndColorProfileApplied);
    OwnPtr<DeferredImageDecoder> decoder = DeferredImageDecoder::createForTesting(actualDecoder.release());
    decoder->setData(*m_data, true);

    SkImageInfo pixInfo = SkImageInfo::MakeN32Premul(1, 1);
    SkAutoPixmapStorage pixels;
    pixels.alloc(pixInfo);

    // Before decoding, the frame is not known to be opaque.
    RefPtr<SkImage> frame = decoder->createFrameAtIndex(0);
    ASSERT_TRUE(frame);
    EXPECT_FALSE(frame->isOpaque());

    // Force a lazy decode by reading pixels.
    EXPECT_TRUE(frame->readPixels(pixels, 0, 0));

    // After decoding, the frame is known to be opaque.
    frame = decoder->createFrameAtIndex(0);
    ASSERT_TRUE(frame);
    EXPECT_TRUE(frame->isOpaque());

    // Re-generating the opaque-marked frame should not fail.
    EXPECT_TRUE(frame->readPixels(pixels, 0, 0));
}
Esempio n. 2
0
DEF_TEST(SpecialImage_Pixmap, reporter) {
    SkAutoPixmapStorage pixmap;

    const SkImageInfo info = SkImageInfo::MakeN32(kFullSize, kFullSize, kOpaque_SkAlphaType);
    pixmap.alloc(info);
    pixmap.erase(SK_ColorGREEN);

    const SkIRect& subset = SkIRect::MakeXYWH(kPad, kPad, kSmallerSize, kSmallerSize);

    pixmap.erase(SK_ColorRED, subset);

    {
        sk_sp<SkSpecialImage> img(SkSpecialImage::MakeFromPixmap(subset, pixmap,
                                                                 nullptr, nullptr));
        test_image(img, reporter, nullptr, false, kPad, kFullSize);
    }
}
static bool draw_path_cell(SkCanvas* canvas, SkImage* img, int expectedCaps) {
    // Draw the image
    canvas->drawImage(img, 0, 0);

    int w = img->width(), h = img->height();

    // Read the pixels back
    SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
    SkAutoPixmapStorage pmap;
    pmap.alloc(info);
    if (!img->readPixels(pmap, 0, 0)) {
        return false;
    }

    // To account for rasterization differences, we scan the middle two rows [y, y+1] of the image
    SkASSERT(h % 2 == 0);
    int y = (h - 1) / 2;

    bool inBlob = false;
    int numBlobs = 0;
    for (int x = 0; x < w; ++x) {
        // We drew white-on-black. We can look for any non-zero value. Just check red.
        // And we care if either row is non-zero, so just add them to simplify everything.
        uint32_t v = SkGetPackedR32(*pmap.addr32(x, y)) + SkGetPackedR32(*pmap.addr32(x, y + 1));

        if (!inBlob && v) {
            ++numBlobs;
        }
        inBlob = SkToBool(v);
    }

    SkPaint outline;
    outline.setStyle(SkPaint::kStroke_Style);
    if (numBlobs == expectedCaps) {
        outline.setColor(0xFF007F00); // Green
    } else if (numBlobs > expectedCaps) {
        outline.setColor(0xFF7F7F00); // Yellow -- more geometry than expected
    } else {
        outline.setColor(0xFF7F0000); // Red -- missing some geometry
    }

    canvas->drawRect(SkRect::MakeWH(w, h), outline);
    return numBlobs == expectedCaps;
}
Esempio n. 4
0
DEF_TEST(color_half_float, reporter) {
    const int w = 100;
    const int h = 100;

    SkImageInfo info = SkImageInfo::Make(w, h, kRGBA_F16_SkColorType, kPremul_SkAlphaType);

    SkAutoPixmapStorage pm;
    pm.alloc(info);
    REPORTER_ASSERT(reporter, pm.getSafeSize() == SkToSizeT(w * h * sizeof(uint64_t)));

    SkColor4f c4 { 1, 0.5f, 0.25f, 0.5f };
    pm.erase(c4);

    SkPM4f origpm4 = c4.premul();
    for (int y = 0; y < pm.height(); ++y) {
        for (int x = 0; x < pm.width(); ++x) {
            SkPM4f pm4 = SkPM4f::FromF16(pm.addrF16(x, y));
            REPORTER_ASSERT(reporter, eq_within_half_float(origpm4, pm4));
        }
    }
}
Esempio n. 5
0
static void generateMask(const SkMask& mask, const SkPath& path,
                         const SkMaskGamma::PreBlend& maskPreBlend) {
    SkPaint paint;

    int srcW = mask.fBounds.width();
    int srcH = mask.fBounds.height();
    int dstW = srcW;
    int dstH = srcH;
    int dstRB = mask.fRowBytes;

    SkMatrix matrix;
    matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft),
                        -SkIntToScalar(mask.fBounds.fTop));

    paint.setAntiAlias(SkMask::kBW_Format != mask.fFormat);
    switch (mask.fFormat) {
        case SkMask::kBW_Format:
            dstRB = 0;  // signals we need a copy
            break;
        case SkMask::kA8_Format:
            break;
        case SkMask::kLCD16_Format:
            // TODO: trigger off LCD orientation
            dstW = 4*dstW - 8;
            matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft + 1),
                                -SkIntToScalar(mask.fBounds.fTop));
            matrix.postScale(SkIntToScalar(4), SK_Scalar1);
            dstRB = 0;  // signals we need a copy
            break;
        default:
            SkDEBUGFAIL("unexpected mask format");
    }

    SkRasterClip clip;
    clip.setRect(SkIRect::MakeWH(dstW, dstH));

    const SkImageInfo info = SkImageInfo::MakeA8(dstW, dstH);
    SkAutoPixmapStorage dst;

    if (0 == dstRB) {
        if (!dst.tryAlloc(info)) {
            // can't allocate offscreen, so empty the mask and return
            sk_bzero(mask.fImage, mask.computeImageSize());
            return;
        }
    } else {
        dst.reset(info, mask.fImage, dstRB);
    }
    sk_bzero(dst.writable_addr(), dst.getSafeSize());

    SkDraw  draw;
    draw.fDst   = dst;
    draw.fRC    = &clip;
    draw.fClip  = &clip.bwRgn();
    draw.fMatrix = &matrix;
    draw.drawPath(path, paint);

    switch (mask.fFormat) {
        case SkMask::kBW_Format:
            packA8ToA1(mask, dst.addr8(0, 0), dst.rowBytes());
            break;
        case SkMask::kA8_Format:
            if (maskPreBlend.isApplicable()) {
                applyLUTToA8Mask(mask, maskPreBlend.fG);
            }
            break;
        case SkMask::kLCD16_Format:
            if (maskPreBlend.isApplicable()) {
                pack4xHToLCD16<true>(dst, mask, maskPreBlend);
            } else {
                pack4xHToLCD16<false>(dst, mask, maskPreBlend);
            }
            break;
        default:
            break;
    }
}
Esempio n. 6
0
size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& proxy,
                                            const DeferredTextureImageUsageParams params[],
                                            int paramCnt, void* buffer,
                                            SkColorSpace* dstColorSpace) const {
    // Extract relevant min/max values from the params array.
    int lowestPreScaleMipLevel = params[0].fPreScaleMipLevel;
    SkFilterQuality highestFilterQuality = params[0].fQuality;
    bool useMipMaps = should_use_mip_maps(params[0]);
    for (int i = 1; i < paramCnt; ++i) {
        if (lowestPreScaleMipLevel > params[i].fPreScaleMipLevel)
            lowestPreScaleMipLevel = params[i].fPreScaleMipLevel;
        if (highestFilterQuality < params[i].fQuality)
            highestFilterQuality = params[i].fQuality;
        useMipMaps |= should_use_mip_maps(params[i]);
    }

    const bool fillMode = SkToBool(buffer);
    if (fillMode && !SkIsAlign8(reinterpret_cast<intptr_t>(buffer))) {
        return 0;
    }

    // Calculate scaling parameters.
    bool isScaled = lowestPreScaleMipLevel != 0;

    SkISize scaledSize;
    if (isScaled) {
        // SkMipMap::ComputeLevelSize takes an index into an SkMipMap. SkMipMaps don't contain the
        // base level, so to get an SkMipMap index we must subtract one from the GL MipMap level.
        scaledSize = SkMipMap::ComputeLevelSize(this->width(), this->height(),
                                                lowestPreScaleMipLevel - 1);
    } else {
        scaledSize = SkISize::Make(this->width(), this->height());
    }

    // We never want to scale at higher than SW medium quality, as SW medium matches GPU high.
    SkFilterQuality scaleFilterQuality = highestFilterQuality;
    if (scaleFilterQuality > kMedium_SkFilterQuality) {
        scaleFilterQuality = kMedium_SkFilterQuality;
    }

    const int maxTextureSize = proxy.fCaps->maxTextureSize();
    if (scaledSize.width() > maxTextureSize || scaledSize.height() > maxTextureSize) {
        return 0;
    }

    SkAutoPixmapStorage pixmap;
    SkImageInfo info;
    size_t pixelSize = 0;
    size_t ctSize = 0;
    int ctCount = 0;
    if (!isScaled && this->peekPixels(&pixmap)) {
        info = pixmap.info();
        pixelSize = SkAlign8(pixmap.getSafeSize());
        if (pixmap.ctable()) {
            ctCount = pixmap.ctable()->count();
            ctSize = SkAlign8(pixmap.ctable()->count() * 4);
        }
    } else {
        // Here we're just using presence of data to know whether there is a codec behind the image.
        // In the future we will access the cacherator and get the exact data that we want to (e.g.
        // yuv planes) upload.
        sk_sp<SkData> data(this->refEncoded());
        if (!data && !this->peekPixels(nullptr)) {
            return 0;
        }
        info = as_IB(this)->onImageInfo().makeWH(scaledSize.width(), scaledSize.height());
        pixelSize = SkAlign8(SkAutoPixmapStorage::AllocSize(info, nullptr));
        if (fillMode) {
            pixmap.alloc(info);
            if (isScaled) {
                if (!this->scalePixels(pixmap, scaleFilterQuality,
                                       SkImage::kDisallow_CachingHint)) {
                    return 0;
                }
            } else {
                if (!this->readPixels(pixmap, 0, 0, SkImage::kDisallow_CachingHint)) {
                    return 0;
                }
            }
            SkASSERT(!pixmap.ctable());
        }
    }
    int mipMapLevelCount = 1;
    if (useMipMaps) {
        // SkMipMap only deals with the mipmap levels it generates, which does
        // not include the base level.
        // That means it generates and holds levels 1-x instead of 0-x.
        // So the total mipmap level count is 1 more than what
        // SkMipMap::ComputeLevelCount returns.
        mipMapLevelCount = SkMipMap::ComputeLevelCount(scaledSize.width(), scaledSize.height()) + 1;

        // We already initialized pixelSize to the size of the base level.
        // SkMipMap will generate the extra mipmap levels. Their sizes need to
        // be added to the total.
        // Index 0 here does not refer to the base mipmap level -- it is
        // SkMipMap's first generated mipmap level (level 1).
        for (int currentMipMapLevelIndex = mipMapLevelCount - 2; currentMipMapLevelIndex >= 0;
             currentMipMapLevelIndex--) {
            SkISize mipSize = SkMipMap::ComputeLevelSize(scaledSize.width(), scaledSize.height(),
                                                         currentMipMapLevelIndex);
            SkImageInfo mipInfo = info.makeWH(mipSize.fWidth, mipSize.fHeight);
            pixelSize += SkAlign8(SkAutoPixmapStorage::AllocSize(mipInfo, nullptr));
        }
    }
    size_t size = 0;
    size_t dtiSize = SkAlign8(sizeof(DeferredTextureImage));
    size += dtiSize;
    size += (mipMapLevelCount - 1) * sizeof(MipMapLevelData);
    // We subtract 1 because DeferredTextureImage already includes the base
    // level in its size
    size_t pixelOffset = size;
    size += pixelSize;
    size_t ctOffset = size;
    size += ctSize;
    size_t colorSpaceOffset = 0;
    size_t colorSpaceSize = 0;
    if (info.colorSpace()) {
        colorSpaceOffset = size;
        colorSpaceSize = info.colorSpace()->writeToMemory(nullptr);
        size += colorSpaceSize;
    }
    if (!fillMode) {
        return size;
    }
    char* bufferAsCharPtr = reinterpret_cast<char*>(buffer);
    char* pixelsAsCharPtr = bufferAsCharPtr + pixelOffset;
    void* pixels = pixelsAsCharPtr;
    void* ct = nullptr;
    if (ctSize) {
        ct = bufferAsCharPtr + ctOffset;
    }

    memcpy(reinterpret_cast<void*>(SkAlign8(reinterpret_cast<uintptr_t>(pixelsAsCharPtr))),
                                   pixmap.addr(), pixmap.getSafeSize());
    if (ctSize) {
        memcpy(ct, pixmap.ctable()->readColors(), ctSize);
    }

    // If the context has sRGB support, and we're intending to render to a surface with an attached
    // color space, and the image has an sRGB-like color space attached, then use our gamma (sRGB)
    // aware mip-mapping.
    SkSourceGammaTreatment gammaTreatment = SkSourceGammaTreatment::kIgnore;
    if (proxy.fCaps->srgbSupport() && SkToBool(dstColorSpace) &&
        info.colorSpace() && info.colorSpace()->gammaCloseToSRGB()) {
        gammaTreatment = SkSourceGammaTreatment::kRespect;
    }

    SkASSERT(info == pixmap.info());
    size_t rowBytes = pixmap.rowBytes();
    static_assert(std::is_standard_layout<DeferredTextureImage>::value,
                  "offsetof, which we use below, requires the type have standard layout");
    auto dtiBufferFiller = DTIBufferFiller{bufferAsCharPtr};
    FILL_MEMBER(dtiBufferFiller, fGammaTreatment, &gammaTreatment);
    FILL_MEMBER(dtiBufferFiller, fContextUniqueID, &proxy.fContextUniqueID);
    int width = info.width();
    FILL_MEMBER(dtiBufferFiller, fWidth, &width);
    int height = info.height();
    FILL_MEMBER(dtiBufferFiller, fHeight, &height);
    SkColorType colorType = info.colorType();
    FILL_MEMBER(dtiBufferFiller, fColorType, &colorType);
    SkAlphaType alphaType = info.alphaType();
    FILL_MEMBER(dtiBufferFiller, fAlphaType, &alphaType);
    FILL_MEMBER(dtiBufferFiller, fColorTableCnt, &ctCount);
    FILL_MEMBER(dtiBufferFiller, fColorTableData, &ct);
    FILL_MEMBER(dtiBufferFiller, fMipMapLevelCount, &mipMapLevelCount);
    memcpy(bufferAsCharPtr + offsetof(DeferredTextureImage, fMipMapLevelData[0].fPixelData),
           &pixels, sizeof(pixels));
    memcpy(bufferAsCharPtr + offsetof(DeferredTextureImage, fMipMapLevelData[0].fRowBytes),
           &rowBytes, sizeof(rowBytes));
    if (colorSpaceSize) {
        void* colorSpace = bufferAsCharPtr + colorSpaceOffset;
        FILL_MEMBER(dtiBufferFiller, fColorSpace, &colorSpace);
        FILL_MEMBER(dtiBufferFiller, fColorSpaceSize, &colorSpaceSize);
        info.colorSpace()->writeToMemory(bufferAsCharPtr + colorSpaceOffset);
    } else {
        memset(bufferAsCharPtr + offsetof(DeferredTextureImage, fColorSpace),
               0, sizeof(DeferredTextureImage::fColorSpace));
        memset(bufferAsCharPtr + offsetof(DeferredTextureImage, fColorSpaceSize),
               0, sizeof(DeferredTextureImage::fColorSpaceSize));
    }

    // Fill in the mipmap levels if they exist
    char* mipLevelPtr = pixelsAsCharPtr + SkAlign8(pixmap.getSafeSize());

    if (useMipMaps) {
        static_assert(std::is_standard_layout<MipMapLevelData>::value,
                      "offsetof, which we use below, requires the type have a standard layout");

        SkAutoTDelete<SkMipMap> mipmaps(SkMipMap::Build(pixmap, gammaTreatment, nullptr));
        // SkMipMap holds only the mipmap levels it generates.
        // A programmer can use the data they provided to SkMipMap::Build as level 0.
        // So the SkMipMap provides levels 1-x but it stores them in its own
        // range 0-(x-1).
        for (int generatedMipLevelIndex = 0; generatedMipLevelIndex < mipMapLevelCount - 1;
             generatedMipLevelIndex++) {
            SkMipMap::Level mipLevel;
            mipmaps->getLevel(generatedMipLevelIndex, &mipLevel);

            // Make sure the mipmap data is after the start of the buffer
            SkASSERT(mipLevelPtr > bufferAsCharPtr);
            // Make sure the mipmap data starts before the end of the buffer
            SkASSERT(mipLevelPtr < bufferAsCharPtr + pixelOffset + pixelSize);
            // Make sure the mipmap data ends before the end of the buffer
            SkASSERT(mipLevelPtr + mipLevel.fPixmap.getSafeSize() <=
                     bufferAsCharPtr + pixelOffset + pixelSize);

            // getSafeSize includes rowbyte padding except for the last row,
            // right?

            memcpy(mipLevelPtr, mipLevel.fPixmap.addr(), mipLevel.fPixmap.getSafeSize());

            memcpy(bufferAsCharPtr + offsetof(DeferredTextureImage, fMipMapLevelData) +
                   sizeof(MipMapLevelData) * (generatedMipLevelIndex + 1) +
                   offsetof(MipMapLevelData, fPixelData), &mipLevelPtr, sizeof(void*));
            size_t rowBytes = mipLevel.fPixmap.rowBytes();
            memcpy(bufferAsCharPtr + offsetof(DeferredTextureImage, fMipMapLevelData) +
                   sizeof(MipMapLevelData) * (generatedMipLevelIndex + 1) +
                   offsetof(MipMapLevelData, fRowBytes), &rowBytes, sizeof(rowBytes));

            mipLevelPtr += SkAlign8(mipLevel.fPixmap.getSafeSize());
        }
    }
    return size;
}
    bool addPathToAtlas(GrVertexBatch::Target* target,
                        FlushInfo* flushInfo,
                        GrBatchAtlas* atlas,
                        ShapeData* shapeData,
                        const GrShape& shape,
                        bool antiAlias,
                        uint32_t dimension,
                        SkScalar scale) const {
        const SkRect& bounds = shape.bounds();

        // generate bounding rect for bitmap draw
        SkRect scaledBounds = bounds;
        // scale to mip level size
        scaledBounds.fLeft *= scale;
        scaledBounds.fTop *= scale;
        scaledBounds.fRight *= scale;
        scaledBounds.fBottom *= scale;
        // move the origin to an integer boundary (gives better results)
        SkScalar dx = SkScalarFraction(scaledBounds.fLeft);
        SkScalar dy = SkScalarFraction(scaledBounds.fTop);
        scaledBounds.offset(-dx, -dy);
        // get integer boundary
        SkIRect devPathBounds;
        scaledBounds.roundOut(&devPathBounds);
        // pad to allow room for antialiasing
        const int intPad = SkScalarCeilToInt(kAntiAliasPad);
        // pre-move origin (after outset, will be 0,0)
        int width = devPathBounds.width();
        int height = devPathBounds.height();
        devPathBounds.fLeft = intPad;
        devPathBounds.fTop = intPad;
        devPathBounds.fRight = intPad + width;
        devPathBounds.fBottom = intPad + height;
        devPathBounds.outset(intPad, intPad);

        // draw path to bitmap
        SkMatrix drawMatrix;
        drawMatrix.setTranslate(-bounds.left(), -bounds.top());
        drawMatrix.postScale(scale, scale);
        drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad);

        // setup bitmap backing
        SkASSERT(devPathBounds.fLeft == 0);
        SkASSERT(devPathBounds.fTop == 0);
        SkAutoPixmapStorage dst;
        if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
                                              devPathBounds.height()))) {
            return false;
        }
        sk_bzero(dst.writable_addr(), dst.getSafeSize());

        // rasterize path
        SkPaint paint;
        paint.setStyle(SkPaint::kFill_Style);
        paint.setAntiAlias(antiAlias);

        SkDraw draw;
        sk_bzero(&draw, sizeof(draw));

        SkRasterClip rasterClip;
        rasterClip.setRect(devPathBounds);
        draw.fRC = &rasterClip;
        draw.fMatrix = &drawMatrix;
        draw.fDst = dst;

        SkPath path;
        shape.asPath(&path);
        draw.drawPathCoverage(path, paint);

        // generate signed distance field
        devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
        width = devPathBounds.width();
        height = devPathBounds.height();
        // TODO We should really generate this directly into the plot somehow
        SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));

        // Generate signed distance field
        SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
                                           (const unsigned char*)dst.addr(),
                                           dst.width(), dst.height(), dst.rowBytes());

        // add to atlas
        SkIPoint16 atlasLocation;
        GrBatchAtlas::AtlasID id;
       if (!atlas->addToAtlas(&id, target, width, height, dfStorage.get(), &atlasLocation)) {
            this->flush(target, flushInfo);
            if (!atlas->addToAtlas(&id, target, width, height, dfStorage.get(), &atlasLocation)) {
                return false;
            }
        }

        // add to cache
        shapeData->fKey.set(shape, dimension);
        shapeData->fScale = scale;
        shapeData->fID = id;
        // change the scaled rect to match the size of the inset distance field
        scaledBounds.fRight = scaledBounds.fLeft +
            SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset);
        scaledBounds.fBottom = scaledBounds.fTop +
            SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset);
        // shift the origin to the correct place relative to the distance field
        // need to also restore the fractional translation
        scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx,
                            -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy);
        shapeData->fBounds = scaledBounds;
        // origin we render from is inset from distance field edge
        atlasLocation.fX += SK_DistanceFieldInset;
        atlasLocation.fY += SK_DistanceFieldInset;
        shapeData->fAtlasLocation = atlasLocation;

        fShapeCache->add(shapeData);
        fShapeList->addToTail(shapeData);
#ifdef DF_PATH_TRACKING
        ++g_NumCachedPaths;
#endif
        return true;
    }
Esempio n. 8
0
size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& proxy,
                                            const DeferredTextureImageUsageParams[],
                                            int paramCnt, void* buffer) const {
    const bool fillMode = SkToBool(buffer);
    if (fillMode && !SkIsAlign8(reinterpret_cast<intptr_t>(buffer))) {
        return 0;
    }

    const int maxTextureSize = proxy.fCaps->maxTextureSize();
    if (width() > maxTextureSize || height() > maxTextureSize) {
        return 0;
    }

    SkAutoPixmapStorage pixmap;
    SkImageInfo info;
    size_t pixelSize = 0;
    size_t ctSize = 0;
    int ctCount = 0;
    if (this->peekPixels(&pixmap)) {
        info = pixmap.info();
        pixelSize = SkAlign8(pixmap.getSafeSize());
        if (pixmap.ctable()) {
            ctCount = pixmap.ctable()->count();
            ctSize = SkAlign8(pixmap.ctable()->count() * 4);
        }
    } else {
        // Here we're just using presence of data to know whether there is a codec behind the image.
        // In the future we will access the cacherator and get the exact data that we want to (e.g.
        // yuv planes) upload.
        SkAutoTUnref<SkData> data(this->refEncoded());
        if (!data) {
            return 0;
        }
        SkAlphaType at = this->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
        info = SkImageInfo::MakeN32(this->width(), this->height(), at);
        pixelSize = SkAlign8(SkAutoPixmapStorage::AllocSize(info, nullptr));
        if (fillMode) {
            pixmap.alloc(info);
            if (!this->readPixels(pixmap, 0, 0, SkImage::kDisallow_CachingHint)) {
                return 0;
            }
            SkASSERT(!pixmap.ctable());
        }
    }
    size_t size = 0;
    size_t dtiSize = SkAlign8(sizeof(DeferredTextureImage));
    size += dtiSize;
    size_t pixelOffset = size;
    size += pixelSize;
    size_t ctOffset = size;
    size += ctSize;
    if (!fillMode) {
        return size;
    }
    intptr_t bufferAsInt = reinterpret_cast<intptr_t>(buffer);
    void* pixels = reinterpret_cast<void*>(bufferAsInt + pixelOffset);
    SkPMColor* ct = nullptr;
    if (ctSize) {
        ct = reinterpret_cast<SkPMColor*>(bufferAsInt + ctOffset);
    }

    memcpy(pixels, pixmap.addr(), pixmap.getSafeSize());
    if (ctSize) {
        memcpy(ct, pixmap.ctable()->readColors(), ctSize);
    }

    SkASSERT(info == pixmap.info());
    size_t rowBytes = pixmap.rowBytes();
    DeferredTextureImage* dti = new (buffer) DeferredTextureImage();
    dti->fContextUniqueID = proxy.fContextUniqueID;
    dti->fData.fInfo = info;
    dti->fData.fPixelData = pixels;
    dti->fData.fRowBytes = rowBytes;
    dti->fData.fColorTableCnt = ctCount;
    dti->fData.fColorTableData = ct;
    return size;
}