Index: src/pathops/SkPathOpsDebug.cpp |
diff --git a/src/pathops/SkPathOpsDebug.cpp b/src/pathops/SkPathOpsDebug.cpp |
index 476fafb4258ba2279c8efd72a208bd85cd6b414d..58b33c0ab9cddbe7fadb35241333390801dd946e 100644 |
--- a/src/pathops/SkPathOpsDebug.cpp |
+++ b/src/pathops/SkPathOpsDebug.cpp |
@@ -8,10 +8,19 @@ |
#include "SkMutex.h" |
#include "SkOpCoincidence.h" |
#include "SkOpContour.h" |
+#include "SkOSFile.h" |
#include "SkPath.h" |
#include "SkPathOpsDebug.h" |
#include "SkString.h" |
+#ifdef SK_DEBUG |
+bool SkPathOpsDebug::gDumpOp; // set to true to write op to file before a crash |
+bool SkPathOpsDebug::gVerifyOp; // set to true to compare result against regions |
+#endif |
+ |
+bool SkPathOpsDebug::gRunFail; // set to true to check for success on tests known to fail |
+bool SkPathOpsDebug::gVeryVerbose; // set to true to run extensive checking tests |
+ |
#undef FAIL_IF |
#define FAIL_IF(cond, coin) \ |
do { if (cond) log->record(SkPathOpsDebug::kFail_Glitch, coin); } while (false) |
@@ -27,10 +36,6 @@ |
class SkCoincidentSpans; |
-#if DEBUG_VALIDATE |
-extern bool FLAGS_runFail; |
-#endif |
- |
#if DEBUG_SORT |
int SkPathOpsDebug::gSortCountDefault = SK_MaxS32; |
int SkPathOpsDebug::gSortCount; |
@@ -647,15 +652,9 @@ void SkOpGlobalState::debugResetLoopCounts() { |
} |
#endif |
-#ifdef SK_DEBUG |
-bool SkOpGlobalState::debugRunFail() const { |
-#if DEBUG_VALIDATE |
- return FLAGS_runFail; |
-#else |
- return false; |
-#endif |
+bool SkOpGlobalState::DebugRunFail() { |
+ return SkPathOpsDebug::gRunFail; |
} |
-#endif |
// this is const so it can be called by const methods that overwise don't alter state |
#if DEBUG_VALIDATE || DEBUG_COIN |
@@ -1362,8 +1361,8 @@ void SkOpAngle::debugValidate() const { |
} |
next = next->fNext; |
} while (next && next != first); |
- SkASSERT(wind == 0 || !FLAGS_runFail); |
- SkASSERT(opp == 0 || !FLAGS_runFail); |
+ SkASSERT(wind == 0 || !SkPathOpsDebug::gRunFail); |
+ SkASSERT(opp == 0 || !SkPathOpsDebug::gRunFail); |
#endif |
} |
@@ -1390,8 +1389,8 @@ void SkOpAngle::debugValidateNext() const { |
#ifdef SK_DEBUG |
void SkCoincidentSpans::debugStartCheck(const SkOpSpanBase* outer, const SkOpSpanBase* over, |
const SkOpGlobalState* debugState) const { |
- SkASSERT(coinPtTEnd()->span() == over || !debugState->debugRunFail()); |
- SkASSERT(oppPtTEnd()->span() == outer || !debugState->debugRunFail()); |
+ SkASSERT(coinPtTEnd()->span() == over || !SkOpGlobalState::DebugRunFail()); |
+ SkASSERT(oppPtTEnd()->span() == outer || !SkOpGlobalState::DebugRunFail()); |
} |
#endif |
@@ -2906,3 +2905,203 @@ void SkPathOpsDebug::ShowOnePath(const SkPath& path, const char* name, bool incl |
iter.setPath(path); |
showPathContours(iter, name); |
} |
+ |
+#ifdef SK_DEBUG |
+#include "SkData.h" |
+#include "SkStream.h" |
+ |
+static void dump_path(FILE* file, const SkPath& path, bool force, bool dumpAsHex) { |
+ SkDynamicMemoryWStream wStream; |
+ path.dump(&wStream, force, dumpAsHex); |
+ sk_sp<SkData> data(wStream.detachAsData()); |
+ fprintf(file, "%.*s\n", (int) data->size(), (char*) data->data()); |
+} |
+ |
+static int dumpID = 0; |
+ |
+void SkPathOpsDebug::DumpOp(const SkPath& one, const SkPath& two, SkPathOp op, |
+ const char* testName) { |
+ FILE* file = sk_fopen("op_dump.txt", kWrite_SkFILE_Flag); |
+ DumpOp(file, one, two, op, testName); |
+} |
+ |
+void SkPathOpsDebug::DumpOp(FILE* file, const SkPath& one, const SkPath& two, SkPathOp op, |
+ const char* testName) { |
+ const char* name = testName ? testName : "op"; |
+ fprintf(file, |
+ "\nstatic void %s_%d(skiatest::Reporter* reporter, const char* filename) {\n", |
+ name, ++dumpID); |
+ fprintf(file, " SkPath path;\n"); |
+ fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", one.getFillType()); |
+ dump_path(file, one, false, true); |
+ fprintf(file, " SkPath path1(path);\n"); |
+ fprintf(file, " path.reset();\n"); |
+ fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", two.getFillType()); |
+ dump_path(file, two, false, true); |
+ fprintf(file, " SkPath path2(path);\n"); |
+ fprintf(file, " testPathOp(reporter, path1, path2, (SkPathOp) %d, filename);\n", op); |
+ fprintf(file, "}\n\n"); |
+ fclose(file); |
+} |
+ |
+void SkPathOpsDebug::DumpSimplify(const SkPath& path, const char* testName) { |
+ FILE* file = sk_fopen("simplify_dump.txt", kWrite_SkFILE_Flag); |
+ DumpSimplify(file, path, testName); |
+} |
+ |
+void SkPathOpsDebug::DumpSimplify(FILE* file, const SkPath& path, const char* testName) { |
+ const char* name = testName ? testName : "simplify"; |
+ fprintf(file, |
+ "\nstatic void %s_%d(skiatest::Reporter* reporter, const char* filename) {\n", |
+ name, ++dumpID); |
+ fprintf(file, " SkPath path;\n"); |
+ fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", path.getFillType()); |
+ dump_path(file, path, false, true); |
+ fprintf(file, " testSimplify(reporter, path, filename);\n"); |
+ fprintf(file, "}\n\n"); |
+ fclose(file); |
+} |
+ |
+#include "SkBitmap.h" |
+#include "SkCanvas.h" |
+#include "SkPaint.h" |
+ |
+const int bitWidth = 64; |
+const int bitHeight = 64; |
+ |
+static void debug_scale_matrix(const SkPath& one, const SkPath* two, SkMatrix& scale) { |
+ SkRect larger = one.getBounds(); |
+ if (two) { |
+ larger.join(two->getBounds()); |
+ } |
+ SkScalar largerWidth = larger.width(); |
+ if (largerWidth < 4) { |
+ largerWidth = 4; |
+ } |
+ SkScalar largerHeight = larger.height(); |
+ if (largerHeight < 4) { |
+ largerHeight = 4; |
+ } |
+ SkScalar hScale = (bitWidth - 2) / largerWidth; |
+ SkScalar vScale = (bitHeight - 2) / largerHeight; |
+ scale.reset(); |
+ scale.preScale(hScale, vScale); |
+ larger.fLeft *= hScale; |
+ larger.fRight *= hScale; |
+ larger.fTop *= vScale; |
+ larger.fBottom *= vScale; |
+ SkScalar dx = -16000 > larger.fLeft ? -16000 - larger.fLeft |
+ : 16000 < larger.fRight ? 16000 - larger.fRight : 0; |
+ SkScalar dy = -16000 > larger.fTop ? -16000 - larger.fTop |
+ : 16000 < larger.fBottom ? 16000 - larger.fBottom : 0; |
+ scale.preTranslate(dx, dy); |
+} |
+ |
+static int debug_paths_draw_the_same(const SkPath& one, const SkPath& two, SkBitmap& bits) { |
+ if (bits.width() == 0) { |
+ bits.allocN32Pixels(bitWidth * 2, bitHeight); |
+ } |
+ SkCanvas canvas(bits); |
+ canvas.drawColor(SK_ColorWHITE); |
+ SkPaint paint; |
+ canvas.save(); |
+ const SkRect& bounds1 = one.getBounds(); |
+ canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1); |
+ canvas.drawPath(one, paint); |
+ canvas.restore(); |
+ canvas.save(); |
+ canvas.translate(-bounds1.fLeft + 1 + bitWidth, -bounds1.fTop + 1); |
+ canvas.drawPath(two, paint); |
+ canvas.restore(); |
+ int errors = 0; |
+ for (int y = 0; y < bitHeight - 1; ++y) { |
+ uint32_t* addr1 = bits.getAddr32(0, y); |
+ uint32_t* addr2 = bits.getAddr32(0, y + 1); |
+ uint32_t* addr3 = bits.getAddr32(bitWidth, y); |
+ uint32_t* addr4 = bits.getAddr32(bitWidth, y + 1); |
+ for (int x = 0; x < bitWidth - 1; ++x) { |
+ // count 2x2 blocks |
+ bool err = addr1[x] != addr3[x]; |
+ if (err) { |
+ errors += addr1[x + 1] != addr3[x + 1] |
+ && addr2[x] != addr4[x] && addr2[x + 1] != addr4[x + 1]; |
+ } |
+ } |
+ } |
+ return errors; |
+} |
+ |
+void SkPathOpsDebug::ReportOpFail(const SkPath& one, const SkPath& two, SkPathOp op) { |
+ SkDebugf("// Op did not expect failure\n"); |
+ DumpOp(stderr, one, two, op, "opTest"); |
+ fflush(stderr); |
+} |
+ |
+void SkPathOpsDebug::VerifyOp(const SkPath& one, const SkPath& two, SkPathOp op, |
+ const SkPath& result) { |
+ SkPath pathOut, scaledPathOut; |
+ SkRegion rgnA, rgnB, openClip, rgnOut; |
+ openClip.setRect(-16000, -16000, 16000, 16000); |
+ rgnA.setPath(one, openClip); |
+ rgnB.setPath(two, openClip); |
+ rgnOut.op(rgnA, rgnB, (SkRegion::Op) op); |
+ rgnOut.getBoundaryPath(&pathOut); |
+ SkMatrix scale; |
+ debug_scale_matrix(one, &two, scale); |
+ SkRegion scaledRgnA, scaledRgnB, scaledRgnOut; |
+ SkPath scaledA, scaledB; |
+ scaledA.addPath(one, scale); |
+ scaledA.setFillType(one.getFillType()); |
+ scaledB.addPath(two, scale); |
+ scaledB.setFillType(two.getFillType()); |
+ scaledRgnA.setPath(scaledA, openClip); |
+ scaledRgnB.setPath(scaledB, openClip); |
+ scaledRgnOut.op(scaledRgnA, scaledRgnB, (SkRegion::Op) op); |
+ scaledRgnOut.getBoundaryPath(&scaledPathOut); |
+ SkBitmap bitmap; |
+ SkPath scaledOut; |
+ scaledOut.addPath(result, scale); |
+ scaledOut.setFillType(result.getFillType()); |
+ int errors = debug_paths_draw_the_same(scaledPathOut, scaledOut, bitmap); |
+ const int MAX_ERRORS = 9; |
+ if (errors > MAX_ERRORS) { |
+ fprintf(stderr, "// Op did not expect errors=%d\n", errors); |
+ DumpOp(stderr, one, two, op, "opTest"); |
+ fflush(stderr); |
+ } |
+} |
+ |
+void SkPathOpsDebug::ReportSimplifyFail(const SkPath& path) { |
+ SkDebugf("// Simplify did not expect failure\n"); |
+ DumpSimplify(stderr, path, "simplifyTest"); |
+ fflush(stderr); |
+} |
+ |
+void SkPathOpsDebug::VerifySimplify(const SkPath& path, const SkPath& result) { |
+ SkPath pathOut, scaledPathOut; |
+ SkRegion rgnA, openClip, rgnOut; |
+ openClip.setRect(-16000, -16000, 16000, 16000); |
+ rgnA.setPath(path, openClip); |
+ rgnOut.getBoundaryPath(&pathOut); |
+ SkMatrix scale; |
+ debug_scale_matrix(path, nullptr, scale); |
+ SkRegion scaledRgnA; |
+ SkPath scaledA; |
+ scaledA.addPath(path, scale); |
+ scaledA.setFillType(path.getFillType()); |
+ scaledRgnA.setPath(scaledA, openClip); |
+ scaledRgnA.getBoundaryPath(&scaledPathOut); |
+ SkBitmap bitmap; |
+ SkPath scaledOut; |
+ scaledOut.addPath(result, scale); |
+ scaledOut.setFillType(result.getFillType()); |
+ int errors = debug_paths_draw_the_same(scaledPathOut, scaledOut, bitmap); |
+ const int MAX_ERRORS = 9; |
+ if (errors > MAX_ERRORS) { |
+ fprintf(stderr, "// Simplify did not expect errors=%d\n", errors); |
+ DumpSimplify(stderr, path, "simplifyTest"); |
+ fflush(stderr); |
+ } |
+} |
+ |
+#endif |