use coverage modes instead of blend modes
Bug: skia: Change-Id: Ib3aa0137644358173ea4087693f33dbc2118c6d2 Reviewed-on: https://skia-review.googlesource.com/99661 Commit-Queue: Mike Reed <reed@google.com> Reviewed-by: Brian Salomon <bsalomon@google.com>
This commit is contained in:
parent
f9a5e62981
commit
1bd556a177
@ -66,36 +66,9 @@ DEF_SIMPLE_GM(shadermaskfilter_image, canvas, 512, 512) {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
enum class SkAlphaBlendMode {
|
||||
kClear, // 0
|
||||
kSrc, // src
|
||||
kDst, // dst
|
||||
kOver, // src + dst - src*dst
|
||||
kIn, // src * dst
|
||||
kSrcOut, // src * (1 - dst)
|
||||
kDstOut, // dst * (1 - src)
|
||||
kXor, // src + dst - 2*src*dst
|
||||
kPlus, // src + dst
|
||||
|
||||
kLast = kPlus,
|
||||
};
|
||||
|
||||
const SkBlendMode gAlphaToBlendMode[] = {
|
||||
SkBlendMode::kClear, // SkAlphaBlendMode::kClear
|
||||
SkBlendMode::kSrc, // SkAlphaBlendMode::kSrc
|
||||
SkBlendMode::kDst, // SkAlphaBlendMode::kDst
|
||||
SkBlendMode::kSrcOver, // SkAlphaBlendMode::kOver
|
||||
SkBlendMode::kSrcIn, // SkAlphaBlendMode::kIn
|
||||
SkBlendMode::kSrcOut, // SkAlphaBlendMode::kSrcOut
|
||||
SkBlendMode::kDstOut, // SkAlphaBlendMode::kDstOut
|
||||
SkBlendMode::kXor, // SkAlphaBlendMode::kXor
|
||||
SkBlendMode::kPlus, // SkAlphaBlendMode::kPlus
|
||||
};
|
||||
}
|
||||
|
||||
#include "SkPictureRecorder.h"
|
||||
#include "SkPath.h"
|
||||
|
||||
static sk_sp<SkMaskFilter> make_path_mf(const SkPath& path, unsigned alpha) {
|
||||
SkPaint paint;
|
||||
paint.setAntiAlias(true);
|
||||
@ -109,38 +82,64 @@ static sk_sp<SkMaskFilter> make_path_mf(const SkPath& path, unsigned alpha) {
|
||||
return SkShaderMaskFilter::Make(shader);
|
||||
}
|
||||
|
||||
DEF_SIMPLE_GM(combinemaskfilter, canvas, 340, 340) {
|
||||
typedef void (*MakePathsProc)(const SkRect&, SkPath*, SkPath*);
|
||||
|
||||
const char* gCoverageName[] = {
|
||||
"union", "sect", "diff", "rev-diff", "xor"
|
||||
};
|
||||
|
||||
DEF_SIMPLE_GM(combinemaskfilter, canvas, 565, 250) {
|
||||
const SkRect r = { 0, 0, 100, 100 };
|
||||
|
||||
SkPaint paint;
|
||||
paint.setColor(SK_ColorRED);
|
||||
|
||||
SkPaint labelP;
|
||||
labelP.setAntiAlias(true);
|
||||
labelP.setTextSize(20);
|
||||
labelP.setTextAlign(SkPaint::kCenter_Align);
|
||||
|
||||
const SkRect r2 = r.makeOutset(1.5f, 1.5f);
|
||||
SkPaint paint2;
|
||||
paint2.setStyle(SkPaint::kStroke_Style);
|
||||
|
||||
SkPath pathA;
|
||||
pathA.moveTo(r.fLeft, r.fBottom);
|
||||
pathA.lineTo(r.fRight, r.fTop);
|
||||
pathA.lineTo(r.fRight, r.fBottom);
|
||||
auto mfA = make_path_mf(pathA, 1 * 0xFF / 3);
|
||||
auto proc0 = [](const SkRect& r, SkPath* pathA, SkPath* pathB) {
|
||||
pathA->moveTo(r.fLeft, r.fBottom);
|
||||
pathA->lineTo(r.fRight, r.fTop);
|
||||
pathA->lineTo(r.fRight, r.fBottom);
|
||||
pathB->moveTo(r.fLeft, r.fTop);
|
||||
pathB->lineTo(r.fRight, r.fBottom);
|
||||
pathB->lineTo(r.fLeft, r.fBottom);
|
||||
};
|
||||
auto proc1 = [](const SkRect& r, SkPath* pathA, SkPath* pathB) {
|
||||
pathA->addCircle(r.width()*0.25f, r.height()*0.25f, r.width()*0.5f);
|
||||
pathB->addCircle(r.width()*0.75f, r.height()*0.75f, r.width()*0.5f);
|
||||
};
|
||||
MakePathsProc procs[] = { proc0, proc1 };
|
||||
|
||||
SkPath pathB;
|
||||
pathB.moveTo(r.fLeft, r.fTop);
|
||||
pathB.lineTo(r.fRight, r.fBottom);
|
||||
pathB.lineTo(r.fLeft, r.fBottom);
|
||||
auto mfB = make_path_mf(pathB, 2 * 0xFF / 3);
|
||||
sk_sp<SkMaskFilter> mfA[2], mfB[2];
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
SkPath a, b;
|
||||
procs[i](r, &a, &b);
|
||||
mfA[i] = make_path_mf(a, 1 * 0xFF / 3);
|
||||
mfB[i] = make_path_mf(b, 2 * 0xFF / 3);
|
||||
}
|
||||
|
||||
canvas->translate(10, 10);
|
||||
canvas->translate(10, 10 + 20);
|
||||
canvas->save();
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
SkPaint paint;
|
||||
paint.setMaskFilter(SkMaskFilter::MakeCombine(mfA, mfB, gAlphaToBlendMode[i]));
|
||||
canvas->drawRect(r2, paint2);
|
||||
canvas->drawRect(r, paint);
|
||||
canvas->translate(r.width() + 10, 0);
|
||||
if ((i % 3) == 2) {
|
||||
canvas->restore();
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
canvas->drawText(gCoverageName[i], strlen(gCoverageName[i]), r.width()*0.5f, -10, labelP);
|
||||
|
||||
SkCoverageMode mode = static_cast<SkCoverageMode>(i);
|
||||
canvas->save();
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
paint.setMaskFilter(SkMaskFilter::MakeCombine(mfA[j], mfB[j], mode));
|
||||
canvas->drawRect(r2, paint2);
|
||||
canvas->drawRect(r, paint);
|
||||
canvas->translate(0, r.height() + 10);
|
||||
canvas->save();
|
||||
}
|
||||
canvas->restore();
|
||||
canvas->translate(r.width() + 10, 0);
|
||||
}
|
||||
canvas->restore();
|
||||
}
|
||||
|
30
include/core/SkCoverageMode.h
Normal file
30
include/core/SkCoverageMode.h
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef SkCoverageMode_DEFINED
|
||||
#define SkCoverageMode_DEFINED
|
||||
|
||||
#include "SkTypes.h"
|
||||
|
||||
/**
|
||||
* Describes geometric operations (ala SkRegion::Op) that can be applied to coverage bytes.
|
||||
* These can be thought of as variants of porter-duff (SkBlendMode) modes, but only applied
|
||||
* to the alpha channel.
|
||||
*
|
||||
* See SkMaskFilter for ways to use these when combining two different masks.
|
||||
*/
|
||||
enum class SkCoverageMode {
|
||||
kUnion, // A ∪ B A+B-A*B
|
||||
kIntersect, // A ∪ B A*B
|
||||
kDifference, // A - B A*(1-B)
|
||||
kReverseDifference, // B - A B*(1-A)
|
||||
kXor, // A ⊕ B A+B-2*A*B
|
||||
|
||||
kLast = kXor
|
||||
};
|
||||
|
||||
#endif
|
@ -8,7 +8,7 @@
|
||||
#ifndef SkMaskFilter_DEFINED
|
||||
#define SkMaskFilter_DEFINED
|
||||
|
||||
#include "SkBlendMode.h"
|
||||
#include "SkCoverageMode.h"
|
||||
#include "SkFlattenable.h"
|
||||
|
||||
class SkString;
|
||||
@ -27,10 +27,10 @@ public:
|
||||
static sk_sp<SkMaskFilter> MakeCompose(sk_sp<SkMaskFilter> outer, sk_sp<SkMaskFilter> inner);
|
||||
|
||||
/**
|
||||
* Compose two maskfilters together using a blendmode. Returns nullptr on failure.
|
||||
* Compose two maskfilters together using a coverage mode. Returns nullptr on failure.
|
||||
*/
|
||||
static sk_sp<SkMaskFilter> MakeCombine(sk_sp<SkMaskFilter> dst, sk_sp<SkMaskFilter> src,
|
||||
SkBlendMode);
|
||||
static sk_sp<SkMaskFilter> MakeCombine(sk_sp<SkMaskFilter> filterA, sk_sp<SkMaskFilter> filterB,
|
||||
SkCoverageMode mode);
|
||||
|
||||
SK_TO_STRING_PUREVIRT()
|
||||
SK_DEFINE_FLATTENABLE_TYPE(SkMaskFilter)
|
||||
|
49
src/core/SkCoverageModePriv.h
Normal file
49
src/core/SkCoverageModePriv.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef SkCoverageModePriv_DEFINED
|
||||
#define SkCoverageModePriv_DEFINED
|
||||
|
||||
#include "SkBlendMode.h"
|
||||
#include "SkCoverageMode.h"
|
||||
|
||||
const SkBlendMode gUncorrelatedCoverageToBlend[] = {
|
||||
SkBlendMode::kSrcOver, // or DstOver
|
||||
SkBlendMode::kSrcIn, // or kDstIn
|
||||
SkBlendMode::kSrcOut,
|
||||
SkBlendMode::kDstOut,
|
||||
SkBlendMode::kXor,
|
||||
};
|
||||
|
||||
#if 0
|
||||
// Experimental idea to extend to overlap types
|
||||
|
||||
Master calculation = X(S,D) + Y(S,D) + Z(S,D)
|
||||
|
||||
enum class SkCoverageOverlap {
|
||||
// X Y Z
|
||||
kUncorrelated, // S*D S*(1-D) D*(1-S)
|
||||
kConjoint, // min(S,D) max(S-D,0) max(D-S,0)
|
||||
kDisjoint, // max(S+D-1,0) min(S,1-D) min(D,1-S)
|
||||
|
||||
kLast = kDisjoint
|
||||
};
|
||||
|
||||
// The coverage modes each have a set of coefficients to be applied to the general equation (above)
|
||||
//
|
||||
// e.g.
|
||||
// kXor+conjoint = max(S-D,0) + max(D-S,0) ==> abs(D-S)
|
||||
//
|
||||
kUnion, // 1,1,1
|
||||
kIntersect, // 1,0,0
|
||||
kDifference, // 0,1,0
|
||||
kReverseDifference, // 0,0,1
|
||||
kXor, // 0,1,1
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
@ -10,6 +10,7 @@
|
||||
#include "SkAutoMalloc.h"
|
||||
#include "SkBlitter.h"
|
||||
#include "SkCachedData.h"
|
||||
#include "SkCoverageModePriv.h"
|
||||
#include "SkDraw.h"
|
||||
#include "SkPath.h"
|
||||
#include "SkRRect.h"
|
||||
@ -384,6 +385,10 @@ template <typename T> static inline T join(const T& a, const T& b) {
|
||||
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:
|
||||
@ -461,8 +466,7 @@ void SkComposeMF::toString(SkString* str) const {
|
||||
|
||||
class SkCombineMF : public SkMaskFilterBase {
|
||||
public:
|
||||
|
||||
SkCombineMF(sk_sp<SkMaskFilter> dst, sk_sp<SkMaskFilter> src, SkBlendMode mode)
|
||||
SkCombineMF(sk_sp<SkMaskFilter> dst, sk_sp<SkMaskFilter> src, SkCoverageMode mode)
|
||||
: fDst(std::move(dst))
|
||||
, fSrc(std::move(src))
|
||||
, fMode(mode)
|
||||
@ -481,13 +485,14 @@ public:
|
||||
}
|
||||
|
||||
SkMask::Format getFormat() const override { return SkMask::kA8_Format; }
|
||||
|
||||
SK_TO_STRING_OVERRIDE()
|
||||
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkCombineMF)
|
||||
|
||||
private:
|
||||
sk_sp<SkMaskFilter> fDst;
|
||||
sk_sp<SkMaskFilter> fSrc;
|
||||
const SkBlendMode fMode;
|
||||
SkCoverageMode fMode;
|
||||
|
||||
void flatten(SkWriteBuffer&) const override;
|
||||
|
||||
@ -499,11 +504,8 @@ private:
|
||||
#include "SkSafeMath.h"
|
||||
|
||||
class DrawIntoMask : public SkDraw {
|
||||
SkMatrix fMatrixStorage;
|
||||
SkRasterClip fRCStorage;
|
||||
SkIPoint fOffset;
|
||||
|
||||
public:
|
||||
// we ignore the offset of the mask->fBounds
|
||||
DrawIntoMask(SkMask* mask) {
|
||||
int w = mask->fBounds.width();
|
||||
int h = mask->fBounds.height();
|
||||
@ -519,17 +521,31 @@ public:
|
||||
|
||||
fRCStorage.setRect({ 0, 0, w, h });
|
||||
fRC = &fRCStorage;
|
||||
|
||||
fOffset.set(mask->fBounds.left(), mask->fBounds.top());
|
||||
}
|
||||
|
||||
void drawMaskAsImage(const SkMask& m, const SkPaint& p) {
|
||||
SkBitmap bm;
|
||||
bm.installMaskPixels(m);
|
||||
this->drawSprite(bm, m.fBounds.left() - fOffset.x(), m.fBounds.top() - fOffset.y(), p);
|
||||
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;
|
||||
@ -542,7 +558,7 @@ bool SkCombineMF::filterMask(SkMask* dst, const SkMask& src, const SkMatrix& ctm
|
||||
return false;
|
||||
}
|
||||
|
||||
dst->fBounds = join(srcM.fBounds, dstM.fBounds);
|
||||
dst->fBounds = join(srcM.fBounds, dstM.fBounds, fMode);
|
||||
dst->fFormat = SkMask::kA8_Format;
|
||||
if (src.fImage == nullptr) {
|
||||
dst->fImage = nullptr;
|
||||
@ -553,9 +569,11 @@ bool SkCombineMF::filterMask(SkMask* dst, const SkMask& src, const SkMatrix& ctm
|
||||
SkPaint p;
|
||||
|
||||
p.setBlendMode(SkBlendMode::kSrc);
|
||||
md.drawMaskAsImage(dstM, p);
|
||||
p.setBlendMode(fMode);
|
||||
md.drawMaskAsImage(srcM, p);
|
||||
dstM.fBounds.offset(-dst->fBounds.fLeft, -dst->fBounds.fTop);
|
||||
md.drawAsBitmap(dstM, p);
|
||||
p.setBlendMode(gUncorrelatedCoverageToBlend[static_cast<int>(fMode)]);
|
||||
srcM.fBounds.offset(-dst->fBounds.fLeft, -dst->fBounds.fTop);
|
||||
md.drawAsBitmap(srcM, p);
|
||||
|
||||
sk_free(srcM.fImage);
|
||||
sk_free(dstM.fImage);
|
||||
@ -572,7 +590,7 @@ sk_sp<SkFlattenable> SkCombineMF::CreateProc(SkReadBuffer& buffer) {
|
||||
SkSafeRange safe;
|
||||
auto dst = buffer.readMaskFilter();
|
||||
auto src = buffer.readMaskFilter();
|
||||
SkBlendMode mode = safe.checkLE(buffer.read32(), SkBlendMode::kLastMode);
|
||||
SkCoverageMode mode = safe.checkLE(buffer.read32(), SkCoverageMode::kLast);
|
||||
if (!buffer.validate(dst && src && safe)) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -587,7 +605,8 @@ void SkCombineMF::toString(SkString* str) const {
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
sk_sp<SkMaskFilter> SkMaskFilter::MakeCompose(sk_sp<SkMaskFilter> outer, sk_sp<SkMaskFilter> inner) {
|
||||
sk_sp<SkMaskFilter> SkMaskFilter::MakeCompose(sk_sp<SkMaskFilter> outer,
|
||||
sk_sp<SkMaskFilter> inner) {
|
||||
if (!outer) {
|
||||
return inner;
|
||||
}
|
||||
@ -602,20 +621,13 @@ sk_sp<SkMaskFilter> SkMaskFilter::MakeCompose(sk_sp<SkMaskFilter> outer, sk_sp<S
|
||||
}
|
||||
|
||||
sk_sp<SkMaskFilter> SkMaskFilter::MakeCombine(sk_sp<SkMaskFilter> dst, sk_sp<SkMaskFilter> src,
|
||||
SkBlendMode mode) {
|
||||
if (mode == SkBlendMode::kClear) {
|
||||
// return sk_sp<SkMaskFilter>(new ClearMF);
|
||||
}
|
||||
if (!dst || mode == SkBlendMode::kSrc || mode == SkBlendMode::kDstATop) {
|
||||
SkCoverageMode mode) {
|
||||
if (!dst) {
|
||||
return src;
|
||||
}
|
||||
if (!src || mode == SkBlendMode::kDst || mode == SkBlendMode::kSrcATop) {
|
||||
if (!src) {
|
||||
return dst;
|
||||
}
|
||||
// This step isn't really needed, but it documents that we don't need any modes after kModulate
|
||||
if (mode > SkBlendMode::kModulate) {
|
||||
mode = SkBlendMode::kSrcOver;
|
||||
}
|
||||
|
||||
if (as_MFB(dst)->getFormat() != SkMask::kA8_Format ||
|
||||
as_MFB(src)->getFormat() != SkMask::kA8_Format) {
|
||||
|
Loading…
Reference in New Issue
Block a user