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:
Mike Reed 2018-01-26 11:42:38 -05:00 committed by Skia Commit-Bot
parent f9a5e62981
commit 1bd556a177
5 changed files with 172 additions and 82 deletions

View File

@ -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();
}

View 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

View File

@ -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)

View 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

View File

@ -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) {