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; } }
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; }
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; }
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; }