return sk_sp<SkMaskFilter>(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<SkScalar>(radiiUL.fX, radiiLL.fX));
- const int top = SkScalarCeilToInt(SkTMax<SkScalar>(radiiUL.fY, radiiUR.fY));
- const int right = SkScalarCeilToInt(SkTMax<SkScalar>(radiiUR.fX, radiiLR.fX));
- const int bot = SkScalarCeilToInt(SkTMax<SkScalar>(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<SkScalar>(devRadiiUL.fX, devRadiiLL.fX));
+ const int devTop = SkScalarCeilToInt(SkTMax<SkScalar>(devRadiiUL.fY, devRadiiUR.fY));
+ const int devRight = SkScalarCeilToInt(SkTMax<SkScalar>(devRadiiUR.fX, devRadiiLR.fX));
+ const int devBot = SkScalarCeilToInt(SkTMax<SkScalar>(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<SkScalar>(srcRadiiUL.fX, srcRadiiLL.fX);
+ const SkScalar srcTop = SkTMax<SkScalar>(srcRadiiUL.fY, srcRadiiUR.fY);
+ const SkScalar srcRight = SkTMax<SkScalar>(srcRadiiUR.fX, srcRadiiLR.fX);
+ const SkScalar srcBot = SkTMax<SkScalar>(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;
class GrRRectBlurEffect : public GrFragmentProcessor {
public:
- static sk_sp<GrFragmentProcessor> Make(GrContext*, float sigma, const SkRRect&);
+ static sk_sp<GrFragmentProcessor> Make(GrContext*,
+ float sigma, float xformedSigma,
+ const SkRRect& srcRRect, const SkRRect& devRRect);
virtual ~GrRRectBlurEffect() {};
const char* name() const override { return "GrRRectBlur"; }
}
sk_sp<GrFragmentProcessor> 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
// 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;
}
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);
}
//////////////////////////////////////////////////////////////////////////////
return true;
}
- sk_sp<GrFragmentProcessor> fp(GrRRectBlurEffect::Make(context, xformedSigma, devRRect));
+ sk_sp<GrFragmentProcessor> fp(GrRRectBlurEffect::Make(context, fSigma, xformedSigma,
+ srcRRect, devRRect));
if (!fp) {
return false;
}
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
{
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;
+ }
+ }
+ }
+ }
+ }
}
+
+
}
}