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