SkImageInfo SkImage_Lazy::buildCacheInfo(CachedFormat format) const { switch (format) { case kLegacy_CachedFormat: return fInfo.makeColorSpace(nullptr); case kLinearF16_CachedFormat: return fInfo.makeColorType(kRGBA_F16_SkColorType) .makeColorSpace(fInfo.colorSpace()->makeLinearGamma()); case kSRGB8888_CachedFormat: // If the transfer function is nearly (but not exactly) sRGB, we don't want the codec // to bother trans-coding. It would be slow, and do more harm than good visually, // so we make sure to leave the colorspace as-is. if (fInfo.colorSpace()->gammaCloseToSRGB()) { return fInfo.makeColorType(kRGBA_8888_SkColorType); } else { return fInfo.makeColorType(kRGBA_8888_SkColorType) .makeColorSpace(fInfo.colorSpace()->makeSRGBGamma()); } case kSBGR8888_CachedFormat: // See note above about not-quite-sRGB transfer functions. if (fInfo.colorSpace()->gammaCloseToSRGB()) { return fInfo.makeColorType(kBGRA_8888_SkColorType); } else { return fInfo.makeColorType(kBGRA_8888_SkColorType) .makeColorSpace(fInfo.colorSpace()->makeSRGBGamma()); } default: SkDEBUGFAIL("Invalid cached format"); return fInfo; } }
bool SkSurface_Gpu::Valid(const SkImageInfo& info) { switch (info.colorType()) { case kRGBA_F16_SkColorType: return (!info.colorSpace()) || info.colorSpace()->gammaIsLinear(); case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: return !info.colorSpace() || info.colorSpace()->gammaCloseToSRGB(); default: return !info.colorSpace(); } }
bool SkPngEncoderMgr::setColorSpace(const SkImageInfo& info) { if (setjmp(png_jmpbuf(fPngPtr))) { return false; } if (info.colorSpace() && info.colorSpace()->isSRGB()) { png_set_sRGB(fPngPtr, fInfoPtr, PNG_sRGB_INTENT_PERCEPTUAL); } else { set_icc(fPngPtr, fInfoPtr, info); } return true; }
DEF_TEST(ColorSpace_Named, r) { const struct { SkColorSpace::Named fNamed; bool fExpectedToSucceed; bool fIsSRGB; } recs[] { { SkColorSpace::kUnknown_Named, false, false }, { SkColorSpace::kSRGB_Named, true, true }, { SkColorSpace::kAdobeRGB_Named, true, false }, }; for (auto rec : recs) { auto cs = SkColorSpace::NewNamed(rec.fNamed); REPORTER_ASSERT(r, !cs == !rec.fExpectedToSucceed); if (cs) { if (rec.fIsSRGB) { REPORTER_ASSERT(r, SkColorSpace::kSRGB_GammaNamed == cs->gammaNamed()); } else { REPORTER_ASSERT(r, SkColorSpace::k2Dot2Curve_GammaNamed == cs->gammaNamed()); } } } SkImageInfo info = SkImageInfo::MakeS32(10, 10, kPremul_SkAlphaType); REPORTER_ASSERT(r, kSRGB_SkColorProfileType == info.profileType()); REPORTER_ASSERT(r, SkColorSpace::kSRGB_GammaNamed == info.colorSpace()->gammaNamed()); }
sk_sp<SkImage> SkSurface_Gpu::onNewImageSnapshot(SkBudgeted budgeted, ForceCopyMode forceCopyMode) { GrRenderTarget* rt = fDevice->accessDrawContext()->accessRenderTarget(); SkASSERT(rt); GrTexture* tex = rt->asTexture(); SkAutoTUnref<GrTexture> copy; // If the original render target is a buffer originally created by the client, then we don't // want to ever retarget the SkSurface at another buffer we create. Force a copy now to avoid // copy-on-write. if (kYes_ForceCopyMode == forceCopyMode || !tex || rt->resourcePriv().refsWrappedObjects()) { GrSurfaceDesc desc = fDevice->accessDrawContext()->desc(); GrContext* ctx = fDevice->context(); desc.fFlags = desc.fFlags & ~kRenderTarget_GrSurfaceFlag; copy.reset(ctx->textureProvider()->createTexture(desc, budgeted)); if (!copy) { return nullptr; } if (!ctx->copySurface(copy, rt)) { return nullptr; } tex = copy; } const SkImageInfo info = fDevice->imageInfo(); sk_sp<SkImage> image; if (tex) { image = sk_make_sp<SkImage_Gpu>(info.width(), info.height(), kNeedNewImageUniqueID, info.alphaType(), tex, sk_ref_sp(info.colorSpace()), budgeted); } return image; }
sk_sp<SkImage> SkImage::MakeFromBitmap(const SkBitmap& bm) { SkPixelRef* pr = bm.pixelRef(); if (nullptr == pr) { return nullptr; } #if SK_SUPPORT_GPU if (GrTexture* tex = pr->getTexture()) { SkAutoTUnref<GrTexture> unrefCopy; if (!bm.isImmutable()) { tex = GrDeepCopyTexture(tex, SkBudgeted::kNo); if (nullptr == tex) { return nullptr; } unrefCopy.reset(tex); } const SkImageInfo info = bm.info(); return sk_make_sp<SkImage_Gpu>(info.width(), info.height(), bm.getGenerationID(), info.alphaType(), tex, sk_ref_sp(info.colorSpace()), SkBudgeted::kNo); } #endif // This will check for immutable (share or copy) return SkMakeImageFromRasterBitmap(bm); }
bool SkImage_Lazy::onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, int srcX, int srcY, CachingHint chint) const { SkColorSpace* dstColorSpace = dstInfo.colorSpace(); SkBitmap bm; if (kDisallow_CachingHint == chint) { CachedFormat cacheFormat = this->chooseCacheFormat(dstColorSpace); SkImageInfo genPixelsInfo = dstInfo; SkTransferFunctionBehavior behavior = getGeneratorBehaviorAndInfo(&genPixelsInfo); if (this->lockAsBitmapOnlyIfAlreadyCached(&bm, cacheFormat)) { return bm.readPixels(dstInfo, dstPixels, dstRB, srcX, srcY); } else { // Try passing the caller's buffer directly down to the generator. If this fails we // may still succeed in the general case, as the generator may prefer some other // config, which we could then convert via SkBitmap::readPixels. if (this->directGeneratePixels(genPixelsInfo, dstPixels, dstRB, srcX, srcY, behavior)) { return true; } // else fall through } } if (this->getROPixels(&bm, dstColorSpace, chint)) { return bm.readPixels(dstInfo, dstPixels, dstRB, srcX, srcY); } return false; }
SkSurfaceCharacterization GrContextThreadSafeProxy::createCharacterization( size_t cacheMaxResourceBytes, const SkImageInfo& ii, const GrBackendFormat& backendFormat, int sampleCnt, GrSurfaceOrigin origin, const SkSurfaceProps& surfaceProps, bool isMipMapped, bool willUseGLFBO0, bool isTextureable) { if (!backendFormat.isValid()) { return SkSurfaceCharacterization(); // return an invalid characterization } if (GrBackendApi::kOpenGL != backendFormat.backend() && willUseGLFBO0) { // The willUseGLFBO0 flags can only be used for a GL backend. return SkSurfaceCharacterization(); // return an invalid characterization } if (!this->caps()->mipMapSupport()) { isMipMapped = false; } GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendFormat, ii.colorType()); if (config == kUnknown_GrPixelConfig) { return SkSurfaceCharacterization(); // return an invalid characterization } if (!SkSurface_Gpu::Valid(this->caps(), config, ii.colorSpace())) { return SkSurfaceCharacterization(); // return an invalid characterization } sampleCnt = this->caps()->getRenderTargetSampleCount(sampleCnt, config); if (!sampleCnt) { return SkSurfaceCharacterization(); // return an invalid characterization } GrFSAAType FSAAType = GrFSAAType::kNone; if (sampleCnt > 1) { FSAAType = this->caps()->usesMixedSamples() ? GrFSAAType::kMixedSamples : GrFSAAType::kUnifiedMSAA; } if (willUseGLFBO0 && isTextureable) { return SkSurfaceCharacterization(); // return an invalid characterization } if (isTextureable && !this->caps()->isConfigTexturable(config)) { // Skia doesn't agree that this is textureable. return SkSurfaceCharacterization(); // return an invalid characterization } return SkSurfaceCharacterization(sk_ref_sp<GrContextThreadSafeProxy>(this), cacheMaxResourceBytes, ii, origin, config, FSAAType, sampleCnt, SkSurfaceCharacterization::Textureable(isTextureable), SkSurfaceCharacterization::MipMapped(isMipMapped), SkSurfaceCharacterization::UsesGLFBO0(willUseGLFBO0), SkSurfaceCharacterization::VulkanSecondaryCBCompatible(false), surfaceProps); }
bool SkJpegCodec::initializeColorXform(const SkImageInfo& dstInfo, bool needsColorXform) { if (needsColorXform) { fColorXform = SkColorSpaceXform::New(sk_ref_sp(this->getInfo().colorSpace()), sk_ref_sp(dstInfo.colorSpace())); if (!fColorXform && kRGBA_F16_SkColorType == dstInfo.colorType()) { return false; } } return true; }
sk_sp<SkSpecialSurface> onMakeSurface(const SkImageInfo& info) const override { if (!fTexture->getContext()) { return nullptr; } GrPixelConfig config = SkImageInfo2GrPixelConfig(info, *fTexture->getContext()->caps()); return SkSpecialSurface::MakeRenderTarget(fTexture->getContext(), info.width(), info.height(), config, sk_ref_sp(info.colorSpace())); }
bool SkPngCodec::initializeXforms(const SkImageInfo& dstInfo, const Options& options, SkPMColor ctable[], int* ctableCount) { if (setjmp(png_jmpbuf(fPng_ptr))) { SkCodecPrintf("Failed on png_read_update_info.\n"); return false; } png_read_update_info(fPng_ptr, fInfo_ptr); // It's important to reset fColorXform to nullptr. We don't do this on rewinding // because the interlaced scanline decoder may need to rewind. fColorXform = nullptr; SkImageInfo swizzlerInfo = dstInfo; bool needsColorXform = needs_color_xform(dstInfo, this->getInfo()); if (needsColorXform) { switch (dstInfo.colorType()) { case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: case kRGBA_F16_SkColorType: swizzlerInfo = swizzlerInfo.makeColorType(kRGBA_8888_SkColorType); if (kPremul_SkAlphaType == dstInfo.alphaType()) { swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); } break; case kIndex_8_SkColorType: break; default: return false; } fColorXform = SkColorSpaceXform::New(sk_ref_sp(this->getInfo().colorSpace()), sk_ref_sp(dstInfo.colorSpace())); if (!fColorXform && kRGBA_F16_SkColorType == dstInfo.colorType()) { return false; } } if (SkEncodedInfo::kPalette_Color == this->getEncodedInfo().color()) { if (!this->createColorTable(dstInfo, ctableCount)) { return false; } } // Copy the color table to the client if they request kIndex8 mode copy_color_table(swizzlerInfo, fColorTable, ctable, ctableCount); // Create the swizzler. SkPngCodec retains ownership of the color table. const SkPMColor* colors = get_color_ptr(fColorTable.get()); fSwizzler.reset(SkSwizzler::CreateSwizzler(this->getEncodedInfo(), colors, swizzlerInfo, options)); SkASSERT(fSwizzler); return true; }
sk_sp<SkImage> SkImage::MakeTextureFromMipMap(GrContext* ctx, const SkImageInfo& info, const GrMipLevel* texels, int mipLevelCount, SkBudgeted budgeted, SkSourceGammaTreatment gammaTreatment) { if (!ctx) { return nullptr; } sk_sp<GrTexture> texture(GrUploadMipMapToTexture(ctx, info, texels, mipLevelCount)); if (!texture) { return nullptr; } texture->texturePriv().setGammaTreatment(gammaTreatment); return sk_make_sp<SkImage_Gpu>(texture->width(), texture->height(), kNeedNewImageUniqueID, info.alphaType(), std::move(texture), sk_ref_sp(info.colorSpace()), budgeted); }
/* * Checks if the conversion between the input image and the requested output * image has been implemented * Sets the output color format */ bool SkHeifCodec::setOutputColorFormat(const SkImageInfo& dstInfo) { if (kUnknown_SkAlphaType == dstInfo.alphaType()) { return false; } if (kOpaque_SkAlphaType != dstInfo.alphaType()) { SkCodecPrintf("Warning: an opaque image should be decoded as opaque " "- it is being decoded as non-opaque, which will draw slower\n"); } switch (dstInfo.colorType()) { case kRGBA_8888_SkColorType: return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888); case kBGRA_8888_SkColorType: return fHeifDecoder->setOutputColor(kHeifColorFormat_BGRA_8888); case kRGB_565_SkColorType: if (this->colorXform()) { return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888); } else { return fHeifDecoder->setOutputColor(kHeifColorFormat_RGB565); } case kRGBA_F16_SkColorType: SkASSERT(this->colorXform()); if (!dstInfo.colorSpace()->gammaIsLinear()) { return false; } return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888); default: return false; } }
/* * We have 4 ways to try to return a texture (in sorted order) * * 1. Check the cache for a pre-existing one * 2. Ask the generator to natively create one * 3. Ask the generator to return YUV planes, which the GPU can convert * 4. Ask the generator to return RGB(A) data, which the GPU can convert */ sk_sp<GrTextureProxy> SkImage_Lazy::lockTextureProxy(GrContext* ctx, const GrUniqueKey& origKey, SkImage::CachingHint chint, bool willBeMipped, SkColorSpace* dstColorSpace, GrTextureMaker::AllowedTexGenType genType) { // Values representing the various texture lock paths we can take. Used for logging the path // taken to a histogram. enum LockTexturePath { kFailure_LockTexturePath, kPreExisting_LockTexturePath, kNative_LockTexturePath, kCompressed_LockTexturePath, // Deprecated kYUV_LockTexturePath, kRGBA_LockTexturePath, }; enum { kLockTexturePathCount = kRGBA_LockTexturePath + 1 }; // Determine which cached format we're going to use (which may involve decoding to a different // info than the generator provides). CachedFormat format = this->chooseCacheFormat(dstColorSpace, ctx->caps()); // Fold the cache format into our texture key GrUniqueKey key; this->makeCacheKeyFromOrigKey(origKey, format, &key); GrProxyProvider* proxyProvider = ctx->contextPriv().proxyProvider(); sk_sp<GrTextureProxy> proxy; // 1. Check the cache for a pre-existing one if (key.isValid()) { proxy = proxyProvider->findOrCreateProxyByUniqueKey(key, kTopLeft_GrSurfaceOrigin); if (proxy) { SK_HISTOGRAM_ENUMERATION("LockTexturePath", kPreExisting_LockTexturePath, kLockTexturePathCount); if (!willBeMipped || GrMipMapped::kYes == proxy->mipMapped()) { return proxy; } } } // The CachedFormat is both an index for which cache "slot" we'll use to store this particular // decoded variant of the encoded data, and also a recipe for how to transform the original // info to get the one that we're going to decode to. const SkImageInfo cacheInfo = this->buildCacheInfo(format); SkImageInfo genPixelsInfo = cacheInfo; SkTransferFunctionBehavior behavior = getGeneratorBehaviorAndInfo(&genPixelsInfo); // 2. Ask the generator to natively create one if (!proxy) { ScopedGenerator generator(fSharedGenerator); if (GrTextureMaker::AllowedTexGenType::kCheap == genType && SkImageGenerator::TexGenType::kCheap != generator->onCanGenerateTexture()) { return nullptr; } if ((proxy = generator->generateTexture(ctx, genPixelsInfo, fOrigin, behavior, willBeMipped))) { SK_HISTOGRAM_ENUMERATION("LockTexturePath", kNative_LockTexturePath, kLockTexturePathCount); set_key_on_proxy(proxyProvider, proxy.get(), nullptr, key); if (!willBeMipped || GrMipMapped::kYes == proxy->mipMapped()) { return proxy; } } } // 3. Ask the generator to return YUV planes, which the GPU can convert. If we will be mipping // the texture we fall through here and have the CPU generate the mip maps for us. if (!proxy && !willBeMipped && !ctx->contextPriv().disableGpuYUVConversion()) { const GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(cacheInfo, *ctx->caps()); ScopedGenerator generator(fSharedGenerator); Generator_GrYUVProvider provider(generator); // The pixels in the texture will be in the generator's color space. If onMakeColorSpace // has been called then this will not match this image's color space. To correct this, apply // a color space conversion from the generator's color space to this image's color space. const SkColorSpace* generatorColorSpace = fSharedGenerator->fGenerator->getInfo().colorSpace(); const SkColorSpace* thisColorSpace = fInfo.colorSpace(); // TODO: Update to create the mipped surface in the YUV generator and draw the base layer // directly into the mipped surface. proxy = provider.refAsTextureProxy(ctx, desc, generatorColorSpace, thisColorSpace); if (proxy) { SK_HISTOGRAM_ENUMERATION("LockTexturePath", kYUV_LockTexturePath, kLockTexturePathCount); set_key_on_proxy(proxyProvider, proxy.get(), nullptr, key); return proxy; } } // 4. Ask the generator to return RGB(A) data, which the GPU can convert SkBitmap bitmap; if (!proxy && this->lockAsBitmap(&bitmap, chint, format, genPixelsInfo, behavior)) { if (willBeMipped) { proxy = proxyProvider->createMipMapProxyFromBitmap(bitmap, dstColorSpace); } if (!proxy) { proxy = GrUploadBitmapToTextureProxy(proxyProvider, bitmap, dstColorSpace); } if (proxy && (!willBeMipped || GrMipMapped::kYes == proxy->mipMapped())) { SK_HISTOGRAM_ENUMERATION("LockTexturePath", kRGBA_LockTexturePath, kLockTexturePathCount); set_key_on_proxy(proxyProvider, proxy.get(), nullptr, key); return proxy; } } if (proxy) { // We need a mipped proxy, but we either found a proxy earlier that wasn't mipped, generated // a native non mipped proxy, or generated a non-mipped yuv proxy. Thus we generate a new // mipped surface and copy the original proxy into the base layer. We will then let the gpu // generate the rest of the mips. SkASSERT(willBeMipped); SkASSERT(GrMipMapped::kNo == proxy->mipMapped()); if (auto mippedProxy = GrCopyBaseMipMapToTextureProxy(ctx, proxy.get())) { set_key_on_proxy(proxyProvider, mippedProxy.get(), proxy.get(), key); return mippedProxy; } // We failed to make a mipped proxy with the base copied into it. This could have // been from failure to make the proxy or failure to do the copy. Thus we will fall // back to just using the non mipped proxy; See skbug.com/7094. return proxy; } SK_HISTOGRAM_ENUMERATION("LockTexturePath", kFailure_LockTexturePath, kLockTexturePathCount); return nullptr; }
GrPixelConfig SkImageInfo2GrPixelConfig(const SkImageInfo& info, const GrCaps& caps) { return SkImageInfo2GrPixelConfig(info.colorType(), info.colorSpace(), caps); }
SkImageCacherator::CachedFormat SkImage_Lazy::chooseCacheFormat(SkColorSpace* dstColorSpace, const GrCaps* grCaps) const { SkColorSpace* cs = fInfo.colorSpace(); if (!cs || !dstColorSpace) { return kLegacy_CachedFormat; } CacheCaps caps(grCaps); switch (fInfo.colorType()) { case kUnknown_SkColorType: case kAlpha_8_SkColorType: case kRGB_565_SkColorType: case kARGB_4444_SkColorType: case kRGB_888x_SkColorType: case kRGBA_1010102_SkColorType: case kRGB_101010x_SkColorType: // We don't support color space on these formats, so always decode in legacy mode: // TODO: Ask the codec to decode these to something else (at least sRGB 8888)? return kLegacy_CachedFormat; case kGray_8_SkColorType: // TODO: What do we do with grayscale sources that have strange color spaces attached? // The codecs and color space xform don't handle this correctly (yet), so drop it on // the floor. (Also, inflating by a factor of 8 is going to be unfortunate). // As it is, we don't directly support sRGB grayscale, so ask the codec to convert // it for us. This bypasses some really sketchy code GrUploadPixmapToTexture. if (cs->gammaCloseToSRGB() && caps.supportsSRGB()) { return kSRGB8888_CachedFormat; } else { return kLegacy_CachedFormat; } case kRGBA_8888_SkColorType: if (cs->gammaCloseToSRGB()) { if (caps.supportsSRGB()) { return kSRGB8888_CachedFormat; } else if (caps.supportsHalfFloat()) { return kLinearF16_CachedFormat; } else { return kLegacy_CachedFormat; } } else { if (caps.supportsHalfFloat()) { return kLinearF16_CachedFormat; } else if (caps.supportsSRGB()) { return kSRGB8888_CachedFormat; } else { return kLegacy_CachedFormat; } } case kBGRA_8888_SkColorType: // Odd case. sBGRA isn't a real thing, so we may not have this texturable. if (caps.supportsSBGR()) { if (cs->gammaCloseToSRGB()) { return kSBGR8888_CachedFormat; } else if (caps.supportsHalfFloat()) { return kLinearF16_CachedFormat; } else if (caps.supportsSRGB()) { return kSRGB8888_CachedFormat; } else { // sBGRA support without sRGBA is highly unlikely (impossible?) Nevertheless. return kLegacy_CachedFormat; } } else { if (cs->gammaCloseToSRGB()) { if (caps.supportsSRGB()) { return kSRGB8888_CachedFormat; } else if (caps.supportsHalfFloat()) { return kLinearF16_CachedFormat; } else { return kLegacy_CachedFormat; } } else { if (caps.supportsHalfFloat()) { return kLinearF16_CachedFormat; } else if (caps.supportsSRGB()) { return kSRGB8888_CachedFormat; } else { return kLegacy_CachedFormat; } } } case kRGBA_F16_SkColorType: if (caps.supportsHalfFloat()) { return kLinearF16_CachedFormat; } else if (caps.supportsSRGB()) { return kSRGB8888_CachedFormat; } else { return kLegacy_CachedFormat; } } SkDEBUGFAIL("Unreachable"); return kLegacy_CachedFormat; }
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 SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options) { auto chooseProc8888 = [&]() { if (kUnpremul_SkAlphaType != srcInfo.alphaType() || SkJpegEncoder::AlphaOption::kIgnore == options.fAlphaOption) { return (transform_scanline_proc) nullptr; } // Note that kRespect mode is only supported with sRGB or linear transfer functions. // The legacy code path is incidentally correct when the transfer function is linear. const bool isSRGBTransferFn = srcInfo.gammaCloseToSRGB() && (SkTransferFunctionBehavior::kRespect == options.fBlendBehavior); if (isSRGBTransferFn) { return transform_scanline_to_premul_linear; } else { return transform_scanline_to_premul_legacy; } }; J_COLOR_SPACE jpegColorType = JCS_EXT_RGBA; int numComponents = 0; switch (srcInfo.colorType()) { case kRGBA_8888_SkColorType: fProc = chooseProc8888(); jpegColorType = JCS_EXT_RGBA; numComponents = 4; break; case kBGRA_8888_SkColorType: fProc = chooseProc8888(); jpegColorType = JCS_EXT_BGRA; numComponents = 4; break; case kRGB_565_SkColorType: fProc = transform_scanline_565; jpegColorType = JCS_RGB; numComponents = 3; break; case kARGB_4444_SkColorType: if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) { return false; } fProc = transform_scanline_444; jpegColorType = JCS_RGB; numComponents = 3; break; case kGray_8_SkColorType: SkASSERT(srcInfo.isOpaque()); jpegColorType = JCS_GRAYSCALE; numComponents = 1; break; case kRGBA_F16_SkColorType: if (!srcInfo.colorSpace() || !srcInfo.colorSpace()->gammaIsLinear() || SkTransferFunctionBehavior::kRespect != options.fBlendBehavior) { return false; } if (kUnpremul_SkAlphaType != srcInfo.alphaType() || SkJpegEncoder::AlphaOption::kIgnore == options.fAlphaOption) { fProc = transform_scanline_F16_to_8888; } else { fProc = transform_scanline_F16_to_premul_8888; } jpegColorType = JCS_EXT_RGBA; numComponents = 4; break; default: return false; } fCInfo.image_width = srcInfo.width(); fCInfo.image_height = srcInfo.height(); fCInfo.in_color_space = jpegColorType; fCInfo.input_components = numComponents; jpeg_set_defaults(&fCInfo); if (kGray_8_SkColorType != srcInfo.colorType()) { switch (options.fDownsample) { case SkJpegEncoder::Downsample::k420: SkASSERT(2 == fCInfo.comp_info[0].h_samp_factor); SkASSERT(2 == fCInfo.comp_info[0].v_samp_factor); SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor); SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor); SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor); SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor); break; case SkJpegEncoder::Downsample::k422: fCInfo.comp_info[0].h_samp_factor = 2; fCInfo.comp_info[0].v_samp_factor = 1; fCInfo.comp_info[1].h_samp_factor = 1; fCInfo.comp_info[1].v_samp_factor = 1; fCInfo.comp_info[2].h_samp_factor = 1; fCInfo.comp_info[2].v_samp_factor = 1; break; case SkJpegEncoder::Downsample::k444: fCInfo.comp_info[0].h_samp_factor = 1; fCInfo.comp_info[0].v_samp_factor = 1; fCInfo.comp_info[1].h_samp_factor = 1; fCInfo.comp_info[1].v_samp_factor = 1; fCInfo.comp_info[2].h_samp_factor = 1; fCInfo.comp_info[2].v_samp_factor = 1; break; } } // Tells libjpeg-turbo to compute optimal Huffman coding tables // for the image. This improves compression at the cost of // slower encode performance. fCInfo.optimize_coding = TRUE; return true; }