SkBlurImageFilter.cpp revision e426babe7552b1cb4e27cdf4e90826feabb9e3b0
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 "SkAutoPixmapStorage.h" 9#include "SkColorPriv.h" 10#include "SkGpuBlurUtils.h" 11#include "SkOpts.h" 12#include "SkReadBuffer.h" 13#include "SkSpecialImage.h" 14#include "SkWriteBuffer.h" 15 16#if SK_SUPPORT_GPU 17#include "GrContext.h" 18#include "SkGr.h" 19#endif 20 21class SkBlurImageFilterImpl : public SkImageFilter { 22public: 23 SkBlurImageFilterImpl(SkScalar sigmaX, 24 SkScalar sigmaY, 25 sk_sp<SkImageFilter> input, 26 const CropRect* cropRect); 27 28 SkRect computeFastBounds(const SkRect&) const override; 29 30 SK_TO_STRING_OVERRIDE() 31 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurImageFilterImpl) 32 33#ifdef SK_SUPPORT_LEGACY_IMAGEFILTER_PTR 34 static SkImageFilter* Create(SkScalar sigmaX, SkScalar sigmaY, SkImageFilter* input = nullptr, 35 const CropRect* cropRect = nullptr) { 36 return SkImageInfo::Make(sigmaX, sigmaY, sk_ref_sp<SkImageFilter>(input), cropRect).release(); 37 } 38#endif 39 40protected: 41 void flatten(SkWriteBuffer&) const override; 42 sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&, 43 SkIPoint* offset) const override; 44 SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override; 45 46private: 47 SkSize fSigma; 48 typedef SkImageFilter INHERITED; 49 50 friend class SkImageFilter; 51}; 52 53SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkImageFilter) 54 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilterImpl) 55SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END 56 57/////////////////////////////////////////////////////////////////////////////// 58 59sk_sp<SkImageFilter> SkImageFilter::MakeBlur(SkScalar sigmaX, SkScalar sigmaY, 60 sk_sp<SkImageFilter> input, 61 const CropRect* cropRect) { 62 if (0 == sigmaX && 0 == sigmaY && !cropRect) { 63 return input; 64 } 65 return sk_sp<SkImageFilter>(new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect)); 66} 67 68// This rather arbitrary-looking value results in a maximum box blur kernel size 69// of 1000 pixels on the raster path, which matches the WebKit and Firefox 70// implementations. Since the GPU path does not compute a box blur, putting 71// the limit on sigma ensures consistent behaviour between the GPU and 72// raster paths. 73#define MAX_SIGMA SkIntToScalar(532) 74 75static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) { 76 SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height()); 77 ctm.mapVectors(&sigma, 1); 78 sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA); 79 sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA); 80 return sigma; 81} 82 83SkBlurImageFilterImpl::SkBlurImageFilterImpl(SkScalar sigmaX, 84 SkScalar sigmaY, 85 sk_sp<SkImageFilter> input, 86 const CropRect* cropRect) 87 : INHERITED(&input, 1, cropRect) 88 , fSigma(SkSize::Make(sigmaX, sigmaY)) { 89} 90 91sk_sp<SkFlattenable> SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) { 92 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); 93 SkScalar sigmaX = buffer.readScalar(); 94 SkScalar sigmaY = buffer.readScalar(); 95 return SkImageFilter::MakeBlur(sigmaX, sigmaY, common.getInput(0), &common.cropRect()); 96} 97 98void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const { 99 this->INHERITED::flatten(buffer); 100 buffer.writeScalar(fSigma.fWidth); 101 buffer.writeScalar(fSigma.fHeight); 102} 103 104static void get_box3_params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset, 105 int *highOffset) { 106 float pi = SkScalarToFloat(SK_ScalarPI); 107 int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f)); 108 *kernelSize = d; 109 if (d % 2 == 1) { 110 *lowOffset = *highOffset = (d - 1) / 2; 111 *kernelSize3 = d; 112 } else { 113 *highOffset = d / 2; 114 *lowOffset = *highOffset - 1; 115 *kernelSize3 = d + 1; 116 } 117} 118 119sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* source, 120 const Context& ctx, 121 SkIPoint* offset) const { 122 SkIPoint inputOffset = SkIPoint::Make(0, 0); 123 124 sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset)); 125 if (!input) { 126 return nullptr; 127 } 128 129 SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY, 130 input->width(), input->height()); 131 132 SkIRect dstBounds; 133 if (!this->applyCropRect(this->mapContext(ctx), inputBounds, &dstBounds)) { 134 return nullptr; 135 } 136 if (!inputBounds.intersect(dstBounds)) { 137 return nullptr; 138 } 139 140 const SkVector sigma = map_sigma(fSigma, ctx.ctm()); 141 142#if SK_SUPPORT_GPU 143 if (source->isTextureBacked()) { 144 GrContext* context = source->getContext(); 145 sk_sp<GrTexture> inputTexture(input->asTextureRef(context)); 146 SkASSERT(inputTexture); 147 148 if (0 == sigma.x() && 0 == sigma.y()) { 149 offset->fX = inputBounds.x(); 150 offset->fY = inputBounds.y(); 151 return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(), 152 -inputOffset.y())); 153 } 154 155 offset->fX = dstBounds.fLeft; 156 offset->fY = dstBounds.fTop; 157 inputBounds.offset(-inputOffset); 158 dstBounds.offset(-inputOffset); 159 sk_sp<GrDrawContext> drawContext(SkGpuBlurUtils::GaussianBlur( 160 context, 161 inputTexture.get(), 162 sk_ref_sp(source->getColorSpace()), 163 dstBounds, 164 &inputBounds, 165 sigma.x(), 166 sigma.y())); 167 if (!drawContext) { 168 return nullptr; 169 } 170 171 // TODO: Get the colorSpace from the drawContext (once it has one) 172 return SkSpecialImage::MakeFromGpu(SkIRect::MakeWH(dstBounds.width(), dstBounds.height()), 173 kNeedNewImageUniqueID_SpecialImage, 174 drawContext->asTexture(), 175 sk_ref_sp(input->getColorSpace()), &source->props()); 176 } 177#endif 178 179 int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX; 180 int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY; 181 get_box3_params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX); 182 get_box3_params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY); 183 184 if (kernelSizeX < 0 || kernelSizeY < 0) { 185 return nullptr; 186 } 187 188 if (kernelSizeX == 0 && kernelSizeY == 0) { 189 offset->fX = inputBounds.x(); 190 offset->fY = inputBounds.y(); 191 return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(), 192 -inputOffset.y())); 193 } 194 195 SkBitmap inputBM; 196 197 if (!input->getROPixels(&inputBM)) { 198 return nullptr; 199 } 200 201 if (inputBM.colorType() != kN32_SkColorType) { 202 return nullptr; 203 } 204 205 SkImageInfo info = SkImageInfo::Make(dstBounds.width(), dstBounds.height(), 206 inputBM.colorType(), inputBM.alphaType()); 207 208 SkBitmap tmp, dst; 209 if (!tmp.tryAllocPixels(info) || !dst.tryAllocPixels(info)) { 210 return nullptr; 211 } 212 213 SkAutoLockPixels inputLock(inputBM), tmpLock(tmp), dstLock(dst); 214 215 offset->fX = dstBounds.fLeft; 216 offset->fY = dstBounds.fTop; 217 SkPMColor* t = tmp.getAddr32(0, 0); 218 SkPMColor* d = dst.getAddr32(0, 0); 219 int w = dstBounds.width(), h = dstBounds.height(); 220 const SkPMColor* s = inputBM.getAddr32(inputBounds.x() - inputOffset.x(), 221 inputBounds.y() - inputOffset.y()); 222 inputBounds.offset(-dstBounds.x(), -dstBounds.y()); 223 dstBounds.offset(-dstBounds.x(), -dstBounds.y()); 224 SkIRect inputBoundsT = SkIRect::MakeLTRB(inputBounds.top(), inputBounds.left(), 225 inputBounds.bottom(), inputBounds.right()); 226 SkIRect dstBoundsT = SkIRect::MakeWH(dstBounds.height(), dstBounds.width()); 227 int sw = int(inputBM.rowBytes() >> 2); 228 229 /** 230 * 231 * In order to make memory accesses cache-friendly, we reorder the passes to 232 * use contiguous memory reads wherever possible. 233 * 234 * For example, the 6 passes of the X-and-Y blur case are rewritten as 235 * follows. Instead of 3 passes in X and 3 passes in Y, we perform 236 * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X, 237 * then 1 pass in X transposed to Y on write. 238 * 239 * +----+ +----+ +----+ +---+ +---+ +---+ +----+ 240 * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB | 241 * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+ 242 * +---+ +---+ +---+ 243 * 244 * In this way, two of the y-blurs become x-blurs applied to transposed 245 * images, and all memory reads are contiguous. 246 */ 247 if (kernelSizeX > 0 && kernelSizeY > 0) { 248 SkOpts::box_blur_xx(s, sw, inputBounds, t, kernelSizeX, lowOffsetX, highOffsetX, w, h); 249 SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX, highOffsetX, lowOffsetX, w, h); 250 SkOpts::box_blur_xy(d, w, dstBounds, t, kernelSizeX3, highOffsetX, highOffsetX, w, h); 251 SkOpts::box_blur_xx(t, h, dstBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w); 252 SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w); 253 SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w); 254 } else if (kernelSizeX > 0) { 255 SkOpts::box_blur_xx(s, sw, inputBounds, d, kernelSizeX, lowOffsetX, highOffsetX, w, h); 256 SkOpts::box_blur_xx(d, w, dstBounds, t, kernelSizeX, highOffsetX, lowOffsetX, w, h); 257 SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX3, highOffsetX, highOffsetX, w, h); 258 } else if (kernelSizeY > 0) { 259 SkOpts::box_blur_yx(s, sw, inputBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w); 260 SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w); 261 SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w); 262 } 263 264 return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(), 265 dstBounds.height()), 266 dst, &source->props()); 267} 268 269 270SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const { 271 SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; 272 bounds.outset(SkScalarMul(fSigma.width(), SkIntToScalar(3)), 273 SkScalarMul(fSigma.height(), SkIntToScalar(3))); 274 return bounds; 275} 276 277SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, 278 MapDirection) const { 279 SkVector sigma = map_sigma(fSigma, ctm); 280 return src.makeOutset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))), 281 SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3)))); 282} 283 284#ifndef SK_IGNORE_TO_STRING 285void SkBlurImageFilterImpl::toString(SkString* str) const { 286 str->appendf("SkBlurImageFilterImpl: ("); 287 str->appendf("sigma: (%f, %f) input (", fSigma.fWidth, fSigma.fHeight); 288 289 if (this->getInput(0)) { 290 this->getInput(0)->toString(str); 291 } 292 293 str->append("))"); 294} 295#endif 296