SkBitmap NativeImageSkia::resizedBitmap(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const { #if PLATFORM(CHROMIUM) if (DeferredImageDecoder::isLazyDecoded(m_image)) return DeferredImageDecoder::createResizedLazyDecodingBitmap(m_image, scaledImageSize, scaledImageSubset); #endif if (!hasResizedBitmap(scaledImageSize, scaledImageSubset)) { bool shouldCache = isDataComplete() && shouldCacheResampling(scaledImageSize, scaledImageSubset); PlatformInstrumentation::willResizeImage(shouldCache); SkBitmap resizedImage = skia::ImageOperations::Resize(m_image, skia::ImageOperations::RESIZE_LANCZOS3, scaledImageSize.width(), scaledImageSize.height(), scaledImageSubset); resizedImage.setImmutable(); PlatformInstrumentation::didResizeImage(); if (!shouldCache) return resizedImage; m_resizedImage = resizedImage; } SkBitmap resizedSubset; SkIRect resizedSubsetRect = m_cachedImageInfo.rectInSubset(scaledImageSubset); m_resizedImage.extractSubset(&resizedSubset, resizedSubsetRect); return resizedSubset; }
bool NativeImageSkia::shouldCacheResampling(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const { // Check whether the requested dimensions match previous request. bool matchesPreviousRequest = m_cachedImageInfo.isEqual(scaledImageSize, scaledImageSubset); if (matchesPreviousRequest) ++m_resizeRequests; else { m_cachedImageInfo.set(scaledImageSize, scaledImageSubset); m_resizeRequests = 0; // Reset m_resizedImage now, because we don't distinguish // between the last requested resize info and m_resizedImage's // resize info. m_resizedImage.reset(); } // We can not cache incomplete frames. This might be a good optimization in // the future, were we know how much of the frame has been decoded, so when // we incrementally draw more of the image, we only have to resample the // parts that are changed. if (!isDataComplete()) return false; // If the destination bitmap is excessively large, we'll never allow caching. static const unsigned long long kLargeBitmapSize = 4096ULL * 4096ULL; unsigned long long fullSize = static_cast<unsigned long long>(scaledImageSize.width()) * static_cast<unsigned long long>(scaledImageSize.height()); unsigned long long fragmentSize = static_cast<unsigned long long>(scaledImageSubset.width()) * static_cast<unsigned long long>(scaledImageSubset.height()); if (fragmentSize > kLargeBitmapSize) return false; // If the destination bitmap is small, we'll always allow caching, since // there is not very much penalty for computing it and it may come in handy. static const unsigned kSmallBitmapSize = 4096; if (fragmentSize <= kSmallBitmapSize) return true; // If "too many" requests have been made for this bitmap, we assume that // many more will be made as well, and we'll go ahead and cache it. static const int kManyRequestThreshold = 4; if (m_resizeRequests >= kManyRequestThreshold) return true; // If more than 1/4 of the resized image is requested, it's worth caching. return fragmentSize > fullSize / 4; }
SkBitmap NativeImageSkia::resizedBitmap(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const { ASSERT(!DeferredImageDecoder::isLazyDecoded(bitmap())); if (!hasResizedBitmap(scaledImageSize, scaledImageSubset)) { bool shouldCache = isDataComplete() && shouldCacheResampling(scaledImageSize, scaledImageSubset); SkBitmap resizedImage = skia::ImageOperations::Resize(bitmap(), skia::ImageOperations::RESIZE_LANCZOS3, scaledImageSize.width(), scaledImageSize.height(), scaledImageSubset); resizedImage.setImmutable(); if (!shouldCache) return resizedImage; m_resizedImage = resizedImage; } SkBitmap resizedSubset; SkIRect resizedSubsetRect = m_cachedImageInfo.rectInSubset(scaledImageSubset); m_resizedImage.extractSubset(&resizedSubset, resizedSubsetRect); return resizedSubset; }
void NativeImageSkia::drawPattern( GraphicsContext* context, const FloatRect& floatSrcRect, const FloatSize& scale, const FloatPoint& phase, CompositeOperator compositeOp, const FloatRect& destRect, WebBlendMode blendMode, const IntSize& repeatSpacing) const { FloatRect normSrcRect = floatSrcRect; normSrcRect.intersect(FloatRect(0, 0, bitmap().width(), bitmap().height())); if (destRect.isEmpty() || normSrcRect.isEmpty()) return; // nothing to draw SkMatrix totalMatrix = context->getTotalMatrix(); AffineTransform ctm = context->getCTM(); SkScalar ctmScaleX = ctm.xScale(); SkScalar ctmScaleY = ctm.yScale(); totalMatrix.preScale(scale.width(), scale.height()); // Figure out what size the bitmap will be in the destination. The // destination rect is the bounds of the pattern, we need to use the // matrix to see how big it will be. SkRect destRectTarget; totalMatrix.mapRect(&destRectTarget, normSrcRect); float destBitmapWidth = SkScalarToFloat(destRectTarget.width()); float destBitmapHeight = SkScalarToFloat(destRectTarget.height()); bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); // Compute the resampling mode. InterpolationQuality resampling; if (context->isAccelerated()) resampling = InterpolationLow; else if (isLazyDecoded) resampling = InterpolationHigh; else resampling = computeInterpolationQuality(totalMatrix, normSrcRect.width(), normSrcRect.height(), destBitmapWidth, destBitmapHeight, isDataComplete()); resampling = limitInterpolationQuality(context, resampling); SkMatrix localMatrix; // We also need to translate it such that the origin of the pattern is the // origin of the destination rect, which is what WebKit expects. Skia uses // the coordinate system origin as the base for the pattern. If WebKit wants // a shifted image, it will shift it from there using the localMatrix. const float adjustedX = phase.x() + normSrcRect.x() * scale.width(); const float adjustedY = phase.y() + normSrcRect.y() * scale.height(); localMatrix.setTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjustedY)); RefPtr<SkShader> shader; SkFilterQuality filterLevel = static_cast<SkFilterQuality>(resampling); // Bicubic filter is only applied to defer-decoded images, see // NativeImageSkia::draw for details. if (resampling == InterpolationHigh && !isLazyDecoded) { // Do nice resampling. filterLevel = kNone_SkFilterQuality; float scaleX = destBitmapWidth / normSrcRect.width(); float scaleY = destBitmapHeight / normSrcRect.height(); SkRect scaledSrcRect; // Since we are resizing the bitmap, we need to remove the scale // applied to the pixels in the bitmap shader. This means we need // CTM * localMatrix to have identity scale. Since we // can't modify CTM (or the rectangle will be drawn in the wrong // place), we must set localMatrix's scale to the inverse of // CTM scale. localMatrix.preScale(ctmScaleX ? 1 / ctmScaleX : 1, ctmScaleY ? 1 / ctmScaleY : 1); // The image fragment generated here is not exactly what is // requested. The scale factor used is approximated and image // fragment is slightly larger to align to integer // boundaries. SkBitmap resampled = extractScaledImageFragment(normSrcRect, scaleX, scaleY, &scaledSrcRect); if (repeatSpacing.isZero()) { shader = adoptRef(SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); } else { shader = adoptRef(SkShader::CreateBitmapShader( createBitmapWithSpace(resampled, repeatSpacing.width() * ctmScaleX, repeatSpacing.height() * ctmScaleY), SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); } } else { // Because no resizing occurred, the shader transform should be // set to the pattern's transform, which just includes scale. localMatrix.preScale(scale.width(), scale.height()); // No need to resample before drawing. SkBitmap srcSubset; bitmap().extractSubset(&srcSubset, enclosingIntRect(normSrcRect)); if (repeatSpacing.isZero()) { shader = adoptRef(SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); } else { shader = adoptRef(SkShader::CreateBitmapShader( createBitmapWithSpace(srcSubset, repeatSpacing.width() * ctmScaleX, repeatSpacing.height() * ctmScaleY), SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); } } SkPaint paint; paint.setShader(shader.get()); paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode)); paint.setColorFilter(context->colorFilter()); paint.setFilterQuality(filterLevel); context->drawRect(destRect, paint); }
void NativeImageSkia::draw( GraphicsContext* context, const SkRect& srcRect, const SkRect& destRect, CompositeOperator compositeOp, WebBlendMode blendMode) const { TRACE_EVENT0("skia", "NativeImageSkia::draw"); bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); SkPaint paint; context->preparePaintForDrawRectToRect(&paint, srcRect, destRect, compositeOp, blendMode, isLazyDecoded, isDataComplete()); // We want to filter it if we decided to do interpolation above, or if // there is something interesting going on with the matrix (like a rotation). // Note: for serialization, we will want to subset the bitmap first so we // don't send extra pixels. context->drawBitmapRect(bitmap(), &srcRect, destRect, &paint); context->didDrawRect(destRect, paint, &bitmap()); }
ResamplingMode NativeImageSkia::computeResamplingMode(const SkMatrix& matrix, float srcWidth, float srcHeight, float destWidth, float destHeight) const { // The percent change below which we will not resample. This usually means // an off-by-one error on the web page, and just doing nearest neighbor // sampling is usually good enough. const float kFractionalChangeThreshold = 0.025f; // Images smaller than this in either direction are considered "small" and // are not resampled ever (see below). const int kSmallImageSizeThreshold = 8; // The amount an image can be stretched in a single direction before we // say that it is being stretched so much that it must be a line or // background that doesn't need resampling. const float kLargeStretch = 3.0f; // Figure out if we should resample this image. We try to prune out some // common cases where resampling won't give us anything, since it is much // slower than drawing stretched. float diffWidth = fabs(destWidth - srcWidth); float diffHeight = fabs(destHeight - srcHeight); bool widthNearlyEqual = diffWidth < std::numeric_limits<float>::epsilon(); bool heightNearlyEqual = diffHeight < std::numeric_limits<float>::epsilon(); // We don't need to resample if the source and destination are the same. if (widthNearlyEqual && heightNearlyEqual) return NoResampling; if (srcWidth <= kSmallImageSizeThreshold || srcHeight <= kSmallImageSizeThreshold || destWidth <= kSmallImageSizeThreshold || destHeight <= kSmallImageSizeThreshold) { // Small image detected. // Resample in the case where the new size would be non-integral. // This can cause noticeable breaks in repeating patterns, except // when the source image is only one pixel wide in that dimension. if ((!nearlyIntegral(destWidth) && srcWidth > 1 + std::numeric_limits<float>::epsilon()) || (!nearlyIntegral(destHeight) && srcHeight > 1 + std::numeric_limits<float>::epsilon())) return LinearResampling; // Otherwise, don't resample small images. These are often used for // borders and rules (think 1x1 images used to make lines). return NoResampling; } if (srcHeight * kLargeStretch <= destHeight || srcWidth * kLargeStretch <= destWidth) { // Large image detected. // Don't resample if it is being stretched a lot in only one direction. // This is trying to catch cases where somebody has created a border // (which might be large) and then is stretching it to fill some part // of the page. if (widthNearlyEqual || heightNearlyEqual) return NoResampling; // The image is growing a lot and in more than one direction. Resampling // is slow and doesn't give us very much when growing a lot. return LinearResampling; } if ((diffWidth / srcWidth < kFractionalChangeThreshold) && (diffHeight / srcHeight < kFractionalChangeThreshold)) { // It is disappointingly common on the web for image sizes to be off by // one or two pixels. We don't bother resampling if the size difference // is a small fraction of the original size. return NoResampling; } // When the image is not yet done loading, use linear. We don't cache the // partially resampled images, and as they come in incrementally, it causes // us to have to resample the whole thing every time. if (!isDataComplete()) return LinearResampling; // Everything else gets resampled. // High quality interpolation only enabled for scaling and translation. if (!(matrix.getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask))) return AwesomeResampling; return LinearResampling; }