blob: 13c72ca0f1eef8d7cc23f1988b97acb3f39cf87e [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SampleCode.h"
#include "SkView.h"
#include "SkCanvas.h"
#include "SkPaint.h"
#include "SkPath.h"
#include "SkMatrix.h"
#include "SkColor.h"
#include "SkTDArray.h"
#include "SkRandom.h"
#include "SkRRect.h"
enum RandomAddPath {
kMoveToPath,
kRMoveToPath,
kLineToPath,
kRLineToPath,
kQuadToPath,
kRQuadToPath,
kConicToPath,
kRConicToPath,
kCubicToPath,
kRCubicToPath,
kArcToPath,
kArcTo2Path,
kClosePath,
kAddArc,
kAddRoundRect1,
kAddRoundRect2,
kAddRRect,
kAddPoly,
kAddPath1,
kAddPath2,
kAddPath3,
kReverseAddPath,
};
const int kRandomAddPath_Last = kReverseAddPath;
const char* gRandomAddPathNames[] = {
"kMoveToPath",
"kRMoveToPath",
"kLineToPath",
"kRLineToPath",
"kQuadToPath",
"kRQuadToPath",
"kConicToPath",
"kRConicToPath",
"kCubicToPath",
"kRCubicToPath",
"kArcToPath",
"kArcTo2Path",
"kClosePath",
"kAddArc",
"kAddRoundRect1",
"kAddRoundRect2",
"kAddRRect",
"kAddPoly",
"kAddPath1",
"kAddPath2",
"kAddPath3",
"kReverseAddPath",
};
enum RandomSetRRect {
kSetEmpty,
kSetRect,
kSetOval,
kSetRectXY,
kSetNinePatch,
kSetRectRadii,
};
const char* gRandomSetRRectNames[] = {
"kSetEmpty",
"kSetRect",
"kSetOval",
"kSetRectXY",
"kSetNinePatch",
"kSetRectRadii",
};
int kRandomSetRRect_Last = kSetRectRadii;
enum RandomSetMatrix {
kSetIdentity,
kSetTranslate,
kSetTranslateX,
kSetTranslateY,
kSetScale,
kSetScaleTranslate,
kSetScaleX,
kSetScaleY,
kSetSkew,
kSetSkewTranslate,
kSetSkewX,
kSetSkewY,
kSetRotate,
kSetRotateTranslate,
kSetPerspectiveX,
kSetPerspectiveY,
kSetAll,
};
int kRandomSetMatrix_Last = kSetAll;
const char* gRandomSetMatrixNames[] = {
"kSetIdentity",
"kSetTranslate",
"kSetTranslateX",
"kSetTranslateY",
"kSetScale",
"kSetScaleTranslate",
"kSetScaleX",
"kSetScaleY",
"kSetSkew",
"kSetSkewTranslate",
"kSetSkewX",
"kSetSkewY",
"kSetRotate",
"kSetRotateTranslate",
"kSetPerspectiveX",
"kSetPerspectiveY",
"kSetAll",
};
class FuzzPath {
public:
FuzzPath()
: fFloatMin(0)
, fFloatMax(800)
, fAddCount(0)
, fPrintName(false)
, fStrokeOnly(false)
, fValidate(false)
{
fTab = " ";
}
void randomize() {
fPathDepth = 0;
fPathDepthLimit = fRand.nextRangeU(1, 2);
fPathContourCount = fRand.nextRangeU(1, 4);
fPathSegmentLimit = fRand.nextRangeU(1, 8);
fClip = makePath();
SkASSERT(!fPathDepth);
fMatrix = makeMatrix();
fPaint = makePaint();
fPathDepthLimit = fRand.nextRangeU(1, 3);
fPathContourCount = fRand.nextRangeU(1, 6);
fPathSegmentLimit = fRand.nextRangeU(1, 16);
fPath = makePath();
SkASSERT(!fPathDepth);
}
const SkPath& getClip() const {
return fClip;
}
const SkMatrix& getMatrix() const {
return fMatrix;
}
const SkPaint& getPaint() const {
return fPaint;
}
const SkPath& getPath() const {
return fPath;
}
void setSeed(int seed) {
fRand.setSeed(seed);
}
void setStrokeOnly() {
fStrokeOnly = true;
}
private:
SkPath::AddPathMode makeAddPathMode() {
return (SkPath::AddPathMode) fRand.nextRangeU(SkPath::kAppend_AddPathMode,
SkPath::kExtend_AddPathMode);
}
RandomAddPath makeAddPathType() {
return (RandomAddPath) fRand.nextRangeU(0, kRandomAddPath_Last);
}
SkScalar makeAngle() {
SkScalar angle;
angle = fRand.nextF();
return angle;
}
bool makeBool() {
return fRand.nextBool();
}
SkPath::Direction makeDirection() {
return (SkPath::Direction) fRand.nextRangeU(SkPath::kCW_Direction, SkPath::kCCW_Direction);
}
SkMatrix makeMatrix() {
SkMatrix matrix;
matrix.reset();
RandomSetMatrix setMatrix = (RandomSetMatrix) fRand.nextRangeU(0, kRandomSetMatrix_Last);
if (fPrintName) {
SkDebugf("%.*s%s\n", fPathDepth * 3, fTab, gRandomSetMatrixNames[setMatrix]);
}
switch (setMatrix) {
case kSetIdentity:
break;
case kSetTranslateX:
matrix.setTranslateX(makeScalar());
break;
case kSetTranslateY:
matrix.setTranslateY(makeScalar());
break;
case kSetTranslate:
matrix.setTranslate(makeScalar(), makeScalar());
break;
case kSetScaleX:
matrix.setScaleX(makeScalar());
break;
case kSetScaleY:
matrix.setScaleY(makeScalar());
break;
case kSetScale:
matrix.setScale(makeScalar(), makeScalar());
break;
case kSetScaleTranslate:
matrix.setScale(makeScalar(), makeScalar(), makeScalar(), makeScalar());
break;
case kSetSkewX:
matrix.setSkewX(makeScalar());
break;
case kSetSkewY:
matrix.setSkewY(makeScalar());
break;
case kSetSkew:
matrix.setSkew(makeScalar(), makeScalar());
break;
case kSetSkewTranslate:
matrix.setSkew(makeScalar(), makeScalar(), makeScalar(), makeScalar());
break;
case kSetRotate:
matrix.setRotate(makeScalar());
break;
case kSetRotateTranslate:
matrix.setRotate(makeScalar(), makeScalar(), makeScalar());
break;
case kSetPerspectiveX:
matrix.setPerspX(makeScalar());
break;
case kSetPerspectiveY:
matrix.setPerspY(makeScalar());
break;
case kSetAll:
matrix.setAll(makeScalar(), makeScalar(), makeScalar(),
makeScalar(), makeScalar(), makeScalar(),
makeScalar(), makeScalar(), makeScalar());
break;
}
return matrix;
}
SkPaint makePaint() {
SkPaint paint;
bool antiAlias = fRand.nextBool();
paint.setAntiAlias(antiAlias);
SkPaint::Style style = fStrokeOnly ? SkPaint::kStroke_Style :
(SkPaint::Style) fRand.nextRangeU(SkPaint::kFill_Style, SkPaint::kStrokeAndFill_Style);
paint.setStyle(style);
SkColor color = (SkColor) fRand.nextU();
paint.setColor(color);
SkScalar width = fRand.nextRangeF(0, 10);
paint.setStrokeWidth(width);
SkScalar miter = makeScalar();
paint.setStrokeMiter(miter);
SkPaint::Cap cap = (SkPaint::Cap) fRand.nextRangeU(SkPaint::kButt_Cap, SkPaint::kSquare_Cap);
paint.setStrokeCap(cap);
SkPaint::Join join = (SkPaint::Join) fRand.nextRangeU(SkPaint::kMiter_Join,
SkPaint::kBevel_Join);
paint.setStrokeJoin(join);
return paint;
}
SkPoint makePoint() {
SkPoint result;
makeScalarArray(2, &result.fX);
return result;
}
void makePointArray(size_t arrayCount, SkPoint* points) {
for (size_t index = 0; index < arrayCount; ++index) {
points[index] = makePoint();
}
}
void makePointArray(SkTDArray<SkPoint>* points) {
size_t arrayCount = fRand.nextRangeU(1, 10);
for (size_t index = 0; index < arrayCount; ++index) {
*points->append() = makePoint();
}
}
SkRect makeRect() {
SkRect result;
makeScalarArray(4, &result.fLeft);
return result;
}
SkRRect makeRRect() {
SkRRect rrect;
RandomSetRRect rrectType = makeSetRRectType();
if (fPrintName) {
SkDebugf("%.*s%s\n", fPathDepth * 3, fTab, gRandomSetRRectNames[rrectType]);
}
switch (rrectType) {
case kSetEmpty:
rrect.setEmpty();
break;
case kSetRect: {
SkRect rect = makeRect();
rrect.setRect(rect);
} break;
case kSetOval: {
SkRect oval = makeRect();
rrect.setOval(oval);
} break;
case kSetRectXY: {
SkRect rect = makeRect();
SkScalar xRad = makeScalar();
SkScalar yRad = makeScalar();
rrect.setRectXY(rect, xRad, yRad);
} break;
case kSetNinePatch: {
SkRect rect = makeRect();
SkScalar leftRad = makeScalar();
SkScalar topRad = makeScalar();
SkScalar rightRad = makeScalar();
SkScalar bottomRad = makeScalar();
rrect.setNinePatch(rect, leftRad, topRad, rightRad, bottomRad);
SkDebugf(""); // keep locals in scope
} break;
case kSetRectRadii: {
SkRect rect = makeRect();
SkVector radii[4];
makeVectorArray(SK_ARRAY_COUNT(radii), radii);
rrect.setRectRadii(rect, radii);
} break;
}
return rrect;
}
SkPath makePath() {
SkPath path;
for (uint32_t cIndex = 0; cIndex < fPathContourCount; ++cIndex) {
uint32_t segments = makeSegmentCount();
for (uint32_t sIndex = 0; sIndex < segments; ++sIndex) {
RandomAddPath addPathType = makeAddPathType();
++fAddCount;
if (fPrintName) {
SkDebugf("%.*s%s\n", fPathDepth * 3, fTab,
gRandomAddPathNames[addPathType]);
}
switch (addPathType) {
case kAddArc: {
SkRect oval = makeRect();
SkScalar startAngle = makeAngle();
SkScalar sweepAngle = makeAngle();
path.addArc(oval, startAngle, sweepAngle);
validate(path);
} break;
case kAddRoundRect1: {
SkRect rect = makeRect();
SkScalar rx = makeScalar(), ry = makeScalar();
SkPath::Direction dir = makeDirection();
path.addRoundRect(rect, rx, ry, dir);
validate(path);
} break;
case kAddRoundRect2: {
SkRect rect = makeRect();
SkScalar radii[8];
makeScalarArray(SK_ARRAY_COUNT(radii), radii);
SkPath::Direction dir = makeDirection();
path.addRoundRect(rect, radii, dir);
validate(path);
} break;
case kAddRRect: {
SkRRect rrect = makeRRect();
SkPath::Direction dir = makeDirection();
path.addRRect(rrect, dir);
validate(path);
} break;
case kAddPoly: {
SkTDArray<SkPoint> points;
makePointArray(&points);
bool close = makeBool();
path.addPoly(&points[0], points.count(), close);
validate(path);
} break;
case kAddPath1:
if (fPathDepth < fPathDepthLimit) {
++fPathDepth;
SkPath src = makePath();
validate(src);
SkScalar dx = makeScalar();
SkScalar dy = makeScalar();
SkPath::AddPathMode mode = makeAddPathMode();
path.addPath(src, dx, dy, mode);
--fPathDepth;
validate(path);
}
break;
case kAddPath2:
if (fPathDepth < fPathDepthLimit) {
++fPathDepth;
SkPath src = makePath();
validate(src);
SkPath::AddPathMode mode = makeAddPathMode();
path.addPath(src, mode);
--fPathDepth;
validate(path);
}
break;
case kAddPath3:
if (fPathDepth < fPathDepthLimit) {
++fPathDepth;
SkPath src = makePath();
validate(src);
SkMatrix matrix = makeMatrix();
SkPath::AddPathMode mode = makeAddPathMode();
path.addPath(src, matrix, mode);
--fPathDepth;
validate(path);
}
break;
case kReverseAddPath:
if (fPathDepth < fPathDepthLimit) {
++fPathDepth;
SkPath src = makePath();
validate(src);
path.reverseAddPath(src);
--fPathDepth;
validate(path);
}
break;
case kMoveToPath: {
SkScalar x = makeScalar();
SkScalar y = makeScalar();
path.moveTo(x, y);
validate(path);
} break;
case kRMoveToPath: {
SkScalar x = makeScalar();
SkScalar y = makeScalar();
path.rMoveTo(x, y);
validate(path);
} break;
case kLineToPath: {
SkScalar x = makeScalar();
SkScalar y = makeScalar();
path.lineTo(x, y);
validate(path);
} break;
case kRLineToPath: {
SkScalar x = makeScalar();
SkScalar y = makeScalar();
path.rLineTo(x, y);
validate(path);
} break;
case kQuadToPath: {
SkPoint pt[2];
makePointArray(SK_ARRAY_COUNT(pt), pt);
path.quadTo(pt[0], pt[1]);
validate(path);
} break;
case kRQuadToPath: {
SkPoint pt[2];
makePointArray(SK_ARRAY_COUNT(pt), pt);
path.rQuadTo(pt[0].fX, pt[0].fY, pt[1].fX, pt[1].fY);
validate(path);
} break;
case kConicToPath: {
SkPoint pt[2];
makePointArray(SK_ARRAY_COUNT(pt), pt);
SkScalar weight = makeScalar();
path.conicTo(pt[0], pt[1], weight);
validate(path);
} break;
case kRConicToPath: {
SkPoint pt[2];
makePointArray(SK_ARRAY_COUNT(pt), pt);
SkScalar weight = makeScalar();
path.rConicTo(pt[0].fX, pt[0].fY, pt[1].fX, pt[1].fY, weight);
validate(path);
} break;
case kCubicToPath: {
SkPoint pt[3];
makePointArray(SK_ARRAY_COUNT(pt), pt);
path.cubicTo(pt[0], pt[1], pt[2]);
validate(path);
} break;
case kRCubicToPath: {
SkPoint pt[3];
makePointArray(SK_ARRAY_COUNT(pt), pt);
path.rCubicTo(pt[0].fX, pt[0].fY, pt[1].fX, pt[1].fY, pt[2].fX, pt[2].fY);
validate(path);
} break;
case kArcToPath: {
SkPoint pt[2];
makePointArray(SK_ARRAY_COUNT(pt), pt);
SkScalar radius = makeScalar();
path.arcTo(pt[0], pt[1], radius);
validate(path);
} break;
case kArcTo2Path: {
SkRect oval = makeRect();
SkScalar startAngle = makeAngle();
SkScalar sweepAngle = makeAngle();
bool forceMoveTo = makeBool();
path.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
validate(path);
} break;
case kClosePath:
path.close();
validate(path);
break;
}
}
}
return path;
}
uint32_t makeSegmentCount() {
return fRand.nextRangeU(1, fPathSegmentLimit);
}
RandomSetRRect makeSetRRectType() {
return (RandomSetRRect) fRand.nextRangeU(0, kRandomSetRRect_Last);
}
SkScalar makeScalar() {
SkScalar scalar;
scalar = fRand.nextRangeF(fFloatMin, fFloatMax);
return scalar;
}
void makeScalarArray(size_t arrayCount, SkScalar* array) {
for (size_t index = 0; index < arrayCount; ++index) {
array[index] = makeScalar();
}
}
void makeVectorArray(size_t arrayCount, SkVector* array) {
for (size_t index = 0; index < arrayCount; ++index) {
array[index] = makeVector();
}
}
SkVector makeVector() {
SkVector result;
makeScalarArray(2, &result.fX);
return result;
}
void validate(const SkPath& path) {
if (fValidate) {
// FIXME: this could probably assert on path.isValid() instead
SkDEBUGCODE(path.validateRef());
}
}
SkRandom fRand;
SkMatrix fMatrix;
SkPath fClip;
SkPaint fPaint;
SkPath fPath;
SkScalar fFloatMin;
SkScalar fFloatMax;
uint32_t fPathContourCount;
int fPathDepth;
int fPathDepthLimit;
uint32_t fPathSegmentLimit;
int fAddCount;
bool fPrintName;
bool fStrokeOnly;
bool fValidate;
const char* fTab;
};
static bool contains_only_moveTo(const SkPath& path) {
int verbCount = path.countVerbs();
if (verbCount == 0) {
return true;
}
SkTDArray<uint8_t> verbs;
verbs.setCount(verbCount);
SkDEBUGCODE(int getVerbResult = ) path.getVerbs(verbs.begin(), verbCount);
SkASSERT(getVerbResult == verbCount);
for (int index = 0; index < verbCount; ++index) {
if (verbs[index] != SkPath::kMove_Verb) {
return false;
}
}
return true;
}
#include "SkGraphics.h"
#include "SkSurface.h"
#include "SkTaskGroup.h"
#include "SkTDArray.h"
static void path_fuzz_stroker(SkBitmap* bitmap, int seed) {
SkTaskGroup().batch(100, [&](int i) {
int localSeed = seed + i;
FuzzPath fuzzPath;
fuzzPath.setStrokeOnly();
fuzzPath.setSeed(localSeed);
fuzzPath.randomize();
const SkPath& path = fuzzPath.getPath();
const SkPaint& paint = fuzzPath.getPaint();
const SkImageInfo& info = bitmap->info();
std::unique_ptr<SkCanvas> canvas(
SkCanvas::MakeRasterDirect(info, bitmap->getPixels(), bitmap->rowBytes()));
int w = info.width() / 4;
int h = info.height() / 4;
int x = localSeed / 4 % 4;
int y = localSeed % 4;
SkRect clipBounds = SkRect::MakeXYWH(SkIntToScalar(x) * w, SkIntToScalar(y) * h,
SkIntToScalar(w), SkIntToScalar(h));
canvas->save();
canvas->clipRect(clipBounds);
canvas->translate(SkIntToScalar(x) * w, SkIntToScalar(y) * h);
canvas->drawPath(path, paint);
canvas->restore();
});
}
class PathFuzzView : public SampleView {
public:
PathFuzzView()
: fOneDraw(false)
{
}
protected:
// overrides from SkEventSink
bool onQuery(SkEvent* evt) override {
if (SampleCode::TitleQ(*evt)) {
SampleCode::TitleR(evt, "PathFuzzer");
return true;
}
return this->INHERITED::onQuery(evt);
}
void onOnceBeforeDraw() override {
fIndex = 0;
SkImageInfo info(SkImageInfo::MakeN32Premul(SkScalarRoundToInt(width()),
SkScalarRoundToInt(height())));
offscreen.allocPixels(info);
path_fuzz_stroker(&offscreen, fIndex);
}
void onDrawContent(SkCanvas* canvas) override {
if (fOneDraw) {
fuzzPath.randomize();
const SkPath& path = fuzzPath.getPath();
const SkPaint& paint = fuzzPath.getPaint();
const SkPath& clip = fuzzPath.getClip();
const SkMatrix& matrix = fuzzPath.getMatrix();
if (!contains_only_moveTo(clip)) {
canvas->clipPath(clip);
}
canvas->setMatrix(matrix);
canvas->drawPath(path, paint);
} else {
path_fuzz_stroker(&offscreen, fIndex += 100);
canvas->drawBitmap(offscreen, 0, 0);
}
}
private:
int fIndex;
SkBitmap offscreen;
FuzzPath fuzzPath;
bool fOneDraw;
typedef SkView INHERITED;
};
static SkView* MyFactory() { return new PathFuzzView; }
static SkViewRegister reg(MyFactory);