From c4d2f907311fca08303c254b2488bd7990fa2f32 Mon Sep 17 00:00:00 2001 From: robertphillips Date: Tue, 16 Aug 2016 09:30:03 -0700 Subject: [PATCH] Update ComputeBlurredRRectParams to compute all the parameters needed for occluded blurred rrect ninepatch draws This is split out of: https://codereview.chromium.org/2245653002/ (Start using vertex attributes for nine-patch blurred rrect draws) GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2248533002 Review-Url: https://codereview.chromium.org/2248533002 --- include/effects/SkBlurMaskFilter.h | 26 +++-- src/effects/SkBlurMaskFilter.cpp | 193 +++++++++++++++++++++++++++---------- tests/BlurTest.cpp | 133 +++++++++++++++++++------ 3 files changed, 268 insertions(+), 84 deletions(-) diff --git a/include/effects/SkBlurMaskFilter.h b/include/effects/SkBlurMaskFilter.h index 12eec60..dfbae6b 100644 --- a/include/effects/SkBlurMaskFilter.h +++ b/include/effects/SkBlurMaskFilter.h @@ -73,15 +73,27 @@ public: SkScalar blurRadius); #endif - static bool ComputeBlurredRRectParams(const SkRRect& rrect, - SkScalar sigma, + static const int kMaxDivisions = 6; + + // This method computes all the parameters for drawing a partially occluded nine-patched + // blurred rrect mask: + // rrectToDraw - the integerized rrect to draw in the mask + // widthHeight - how large to make the mask (rrectToDraw will be centered in this coord sys) + // rectXs, rectYs - the x & y coordinates of the covering geometry lattice + // texXs, texYs - the texture coordinate at each point in rectXs & rectYs + // numXs, numYs - number of coordinates in the x & y directions + // skipMask - bit mask that contains a 1-bit whenever one of the cells is occluded + // It returns true if 'devRRect' is nine-patchable + static bool ComputeBlurredRRectParams(const SkRRect& srcRRect, const SkRRect& devRRect, + const SkRect& occluder, + SkScalar sigma, SkScalar xformedSigma, SkRRect* rrectToDraw, SkISize* widthHeight, - SkScalar xs[4], - int* numXs, - SkScalar ys[4], - int* numYs); - + SkScalar rectXs[kMaxDivisions], + SkScalar rectYs[kMaxDivisions], + SkScalar texXs[kMaxDivisions], + SkScalar texYs[kMaxDivisions], + int* numXs, int* numYs, uint32_t* skipMask); SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP() diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp index 0e6de9e..c044333 100644 --- a/src/effects/SkBlurMaskFilter.cpp +++ b/src/effects/SkBlurMaskFilter.cpp @@ -139,59 +139,144 @@ sk_sp SkBlurMaskFilter::Make(SkBlurStyle style, SkScalar sigma, return sk_sp(new SkBlurMaskFilterImpl(sigma, style, occluder, flags)); } -bool SkBlurMaskFilter::ComputeBlurredRRectParams(const SkRRect& rrect, - SkScalar sigma, +// linearly interpolate between y1 & y3 to match x2's position between x1 & x3 +static SkScalar interp(SkScalar x1, SkScalar x2, SkScalar x3, SkScalar y1, SkScalar y3) { + SkASSERT(x1 <= x2 && x2 <= x3); + SkASSERT(y1 <= y3); + + SkScalar t = (x2 - x1) / (x3 - x1); + return y1 + t * (y3 - y1); +} + +// Insert 'lower' and 'higher' into 'array1' and insert a new value at each matching insertion +// point in 'array2' that linearly interpolates between the existing values. +// Return a bit mask which contains a copy of 'inputMask' for all the cells between the two +// insertion points. +static uint32_t insert_into_arrays(SkScalar* array1, SkScalar* array2, + SkScalar lower, SkScalar higher, + int* num, uint32_t inputMask, int maskSize) { + SkASSERT(lower < higher); + SkASSERT(lower >= array1[0] && higher <= array1[*num-1]); + + int32_t skipMask = 0x0; + int i; + for (i = 0; i < *num; ++i) { + if (lower >= array1[i] && lower < array1[i+1]) { + if (!SkScalarNearlyEqual(lower, array1[i])) { + memmove(&array1[i+2], &array1[i+1], (*num-i-1)*sizeof(SkScalar)); + array1[i+1] = lower; + memmove(&array2[i+2], &array2[i+1], (*num-i-1)*sizeof(SkScalar)); + array2[i+1] = interp(array1[i], lower, array1[i+2], array2[i], array2[i+2]); + i++; + (*num)++; + } + break; + } + } + for ( ; i < *num; ++i) { + skipMask |= inputMask << (i*maskSize); + if (higher > array1[i] && higher <= array1[i+1]) { + if (!SkScalarNearlyEqual(higher, array1[i+1])) { + memmove(&array1[i+2], &array1[i+1], (*num-i-1)*sizeof(SkScalar)); + array1[i+1] = higher; + memmove(&array2[i+2], &array2[i+1], (*num-i-1)*sizeof(SkScalar)); + array2[i+1] = interp(array1[i], higher, array1[i+2], array2[i], array2[i+2]); + (*num)++; + } + break; + } + } + + return skipMask; +} + +bool SkBlurMaskFilter::ComputeBlurredRRectParams(const SkRRect& srcRRect, const SkRRect& devRRect, + const SkRect& occluder, + SkScalar sigma, SkScalar xformedSigma, SkRRect* rrectToDraw, SkISize* widthHeight, - SkScalar xs[4], - int* numXs, - SkScalar ys[4], - int* numYs) { - unsigned int blurRadius = 3*SkScalarCeilToInt(sigma-1/6.0f); - - const SkRect& orig = rrect.getBounds(); - const SkVector& radiiUL = rrect.radii(SkRRect::kUpperLeft_Corner); - const SkVector& radiiUR = rrect.radii(SkRRect::kUpperRight_Corner); - const SkVector& radiiLR = rrect.radii(SkRRect::kLowerRight_Corner); - const SkVector& radiiLL = rrect.radii(SkRRect::kLowerLeft_Corner); - - const int left = SkScalarCeilToInt(SkTMax(radiiUL.fX, radiiLL.fX)); - const int top = SkScalarCeilToInt(SkTMax(radiiUL.fY, radiiUR.fY)); - const int right = SkScalarCeilToInt(SkTMax(radiiUR.fX, radiiLR.fX)); - const int bot = SkScalarCeilToInt(SkTMax(radiiLL.fY, radiiLR.fY)); + SkScalar rectXs[kMaxDivisions], + SkScalar rectYs[kMaxDivisions], + SkScalar texXs[kMaxDivisions], + SkScalar texYs[kMaxDivisions], + int* numXs, int* numYs, uint32_t* skipMask) { + unsigned int devBlurRadius = 3*SkScalarCeilToInt(xformedSigma-1/6.0f); + SkScalar srcBlurRadius = 3.0f * sigma; + + const SkRect& devOrig = devRRect.getBounds(); + const SkVector& devRadiiUL = devRRect.radii(SkRRect::kUpperLeft_Corner); + const SkVector& devRadiiUR = devRRect.radii(SkRRect::kUpperRight_Corner); + const SkVector& devRadiiLR = devRRect.radii(SkRRect::kLowerRight_Corner); + const SkVector& devRadiiLL = devRRect.radii(SkRRect::kLowerLeft_Corner); + + const int devLeft = SkScalarCeilToInt(SkTMax(devRadiiUL.fX, devRadiiLL.fX)); + const int devTop = SkScalarCeilToInt(SkTMax(devRadiiUL.fY, devRadiiUR.fY)); + const int devRight = SkScalarCeilToInt(SkTMax(devRadiiUR.fX, devRadiiLR.fX)); + const int devBot = SkScalarCeilToInt(SkTMax(devRadiiLL.fY, devRadiiLR.fY)); // This is a conservative check for nine-patchability - if (orig.fLeft + left + blurRadius >= orig.fRight - right - blurRadius || - orig.fTop + top + blurRadius >= orig.fBottom - bot - blurRadius) { + if (devOrig.fLeft + devLeft + devBlurRadius >= devOrig.fRight - devRight - devBlurRadius || + devOrig.fTop + devTop + devBlurRadius >= devOrig.fBottom - devBot - devBlurRadius) { return false; } - int newRRWidth, newRRHeight; + const SkVector& srcRadiiUL = srcRRect.radii(SkRRect::kUpperLeft_Corner); + const SkVector& srcRadiiUR = srcRRect.radii(SkRRect::kUpperRight_Corner); + const SkVector& srcRadiiLR = srcRRect.radii(SkRRect::kLowerRight_Corner); + const SkVector& srcRadiiLL = srcRRect.radii(SkRRect::kLowerLeft_Corner); + + const SkScalar srcLeft = SkTMax(srcRadiiUL.fX, srcRadiiLL.fX); + const SkScalar srcTop = SkTMax(srcRadiiUL.fY, srcRadiiUR.fY); + const SkScalar srcRight = SkTMax(srcRadiiUR.fX, srcRadiiLR.fX); + const SkScalar srcBot = SkTMax(srcRadiiLL.fY, srcRadiiLR.fY); + + int newRRWidth = 2*devBlurRadius + devLeft + devRight + 1; + int newRRHeight = 2*devBlurRadius + devTop + devBot + 1; + widthHeight->fWidth = newRRWidth + 2 * devBlurRadius; + widthHeight->fHeight = newRRHeight + 2 * devBlurRadius; + + const SkRect srcProxyRect = srcRRect.getBounds().makeOutset(srcBlurRadius, srcBlurRadius); + + rectXs[0] = srcProxyRect.fLeft; + rectXs[1] = srcProxyRect.fLeft + 2*srcBlurRadius + srcLeft; + rectXs[2] = srcProxyRect.fRight - 2*srcBlurRadius - srcRight; + rectXs[3] = srcProxyRect.fRight; + + rectYs[0] = srcProxyRect.fTop; + rectYs[1] = srcProxyRect.fTop + 2*srcBlurRadius + srcTop; + rectYs[2] = srcProxyRect.fBottom - 2*srcBlurRadius - srcBot; + rectYs[3] = srcProxyRect.fBottom; + + texXs[0] = 0.0f; + texXs[1] = 2.0f*devBlurRadius + devLeft; + texXs[2] = 2.0f*devBlurRadius + devLeft + 1; + texXs[3] = SkIntToScalar(widthHeight->fWidth); + + texYs[0] = 0.0f; + texYs[1] = 2.0f*devBlurRadius + devTop; + texYs[2] = 2.0f*devBlurRadius + devTop + 1; + texYs[3] = SkIntToScalar(widthHeight->fHeight); + + SkRect temp = occluder; - // 3x3 case - newRRWidth = 2*blurRadius + left + right + 1; - newRRHeight = 2*blurRadius + top + bot + 1; - widthHeight->fWidth = newRRWidth + 2 * blurRadius; - widthHeight->fHeight = newRRHeight + 2 * blurRadius; - // TODO: need to return non-normalized indices - xs[0] = 0.0f; - xs[1] = (blurRadius + left) / (float) widthHeight->fWidth; - xs[2] = (blurRadius + left + 1.0f) / widthHeight->fWidth; - xs[3] = 1.0f; *numXs = 4; - ys[0] = 0.0f; - ys[1] = (blurRadius + top) / (float) widthHeight->fHeight; - ys[2] = (blurRadius + top + 1.0f) / widthHeight->fHeight; - ys[3] = 1.0f; *numYs = 4; + *skipMask = 0; + if (!temp.isEmpty() && (srcProxyRect.contains(temp) || temp.intersect(srcProxyRect))) { + *skipMask = insert_into_arrays(rectXs, texXs, temp.fLeft, temp.fRight, numXs, 0x1, 1); + *skipMask = insert_into_arrays(rectYs, texYs, temp.fTop, temp.fBottom, + numYs, *skipMask, *numXs-1); + } - const SkRect newRect = SkRect::MakeXYWH(SkIntToScalar(blurRadius), SkIntToScalar(blurRadius), - SkIntToScalar(newRRWidth), SkIntToScalar(newRRHeight)); + const SkRect newRect = SkRect::MakeXYWH(SkIntToScalar(devBlurRadius), + SkIntToScalar(devBlurRadius), + SkIntToScalar(newRRWidth), + SkIntToScalar(newRRHeight)); SkVector newRadii[4]; - newRadii[0] = { SkScalarCeilToScalar(radiiUL.fX), SkScalarCeilToScalar(radiiUL.fY) }; - newRadii[1] = { SkScalarCeilToScalar(radiiUR.fX), SkScalarCeilToScalar(radiiUR.fY) }; - newRadii[2] = { SkScalarCeilToScalar(radiiLR.fX), SkScalarCeilToScalar(radiiLR.fY) }; - newRadii[3] = { SkScalarCeilToScalar(radiiLL.fX), SkScalarCeilToScalar(radiiLL.fY) }; + newRadii[0] = { SkScalarCeilToScalar(devRadiiUL.fX), SkScalarCeilToScalar(devRadiiUL.fY) }; + newRadii[1] = { SkScalarCeilToScalar(devRadiiUR.fX), SkScalarCeilToScalar(devRadiiUR.fY) }; + newRadii[2] = { SkScalarCeilToScalar(devRadiiLR.fX), SkScalarCeilToScalar(devRadiiLR.fY) }; + newRadii[3] = { SkScalarCeilToScalar(devRadiiLL.fX), SkScalarCeilToScalar(devRadiiLL.fY) }; rrectToDraw->setRectRadii(newRect, newRadii); return true; @@ -983,7 +1068,9 @@ bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider, class GrRRectBlurEffect : public GrFragmentProcessor { public: - static sk_sp Make(GrContext*, float sigma, const SkRRect&); + static sk_sp Make(GrContext*, + float sigma, float xformedSigma, + const SkRRect& srcRRect, const SkRRect& devRRect); virtual ~GrRRectBlurEffect() {}; const char* name() const override { return "GrRRectBlur"; } @@ -1069,8 +1156,8 @@ static sk_sp find_or_create_rrect_blur_mask(GrContext* context, } sk_sp GrRRectBlurEffect::Make(GrContext* context, - float xformedSigma, - const SkRRect& devRRect) { + float sigma, float xformedSigma, + const SkRRect& srcRRect, const SkRRect& devRRect) { SkASSERT(!devRRect.isCircle()); // Should've been caught up-stream // TODO: loosen this up @@ -1083,13 +1170,18 @@ sk_sp GrRRectBlurEffect::Make(GrContext* context, // width (and height) of the rrect. SkRRect rrectToDraw; SkISize size; - SkScalar ignored[4]; + SkScalar ignored[SkBlurMaskFilter::kMaxDivisions]; int ignoredSize; + uint32_t ignored32; - bool ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(devRRect, xformedSigma, + bool ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(srcRRect, devRRect, + SkRect::MakeEmpty(), + sigma, xformedSigma, &rrectToDraw, &size, - ignored, &ignoredSize, - ignored, &ignoredSize); + ignored, ignored, + ignored, ignored, + &ignoredSize, &ignoredSize, + &ignored32); if (!ninePatchable) { return nullptr; } @@ -1136,7 +1228,7 @@ sk_sp GrRRectBlurEffect::TestCreate(GrProcessorTestData* d) SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); SkRRect rrect; rrect.setRectXY(SkRect::MakeWH(w, h), r, r); - return GrRRectBlurEffect::Make(d->fContext, sigma, rrect); + return GrRRectBlurEffect::Make(d->fContext, sigma, sigma, rrect, rrect); } ////////////////////////////////////////////////////////////////////////////// @@ -1279,7 +1371,8 @@ bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrContext* context, return true; } - sk_sp fp(GrRRectBlurEffect::Make(context, xformedSigma, devRRect)); + sk_sp fp(GrRRectBlurEffect::Make(context, fSigma, xformedSigma, + srcRRect, devRRect)); if (!fp) { return false; } diff --git a/tests/BlurTest.cpp b/tests/BlurTest.cpp index 32e2930..d9fca98 100644 --- a/tests/BlurTest.cpp +++ b/tests/BlurTest.cpp @@ -577,12 +577,15 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SmallBoxBlurBug, reporter, ctxInfo) { DEF_TEST(BlurredRRectNinePatchComputation, reporter) { const SkRect r = SkRect::MakeXYWH(10, 10, 100, 100); + static const SkScalar kBlurRad = 3.0f; bool ninePatchable; SkRRect rrectToDraw; SkISize size; - SkScalar xs[4], ys[4]; - int numXs, numYs; + SkScalar rectXs[SkBlurMaskFilter::kMaxDivisions], rectYs[SkBlurMaskFilter::kMaxDivisions]; + SkScalar texXs[SkBlurMaskFilter::kMaxDivisions], texYs[SkBlurMaskFilter::kMaxDivisions]; + int numX, numY; + uint32_t skipMask; // not nine-patchable { @@ -591,47 +594,123 @@ DEF_TEST(BlurredRRectNinePatchComputation, reporter) { SkRRect rr; rr.setRectRadii(r, radii); - ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(rr, 3.0f, &rrectToDraw, &size, - xs, &numXs, ys, &numYs); + ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(rr, rr, SkRect::MakeEmpty(), + kBlurRad, kBlurRad, + &rrectToDraw, &size, + rectXs, rectYs, texXs, texYs, + &numX, &numY, &skipMask); REPORTER_ASSERT(reporter, !ninePatchable); } // simple circular { + static const SkScalar kCornerRad = 10.0f; SkRRect rr; - rr.setRectXY(r, 10, 10); + rr.setRectXY(r, kCornerRad, kCornerRad); - ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(rr, 3.0f, &rrectToDraw, &size, - xs, &numXs, ys, &numYs); + ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(rr, rr, SkRect::MakeEmpty(), + kBlurRad, kBlurRad, + &rrectToDraw, &size, + rectXs, rectYs, texXs, texYs, + &numX, &numY, &skipMask); + + static const SkScalar kAns = 12.0f * kBlurRad + 2.0f * kCornerRad + 1.0f; REPORTER_ASSERT(reporter, ninePatchable); - REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fWidth), 57.0f)); - REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fHeight), 57.0)); - REPORTER_ASSERT(reporter, 4 == numXs && 4 == numYs); - for (int i = 0; i < numXs; ++i) { - REPORTER_ASSERT(reporter, xs[i] >= 0.0f && xs[i] <= 1.0f); - } - for (int i = 0; i < numYs; ++i) { - REPORTER_ASSERT(reporter, ys[i] >= 0.0f && ys[i] <= 1.0f); - } + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fWidth), kAns)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fHeight), kAns)); + REPORTER_ASSERT(reporter, 4 == numX && 4 == numY); + REPORTER_ASSERT(reporter, !skipMask); } // simple elliptical { + static const SkScalar kXCornerRad = 2.0f; + static const SkScalar kYCornerRad = 10.0f; SkRRect rr; - rr.setRectXY(r, 2, 10); + rr.setRectXY(r, kXCornerRad, kYCornerRad); + + ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(rr, rr, SkRect::MakeEmpty(), + kBlurRad, kBlurRad, + &rrectToDraw, &size, + rectXs, rectYs, texXs, texYs, + &numX, &numY, &skipMask); + + static const SkScalar kXAns = 12.0f * kBlurRad + 2.0f * kXCornerRad + 1.0f; + static const SkScalar kYAns = 12.0f * kBlurRad + 2.0f * kYCornerRad + 1.0f; - ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(rr, 3.0f, &rrectToDraw, &size, - xs, &numXs, ys, &numYs); REPORTER_ASSERT(reporter, ninePatchable); - REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fWidth), 41.0f)); - REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fHeight), 57.0)); - REPORTER_ASSERT(reporter, 4 == numXs && 4 == numYs); - for (int i = 0; i < numXs; ++i) { - REPORTER_ASSERT(reporter, xs[i] >= 0.0f && xs[i] <= 1.0f); - } - for (int i = 0; i < numYs; ++i) { - REPORTER_ASSERT(reporter, ys[i] >= 0.0f && ys[i] <= 1.0f); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fWidth), kXAns)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fHeight), kYAns)); + REPORTER_ASSERT(reporter, 4 == numX && 4 == numY); + REPORTER_ASSERT(reporter, !skipMask); + } + + // test-out occlusion + { + static const SkScalar kCornerRad = 10.0f; + SkRRect rr; + rr.setRectXY(r, kCornerRad, kCornerRad); + + // The rectXs & rectYs should be { 1, 29, 91, 119 }. Add two more points around each. + SkScalar testLocs[] = { + -18.0f, -9.0f, + 1.0f, + 9.0f, 18.0f, + 29.0f, + 39.0f, 49.0f, + 91.0f, + 109.0f, 118.0f, + 119.0f, + 139.0f, 149.0f + }; + + for (int minY = 0; minY < (int)SK_ARRAY_COUNT(testLocs); ++minY) { + for (int maxY = minY+1; maxY < (int)SK_ARRAY_COUNT(testLocs); ++maxY) { + for (int minX = 0; minX < (int)SK_ARRAY_COUNT(testLocs); ++minX) { + for (int maxX = minX+1; maxX < (int)SK_ARRAY_COUNT(testLocs); ++maxX) { + SkRect occluder = SkRect::MakeLTRB(testLocs[minX], testLocs[minY], + testLocs[maxX], testLocs[maxY]); + if (occluder.isEmpty()) { + continue; + } + + ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams( + rr, rr, occluder, + kBlurRad, kBlurRad, + &rrectToDraw, &size, + rectXs, rectYs, texXs, texYs, + &numX, &numY, &skipMask); + + static const SkScalar kAns = 12.0f * kBlurRad + 2.0f * kCornerRad + 1.0f; + REPORTER_ASSERT(reporter, ninePatchable); + REPORTER_ASSERT(reporter, + SkScalarNearlyEqual(SkIntToScalar(size.fWidth), kAns)); + REPORTER_ASSERT(reporter, + SkScalarNearlyEqual(SkIntToScalar(size.fHeight), kAns)); + + int checkBit = 0x1; + for (int y = 0; y < numY-1; ++y) { + for (int x = 0; x < numX-1; ++x) { + SkRect cell = SkRect::MakeLTRB(rectXs[x], rectYs[y], + rectXs[x+1], rectYs[y+1]); + REPORTER_ASSERT(reporter, + SkToBool(skipMask & checkBit) == + (cell.isEmpty() || occluder.contains(cell))); + + REPORTER_ASSERT(reporter, texXs[x] >= 0 && + texXs[x] <= size.fWidth); + REPORTER_ASSERT(reporter, texYs[y] >= 0 && + texXs[y] <= size.fHeight); + + checkBit <<= 1; + } + } + } + } + } } + + } } -- 2.7.4