1 /*
2 * Copyright 2006 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 "src/core/SkBlurMaskFilterImpl.h"
9
10 #include "include/core/SkBlendMode.h"
11 #include "include/core/SkBlurTypes.h"
12 #include "include/core/SkFlattenable.h"
13 #include "include/core/SkImageFilter.h"
14 #include "include/core/SkImageInfo.h"
15 #include "include/core/SkMaskFilter.h"
16 #include "include/core/SkMatrix.h"
17 #include "include/core/SkPaint.h"
18 #include "include/core/SkPath.h"
19 #include "include/core/SkPathBuilder.h"
20 #include "include/core/SkPathTypes.h"
21 #include "include/core/SkPixmap.h"
22 #include "include/core/SkPoint.h"
23 #include "include/core/SkRRect.h"
24 #include "include/core/SkRect.h"
25 #include "include/core/SkRefCnt.h"
26 #include "include/core/SkScalar.h"
27 #include "include/effects/SkImageFilters.h"
28 #include "include/private/base/SkAlign.h"
29 #include "include/private/base/SkAssert.h"
30 #include "include/private/base/SkFloatingPoint.h"
31 #include "include/private/base/SkTemplates.h"
32 #include "src/base/SkTLazy.h"
33 #include "src/core/SkBlitter_A8.h"
34 #include "src/core/SkBlurMask.h"
35 #include "src/core/SkCachedData.h"
36 #include "src/core/SkDrawBase.h"
37 #include "src/core/SkMask.h"
38 #include "src/core/SkMaskCache.h"
39 #include "src/core/SkMaskFilterBase.h"
40 #include "src/core/SkRasterClip.h"
41 #include "src/core/SkReadBuffer.h"
42 #include "src/core/SkResourceCache.h"
43 #include "src/core/SkWriteBuffer.h"
44
45 #include <algorithm>
46 #include <cstdint>
47 #include <cstring>
48 #include <utility>
49
SkBlurMaskFilterImpl(SkScalar sigma,SkBlurStyle style,bool respectCTM)50 SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle style, bool respectCTM)
51 : fSigma(sigma)
52 , fBlurStyle(style)
53 , fRespectCTM(respectCTM) {
54 SkASSERT(fSigma > 0);
55 SkASSERT((unsigned)style <= kLastEnum_SkBlurStyle);
56 }
57
getFormat() const58 SkMask::Format SkBlurMaskFilterImpl::getFormat() const {
59 return SkMask::kA8_Format;
60 }
61
asABlur(BlurRec * rec) const62 bool SkBlurMaskFilterImpl::asABlur(BlurRec* rec) const {
63 if (this->ignoreXform()) {
64 return false;
65 }
66
67 if (rec) {
68 rec->fSigma = fSigma;
69 rec->fStyle = fBlurStyle;
70 }
71 return true;
72 }
73
asImageFilter(const SkMatrix & ctm) const74 sk_sp<SkImageFilter> SkBlurMaskFilterImpl::asImageFilter(const SkMatrix& ctm) const {
75 float sigma = fSigma;
76 if (this->ignoreXform()) {
77 // This is analogous to computeXformedSigma(), but it might be more correct to wrap the
78 // blur image filter in a local matrix with ctm^-1, or to control the skif::Mapping when
79 // the mask filter layer is restored. This is inaccurate when 'ctm' has skew or perspective
80 const float ctmScaleFactor = fSigma / ctm.mapRadius(fSigma);
81 sigma *= ctmScaleFactor;
82 }
83
84 // The null input image filter will be bound to the original coverage mask.
85 sk_sp<SkImageFilter> filter = SkImageFilters::Blur(sigma, sigma, nullptr);
86 // Combine the original coverage mask (src) and the blurred coverage mask (dst)
87 switch(fBlurStyle) {
88 case kInner_SkBlurStyle: // dst = dst * src
89 // = 0 * src + src * dst
90 return SkImageFilters::Blend(SkBlendMode::kDstIn, std::move(filter), nullptr);
91 case kSolid_SkBlurStyle: // dst = src + dst - src * dst
92 // = 1 * src + (1 - src) * dst
93 return SkImageFilters::Blend(SkBlendMode::kSrcOver, std::move(filter), nullptr);
94 case kOuter_SkBlurStyle: // dst = dst * (1 - src)
95 // = 0 * src + (1 - src) * dst
96 return SkImageFilters::Blend(SkBlendMode::kDstOut, std::move(filter), nullptr);
97 case kNormal_SkBlurStyle:
98 return filter;
99 }
100 SkUNREACHABLE;
101 }
102
computeXformedSigma(const SkMatrix & ctm) const103 SkScalar SkBlurMaskFilterImpl::computeXformedSigma(const SkMatrix& ctm) const {
104 constexpr SkScalar kMaxBlurSigma = SkIntToScalar(128);
105 SkScalar xformedSigma = this->ignoreXform() ? fSigma : ctm.mapRadius(fSigma);
106 return std::min(xformedSigma, kMaxBlurSigma);
107 }
108
filterMask(SkMaskBuilder * dst,const SkMask & src,const SkMatrix & matrix,SkIPoint * margin) const109 bool SkBlurMaskFilterImpl::filterMask(SkMaskBuilder* dst, const SkMask& src,
110 const SkMatrix& matrix,
111 SkIPoint* margin) const {
112 SkScalar sigma = this->computeXformedSigma(matrix);
113 return SkBlurMask::BoxBlur(dst, src, sigma, fBlurStyle, margin);
114 }
115
filterRectMask(SkMaskBuilder * dst,const SkRect & r,const SkMatrix & matrix,SkIPoint * margin,SkMaskBuilder::CreateMode createMode) const116 bool SkBlurMaskFilterImpl::filterRectMask(SkMaskBuilder* dst, const SkRect& r,
117 const SkMatrix& matrix,
118 SkIPoint* margin,
119 SkMaskBuilder::CreateMode createMode) const {
120 SkScalar sigma = computeXformedSigma(matrix);
121
122 return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle, margin, createMode);
123 }
124
filterRRectMask(SkMaskBuilder * dst,const SkRRect & r,const SkMatrix & matrix,SkIPoint * margin,SkMaskBuilder::CreateMode createMode) const125 bool SkBlurMaskFilterImpl::filterRRectMask(SkMaskBuilder* dst, const SkRRect& r,
126 const SkMatrix& matrix,
127 SkIPoint* margin,
128 SkMaskBuilder::CreateMode createMode) const {
129 SkScalar sigma = computeXformedSigma(matrix);
130
131 return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle, margin, createMode);
132 }
133
prepare_to_draw_into_mask(const SkRect & bounds,SkMaskBuilder * mask)134 static bool prepare_to_draw_into_mask(const SkRect& bounds, SkMaskBuilder* mask) {
135 SkASSERT(mask != nullptr);
136
137 mask->bounds() = bounds.roundOut();
138 mask->rowBytes() = SkAlign4(mask->fBounds.width());
139 mask->format() = SkMask::kA8_Format;
140 const size_t size = mask->computeImageSize();
141 if (size == 0) {
142 return false;
143 }
144 mask->image() = SkMaskBuilder::AllocImage(size, SkMaskBuilder::kZeroInit_Alloc);
145 if (nullptr == mask->fImage) {
146 return false;
147 }
148 return true;
149 }
150
draw_into_mask(SkMaskBuilder * mask,const SkRect & bounds,Proc proc)151 template <typename Proc> bool draw_into_mask(SkMaskBuilder* mask, const SkRect& bounds, Proc proc) {
152 if (!prepare_to_draw_into_mask(bounds, mask)) {
153 return false;
154 }
155
156 const int dx = mask->fBounds.fLeft;
157 const int dy = mask->fBounds.fTop;
158 SkRasterClip rclip(mask->fBounds);
159 rclip.setRect(mask->fBounds.makeOffset(-dx, -dy));
160
161 SkASSERT(mask->fFormat == SkMask::kA8_Format);
162 auto info = SkImageInfo::MakeA8(mask->fBounds.width(), mask->fBounds.height());
163 auto pm = SkPixmap(info, mask->fImage, mask->fRowBytes);
164
165 SkMatrix ctm = SkMatrix::Translate(-SkIntToScalar(dx), -SkIntToScalar(dy));
166
167 SkDrawBase draw;
168 draw.fBlitterChooser = SkA8Blitter_Choose;
169 draw.fCTM = &ctm;
170 draw.fDst = pm;
171 draw.fRC = &rclip;
172
173 SkPaint paint;
174 paint.setAntiAlias(true);
175
176 proc(draw, paint);
177 return true;
178 }
179
draw_rects_into_mask(const SkRect rects[],int count,SkMaskBuilder * mask)180 static bool draw_rects_into_mask(const SkRect rects[], int count, SkMaskBuilder* mask) {
181 return draw_into_mask(mask, rects[0], [&](SkDrawBase& draw, const SkPaint& paint) {
182 if (1 == count) {
183 draw.drawRect(rects[0], paint);
184 } else {
185 // todo: do I need a fast way to do this?
186 SkPath path = SkPathBuilder().addRect(rects[0])
187 .addRect(rects[1])
188 .setFillType(SkPathFillType::kEvenOdd)
189 .detach();
190 draw.drawPath(path, paint);
191 }
192 });
193 }
194
draw_rrect_into_mask(const SkRRect rrect,SkMaskBuilder * mask)195 static bool draw_rrect_into_mask(const SkRRect rrect, SkMaskBuilder* mask) {
196 return draw_into_mask(mask, rrect.rect(), [&](SkDrawBase& draw, const SkPaint& paint) {
197 draw.drawRRect(rrect, paint);
198 });
199 }
200
rect_exceeds(const SkRect & r,SkScalar v)201 static bool rect_exceeds(const SkRect& r, SkScalar v) {
202 return r.fLeft < -v || r.fTop < -v || r.fRight > v || r.fBottom > v ||
203 r.width() > v || r.height() > v;
204 }
205
copy_mask_to_cacheddata(SkMaskBuilder * mask)206 static SkCachedData* copy_mask_to_cacheddata(SkMaskBuilder* mask) {
207 const size_t size = mask->computeTotalImageSize();
208 SkCachedData* data = SkResourceCache::NewCachedData(size);
209 if (data) {
210 memcpy(data->writable_data(), mask->fImage, size);
211 SkMaskBuilder::FreeImage(mask->image());
212 mask->image() = (uint8_t*)data->writable_data();
213 }
214 return data;
215 }
216
find_cached_rrect(SkTLazy<SkMask> * mask,SkScalar sigma,SkBlurStyle style,const SkRRect & rrect)217 static SkCachedData* find_cached_rrect(SkTLazy<SkMask>* mask, SkScalar sigma, SkBlurStyle style,
218 const SkRRect& rrect) {
219 return SkMaskCache::FindAndRef(sigma, style, rrect, mask);
220 }
221
add_cached_rrect(SkMaskBuilder * mask,SkScalar sigma,SkBlurStyle style,const SkRRect & rrect)222 static SkCachedData* add_cached_rrect(SkMaskBuilder* mask, SkScalar sigma, SkBlurStyle style,
223 const SkRRect& rrect) {
224 SkCachedData* cache = copy_mask_to_cacheddata(mask);
225 if (cache) {
226 SkMaskCache::Add(sigma, style, rrect, *mask, cache);
227 }
228 return cache;
229 }
230
find_cached_rects(SkTLazy<SkMask> * mask,SkScalar sigma,SkBlurStyle style,const SkRect rects[],int count)231 static SkCachedData* find_cached_rects(SkTLazy<SkMask>* mask, SkScalar sigma, SkBlurStyle style,
232 const SkRect rects[], int count) {
233 return SkMaskCache::FindAndRef(sigma, style, rects, count, mask);
234 }
235
add_cached_rects(SkMaskBuilder * mask,SkScalar sigma,SkBlurStyle style,const SkRect rects[],int count)236 static SkCachedData* add_cached_rects(SkMaskBuilder* mask, SkScalar sigma, SkBlurStyle style,
237 const SkRect rects[], int count) {
238 SkCachedData* cache = copy_mask_to_cacheddata(mask);
239 if (cache) {
240 SkMaskCache::Add(sigma, style, rects, count, *mask, cache);
241 }
242 return cache;
243 }
244
245 static const bool c_analyticBlurRRect{true};
246
247 SkMaskFilterBase::FilterReturn
filterRRectToNine(const SkRRect & rrect,const SkMatrix & matrix,const SkIRect & clipBounds,SkTLazy<NinePatch> * patch) const248 SkBlurMaskFilterImpl::filterRRectToNine(const SkRRect& rrect, const SkMatrix& matrix,
249 const SkIRect& clipBounds,
250 SkTLazy<NinePatch>* patch) const {
251 SkASSERT(patch != nullptr);
252 switch (rrect.getType()) {
253 case SkRRect::kEmpty_Type:
254 // Nothing to draw.
255 return kFalse_FilterReturn;
256
257 case SkRRect::kRect_Type:
258 // We should have caught this earlier.
259 SkASSERT(false);
260 [[fallthrough]];
261 case SkRRect::kOval_Type:
262 // The nine patch special case does not handle ovals, and we
263 // already have code for rectangles.
264 return kUnimplemented_FilterReturn;
265
266 // These three can take advantage of this fast path.
267 case SkRRect::kSimple_Type:
268 case SkRRect::kNinePatch_Type:
269 case SkRRect::kComplex_Type:
270 break;
271 }
272
273 // TODO: report correct metrics for innerstyle, where we do not grow the
274 // total bounds, but we do need an inset the size of our blur-radius
275 if (kInner_SkBlurStyle == fBlurStyle) {
276 return kUnimplemented_FilterReturn;
277 }
278
279 // TODO: take clipBounds into account to limit our coordinates up front
280 // for now, just skip too-large src rects (to take the old code path).
281 if (rect_exceeds(rrect.rect(), SkIntToScalar(32767))) {
282 return kUnimplemented_FilterReturn;
283 }
284
285 SkIPoint margin;
286 SkMaskBuilder srcM(nullptr, rrect.rect().roundOut(), 0, SkMask::kA8_Format), dstM;
287
288 bool filterResult = false;
289 if (c_analyticBlurRRect) {
290 // special case for fast round rect blur
291 // don't actually do the blur the first time, just compute the correct size
292 filterResult = this->filterRRectMask(&dstM, rrect, matrix, &margin,
293 SkMaskBuilder::kJustComputeBounds_CreateMode);
294 }
295
296 if (!filterResult) {
297 filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
298 }
299
300 if (!filterResult) {
301 return kFalse_FilterReturn;
302 }
303
304 // Now figure out the appropriate width and height of the smaller round rectangle
305 // to stretch. It will take into account the larger radius per side as well as double
306 // the margin, to account for inner and outer blur.
307 const SkVector& UL = rrect.radii(SkRRect::kUpperLeft_Corner);
308 const SkVector& UR = rrect.radii(SkRRect::kUpperRight_Corner);
309 const SkVector& LR = rrect.radii(SkRRect::kLowerRight_Corner);
310 const SkVector& LL = rrect.radii(SkRRect::kLowerLeft_Corner);
311
312 const SkScalar leftUnstretched = std::max(UL.fX, LL.fX) + SkIntToScalar(2 * margin.fX);
313 const SkScalar rightUnstretched = std::max(UR.fX, LR.fX) + SkIntToScalar(2 * margin.fX);
314
315 // Extra space in the middle to ensure an unchanging piece for stretching. Use 3 to cover
316 // any fractional space on either side plus 1 for the part to stretch.
317 const SkScalar stretchSize = SkIntToScalar(3);
318
319 const SkScalar totalSmallWidth = leftUnstretched + rightUnstretched + stretchSize;
320 if (totalSmallWidth >= rrect.rect().width()) {
321 // There is no valid piece to stretch.
322 return kUnimplemented_FilterReturn;
323 }
324
325 const SkScalar topUnstretched = std::max(UL.fY, UR.fY) + SkIntToScalar(2 * margin.fY);
326 const SkScalar bottomUnstretched = std::max(LL.fY, LR.fY) + SkIntToScalar(2 * margin.fY);
327
328 const SkScalar totalSmallHeight = topUnstretched + bottomUnstretched + stretchSize;
329 if (totalSmallHeight >= rrect.rect().height()) {
330 // There is no valid piece to stretch.
331 return kUnimplemented_FilterReturn;
332 }
333
334 SkRect smallR = SkRect::MakeWH(totalSmallWidth, totalSmallHeight);
335
336 SkRRect smallRR;
337 SkVector radii[4];
338 radii[SkRRect::kUpperLeft_Corner] = UL;
339 radii[SkRRect::kUpperRight_Corner] = UR;
340 radii[SkRRect::kLowerRight_Corner] = LR;
341 radii[SkRRect::kLowerLeft_Corner] = LL;
342 smallRR.setRectRadii(smallR, radii);
343
344 const SkScalar sigma = this->computeXformedSigma(matrix);
345 SkTLazy<SkMask> cachedMask;
346 SkCachedData* cache = find_cached_rrect(&cachedMask, sigma, fBlurStyle, smallRR);
347 if (!cache) {
348 SkMaskBuilder filterM;
349 bool analyticBlurWorked = false;
350 if (c_analyticBlurRRect) {
351 analyticBlurWorked =
352 this->filterRRectMask(&filterM, smallRR, matrix, &margin,
353 SkMaskBuilder::kComputeBoundsAndRenderImage_CreateMode);
354 }
355
356 if (!analyticBlurWorked) {
357 if (!draw_rrect_into_mask(smallRR, &srcM)) {
358 return kFalse_FilterReturn;
359 }
360 SkAutoMaskFreeImage amf(srcM.image());
361
362 if (!this->filterMask(&filterM, srcM, matrix, &margin)) {
363 return kFalse_FilterReturn;
364 }
365 }
366 cache = add_cached_rrect(&filterM, sigma, fBlurStyle, smallRR);
367 cachedMask.init(filterM);
368 }
369
370 SkIRect bounds = cachedMask->fBounds;
371 bounds.offsetTo(0, 0);
372 patch->init(SkMask{cachedMask->fImage, bounds, cachedMask->fRowBytes, cachedMask->fFormat},
373 dstM.fBounds,
374 SkIPoint{SkScalarCeilToInt(leftUnstretched) + 1,
375 SkScalarCeilToInt(topUnstretched) + 1},
376 cache); // transfer ownership to patch
377 return kTrue_FilterReturn;
378 }
379
380 // Use the faster analytic blur approach for ninepatch rects
381 static const bool c_analyticBlurNinepatch{true};
382
383 SkMaskFilterBase::FilterReturn
filterRectsToNine(const SkRect rects[],int count,const SkMatrix & matrix,const SkIRect & clipBounds,SkTLazy<NinePatch> * patch) const384 SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count,
385 const SkMatrix& matrix,
386 const SkIRect& clipBounds,
387 SkTLazy<NinePatch>* patch) const {
388 if (count < 1 || count > 2) {
389 return kUnimplemented_FilterReturn;
390 }
391
392 // TODO: report correct metrics for innerstyle, where we do not grow the
393 // total bounds, but we do need an inset the size of our blur-radius
394 if (kInner_SkBlurStyle == fBlurStyle || kOuter_SkBlurStyle == fBlurStyle) {
395 return kUnimplemented_FilterReturn;
396 }
397
398 // TODO: take clipBounds into account to limit our coordinates up front
399 // for now, just skip too-large src rects (to take the old code path).
400 if (rect_exceeds(rects[0], SkIntToScalar(32767))) {
401 return kUnimplemented_FilterReturn;
402 }
403
404 SkIPoint margin;
405 SkMaskBuilder srcM(nullptr, rects[0].roundOut(), 0, SkMask::kA8_Format), dstM;
406
407 bool filterResult = false;
408 if (count == 1 && c_analyticBlurNinepatch) {
409 // special case for fast rect blur
410 // don't actually do the blur the first time, just compute the correct size
411 filterResult = this->filterRectMask(&dstM, rects[0], matrix, &margin,
412 SkMaskBuilder::kJustComputeBounds_CreateMode);
413 } else {
414 filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
415 }
416
417 if (!filterResult) {
418 return kFalse_FilterReturn;
419 }
420
421 /*
422 * smallR is the smallest version of 'rect' that will still guarantee that
423 * we get the same blur results on all edges, plus 1 center row/col that is
424 * representative of the extendible/stretchable edges of the ninepatch.
425 * Since our actual edge may be fractional we inset 1 more to be sure we
426 * don't miss any interior blur.
427 * x is an added pixel of blur, and { and } are the (fractional) edge
428 * pixels from the original rect.
429 *
430 * x x { x x .... x x } x x
431 *
432 * Thus, in this case, we inset by a total of 5 (on each side) beginning
433 * with our outer-rect (dstM.fBounds)
434 */
435 SkRect smallR[2];
436 SkIPoint center;
437
438 // +2 is from +1 for each edge (to account for possible fractional edges
439 int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 2;
440 int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 2;
441 SkIRect innerIR;
442
443 if (1 == count) {
444 innerIR = srcM.fBounds;
445 center.set(smallW, smallH);
446 } else {
447 SkASSERT(2 == count);
448 rects[1].roundIn(&innerIR);
449 center.set(smallW + (innerIR.left() - srcM.fBounds.left()),
450 smallH + (innerIR.top() - srcM.fBounds.top()));
451 }
452
453 // +1 so we get a clean, stretchable, center row/col
454 smallW += 1;
455 smallH += 1;
456
457 // we want the inset amounts to be integral, so we don't change any
458 // fractional phase on the fRight or fBottom of our smallR.
459 const SkScalar dx = SkIntToScalar(innerIR.width() - smallW);
460 const SkScalar dy = SkIntToScalar(innerIR.height() - smallH);
461 if (dx < 0 || dy < 0) {
462 // we're too small, relative to our blur, to break into nine-patch,
463 // so we ask to have our normal filterMask() be called.
464 return kUnimplemented_FilterReturn;
465 }
466
467 smallR[0].setLTRB(rects[0].left(), rects[0].top(),
468 rects[0].right() - dx, rects[0].bottom() - dy);
469 if (smallR[0].width() < 2 || smallR[0].height() < 2) {
470 return kUnimplemented_FilterReturn;
471 }
472 if (2 == count) {
473 smallR[1].setLTRB(rects[1].left(), rects[1].top(),
474 rects[1].right() - dx, rects[1].bottom() - dy);
475 SkASSERT(!smallR[1].isEmpty());
476 }
477
478 const SkScalar sigma = this->computeXformedSigma(matrix);
479 SkTLazy<SkMask> cachedMask;
480 SkCachedData* cache = find_cached_rects(&cachedMask, sigma, fBlurStyle, smallR, count);
481 if (!cache) {
482 SkMaskBuilder filterM;
483 if (count > 1 || !c_analyticBlurNinepatch) {
484 if (!draw_rects_into_mask(smallR, count, &srcM)) {
485 return kFalse_FilterReturn;
486 }
487
488 SkAutoMaskFreeImage amf(srcM.image());
489
490 if (!this->filterMask(&filterM, srcM, matrix, &margin)) {
491 return kFalse_FilterReturn;
492 }
493 } else {
494 if (!this->filterRectMask(&filterM, smallR[0], matrix, &margin,
495 SkMaskBuilder::kComputeBoundsAndRenderImage_CreateMode)) {
496 return kFalse_FilterReturn;
497 }
498 }
499 cache = add_cached_rects(&filterM, sigma, fBlurStyle, smallR, count);
500 cachedMask.init(filterM);
501 }
502 SkIRect bounds = cachedMask->fBounds;
503 bounds.offsetTo(0, 0);
504 patch->init(SkMask{cachedMask->fImage, bounds, cachedMask->fRowBytes, cachedMask->fFormat},
505 dstM.fBounds, center, cache); // transfer ownership to patch
506 return kTrue_FilterReturn;
507 }
508
computeFastBounds(const SkRect & src,SkRect * dst) const509 void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src,
510 SkRect* dst) const {
511 // TODO: if we're doing kInner blur, should we return a different outset?
512 // i.e. pad == 0 ?
513
514 SkScalar pad = 3.0f * fSigma;
515
516 dst->setLTRB(src.fLeft - pad, src.fTop - pad,
517 src.fRight + pad, src.fBottom + pad);
518 }
519
CreateProc(SkReadBuffer & buffer)520 sk_sp<SkFlattenable> SkBlurMaskFilterImpl::CreateProc(SkReadBuffer& buffer) {
521 const SkScalar sigma = buffer.readScalar();
522 SkBlurStyle style = buffer.read32LE(kLastEnum_SkBlurStyle);
523
524 uint32_t flags = buffer.read32LE(0x3); // historically we only recorded 2 bits
525 bool respectCTM = !(flags & 1); // historically we stored ignoreCTM in low bit
526
527 return SkMaskFilter::MakeBlur((SkBlurStyle)style, sigma, respectCTM);
528 }
529
flatten(SkWriteBuffer & buffer) const530 void SkBlurMaskFilterImpl::flatten(SkWriteBuffer& buffer) const {
531 buffer.writeScalar(fSigma);
532 buffer.writeUInt(fBlurStyle);
533 buffer.writeUInt(!fRespectCTM); // historically we recorded ignoreCTM
534 }
535
sk_register_blur_maskfilter_createproc()536 void sk_register_blur_maskfilter_createproc() { SK_REGISTER_FLATTENABLE(SkBlurMaskFilterImpl); }
537
MakeBlur(SkBlurStyle style,SkScalar sigma,bool respectCTM)538 sk_sp<SkMaskFilter> SkMaskFilter::MakeBlur(SkBlurStyle style, SkScalar sigma, bool respectCTM) {
539 if (SkIsFinite(sigma) && sigma > 0) {
540 return sk_sp<SkMaskFilter>(new SkBlurMaskFilterImpl(sigma, style, respectCTM));
541 }
542 return nullptr;
543 }
544