This source file includes following definitions.
- nearlyIntegral
- computeResamplingMode
- limitResamplingMode
- convertToSkiaFilterLevel
- extractScaledImageFragment
- drawResampledBitmap
- m_resizeRequests
- m_resizeRequests
- decodedSize
- hasResizedBitmap
- resizedBitmap
- shouldDrawAntiAliased
- draw
- createBitmapWithSpace
- drawPattern
- shouldCacheResampling
- isEqual
- set
- rectInSubset
#include "config.h"
#include "platform/graphics/skia/NativeImageSkia.h"
#include "platform/PlatformInstrumentation.h"
#include "platform/TraceEvent.h"
#include "platform/geometry/FloatPoint.h"
#include "platform/geometry/FloatRect.h"
#include "platform/geometry/FloatSize.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/Image.h"
#include "platform/graphics/DeferredImageDecoder.h"
#include "platform/graphics/skia/SkiaUtils.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "third_party/skia/include/core/SkShader.h"
#include <math.h>
#include <limits>
namespace WebCore {
static bool nearlyIntegral(float value)
{
return fabs(value - floorf(value)) < std::numeric_limits<float>::epsilon();
}
ResamplingMode NativeImageSkia::computeResamplingMode(const SkMatrix& matrix, float srcWidth, float srcHeight, float destWidth, float destHeight) const
{
const float kFractionalChangeThreshold = 0.025f;
const int kSmallImageSizeThreshold = 8;
const float kLargeStretch = 3.0f;
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();
if (widthNearlyEqual && heightNearlyEqual)
return NoResampling;
if (srcWidth <= kSmallImageSizeThreshold
|| srcHeight <= kSmallImageSizeThreshold
|| destWidth <= kSmallImageSizeThreshold
|| destHeight <= kSmallImageSizeThreshold) {
if ((!nearlyIntegral(destWidth) && srcWidth > 1 + std::numeric_limits<float>::epsilon())
|| (!nearlyIntegral(destHeight) && srcHeight > 1 + std::numeric_limits<float>::epsilon()))
return LinearResampling;
return NoResampling;
}
if (srcHeight * kLargeStretch <= destHeight || srcWidth * kLargeStretch <= destWidth) {
if (widthNearlyEqual || heightNearlyEqual)
return NoResampling;
return LinearResampling;
}
if ((diffWidth / srcWidth < kFractionalChangeThreshold)
&& (diffHeight / srcHeight < kFractionalChangeThreshold)) {
return NoResampling;
}
if (!isDataComplete())
return LinearResampling;
if (!(matrix.getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)))
return AwesomeResampling;
return LinearResampling;
}
static ResamplingMode limitResamplingMode(GraphicsContext* context, ResamplingMode resampling)
{
switch (context->imageInterpolationQuality()) {
case InterpolationNone:
return NoResampling;
case InterpolationMedium:
if (resampling == AwesomeResampling)
return LinearWithMipmapsResampling;
break;
case InterpolationLow:
if (resampling == AwesomeResampling || resampling == LinearWithMipmapsResampling)
return LinearResampling;
break;
case InterpolationHigh:
break;
}
return resampling;
}
static SkPaint::FilterLevel convertToSkiaFilterLevel(bool useBicubicFilter, ResamplingMode resampling)
{
if (useBicubicFilter)
return SkPaint::kHigh_FilterLevel;
switch (resampling) {
case LinearWithMipmapsResampling:
return SkPaint::kMedium_FilterLevel;
case LinearResampling:
return SkPaint::kLow_FilterLevel;
case AwesomeResampling:
default:
return SkPaint::kNone_FilterLevel;
}
}
SkBitmap NativeImageSkia::extractScaledImageFragment(const SkRect& srcRect, float scaleX, float scaleY, SkRect* scaledSrcRect) const
{
SkISize imageSize = SkISize::Make(bitmap().width(), bitmap().height());
SkISize scaledImageSize = SkISize::Make(clampToInteger(roundf(imageSize.width() * scaleX)),
clampToInteger(roundf(imageSize.height() * scaleY)));
SkRect imageRect = SkRect::MakeWH(imageSize.width(), imageSize.height());
SkRect scaledImageRect = SkRect::MakeWH(scaledImageSize.width(), scaledImageSize.height());
SkMatrix scaleTransform;
scaleTransform.setRectToRect(imageRect, scaledImageRect, SkMatrix::kFill_ScaleToFit);
scaleTransform.mapRect(scaledSrcRect, srcRect);
scaledSrcRect->intersect(scaledImageRect);
SkIRect enclosingScaledSrcRect = enclosingIntRect(*scaledSrcRect);
enclosingScaledSrcRect.intersect(SkIRect::MakeSize(scaledImageSize));
scaledSrcRect->offset(-enclosingScaledSrcRect.x(), -enclosingScaledSrcRect.y());
return resizedBitmap(scaledImageSize, enclosingScaledSrcRect);
}
void NativeImageSkia::drawResampledBitmap(GraphicsContext* context, SkPaint& paint, const SkRect& srcRect, const SkRect& destRect) const
{
TRACE_EVENT0("skia", "drawResampledBitmap");
SkRect screenRect;
context->getTotalMatrix().mapRect(&screenRect, destRect);
float realScaleX = screenRect.width() / srcRect.width();
float realScaleY = screenRect.height() / srcRect.height();
SkRect destRectVisibleSubset;
if (!context->canvas()->getClipBounds(&destRectVisibleSubset))
return;
if (!destRectVisibleSubset.intersect(destRect))
return;
SkMatrix destToSrcTransform;
SkRect srcRectVisibleSubset;
destToSrcTransform.setRectToRect(destRect, srcRect, SkMatrix::kFill_ScaleToFit);
destToSrcTransform.mapRect(&srcRectVisibleSubset, destRectVisibleSubset);
SkRect scaledSrcRect;
SkBitmap scaledImageFragment = extractScaledImageFragment(srcRectVisibleSubset, realScaleX, realScaleY, &scaledSrcRect);
context->drawBitmapRect(scaledImageFragment, &scaledSrcRect, destRectVisibleSubset, &paint);
}
NativeImageSkia::NativeImageSkia()
: m_resizeRequests(0)
{
}
NativeImageSkia::NativeImageSkia(const SkBitmap& other)
: m_image(other)
, m_resizeRequests(0)
{
}
NativeImageSkia::NativeImageSkia(const SkBitmap& image, const SkBitmap& resizedImage, const ImageResourceInfo& cachedImageInfo, int resizeRequests)
: m_image(image)
, m_resizedImage(resizedImage)
, m_cachedImageInfo(cachedImageInfo)
, m_resizeRequests(resizeRequests)
{
}
NativeImageSkia::~NativeImageSkia()
{
}
int NativeImageSkia::decodedSize() const
{
return m_image.getSize() + m_resizedImage.getSize();
}
bool NativeImageSkia::hasResizedBitmap(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const
{
bool imageScaleEqual = m_cachedImageInfo.scaledImageSize == scaledImageSize;
bool scaledImageSubsetAvailable = m_cachedImageInfo.scaledImageSubset.contains(scaledImageSubset);
return imageScaleEqual && scaledImageSubsetAvailable && !m_resizedImage.empty();
}
SkBitmap NativeImageSkia::resizedBitmap(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const
{
ASSERT(!DeferredImageDecoder::isLazyDecoded(m_image));
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;
}
static bool shouldDrawAntiAliased(GraphicsContext* context, const SkRect& destRect)
{
if (!context->shouldAntialias())
return false;
const SkMatrix totalMatrix = context->getTotalMatrix();
if (!totalMatrix.rectStaysRect())
return true;
if (!context->shouldAntialiasHairlineImages())
return false;
SkScalar widthExpansion, heightExpansion;
if (totalMatrix.getType() & SkMatrix::kAffine_Mask)
widthExpansion = totalMatrix[SkMatrix::kMSkewY], heightExpansion = totalMatrix[SkMatrix::kMSkewX];
else
widthExpansion = totalMatrix[SkMatrix::kMScaleX], heightExpansion = totalMatrix[SkMatrix::kMScaleY];
return destRect.width() * fabs(widthExpansion) < 1 || destRect.height() * fabs(heightExpansion) < 1;
}
void NativeImageSkia::draw(GraphicsContext* context, const SkRect& srcRect, const SkRect& destRect, PassRefPtr<SkXfermode> compOp) const
{
TRACE_EVENT0("skia", "NativeImageSkia::draw");
SkPaint paint;
paint.setXfermode(compOp.get());
paint.setColorFilter(context->colorFilter());
paint.setAlpha(context->getNormalizedAlpha());
paint.setLooper(context->drawLooper());
paint.setAntiAlias(shouldDrawAntiAliased(context, destRect));
ResamplingMode resampling;
if (context->isAccelerated()) {
resampling = LinearResampling;
} else if (context->printing()) {
resampling = NoResampling;
} else {
SkRect destRectTarget = destRect;
SkMatrix totalMatrix = context->getTotalMatrix();
if (!(totalMatrix.getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)))
totalMatrix.mapRect(&destRectTarget, destRect);
resampling = computeResamplingMode(totalMatrix,
SkScalarToFloat(srcRect.width()), SkScalarToFloat(srcRect.height()),
SkScalarToFloat(destRectTarget.width()), SkScalarToFloat(destRectTarget.height()));
}
if (resampling == NoResampling) {
resampling = LinearResampling;
}
resampling = limitResamplingMode(context, resampling);
bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap());
bool useBicubicFilter = resampling == AwesomeResampling && isLazyDecoded;
paint.setFilterLevel(convertToSkiaFilterLevel(useBicubicFilter, resampling));
if (resampling == AwesomeResampling && !useBicubicFilter) {
drawResampledBitmap(context, paint, srcRect, destRect);
} else {
context->drawBitmapRect(bitmap(), &srcRect, destRect, &paint);
}
if (isLazyDecoded)
PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID());
context->didDrawRect(destRect, paint, &bitmap());
}
static SkBitmap createBitmapWithSpace(const SkBitmap& bitmap, int spaceWidth, int spaceHeight)
{
SkImageInfo info = bitmap.info();
info.fWidth += spaceWidth;
info.fHeight += spaceHeight;
info.fAlphaType = kPremul_SkAlphaType;
SkBitmap result;
result.allocPixels(info);
result.eraseColor(SK_ColorTRANSPARENT);
bitmap.copyPixelsTo(reinterpret_cast<uint8_t*>(result.getPixels()), result.rowBytes() * result.height(), result.rowBytes());
return result;
}
void NativeImageSkia::drawPattern(
GraphicsContext* context,
const FloatRect& floatSrcRect,
const FloatSize& scale,
const FloatPoint& phase,
CompositeOperator compositeOp,
const FloatRect& destRect,
blink::WebBlendMode blendMode,
const IntSize& repeatSpacing) const
{
FloatRect normSrcRect = floatSrcRect;
normSrcRect.intersect(FloatRect(0, 0, bitmap().width(), bitmap().height()));
if (destRect.isEmpty() || normSrcRect.isEmpty())
return;
SkMatrix totalMatrix = context->getTotalMatrix();
AffineTransform ctm = context->getCTM();
SkScalar ctmScaleX = ctm.xScale();
SkScalar ctmScaleY = ctm.yScale();
totalMatrix.preScale(scale.width(), scale.height());
SkRect destRectTarget;
totalMatrix.mapRect(&destRectTarget, normSrcRect);
float destBitmapWidth = SkScalarToFloat(destRectTarget.width());
float destBitmapHeight = SkScalarToFloat(destRectTarget.height());
ResamplingMode resampling;
if (context->isAccelerated() || context->printing())
resampling = LinearResampling;
else
resampling = computeResamplingMode(totalMatrix, normSrcRect.width(), normSrcRect.height(), destBitmapWidth, destBitmapHeight);
resampling = limitResamplingMode(context, resampling);
SkMatrix shaderTransform;
RefPtr<SkShader> shader;
bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap());
bool useBicubicFilter = resampling == AwesomeResampling && isLazyDecoded;
if (resampling == AwesomeResampling && !useBicubicFilter) {
float scaleX = destBitmapWidth / normSrcRect.width();
float scaleY = destBitmapHeight / normSrcRect.height();
SkRect scaledSrcRect;
SkBitmap resampled = extractScaledImageFragment(normSrcRect, scaleX, scaleY, &scaledSrcRect);
if (repeatSpacing.isZero()) {
shader = adoptRef(SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
} else {
shader = adoptRef(SkShader::CreateBitmapShader(
createBitmapWithSpace(resampled, repeatSpacing.width() * ctmScaleX, repeatSpacing.height() * ctmScaleY),
SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
}
shaderTransform.setScale(ctmScaleX ? 1 / ctmScaleX : 1, ctmScaleY ? 1 / ctmScaleY : 1);
} else {
SkBitmap srcSubset;
bitmap().extractSubset(&srcSubset, enclosingIntRect(normSrcRect));
if (repeatSpacing.isZero()) {
shader = adoptRef(SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
} else {
shader = adoptRef(SkShader::CreateBitmapShader(
createBitmapWithSpace(srcSubset, repeatSpacing.width() * ctmScaleX, repeatSpacing.height() * ctmScaleY),
SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
}
shaderTransform.setScale(scale.width(), scale.height());
}
float adjustedX = phase.x() + normSrcRect.x() * scale.width();
float adjustedY = phase.y() + normSrcRect.y() * scale.height();
shaderTransform.postTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjustedY));
shader->setLocalMatrix(shaderTransform);
SkPaint paint;
paint.setShader(shader.get());
paint.setXfermode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode).get());
paint.setColorFilter(context->colorFilter());
paint.setFilterLevel(convertToSkiaFilterLevel(useBicubicFilter, resampling));
if (isLazyDecoded)
PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID());
context->drawRect(destRect, paint);
}
bool NativeImageSkia::shouldCacheResampling(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const
{
bool matchesPreviousRequest = m_cachedImageInfo.isEqual(scaledImageSize, scaledImageSubset);
if (matchesPreviousRequest)
++m_resizeRequests;
else {
m_cachedImageInfo.set(scaledImageSize, scaledImageSubset);
m_resizeRequests = 0;
m_resizedImage.reset();
}
if (!isDataComplete())
return false;
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;
static const unsigned kSmallBitmapSize = 4096;
if (fragmentSize <= kSmallBitmapSize)
return true;
static const int kManyRequestThreshold = 4;
if (m_resizeRequests >= kManyRequestThreshold)
return true;
return fragmentSize > fullSize / 4;
}
NativeImageSkia::ImageResourceInfo::ImageResourceInfo()
{
scaledImageSize.setEmpty();
scaledImageSubset.setEmpty();
}
bool NativeImageSkia::ImageResourceInfo::isEqual(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset) const
{
return scaledImageSize == otherScaledImageSize && scaledImageSubset == otherScaledImageSubset;
}
void NativeImageSkia::ImageResourceInfo::set(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset)
{
scaledImageSize = otherScaledImageSize;
scaledImageSubset = otherScaledImageSubset;
}
SkIRect NativeImageSkia::ImageResourceInfo::rectInSubset(const SkIRect& otherScaledImageSubset)
{
if (!scaledImageSubset.contains(otherScaledImageSubset))
return SkIRect::MakeEmpty();
SkIRect subsetRect = otherScaledImageSubset;
subsetRect.offset(-scaledImageSubset.x(), -scaledImageSubset.y());
return subsetRect;
}
}