blob: 573fe3cabb81354e50dd8990af4fb0128a2cbb43 [file] [log] [blame]
/*
* 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/SkMaskFilterBase.h"
#include "include/core/SkPath.h"
#include "include/core/SkRRect.h"
#include "src/core/SkAutoMalloc.h"
#include "src/core/SkBlitter.h"
#include "src/core/SkBlurPriv.h"
#include "src/core/SkCachedData.h"
#include "src/core/SkCoverageModePriv.h"
#include "src/core/SkDraw.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkRasterClip.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkWriteBuffer.h"
#if SK_SUPPORT_GPU
#include "src/gpu/GrFragmentProcessor.h"
#include "src/gpu/GrTextureProxy.h"
#include "src/gpu/effects/GrXfermodeFragmentProcessor.h"
#include "src/gpu/text/GrSDFMaskFilter.h"
#endif
SkMaskFilterBase::NinePatch::~NinePatch() {
if (fCache) {
SkASSERT((const void*)fMask.fImage == fCache->data());
fCache->unref();
} else {
SkMask::FreeImage(fMask.fImage);
}
}
bool SkMaskFilterBase::asABlur(BlurRec*) const {
return false;
}
static void extractMaskSubset(const SkMask& src, SkMask* dst) {
SkASSERT(src.fBounds.contains(dst->fBounds));
const int dx = dst->fBounds.left() - src.fBounds.left();
const int dy = dst->fBounds.top() - src.fBounds.top();
dst->fImage = src.fImage + dy * src.fRowBytes + dx;
dst->fRowBytes = src.fRowBytes;
dst->fFormat = src.fFormat;
}
static void blitClippedMask(SkBlitter* blitter, const SkMask& mask,
const SkIRect& bounds, const SkIRect& clipR) {
SkIRect r;
if (r.intersect(bounds, clipR)) {
blitter->blitMask(mask, r);
}
}
static void blitClippedRect(SkBlitter* blitter, const SkIRect& rect, const SkIRect& clipR) {
SkIRect r;
if (r.intersect(rect, clipR)) {
blitter->blitRect(r.left(), r.top(), r.width(), r.height());
}
}
#if 0
static void dump(const SkMask& mask) {
for (int y = mask.fBounds.top(); y < mask.fBounds.bottom(); ++y) {
for (int x = mask.fBounds.left(); x < mask.fBounds.right(); ++x) {
SkDebugf("%02X", *mask.getAddr8(x, y));
}
SkDebugf("\n");
}
SkDebugf("\n");
}
#endif
static void draw_nine_clipped(const SkMask& mask, const SkIRect& outerR,
const SkIPoint& center, bool fillCenter,
const SkIRect& clipR, SkBlitter* blitter) {
int cx = center.x();
int cy = center.y();
SkMask m;
// top-left
m.fBounds = mask.fBounds;
m.fBounds.fRight = cx;
m.fBounds.fBottom = cy;
if (m.fBounds.width() > 0 && m.fBounds.height() > 0) {
extractMaskSubset(mask, &m);
m.fBounds.offsetTo(outerR.left(), outerR.top());
blitClippedMask(blitter, m, m.fBounds, clipR);
}
// top-right
m.fBounds = mask.fBounds;
m.fBounds.fLeft = cx + 1;
m.fBounds.fBottom = cy;
if (m.fBounds.width() > 0 && m.fBounds.height() > 0) {
extractMaskSubset(mask, &m);
m.fBounds.offsetTo(outerR.right() - m.fBounds.width(), outerR.top());
blitClippedMask(blitter, m, m.fBounds, clipR);
}
// bottom-left
m.fBounds = mask.fBounds;
m.fBounds.fRight = cx;
m.fBounds.fTop = cy + 1;
if (m.fBounds.width() > 0 && m.fBounds.height() > 0) {
extractMaskSubset(mask, &m);
m.fBounds.offsetTo(outerR.left(), outerR.bottom() - m.fBounds.height());
blitClippedMask(blitter, m, m.fBounds, clipR);
}
// bottom-right
m.fBounds = mask.fBounds;
m.fBounds.fLeft = cx + 1;
m.fBounds.fTop = cy + 1;
if (m.fBounds.width() > 0 && m.fBounds.height() > 0) {
extractMaskSubset(mask, &m);
m.fBounds.offsetTo(outerR.right() - m.fBounds.width(),
outerR.bottom() - m.fBounds.height());
blitClippedMask(blitter, m, m.fBounds, clipR);
}
SkIRect innerR;
innerR.setLTRB(outerR.left() + cx - mask.fBounds.left(),
outerR.top() + cy - mask.fBounds.top(),
outerR.right() + (cx + 1 - mask.fBounds.right()),
outerR.bottom() + (cy + 1 - mask.fBounds.bottom()));
if (fillCenter) {
blitClippedRect(blitter, innerR, clipR);
}
const int innerW = innerR.width();
size_t storageSize = (innerW + 1) * (sizeof(int16_t) + sizeof(uint8_t));
SkAutoSMalloc<4*1024> storage(storageSize);
int16_t* runs = (int16_t*)storage.get();
uint8_t* alpha = (uint8_t*)(runs + innerW + 1);
SkIRect r;
// top
r.setLTRB(innerR.left(), outerR.top(), innerR.right(), innerR.top());
if (r.intersect(clipR)) {
int startY = SkMax32(0, r.top() - outerR.top());
int stopY = startY + r.height();
int width = r.width();
for (int y = startY; y < stopY; ++y) {
runs[0] = width;
runs[width] = 0;
alpha[0] = *mask.getAddr8(cx, mask.fBounds.top() + y);
blitter->blitAntiH(r.left(), outerR.top() + y, alpha, runs);
}
}
// bottom
r.setLTRB(innerR.left(), innerR.bottom(), innerR.right(), outerR.bottom());
if (r.intersect(clipR)) {
int startY = outerR.bottom() - r.bottom();
int stopY = startY + r.height();
int width = r.width();
for (int y = startY; y < stopY; ++y) {
runs[0] = width;
runs[width] = 0;
alpha[0] = *mask.getAddr8(cx, mask.fBounds.bottom() - y - 1);
blitter->blitAntiH(r.left(), outerR.bottom() - y - 1, alpha, runs);
}
}
// left
r.setLTRB(outerR.left(), innerR.top(), innerR.left(), innerR.bottom());
if (r.intersect(clipR)) {
SkMask m;
m.fImage = mask.getAddr8(mask.fBounds.left() + r.left() - outerR.left(),
mask.fBounds.top() + cy);
m.fBounds = r;
m.fRowBytes = 0; // so we repeat the scanline for our height
m.fFormat = SkMask::kA8_Format;
blitter->blitMask(m, r);
}
// right
r.setLTRB(innerR.right(), innerR.top(), outerR.right(), innerR.bottom());
if (r.intersect(clipR)) {
SkMask m;
m.fImage = mask.getAddr8(mask.fBounds.right() - outerR.right() + r.left(),
mask.fBounds.top() + cy);
m.fBounds = r;
m.fRowBytes = 0; // so we repeat the scanline for our height
m.fFormat = SkMask::kA8_Format;
blitter->blitMask(m, r);
}
}
static void draw_nine(const SkMask& mask, const SkIRect& outerR, const SkIPoint& center,
bool fillCenter, const SkRasterClip& clip, SkBlitter* blitter) {
// if we get here, we need to (possibly) resolve the clip and blitter
SkAAClipBlitterWrapper wrapper(clip, blitter);
blitter = wrapper.getBlitter();
SkRegion::Cliperator clipper(wrapper.getRgn(), outerR);
if (!clipper.done()) {
const SkIRect& cr = clipper.rect();
do {
draw_nine_clipped(mask, outerR, center, fillCenter, cr, blitter);
clipper.next();
} while (!clipper.done());
}
}
static int countNestedRects(const SkPath& path, SkRect rects[2]) {
if (SkPathPriv::IsNestedFillRects(path, rects)) {
return 2;
}
return path.isRect(&rects[0]);
}
bool SkMaskFilterBase::filterRRect(const SkRRect& devRRect, const SkMatrix& matrix,
const SkRasterClip& clip, SkBlitter* blitter) const {
// Attempt to speed up drawing by creating a nine patch. If a nine patch
// cannot be used, return false to allow our caller to recover and perform
// the drawing another way.
NinePatch patch;
patch.fMask.fImage = nullptr;
if (kTrue_FilterReturn != this->filterRRectToNine(devRRect, matrix,
clip.getBounds(),
&patch)) {
SkASSERT(nullptr == patch.fMask.fImage);
return false;
}
draw_nine(patch.fMask, patch.fOuterRect, patch.fCenter, true, clip, blitter);
return true;
}
bool SkMaskFilterBase::filterPath(const SkPath& devPath, const SkMatrix& matrix,
const SkRasterClip& clip, SkBlitter* blitter,
SkStrokeRec::InitStyle style) const {
SkRect rects[2];
int rectCount = 0;
if (SkStrokeRec::kFill_InitStyle == style) {
rectCount = countNestedRects(devPath, rects);
}
if (rectCount > 0) {
NinePatch patch;
switch (this->filterRectsToNine(rects, rectCount, matrix, clip.getBounds(), &patch)) {
case kFalse_FilterReturn:
SkASSERT(nullptr == patch.fMask.fImage);
return false;
case kTrue_FilterReturn:
draw_nine(patch.fMask, patch.fOuterRect, patch.fCenter, 1 == rectCount, clip,
blitter);
return true;
case kUnimplemented_FilterReturn:
SkASSERT(nullptr == patch.fMask.fImage);
// fall through
break;
}
}
SkMask srcM, dstM;
if (!SkDraw::DrawToMask(devPath, &clip.getBounds(), this, &matrix, &srcM,
SkMask::kComputeBoundsAndRenderImage_CreateMode,
style)) {
return false;
}
SkAutoMaskFreeImage autoSrc(srcM.fImage);
if (!this->filterMask(&dstM, srcM, matrix, nullptr)) {
return false;
}
SkAutoMaskFreeImage autoDst(dstM.fImage);
// if we get here, we need to (possibly) resolve the clip and blitter
SkAAClipBlitterWrapper wrapper(clip, blitter);
blitter = wrapper.getBlitter();
SkRegion::Cliperator clipper(wrapper.getRgn(), dstM.fBounds);
if (!clipper.done()) {
const SkIRect& cr = clipper.rect();
do {
blitter->blitMask(dstM, cr);
clipper.next();
} while (!clipper.done());
}
return true;
}
SkMaskFilterBase::FilterReturn
SkMaskFilterBase::filterRRectToNine(const SkRRect&, const SkMatrix&,
const SkIRect& clipBounds, NinePatch*) const {
return kUnimplemented_FilterReturn;
}
SkMaskFilterBase::FilterReturn
SkMaskFilterBase::filterRectsToNine(const SkRect[], int count, const SkMatrix&,
const SkIRect& clipBounds, NinePatch*) const {
return kUnimplemented_FilterReturn;
}
#if SK_SUPPORT_GPU
std::unique_ptr<GrFragmentProcessor>
SkMaskFilterBase::asFragmentProcessor(const GrFPArgs& args) const {
auto fp = this->onAsFragmentProcessor(args);
if (fp) {
SkASSERT(this->hasFragmentProcessor());
} else {
SkASSERT(!this->hasFragmentProcessor());
}
return fp;
}
bool SkMaskFilterBase::hasFragmentProcessor() const {
return this->onHasFragmentProcessor();
}
std::unique_ptr<GrFragmentProcessor>
SkMaskFilterBase::onAsFragmentProcessor(const GrFPArgs&) const {
return nullptr;
}
bool SkMaskFilterBase::onHasFragmentProcessor() const { return false; }
bool SkMaskFilterBase::canFilterMaskGPU(const GrShape& shape,
const SkIRect& devSpaceShapeBounds,
const SkIRect& clipBounds,
const SkMatrix& ctm,
SkIRect* maskRect) const {
return false;
}
bool SkMaskFilterBase::directFilterMaskGPU(GrRecordingContext*,
GrRenderTargetContext*,
GrPaint&&,
const GrClip&,
const SkMatrix& viewMatrix,
const GrShape&) const {
return false;
}
sk_sp<GrTextureProxy> SkMaskFilterBase::filterMaskGPU(GrRecordingContext*,
sk_sp<GrTextureProxy> srcProxy,
GrColorType srcColorType,
SkAlphaType srcAlphaType,
const SkMatrix& ctm,
const SkIRect& maskRect) const {
return nullptr;
}
#endif
void SkMaskFilterBase::computeFastBounds(const SkRect& src, SkRect* dst) const {
SkMask srcM, dstM;
srcM.fBounds = src.roundOut();
srcM.fRowBytes = 0;
srcM.fFormat = SkMask::kA8_Format;
SkIPoint margin; // ignored
if (this->filterMask(&dstM, srcM, SkMatrix::I(), &margin)) {
dst->set(dstM.fBounds);
} else {
dst->set(srcM.fBounds);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T> static inline T join(const T& a, const T& b) {
T r = a;
r.join(b);
return r;
}
template <typename T> static inline T sect(const T& a, const T& b) {
T r = a;
return r.intersect(b) ? r : T::MakeEmpty();
}
class SkComposeMF : public SkMaskFilterBase {
public:
SkComposeMF(sk_sp<SkMaskFilter> outer, sk_sp<SkMaskFilter> inner)
: fOuter(std::move(outer))
, fInner(std::move(inner))
{
SkASSERT(as_MFB(fOuter)->getFormat() == SkMask::kA8_Format);
SkASSERT(as_MFB(fInner)->getFormat() == SkMask::kA8_Format);
}
bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, SkIPoint*) const override;
void computeFastBounds(const SkRect& src, SkRect* dst) const override {
SkRect tmp;
as_MFB(fInner)->computeFastBounds(src, &tmp);
as_MFB(fOuter)->computeFastBounds(tmp, dst);
}
SkMask::Format getFormat() const override { return SkMask::kA8_Format; }
protected:
#if SK_SUPPORT_GPU
std::unique_ptr<GrFragmentProcessor> onAsFragmentProcessor(const GrFPArgs& args) const override{
std::unique_ptr<GrFragmentProcessor> array[2] = {
as_MFB(fInner)->asFragmentProcessor(args),
as_MFB(fOuter)->asFragmentProcessor(args),
};
if (!array[0] || !array[1]) {
return nullptr;
}
return GrFragmentProcessor::RunInSeries(array, 2);
}
bool onHasFragmentProcessor() const override {
return as_MFB(fInner)->hasFragmentProcessor() && as_MFB(fOuter)->hasFragmentProcessor();
}
#endif
private:
SK_FLATTENABLE_HOOKS(SkComposeMF)
sk_sp<SkMaskFilter> fOuter;
sk_sp<SkMaskFilter> fInner;
void flatten(SkWriteBuffer&) const override;
friend class SkMaskFilter;
typedef SkMaskFilterBase INHERITED;
};
bool SkComposeMF::filterMask(SkMask* dst, const SkMask& src, const SkMatrix& ctm,
SkIPoint* margin) const {
SkIPoint innerMargin;
SkMask innerMask;
if (!as_MFB(fInner)->filterMask(&innerMask, src, ctm, &innerMargin)) {
return false;
}
if (!as_MFB(fOuter)->filterMask(dst, innerMask, ctm, margin)) {
return false;
}
if (margin) {
margin->fX += innerMargin.fX;
margin->fY += innerMargin.fY;
}
sk_free(innerMask.fImage);
return true;
}
void SkComposeMF::flatten(SkWriteBuffer & buffer) const {
buffer.writeFlattenable(fOuter.get());
buffer.writeFlattenable(fInner.get());
}
sk_sp<SkFlattenable> SkComposeMF::CreateProc(SkReadBuffer& buffer) {
auto outer = buffer.readMaskFilter();
auto inner = buffer.readMaskFilter();
if (!buffer.validate(outer && inner)) {
return nullptr;
}
return SkMaskFilter::MakeCompose(std::move(outer), std::move(inner));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
class SkCombineMF : public SkMaskFilterBase {
public:
SkCombineMF(sk_sp<SkMaskFilter> dst, sk_sp<SkMaskFilter> src, SkCoverageMode mode)
: fDst(std::move(dst))
, fSrc(std::move(src))
, fMode(mode)
{
SkASSERT(as_MFB(fSrc)->getFormat() == SkMask::kA8_Format);
SkASSERT(as_MFB(fDst)->getFormat() == SkMask::kA8_Format);
}
bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&, SkIPoint*) const override;
void computeFastBounds(const SkRect& src, SkRect* dst) const override {
SkRect srcR, dstR;
as_MFB(fSrc)->computeFastBounds(src, &srcR);
as_MFB(fDst)->computeFastBounds(src, &dstR);
*dst = join(srcR, dstR);
}
SkMask::Format getFormat() const override { return SkMask::kA8_Format; }
SK_FLATTENABLE_HOOKS(SkCombineMF)
protected:
#if SK_SUPPORT_GPU
std::unique_ptr<GrFragmentProcessor> onAsFragmentProcessor(const GrFPArgs& args) const override{
auto src = as_MFB(fSrc)->asFragmentProcessor(args);
auto dst = as_MFB(fDst)->asFragmentProcessor(args);
if (!src || !dst) {
return nullptr;
}
return GrXfermodeFragmentProcessor::MakeFromTwoProcessors(std::move(src), std::move(dst),
SkUncorrelatedCoverageModeToBlendMode(fMode));
}
bool onHasFragmentProcessor() const override {
return as_MFB(fSrc)->hasFragmentProcessor() && as_MFB(fDst)->hasFragmentProcessor();
}
#endif
private:
sk_sp<SkMaskFilter> fDst;
sk_sp<SkMaskFilter> fSrc;
SkCoverageMode fMode;
void flatten(SkWriteBuffer&) const override;
friend class SkMaskFilter;
typedef SkMaskFilterBase INHERITED;
};
#include "src/core/SkSafeMath.h"
class DrawIntoMask : public SkDraw {
public:
// we ignore the offset of the mask->fBounds
DrawIntoMask(SkMask* mask) {
int w = mask->fBounds.width();
int h = mask->fBounds.height();
size_t size = SkSafeMath::Mul(w, h);
mask->fFormat = SkMask::kA8_Format;
mask->fImage = SkMask::AllocImage(size, SkMask::kZeroInit_Alloc);
mask->fRowBytes = w;
SkAssertResult(fDst.reset(*mask));
fMatrixStorage.reset();
fMatrix = &fMatrixStorage;
fRCStorage.setRect({ 0, 0, w, h });
fRC = &fRCStorage;
}
void drawAsBitmap(const SkMask& m, const SkPaint& p) {
SkBitmap b;
b.installMaskPixels(m);
this->drawSprite(b, m.fBounds.fLeft, m.fBounds.fTop, p);
}
private:
SkMatrix fMatrixStorage;
SkRasterClip fRCStorage;
};
static SkIRect join(const SkIRect& src, const SkIRect& dst, SkCoverageMode mode) {
switch (mode) {
case SkCoverageMode::kUnion: return join(src, dst);
case SkCoverageMode::kIntersect: return sect(src, dst);
case SkCoverageMode::kDifference: return src;
case SkCoverageMode::kReverseDifference: return dst;
case SkCoverageMode::kXor: return join(src, dst);
}
// not reached
return { 0, 0, 0, 0 };
}
bool SkCombineMF::filterMask(SkMask* dst, const SkMask& src, const SkMatrix& ctm,
SkIPoint* margin) const {
SkIPoint srcP, dstP;
SkMask srcM, dstM;
if (!as_MFB(fSrc)->filterMask(&srcM, src, ctm, &srcP)) {
return false;
}
if (!as_MFB(fDst)->filterMask(&dstM, src, ctm, &dstP)) {
return false;
}
dst->fBounds = join(srcM.fBounds, dstM.fBounds, fMode);
dst->fFormat = SkMask::kA8_Format;
if (src.fImage == nullptr) {
dst->fImage = nullptr;
return true;
}
DrawIntoMask md(dst);
SkPaint p;
p.setBlendMode(SkBlendMode::kSrc);
dstM.fBounds.offset(-dst->fBounds.fLeft, -dst->fBounds.fTop);
md.drawAsBitmap(dstM, p);
p.setBlendMode(SkUncorrelatedCoverageModeToBlendMode(fMode));
srcM.fBounds.offset(-dst->fBounds.fLeft, -dst->fBounds.fTop);
md.drawAsBitmap(srcM, p);
sk_free(srcM.fImage);
sk_free(dstM.fImage);
return true;
}
void SkCombineMF::flatten(SkWriteBuffer & buffer) const {
buffer.writeFlattenable(fDst.get());
buffer.writeFlattenable(fSrc.get());
buffer.write32(static_cast<uint32_t>(fMode));
}
sk_sp<SkFlattenable> SkCombineMF::CreateProc(SkReadBuffer& buffer) {
auto dst = buffer.readMaskFilter();
auto src = buffer.readMaskFilter();
SkCoverageMode mode = buffer.read32LE(SkCoverageMode::kLast);
if (!buffer.validate(dst && src)) {
return nullptr;
}
return SkMaskFilter::MakeCombine(std::move(dst), std::move(src), mode);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
class SkMatrixMF : public SkMaskFilterBase {
public:
SkMatrixMF(sk_sp<SkMaskFilter> filter, const SkMatrix& lm)
: fFilter(std::move(filter))
, fLM(lm)
{}
bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix& ctm,
SkIPoint* margin) const override {
return as_MFB(fFilter)->filterMask(dst, src, SkMatrix::Concat(ctm, fLM), margin);
}
void computeFastBounds(const SkRect& src, SkRect* dst) const override {
*dst = src;
SkRect tmp;
fLM.mapRect(&tmp, src);
as_MFB(fFilter)->computeFastBounds(tmp, dst);
}
SkMask::Format getFormat() const override { return as_MFB(fFilter)->getFormat(); }
SK_FLATTENABLE_HOOKS(SkMatrixMF)
protected:
#if SK_SUPPORT_GPU
std::unique_ptr<GrFragmentProcessor> onAsFragmentProcessor(const GrFPArgs& args) const override{
return as_MFB(fFilter)->asFragmentProcessor(GrFPArgs::WithPostLocalMatrix(args, fLM));
}
bool onHasFragmentProcessor() const override {
return as_MFB(fFilter)->hasFragmentProcessor();
}
#endif
private:
sk_sp<SkMaskFilter> fFilter;
const SkMatrix fLM;
void flatten(SkWriteBuffer& buffer) const override {
buffer.writeMatrix(fLM);
buffer.writeFlattenable(fFilter.get());
}
friend class SkMaskFilter;
typedef SkMaskFilterBase INHERITED;
};
sk_sp<SkFlattenable> SkMatrixMF::CreateProc(SkReadBuffer& buffer) {
SkMatrix m;
buffer.readMatrix(&m);
auto filter = buffer.readMaskFilter();
return filter ? filter->makeWithMatrix(m) : nullptr;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
sk_sp<SkMaskFilter> SkMaskFilter::MakeCompose(sk_sp<SkMaskFilter> outer,
sk_sp<SkMaskFilter> inner) {
if (!outer) {
return inner;
}
if (!inner) {
return outer;
}
if (as_MFB(inner)->getFormat() != SkMask::kA8_Format ||
as_MFB(outer)->getFormat() != SkMask::kA8_Format) {
return nullptr;
}
return sk_sp<SkMaskFilter>(new SkComposeMF(std::move(outer), std::move(inner)));
}
sk_sp<SkMaskFilter> SkMaskFilter::MakeCombine(sk_sp<SkMaskFilter> dst, sk_sp<SkMaskFilter> src,
SkCoverageMode mode) {
if (!dst) {
return src;
}
if (!src) {
return dst;
}
if (as_MFB(dst)->getFormat() != SkMask::kA8_Format ||
as_MFB(src)->getFormat() != SkMask::kA8_Format) {
return nullptr;
}
return sk_sp<SkMaskFilter>(new SkCombineMF(std::move(dst), std::move(src), mode));
}
sk_sp<SkMaskFilter> SkMaskFilter::makeWithMatrix(const SkMatrix& lm) const {
sk_sp<SkMaskFilter> me = sk_ref_sp(const_cast<SkMaskFilter*>(this));
if (lm.isIdentity()) {
return me;
}
return sk_sp<SkMaskFilter>(new SkMatrixMF(std::move(me), lm));
}
void SkMaskFilter::RegisterFlattenables() {
SK_REGISTER_FLATTENABLE(SkMatrixMF);
SK_REGISTER_FLATTENABLE(SkComposeMF);
SK_REGISTER_FLATTENABLE(SkCombineMF);
sk_register_blur_maskfilter_createproc();
#if SK_SUPPORT_GPU
gr_register_sdf_maskfilter_createproc();
#endif
}