#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/GraphicsContext.h"
#include "platform/graphics/skia/SkiaUtils.h"
-#include "skia/ext/image_operations.h"
+#include "platform/transforms/AffineTransform.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "third_party/skia/include/core/SkShader.h"
-#include <math.h>
-
namespace blink {
-// 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).
-//
-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| 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 resizedBitmap(scaledImageSize, enclosingScaledSrcRect);
-}
-
-NativeImageSkia::NativeImageSkia()
- : m_resizeRequests(0)
-{
-}
-
-NativeImageSkia::NativeImageSkia(const SkBitmap& other)
- : m_bitmap(other)
- , m_resizeRequests(0)
-{
-}
-
-NativeImageSkia::~NativeImageSkia()
-{
-}
-
-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(bitmap()));
-
- if (!hasResizedBitmap(scaledImageSize, scaledImageSubset)) {
- bool shouldCache = isDataComplete()
- && shouldCacheResampling(scaledImageSize, scaledImageSubset);
-
- TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "ResizeImage", "cached", shouldCache);
- // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing.
- PlatformInstrumentation::willResizeImage(shouldCache);
- SkBitmap resizedImage = skia::ImageOperations::Resize(bitmap(), 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;
-}
-
void NativeImageSkia::draw(
GraphicsContext* context,
const SkRect& srcRect,
TRACE_EVENT0("skia", "NativeImageSkia::draw");
bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap());
+ bool isOpaque = bitmap().isOpaque();
SkPaint paint;
- context->preparePaintForDrawRectToRect(&paint, srcRect, destRect, compositeOp, blendMode, isLazyDecoded, isDataComplete());
+ context->preparePaintForDrawRectToRect(&paint, srcRect, destRect, compositeOp, blendMode, !isOpaque, 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
static SkBitmap createBitmapWithSpace(const SkBitmap& bitmap, int spaceWidth, int spaceHeight)
{
SkImageInfo info = bitmap.info();
- info.fWidth += spaceWidth;
- info.fHeight += spaceHeight;
- info.fAlphaType = kPremul_SkAlphaType;
+ info = SkImageInfo::Make(info.width() + spaceWidth, info.height() + spaceHeight, info.colorType(), kPremul_SkAlphaType);
SkBitmap result;
result.allocPixels(info);
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
const float adjustedY = phase.y() + normSrcRect.y() * scale.height();
localMatrix.setTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjustedY));
- RefPtr<SkShader> shader;
- SkPaint::FilterLevel filterLevel = static_cast<SkPaint::FilterLevel>(resampling);
-
- // Bicubic filter is only applied to defer-decoded images, see
- // NativeImageSkia::draw for details.
- if (resampling == InterpolationHigh && !isLazyDecoded) {
- // Do nice resampling.
- filterLevel = SkPaint::kNone_FilterLevel;
- 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));
- }
+ // 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());
+
+ SkBitmap bitmapToPaint;
+ bitmap().extractSubset(&bitmapToPaint, enclosingIntRect(normSrcRect));
+ if (!repeatSpacing.isZero()) {
+ bitmapToPaint = createBitmapWithSpace(
+ bitmapToPaint,
+ repeatSpacing.width() * ctmScaleX / scale.width(),
+ repeatSpacing.height() * ctmScaleY / scale.height());
}
+ RefPtr<SkShader> shader = adoptRef(SkShader::CreateBitmapShader(bitmapToPaint, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix));
SkPaint paint;
paint.setShader(shader.get());
- paint.setXfermode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode).get());
+ paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode));
paint.setColorFilter(context->colorFilter());
- paint.setFilterLevel(filterLevel);
+ paint.setFilterLevel(static_cast<SkPaint::FilterLevel>(resampling));
if (isLazyDecoded)
PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID());
context->drawRect(destRect, paint);
}
-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;
-}
-
-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;
-}
-
} // namespace blink