SkBlurImageFilter.cpp revision 5a4fb6eb335d29f24a1b239d1c0eb11b5d2ee59f
1/* 2 * Copyright 2011 The Android Open Source Project 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "SkBlurImageFilter.h" 9 10#include "SkAutoPixmapStorage.h" 11#include "SkColorData.h" 12#include "SkColorSpaceXformer.h" 13#include "SkTFitsIn.h" 14#include "SkGpuBlurUtils.h" 15#include "SkOpts.h" 16#include "SkReadBuffer.h" 17#include "SkSpecialImage.h" 18#include "SkWriteBuffer.h" 19 20#if SK_SUPPORT_GPU 21#include "GrContext.h" 22#include "GrTextureProxy.h" 23#include "SkGr.h" 24#endif 25 26class SkBlurImageFilterImpl final : public SkImageFilter { 27public: 28 SkBlurImageFilterImpl(SkScalar sigmaX, 29 SkScalar sigmaY, 30 sk_sp<SkImageFilter> input, 31 const CropRect* cropRect, 32 SkBlurImageFilter::TileMode tileMode); 33 34 SkRect computeFastBounds(const SkRect&) const override; 35 36 SK_TO_STRING_OVERRIDE() 37 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurImageFilterImpl) 38 39protected: 40 void flatten(SkWriteBuffer&) const override; 41 sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&, 42 SkIPoint* offset) const override; 43 sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; 44 SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override; 45 46private: 47 typedef SkImageFilter INHERITED; 48 friend class SkImageFilter; 49 50 #if SK_SUPPORT_GPU 51 sk_sp<SkSpecialImage> gpuFilter( 52 SkSpecialImage *source, 53 SkVector sigma, const sk_sp<SkSpecialImage> &input, 54 SkIRect inputBounds, SkIRect dstBounds) const; 55 #endif 56 57 sk_sp<SkSpecialImage> cpuFilter( 58 SkSpecialImage *source, 59 SkVector sigma, const sk_sp<SkSpecialImage> &input, 60 SkIRect inputBounds, SkIRect dstBounds) const; 61 62 SkSize fSigma; 63 SkBlurImageFilter::TileMode fTileMode; 64}; 65 66SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkImageFilter) 67 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl) 68SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END 69 70/////////////////////////////////////////////////////////////////////////////// 71 72sk_sp<SkImageFilter> SkBlurImageFilter::Make(SkScalar sigmaX, SkScalar sigmaY, 73 sk_sp<SkImageFilter> input, 74 const SkImageFilter::CropRect* cropRect, 75 TileMode tileMode) { 76 if (0 == sigmaX && 0 == sigmaY && !cropRect) { 77 return input; 78 } 79 return sk_sp<SkImageFilter>( 80 new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect, tileMode)); 81} 82 83// This rather arbitrary-looking value results in a maximum box blur kernel size 84// of 1000 pixels on the raster path, which matches the WebKit and Firefox 85// implementations. Since the GPU path does not compute a box blur, putting 86// the limit on sigma ensures consistent behaviour between the GPU and 87// raster paths. 88#define MAX_SIGMA SkIntToScalar(532) 89 90static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) { 91 SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height()); 92 ctm.mapVectors(&sigma, 1); 93 sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA); 94 sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA); 95 return sigma; 96} 97 98SkBlurImageFilterImpl::SkBlurImageFilterImpl(SkScalar sigmaX, 99 SkScalar sigmaY, 100 sk_sp<SkImageFilter> input, 101 const CropRect* cropRect, 102 SkBlurImageFilter::TileMode tileMode) 103 : INHERITED(&input, 1, cropRect), fSigma{sigmaX, sigmaY}, fTileMode(tileMode) {} 104 105sk_sp<SkFlattenable> SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) { 106 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); 107 SkScalar sigmaX = buffer.readScalar(); 108 SkScalar sigmaY = buffer.readScalar(); 109 SkBlurImageFilter::TileMode tileMode; 110 if (buffer.isVersionLT(SkReadBuffer::kTileModeInBlurImageFilter_Version)) { 111 tileMode = SkBlurImageFilter::kClampToBlack_TileMode; 112 } else { 113 tileMode = static_cast<SkBlurImageFilter::TileMode>(buffer.readInt()); 114 } 115 116 static_assert(SkBlurImageFilter::kMax_TileMode == 2, "CreateProc"); 117 SkASSERT(tileMode <= SkBlurImageFilter::kMax_TileMode); 118 119 return SkBlurImageFilter::Make( 120 sigmaX, sigmaY, common.getInput(0), &common.cropRect(), tileMode); 121} 122 123void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const { 124 this->INHERITED::flatten(buffer); 125 buffer.writeScalar(fSigma.fWidth); 126 buffer.writeScalar(fSigma.fHeight); 127 128 static_assert(SkBlurImageFilter::kMax_TileMode == 2, "flatten"); 129 SkASSERT(fTileMode <= SkBlurImageFilter::kMax_TileMode); 130 131 buffer.writeInt(static_cast<int>(fTileMode)); 132} 133 134#if SK_SUPPORT_GPU 135static GrTextureDomain::Mode to_texture_domain_mode(SkBlurImageFilter::TileMode tileMode) { 136 switch (tileMode) { 137 case SkBlurImageFilter::TileMode::kClamp_TileMode: 138 return GrTextureDomain::kClamp_Mode; 139 case SkBlurImageFilter::TileMode::kClampToBlack_TileMode: 140 return GrTextureDomain::kDecal_Mode; 141 case SkBlurImageFilter::TileMode::kRepeat_TileMode: 142 return GrTextureDomain::kRepeat_Mode; 143 default: 144 SK_ABORT("Unsupported tile mode."); 145 return GrTextureDomain::kDecal_Mode; 146 } 147} 148#endif 149 150static void get_box3_params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset, 151 int *highOffset) { 152 float pi = SkScalarToFloat(SK_ScalarPI); 153 int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f)); 154 *kernelSize = d; 155 if (d % 2 == 1) { 156 *lowOffset = *highOffset = (d - 1) / 2; 157 *kernelSize3 = d; 158 } else { 159 *highOffset = d / 2; 160 *lowOffset = *highOffset - 1; 161 *kernelSize3 = d + 1; 162 } 163} 164 165sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* source, 166 const Context& ctx, 167 SkIPoint* offset) const { 168 SkIPoint inputOffset = SkIPoint::Make(0, 0); 169 170 sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset)); 171 if (!input) { 172 return nullptr; 173 } 174 175 SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY, 176 input->width(), input->height()); 177 178 // Calculate the destination bounds. 179 SkIRect dstBounds; 180 if (!this->applyCropRect(this->mapContext(ctx), inputBounds, &dstBounds)) { 181 return nullptr; 182 } 183 if (!inputBounds.intersect(dstBounds)) { 184 return nullptr; 185 } 186 187 // Save the offset in preparation to make all rectangles relative to the inputOffset. 188 SkIPoint resultOffset = SkIPoint::Make(dstBounds.fLeft, dstBounds.fTop); 189 190 // Make all bounds relative to the inputOffset. 191 inputBounds.offset(-inputOffset); 192 dstBounds.offset(-inputOffset); 193 194 const SkVector sigma = map_sigma(fSigma, ctx.ctm()); 195 if (sigma.x() < 0 || sigma.y() < 0) { 196 return nullptr; 197 } 198 199 sk_sp<SkSpecialImage> result; 200#if SK_SUPPORT_GPU 201 if (source->isTextureBacked()) { 202 // Ensure the input is in the destination's gamut. This saves us from having to do the 203 // xform during the filter itself. 204 input = ImageToColorSpace(input.get(), ctx.outputProperties()); 205 206 result = this->gpuFilter(source, sigma, input, inputBounds, dstBounds); 207 } else 208 #endif 209 { 210 #if defined(SK_SUPPORT_LEGACY_BLUR_IMAGE) 211 result = this->cpuFilter(source, sigma, input, inputBounds, dstBounds); 212 #else 213 // The new code will go here. 214 result = this->cpuFilter(source, sigma, input, inputBounds, dstBounds); 215 #endif 216 } 217 218 // Return the resultOffset if the blur succeeded. 219 if (result != nullptr) { 220 *offset = resultOffset; 221 } 222 return result; 223} 224 225#if SK_SUPPORT_GPU 226sk_sp<SkSpecialImage> SkBlurImageFilterImpl::gpuFilter( 227 SkSpecialImage *source, 228 SkVector sigma, const sk_sp<SkSpecialImage> &input, 229 SkIRect inputBounds, SkIRect dstBounds) const 230{ 231 // If both sigmas produce arms of the cross that are less than 1/2048, then they 232 // do not contribute to the sum of the filter in a way to change a gamma corrected result. 233 // Let s = 1/(2*sigma^2) 234 // The normalizing value n = 1 + 4*E^(-s) + 4*E^(-2s) 235 // The raw cross arm value c = E^-s 236 // The normalized cross arm value = c/n 237 // N[Solve[{c/n == 1/2048, sigma > 0}, sigma], 16] 238 static constexpr double kCrossTooSmall = 0.2561130112451658; 239 if (sigma.x() < kCrossTooSmall && sigma.y() < kCrossTooSmall) { 240 return input->makeSubset(inputBounds); 241 } 242 243 GrContext* context = source->getContext(); 244 245 sk_sp<GrTextureProxy> inputTexture(input->asTextureProxyRef(context)); 246 if (!inputTexture) { 247 return nullptr; 248 } 249 250 // Typically, we would create the RTC with the output's color space (from ctx), but we 251 // always blur in the PixelConfig of the *input*. Those might not be compatible (if they 252 // have different transfer functions). We've already guaranteed that those color spaces 253 // have the same gamut, so in this case, we do everything in the input's color space. 254 sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur( 255 context, 256 std::move(inputTexture), 257 sk_ref_sp(input->getColorSpace()), 258 dstBounds, 259 inputBounds, 260 sigma.x(), 261 sigma.y(), 262 to_texture_domain_mode(fTileMode))); 263 if (!renderTargetContext) { 264 return nullptr; 265 } 266 267 return SkSpecialImage::MakeDeferredFromGpu(context, 268 SkIRect::MakeWH(dstBounds.width(), 269 dstBounds.height()), 270 kNeedNewImageUniqueID_SpecialImage, 271 renderTargetContext->asTextureProxyRef(), 272 renderTargetContext->refColorSpace(), 273 &source->props()); 274} 275#endif 276 277// TODO: Implement CPU backend for different fTileMode. 278sk_sp<SkSpecialImage> SkBlurImageFilterImpl::cpuFilter( 279 SkSpecialImage *source, 280 SkVector sigma, const sk_sp<SkSpecialImage> &input, 281 SkIRect inputBounds, SkIRect dstBounds) const 282{ 283 // If both sigmas will result in a zero width window, there is nothing to do. 284 // N[Solve[sigma*3*Sqrt[2 Pi]/4 == 1/2, sigma], 16] 285 static constexpr double kZeroWindow = 0.2659615202676218; 286 if (sigma.x() < kZeroWindow && sigma.y() < kZeroWindow) { 287 return input->makeSubset(inputBounds); 288 } 289 290 int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX; 291 int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY; 292 get_box3_params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX); 293 get_box3_params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY); 294 295 SkBitmap inputBM; 296 297 if (!input->getROPixels(&inputBM) && inputBM.colorType() != kN32_SkColorType) { 298 return nullptr; 299 } 300 301 SkImageInfo info = SkImageInfo::Make(dstBounds.width(), dstBounds.height(), 302 inputBM.colorType(), inputBM.alphaType()); 303 304 SkBitmap tmp, dst; 305 if (!tmp.tryAllocPixels(info) || !dst.tryAllocPixels(info)) { 306 return nullptr; 307 } 308 309 // Get ready to blur. 310 const SkPMColor* s = inputBM.getAddr32(inputBounds.x(), inputBounds.y()); 311 SkPMColor* t = tmp.getAddr32(0, 0); 312 SkPMColor* d = dst.getAddr32(0, 0); 313 314 // Shift everything from being relative to the orignal input bounds to the destination bounds. 315 inputBounds.offset(-dstBounds.x(), -dstBounds.y()); 316 dstBounds.offset(-dstBounds.x(), -dstBounds.y()); 317 318 int w = dstBounds.width(), 319 h = dstBounds.height(), 320 sw = inputBM.rowBytesAsPixels(); 321 322 SkIRect inputBoundsT = SkIRect::MakeLTRB(inputBounds.top(), inputBounds.left(), 323 inputBounds.bottom(), inputBounds.right()); 324 SkIRect dstBoundsT = SkIRect::MakeWH(dstBounds.height(), dstBounds.width()); 325 326 /** 327 * 328 * In order to make memory accesses cache-friendly, we reorder the passes to 329 * use contiguous memory reads wherever possible. 330 * 331 * For example, the 6 passes of the X-and-Y blur case are rewritten as 332 * follows. Instead of 3 passes in X and 3 passes in Y, we perform 333 * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X, 334 * then 1 pass in X transposed to Y on write. 335 * 336 * +----+ +----+ +----+ +---+ +---+ +---+ +----+ 337 * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB | 338 * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+ 339 * +---+ +---+ +---+ 340 * 341 * In this way, two of the y-blurs become x-blurs applied to transposed 342 * images, and all memory reads are contiguous. 343 */ 344 if (kernelSizeX > 0 && kernelSizeY > 0) { 345 SkOpts::box_blur_xx(s, sw, inputBounds, t, kernelSizeX, lowOffsetX, highOffsetX, w, h); 346 SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX, highOffsetX, lowOffsetX, w, h); 347 SkOpts::box_blur_xy(d, w, dstBounds, t, kernelSizeX3, highOffsetX, highOffsetX, w, h); 348 SkOpts::box_blur_xx(t, h, dstBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w); 349 SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w); 350 SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w); 351 } else if (kernelSizeX > 0) { 352 SkOpts::box_blur_xx(s, sw, inputBounds, d, kernelSizeX, lowOffsetX, highOffsetX, w, h); 353 SkOpts::box_blur_xx(d, w, dstBounds, t, kernelSizeX, highOffsetX, lowOffsetX, w, h); 354 SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX3, highOffsetX, highOffsetX, w, h); 355 } else if (kernelSizeY > 0) { 356 SkOpts::box_blur_yx(s, sw, inputBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w); 357 SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w); 358 SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w); 359 } 360 361 return SkSpecialImage::MakeFromRaster(SkIRect::MakeSize(dstBounds.size()), 362 dst, &source->props()); 363} 364 365sk_sp<SkImageFilter> SkBlurImageFilterImpl::onMakeColorSpace(SkColorSpaceXformer* xformer) 366const { 367 SkASSERT(1 == this->countInputs()); 368 369 auto input = xformer->apply(this->getInput(0)); 370 if (this->getInput(0) != input.get()) { 371 return SkBlurImageFilter::Make(fSigma.width(), fSigma.height(), std::move(input), 372 this->getCropRectIfSet(), fTileMode); 373 } 374 return this->refMe(); 375} 376 377SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const { 378 SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; 379 bounds.outset(fSigma.width() * 3, fSigma.height() * 3); 380 return bounds; 381} 382 383SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, 384 MapDirection) const { 385 SkVector sigma = map_sigma(fSigma, ctm); 386 return src.makeOutset(SkScalarCeilToInt(sigma.x() * 3), SkScalarCeilToInt(sigma.y() * 3)); 387} 388 389#ifndef SK_IGNORE_TO_STRING 390void SkBlurImageFilterImpl::toString(SkString* str) const { 391 str->appendf("SkBlurImageFilterImpl: ("); 392 str->appendf("sigma: (%f, %f) tileMode: %d input (", fSigma.fWidth, fSigma.fHeight, 393 static_cast<int>(fTileMode)); 394 395 if (this->getInput(0)) { 396 this->getInput(0)->toString(str); 397 } 398 399 str->append("))"); 400} 401#endif 402