Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Blur.h"
#include <math.h>
#include <string.h>
#include <algorithm>
#include "2D.h"
#include "DataSurfaceHelpers.h"
#include "HelpersSkia.h"
#include "NumericTools.h"
#include "Tools.h"
#include "skia/include/core/SkBlurTypes.h"
#include "skia/include/core/SkCanvas.h"
#include "skia/include/core/SkSurface.h"
#include "skia/include/effects/SkImageFilters.h"
#include "skia/src/core/SkBlurMask.h"
#include "skia/src/core/SkMaskBlurFilter.h"
namespace mozilla {
namespace gfx {
template <typename T>
struct PixelValue {
T value;
explicit PixelValue(T aValue) : value(aValue) {}
void Spread(const PixelValue& aOther) {
value = std::max(value, aOther.value);
}
};
template <>
struct PixelValue<uint32_t> {
union {
struct {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
uint32_t value;
};
explicit PixelValue(uint32_t aValue) { value = aValue; }
void Spread(const PixelValue& aOther) {
r = std::max(r, aOther.r);
g = std::max(g, aOther.g);
b = std::max(b, aOther.b);
a = std::max(a, aOther.a);
}
};
template <typename T>
static void SpreadHorizontal(const T* aInput, T* aOutput, int32_t aRadius,
int32_t aWidth, int32_t aRows, int32_t aStride,
const IntRect& aSkipRect) {
if (aRadius == 0) {
memcpy(aOutput, aInput, aStride * aRows * sizeof(T));
return;
}
bool skipRectCoversWholeRow =
0 >= aSkipRect.X() && aWidth <= aSkipRect.XMost();
for (int32_t y = 0; y < aRows; y++) {
// Check whether the skip rect intersects this row. If the skip
// rect covers the whole surface in this row, we can avoid
// this row entirely (and any others along the skip rect).
bool inSkipRectY = aSkipRect.ContainsY(y);
if (inSkipRectY && skipRectCoversWholeRow) {
y = aSkipRect.YMost() - 1;
continue;
}
T* dst = &aOutput[aStride * y];
for (int32_t x = 0; x < aWidth; x++) {
// Check whether we are within the skip rect. If so, go
// to the next point outside the skip rect.
if (inSkipRectY && aSkipRect.ContainsX(x)) {
x = aSkipRect.XMost();
if (x >= aWidth) break;
}
int32_t sMin = std::max(x - aRadius, 0);
int32_t sMax = std::min(x + aRadius, aWidth - 1);
const auto* src =
reinterpret_cast<const PixelValue<T>*>(&aInput[aStride * y + sMin]);
PixelValue<T> v(0);
for (int32_t s = sMin; s <= sMax; ++s) {
v.Spread(*src);
++src;
}
*dst = v.value;
++dst;
}
}
}
template <typename T>
static void SpreadVertical(const T* aInput, T* aOutput, int32_t aRadius,
int32_t aWidth, int32_t aRows, int32_t aStride,
const IntRect& aSkipRect) {
if (aRadius == 0) {
memcpy(aOutput, aInput, aStride * aRows * sizeof(T));
return;
}
bool skipRectCoversWholeColumn =
0 >= aSkipRect.Y() && aRows <= aSkipRect.YMost();
for (int32_t x = 0; x < aWidth; x++) {
bool inSkipRectX = aSkipRect.ContainsX(x);
if (inSkipRectX && skipRectCoversWholeColumn) {
x = aSkipRect.XMost() - 1;
continue;
}
T* dst = &aOutput[x];
for (int32_t y = 0; y < aRows; y++) {
// Check whether we are within the skip rect. If so, go
// to the next point outside the skip rect.
if (inSkipRectX && aSkipRect.ContainsY(y)) {
y = aSkipRect.YMost();
if (y >= aRows) break;
}
int32_t sMin = std::max(y - aRadius, 0);
int32_t sMax = std::min(y + aRadius, aRows - 1);
const auto* src =
reinterpret_cast<const PixelValue<T>*>(&aInput[aStride * sMin + x]);
PixelValue<T> v(0);
for (int32_t s = sMin; s <= sMax; ++s) {
v.Spread(*src);
src += aStride;
}
*dst = v.value;
dst += aStride;
}
}
}
GaussianBlur::GaussianBlur(const Rect& aRect, const IntSize& aSpreadRadius,
const Point& aSigma, const Rect* aDirtyRect,
const Rect* aSkipRect, SurfaceFormat aFormat,
bool aClamp) {
Init(aRect, aSpreadRadius, aSigma, aDirtyRect, aSkipRect, aFormat, aClamp);
}
void GaussianBlur::Init(const Rect& aRect, const IntSize& aSpreadRadius,
const Point& aBlurSigma, const Rect* aDirtyRect,
const Rect* aSkipRect, SurfaceFormat aFormat,
bool aClamp) {
switch (aFormat) {
case SurfaceFormat::A8:
case SurfaceFormat::B8G8R8A8:
case SurfaceFormat::B8G8R8X8:
case SurfaceFormat::R8G8B8A8:
case SurfaceFormat::R8G8B8X8:
case SurfaceFormat::A8R8G8B8:
case SurfaceFormat::X8R8G8B8:
break;
default:
MOZ_RELEASE_ASSERT(false, "Unsupported format for GaussianBlur");
break;
}
mFormat = aFormat;
mClamp = aClamp;
mSpreadRadius = aSpreadRadius;
mBlurSigma = aBlurSigma;
mBlurRadius = GaussianBlur::CalculateBlurRadius(aBlurSigma);
Rect rect(aRect);
rect.Inflate(Size(mBlurRadius + aSpreadRadius));
rect.RoundOut();
if (aDirtyRect) {
// If we get passed a dirty rect from layout, we can minimize the
// shadow size and make painting faster.
mHasDirtyRect = true;
mDirtyRect = *aDirtyRect;
Rect requiredBlurArea = mDirtyRect.Intersect(rect);
requiredBlurArea.Inflate(Size(mBlurRadius + aSpreadRadius));
rect = requiredBlurArea.Intersect(rect);
} else {
mHasDirtyRect = false;
}
mRect = TruncatedToInt(rect);
if (mRect.IsEmpty()) {
return;
}
if (aSkipRect) {
// If we get passed a skip rect, we can lower the amount of
// blurring/spreading we need to do. We convert it to IntRect to avoid
// expensive int<->float conversions if we were to use Rect instead.
Rect skipRect = *aSkipRect;
skipRect.Deflate(Size(mBlurRadius + aSpreadRadius));
mSkipRect = RoundedIn(skipRect);
mSkipRect = mSkipRect.Intersect(mRect);
if (mSkipRect.IsEqualInterior(mRect)) {
return;
}
mSkipRect -= mRect.TopLeft();
if (mSkipRect.IsEmpty()) {
mSkipRect = IntRect();
}
} else {
mSkipRect = IntRect();
}
int32_t stride = StrideForFormatAndWidth(mFormat, mRect.Width());
if (stride >= 0) {
mStride = stride;
// We need to leave room for an additional 3 bytes for a potential overrun
// in our blurring code.
size_t size = BufferSizeFromStrideAndHeight(mStride, mRect.Height(), 3);
if (size != 0) {
mSurfaceAllocationSize = size;
}
}
}
GaussianBlur::GaussianBlur(const Point& aSigma, bool aClamp)
: mBlurSigma(aSigma),
mBlurRadius(CalculateBlurRadius(aSigma)),
mClamp(aClamp) {}
IntSize GaussianBlur::GetSize() const { return mRect.Size(); }
SurfaceFormat GaussianBlur::GetFormat() const { return mFormat; }
int32_t GaussianBlur::GetStride() const { return mStride; }
IntRect GaussianBlur::GetRect() const { return mRect; }
Rect* GaussianBlur::GetDirtyRect() {
if (mHasDirtyRect) {
return &mDirtyRect;
}
return nullptr;
}
size_t GaussianBlur::GetSurfaceAllocationSize() const {
return mSurfaceAllocationSize;
}
bool GaussianBlur::Spread(uint8_t* aData, int32_t aStride, const IntSize& aSize,
SurfaceFormat aFormat) const {
size_t bufSize = BufferSizeFromStrideAndHeight(aStride, aSize.height);
if (!bufSize) {
return false;
}
uint8_t* tmpData = (uint8_t*)calloc(1, bufSize);
if (!tmpData) {
return false;
}
if (aFormat == SurfaceFormat::A8) {
SpreadHorizontal(aData, tmpData, mSpreadRadius.width, aSize.width,
aSize.height, aStride, mSkipRect);
SpreadVertical(tmpData, aData, mSpreadRadius.height, aSize.width,
aSize.height, aStride, mSkipRect);
} else {
uint32_t* data32 = reinterpret_cast<uint32_t*>(aData);
uint32_t* tmpData32 = reinterpret_cast<uint32_t*>(tmpData);
int32_t stride32 = aStride / sizeof(uint32_t);
SpreadHorizontal(data32, tmpData32, mSpreadRadius.width, aSize.width,
aSize.height, stride32, mSkipRect);
SpreadVertical(tmpData32, data32, mSpreadRadius.height, aSize.width,
aSize.height, stride32, mSkipRect);
}
free(tmpData);
return true;
}
void GaussianBlur::Blur(uint8_t* aData, int32_t aStride, const IntSize& aSize,
SurfaceFormat aFormat) const {
if (!aData || aStride <= 0) {
return;
}
if (aFormat == SurfaceFormat::UNKNOWN) {
aFormat = mFormat;
if (aFormat == SurfaceFormat::UNKNOWN) {
return;
}
}
if (mBlurRadius.width > 0 || mBlurRadius.height > 0 ||
mSpreadRadius.width > 0 || mSpreadRadius.height > 0) {
if (sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(
MakeSkiaImageInfo(aSize, aFormat), aData, aStride)) {
BlurSkSurface(surface.get());
}
}
}
static void FreeBlurredMask(const void*, void* aImage) {
SkMaskBuilder::FreeImage(aImage);
}
// Blurs an A8 image as a coverage mask, which is much cheaper than running a
// blur image filter over a colored layer. Returns the blurred mask and its
// offset relative to the source image, or nullptr if this isn't possible.
SkImage* GaussianBlur::BlurAlphaMask(SkImage* aAlphaImage, const Point& aSigma,
SkPoint& aOffset) {
SkPixmap pixmap;
// Skia's mask blur is limited to uniform blur with sigma <= 135.
if (aSigma.x != aSigma.y || aSigma.x > 135.0f ||
aAlphaImage->colorType() != kAlpha_8_SkColorType ||
!aAlphaImage->peekPixels(&pixmap) ||
!SkTFitsIn<uint32_t>(pixmap.rowBytes())) {
return nullptr;
}
SkMask src(pixmap.addr8(0, 0), pixmap.bounds(), uint32_t(pixmap.rowBytes()),
SkMask::kA8_Format);
SkMaskBuilder dst;
SkMaskBlurFilter blurFilter(aSigma.x, aSigma.y);
if (blurFilter.hasNoBlur()) {
return nullptr;
}
blurFilter.blur(src, &dst);
if (dst.image() && dst.format() == SkMask::kA8_Format) {
SkPixmap dstPixmap(
SkImageInfo::MakeA8(dst.bounds().width(), dst.bounds().height()),
dst.image(), dst.rowBytes());
if (sk_sp<SkImage> blurred = SkImages::RasterFromPixmap(
dstPixmap, FreeBlurredMask, dst.image())) {
aOffset = SkPoint::Make(dst.bounds().left(), dst.bounds().top());
return blurred.release();
}
}
if (dst.image()) {
SkMaskBuilder::FreeImage(dst.image());
}
return nullptr;
}
bool GaussianBlur::BlurSkSurface(SkSurface* aSurface) const {
IntSize size(aSurface->width(), aSurface->height());
MOZ_ASSERT(mRect.IsEmpty() || size == mRect.Size());
SkCanvas* canvas = aSurface->getCanvas();
if (!canvas) {
return false;
}
if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) {
SkImageInfo info;
size_t rowBytes = 0;
uint8_t* pixels = (uint8_t*)canvas->accessTopLayerPixels(&info, &rowBytes);
if (!pixels ||
!Spread(pixels, rowBytes, IntSize(info.width(), info.height()),
SkiaColorTypeToGfxFormat(info.colorType()))) {
return false;
}
}
if (mBlurRadius.width <= 0 && mBlurRadius.height <= 0) {
return true;
}
sk_sp<SkImage> snapshot = aSurface->makeImageSnapshot();
if (!snapshot) {
return false;
}
canvas->save();
canvas->resetMatrix();
SkPaint blurPaint;
blurPaint.setBlendMode(SkBlendMode::kSrc);
SkPoint offset = SkPoint::Make(0, 0);
sk_sp<SkImage> blurred(
!mClamp ? BlurAlphaMask(snapshot.get(), mBlurSigma, offset) : nullptr);
if (blurred) {
snapshot = blurred;
} else {
sk_sp<SkImageFilter> blurFilter(SkImageFilters::Blur(
mBlurSigma.x, mBlurSigma.y,
mClamp ? SkTileMode::kClamp : SkTileMode::kDecal, nullptr));
blurPaint.setImageFilter(blurFilter);
}
SkSamplingOptions sampling(SkFilterMode::kNearest);
auto constraint = SkCanvas::kFast_SrcRectConstraint;
if (mSkipRect.IsEmpty()) {
canvas->drawImage(snapshot, offset.x(), offset.y(), sampling, &blurPaint);
} else {
SkRect skipRect = IntRectToSkRect(mSkipRect).makeOffset(-offset);
SkRect bounds = SkRect::Make(snapshot->bounds());
SkRect top = SkRect::MakeLTRB(bounds.left(), bounds.top(), bounds.right(),
skipRect.top());
if (top.intersect(bounds)) {
canvas->drawImageRect(snapshot, top, top.makeOffset(offset), sampling,
&blurPaint, constraint);
}
SkRect left = SkRect::MakeLTRB(bounds.left(), skipRect.top(),
skipRect.left(), skipRect.bottom());
if (left.intersect(bounds)) {
canvas->drawImageRect(snapshot, left, left.makeOffset(offset), sampling,
&blurPaint, constraint);
}
SkRect right = SkRect::MakeLTRB(skipRect.right(), skipRect.top(),
bounds.right(), skipRect.bottom());
if (right.intersect(bounds)) {
canvas->drawImageRect(snapshot, right, right.makeOffset(offset), sampling,
&blurPaint, constraint);
}
SkRect bottom = SkRect::MakeLTRB(bounds.left(), skipRect.bottom(),
bounds.right(), bounds.bottom());
if (bottom.intersect(bounds)) {
canvas->drawImageRect(snapshot, bottom, bottom.makeOffset(offset),
sampling, &blurPaint, constraint);
}
}
canvas->restore();
return true;
}
/**
* Compute the box blur size (which we're calling the blur radius) from
* the standard deviation.
*
* Much of this, the 3 * sqrt(2 * pi) / 4, is the known value for
* approximating a Gaussian using box blurs. This yields quite a good
* approximation for a Gaussian. Then we multiply this by 1.5 since our
* code wants the radius of the entire triple-box-blur kernel instead of
* the diameter of an individual box blur. For more details, see:
*/
constexpr double sqrt_2_PI = 0x1.40d931ff62705p+1; // sqrt is not constexpr
static constexpr Float GAUSSIAN_SCALE_FACTOR = Float((3 * sqrt_2_PI / 4) * 1.5);
IntSize GaussianBlur::CalculateBlurRadius(const Point& aStd) {
IntSize size(
static_cast<int32_t>(floor(aStd.x * GAUSSIAN_SCALE_FACTOR + 0.5f)),
static_cast<int32_t>(floor(aStd.y * GAUSSIAN_SCALE_FACTOR + 0.5f)));
return size;
}
Float GaussianBlur::CalculateBlurSigma(int32_t aBlurRadius) {
return aBlurRadius / GAUSSIAN_SCALE_FACTOR;
}
} // namespace gfx
} // namespace mozilla