Don't use sprite-blit if cubic sampling

Need this to land first: https://chromium-review.googlesource.com/c/chromium/src/+/2597643

Bug: skia:7650
Change-Id: Idebecfc8a5922a937fd46ccff1eea4df53f6b0cd
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/345170
Reviewed-by: Mike Klein <mtklein@google.com>
Reviewed-by: Florin Malita <fmalita@google.com>
Commit-Queue: Mike Reed <reed@google.com>
This commit is contained in:
Mike Reed 2020-12-21 09:33:59 -05:00 committed by Skia Commit-Bot
parent 88883c4ca2
commit 6aea078802
9 changed files with 125 additions and 29 deletions

View File

@ -238,6 +238,7 @@ tests_sources = [
"$_tests/SVGDeviceTest.cpp",
"$_tests/SafeMathTest.cpp",
"$_tests/SamplePatternDictionaryTest.cpp",
"$_tests/SamplingTest.cpp",
"$_tests/ScalarTest.cpp",
"$_tests/ScaleToSidesTest.cpp",
"$_tests/SerialProcsTest.cpp",

View File

@ -2708,7 +2708,8 @@ private:
/**
* Returns true if the paint's imagefilter can be invoked directly, without needed a layer.
*/
bool canDrawBitmapAsSprite(SkScalar x, SkScalar y, int w, int h, const SkPaint&);
bool canDrawBitmapAsSprite(SkScalar x, SkScalar y, int w, int h, const SkSamplingOptions&,
const SkPaint&);
/**
* Returns true if the clip (for any active layer) contains antialiasing.

View File

@ -2205,13 +2205,14 @@ void SkCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) {
this->topDevice()->drawPath(path, layer.paint());
}
bool SkCanvas::canDrawBitmapAsSprite(SkScalar x, SkScalar y, int w, int h, const SkPaint& paint) {
bool SkCanvas::canDrawBitmapAsSprite(SkScalar x, SkScalar y, int w, int h,
const SkSamplingOptions& sampling, const SkPaint& paint) {
if (!paint.getImageFilter()) {
return false;
}
const SkMatrix& ctm = this->getTotalMatrix();
if (!SkTreatAsSprite(ctm, SkISize::Make(w, h), paint)) {
if (!SkTreatAsSprite(ctm, SkISize::Make(w, h), sampling, paint)) {
return false;
}
@ -2257,8 +2258,11 @@ void SkCanvas::onDrawImage(const SkImage* image, SkScalar x, SkScalar y, const S
return;
}
// TODO: this should be passed in directly by the client
const SkSamplingOptions sampling(paint ? paint->getFilterQuality() : kNone_SkFilterQuality);
if (realPaint.getImageFilter() &&
this->canDrawBitmapAsSprite(x, y, image->width(), image->height(), realPaint) &&
this->canDrawBitmapAsSprite(x, y, image->width(), image->height(), sampling, realPaint) &&
!image_to_color_filter(&realPaint)) {
// Evaluate the image filter directly on the input image and then draw the result, instead
// of first drawing the image to a temporary layer and filtering.

View File

@ -30,6 +30,7 @@
#include "src/core/SkPathPriv.h"
#include "src/core/SkRasterClip.h"
#include "src/core/SkRectPriv.h"
#include "src/core/SkSamplingPriv.h"
#include "src/core/SkScan.h"
#include "src/core/SkStroke.h"
#include "src/core/SkTLazy.h"
@ -37,13 +38,6 @@
#include <utility>
static bool allows_sprite(const SkSamplingOptions& sampling) {
// Legacy behavior is to ignore sampling if there is no matrix, but new behavior
// should respect cubic, and not draw as sprite. Need to rebaseline test images
// to respect this...
// return !sampling.useCubic
return true;
}
static SkPaint make_paint_with_image(const SkPaint& origPaint, const SkBitmap& bitmap,
const SkSamplingOptions& sampling,
SkMatrix* matrix = nullptr) {
@ -983,7 +977,8 @@ void SkDraw::drawBitmapAsMask(const SkBitmap& bitmap, const SkSamplingOptions& s
}
SkMatrix ctm = fMatrixProvider->localToDevice();
if (allows_sprite(sampling) && SkTreatAsSprite(ctm, bitmap.dimensions(), paint)) {
if (SkTreatAsSprite(ctm, bitmap.dimensions(), sampling, paint))
{
int ix = SkScalarRoundToInt(ctm.getTranslateX());
int iy = SkScalarRoundToInt(ctm.getTranslateY());
@ -1101,8 +1096,7 @@ void SkDraw::drawBitmap(const SkBitmap& bitmap, const SkMatrix& prematrix,
}
if (bitmap.colorType() != kAlpha_8_SkColorType
&& allows_sprite(sampling)
&& SkTreatAsSprite(matrix, bitmap.dimensions(), *paint)) {
&& SkTreatAsSprite(matrix, bitmap.dimensions(), sampling, *paint)) {
//
// It is safe to call lock pixels now, since we know the matrix is
// (more or less) identity.

View File

@ -1628,8 +1628,14 @@ void SkMatrix::dump() const {
///////////////////////////////////////////////////////////////////////////////
#include "src/core/SkMatrixUtils.h"
#include "src/core/SkSamplingPriv.h"
bool SkTreatAsSprite(const SkMatrix& mat, const SkISize& size, const SkSamplingOptions& sampling,
const SkPaint& paint) {
if (!SkSamplingPriv::NoChangeWithIdentityMatrix(sampling)) {
return false;
}
bool SkTreatAsSprite(const SkMatrix& mat, const SkISize& size, const SkPaint& paint) {
// Our path aa is 2-bits, and our rect aa is 8, so we could use 8,
// but in practice 4 seems enough (still looks smooth) and allows
// more slightly fractional cases to fall into the fast (sprite) case.

View File

@ -13,6 +13,7 @@
class SkMatrix;
class SkPaint;
struct SkSamplingOptions;
/**
* Given a matrix, size and paint, return true if the computed dst-rect would
@ -22,7 +23,8 @@ class SkPaint;
*
* The src-rect is defined to be { 0, 0, size.width(), size.height() }
*/
bool SkTreatAsSprite(const SkMatrix&, const SkISize& size, const SkPaint& paint);
bool SkTreatAsSprite(const SkMatrix&, const SkISize& size, const SkSamplingOptions&,
const SkPaint&);
/** Decomposes the upper-left 2x2 of the matrix into a rotation (represented by
the cosine and sine of the rotation angle), followed by a non-uniform scale,

29
src/core/SkSamplingPriv.h Normal file
View File

@ -0,0 +1,29 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkSamplingPriv_DEFINED
#define SkSamplingPriv_DEFINED
#include "include/core/SkSamplingOptions.h"
class SkSamplingPriv {
public:
// Returns true if the sampling can be ignored when the CTM is identity.
static bool NoChangeWithIdentityMatrix(const SkSamplingOptions& sampling) {
#ifdef SK_SUPPORT_LEGACY_SPRITE_IGNORE_HQ
// Legacy behavior is to ignore sampling if there is identity matrix, even with cubic
// reampling.
return true;
#else
// If B == 0, the cubic resampler should have no effect for identity matrices
// https://entropymine.com/imageworsener/bicubic/
return !sampling.useCubic || sampling.cubic.B == 0;
#endif
}
};
#endif

View File

@ -54,12 +54,14 @@ static void test_treatAsSprite(skiatest::Reporter* reporter) {
SkPaint aaPaint;
aaPaint.setAntiAlias(true);
const SkSamplingOptions sampling;
// assert: translate-only no-aa can always be treated as sprite
for (int i = 0; i < 1000; ++i) {
rand_matrix(&mat, rand, SkMatrix::kTranslate_Mask);
for (int j = 0; j < 1000; ++j) {
rand_size(&size, rand);
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, noaaPaint));
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, sampling, noaaPaint));
}
}
@ -68,8 +70,8 @@ static void test_treatAsSprite(skiatest::Reporter* reporter) {
rand_matrix(&mat, rand, SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask);
for (int j = 0; j < 1000; ++j) {
rand_size(&size, rand);
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, noaaPaint));
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, aaPaint));
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, sampling, noaaPaint));
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, sampling, aaPaint));
}
}
@ -77,33 +79,33 @@ static void test_treatAsSprite(skiatest::Reporter* reporter) {
const SkScalar tooMuchSubpixel = 100.1f;
mat.setTranslate(tooMuchSubpixel, 0);
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, aaPaint));
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, sampling, aaPaint));
mat.setTranslate(0, tooMuchSubpixel);
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, aaPaint));
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, sampling, aaPaint));
const SkScalar tinySubPixel = 100.02f;
mat.setTranslate(tinySubPixel, 0);
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, aaPaint));
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, sampling, aaPaint));
mat.setTranslate(0, tinySubPixel);
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, aaPaint));
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, sampling, aaPaint));
const SkScalar twoThirds = SK_Scalar1 * 2 / 3;
const SkScalar bigScale = (size.width() + twoThirds) / size.width();
mat.setScale(bigScale, bigScale);
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, noaaPaint));
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, aaPaint));
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, sampling, noaaPaint));
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, sampling, aaPaint));
const SkScalar oneThird = SK_Scalar1 / 3;
const SkScalar smallScale = (size.width() + oneThird) / size.width();
mat.setScale(smallScale, smallScale);
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, noaaPaint));
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, aaPaint));
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, sampling, noaaPaint));
REPORTER_ASSERT(reporter, !SkTreatAsSprite(mat, size, sampling, aaPaint));
const SkScalar oneFortyth = SK_Scalar1 / 40;
const SkScalar tinyScale = (size.width() + oneFortyth) / size.width();
mat.setScale(tinyScale, tinyScale);
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, noaaPaint));
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, aaPaint));
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, sampling, noaaPaint));
REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, sampling, aaPaint));
}
static void test_wacky_bitmapshader(skiatest::Reporter* reporter,

57
tests/SamplingTest.cpp Normal file
View File

@ -0,0 +1,57 @@
/*
* Copyright 2020 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkCanvas.h"
#include "include/core/SkSurface.h"
#include "include/utils/SkRandom.h"
#include "src/core/SkSamplingPriv.h"
#include "tests/Test.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
// In general, sampling under identity matrix should not affect the pixels. However,
// cubic resampling when B != 0 is expected to change pixels.
//
DEF_TEST(sampling_with_identity_matrix, r) {
const char* names[] = {
"images/mandrill_128.png", "images/color_wheel.jpg",
};
SkRandom rand;
for (auto name : names) {
auto src = GetResourceAsImage(name);
auto surf = SkSurface::MakeRasterN32Premul(src->width(), src->height());
auto canvas = surf->getCanvas();
auto dotest = [&](const SkSamplingOptions& sampling, bool expect_same) {
canvas->clear(0);
canvas->drawImage(src.get(), 0, 0, sampling, nullptr);
auto dst = surf->makeImageSnapshot();
REPORTER_ASSERT(r, SkSamplingPriv::NoChangeWithIdentityMatrix(sampling) == expect_same);
REPORTER_ASSERT(r, ToolUtils::equal_pixels(src.get(), dst.get()) == expect_same);
};
// Exercise all non-cubics -- expecting no changes
for (auto m : {SkMipmapMode::kNone, SkMipmapMode::kNearest, SkMipmapMode::kLinear}) {
for (auto f : {SkFilterMode::kNearest, SkFilterMode::kLinear}) {
dotest(SkSamplingOptions(f, m), true);
}
}
// Exercise cubic variants with B zero and non-zero
constexpr int N = 30; // try a bunch of random values
for (int i = 0; i < N; ++i) {
float C = rand.nextF();
dotest(SkSamplingOptions({0, C}), true);
float B = rand.nextF() * 0.9f + 0.05f; // non-zero but still within (0,,,1]
SkASSERT(B != 0);
dotest(SkSamplingOptions({B, C}), false);
}
}
}