mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
330 lines
13 KiB
C++
330 lines
13 KiB
C++
/*
|
|
* Copyright (c) 2006,2007,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 "flutter/sky/engine/platform/graphics/skia/SkiaUtils.h"
|
|
|
|
#include "flutter/sky/engine/platform/graphics/GraphicsContext.h"
|
|
#include "third_party/skia/include/core/SkColorPriv.h"
|
|
#include "third_party/skia/include/core/SkRegion.h"
|
|
|
|
namespace blink {
|
|
|
|
static const struct CompositOpToXfermodeMode {
|
|
CompositeOperator mCompositOp;
|
|
SkBlendMode m_xfermodeMode;
|
|
} gMapCompositOpsToXfermodeModes[] = {
|
|
{CompositeClear, SkBlendMode::kClear},
|
|
{CompositeCopy, SkBlendMode::kSrc},
|
|
{CompositeSourceOver, SkBlendMode::kSrcOver},
|
|
{CompositeSourceIn, SkBlendMode::kSrcIn},
|
|
{CompositeSourceOut, SkBlendMode::kSrcOut},
|
|
{CompositeSourceAtop, SkBlendMode::kSrcATop},
|
|
{CompositeDestinationOver, SkBlendMode::kDstOver},
|
|
{CompositeDestinationIn, SkBlendMode::kDstIn},
|
|
{CompositeDestinationOut, SkBlendMode::kDstOut},
|
|
{CompositeDestinationAtop, SkBlendMode::kDstATop},
|
|
{CompositeXOR, SkBlendMode::kXor},
|
|
{CompositePlusDarker, SkBlendMode::kDarken},
|
|
{CompositePlusLighter, SkBlendMode::kPlus}};
|
|
|
|
// keep this array in sync with WebBlendMode enum in
|
|
// public/platform/WebBlendMode.h
|
|
static const SkBlendMode gMapBlendOpsToXfermodeModes[] = {
|
|
SkBlendMode::kClear, // WebBlendModeNormal
|
|
SkBlendMode::kMultiply, // WebBlendModeMultiply
|
|
SkBlendMode::kScreen, // WebBlendModeScreen
|
|
SkBlendMode::kOverlay, // WebBlendModeOverlay
|
|
SkBlendMode::kDarken, // WebBlendModeDarken
|
|
SkBlendMode::kLighten, // WebBlendModeLighten
|
|
SkBlendMode::kColorDodge, // WebBlendModeColorDodge
|
|
SkBlendMode::kColorBurn, // WebBlendModeColorBurn
|
|
SkBlendMode::kHardLight, // WebBlendModeHardLight
|
|
SkBlendMode::kSoftLight, // WebBlendModeSoftLight
|
|
SkBlendMode::kDifference, // WebBlendModeDifference
|
|
SkBlendMode::kExclusion, // WebBlendModeExclusion
|
|
SkBlendMode::kHue, // WebBlendModeHue
|
|
SkBlendMode::kSaturation, // WebBlendModeSaturation
|
|
SkBlendMode::kColor, // WebBlendModeColor
|
|
SkBlendMode::kLuminosity // WebBlendModeLuminosity
|
|
};
|
|
|
|
SkBlendMode WebCoreCompositeToSkiaComposite(CompositeOperator op,
|
|
WebBlendMode blendMode) {
|
|
if (blendMode != WebBlendModeNormal) {
|
|
if (static_cast<uint8_t>(blendMode) >=
|
|
SK_ARRAY_COUNT(gMapBlendOpsToXfermodeModes)) {
|
|
SkDEBUGF(
|
|
("GraphicsContext::setPlatformCompositeOperation unknown "
|
|
"WebBlendMode %d\n",
|
|
blendMode));
|
|
return SkBlendMode::kSrcOver;
|
|
}
|
|
return gMapBlendOpsToXfermodeModes[static_cast<uint8_t>(blendMode)];
|
|
}
|
|
|
|
const CompositOpToXfermodeMode* table = gMapCompositOpsToXfermodeModes;
|
|
if (static_cast<uint8_t>(op) >=
|
|
SK_ARRAY_COUNT(gMapCompositOpsToXfermodeModes)) {
|
|
SkDEBUGF(
|
|
("GraphicsContext::setPlatformCompositeOperation unknown "
|
|
"CompositeOperator %d\n",
|
|
op));
|
|
return SkBlendMode::kSrcOver;
|
|
}
|
|
SkASSERT(table[static_cast<uint8_t>(op)].mCompositOp == op);
|
|
return table[static_cast<uint8_t>(op)].m_xfermodeMode;
|
|
}
|
|
|
|
static U8CPU InvScaleByte(U8CPU component, uint32_t scale) {
|
|
SkASSERT(component == (uint8_t)component);
|
|
return (component * scale + 0x8000) >> 16;
|
|
}
|
|
|
|
SkColor SkPMColorToColor(SkPMColor pm) {
|
|
if (!pm)
|
|
return 0;
|
|
unsigned a = SkGetPackedA32(pm);
|
|
if (!a) {
|
|
// A zero alpha value when there are non-zero R, G, or B channels is an
|
|
// invalid premultiplied color (since all channels should have been
|
|
// multiplied by 0 if a=0).
|
|
SkASSERT(false);
|
|
// In production, return 0 to protect against division by zero.
|
|
return 0;
|
|
}
|
|
|
|
uint32_t scale = (255 << 16) / a;
|
|
|
|
return SkColorSetARGB(a, InvScaleByte(SkGetPackedR32(pm), scale),
|
|
InvScaleByte(SkGetPackedG32(pm), scale),
|
|
InvScaleByte(SkGetPackedB32(pm), scale));
|
|
}
|
|
|
|
bool SkPathContainsPoint(const SkPath& originalPath,
|
|
const FloatPoint& point,
|
|
SkPath::FillType ft) {
|
|
SkRect bounds = originalPath.getBounds();
|
|
|
|
// We can immediately return false if the point is outside the bounding
|
|
// rect. We don't use bounds.contains() here, since it would exclude
|
|
// points on the right and bottom edges of the bounding rect, and we want
|
|
// to include them.
|
|
SkScalar fX = SkFloatToScalar(point.x());
|
|
SkScalar fY = SkFloatToScalar(point.y());
|
|
if (fX < bounds.fLeft || fX > bounds.fRight || fY < bounds.fTop ||
|
|
fY > bounds.fBottom)
|
|
return false;
|
|
|
|
// Scale the path to a large size before hit testing for two reasons:
|
|
// 1) Skia has trouble with coordinates close to the max signed 16-bit values,
|
|
// so we scale larger paths down.
|
|
// TODO: when Skia is patched to work properly with large values, this will
|
|
// not be necessary.
|
|
// 2) Skia does not support analytic hit testing, so we scale paths up to do
|
|
// raster hit testing with subpixel accuracy.
|
|
SkScalar biggestCoord =
|
|
std::max(std::max(std::max(bounds.fRight, bounds.fBottom), -bounds.fLeft),
|
|
-bounds.fTop);
|
|
if (SkScalarNearlyZero(biggestCoord))
|
|
return false;
|
|
biggestCoord = std::max(std::max(biggestCoord, fX + 1), fY + 1);
|
|
|
|
const SkScalar kMaxCoordinate = SkIntToScalar(1 << 15);
|
|
SkScalar scale = kMaxCoordinate / biggestCoord;
|
|
|
|
SkRegion rgn;
|
|
SkRegion clip;
|
|
SkMatrix m;
|
|
SkPath scaledPath(originalPath);
|
|
|
|
scaledPath.setFillType(ft);
|
|
m.setScale(scale, scale);
|
|
scaledPath.transform(m, 0);
|
|
|
|
int x = static_cast<int>(floorf(0.5f + point.x() * scale));
|
|
int y = static_cast<int>(floorf(0.5f + point.y() * scale));
|
|
clip.setRect(x - 1, y - 1, x + 1, y + 1);
|
|
|
|
return rgn.setPath(scaledPath, clip);
|
|
}
|
|
|
|
SkMatrix affineTransformToSkMatrix(const AffineTransform& source) {
|
|
SkMatrix result;
|
|
|
|
result.setScaleX(WebCoreDoubleToSkScalar(source.a()));
|
|
result.setSkewX(WebCoreDoubleToSkScalar(source.c()));
|
|
result.setTranslateX(WebCoreDoubleToSkScalar(source.e()));
|
|
|
|
result.setScaleY(WebCoreDoubleToSkScalar(source.d()));
|
|
result.setSkewY(WebCoreDoubleToSkScalar(source.b()));
|
|
result.setTranslateY(WebCoreDoubleToSkScalar(source.f()));
|
|
|
|
// FIXME: Set perspective properly.
|
|
result.setPerspX(0);
|
|
result.setPerspY(0);
|
|
result.set(SkMatrix::kMPersp2, SK_Scalar1);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool nearlyIntegral(float value) {
|
|
return fabs(value - floorf(value)) < std::numeric_limits<float>::epsilon();
|
|
}
|
|
|
|
InterpolationQuality limitInterpolationQuality(
|
|
const GraphicsContext* context,
|
|
InterpolationQuality resampling) {
|
|
return std::min(resampling, context->imageInterpolationQuality());
|
|
}
|
|
|
|
InterpolationQuality computeInterpolationQuality(const SkMatrix& matrix,
|
|
float srcWidth,
|
|
float srcHeight,
|
|
float destWidth,
|
|
float destHeight,
|
|
bool isDataComplete) {
|
|
// 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 InterpolationNone;
|
|
|
|
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 InterpolationLow;
|
|
|
|
// Otherwise, don't resample small images. These are often used for
|
|
// borders and rules (think 1x1 images used to make lines).
|
|
return InterpolationNone;
|
|
}
|
|
|
|
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 InterpolationNone;
|
|
|
|
// 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 InterpolationLow;
|
|
}
|
|
|
|
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 InterpolationNone;
|
|
}
|
|
|
|
// 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 InterpolationLow;
|
|
|
|
// Everything else gets resampled.
|
|
// High quality interpolation only enabled for scaling and translation.
|
|
if (!(matrix.getType() &
|
|
(SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)))
|
|
return InterpolationHigh;
|
|
|
|
return InterpolationLow;
|
|
}
|
|
|
|
bool shouldDrawAntiAliased(const GraphicsContext* context,
|
|
const SkRect& destRect) {
|
|
if (!context->shouldAntialias())
|
|
return false;
|
|
const SkMatrix totalMatrix = context->getTotalMatrix();
|
|
// Don't disable anti-aliasing if we're rotated or skewed.
|
|
if (!totalMatrix.rectStaysRect())
|
|
return true;
|
|
// Disable anti-aliasing for scales or n*90 degree rotations.
|
|
// Allow to opt out of the optimization though for "hairline" geometry
|
|
// images - using the shouldAntialiasHairlineImages() GraphicsContext flag.
|
|
if (!context->shouldAntialiasHairlineImages())
|
|
return false;
|
|
// Check if the dimensions of the destination are "small" (less than one
|
|
// device pixel). To prevent sudden drop-outs. Since we know that
|
|
// kRectStaysRect_Mask is set, the matrix either has scale and no skew or
|
|
// vice versa. We can query the kAffine_Mask flag to determine which case
|
|
// it is.
|
|
// FIXME: This queries the CTM while drawing, which is generally
|
|
// discouraged. Always drawing with AA can negatively impact performance
|
|
// though - that's why it's not always on.
|
|
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;
|
|
}
|
|
|
|
} // namespace blink
|