/* * High quality is implemented by performing up-right scale-only filtering and then * using bilerp for any remaining transformations. */ bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmap& origBitmap) { if (fQuality != kHigh_SkFilterQuality) { return false; } // Our default return state is to downgrade the request to Medium, w/ or w/o setting fBitmap // to a valid bitmap. If we succeed, we will set this to Low instead. fQuality = kMedium_SkFilterQuality; if (kN32_SkColorType != origBitmap.colorType() || !cache_size_okay(origBitmap, fInvMatrix) || fInvMatrix.hasPerspective()) { return false; // can't handle the reqeust } SkScalar invScaleX = fInvMatrix.getScaleX(); SkScalar invScaleY = fInvMatrix.getScaleY(); if (fInvMatrix.getType() & SkMatrix::kAffine_Mask) { SkSize scale; if (!fInvMatrix.decomposeScale(&scale)) { return false; } invScaleX = scale.width(); invScaleY = scale.height(); } if (SkScalarNearlyEqual(invScaleX, 1) && SkScalarNearlyEqual(invScaleY, 1)) { return false; // no need for HQ } SkScalar trueDestWidth = origBitmap.width() / invScaleX; SkScalar trueDestHeight = origBitmap.height() / invScaleY; SkScalar roundedDestWidth = SkScalarRoundToScalar(trueDestWidth); SkScalar roundedDestHeight = SkScalarRoundToScalar(trueDestHeight); if (!SkBitmapCache::Find(origBitmap, roundedDestWidth, roundedDestHeight, &fResultBitmap)) { SkAutoPixmapUnlock src; if (!origBitmap.requestLock(&src)) { return false; } if (!SkBitmapScaler::Resize(&fResultBitmap, src.pixmap(), SkBitmapScaler::RESIZE_BEST, roundedDestWidth, roundedDestHeight, SkResourceCache::GetAllocator())) { return false; // we failed to create fScaledBitmap } SkASSERT(fResultBitmap.getPixels()); fResultBitmap.setImmutable(); SkBitmapCache::Add(origBitmap, roundedDestWidth, roundedDestHeight, fResultBitmap); } SkASSERT(fResultBitmap.getPixels()); fInvMatrix.postScale(roundedDestWidth / origBitmap.width(), roundedDestHeight / origBitmap.height()); fQuality = kLow_SkFilterQuality; return true; }
bool SkBitmapProcState::possiblyScaleImage() { SkASSERT(NULL == fBitmap); fAdjustedMatrix = false; if (fFilterLevel <= SkPaint::kLow_FilterLevel) { return false; } // Check to see if the transformation matrix is simple, and if we're // doing high quality scaling. If so, do the bitmap scale here and // remove the (non-fractional) scaling component from the matrix. SkScalar invScaleX = fInvMatrix.getScaleX(); SkScalar invScaleY = fInvMatrix.getScaleY(); float trueDestWidth = fOrigBitmap.width() / invScaleX; float trueDestHeight = fOrigBitmap.height() / invScaleY; #ifndef SK_IGNORE_PROPER_FRACTIONAL_SCALING float roundedDestWidth = SkScalarRoundToScalar(trueDestWidth); float roundedDestHeight = SkScalarRoundToScalar(trueDestHeight); #else float roundedDestWidth = trueDestWidth; float roundedDestHeight = trueDestHeight; #endif if (SkPaint::kHigh_FilterLevel == fFilterLevel && fInvMatrix.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask) && kN32_SkColorType == fOrigBitmap.colorType() && cache_size_okay(fOrigBitmap, fInvMatrix)) { if (SkScalarNearlyEqual(invScaleX,1.0f) && SkScalarNearlyEqual(invScaleY,1.0f)) { // short-circuit identity scaling; the output is supposed to // be the same as the input, so we might as well go fast. // Note(humper): We could also probably do this if the scales // are close to -1 as well, since the flip doesn't require // any fancy re-sampling... // Set our filter level to low -- the only post-filtering this // image might require is some interpolation if the translation // is fractional. fFilterLevel = SkPaint::kLow_FilterLevel; return false; } if (!SkBitmapCache::Find(fOrigBitmap, roundedDestWidth, roundedDestHeight, &fScaledBitmap)) { // All the criteria are met; let's make a new bitmap. if (!SkBitmapScaler::Resize(&fScaledBitmap, fOrigBitmap, SkBitmapScaler::RESIZE_BEST, roundedDestWidth, roundedDestHeight, SkResourceCache::GetAllocator())) { // we failed to create fScaledBitmap, so just return and let // the scanline proc handle it. return false; } SkASSERT(fScaledBitmap.getPixels()); fScaledBitmap.setImmutable(); SkBitmapCache::Add(fOrigBitmap, roundedDestWidth, roundedDestHeight, fScaledBitmap); } SkASSERT(fScaledBitmap.getPixels()); fBitmap = &fScaledBitmap; // set the inv matrix type to translate-only; fInvMatrix.setTranslate(fInvMatrix.getTranslateX() / fInvMatrix.getScaleX(), fInvMatrix.getTranslateY() / fInvMatrix.getScaleY()); #ifndef SK_IGNORE_PROPER_FRACTIONAL_SCALING // reintroduce any fractional scaling missed by our integral scale done above. float fractionalScaleX = roundedDestWidth/trueDestWidth; float fractionalScaleY = roundedDestHeight/trueDestHeight; fInvMatrix.postScale(fractionalScaleX, fractionalScaleY); #endif fAdjustedMatrix = true; // Set our filter level to low -- the only post-filtering this // image might require is some interpolation if the translation // is fractional or if there's any remaining scaling to be done. fFilterLevel = SkPaint::kLow_FilterLevel; return true; } /* * If High, then our special-case for scale-only did not take, and so we * have to make a choice: * 1. fall back on mipmaps + bilerp * 2. fall back on scanline bicubic filter * For now, we compute the "scale" value from the matrix, and have a * threshold to decide when bicubic is better, and when mips are better. * No doubt a fancier decision tree could be used uere. * * If Medium, then we just try to build a mipmap and select a level, * setting the filter-level to kLow to signal that we just need bilerp * to process the selected level. */ SkScalar scaleSqd = effective_matrix_scale_sqrd(fInvMatrix); if (SkPaint::kHigh_FilterLevel == fFilterLevel) { // Set the limit at 0.25 for the CTM... if the CTM is scaling smaller // than this, then the mipmaps quality may be greater (certainly faster) // so we only keep High quality if the scale is greater than this. // // Since we're dealing with the inverse, we compare against its inverse. const SkScalar bicubicLimit = 4.0f; const SkScalar bicubicLimitSqd = bicubicLimit * bicubicLimit; if (scaleSqd < bicubicLimitSqd) { // use bicubic scanline return false; } // else set the filter-level to Medium, since we're scaling down and // want to reqeust mipmaps fFilterLevel = SkPaint::kMedium_FilterLevel; } SkASSERT(SkPaint::kMedium_FilterLevel == fFilterLevel); /** * Medium quality means use a mipmap for down-scaling, and just bilper * for upscaling. Since we're examining the inverse matrix, we look for * a scale > 1 to indicate down scaling by the CTM. */ if (scaleSqd > SK_Scalar1) { fCurrMip.reset(SkMipMapCache::FindAndRef(fOrigBitmap)); if (NULL == fCurrMip.get()) { fCurrMip.reset(SkMipMap::Build(fOrigBitmap)); if (NULL == fCurrMip.get()) { return false; } SkMipMapCache::Add(fOrigBitmap, fCurrMip); } SkScalar levelScale = SkScalarInvert(SkScalarSqrt(scaleSqd)); SkMipMap::Level level; if (fCurrMip->extractLevel(levelScale, &level)) { SkScalar invScaleFixup = level.fScale; fInvMatrix.postScale(invScaleFixup, invScaleFixup); const SkImageInfo info = fOrigBitmap.info().makeWH(level.fWidth, level.fHeight); // todo: if we could wrap the fCurrMip in a pixelref, then we could just install // that here, and not need to explicitly track it ourselves. fScaledBitmap.installPixels(info, level.fPixels, level.fRowBytes); fBitmap = &fScaledBitmap; fFilterLevel = SkPaint::kLow_FilterLevel; return true; } } return false; }
bool SkBitmapProcState::possiblyScaleImage() { AutoScaledCacheUnlocker unlocker(&fScaledCacheID); SkASSERT(NULL == fBitmap); SkASSERT(NULL == fScaledCacheID); if (fFilterLevel <= SkPaint::kLow_FilterLevel) { return false; } // Check to see if the transformation matrix is simple, and if we're // doing high quality scaling. If so, do the bitmap scale here and // remove the scaling component from the matrix. if (SkPaint::kHigh_FilterLevel == fFilterLevel && fInvMatrix.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask) && kN32_SkColorType == fOrigBitmap.colorType() && cache_size_okay(fOrigBitmap, fInvMatrix)) { SkScalar invScaleX = fInvMatrix.getScaleX(); SkScalar invScaleY = fInvMatrix.getScaleY(); fScaledCacheID = SkScaledImageCache::FindAndLock(fOrigBitmap, invScaleX, invScaleY, &fScaledBitmap); if (fScaledCacheID) { fScaledBitmap.lockPixels(); if (!fScaledBitmap.getPixels()) { fScaledBitmap.unlockPixels(); // found a purged entry (discardablememory?), release it SkScaledImageCache::Unlock(fScaledCacheID); fScaledCacheID = NULL; // fall through to rebuild } } if (NULL == fScaledCacheID) { float dest_width = fOrigBitmap.width() / invScaleX; float dest_height = fOrigBitmap.height() / invScaleY; // All the criteria are met; let's make a new bitmap. if (!SkBitmapScaler::Resize(&fScaledBitmap, fOrigBitmap, SkBitmapScaler::RESIZE_BEST, dest_width, dest_height, SkScaledImageCache::GetAllocator())) { // we failed to create fScaledBitmap, so just return and let // the scanline proc handle it. return false; } SkASSERT(NULL != fScaledBitmap.getPixels()); fScaledCacheID = SkScaledImageCache::AddAndLock(fOrigBitmap, invScaleX, invScaleY, fScaledBitmap); if (!fScaledCacheID) { fScaledBitmap.reset(); return false; } SkASSERT(NULL != fScaledBitmap.getPixels()); } SkASSERT(NULL != fScaledBitmap.getPixels()); fBitmap = &fScaledBitmap; // set the inv matrix type to translate-only; fInvMatrix.setTranslate(fInvMatrix.getTranslateX() / fInvMatrix.getScaleX(), fInvMatrix.getTranslateY() / fInvMatrix.getScaleY()); // no need for any further filtering; we just did it! fFilterLevel = SkPaint::kNone_FilterLevel; unlocker.release(); return true; } /* * If High, then our special-case for scale-only did not take, and so we * have to make a choice: * 1. fall back on mipmaps + bilerp * 2. fall back on scanline bicubic filter * For now, we compute the "scale" value from the matrix, and have a * threshold to decide when bicubic is better, and when mips are better. * No doubt a fancier decision tree could be used uere. * * If Medium, then we just try to build a mipmap and select a level, * setting the filter-level to kLow to signal that we just need bilerp * to process the selected level. */ SkScalar scaleSqd = effective_matrix_scale_sqrd(fInvMatrix); if (SkPaint::kHigh_FilterLevel == fFilterLevel) { // Set the limit at 0.25 for the CTM... if the CTM is scaling smaller // than this, then the mipmaps quality may be greater (certainly faster) // so we only keep High quality if the scale is greater than this. // // Since we're dealing with the inverse, we compare against its inverse. const SkScalar bicubicLimit = 4.0f; const SkScalar bicubicLimitSqd = bicubicLimit * bicubicLimit; if (scaleSqd < bicubicLimitSqd) { // use bicubic scanline return false; } // else set the filter-level to Medium, since we're scaling down and // want to reqeust mipmaps fFilterLevel = SkPaint::kMedium_FilterLevel; } SkASSERT(SkPaint::kMedium_FilterLevel == fFilterLevel); /** * Medium quality means use a mipmap for down-scaling, and just bilper * for upscaling. Since we're examining the inverse matrix, we look for * a scale > 1 to indicate down scaling by the CTM. */ if (scaleSqd > SK_Scalar1) { const SkMipMap* mip = NULL; SkASSERT(NULL == fScaledCacheID); fScaledCacheID = SkScaledImageCache::FindAndLockMip(fOrigBitmap, &mip); if (!fScaledCacheID) { SkASSERT(NULL == mip); mip = SkMipMap::Build(fOrigBitmap); if (mip) { fScaledCacheID = SkScaledImageCache::AddAndLockMip(fOrigBitmap, mip); SkASSERT(mip->getRefCnt() > 1); mip->unref(); // the cache took a ref SkASSERT(fScaledCacheID); } } else { SkASSERT(mip); } if (mip) { SkScalar levelScale = SkScalarInvert(SkScalarSqrt(scaleSqd)); SkMipMap::Level level; if (mip->extractLevel(levelScale, &level)) { SkScalar invScaleFixup = level.fScale; fInvMatrix.postScale(invScaleFixup, invScaleFixup); SkImageInfo info = fOrigBitmap.info(); info.fWidth = level.fWidth; info.fHeight = level.fHeight; fScaledBitmap.installPixels(info, level.fPixels, level.fRowBytes); fBitmap = &fScaledBitmap; fFilterLevel = SkPaint::kLow_FilterLevel; unlocker.release(); return true; } } } return false; }
/* * High quality is implemented by performing up-right scale-only filtering and then * using bilerp for any remaining transformations. */ bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmapProvider& provider) { if (fQuality != kHigh_SkFilterQuality) { return false; } // Our default return state is to downgrade the request to Medium, w/ or w/o setting fBitmap // to a valid bitmap. If we succeed, we will set this to Low instead. fQuality = kMedium_SkFilterQuality; if (kN32_SkColorType != provider.info().colorType() || !cache_size_okay(provider, fInvMatrix) || fInvMatrix.hasPerspective()) { return false; // can't handle the reqeust } SkScalar invScaleX = fInvMatrix.getScaleX(); SkScalar invScaleY = fInvMatrix.getScaleY(); if (fInvMatrix.getType() & SkMatrix::kAffine_Mask) { SkSize scale; if (!fInvMatrix.decomposeScale(&scale)) { return false; } invScaleX = scale.width(); invScaleY = scale.height(); } if (SkScalarNearlyEqual(invScaleX, 1) && SkScalarNearlyEqual(invScaleY, 1)) { return false; // no need for HQ } #ifndef SK_SUPPORT_LEGACY_HQ_DOWNSAMPLING if (invScaleX > 1 || invScaleY > 1) { return false; // only use HQ when upsampling } #endif const int dstW = SkScalarRoundToScalar(provider.width() / invScaleX); const int dstH = SkScalarRoundToScalar(provider.height() / invScaleY); const SkBitmapCacheDesc desc = provider.makeCacheDesc(dstW, dstH); if (!SkBitmapCache::FindWH(desc, &fResultBitmap)) { SkBitmap orig; if (!provider.asBitmap(&orig)) { return false; } SkAutoPixmapUnlock src; if (!orig.requestLock(&src)) { return false; } if (!SkBitmapScaler::Resize(&fResultBitmap, src.pixmap(), kHQ_RESIZE_METHOD, dstW, dstH, SkResourceCache::GetAllocator())) { return false; // we failed to create fScaledBitmap } SkASSERT(fResultBitmap.getPixels()); fResultBitmap.setImmutable(); if (!provider.isVolatile()) { if (SkBitmapCache::AddWH(desc, fResultBitmap)) { provider.notifyAddedToCache(); } } } SkASSERT(fResultBitmap.getPixels()); fInvMatrix.postScale(SkIntToScalar(dstW) / provider.width(), SkIntToScalar(dstH) / provider.height()); fQuality = kLow_SkFilterQuality; return true; }