skia2/tests/PathOpsConicIntersectionTest.cpp
caryclark a35ab3e6e0 fix fuzzers
Many old pathops-related fuzz failures have built up while
the codebase was under a state a flux. Now that the code
is stable, address these failures.

Most of the CL plumbs the debug global state to downstream
routines so that, if the data is not trusted (ala fuzzed)
the function can safely exit without asserting.

TBR=reed@google.com
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2426173002

Review-Url: https://chromiumcodereview.appspot.com/2426173002
2016-10-20 08:32:18 -07:00

356 lines
14 KiB
C++

/*
* 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 "PathOpsTestCommon.h"
#include "SkGeometry.h"
#include "SkIntersections.h"
#include "Test.h"
/*
manually compute the intersection of a pair of circles and see if the conic intersection matches
given two circles
construct a line connecting their centers
*/
static const ConicPts testSet[] = {
{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
{{{{5.1114602088928223, 628.77813720703125},
{10.834027290344238, 988.964111328125},
{163.40835571289062, 988.964111328125}}}, 0.72944212f},
{{{{163.40835571289062, 988.964111328125},
{5, 988.964111328125},
{5, 614.7423095703125}}}, 0.707106769f},
{{{{11.17222976684570312, -8.103978157043457031},
{22.91432571411132812, -10.37866020202636719},
{23.7764129638671875, -7.725424289703369141}}}, 1.00862849f},
{{{{-1.545085430145263672, -4.755282402038574219},
{22.23132705688476562, -12.48070907592773438},
{23.7764129638671875, -7.725427150726318359}}}, 0.707106769f},
{{{{-4,1}, {-4,5}, {0,5}}}, 0.707106769f},
{{{{-3,4}, {-3,1}, {0,1}}}, 0.707106769f},
{{{{0, 0}, {0, 1}, {1, 1}}}, 0.5f},
{{{{1, 0}, {0, 0}, {0, 1}}}, 0.5f},
};
const int testSetCount = (int) SK_ARRAY_COUNT(testSet);
static void chopCompare(const SkConic chopped[2], const SkDConic dChopped[2]) {
SkASSERT(roughly_equal(chopped[0].fW, dChopped[0].fWeight));
SkASSERT(roughly_equal(chopped[1].fW, dChopped[1].fWeight));
for (int cIndex = 0; cIndex < 2; ++cIndex) {
for (int pIndex = 0; pIndex < 3; ++pIndex) {
SkDPoint up;
up.set(chopped[cIndex].fPts[pIndex]);
SkASSERT(dChopped[cIndex].fPts[pIndex].approximatelyEqual(up));
}
}
#if DEBUG_VISUALIZE_CONICS
dChopped[0].dump();
dChopped[1].dump();
#endif
}
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkImageEncoder.h"
#include "SkPathOpsRect.h"
#include "SkPaint.h"
#include "SkString.h"
#define DEBUG_VISUALIZE_CONICS 0
#if DEBUG_VISUALIZE_CONICS
static void writePng(const SkConic& c, const SkConic ch[2], const char* name) {
const int scale = 10;
SkConic conic, chopped[2];
for (int index = 0; index < 3; ++index) {
conic.fPts[index].fX = c.fPts[index].fX * scale;
conic.fPts[index].fY = c.fPts[index].fY * scale;
for (int chIndex = 0; chIndex < 2; ++chIndex) {
chopped[chIndex].fPts[index].fX = ch[chIndex].fPts[index].fX * scale;
chopped[chIndex].fPts[index].fY = ch[chIndex].fPts[index].fY * scale;
}
}
conic.fW = c.fW;
chopped[0].fW = ch[0].fW;
chopped[1].fW = ch[1].fW;
SkBitmap bitmap;
SkRect bounds;
conic.computeTightBounds(&bounds);
bounds.outset(10, 10);
bitmap.tryAllocPixels(SkImageInfo::MakeN32Premul(
SkScalarRoundToInt(bounds.width()), SkScalarRoundToInt(bounds.height())));
SkCanvas canvas(bitmap);
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
canvas.translate(-bounds.fLeft, -bounds.fTop);
canvas.drawColor(SK_ColorWHITE);
SkPath path;
path.moveTo(conic.fPts[0]);
path.conicTo(conic.fPts[1], conic.fPts[2], conic.fW);
paint.setARGB(0x80, 0xFF, 0, 0);
canvas.drawPath(path, paint);
path.reset();
path.moveTo(chopped[0].fPts[0]);
path.conicTo(chopped[0].fPts[1], chopped[0].fPts[2], chopped[0].fW);
path.moveTo(chopped[1].fPts[0]);
path.conicTo(chopped[1].fPts[1], chopped[1].fPts[2], chopped[1].fW);
paint.setARGB(0x80, 0, 0, 0xFF);
canvas.drawPath(path, paint);
SkString filename("c:\\Users\\caryclark\\Documents\\");
filename.appendf("%s.png", name);
SkImageEncoder::EncodeFile(filename.c_str(), bitmap,
SkImageEncoder::kPNG_Type, 100);
}
static void writeDPng(const SkDConic& dC, const char* name) {
const int scale = 5;
SkDConic dConic = {{{ {dC.fPts[0].fX * scale, dC.fPts[0].fY * scale },
{dC.fPts[1].fX * scale, dC.fPts[1].fY * scale },
{dC.fPts[2].fX * scale, dC.fPts[2].fY * scale }}}, dC.fWeight };
SkBitmap bitmap;
SkDRect bounds;
bounds.setBounds(dConic);
bounds.fLeft -= 10;
bounds.fTop -= 10;
bounds.fRight += 10;
bounds.fBottom += 10;
bitmap.tryAllocPixels(SkImageInfo::MakeN32Premul(
SkScalarRoundToInt(SkDoubleToScalar(bounds.width())),
SkScalarRoundToInt(SkDoubleToScalar(bounds.height()))));
SkCanvas canvas(bitmap);
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
canvas.translate(SkDoubleToScalar(-bounds.fLeft), SkDoubleToScalar(-bounds.fTop));
canvas.drawColor(SK_ColorWHITE);
SkPath path;
path.moveTo(dConic.fPts[0].asSkPoint());
path.conicTo(dConic.fPts[1].asSkPoint(), dConic.fPts[2].asSkPoint(), dConic.fWeight);
paint.setARGB(0x80, 0xFF, 0, 0);
canvas.drawPath(path, paint);
path.reset();
const int chops = 2;
for (int tIndex = 0; tIndex < chops; ++tIndex) {
SkDConic chopped = dConic.subDivide(tIndex / (double) chops,
(tIndex + 1) / (double) chops);
path.moveTo(chopped.fPts[0].asSkPoint());
path.conicTo(chopped.fPts[1].asSkPoint(), chopped.fPts[2].asSkPoint(), chopped.fWeight);
}
paint.setARGB(0x80, 0, 0, 0xFF);
canvas.drawPath(path, paint);
SkString filename("c:\\Users\\caryclark\\Documents\\");
filename.appendf("%s.png", name);
SkImageEncoder::EncodeFile(filename.c_str(), bitmap,
SkImageEncoder::kPNG_Type, 100);
}
#endif
static void chopBothWays(const SkDConic& dConic, double t, const char* name) {
SkConic conic;
for (int index = 0; index < 3; ++index) {
conic.fPts[index] = dConic.fPts[index].asSkPoint();
}
conic.fW = dConic.fWeight;
SkConic chopped[2];
SkDConic dChopped[2];
if (!conic.chopAt(SkDoubleToScalar(t), chopped)) {
return;
}
dChopped[0] = dConic.subDivide(0, t);
dChopped[1] = dConic.subDivide(t, 1);
#if DEBUG_VISUALIZE_CONICS
dConic.dump();
#endif
chopCompare(chopped, dChopped);
#if DEBUG_VISUALIZE_CONICS
writePng(conic, chopped, name);
#endif
}
#if DEBUG_VISUALIZE_CONICS
const SkDConic frame0[] = {
{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
};
const SkDConic frame1[] = {
{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
{{{{306.58801299999999, -227.983994}, {212.46499600000001, -262.24200400000001}, {95.551200899999998, 58.976398500000002}}}, 0.707107008f},
{{{{377.21899400000001, -141.98100299999999}, {237.77799285476553, -166.56830755921084}, {134.08399674208422, -155.06258330544892}}}, 0.788580656f},
{{{{134.08399674208422, -155.06258330544892}, {30.390000629402859, -143.55685905168704}, {23.185499199999999, -102.697998}}}, 0.923879623f},
};
const SkDConic frame2[] = {
{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
{{{{205.78973252799028, -158.12538713371103}, {143.97848953841861, -74.076645245042371}, {95.551200899999998, 58.976398500000002}}}, 0.923879623f},
{{{{377.21899400000001, -141.98100299999999}, {237.77799285476553, -166.56830755921084}, {134.08399674208422, -155.06258330544892}}}, 0.788580656f},
};
const SkDConic frame3[] = {
{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
{{{{205.78973252799028, -158.12538713371103}, {143.97848953841861, -74.076645245042371}, {95.551200899999998, 58.976398500000002}}}, 0.923879623f},
{{{{252.08225670812539, -156.90491625851064}, {185.93099479842493, -160.81544543232982}, {134.08399674208422, -155.06258330544892}}}, 0.835816324f},
};
const SkDConic frame4[] = {
{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
{{{{205.78973252799028, -158.12538713371103}, {174.88411103320448, -116.10101618937664}, {145.19509369736275, -56.857102571363754}}}, 0.871667147f},
{{{{252.08225670812539, -156.90491625851064}, {185.93099479842493, -160.81544543232982}, {134.08399674208422, -155.06258330544892}}}, 0.835816324f},
};
const SkDConic frame5[] = {
{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
{{{{205.78973252799028, -158.12538713371103}, {174.88411103320448, -116.10101618937664}, {145.19509369736275, -56.857102571363754}}}, 0.871667147f},
{{{{252.08225670812539, -156.90491625851064}, {219.70109133058406, -158.81912754088933}, {190.17095392508796, -158.38373974664466}}}, 0.858306944f},
};
const SkDConic frame6[] = {
{{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
{{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
{{{{205.78973252799028, -158.12538713371103}, {190.33692178059735, -137.11320166154385}, {174.87004877564593, -111.2132534799228}}}, 0.858117759f},
{{{{252.08225670812539, -156.90491625851064}, {219.70109133058406, -158.81912754088933}, {190.17095392508796, -158.38373974664466}}}, 0.858306944f},
};
const SkDConic* frames[] = {
frame0, frame1, frame2, frame3, frame4, frame5, frame6
};
const int frameSizes[] = { (int) SK_ARRAY_COUNT(frame0), (int) SK_ARRAY_COUNT(frame1),
(int) SK_ARRAY_COUNT(frame2), (int) SK_ARRAY_COUNT(frame3),
(int) SK_ARRAY_COUNT(frame4), (int) SK_ARRAY_COUNT(frame5),
(int) SK_ARRAY_COUNT(frame6),
};
static void writeFrames() {
const int scale = 5;
for (int index = 0; index < (int) SK_ARRAY_COUNT(frameSizes); ++index) {
SkDRect bounds;
bool boundsSet = false;
int frameSize = frameSizes[index];
for (int fIndex = 0; fIndex < frameSize; ++fIndex) {
const SkDConic& dC = frames[index][fIndex];
SkDConic dConic = {{{ {dC.fPts[0].fX * scale, dC.fPts[0].fY * scale },
{dC.fPts[1].fX * scale, dC.fPts[1].fY * scale },
{dC.fPts[2].fX * scale, dC.fPts[2].fY * scale }}}, dC.fWeight };
SkDRect dBounds;
dBounds.setBounds(dConic);
if (!boundsSet) {
bounds = dBounds;
boundsSet = true;
} else {
bounds.add((SkDPoint&) dBounds.fLeft);
bounds.add((SkDPoint&) dBounds.fRight);
}
}
bounds.fLeft -= 10;
bounds.fTop -= 10;
bounds.fRight += 10;
bounds.fBottom += 10;
SkBitmap bitmap;
bitmap.tryAllocPixels(SkImageInfo::MakeN32Premul(
SkScalarRoundToInt(SkDoubleToScalar(bounds.width())),
SkScalarRoundToInt(SkDoubleToScalar(bounds.height()))));
SkCanvas canvas(bitmap);
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
canvas.translate(SkDoubleToScalar(-bounds.fLeft), SkDoubleToScalar(-bounds.fTop));
canvas.drawColor(SK_ColorWHITE);
for (int fIndex = 0; fIndex < frameSize; ++fIndex) {
const SkDConic& dC = frames[index][fIndex];
SkDConic dConic = {{{ {dC.fPts[0].fX * scale, dC.fPts[0].fY * scale },
{dC.fPts[1].fX * scale, dC.fPts[1].fY * scale },
{dC.fPts[2].fX * scale, dC.fPts[2].fY * scale }}}, dC.fWeight };
SkPath path;
path.moveTo(dConic.fPts[0].asSkPoint());
path.conicTo(dConic.fPts[1].asSkPoint(), dConic.fPts[2].asSkPoint(), dConic.fWeight);
if (fIndex < 2) {
paint.setARGB(0x80, 0xFF, 0, 0);
} else {
paint.setARGB(0x80, 0, 0, 0xFF);
}
canvas.drawPath(path, paint);
}
SkString filename("c:\\Users\\caryclark\\Documents\\");
filename.appendf("f%d.png", index);
SkImageEncoder::EncodeFile(filename.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
}
}
#endif
static void oneOff(skiatest::Reporter* reporter, const ConicPts& conic1, const ConicPts& conic2,
bool coin) {
#if DEBUG_VISUALIZE_CONICS
writeFrames();
#endif
SkDConic c1, c2;
c1.debugSet(conic1.fPts.fPts, conic1.fWeight);
c2.debugSet(conic2.fPts.fPts, conic2.fWeight);
chopBothWays(c1, 0.5, "c1");
chopBothWays(c2, 0.5, "c2");
#if DEBUG_VISUALIZE_CONICS
writeDPng(c1, "d1");
writeDPng(c2, "d2");
#endif
SkASSERT(ValidConic(c1));
SkASSERT(ValidConic(c2));
SkIntersections intersections;
intersections.intersect(c1, c2);
if (coin && intersections.used() != 2) {
SkDebugf("");
}
REPORTER_ASSERT(reporter, !coin || intersections.used() == 2);
double tt1, tt2;
SkDPoint xy1, xy2;
for (int pt3 = 0; pt3 < intersections.used(); ++pt3) {
tt1 = intersections[0][pt3];
xy1 = c1.ptAtT(tt1);
tt2 = intersections[1][pt3];
xy2 = c2.ptAtT(tt2);
const SkDPoint& iPt = intersections.pt(pt3);
REPORTER_ASSERT(reporter, xy1.approximatelyEqual(iPt));
REPORTER_ASSERT(reporter, xy2.approximatelyEqual(iPt));
REPORTER_ASSERT(reporter, xy1.approximatelyEqual(xy2));
}
reporter->bumpTestCount();
}
static void oneOff(skiatest::Reporter* reporter, int outer, int inner) {
const ConicPts& c1 = testSet[outer];
const ConicPts& c2 = testSet[inner];
oneOff(reporter, c1, c2, false);
}
static void oneOffTests(skiatest::Reporter* reporter) {
for (int outer = 0; outer < testSetCount - 1; ++outer) {
for (int inner = outer + 1; inner < testSetCount; ++inner) {
oneOff(reporter, outer, inner);
}
}
}
DEF_TEST(PathOpsConicIntersectionOneOff, reporter) {
oneOff(reporter, 0, 1);
}
DEF_TEST(PathOpsConicIntersection, reporter) {
oneOffTests(reporter);
}