tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Blur.cpp (12843B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "Blur.h"
      8 
      9 #include <algorithm>
     10 #include <math.h>
     11 #include <string.h>
     12 
     13 #include "NumericTools.h"
     14 
     15 #include "2D.h"
     16 #include "DataSurfaceHelpers.h"
     17 #include "HelpersSkia.h"
     18 #include "Tools.h"
     19 
     20 #include "skia/include/core/SkCanvas.h"
     21 #include "skia/include/core/SkSurface.h"
     22 #include "skia/include/effects/SkImageFilters.h"
     23 
     24 namespace mozilla {
     25 namespace gfx {
     26 
     27 template <typename T>
     28 struct PixelValue {
     29  T value;
     30 
     31  explicit PixelValue(T aValue) : value(aValue) {}
     32 
     33  void Spread(const PixelValue& aOther) {
     34    value = std::max(value, aOther.value);
     35  }
     36 };
     37 
     38 template <>
     39 struct PixelValue<uint32_t> {
     40  union {
     41    struct {
     42      uint8_t r;
     43      uint8_t g;
     44      uint8_t b;
     45      uint8_t a;
     46    };
     47    uint32_t value;
     48  };
     49 
     50  explicit PixelValue(uint32_t aValue) { value = aValue; }
     51 
     52  void Spread(const PixelValue& aOther) {
     53    r = std::max(r, aOther.r);
     54    g = std::max(g, aOther.g);
     55    b = std::max(b, aOther.b);
     56    a = std::max(a, aOther.a);
     57  }
     58 };
     59 
     60 template <typename T>
     61 static void SpreadHorizontal(const T* aInput, T* aOutput, int32_t aRadius,
     62                             int32_t aWidth, int32_t aRows, int32_t aStride,
     63                             const IntRect& aSkipRect) {
     64  if (aRadius == 0) {
     65    memcpy(aOutput, aInput, aStride * aRows * sizeof(T));
     66    return;
     67  }
     68 
     69  bool skipRectCoversWholeRow =
     70      0 >= aSkipRect.X() && aWidth <= aSkipRect.XMost();
     71  for (int32_t y = 0; y < aRows; y++) {
     72    // Check whether the skip rect intersects this row. If the skip
     73    // rect covers the whole surface in this row, we can avoid
     74    // this row entirely (and any others along the skip rect).
     75    bool inSkipRectY = aSkipRect.ContainsY(y);
     76    if (inSkipRectY && skipRectCoversWholeRow) {
     77      y = aSkipRect.YMost() - 1;
     78      continue;
     79    }
     80 
     81    T* dst = &aOutput[aStride * y];
     82    for (int32_t x = 0; x < aWidth; x++) {
     83      // Check whether we are within the skip rect. If so, go
     84      // to the next point outside the skip rect.
     85      if (inSkipRectY && aSkipRect.ContainsX(x)) {
     86        x = aSkipRect.XMost();
     87        if (x >= aWidth) break;
     88      }
     89 
     90      int32_t sMin = std::max(x - aRadius, 0);
     91      int32_t sMax = std::min(x + aRadius, aWidth - 1);
     92      const auto* src =
     93          reinterpret_cast<const PixelValue<T>*>(&aInput[aStride * y + sMin]);
     94      PixelValue<T> v(0);
     95      for (int32_t s = sMin; s <= sMax; ++s) {
     96        v.Spread(*src);
     97        ++src;
     98      }
     99      *dst = v.value;
    100      ++dst;
    101    }
    102  }
    103 }
    104 
    105 template <typename T>
    106 static void SpreadVertical(const T* aInput, T* aOutput, int32_t aRadius,
    107                           int32_t aWidth, int32_t aRows, int32_t aStride,
    108                           const IntRect& aSkipRect) {
    109  if (aRadius == 0) {
    110    memcpy(aOutput, aInput, aStride * aRows * sizeof(T));
    111    return;
    112  }
    113 
    114  bool skipRectCoversWholeColumn =
    115      0 >= aSkipRect.Y() && aRows <= aSkipRect.YMost();
    116  for (int32_t x = 0; x < aWidth; x++) {
    117    bool inSkipRectX = aSkipRect.ContainsX(x);
    118    if (inSkipRectX && skipRectCoversWholeColumn) {
    119      x = aSkipRect.XMost() - 1;
    120      continue;
    121    }
    122 
    123    T* dst = &aOutput[x];
    124    for (int32_t y = 0; y < aRows; y++) {
    125      // Check whether we are within the skip rect. If so, go
    126      // to the next point outside the skip rect.
    127      if (inSkipRectX && aSkipRect.ContainsY(y)) {
    128        y = aSkipRect.YMost();
    129        if (y >= aRows) break;
    130      }
    131 
    132      int32_t sMin = std::max(y - aRadius, 0);
    133      int32_t sMax = std::min(y + aRadius, aRows - 1);
    134      const auto* src =
    135          reinterpret_cast<const PixelValue<T>*>(&aInput[aStride * sMin + x]);
    136      PixelValue<T> v(0);
    137      for (int32_t s = sMin; s <= sMax; ++s) {
    138        v.Spread(*src);
    139        src += aStride;
    140      }
    141      *dst = v.value;
    142      dst += aStride;
    143    }
    144  }
    145 }
    146 
    147 GaussianBlur::GaussianBlur(const Rect& aRect, const IntSize& aSpreadRadius,
    148                           const Point& aSigma, const Rect* aDirtyRect,
    149                           const Rect* aSkipRect, SurfaceFormat aFormat,
    150                           bool aClamp) {
    151  Init(aRect, aSpreadRadius, aSigma, aDirtyRect, aSkipRect, aFormat, aClamp);
    152 }
    153 
    154 GaussianBlur::GaussianBlur() {}
    155 
    156 void GaussianBlur::Init(const Rect& aRect, const IntSize& aSpreadRadius,
    157                        const Point& aBlurSigma, const Rect* aDirtyRect,
    158                        const Rect* aSkipRect, SurfaceFormat aFormat,
    159                        bool aClamp) {
    160  switch (aFormat) {
    161    case SurfaceFormat::A8:
    162    case SurfaceFormat::B8G8R8A8:
    163    case SurfaceFormat::B8G8R8X8:
    164    case SurfaceFormat::R8G8B8A8:
    165    case SurfaceFormat::R8G8B8X8:
    166    case SurfaceFormat::A8R8G8B8:
    167    case SurfaceFormat::X8R8G8B8:
    168      break;
    169    default:
    170      MOZ_RELEASE_ASSERT(false, "Unsupported format for GaussianBlur");
    171      break;
    172  }
    173 
    174  mFormat = aFormat;
    175  mClamp = aClamp;
    176  mSpreadRadius = aSpreadRadius;
    177  mBlurSigma = aBlurSigma;
    178  mBlurRadius = GaussianBlur::CalculateBlurRadius(aBlurSigma);
    179 
    180  Rect rect(aRect);
    181  rect.Inflate(Size(mBlurRadius + aSpreadRadius));
    182  rect.RoundOut();
    183 
    184  if (aDirtyRect) {
    185    // If we get passed a dirty rect from layout, we can minimize the
    186    // shadow size and make painting faster.
    187    mHasDirtyRect = true;
    188    mDirtyRect = *aDirtyRect;
    189    Rect requiredBlurArea = mDirtyRect.Intersect(rect);
    190    requiredBlurArea.Inflate(Size(mBlurRadius + aSpreadRadius));
    191    rect = requiredBlurArea.Intersect(rect);
    192  } else {
    193    mHasDirtyRect = false;
    194  }
    195 
    196  mRect = TruncatedToInt(rect);
    197  if (mRect.IsEmpty()) {
    198    return;
    199  }
    200 
    201  if (aSkipRect) {
    202    // If we get passed a skip rect, we can lower the amount of
    203    // blurring/spreading we need to do. We convert it to IntRect to avoid
    204    // expensive int<->float conversions if we were to use Rect instead.
    205    Rect skipRect = *aSkipRect;
    206    skipRect.Deflate(Size(mBlurRadius + aSpreadRadius));
    207    mSkipRect = RoundedIn(skipRect);
    208    mSkipRect = mSkipRect.Intersect(mRect);
    209    if (mSkipRect.IsEqualInterior(mRect)) {
    210      return;
    211    }
    212 
    213    mSkipRect -= mRect.TopLeft();
    214    if (mSkipRect.IsEmpty()) {
    215      mSkipRect = IntRect();
    216    }
    217  } else {
    218    mSkipRect = IntRect();
    219  }
    220 
    221  int32_t stride = StrideForFormatAndWidth(mFormat, mRect.Width());
    222  if (stride >= 0) {
    223    mStride = stride;
    224 
    225    // We need to leave room for an additional 3 bytes for a potential overrun
    226    // in our blurring code.
    227    size_t size = BufferSizeFromStrideAndHeight(mStride, mRect.Height(), 3);
    228    if (size != 0) {
    229      mSurfaceAllocationSize = size;
    230    }
    231  }
    232 }
    233 
    234 GaussianBlur::GaussianBlur(const Point& aSigma, bool aClamp)
    235    : mBlurSigma(aSigma),
    236      mBlurRadius(CalculateBlurRadius(aSigma)),
    237      mClamp(aClamp) {}
    238 
    239 GaussianBlur::~GaussianBlur() = default;
    240 
    241 IntSize GaussianBlur::GetSize() const { return mRect.Size(); }
    242 
    243 SurfaceFormat GaussianBlur::GetFormat() const { return mFormat; }
    244 
    245 int32_t GaussianBlur::GetStride() const { return mStride; }
    246 
    247 IntRect GaussianBlur::GetRect() const { return mRect; }
    248 
    249 Rect* GaussianBlur::GetDirtyRect() {
    250  if (mHasDirtyRect) {
    251    return &mDirtyRect;
    252  }
    253 
    254  return nullptr;
    255 }
    256 
    257 size_t GaussianBlur::GetSurfaceAllocationSize() const {
    258  return mSurfaceAllocationSize;
    259 }
    260 
    261 bool GaussianBlur::Spread(uint8_t* aData, int32_t aStride, const IntSize& aSize,
    262                          SurfaceFormat aFormat) const {
    263  size_t bufSize = BufferSizeFromStrideAndHeight(aStride, aSize.height);
    264  if (!bufSize) {
    265    return false;
    266  }
    267  uint8_t* tmpData = (uint8_t*)calloc(1, bufSize);
    268  if (!tmpData) {
    269    return false;
    270  }
    271  if (aFormat == SurfaceFormat::A8) {
    272    SpreadHorizontal(aData, tmpData, mSpreadRadius.width, aSize.width,
    273                     aSize.height, aStride, mSkipRect);
    274    SpreadVertical(tmpData, aData, mSpreadRadius.height, aSize.width,
    275                   aSize.height, aStride, mSkipRect);
    276  } else {
    277    uint32_t* data32 = reinterpret_cast<uint32_t*>(aData);
    278    uint32_t* tmpData32 = reinterpret_cast<uint32_t*>(tmpData);
    279    int32_t stride32 = aStride / sizeof(uint32_t);
    280    SpreadHorizontal(data32, tmpData32, mSpreadRadius.width, aSize.width,
    281                     aSize.height, stride32, mSkipRect);
    282    SpreadVertical(tmpData32, data32, mSpreadRadius.height, aSize.width,
    283                   aSize.height, stride32, mSkipRect);
    284  }
    285  free(tmpData);
    286  return true;
    287 }
    288 
    289 void GaussianBlur::Blur(uint8_t* aData, int32_t aStride, const IntSize& aSize,
    290                        SurfaceFormat aFormat) const {
    291  if (!aData || aStride <= 0) {
    292    return;
    293  }
    294  if (aFormat == SurfaceFormat::UNKNOWN) {
    295    aFormat = mFormat;
    296    if (aFormat == SurfaceFormat::UNKNOWN) {
    297      return;
    298    }
    299  }
    300  if (mBlurRadius.width > 0 || mBlurRadius.height > 0 ||
    301      mSpreadRadius.width > 0 || mSpreadRadius.height > 0) {
    302    if (sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(
    303            MakeSkiaImageInfo(aSize, aFormat), aData, aStride)) {
    304      BlurSkSurface(surface.get());
    305    }
    306  }
    307 }
    308 
    309 bool GaussianBlur::BlurSkSurface(SkSurface* aSurface) const {
    310  IntSize size(aSurface->width(), aSurface->height());
    311  MOZ_ASSERT(mRect.IsEmpty() || size == mRect.Size());
    312  SkCanvas* canvas = aSurface->getCanvas();
    313  if (!canvas) {
    314    return false;
    315  }
    316 
    317  if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) {
    318    SkImageInfo info;
    319    size_t rowBytes = 0;
    320    uint8_t* pixels = (uint8_t*)canvas->accessTopLayerPixels(&info, &rowBytes);
    321    if (!pixels ||
    322        !Spread(pixels, rowBytes, IntSize(info.width(), info.height()),
    323                SkiaColorTypeToGfxFormat(info.colorType()))) {
    324      return false;
    325    }
    326  }
    327 
    328  if (mBlurRadius.width <= 0 && mBlurRadius.height <= 0) {
    329    return true;
    330  }
    331 
    332  sk_sp<SkImage> snapshot = aSurface->makeImageSnapshot();
    333  if (!snapshot) {
    334    return false;
    335  }
    336  canvas->save();
    337  canvas->resetMatrix();
    338  SkPaint blurPaint;
    339  blurPaint.setBlendMode(SkBlendMode::kSrc);
    340  sk_sp<SkImageFilter> blurFilter(SkImageFilters::Blur(
    341      mBlurSigma.x, mBlurSigma.y,
    342      mClamp ? SkTileMode::kClamp : SkTileMode::kDecal, nullptr));
    343  blurPaint.setImageFilter(blurFilter);
    344  SkSamplingOptions sampling(SkFilterMode::kNearest);
    345  auto constraint = SkCanvas::kFast_SrcRectConstraint;
    346  if (mSkipRect.IsEmpty()) {
    347    canvas->drawImage(snapshot, 0, 0, sampling, &blurPaint);
    348  } else {
    349    SkRect top = SkRect::MakeIWH(size.width, size.height);
    350    if (top.intersect(SkRect::MakeLTRB(0, 0, size.width, mSkipRect.y))) {
    351      canvas->drawImageRect(snapshot, top, top, sampling, &blurPaint,
    352                            constraint);
    353    }
    354    SkRect left = SkRect::MakeIWH(size.width, size.height);
    355    if (left.intersect(
    356            SkRect::MakeLTRB(0, mSkipRect.y, mSkipRect.x, mSkipRect.YMost()))) {
    357      canvas->drawImageRect(snapshot, left, left, sampling, &blurPaint,
    358                            constraint);
    359    }
    360    SkRect right = SkRect::MakeIWH(size.width, size.height);
    361    if (right.intersect(SkRect::MakeLTRB(mSkipRect.XMost(), mSkipRect.y,
    362                                         size.width, mSkipRect.YMost()))) {
    363      canvas->drawImageRect(snapshot, right, right, sampling, &blurPaint,
    364                            constraint);
    365    }
    366    SkRect bottom = SkRect::MakeIWH(size.width, size.height);
    367    if (bottom.intersect(
    368            SkRect::MakeLTRB(0, mSkipRect.YMost(), size.width, size.height))) {
    369      canvas->drawImageRect(snapshot, bottom, bottom, sampling, &blurPaint,
    370                            constraint);
    371    }
    372  }
    373  canvas->restore();
    374  return true;
    375 }
    376 
    377 /**
    378 * Compute the box blur size (which we're calling the blur radius) from
    379 * the standard deviation.
    380 *
    381 * Much of this, the 3 * sqrt(2 * pi) / 4, is the known value for
    382 * approximating a Gaussian using box blurs.  This yields quite a good
    383 * approximation for a Gaussian.  Then we multiply this by 1.5 since our
    384 * code wants the radius of the entire triple-box-blur kernel instead of
    385 * the diameter of an individual box blur.  For more details, see:
    386 *   http://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement
    387 *   https://bugzilla.mozilla.org/show_bug.cgi?id=590039#c19
    388 */
    389 constexpr double sqrt_2_PI = 0x1.40d931ff62705p+1;  // sqrt is not constexpr
    390 static constexpr Float GAUSSIAN_SCALE_FACTOR = Float((3 * sqrt_2_PI / 4) * 1.5);
    391 
    392 IntSize GaussianBlur::CalculateBlurRadius(const Point& aStd) {
    393  IntSize size(
    394      static_cast<int32_t>(floor(aStd.x * GAUSSIAN_SCALE_FACTOR + 0.5f)),
    395      static_cast<int32_t>(floor(aStd.y * GAUSSIAN_SCALE_FACTOR + 0.5f)));
    396 
    397  return size;
    398 }
    399 
    400 Float GaussianBlur::CalculateBlurSigma(int32_t aBlurRadius) {
    401  return aBlurRadius / GAUSSIAN_SCALE_FACTOR;
    402 }
    403 
    404 }  // namespace gfx
    405 }  // namespace mozilla