diff options
author | Herb Derby <herb@google.com> | 2017-09-06 14:40:48 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-09-06 18:59:28 +0000 |
commit | 2c75957b0fc931a1376e613e29123bcd43d5f14f (patch) | |
tree | d1a3f63be43173962a66eb88a5ddb4e1904a53eb /src | |
parent | cc7f660a85204aadb6ba658bc905664bba4712ff (diff) |
Clean up mask blur code.
This code has interpolation for small radii like the old code.
This code is protected by the flag:
SK_LEGACY_SUPPORT_INTEGER_SMALL_RADII
In addition, I have improved the scale calculation for Gauss to be more
accurate, and it is also protected by the flag:
SK_LEGACY_SUPPORT_INTEGER_SMALL_RADII
When SK_LEGACY_SUPPORT_INTEGER_SMALL_RADII is defined, then there
is no difference.
Bug: skia:
Change-Id: I588299c3768dd009852ce41f33a6a8572d9f4ad3
Reviewed-on: https://skia-review.googlesource.com/41746
Commit-Queue: Herb Derby <herb@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
Diffstat (limited to 'src')
-rw-r--r-- | src/core/SkMaskBlurFilter.cpp | 906 | ||||
-rw-r--r-- | src/core/SkMaskBlurFilter.h | 59 |
2 files changed, 537 insertions, 428 deletions
diff --git a/src/core/SkMaskBlurFilter.cpp b/src/core/SkMaskBlurFilter.cpp index 840f108a3b..294b4a6322 100644 --- a/src/core/SkMaskBlurFilter.cpp +++ b/src/core/SkMaskBlurFilter.cpp @@ -8,129 +8,561 @@ #include "SkMaskBlurFilter.h" #include <cmath> +#include <climits> -#include "SkMakeUnique.h" +#include "SkArenaAlloc.h" #include "SkSafeMath.h" static const double kPi = 3.14159265358979323846264338327950288; -static uint64_t weight_from_diameter(uint32_t d) { - uint64_t d2 = d * d; - uint64_t d3 = d2 * d; - if ((d&1) == 0) { - // d * d * (d + 1); - return d3 + d2; - } - - return d3; -} - #if defined(SK_SUPPORT_LEGACY_USE_GAUSS_FOR_SMALL_RADII) static constexpr double kSmallSigma = 0.0; #else static constexpr double kSmallSigma = 2.0; #endif -static uint32_t filter_window(double sigma) { - if (sigma < kSmallSigma) { - auto radius = static_cast<uint32_t>(ceil(1.5 * sigma - 0.5)); - return 2 * radius + 1; +class BlurScanInterface { +public: + virtual ~BlurScanInterface() = default; + virtual void blur(const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, + uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const = 0; +}; + +class PlanningInterface { +public: + virtual ~PlanningInterface() = default; + virtual size_t bufferSize() const = 0; + virtual size_t border() const = 0; + virtual bool needsBlur() const = 0; + virtual BlurScanInterface* makeBlurScan( + SkArenaAlloc* alloc, size_t width, uint32_t* buffer) const = 0; +}; + +class None final : public PlanningInterface { +public: + None() = default; + size_t bufferSize() const override { return 0; } + size_t border() const override { return 0; } + bool needsBlur() const override { return false; } + BlurScanInterface* makeBlurScan( + SkArenaAlloc* alloc, size_t width, uint32_t* buffer) const override { + SK_ABORT("Should never be called."); + return nullptr; } - auto possibleWindow = static_cast<uint32_t>(floor(sigma * 3 * sqrt(2 * kPi) / 4 + 0.5)); - return std::max(1u, possibleWindow); -} +private: +}; -static std::tuple<uint64_t, uint64_t> interp_factors(double sigma) { - double radius = 1.5 * sigma - 0.5; - double outerRadius = ceil(radius); - double outerWindow = 2*outerRadius + 1; - double innerRadius = outerRadius - 1; - double innerWindow = 2*innerRadius + 1; +// This class is deprecated, and will be replaced by Box. +class PlanBoxInteger final : public PlanningInterface { +public: + explicit PlanBoxInteger(double sigma) { - // The inner and outer sums are divided by the window size to get the average over the window. - // Then the two averages are weighted by how close the radius is to the inner or outer radius. - double outerFactor = (radius - innerRadius) / outerWindow; - double innerFactor = (outerRadius - radius) / innerWindow; + // Calculate the radius from sigma. Taken from the old code until something better is + // figured out. + auto possibleRadius = 1.5 * sigma - 0.5; + auto radius = std::max(1.0, std::ceil(possibleRadius)); + auto window = 2 * radius + 1; - return std::make_tuple(static_cast<uint64_t>(round(outerFactor * (1ull << 32))), - static_cast<uint64_t>(round(innerFactor * (1ull << 32)))); -} + fWindow = static_cast<size_t>(window); + } -SkMaskBlurFilter::FilterInfo::FilterInfo(double sigma) - : fIsSmall{sigma < kSmallSigma} - , fFilterWindow{filter_window(sigma)} - , fWeight{fIsSmall ? fFilterWindow : weight_from_diameter(fFilterWindow)} - , fScaledWeight{(static_cast<uint64_t>(1) << 32) / fWeight} - , fInterpFactors{interp_factors(sigma)} -{ - SkASSERT(sigma >= 0); -} + size_t bufferSize() const override { return fWindow - 1; } -uint64_t SkMaskBlurFilter::FilterInfo::weight() const { - return fWeight; + size_t border() const override { return (fWindow - 1) / 2; } -} -uint32_t SkMaskBlurFilter::FilterInfo::borderSize() const { - if (this->isSmall()) { - return (fFilterWindow - 1) / 2; + bool needsBlur() const override { return true; } + + BlurScanInterface* makeBlurScan( + SkArenaAlloc* alloc, size_t width, uint32_t* buffer) const override + { + auto weight = static_cast<uint64_t>(round(1.0 / fWindow * (1ull << 32))); + auto bufferEnd = buffer + this->bufferSize(); + size_t noChangeCount = fWindow > width ? fWindow - width : 0; + + return alloc->make<BoxInteger>(weight, buffer, bufferEnd, noChangeCount); } - if ((fFilterWindow&1) == 0) { - return 3 * (fFilterWindow / 2) - 1; +private: + class BoxInteger final : public BlurScanInterface { + public: + BoxInteger(uint64_t weight, uint32_t* buffer, uint32_t* bufferEnd, size_t noChangeCount) + : fWeight{weight} + , fBuffer0{buffer} + , fBuffer0End{bufferEnd} + , fNoChangeCount{noChangeCount} { } + + void blur(const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, + uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const override { + auto buffer0Cursor = fBuffer0; + std::memset(&fBuffer0[0], 0, (fBuffer0End - fBuffer0) * sizeof(*fBuffer0)); + uint32_t sum0 = 0; + + // Consume the source generating pixels. + for (auto srcCursor = src; + srcCursor < srcEnd; dst += dstStride, srcCursor += srcStride) { + uint32_t s = *srcCursor; + sum0 += s; + + *dst = this->finalScale(sum0); + + sum0 -= *buffer0Cursor; + *buffer0Cursor = s; + buffer0Cursor = + (buffer0Cursor + 1) < fBuffer0End ? buffer0Cursor + 1 : &fBuffer0[0]; + } + + // This handles the case when both ends of the box are not between [src, srcEnd), and + // both are zero at that point. + for (size_t i = 0; i < fNoChangeCount; i++) { + uint32_t s = 0; + sum0 += s; + + *dst = this->finalScale(sum0); + + sum0 -= *buffer0Cursor; + *buffer0Cursor = s; + buffer0Cursor = + (buffer0Cursor + 1) < fBuffer0End ? buffer0Cursor + 1 : &fBuffer0[0]; + dst += dstStride; + } + + // Starting from the right, fill in the rest of the buffer. + std::memset(&fBuffer0[0], 0, (fBuffer0End - &fBuffer0[0]) * sizeof(fBuffer0[0])); + + sum0 = 0; + + uint8_t* dstCursor = dstEnd; + const uint8_t* srcCursor = srcEnd; + do { + dstCursor -= dstStride; + srcCursor -= srcStride; + uint32_t s = *srcCursor; + sum0 += s; + + *dstCursor = this->finalScale(sum0); + + sum0 -= *buffer0Cursor; + *buffer0Cursor = s; + buffer0Cursor = + (buffer0Cursor + 1) < fBuffer0End ? buffer0Cursor + 1 : &fBuffer0[0]; + } while (dstCursor > dst); + } + + static constexpr uint64_t kHalf = static_cast<uint64_t>(1) << 31; + + uint8_t finalScale(uint32_t sum) const { + return SkTo<uint8_t>((fWeight * sum + kHalf) >> 32); + } + + const uint64_t fWeight; + uint32_t* const fBuffer0; + uint32_t* const fBuffer0End; + const size_t fNoChangeCount; + }; + + size_t fWindow; +}; + +class PlanBox final : public PlanningInterface { +public: + explicit PlanBox(double sigma) { + // Calculate the radius from sigma. Taken from the old code until something better is + // figured out. + auto possibleRadius = 1.5 * sigma - 0.5; + auto radius = std::max(std::numeric_limits<double>::epsilon(), possibleRadius); + auto outerRadius = std::ceil(radius); + auto outerWindow = 2 * outerRadius + 1; + auto outerFactor = (1 - (outerRadius - radius)) / outerWindow; + fOuterWeight = static_cast<uint64_t>(round(outerFactor * (1ull << 32))); + + auto innerRadius = outerRadius - 1; + auto innerWindow = 2 * innerRadius + 1; + auto innerFactor = (1 - (radius - innerRadius)) / innerWindow; + fInnerWeight = static_cast<uint64_t>(round(innerFactor * (1ull << 32))); + + // Sliding window is defined by the relationship between the outer and inner widows. + // In the single window case, you add the element on the right, and subtract the element on + // the left. But, because two windows are used, this relationship is more complicated; an + // element is added from the right of the outer window, and subtracted from the left of the + // inner window. Because innerWindow = outerWindow - 2, the distance between + // the left and right in the two window case is outerWindow - 1. + fSlidingWindow = static_cast<size_t>(outerWindow - 1); } - return 3 * (fFilterWindow / 2); -} + size_t bufferSize() const override { return 0; } + + // Remember that sliding window = window - 1. Therefore, radius = sliding window / 2. + size_t border() const override { return fSlidingWindow / 2; } + + bool needsBlur() const override { return true; } + + BlurScanInterface* makeBlurScan( + SkArenaAlloc* alloc, size_t width, uint32_t* buffer) const override + { + size_t noChangeCount; + size_t trailingEdgeZeroCount; + + // The relation between the slidingWindow and the width dictates two operating modes. + // * width >= slidingWindow - both sides of the window are contained in the image while + // scanning. Therefore, we assume that slidingWindow zeros are consumed on the trailing + // edge of the window. After this count, then both edges are traversing the image. + // * slidingWindow > width - both sides of the window are off the image while scanning + // the middle. The front edge of the window can only travel width until it falls off the + // image. At this point, both edges of the window are off the image consuming zeros + // and therefore, the destination value does not change. The scan produces unchanged + // values until the trailing edge of the window enters the image. This count is + // slidingWindow - width. + if (width >= fSlidingWindow) { + noChangeCount = 0; + trailingEdgeZeroCount = fSlidingWindow; + } else { + noChangeCount = fSlidingWindow - width; + trailingEdgeZeroCount = width; + } + return alloc->make<Box>(fOuterWeight, fInnerWeight, noChangeCount, trailingEdgeZeroCount); + + } + +private: + class Box final : public BlurScanInterface { + public: + Box(uint64_t outerWeight, uint64_t innerWeight, + size_t noChangeCount, size_t trailingEdgeZeroCount) + : fOuterWeight{outerWeight} + , fInnerWeight{innerWeight} + , fNoChangeCount{noChangeCount} + , fTrailingEdgeZeroCount{trailingEdgeZeroCount} { } + + void blur(const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, + uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const override { + auto rightOuter = src; + auto dstCursor = dst; + + uint32_t outerSum = 0; + uint32_t innerSum = 0; + for (size_t i = 0; i < fTrailingEdgeZeroCount; i++) { + innerSum = outerSum; + outerSum += *rightOuter; + *dstCursor = this->interpolateSums(outerSum, innerSum); + + rightOuter += srcStride; + dstCursor += dstStride; + } + + // slidingWindow > width + for (size_t i = 0; i < fNoChangeCount; i++) { + *dstCursor = this->interpolateSums(outerSum, innerSum);; + dstCursor += dstStride; + } + + // width > slidingWindow + auto leftInner = src; + while (rightOuter < srcEnd) { + innerSum = outerSum - *leftInner; + outerSum += *rightOuter; + *dstCursor = this->interpolateSums(outerSum, innerSum); + outerSum -= *leftInner; + + rightOuter += srcStride; + leftInner += srcStride; + dstCursor += dstStride; + } + + auto leftOuter = srcEnd; + dstCursor = dstEnd; + outerSum = 0; + for (size_t i = 0; i < fTrailingEdgeZeroCount; i++) { + leftOuter -= srcStride; + dstCursor -= dstStride; + + innerSum = outerSum; + outerSum += *leftOuter; + *dstCursor = this->interpolateSums(outerSum, innerSum); + } + } -size_t SkMaskBlurFilter::FilterInfo::diameter(uint8_t pass) const { - SkASSERT(pass <= 2); + private: + static constexpr uint64_t kHalf = static_cast<uint64_t>(1) << 31; - if ((fFilterWindow&1) == 0) { - // Handle even case. - switch (pass) { - case 0: return fFilterWindow; - case 1: return fFilterWindow; - case 2: return fFilterWindow+1; + uint8_t interpolateSums(uint32_t outerSum, uint32_t innerSum) const { + return SkTo<uint8_t>((fOuterWeight * outerSum + fInnerWeight * innerSum + kHalf) >> 32); } + uint64_t fOuterWeight; + uint64_t fInnerWeight; + size_t fNoChangeCount; + size_t fTrailingEdgeZeroCount; + }; +private: + uint64_t fOuterWeight; + uint64_t fInnerWeight; + size_t fSlidingWindow; +}; + +class PlanGauss final : public PlanningInterface { +public: + explicit PlanGauss(double sigma) { + auto possibleWindow = static_cast<size_t>(floor(sigma * 3 * sqrt(2 * kPi) / 4 + 0.5)); + auto window = std::max(static_cast<size_t>(1), possibleWindow); + + fPass0Size = window - 1; + fPass1Size = window - 1; + fPass2Size = (window & 1) == 1 ? window - 1 : window; + + // Calculating the border is tricky. I will go through the odd case which is simpler, and + // then through the even case. Given a stack of filters seven wide for the odd case of + // three passes. + // + // S + // aaaAaaa + // bbbBbbb + // cccCccc + // D + // + // The furthest changed pixel is when the filters are in the following configuration. + // + // S + // aaaAaaa + // bbbBbbb + // cccCccc + // D + // + // The A pixel is calculated using the value S, the B uses A, and the C uses B, and + // finally D is C. So, with a window size of seven the border is nine. In general, the + // border is 3*((window - 1)/2). + // + // For even cases the filter stack is more complicated. The spec specifies two passes + // of even filters and a final pass of odd filters. A stack for a width of six looks like + // this. + // + // S + // aaaAaa + // bbBbbb + // cccCccc + // D + // + // The furthest pixel looks like this. + // + // S + // aaaAaa + // bbBbbb + // cccCccc + // D + // + // For a window of size, the border value is seven. In general the border is 3 * + // (window/2) -1. + fBorder = (window & 1) == 1 ? 3 * ((window - 1) / 2) : 3 * (window / 2) - 1; + fSlidingWindow = 2 * fBorder + 1; + + // If the window is odd then the divisor is just window ^ 3 otherwise, + // it is window * window * (window + 1) = window ^ 2 + window ^ 3; + auto window2 = window * window; + auto window3 = window2 * window; + auto divisor = (window & 1) == 1 ? window3 : window3 + window2; + + #if defined(SK_LEGACY_SUPPORT_INTEGER_SMALL_RADII) + fWeight = (static_cast<uint64_t>(1) << 32) / divisor; + #else + fWeight = static_cast<uint64_t>(round(1.0 / divisor * (1ull << 32))); + #endif } - return fFilterWindow; -} + size_t bufferSize() const override { return fPass0Size + fPass1Size + fPass2Size; } -uint64_t SkMaskBlurFilter::FilterInfo::scaledWeight() const { - return fScaledWeight; -} + size_t border() const override { return fBorder; } -bool SkMaskBlurFilter::FilterInfo::isSmall() const { - return fIsSmall; -} + bool needsBlur() const override { return true; } + + BlurScanInterface* makeBlurScan( + SkArenaAlloc* alloc, size_t width, uint32_t* buffer) const override + { + uint32_t* buffer0, *buffer0End, *buffer1, *buffer1End, *buffer2, *buffer2End; + buffer0 = buffer; + buffer0End = buffer1 = buffer0 + fPass0Size; + buffer1End = buffer2 = buffer1 + fPass1Size; + buffer2End = buffer2 + fPass2Size; + size_t noChangeCount = fSlidingWindow > width ? fSlidingWindow - width : 0; + + return alloc->make<Gauss>( + fWeight, noChangeCount, + buffer0, buffer0End, + buffer1, buffer1End, + buffer2, buffer2End); + } -std::tuple<uint64_t, uint64_t> SkMaskBlurFilter::FilterInfo::interpFactors() const { - return fInterpFactors; +public: + class Gauss final : public BlurScanInterface { + public: + Gauss(uint64_t weight, size_t noChangeCount, + uint32_t* buffer0, uint32_t* buffer0End, + uint32_t* buffer1, uint32_t* buffer1End, + uint32_t* buffer2, uint32_t* buffer2End) + : fWeight{weight} + , fNoChangeCount{noChangeCount} + , fBuffer0{buffer0} + , fBuffer0End{buffer0End} + , fBuffer1{buffer1} + , fBuffer1End{buffer1End} + , fBuffer2{buffer2} + , fBuffer2End{buffer2End} + { } + + void blur(const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, + uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const override { + auto buffer0Cursor = fBuffer0; + auto buffer1Cursor = fBuffer1; + auto buffer2Cursor = fBuffer2; + + std::memset(fBuffer0, 0x00, (fBuffer2End - fBuffer0) * sizeof(*fBuffer0)); + + uint32_t sum0 = 0; + uint32_t sum1 = 0; + uint32_t sum2 = 0; + + // Consume the source generating pixels. + for (auto srcCursor = src; + srcCursor < srcEnd; dst += dstStride, srcCursor += srcStride) { + uint32_t s = *srcCursor; + sum0 += s; + sum1 += sum0; + sum2 += sum1; + + *dst = this->finalScale(sum2); + + sum2 -= *buffer2Cursor; + *buffer2Cursor = sum1; + buffer2Cursor = (buffer2Cursor + 1) < fBuffer2End ? buffer2Cursor + 1 : fBuffer2; + + sum1 -= *buffer1Cursor; + *buffer1Cursor = sum0; + buffer1Cursor = (buffer1Cursor + 1) < fBuffer1End ? buffer1Cursor + 1 : fBuffer1; + + sum0 -= *buffer0Cursor; + *buffer0Cursor = s; + buffer0Cursor = (buffer0Cursor + 1) < fBuffer0End ? buffer0Cursor + 1 : fBuffer0; + } + + for (size_t i = 0; i < fNoChangeCount; i++) { + uint32_t s = 0; + sum0 += s; + sum1 += sum0; + sum2 += sum1; + + *dst = this->finalScale(sum2); + + sum2 -= *buffer2Cursor; + *buffer2Cursor = sum1; + buffer2Cursor = (buffer2Cursor + 1) < fBuffer2End ? buffer2Cursor + 1 : fBuffer2; + + sum1 -= *buffer1Cursor; + *buffer1Cursor = sum0; + buffer1Cursor = (buffer1Cursor + 1) < fBuffer1End ? buffer1Cursor + 1 : fBuffer1; + + sum0 -= *buffer0Cursor; + *buffer0Cursor = s; + buffer0Cursor = (buffer0Cursor + 1) < fBuffer0End ? buffer0Cursor + 1 : fBuffer0; + + dst += dstStride; + } + + // Starting from the right, fill in the rest of the buffer. + std::memset(fBuffer0, 0, (fBuffer2End - fBuffer0) * sizeof(*fBuffer0)); + + sum0 = sum1 = sum2 = 0; + + uint8_t* dstCursor = dstEnd; + const uint8_t* srcCursor = srcEnd; + while (dstCursor > dst) { + dstCursor -= dstStride; + srcCursor -= srcStride; + uint32_t s = *srcCursor; + sum0 += s; + sum1 += sum0; + sum2 += sum1; + + *dstCursor = this->finalScale(sum2); + + sum2 -= *buffer2Cursor; + *buffer2Cursor = sum1; + buffer2Cursor = (buffer2Cursor + 1) < fBuffer2End ? buffer2Cursor + 1 : fBuffer2; + + sum1 -= *buffer1Cursor; + *buffer1Cursor = sum0; + buffer1Cursor = (buffer1Cursor + 1) < fBuffer1End ? buffer1Cursor + 1 : fBuffer1; + + sum0 -= *buffer0Cursor; + *buffer0Cursor = s; + buffer0Cursor = (buffer0Cursor + 1) < fBuffer0End ? buffer0Cursor + 1 : fBuffer0; + } + } + + private: + static constexpr uint64_t kHalf = static_cast<uint64_t>(1) << 31; + + uint8_t finalScale(uint32_t sum) const { + return SkTo<uint8_t>((fWeight * sum + kHalf) >> 32); + } + + uint64_t fWeight; + size_t fNoChangeCount; + uint32_t* fBuffer0; + uint32_t* fBuffer0End; + uint32_t* fBuffer1; + uint32_t* fBuffer1End; + uint32_t* fBuffer2; + uint32_t* fBuffer2End; + }; + + uint64_t fWeight; + size_t fBorder; + size_t fSlidingWindow; + size_t fPass0Size; + size_t fPass1Size; + size_t fPass2Size; }; +static PlanningInterface* make_plan(SkArenaAlloc* alloc, double sigma) { + PlanningInterface* plan = nullptr; + + if (3 * sigma <= 1) { + plan = alloc->make<None>(); + } else if (sigma < kSmallSigma) { + #if defined(SK_LEGACY_SUPPORT_INTEGER_SMALL_RADII) + plan = alloc->make<PlanBoxInteger>(sigma); + #else + plan = alloc->make<PlanBox>(sigma); + #endif + } else { + plan = alloc->make<PlanGauss>(sigma); + } + + return plan; +}; SkMaskBlurFilter::SkMaskBlurFilter(double sigmaW, double sigmaH) - : fInfoW{sigmaW}, fInfoH{sigmaH} - , fBuffer0{skstd::make_unique_default<uint32_t[]>(bufferSize(0))} - , fBuffer1{skstd::make_unique_default<uint32_t[]>(bufferSize(1))} - , fBuffer2{skstd::make_unique_default<uint32_t[]>(bufferSize(2))} { + : fSigmaW{std::max(sigmaW, 0.0)} + , fSigmaH{std::max(sigmaH, 0.0)} +{ + SkASSERT(sigmaW >= 0); + SkASSERT(sigmaH >= 0); } bool SkMaskBlurFilter::hasNoBlur() const { - return fInfoW.weight() <= 1 && fInfoH.weight() <= 1; + return (3 * fSigmaW <= 1) && (3 * fSigmaH <= 1); } SkIPoint SkMaskBlurFilter::blur(const SkMask& src, SkMask* dst) const { - uint64_t weightW = fInfoW.weight(); - uint64_t weightH = fInfoH.weight(); + // 1024 is a place holder guess until more analysis can be done. + SkSTArenaAlloc<1024> alloc; - size_t borderW = fInfoW.borderSize(); - size_t borderH = fInfoH.borderSize(); + PlanningInterface* planW = make_plan(&alloc, fSigmaW); + PlanningInterface* planH = make_plan(&alloc, fSigmaH); - size_t srcW = src.fBounds.width(); - size_t srcH = src.fBounds.height(); + size_t borderW = planW->border(); + size_t borderH = planH->border(); + + auto srcW = SkTo<size_t>(src.fBounds.width()); + auto srcH = SkTo<size_t>(src.fBounds.height()); SkSafeMath safe; @@ -144,7 +576,7 @@ SkIPoint SkMaskBlurFilter::blur(const SkMask& src, SkMask* dst) const { dst->fBounds.offset(-SkTo<int32_t>(borderW), -SkTo<int32_t>(borderH)); dst->fImage = nullptr; - dst->fRowBytes = dstW; + dst->fRowBytes = SkTo<uint32_t>(dstW); dst->fFormat = SkMask::kA8_Format; if (src.fImage == nullptr) { @@ -159,52 +591,56 @@ SkIPoint SkMaskBlurFilter::blur(const SkMask& src, SkMask* dst) const { } dst->fImage = SkMask::AllocImage(toAlloc); + auto bufferSize = std::max(planW->bufferSize(), planH->bufferSize()); + auto buffer = alloc.makeArrayDefault<uint32_t>(bufferSize); - if (weightW > 1 && weightH > 1) { + if (planW->needsBlur() && planH->needsBlur()) { // Blur both directions. size_t tmpW = srcH; size_t tmpH = dstW; - auto tmp = skstd::make_unique_default<uint8_t[]>(tmpW * tmpH); + auto tmp = alloc.makeArrayDefault<uint8_t>(tmpW * tmpH); // Blur horizontally, and transpose. + auto scanW = planW->makeBlurScan(&alloc, srcW, buffer); for (size_t y = 0; y < srcH; y++) { auto srcStart = &src.fImage[y * src.fRowBytes]; auto tmpStart = &tmp[y]; - this->blurOneScan(fInfoW, srcW, - srcStart, 1, srcStart + srcW, - tmpStart, tmpW, tmpStart + tmpW * tmpH); + scanW->blur(srcStart, 1, srcStart + srcW, + tmpStart, tmpW, tmpStart + tmpW * tmpH); } // Blur vertically (scan in memory order because of the transposition), // and transpose back to the original orientation. + auto scanH = planH->makeBlurScan(&alloc, tmpW, buffer); for (size_t y = 0; y < tmpH; y++) { auto tmpStart = &tmp[y * tmpW]; auto dstStart = &dst->fImage[y]; - this->blurOneScan(fInfoH, srcH, - tmpStart, 1, tmpStart + tmpW, - dstStart, dst->fRowBytes, dstStart + dst->fRowBytes * dstH); + + scanH->blur(tmpStart, 1, tmpStart + tmpW, + dstStart, dst->fRowBytes, dstStart + dst->fRowBytes * dstH); } - } else if (weightW > 1) { + } else if (planW->needsBlur()) { // Blur only horizontally. + auto scanW = planW->makeBlurScan(&alloc, srcW, buffer); for (size_t y = 0; y < srcH; y++) { auto srcStart = &src.fImage[y * src.fRowBytes]; auto dstStart = &dst->fImage[y * dst->fRowBytes]; - this->blurOneScan(fInfoW, srcW, - srcStart, 1, srcStart + srcW, - dstStart, 1, dstStart + dstW); + scanW->blur(srcStart, 1, srcStart + srcW, + dstStart, 1, dstStart + dstW); + } - } else if (weightH > 1) { + } else if (planH->needsBlur()) { // Blur only vertically. + auto srcEnd = &src.fImage[src.fRowBytes * srcH]; + auto dstEnd = &dst->fImage[dst->fRowBytes * dstH]; + auto scanH = planH->makeBlurScan(&alloc, srcH, buffer); for (size_t x = 0; x < srcW; x++) { auto srcStart = &src.fImage[x]; - auto srcEnd = &src.fImage[src.fRowBytes * srcH]; auto dstStart = &dst->fImage[x]; - auto dstEnd = &dst->fImage[dst->fRowBytes * dstH]; - this->blurOneScan(fInfoH, srcH, - srcStart, src.fRowBytes, srcEnd, - dstStart, dst->fRowBytes, dstEnd); + scanH->blur(srcStart, src.fRowBytes, srcEnd, + dstStart, dst->fRowBytes, dstEnd); } } else { // Copy to dst. No Blur. @@ -217,276 +653,4 @@ SkIPoint SkMaskBlurFilter::blur(const SkMask& src, SkMask* dst) const { return {SkTo<int32_t>(borderW), SkTo<int32_t>(borderH)}; } -size_t SkMaskBlurFilter::bufferSize(uint8_t bufferPass) const { - return std::max(fInfoW.diameter(bufferPass), fInfoH.diameter(bufferPass)) - 1; -} -// Blur one horizontal scan into the dst. -void SkMaskBlurFilter::blurOneScan( - const FilterInfo& info, size_t width, - const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, - uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const { - // We don't think this is good for quality. It is good for compatibility - // with previous expectations... - if (info.isSmall()) { - #if defined(SK_LEGACY_SUPPORT_INTEGER_SMALL_RADII) - this->blurOneScanBox(info, src, srcStride, srcEnd, dst, dstStride, dstEnd); - #else - this->blurOneScanBoxInterp(info, width, src, srcStride, srcEnd, dst, dstStride, dstEnd); - #endif - - } else { - this->blurOneScanGauss(info, src, srcStride, srcEnd, dst, dstStride, dstEnd); - } - -} - -static constexpr uint64_t half = static_cast<uint64_t>(1) << 31; - -static uint8_t final_scale(uint64_t weight, uint32_t sum) { - return SkTo<uint8_t>((weight * sum + half) >> 32); -} - -static uint8_t interp_final_scale(uint64_t outerWeight, uint32_t outerSum, - uint64_t innerWeight, uint32_t innerSum) { - return SkTo<uint8_t>((outerWeight * outerSum + innerWeight * innerSum + half) >> 32); -} - - -// Blur one horizontal scan into the dst. -void SkMaskBlurFilter::blurOneScanBox( - const FilterInfo& info, - const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, - uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const { - - auto buffer0Begin = &fBuffer0[0]; - auto buffer0Cursor = buffer0Begin; - auto buffer0End = &fBuffer0[0] + info.diameter(0) - 1; - std::memset(&fBuffer0[0], 0, (buffer0End - buffer0Begin) * sizeof(fBuffer0[0])); - uint32_t sum0 = 0; - const uint64_t half = static_cast<uint64_t>(1) << 31; - - // Consume the source generating pixels. - for (auto srcCursor = src; srcCursor < srcEnd; dst += dstStride, srcCursor += srcStride) { - uint32_t s = *srcCursor; - sum0 += s; - - *dst = SkTo<uint8_t>((info.scaledWeight() * sum0 + half) >> 32); - - - sum0 -= *buffer0Cursor; - *buffer0Cursor = s; - buffer0Cursor = (buffer0Cursor + 1) < buffer0End ? buffer0Cursor + 1 : &fBuffer0[0]; - } - - // This handles the case when both ends of the box are not between [src, srcEnd), and both - // are zero at that point. - for (auto i = 0; i < static_cast<ptrdiff_t>(2 * info.borderSize()) - (srcEnd - src); i++) { - uint32_t s = 0; - sum0 += s; - - *dst = SkTo<uint8_t>((info.scaledWeight() * sum0 + half) >> 32); - - sum0 -= *buffer0Cursor; - *buffer0Cursor = s; - buffer0Cursor = (buffer0Cursor + 1) < buffer0End ? buffer0Cursor + 1 : &fBuffer0[0]; - dst += dstStride; - } - - // Starting from the right, fill in the rest of the buffer. - std::memset(&fBuffer0[0], 0, (buffer0End - &fBuffer0[0]) * sizeof(fBuffer0[0])); - - sum0 = 0; - - uint8_t* dstCursor = dstEnd; - const uint8_t* srcCursor = srcEnd; - do { - dstCursor -= dstStride; - srcCursor -= srcStride; - uint32_t s = *srcCursor; - sum0 += s; - - *dstCursor = SkTo<uint8_t>((info.scaledWeight() * sum0 + half) >> 32); - - sum0 -= *buffer0Cursor; - *buffer0Cursor = s; - buffer0Cursor = (buffer0Cursor + 1) < buffer0End ? buffer0Cursor + 1 : &fBuffer0[0]; - } while (dstCursor > dst); - -} - -// Blur one horizontal scan into the dst. -void SkMaskBlurFilter::blurOneScanBoxInterp( - const FilterInfo& info, size_t width, - const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, - uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const -{ - uint64_t outerFactor, innerFactor; - std::tie(outerFactor, innerFactor) = info.interpFactors(); - - // There are sever windows used in thinking about this algorithm. There is the outer window - // that is generated by the ceiling of the radius. There is the inner window that is two - // smaller than the outer window, and they are centered on each other. Then there is the - // scanning window which is from the right edge of the outer window to the left edge of the - // inner window. - auto outerWindow = info.diameter(2); - auto slidingWindow = outerWindow - 1; - auto border = std::min(width, slidingWindow); - - auto rightOuter = src; - auto dstCursor = dst; - - uint32_t outerSum = 0; - uint32_t innerSum = 0; - for (size_t i = 0; i < border; i++) { - innerSum = outerSum; - outerSum += *rightOuter; - *dstCursor = interp_final_scale(outerFactor, outerSum, innerFactor, innerSum); - rightOuter += srcStride; - dstCursor += dstStride; - } - - // Consider the two filter cases: - // * slidingWindow > width - in this case the right edge of the window reaches the end of the - // source data before left edge starts consuming source data. This - // means that the will be adding and subtracting zero as it advances, - // the sum never changing. - // * width < slidingWindow - in this case the right edge of the window will continue consuming - // source data after the left edge of the window starts consuming - // source data. - - if (slidingWindow > width) { - auto v = interp_final_scale(outerFactor, outerSum, innerFactor, innerSum); - for (auto i = width; i < slidingWindow; i++) { - *dstCursor = v; - dstCursor += dstStride; - } - } else if (slidingWindow < width) { - auto leftInner = src; - while (rightOuter < srcEnd) { - innerSum = outerSum - *leftInner; - outerSum += *rightOuter; - *dstCursor = interp_final_scale(outerFactor, outerSum, innerFactor, innerSum); - outerSum -= *leftInner; - rightOuter += srcStride; - leftInner += srcStride; - dstCursor += dstStride; - } - } - - auto leftOuter = srcEnd; - dstCursor = dstEnd; - outerSum = 0; - for (size_t i = 0; i < border; i++) { - leftOuter -= srcStride; - dstCursor -= dstStride; - innerSum = outerSum; - outerSum += *leftOuter; - *dstCursor = interp_final_scale(outerFactor, outerSum, innerFactor, innerSum); - } -} - -// Blur one horizontal scan into the dst. -void SkMaskBlurFilter::blurOneScanGauss( - const FilterInfo& info, - const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, - uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const { - - auto buffer0Begin = &fBuffer0[0]; - auto buffer1Begin = &fBuffer1[0]; - auto buffer2Begin = &fBuffer2[0]; - - auto buffer0Cursor = buffer0Begin; - auto buffer1Cursor = buffer1Begin; - auto buffer2Cursor = buffer2Begin; - - auto buffer0End = &fBuffer0[0] + info.diameter(0) - 1; - auto buffer1End = &fBuffer1[0] + info.diameter(1) - 1; - auto buffer2End = &fBuffer2[0] + info.diameter(2) - 1; - - std::memset(&fBuffer0[0], 0, (buffer0End - buffer0Begin) * sizeof(fBuffer0[0])); - std::memset(&fBuffer1[0], 0, (buffer1End - buffer1Begin) * sizeof(fBuffer1[0])); - std::memset(&fBuffer2[0], 0, (buffer2End - buffer2Begin) * sizeof(fBuffer2[0])); - - uint32_t sum0 = 0; - uint32_t sum1 = 0; - uint32_t sum2 = 0; - - // Consume the source generating pixels. - for (auto srcCursor = src; srcCursor < srcEnd; dst += dstStride, srcCursor += srcStride) { - uint32_t s = *srcCursor; - sum0 += s; - sum1 += sum0; - sum2 += sum1; - - *dst = final_scale(info.scaledWeight(), sum2); - - sum2 -= *buffer2Cursor; - *buffer2Cursor = sum1; - buffer2Cursor = (buffer2Cursor + 1) < buffer2End ? buffer2Cursor + 1 : &fBuffer2[0]; - - sum1 -= *buffer1Cursor; - *buffer1Cursor = sum0; - buffer1Cursor = (buffer1Cursor + 1) < buffer1End ? buffer1Cursor + 1 : &fBuffer1[0]; - - sum0 -= *buffer0Cursor; - *buffer0Cursor = s; - buffer0Cursor = (buffer0Cursor + 1) < buffer0End ? buffer0Cursor + 1 : &fBuffer0[0]; - } - - // This handles the case when both ends of the box are not between [src, srcEnd), and both - // are zero at that point. - for (auto i = 0; i < static_cast<ptrdiff_t>(2 * info.borderSize()) - (srcEnd - src); i++) { - uint32_t s = 0; - sum0 += s; - sum1 += sum0; - sum2 += sum1; - - *dst = final_scale(info.scaledWeight(), sum2); - - sum2 -= *buffer2Cursor; - *buffer2Cursor = sum1; - buffer2Cursor = (buffer2Cursor + 1) < buffer2End ? buffer2Cursor + 1 : &fBuffer2[0]; - - sum1 -= *buffer1Cursor; - *buffer1Cursor = sum0; - buffer1Cursor = (buffer1Cursor + 1) < buffer1End ? buffer1Cursor + 1 : &fBuffer1[0]; - - sum0 -= *buffer0Cursor; - *buffer0Cursor = s; - buffer0Cursor = (buffer0Cursor + 1) < buffer0End ? buffer0Cursor + 1 : &fBuffer0[0]; - dst += dstStride; - } - - // Starting from the right, fill in the rest of the buffer. - std::memset(&fBuffer0[0], 0, (buffer0End - &fBuffer0[0]) * sizeof(fBuffer0[0])); - std::memset(&fBuffer1[0], 0, (buffer1End - &fBuffer1[0]) * sizeof(fBuffer1[0])); - std::memset(&fBuffer2[0], 0, (buffer2End - &fBuffer2[0]) * sizeof(fBuffer2[0])); - - sum0 = sum1 = sum2 = 0; - - uint8_t* dstCursor = dstEnd; - const uint8_t* srcCursor = srcEnd; - do { - dstCursor -= dstStride; - srcCursor -= srcStride; - uint32_t s = *srcCursor; - sum0 += s; - sum1 += sum0; - sum2 += sum1; - - *dstCursor = final_scale(info.scaledWeight(), sum2); - - sum2 -= *buffer2Cursor; - *buffer2Cursor = sum1; - buffer2Cursor = (buffer2Cursor + 1) < buffer2End ? buffer2Cursor + 1 : &fBuffer2[0]; - - sum1 -= *buffer1Cursor; - *buffer1Cursor = sum0; - buffer1Cursor = (buffer1Cursor + 1) < buffer1End ? buffer1Cursor + 1 : &fBuffer1[0]; - - sum0 -= *buffer0Cursor; - *buffer0Cursor = s; - buffer0Cursor = (buffer0Cursor + 1) < buffer0End ? buffer0Cursor + 1 : &fBuffer0[0]; - } while (dstCursor > dst); -} diff --git a/src/core/SkMaskBlurFilter.h b/src/core/SkMaskBlurFilter.h index 26efe95de5..0a7895d95c 100644 --- a/src/core/SkMaskBlurFilter.h +++ b/src/core/SkMaskBlurFilter.h @@ -19,39 +19,6 @@ // https://drafts.fxtf.org/filters/#feGaussianBlurElement class SkMaskBlurFilter { public: - // Given a filter specified by sigma, generate various quantities. - class FilterInfo { - public: - explicit FilterInfo(double sigma); - - // The final weight to divide by given a box size calculated from sigma accumulated for - // all three passes. For example, if the box size is 5, then the final weight for all - // three passes is 5^3 or 125. - uint64_t weight() const; - - // The distance between the first value of the dst and the first value of the src. - uint32_t borderSize() const; - - // The size of the box filter. - size_t diameter(uint8_t) const; - - // A factor used to simulate division using multiplication and shift. - uint64_t scaledWeight() const; - - // Returned when sigma < 2. - bool isSmall() const; - - // Factors for interpolating box blur. - std::tuple<uint64_t, uint64_t> interpFactors() const; - - private: - const bool fIsSmall; - const uint32_t fFilterWindow; - const uint64_t fWeight; - const uint64_t fScaledWeight; - const std::tuple<uint64_t, uint64_t> fInterpFactors; - }; - // Create an object suitable for filtering an SkMask using a filter with width sigmaW and // height sigmaH. SkMaskBlurFilter(double sigmaW, double sigmaH); @@ -63,30 +30,8 @@ public: SkIPoint blur(const SkMask& src, SkMask* dst) const; private: - size_t bufferSize(uint8_t bufferPass) const; - - void blurOneScan(const FilterInfo& gen, size_t width, - const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, - uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const; - - void blurOneScanBox(const FilterInfo& gen, - const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, - uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const; - - void blurOneScanBoxInterp(const FilterInfo& gen, size_t width, - const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, - uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const; - - void blurOneScanGauss(const FilterInfo& gen, - const uint8_t* src, size_t srcStride, const uint8_t* srcEnd, - uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const; - - - const FilterInfo fInfoW, - fInfoH; - std::unique_ptr<uint32_t[]> fBuffer0, - fBuffer1, - fBuffer2; + const double fSigmaW; + const double fSigmaH; }; #endif // SkBlurMaskFilter_DEFINED |