Home | History | Annotate | Download | only in pathops
      1 /*
      2  * Copyright 2012 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 #include "SkAddIntersections.h"
      8 #include "SkOpCoincidence.h"
      9 #include "SkOpEdgeBuilder.h"
     10 #include "SkPathOpsCommon.h"
     11 #include "SkPathWriter.h"
     12 
     13 static SkOpSegment* findChaseOp(SkTDArray<SkOpSpanBase*>& chase, SkOpSpanBase** startPtr,
     14         SkOpSpanBase** endPtr) {
     15     while (chase.count()) {
     16         SkOpSpanBase* span;
     17         chase.pop(&span);
     18         // OPTIMIZE: prev makes this compatible with old code -- but is it necessary?
     19         *startPtr = span->ptT()->prev()->span();
     20         SkOpSegment* segment = (*startPtr)->segment();
     21         bool done = true;
     22         *endPtr = nullptr;
     23         if (SkOpAngle* last = segment->activeAngle(*startPtr, startPtr, endPtr, &done)) {
     24             *startPtr = last->start();
     25             *endPtr = last->end();
     26    #if TRY_ROTATE
     27             *chase.insert(0) = span;
     28    #else
     29             *chase.append() = span;
     30    #endif
     31             return last->segment();
     32         }
     33         if (done) {
     34             continue;
     35         }
     36         int winding;
     37         bool sortable;
     38         const SkOpAngle* angle = AngleWinding(*startPtr, *endPtr, &winding, &sortable);
     39         if (winding == SK_MinS32) {
     40             continue;
     41         }
     42         int sumMiWinding, sumSuWinding;
     43         if (sortable) {
     44             segment = angle->segment();
     45             sumMiWinding = segment->updateWindingReverse(angle);
     46             sumSuWinding = segment->updateOppWindingReverse(angle);
     47             if (segment->operand()) {
     48                 SkTSwap<int>(sumMiWinding, sumSuWinding);
     49             }
     50         }
     51         SkOpSegment* first = nullptr;
     52         const SkOpAngle* firstAngle = angle;
     53         while ((angle = angle->next()) != firstAngle) {
     54             segment = angle->segment();
     55             SkOpSpanBase* start = angle->start();
     56             SkOpSpanBase* end = angle->end();
     57             int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
     58             if (sortable) {
     59                 segment->setUpWindings(start, end, &sumMiWinding, &sumSuWinding,
     60                         &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
     61             }
     62             if (!segment->done(angle)) {
     63                 if (!first && (sortable || start->starter(end)->windSum() != SK_MinS32)) {
     64                     first = segment;
     65                     *startPtr = start;
     66                     *endPtr = end;
     67                 }
     68                 // OPTIMIZATION: should this also add to the chase?
     69                 if (sortable) {
     70                     (void) segment->markAngle(maxWinding, sumWinding, oppMaxWinding,
     71                         oppSumWinding, angle);
     72                 }
     73             }
     74         }
     75         if (first) {
     76        #if TRY_ROTATE
     77             *chase.insert(0) = span;
     78        #else
     79             *chase.append() = span;
     80        #endif
     81             return first;
     82         }
     83     }
     84     return nullptr;
     85 }
     86 
     87 static bool bridgeOp(SkOpContourHead* contourList, const SkPathOp op,
     88         const int xorMask, const int xorOpMask, SkPathWriter* simple, SkChunkAlloc* allocator) {
     89     bool unsortable = false;
     90     do {
     91         SkOpSpan* span = FindSortableTop(contourList);
     92         if (!span) {
     93             break;
     94         }
     95         SkOpSegment* current = span->segment();
     96         SkOpSpanBase* start = span->next();
     97         SkOpSpanBase* end = span;
     98         SkTDArray<SkOpSpanBase*> chase;
     99         do {
    100             if (current->activeOp(start, end, xorMask, xorOpMask, op)) {
    101                 do {
    102                     if (!unsortable && current->done()) {
    103                         break;
    104                     }
    105                     SkASSERT(unsortable || !current->done());
    106                     SkOpSpanBase* nextStart = start;
    107                     SkOpSpanBase* nextEnd = end;
    108                     SkOpSegment* next = current->findNextOp(&chase, &nextStart, &nextEnd,
    109                             &unsortable, op, xorMask, xorOpMask);
    110                     if (!next) {
    111                         if (!unsortable && simple->hasMove()
    112                                 && current->verb() != SkPath::kLine_Verb
    113                                 && !simple->isClosed()) {
    114                             if (!current->addCurveTo(start, end, simple)) {
    115                                 return false;
    116                             }
    117                     #if DEBUG_ACTIVE_SPANS
    118                             if (!simple->isClosed()) {
    119                                 DebugShowActiveSpans(contourList);
    120                             }
    121                     #endif
    122                         }
    123                         break;
    124                     }
    125         #if DEBUG_FLOW
    126                     SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__,
    127                             current->debugID(), start->pt().fX, start->pt().fY,
    128                             end->pt().fX, end->pt().fY);
    129         #endif
    130                     if (!current->addCurveTo(start, end, simple)) {
    131                         return false;
    132                     }
    133                     current = next;
    134                     start = nextStart;
    135                     end = nextEnd;
    136                 } while (!simple->isClosed() && (!unsortable || !start->starter(end)->done()));
    137                 if (current->activeWinding(start, end) && !simple->isClosed()) {
    138                     SkOpSpan* spanStart = start->starter(end);
    139                     if (!spanStart->done()) {
    140                         if (!current->addCurveTo(start, end, simple)) {
    141                             return false;
    142                         }
    143                         current->markDone(spanStart);
    144                     }
    145                 }
    146                 simple->close();
    147             } else {
    148                 SkOpSpanBase* last = current->markAndChaseDone(start, end);
    149                 if (last && !last->chased()) {
    150                     last->setChased(true);
    151                     SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last));
    152                     *chase.append() = last;
    153 #if DEBUG_WINDING
    154                     SkDebugf("%s chase.append id=%d", __FUNCTION__, last->segment()->debugID());
    155                     if (!last->final()) {
    156                          SkDebugf(" windSum=%d", last->upCast()->windSum());
    157                     }
    158                     SkDebugf("\n");
    159 #endif
    160                 }
    161             }
    162             current = findChaseOp(chase, &start, &end);
    163         #if DEBUG_ACTIVE_SPANS
    164             DebugShowActiveSpans(contourList);
    165         #endif
    166             if (!current) {
    167                 break;
    168             }
    169         } while (true);
    170     } while (true);
    171     return simple->someAssemblyRequired();
    172 }
    173 
    174 // pretty picture:
    175 // https://docs.google.com/a/google.com/drawings/d/1sPV8rPfpEFXymBp3iSbDRWAycp1b-7vD9JP2V-kn9Ss/edit?usp=sharing
    176 static const SkPathOp gOpInverse[kReverseDifference_SkPathOp + 1][2][2] = {
    177 //                  inside minuend                               outside minuend
    178 //     inside subtrahend     outside subtrahend      inside subtrahend     outside subtrahend
    179 {{ kDifference_SkPathOp,   kIntersect_SkPathOp }, { kUnion_SkPathOp, kReverseDifference_SkPathOp }},
    180 {{ kIntersect_SkPathOp,   kDifference_SkPathOp }, { kReverseDifference_SkPathOp, kUnion_SkPathOp }},
    181 {{ kUnion_SkPathOp, kReverseDifference_SkPathOp }, { kDifference_SkPathOp,   kIntersect_SkPathOp }},
    182 {{ kXOR_SkPathOp,                 kXOR_SkPathOp }, { kXOR_SkPathOp,                kXOR_SkPathOp }},
    183 {{ kReverseDifference_SkPathOp, kUnion_SkPathOp }, { kIntersect_SkPathOp,   kDifference_SkPathOp }},
    184 };
    185 
    186 static const bool gOutInverse[kReverseDifference_SkPathOp + 1][2][2] = {
    187     {{ false, false }, { true, false }},  // diff
    188     {{ false, false }, { false, true }},  // sect
    189     {{ false, true }, { true, true }},    // union
    190     {{ false, true }, { true, false }},   // xor
    191     {{ false, true }, { false, false }},  // rev diff
    192 };
    193 
    194 #define DEBUGGING_PATHOPS_FROM_HOST 0  // enable to debug svg in chrome -- note path hardcoded below
    195 #if DEBUGGING_PATHOPS_FROM_HOST
    196 #include "SkData.h"
    197 #include "SkStream.h"
    198 
    199 static void dump_path(FILE* file, const SkPath& path, bool force, bool dumpAsHex) {
    200     SkDynamicMemoryWStream wStream;
    201     path.dump(&wStream, force, dumpAsHex);
    202     SkAutoDataUnref data(wStream.copyToData());
    203     fprintf(file, "%.*s\n", (int) data->size(), data->data());
    204 }
    205 
    206 static int dumpID = 0;
    207 
    208 static void dump_op(const SkPath& one, const SkPath& two, SkPathOp op) {
    209 #if SK_BUILD_FOR_MAC
    210     FILE* file = fopen("/Users/caryclark/Documents/svgop.txt", "w");
    211 #else
    212     FILE* file = fopen("/usr/local/google/home/caryclark/Documents/svgop.txt", "w");
    213 #endif
    214     fprintf(file,
    215             "\nstatic void fuzz763_%d(skiatest::Reporter* reporter, const char* filename) {\n",
    216             ++dumpID);
    217     fprintf(file, "    SkPath path;\n");
    218     fprintf(file, "    path.setFillType((SkPath::FillType) %d);\n", one.getFillType());
    219     dump_path(file, one, false, true);
    220     fprintf(file, "    SkPath path1(path);\n");
    221     fprintf(file, "    path.reset();\n");
    222     fprintf(file, "    path.setFillType((SkPath::FillType) %d);\n", two.getFillType());
    223     dump_path(file, two, false, true);
    224     fprintf(file, "    SkPath path2(path);\n");
    225     fprintf(file, "    testPathOp(reporter, path1, path2, (SkPathOp) %d, filename);\n", op);
    226     fprintf(file, "}\n");
    227     fclose(file);
    228 }
    229 #endif
    230 
    231 
    232 #if DEBUG_T_SECT_LOOP_COUNT
    233 
    234 #include "SkMutex.h"
    235 
    236 SK_DECLARE_STATIC_MUTEX(debugWorstLoop);
    237 
    238 SkOpGlobalState debugWorstState(nullptr, nullptr  SkDEBUGPARAMS(nullptr));
    239 
    240 void ReportPathOpsDebugging() {
    241     debugWorstState.debugLoopReport();
    242 }
    243 
    244 extern void (*gVerboseFinalize)();
    245 
    246 #endif
    247 
    248 bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result,
    249         bool expectSuccess  SkDEBUGPARAMS(const char* testName)) {
    250     SkChunkAlloc allocator(4096);  // FIXME: add a constant expression here, tune
    251     SkOpContour contour;
    252     SkOpContourHead* contourList = static_cast<SkOpContourHead*>(&contour);
    253     SkOpCoincidence coincidence;
    254     SkOpGlobalState globalState(&coincidence, contourList  SkDEBUGPARAMS(testName));
    255 #if DEBUGGING_PATHOPS_FROM_HOST
    256     dump_op(one, two, op);
    257 #endif
    258 #if 0 && DEBUG_SHOW_TEST_NAME
    259     char* debugName = DEBUG_FILENAME_STRING;
    260     if (debugName && debugName[0]) {
    261         SkPathOpsDebug::BumpTestName(debugName);
    262         SkPathOpsDebug::ShowPath(one, two, op, debugName);
    263     }
    264 #endif
    265     op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()];
    266     SkPath::FillType fillType = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()]
    267             ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType;
    268     const SkPath* minuend = &one;
    269     const SkPath* subtrahend = &two;
    270     if (op == kReverseDifference_SkPathOp) {
    271         minuend = &two;
    272         subtrahend = &one;
    273         op = kDifference_SkPathOp;
    274     }
    275 #if DEBUG_SORT
    276     SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault;
    277 #endif
    278     // turn path into list of segments
    279     SkOpEdgeBuilder builder(*minuend, &contour, &allocator, &globalState);
    280     if (builder.unparseable()) {
    281         return false;
    282     }
    283     const int xorMask = builder.xorMask();
    284     builder.addOperand(*subtrahend);
    285     if (!builder.finish(&allocator)) {
    286         return false;
    287     }
    288 #if DEBUG_DUMP_SEGMENTS
    289     contourList->dumpSegments("seg", op);
    290 #endif
    291 
    292     const int xorOpMask = builder.xorMask();
    293     if (!SortContourList(&contourList, xorMask == kEvenOdd_PathOpsMask,
    294             xorOpMask == kEvenOdd_PathOpsMask)) {
    295         result->reset();
    296         result->setFillType(fillType);
    297         return true;
    298     }
    299     // find all intersections between segments
    300     SkOpContour* current = contourList;
    301     do {
    302         SkOpContour* next = current;
    303         while (AddIntersectTs(current, next, &coincidence, &allocator)
    304                 && (next = next->next()))
    305             ;
    306     } while ((current = current->next()));
    307 #if DEBUG_VALIDATE
    308     globalState.setPhase(SkOpGlobalState::kWalking);
    309 #endif
    310     if (!HandleCoincidence(contourList, &coincidence, &allocator)) {
    311         return false;
    312     }
    313 #if DEBUG_ALIGNMENT
    314     contourList->dumpSegments("aligned");
    315 #endif
    316     // construct closed contours
    317     result->reset();
    318     result->setFillType(fillType);
    319     SkPathWriter wrapper(*result);
    320     bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper, &allocator);
    321     {  // if some edges could not be resolved, assemble remaining fragments
    322         SkPath temp;
    323         temp.setFillType(fillType);
    324         SkPathWriter assembled(temp);
    325         Assemble(wrapper, &assembled);
    326         *result = *assembled.nativePath();
    327         result->setFillType(fillType);
    328     }
    329 #if DEBUG_T_SECT_LOOP_COUNT
    330     {
    331         SkAutoMutexAcquire autoM(debugWorstLoop);
    332         if (!gVerboseFinalize) {
    333             gVerboseFinalize = &ReportPathOpsDebugging;
    334         }
    335         debugWorstState.debugDoYourWorst(&globalState);
    336     }
    337 #endif
    338     return true;
    339 }
    340 
    341 #define DEBUG_VERIFY 0
    342 
    343 #if DEBUG_VERIFY
    344 #include "SkBitmap.h"
    345 #include "SkCanvas.h"
    346 #include "SkPaint.h"
    347 
    348 const int bitWidth = 64;
    349 const int bitHeight = 64;
    350 
    351 static void debug_scale_matrix(const SkPath& one, const SkPath& two, SkMatrix& scale) {
    352     SkRect larger = one.getBounds();
    353     larger.join(two.getBounds());
    354     SkScalar largerWidth = larger.width();
    355     if (largerWidth < 4) {
    356         largerWidth = 4;
    357     }
    358     SkScalar largerHeight = larger.height();
    359     if (largerHeight < 4) {
    360         largerHeight = 4;
    361     }
    362     SkScalar hScale = (bitWidth - 2) / largerWidth;
    363     SkScalar vScale = (bitHeight - 2) / largerHeight;
    364     scale.reset();
    365     scale.preScale(hScale, vScale);
    366     larger.fLeft *= hScale;
    367     larger.fRight *= hScale;
    368     larger.fTop *= vScale;
    369     larger.fBottom *= vScale;
    370     SkScalar dx = -16000 > larger.fLeft ? -16000 - larger.fLeft
    371             : 16000 < larger.fRight ? 16000 - larger.fRight : 0;
    372     SkScalar dy = -16000 > larger.fTop ? -16000 - larger.fTop
    373             : 16000 < larger.fBottom ? 16000 - larger.fBottom : 0;
    374     scale.preTranslate(dx, dy);
    375 }
    376 
    377 static int debug_paths_draw_the_same(const SkPath& one, const SkPath& two, SkBitmap& bits) {
    378     if (bits.width() == 0) {
    379         bits.allocN32Pixels(bitWidth * 2, bitHeight);
    380     }
    381     SkCanvas canvas(bits);
    382     canvas.drawColor(SK_ColorWHITE);
    383     SkPaint paint;
    384     canvas.save();
    385     const SkRect& bounds1 = one.getBounds();
    386     canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1);
    387     canvas.drawPath(one, paint);
    388     canvas.restore();
    389     canvas.save();
    390     canvas.translate(-bounds1.fLeft + 1 + bitWidth, -bounds1.fTop + 1);
    391     canvas.drawPath(two, paint);
    392     canvas.restore();
    393     int errors = 0;
    394     for (int y = 0; y < bitHeight - 1; ++y) {
    395         uint32_t* addr1 = bits.getAddr32(0, y);
    396         uint32_t* addr2 = bits.getAddr32(0, y + 1);
    397         uint32_t* addr3 = bits.getAddr32(bitWidth, y);
    398         uint32_t* addr4 = bits.getAddr32(bitWidth, y + 1);
    399         for (int x = 0; x < bitWidth - 1; ++x) {
    400             // count 2x2 blocks
    401             bool err = addr1[x] != addr3[x];
    402             if (err) {
    403                 errors += addr1[x + 1] != addr3[x + 1]
    404                         && addr2[x] != addr4[x] && addr2[x + 1] != addr4[x + 1];
    405             }
    406         }
    407     }
    408     return errors;
    409 }
    410 
    411 #endif
    412 
    413 bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) {
    414 #if DEBUG_VERIFY
    415     if (!OpDebug(one, two, op, result, true  SkDEBUGPARAMS(nullptr))) {
    416         SkDebugf("%s did not expect failure\none: fill=%d\n", __FUNCTION__, one.getFillType());
    417         one.dumpHex();
    418         SkDebugf("two: fill=%d\n", two.getFillType());
    419         two.dumpHex();
    420         SkASSERT(0);
    421         return false;
    422     }
    423     SkPath pathOut, scaledPathOut;
    424     SkRegion rgnA, rgnB, openClip, rgnOut;
    425     openClip.setRect(-16000, -16000, 16000, 16000);
    426     rgnA.setPath(one, openClip);
    427     rgnB.setPath(two, openClip);
    428     rgnOut.op(rgnA, rgnB, (SkRegion::Op) op);
    429     rgnOut.getBoundaryPath(&pathOut);
    430     SkMatrix scale;
    431     debug_scale_matrix(one, two, scale);
    432     SkRegion scaledRgnA, scaledRgnB, scaledRgnOut;
    433     SkPath scaledA, scaledB;
    434     scaledA.addPath(one, scale);
    435     scaledA.setFillType(one.getFillType());
    436     scaledB.addPath(two, scale);
    437     scaledB.setFillType(two.getFillType());
    438     scaledRgnA.setPath(scaledA, openClip);
    439     scaledRgnB.setPath(scaledB, openClip);
    440     scaledRgnOut.op(scaledRgnA, scaledRgnB, (SkRegion::Op) op);
    441     scaledRgnOut.getBoundaryPath(&scaledPathOut);
    442     SkBitmap bitmap;
    443     SkPath scaledOut;
    444     scaledOut.addPath(*result, scale);
    445     scaledOut.setFillType(result->getFillType());
    446     int errors = debug_paths_draw_the_same(scaledPathOut, scaledOut, bitmap);
    447     const int MAX_ERRORS = 9;
    448     if (errors > MAX_ERRORS) {
    449         SkDebugf("%s did not expect failure\none: fill=%d\n", __FUNCTION__, one.getFillType());
    450         one.dumpHex();
    451         SkDebugf("two: fill=%d\n", two.getFillType());
    452         two.dumpHex();
    453         SkASSERT(0);
    454     }
    455     return true;
    456 #else
    457     return OpDebug(one, two, op, result, true  SkDEBUGPARAMS(nullptr));
    458 #endif
    459 }
    460