| /* |
| * Copyright 2006 The Android Open Source Project |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/core/SkBlurMaskFilterImpl.h" |
| |
| #include "include/core/SkBlurTypes.h" |
| #include "include/core/SkFlattenable.h" |
| #include "include/core/SkImageInfo.h" |
| #include "include/core/SkMaskFilter.h" |
| #include "include/core/SkMatrix.h" |
| #include "include/core/SkPaint.h" |
| #include "include/core/SkPath.h" |
| #include "include/core/SkPathBuilder.h" |
| #include "include/core/SkPathTypes.h" |
| #include "include/core/SkPixmap.h" |
| #include "include/core/SkPoint.h" |
| #include "include/core/SkRRect.h" |
| #include "include/core/SkRect.h" |
| #include "include/core/SkRefCnt.h" |
| #include "include/core/SkScalar.h" |
| #include "include/private/base/SkAlign.h" |
| #include "include/private/base/SkAssert.h" |
| #include "include/private/base/SkTemplates.h" |
| #include "src/core/SkBlitter_A8.h" |
| #include "src/core/SkBlurMask.h" |
| #include "src/core/SkCachedData.h" |
| #include "src/core/SkDrawBase.h" |
| #include "src/core/SkMask.h" |
| #include "src/core/SkMaskCache.h" |
| #include "src/core/SkMaskFilterBase.h" |
| #include "src/core/SkMatrixProvider.h" |
| #include "src/core/SkRasterClip.h" |
| #include "src/core/SkReadBuffer.h" |
| #include "src/core/SkResourceCache.h" |
| #include "src/core/SkWriteBuffer.h" |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <cstring> |
| |
| SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle style, bool respectCTM) |
| : fSigma(sigma) |
| , fBlurStyle(style) |
| , fRespectCTM(respectCTM) { |
| SkASSERT(fSigma > 0); |
| SkASSERT((unsigned)style <= kLastEnum_SkBlurStyle); |
| } |
| |
| SkMask::Format SkBlurMaskFilterImpl::getFormat() const { |
| return SkMask::kA8_Format; |
| } |
| |
| bool SkBlurMaskFilterImpl::asABlur(BlurRec* rec) const { |
| if (this->ignoreXform()) { |
| return false; |
| } |
| |
| if (rec) { |
| rec->fSigma = fSigma; |
| rec->fStyle = fBlurStyle; |
| } |
| return true; |
| } |
| |
| SkScalar SkBlurMaskFilterImpl::computeXformedSigma(const SkMatrix& ctm) const { |
| constexpr SkScalar kMaxBlurSigma = SkIntToScalar(128); |
| SkScalar xformedSigma = this->ignoreXform() ? fSigma : ctm.mapRadius(fSigma); |
| return std::min(xformedSigma, kMaxBlurSigma); |
| } |
| |
| bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src, |
| const SkMatrix& matrix, |
| SkIPoint* margin) const { |
| SkScalar sigma = this->computeXformedSigma(matrix); |
| return SkBlurMask::BoxBlur(dst, src, sigma, fBlurStyle, margin); |
| } |
| |
| bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r, |
| const SkMatrix& matrix, |
| SkIPoint* margin, SkMask::CreateMode createMode) const { |
| SkScalar sigma = computeXformedSigma(matrix); |
| |
| return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle, margin, createMode); |
| } |
| |
| bool SkBlurMaskFilterImpl::filterRRectMask(SkMask* dst, const SkRRect& r, |
| const SkMatrix& matrix, |
| SkIPoint* margin, SkMask::CreateMode createMode) const { |
| SkScalar sigma = computeXformedSigma(matrix); |
| |
| return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle, margin, createMode); |
| } |
| |
| static bool prepare_to_draw_into_mask(const SkRect& bounds, SkMask* mask) { |
| SkASSERT(mask != nullptr); |
| |
| mask->fBounds = bounds.roundOut(); |
| mask->fRowBytes = SkAlign4(mask->fBounds.width()); |
| mask->fFormat = SkMask::kA8_Format; |
| const size_t size = mask->computeImageSize(); |
| mask->fImage = SkMask::AllocImage(size, SkMask::kZeroInit_Alloc); |
| if (nullptr == mask->fImage) { |
| return false; |
| } |
| return true; |
| } |
| |
| template <typename Proc> bool draw_into_mask(SkMask* mask, const SkRect& bounds, Proc proc) { |
| if (!prepare_to_draw_into_mask(bounds, mask)) { |
| return false; |
| } |
| |
| const int dx = mask->fBounds.fLeft; |
| const int dy = mask->fBounds.fTop; |
| SkRasterClip rclip(mask->fBounds); |
| rclip.setRect(mask->fBounds.makeOffset(-dx, -dy)); |
| |
| SkASSERT(mask->fFormat == SkMask::kA8_Format); |
| auto info = SkImageInfo::MakeA8(mask->fBounds.width(), mask->fBounds.height()); |
| auto pm = SkPixmap(info, mask->fImage, mask->fRowBytes); |
| |
| SkMatrix ctm = SkMatrix::Translate(-SkIntToScalar(dx), -SkIntToScalar(dy)); |
| |
| SkMatrixProvider matrixProvider(ctm); |
| |
| SkDrawBase draw; |
| draw.fBlitterChooser = SkA8Blitter_Choose; |
| draw.fMatrixProvider = &matrixProvider; |
| draw.fDst = pm; |
| draw.fRC = &rclip; |
| |
| SkPaint paint; |
| paint.setAntiAlias(true); |
| |
| proc(draw, paint); |
| return true; |
| } |
| |
| static bool draw_rects_into_mask(const SkRect rects[], int count, SkMask* mask) { |
| return draw_into_mask(mask, rects[0], [&](SkDrawBase& draw, const SkPaint& paint) { |
| if (1 == count) { |
| draw.drawRect(rects[0], paint); |
| } else { |
| // todo: do I need a fast way to do this? |
| SkPath path = SkPathBuilder().addRect(rects[0]) |
| .addRect(rects[1]) |
| .setFillType(SkPathFillType::kEvenOdd) |
| .detach(); |
| draw.drawPath(path, paint); |
| } |
| }); |
| } |
| |
| static bool draw_rrect_into_mask(const SkRRect rrect, SkMask* mask) { |
| return draw_into_mask(mask, rrect.rect(), [&](SkDrawBase& draw, const SkPaint& paint) { |
| draw.drawRRect(rrect, paint); |
| }); |
| } |
| |
| static bool rect_exceeds(const SkRect& r, SkScalar v) { |
| return r.fLeft < -v || r.fTop < -v || r.fRight > v || r.fBottom > v || |
| r.width() > v || r.height() > v; |
| } |
| |
| static SkCachedData* copy_mask_to_cacheddata(SkMask* mask) { |
| const size_t size = mask->computeTotalImageSize(); |
| SkCachedData* data = SkResourceCache::NewCachedData(size); |
| if (data) { |
| memcpy(data->writable_data(), mask->fImage, size); |
| SkMask::FreeImage(mask->fImage); |
| mask->fImage = (uint8_t*)data->data(); |
| } |
| return data; |
| } |
| |
| static SkCachedData* find_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style, |
| const SkRRect& rrect) { |
| return SkMaskCache::FindAndRef(sigma, style, rrect, mask); |
| } |
| |
| static SkCachedData* add_cached_rrect(SkMask* mask, SkScalar sigma, SkBlurStyle style, |
| const SkRRect& rrect) { |
| SkCachedData* cache = copy_mask_to_cacheddata(mask); |
| if (cache) { |
| SkMaskCache::Add(sigma, style, rrect, *mask, cache); |
| } |
| return cache; |
| } |
| |
| static SkCachedData* find_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style, |
| const SkRect rects[], int count) { |
| return SkMaskCache::FindAndRef(sigma, style, rects, count, mask); |
| } |
| |
| static SkCachedData* add_cached_rects(SkMask* mask, SkScalar sigma, SkBlurStyle style, |
| const SkRect rects[], int count) { |
| SkCachedData* cache = copy_mask_to_cacheddata(mask); |
| if (cache) { |
| SkMaskCache::Add(sigma, style, rects, count, *mask, cache); |
| } |
| return cache; |
| } |
| |
| static const bool c_analyticBlurRRect{true}; |
| |
| SkMaskFilterBase::FilterReturn |
| SkBlurMaskFilterImpl::filterRRectToNine(const SkRRect& rrect, const SkMatrix& matrix, |
| const SkIRect& clipBounds, |
| NinePatch* patch) const { |
| SkASSERT(patch != nullptr); |
| switch (rrect.getType()) { |
| case SkRRect::kEmpty_Type: |
| // Nothing to draw. |
| return kFalse_FilterReturn; |
| |
| case SkRRect::kRect_Type: |
| // We should have caught this earlier. |
| SkASSERT(false); |
| [[fallthrough]]; |
| case SkRRect::kOval_Type: |
| // The nine patch special case does not handle ovals, and we |
| // already have code for rectangles. |
| return kUnimplemented_FilterReturn; |
| |
| // These three can take advantage of this fast path. |
| case SkRRect::kSimple_Type: |
| case SkRRect::kNinePatch_Type: |
| case SkRRect::kComplex_Type: |
| break; |
| } |
| |
| // TODO: report correct metrics for innerstyle, where we do not grow the |
| // total bounds, but we do need an inset the size of our blur-radius |
| if (kInner_SkBlurStyle == fBlurStyle) { |
| return kUnimplemented_FilterReturn; |
| } |
| |
| // TODO: take clipBounds into account to limit our coordinates up front |
| // for now, just skip too-large src rects (to take the old code path). |
| if (rect_exceeds(rrect.rect(), SkIntToScalar(32767))) { |
| return kUnimplemented_FilterReturn; |
| } |
| |
| SkIPoint margin; |
| SkMask srcM, dstM; |
| srcM.fBounds = rrect.rect().roundOut(); |
| srcM.fFormat = SkMask::kA8_Format; |
| srcM.fRowBytes = 0; |
| |
| bool filterResult = false; |
| if (c_analyticBlurRRect) { |
| // special case for fast round rect blur |
| // don't actually do the blur the first time, just compute the correct size |
| filterResult = this->filterRRectMask(&dstM, rrect, matrix, &margin, |
| SkMask::kJustComputeBounds_CreateMode); |
| } |
| |
| if (!filterResult) { |
| filterResult = this->filterMask(&dstM, srcM, matrix, &margin); |
| } |
| |
| if (!filterResult) { |
| return kFalse_FilterReturn; |
| } |
| |
| // Now figure out the appropriate width and height of the smaller round rectangle |
| // to stretch. It will take into account the larger radius per side as well as double |
| // the margin, to account for inner and outer blur. |
| const SkVector& UL = rrect.radii(SkRRect::kUpperLeft_Corner); |
| const SkVector& UR = rrect.radii(SkRRect::kUpperRight_Corner); |
| const SkVector& LR = rrect.radii(SkRRect::kLowerRight_Corner); |
| const SkVector& LL = rrect.radii(SkRRect::kLowerLeft_Corner); |
| |
| const SkScalar leftUnstretched = std::max(UL.fX, LL.fX) + SkIntToScalar(2 * margin.fX); |
| const SkScalar rightUnstretched = std::max(UR.fX, LR.fX) + SkIntToScalar(2 * margin.fX); |
| |
| // Extra space in the middle to ensure an unchanging piece for stretching. Use 3 to cover |
| // any fractional space on either side plus 1 for the part to stretch. |
| const SkScalar stretchSize = SkIntToScalar(3); |
| |
| const SkScalar totalSmallWidth = leftUnstretched + rightUnstretched + stretchSize; |
| if (totalSmallWidth >= rrect.rect().width()) { |
| // There is no valid piece to stretch. |
| return kUnimplemented_FilterReturn; |
| } |
| |
| const SkScalar topUnstretched = std::max(UL.fY, UR.fY) + SkIntToScalar(2 * margin.fY); |
| const SkScalar bottomUnstretched = std::max(LL.fY, LR.fY) + SkIntToScalar(2 * margin.fY); |
| |
| const SkScalar totalSmallHeight = topUnstretched + bottomUnstretched + stretchSize; |
| if (totalSmallHeight >= rrect.rect().height()) { |
| // There is no valid piece to stretch. |
| return kUnimplemented_FilterReturn; |
| } |
| |
| SkRect smallR = SkRect::MakeWH(totalSmallWidth, totalSmallHeight); |
| |
| SkRRect smallRR; |
| SkVector radii[4]; |
| radii[SkRRect::kUpperLeft_Corner] = UL; |
| radii[SkRRect::kUpperRight_Corner] = UR; |
| radii[SkRRect::kLowerRight_Corner] = LR; |
| radii[SkRRect::kLowerLeft_Corner] = LL; |
| smallRR.setRectRadii(smallR, radii); |
| |
| const SkScalar sigma = this->computeXformedSigma(matrix); |
| SkCachedData* cache = find_cached_rrect(&patch->fMask, sigma, fBlurStyle, smallRR); |
| if (!cache) { |
| bool analyticBlurWorked = false; |
| if (c_analyticBlurRRect) { |
| analyticBlurWorked = |
| this->filterRRectMask(&patch->fMask, smallRR, matrix, &margin, |
| SkMask::kComputeBoundsAndRenderImage_CreateMode); |
| } |
| |
| if (!analyticBlurWorked) { |
| if (!draw_rrect_into_mask(smallRR, &srcM)) { |
| return kFalse_FilterReturn; |
| } |
| |
| SkAutoMaskFreeImage amf(srcM.fImage); |
| |
| if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) { |
| return kFalse_FilterReturn; |
| } |
| } |
| cache = add_cached_rrect(&patch->fMask, sigma, fBlurStyle, smallRR); |
| } |
| |
| patch->fMask.fBounds.offsetTo(0, 0); |
| patch->fOuterRect = dstM.fBounds; |
| patch->fCenter.fX = SkScalarCeilToInt(leftUnstretched) + 1; |
| patch->fCenter.fY = SkScalarCeilToInt(topUnstretched) + 1; |
| SkASSERT(nullptr == patch->fCache); |
| patch->fCache = cache; // transfer ownership to patch |
| return kTrue_FilterReturn; |
| } |
| |
| // Use the faster analytic blur approach for ninepatch rects |
| static const bool c_analyticBlurNinepatch{true}; |
| |
| SkMaskFilterBase::FilterReturn |
| SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count, |
| const SkMatrix& matrix, |
| const SkIRect& clipBounds, |
| NinePatch* patch) const { |
| if (count < 1 || count > 2) { |
| return kUnimplemented_FilterReturn; |
| } |
| |
| // TODO: report correct metrics for innerstyle, where we do not grow the |
| // total bounds, but we do need an inset the size of our blur-radius |
| if (kInner_SkBlurStyle == fBlurStyle || kOuter_SkBlurStyle == fBlurStyle) { |
| return kUnimplemented_FilterReturn; |
| } |
| |
| // TODO: take clipBounds into account to limit our coordinates up front |
| // for now, just skip too-large src rects (to take the old code path). |
| if (rect_exceeds(rects[0], SkIntToScalar(32767))) { |
| return kUnimplemented_FilterReturn; |
| } |
| |
| SkIPoint margin; |
| SkMask srcM, dstM; |
| srcM.fBounds = rects[0].roundOut(); |
| srcM.fFormat = SkMask::kA8_Format; |
| srcM.fRowBytes = 0; |
| |
| bool filterResult = false; |
| if (count == 1 && c_analyticBlurNinepatch) { |
| // special case for fast rect blur |
| // don't actually do the blur the first time, just compute the correct size |
| filterResult = this->filterRectMask(&dstM, rects[0], matrix, &margin, |
| SkMask::kJustComputeBounds_CreateMode); |
| } else { |
| filterResult = this->filterMask(&dstM, srcM, matrix, &margin); |
| } |
| |
| if (!filterResult) { |
| return kFalse_FilterReturn; |
| } |
| |
| /* |
| * smallR is the smallest version of 'rect' that will still guarantee that |
| * we get the same blur results on all edges, plus 1 center row/col that is |
| * representative of the extendible/stretchable edges of the ninepatch. |
| * Since our actual edge may be fractional we inset 1 more to be sure we |
| * don't miss any interior blur. |
| * x is an added pixel of blur, and { and } are the (fractional) edge |
| * pixels from the original rect. |
| * |
| * x x { x x .... x x } x x |
| * |
| * Thus, in this case, we inset by a total of 5 (on each side) beginning |
| * with our outer-rect (dstM.fBounds) |
| */ |
| SkRect smallR[2]; |
| SkIPoint center; |
| |
| // +2 is from +1 for each edge (to account for possible fractional edges |
| int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 2; |
| int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 2; |
| SkIRect innerIR; |
| |
| if (1 == count) { |
| innerIR = srcM.fBounds; |
| center.set(smallW, smallH); |
| } else { |
| SkASSERT(2 == count); |
| rects[1].roundIn(&innerIR); |
| center.set(smallW + (innerIR.left() - srcM.fBounds.left()), |
| smallH + (innerIR.top() - srcM.fBounds.top())); |
| } |
| |
| // +1 so we get a clean, stretchable, center row/col |
| smallW += 1; |
| smallH += 1; |
| |
| // we want the inset amounts to be integral, so we don't change any |
| // fractional phase on the fRight or fBottom of our smallR. |
| const SkScalar dx = SkIntToScalar(innerIR.width() - smallW); |
| const SkScalar dy = SkIntToScalar(innerIR.height() - smallH); |
| if (dx < 0 || dy < 0) { |
| // we're too small, relative to our blur, to break into nine-patch, |
| // so we ask to have our normal filterMask() be called. |
| return kUnimplemented_FilterReturn; |
| } |
| |
| smallR[0].setLTRB(rects[0].left(), rects[0].top(), |
| rects[0].right() - dx, rects[0].bottom() - dy); |
| if (smallR[0].width() < 2 || smallR[0].height() < 2) { |
| return kUnimplemented_FilterReturn; |
| } |
| if (2 == count) { |
| smallR[1].setLTRB(rects[1].left(), rects[1].top(), |
| rects[1].right() - dx, rects[1].bottom() - dy); |
| SkASSERT(!smallR[1].isEmpty()); |
| } |
| |
| const SkScalar sigma = this->computeXformedSigma(matrix); |
| SkCachedData* cache = find_cached_rects(&patch->fMask, sigma, fBlurStyle, smallR, count); |
| if (!cache) { |
| if (count > 1 || !c_analyticBlurNinepatch) { |
| if (!draw_rects_into_mask(smallR, count, &srcM)) { |
| return kFalse_FilterReturn; |
| } |
| |
| SkAutoMaskFreeImage amf(srcM.fImage); |
| |
| if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) { |
| return kFalse_FilterReturn; |
| } |
| } else { |
| if (!this->filterRectMask(&patch->fMask, smallR[0], matrix, &margin, |
| SkMask::kComputeBoundsAndRenderImage_CreateMode)) { |
| return kFalse_FilterReturn; |
| } |
| } |
| cache = add_cached_rects(&patch->fMask, sigma, fBlurStyle, smallR, count); |
| } |
| patch->fMask.fBounds.offsetTo(0, 0); |
| patch->fOuterRect = dstM.fBounds; |
| patch->fCenter = center; |
| SkASSERT(nullptr == patch->fCache); |
| patch->fCache = cache; // transfer ownership to patch |
| return kTrue_FilterReturn; |
| } |
| |
| void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src, |
| SkRect* dst) const { |
| // TODO: if we're doing kInner blur, should we return a different outset? |
| // i.e. pad == 0 ? |
| |
| SkScalar pad = 3.0f * fSigma; |
| |
| dst->setLTRB(src.fLeft - pad, src.fTop - pad, |
| src.fRight + pad, src.fBottom + pad); |
| } |
| |
| sk_sp<SkFlattenable> SkBlurMaskFilterImpl::CreateProc(SkReadBuffer& buffer) { |
| const SkScalar sigma = buffer.readScalar(); |
| SkBlurStyle style = buffer.read32LE(kLastEnum_SkBlurStyle); |
| |
| uint32_t flags = buffer.read32LE(0x3); // historically we only recorded 2 bits |
| bool respectCTM = !(flags & 1); // historically we stored ignoreCTM in low bit |
| |
| return SkMaskFilter::MakeBlur((SkBlurStyle)style, sigma, respectCTM); |
| } |
| |
| void SkBlurMaskFilterImpl::flatten(SkWriteBuffer& buffer) const { |
| buffer.writeScalar(fSigma); |
| buffer.writeUInt(fBlurStyle); |
| buffer.writeUInt(!fRespectCTM); // historically we recorded ignoreCTM |
| } |
| |
| void sk_register_blur_maskfilter_createproc() { SK_REGISTER_FLATTENABLE(SkBlurMaskFilterImpl); } |
| |
| sk_sp<SkMaskFilter> SkMaskFilter::MakeBlur(SkBlurStyle style, SkScalar sigma, bool respectCTM) { |
| if (SkScalarIsFinite(sigma) && sigma > 0) { |
| return sk_sp<SkMaskFilter>(new SkBlurMaskFilterImpl(sigma, style, respectCTM)); |
| } |
| return nullptr; |
| } |