Use interpolating blur for sigma < 2

Reimplemnet the original interpolating code. Moving from the
integer blur code to this code is controled by the flag:
SK_LEGACY_SUPPORT_INTEGER_SMALL_RADII

BUG=chromium:759273

Change-Id: I17205d704a0cae68a8a027c6bb235f81b5c62e24
Reviewed-on: https://skia-review.googlesource.com/41082
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Herb Derby <herb@google.com>
This commit is contained in:
Herb Derby 2017-08-31 10:10:01 -04:00 committed by Skia Commit-Bot
parent e3d1e5d768
commit 56ffafee44
2 changed files with 138 additions and 21 deletions

View File

@ -40,11 +40,28 @@ static uint32_t filter_window(double sigma) {
return std::max(1u, possibleWindow);
}
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;
// 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;
return std::make_tuple(static_cast<uint64_t>(round(outerFactor * (1ull << 32))),
static_cast<uint64_t>(round(innerFactor * (1ull << 32))));
}
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);
}
@ -88,6 +105,11 @@ bool SkMaskBlurFilter::FilterInfo::isSmall() const {
return fIsSmall;
}
std::tuple<uint64_t, uint64_t> SkMaskBlurFilter::FilterInfo::interpFactors() const {
return fInterpFactors;
};
SkMaskBlurFilter::SkMaskBlurFilter(double sigmaW, double sigmaH)
: fInfoW{sigmaW}, fInfoH{sigmaH}
, fBuffer0{skstd::make_unique_default<uint32_t[]>(bufferSize(0))}
@ -148,7 +170,7 @@ SkIPoint SkMaskBlurFilter::blur(const SkMask& src, SkMask* dst) const {
for (size_t y = 0; y < srcH; y++) {
auto srcStart = &src.fImage[y * src.fRowBytes];
auto tmpStart = &tmp[y];
this->blurOneScan(fInfoW,
this->blurOneScan(fInfoW, srcW,
srcStart, 1, srcStart + srcW,
tmpStart, tmpW, tmpStart + tmpW * tmpH);
}
@ -158,7 +180,7 @@ SkIPoint SkMaskBlurFilter::blur(const SkMask& src, SkMask* dst) const {
for (size_t y = 0; y < tmpH; y++) {
auto tmpStart = &tmp[y * tmpW];
auto dstStart = &dst->fImage[y];
this->blurOneScan(fInfoH,
this->blurOneScan(fInfoH, srcH,
tmpStart, 1, tmpStart + tmpW,
dstStart, dst->fRowBytes, dstStart + dst->fRowBytes * dstH);
}
@ -168,7 +190,7 @@ SkIPoint SkMaskBlurFilter::blur(const SkMask& src, SkMask* dst) const {
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,
this->blurOneScan(fInfoW, srcW,
srcStart, 1, srcStart + srcW,
dstStart, 1, dstStart + dstW);
}
@ -180,7 +202,7 @@ SkIPoint SkMaskBlurFilter::blur(const SkMask& src, SkMask* dst) const {
auto srcEnd = &src.fImage[src.fRowBytes * srcH];
auto dstStart = &dst->fImage[x];
auto dstEnd = &dst->fImage[dst->fRowBytes * dstH];
this->blurOneScan(fInfoH,
this->blurOneScan(fInfoH, srcH,
srcStart, src.fRowBytes, srcEnd,
dstStart, dst->fRowBytes, dstEnd);
}
@ -201,24 +223,41 @@ size_t SkMaskBlurFilter::bufferSize(uint8_t bufferPass) const {
// Blur one horizontal scan into the dst.
void SkMaskBlurFilter::blurOneScan(
FilterInfo info,
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()) {
this->blurOneScanBox(info, src, srcStride, srcEnd, dst, dstStride, dstEnd);
#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(
FilterInfo info,
const FilterInfo& info,
const uint8_t* src, size_t srcStride, const uint8_t* srcEnd,
uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const {
uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const {
auto buffer0Begin = &fBuffer0[0];
auto buffer0Cursor = buffer0Begin;
@ -276,9 +315,80 @@ void SkMaskBlurFilter::blurOneScanBox(
}
// 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(
FilterInfo info,
const FilterInfo& info,
const uint8_t* src, size_t srcStride, const uint8_t* srcEnd,
uint8_t* dst, size_t dstStride, uint8_t* dstEnd) const {
@ -302,8 +412,6 @@ void SkMaskBlurFilter::blurOneScanGauss(
uint32_t sum1 = 0;
uint32_t sum2 = 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;
@ -311,7 +419,7 @@ void SkMaskBlurFilter::blurOneScanGauss(
sum1 += sum0;
sum2 += sum1;
*dst = SkTo<uint8_t>((info.scaledWeight() * sum2 + half) >> 32);
*dst = final_scale(info.scaledWeight(), sum2);
sum2 -= *buffer2Cursor;
*buffer2Cursor = sum1;
@ -334,7 +442,7 @@ void SkMaskBlurFilter::blurOneScanGauss(
sum1 += sum0;
sum2 += sum1;
*dst = SkTo<uint8_t>((info.scaledWeight() * sum2 + half) >> 32);
*dst = final_scale(info.scaledWeight(), sum2);
sum2 -= *buffer2Cursor;
*buffer2Cursor = sum1;
@ -367,7 +475,7 @@ void SkMaskBlurFilter::blurOneScanGauss(
sum1 += sum0;
sum2 += sum1;
*dstCursor = SkTo<uint8_t>((info.scaledWeight() * sum2 + half) >> 32);
*dstCursor = final_scale(info.scaledWeight(), sum2);
sum2 -= *buffer2Cursor;
*buffer2Cursor = sum1;

View File

@ -10,6 +10,7 @@
#include <algorithm>
#include <memory>
#include <tuple>
#include "SkMask.h"
#include "SkTypes.h"
@ -40,11 +41,15 @@ public:
// 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 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
@ -60,15 +65,19 @@ public:
private:
size_t bufferSize(uint8_t bufferPass) const;
void blurOneScan(FilterInfo gen,
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(FilterInfo gen,
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 blurOneScanGauss(FilterInfo gen,
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;