blob: 806856a1066c83c0245e8fe284aec008f0931d88 [file] [log] [blame]
/*
* Copyright (c) 2008, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "SkBitmap.h"
#include "SkRect.h"
#include "SkShader.h"
#include "core/platform/FloatConversion.h"
#include "core/platform/graphics/BitmapImage.h"
#include "core/platform/graphics/FloatRect.h"
#include "core/platform/graphics/GraphicsContextStateSaver.h"
#include "core/platform/graphics/ImageObserver.h"
#include "core/platform/graphics/chromium/DeferredImageDecoder.h"
#include "core/platform/graphics/skia/NativeImageSkia.h"
#include "core/platform/graphics/skia/SkiaUtils.h"
#include "core/platform/graphics/transforms/AffineTransform.h"
#include <wtf/text/WTFString.h>
#include <math.h>
#include <limits>
#include "core/platform/chromium/TraceEvent.h"
namespace WebCore {
// Used by computeResamplingMode to tell how bitmaps should be resampled.
enum ResamplingMode {
// Nearest neighbor resampling. Used when we detect that the page is
// trying to make a pattern by stretching a small bitmap very large.
RESAMPLE_NONE,
// Default skia resampling. Used for large growing of images where high
// quality resampling doesn't get us very much except a slowdown.
RESAMPLE_LINEAR,
// High quality resampling.
RESAMPLE_AWESOME,
};
static ResamplingMode computeResamplingMode(const SkMatrix& matrix, const NativeImageSkia& bitmap, float srcWidth, float srcHeight, float destWidth, float destHeight)
{
// 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 RESAMPLE_NONE;
if (srcWidth <= kSmallImageSizeThreshold
|| srcHeight <= kSmallImageSizeThreshold
|| destWidth <= kSmallImageSizeThreshold
|| destHeight <= kSmallImageSizeThreshold) {
// Never resample small images. These are often used for borders and
// rules (think 1x1 images used to make lines).
return RESAMPLE_NONE;
}
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 RESAMPLE_NONE;
// 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 RESAMPLE_LINEAR;
}
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 RESAMPLE_NONE;
}
// 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 (!bitmap.isDataComplete())
return RESAMPLE_LINEAR;
// Everything else gets resampled.
// High quality interpolation only enabled for scaling and translation.
if (!(matrix.getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)))
return RESAMPLE_AWESOME;
return RESAMPLE_LINEAR;
}
static ResamplingMode limitResamplingMode(GraphicsContext* context, ResamplingMode resampling)
{
switch (context->imageInterpolationQuality()) {
case InterpolationNone:
return RESAMPLE_NONE;
case InterpolationMedium:
// For now we treat InterpolationMedium and InterpolationLow the same.
case InterpolationLow:
if (resampling == RESAMPLE_AWESOME)
return RESAMPLE_LINEAR;
break;
case InterpolationHigh:
case InterpolationDefault:
break;
}
return resampling;
}
// Return true if the rectangle is aligned to integer boundaries.
// See comments for computeBitmapDrawRects() for how this is used.
static bool areBoundariesIntegerAligned(const SkRect& rect)
{
// Value is 1.19209e-007. This is the tolerance threshold.
const float epsilon = std::numeric_limits<float>::epsilon();
SkIRect roundedRect = roundedIntRect(rect);
return fabs(rect.x() - roundedRect.x()) < epsilon
&& fabs(rect.y() - roundedRect.y()) < epsilon
&& fabs(rect.right() - roundedRect.right()) < epsilon
&& fabs(rect.bottom() - roundedRect.bottom()) < epsilon;
}
// This function is used to scale an image and extract a scaled fragment.
//
// ALGORITHM
//
// Because the scaled image size has to be integers, we approximate the real
// scale with the following formula (only X direction is shown):
//
// scaledImageWidth = round(scaleX * imageRect.width())
// approximateScaleX = scaledImageWidth / imageRect.width()
//
// With this method we maintain a constant scale factor among fragments in
// the scaled image. This allows fragments to stitch together to form the
// full scaled image. The downside is there will be a small difference
// between |scaleX| and |approximateScaleX|.
//
// A scaled image fragment is identified by:
//
// - Scaled image size
// - Scaled image fragment rectangle (IntRect)
//
// Scaled image size has been determined and the next step is to compute the
// rectangle for the scaled image fragment which needs to be an IntRect.
//
// scaledSrcRect = srcRect * (approximateScaleX, approximateScaleY)
// enclosingScaledSrcRect = enclosingIntRect(scaledSrcRect)
//
// Finally we extract the scaled image fragment using
// (scaledImageSize, enclosingScaledSrcRect).
//
static SkBitmap extractScaledImageFragment(const NativeImageSkia& bitmap, const SkRect& srcRect, float scaleX, float scaleY, SkRect* scaledSrcRect)
{
SkISize imageSize = SkISize::Make(bitmap.bitmap().width(), bitmap.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| can be larger than |scaledImageSize| because
// of float inaccuracy so clip to get inside.
enclosingScaledSrcRect.intersect(SkIRect::MakeSize(scaledImageSize));
// scaledSrcRect is relative to the pixel snapped fragment we're extracting.
scaledSrcRect->offset(-enclosingScaledSrcRect.x(), -enclosingScaledSrcRect.y());
return bitmap.resizedBitmap(scaledImageSize, enclosingScaledSrcRect);
}
// This does a lot of computation to resample only the portion of the bitmap
// that will only be drawn. This is critical for performance since when we are
// scrolling, for example, we are only drawing a small strip of the image.
// Resampling the whole image every time is very slow, so this speeds up things
// dramatically.
//
// Note: this code is only used when the canvas transformation is limited to
// scaling or translation.
static void drawResampledBitmap(GraphicsContext* context, SkPaint& paint, const NativeImageSkia& bitmap, const SkRect& srcRect, const SkRect& destRect)
{
TRACE_EVENT0("skia", "drawResampledBitmap");
// We want to scale |destRect| with transformation in the canvas to obtain
// the final scale. The final scale is a combination of scale transform
// in canvas and explicit scaling (srcRect and destRect).
SkRect screenRect;
context->getTotalMatrix().mapRect(&screenRect, destRect);
float realScaleX = screenRect.width() / srcRect.width();
float realScaleY = screenRect.height() / srcRect.height();
// This part of code limits scaling only to visible portion in the
SkRect destRectVisibleSubset;
ClipRectToCanvas(context, destRect, &destRectVisibleSubset);
// ClipRectToCanvas often overshoots, resulting in a larger region than our
// original destRect. Intersecting gets us back inside.
if (!destRectVisibleSubset.intersect(destRect))
return; // Nothing visible in destRect.
// Find the corresponding rect in the source image.
SkMatrix destToSrcTransform;
SkRect srcRectVisibleSubset;
destToSrcTransform.setRectToRect(destRect, srcRect, SkMatrix::kFill_ScaleToFit);
destToSrcTransform.mapRect(&srcRectVisibleSubset, destRectVisibleSubset);
SkRect scaledSrcRect;
SkBitmap scaledImageFragment = extractScaledImageFragment(bitmap, srcRectVisibleSubset, realScaleX, realScaleY, &scaledSrcRect);
context->drawBitmapRect(scaledImageFragment, &scaledSrcRect, destRectVisibleSubset, &paint);
}
static bool hasNon90rotation(GraphicsContext* context)
{
return !context->getTotalMatrix().rectStaysRect();
}
void Image::paintSkBitmap(GraphicsContext* context, const NativeImageSkia& bitmap, const SkRect& srcRect, const SkRect& destRect, const SkXfermode::Mode& compOp)
{
TRACE_EVENT0("skia", "paintSkBitmap");
SkPaint paint;
paint.setXfermodeMode(compOp);
paint.setAlpha(context->getNormalizedAlpha());
paint.setLooper(context->drawLooper());
// only antialias if we're rotated or skewed
paint.setAntiAlias(hasNon90rotation(context));
ResamplingMode resampling;
if (context->isAccelerated())
resampling = RESAMPLE_LINEAR;
else if (context->printing())
resampling = RESAMPLE_NONE;
else {
// Take into account scale applied to the canvas when computing sampling mode (e.g. CSS scale or page scale).
SkRect destRectTarget = destRect;
if (!(context->getTotalMatrix().getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)))
context->getTotalMatrix().mapRect(&destRectTarget, destRect);
resampling = computeResamplingMode(context->getTotalMatrix(), bitmap,
SkScalarToFloat(srcRect.width()), SkScalarToFloat(srcRect.height()),
SkScalarToFloat(destRectTarget.width()), SkScalarToFloat(destRectTarget.height()));
}
if (resampling == RESAMPLE_NONE) {
// FIXME: This is to not break tests (it results in the filter bitmap flag
// being set to true). We need to decide if we respect RESAMPLE_NONE
// being returned from computeResamplingMode.
resampling = RESAMPLE_LINEAR;
}
resampling = limitResamplingMode(context, resampling);
paint.setFilterBitmap(resampling == RESAMPLE_LINEAR);
// FIXME: Bicubic filtering in Skia is only applied to defer-decoded images
// as an experiment. Once this filtering code path becomes stable we should
// turn this on for all cases, including non-defer-decoded images.
bool useBicubicFilter = resampling == RESAMPLE_AWESOME
&& DeferredImageDecoder::isLazyDecoded(bitmap.bitmap());
if (useBicubicFilter)
paint.setFlags(paint.getFlags() | SkPaint::kBicubicFilterBitmap_Flag);
if (resampling == RESAMPLE_AWESOME && !useBicubicFilter) {
// Resample the image and then draw the result to canvas with bilinear
// filtering.
drawResampledBitmap(context, paint, bitmap, srcRect, destRect);
} else {
// 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.bitmap(), &srcRect, destRect, &paint);
}
context->didDrawRect(destRect, paint, &bitmap.bitmap());
}
bool FrameData::clear(bool clearMetadata)
{
if (clearMetadata)
m_haveMetadata = false;
m_orientation = DefaultImageOrientation;
if (m_frame) {
m_frame.clear();
return true;
}
return false;
}
void Image::drawPattern(GraphicsContext* context,
const FloatRect& floatSrcRect,
const AffineTransform& patternTransform,
const FloatPoint& phase,
CompositeOperator compositeOp,
const FloatRect& destRect,
BlendMode blendMode)
{
TRACE_EVENT0("skia", "Image::drawPattern");
RefPtr<NativeImageSkia> bitmap = nativeImageForCurrentFrame();
if (!bitmap)
return;
FloatRect normSrcRect = adjustForNegativeSize(floatSrcRect);
normSrcRect.intersect(FloatRect(0, 0, bitmap->bitmap().width(), bitmap->bitmap().height()));
if (destRect.isEmpty() || normSrcRect.isEmpty())
return; // nothing to draw
SkMatrix ctm = context->getTotalMatrix();
SkMatrix totalMatrix;
totalMatrix.setConcat(ctm, patternTransform);
// 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());
// Compute the resampling mode.
ResamplingMode resampling;
if (context->isAccelerated() || context->printing())
resampling = RESAMPLE_LINEAR;
else
resampling = computeResamplingMode(totalMatrix, *bitmap, normSrcRect.width(), normSrcRect.height(), destBitmapWidth, destBitmapHeight);
resampling = limitResamplingMode(context, resampling);
// Load the transform WebKit requested.
SkMatrix matrix(patternTransform);
SkShader* shader;
// Bicubic filter is only applied to defer-decoded images, see
// paintSkBitmap() for details.
bool useBicubicFilter = resampling == RESAMPLE_AWESOME
&& DeferredImageDecoder::isLazyDecoded(bitmap->bitmap());
if (resampling == RESAMPLE_AWESOME && !useBicubicFilter) {
// Do nice resampling.
float scaleX = destBitmapWidth / normSrcRect.width();
float scaleY = destBitmapHeight / normSrcRect.height();
SkRect scaledSrcRect;
// 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(*bitmap, normSrcRect, scaleX, scaleY, &scaledSrcRect);
shader = SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode);
// Since we just resized the bitmap, we need to remove the scale
// applied to the pixels in the bitmap shader. This means we need
// CTM * patternTransform to have identity scale. Since we
// can't modify CTM (or the rectangle will be drawn in the wrong
// place), we must set patternTransform's scale to the inverse of
// CTM scale.
matrix.setScaleX(ctm.getScaleX() ? 1 / ctm.getScaleX() : 1);
matrix.setScaleY(ctm.getScaleY() ? 1 / ctm.getScaleY() : 1);
} else {
// No need to resample before drawing.
SkBitmap srcSubset;
bitmap->bitmap().extractSubset(&srcSubset, enclosingIntRect(normSrcRect));
shader = SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode);
}
// 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 patter. If WebKit wants
// a shifted image, it will shift it from there using the patternTransform.
float adjustedX = phase.x() + normSrcRect.x() *
narrowPrecisionToFloat(patternTransform.a());
float adjustedY = phase.y() + normSrcRect.y() *
narrowPrecisionToFloat(patternTransform.d());
matrix.postTranslate(SkFloatToScalar(adjustedX),
SkFloatToScalar(adjustedY));
shader->setLocalMatrix(matrix);
SkPaint paint;
paint.setShader(shader)->unref();
paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode));
paint.setFilterBitmap(resampling == RESAMPLE_LINEAR);
if (useBicubicFilter)
paint.setFlags(paint.getFlags() | SkPaint::kBicubicFilterBitmap_Flag);
context->drawRect(destRect, paint);
}
} // namespace WebCore