Home | History | Annotate | Download | only in gm
      1 /*
      2  * Copyright 2015 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 
      8 #include "gm.h"
      9 #include "SkInsetConvexPolygon.h"
     10 #include "SkPathPriv.h"
     11 
     12 static void create_ngon(int n, SkPoint* pts, SkScalar width, SkScalar height) {
     13     float angleStep = 360.0f / n, angle = 0.0f, sin, cos;
     14     if ((n % 2) == 1) {
     15         angle = angleStep/2.0f;
     16     }
     17 
     18     for (int i = 0; i < n; ++i) {
     19         sin = SkScalarSinCos(SkDegreesToRadians(angle), &cos);
     20         pts[i].fX = -sin * width;
     21         pts[i].fY = cos * height;
     22         angle += angleStep;
     23     }
     24 }
     25 
     26 namespace ConvexLineOnlyData {
     27 // narrow rect
     28 const SkPoint gPoints0[] = {
     29     { -1.5f, -50.0f },
     30     { 1.5f, -50.0f },
     31     { 1.5f,  50.0f },
     32     { -1.5f,  50.0f }
     33 };
     34 // narrow rect on an angle
     35 const SkPoint gPoints1[] = {
     36     { -50.0f, -49.0f },
     37     { -49.0f, -50.0f },
     38     { 50.0f,  49.0f },
     39     { 49.0f,  50.0f }
     40 };
     41 // trap - narrow on top - wide on bottom
     42 const SkPoint gPoints2[] = {
     43     { -10.0f, -50.0f },
     44     { 10.0f, -50.0f },
     45     { 50.0f,  50.0f },
     46     { -50.0f,  50.0f }
     47 };
     48 // wide skewed rect
     49 const SkPoint gPoints3[] = {
     50     { -50.0f, -50.0f },
     51     { 0.0f, -50.0f },
     52     { 50.0f,  50.0f },
     53     { 0.0f,  50.0f }
     54 };
     55 // thin rect with colinear-ish lines
     56 const SkPoint gPoints4[] = {
     57     { -6.0f, -50.0f },
     58     { 4.0f, -50.0f },
     59     { 5.0f, -25.0f },
     60     { 6.0f,   0.0f },
     61     { 5.0f,  25.0f },
     62     { 4.0f,  50.0f },
     63     { -4.0f,  50.0f }
     64 };
     65 // degenerate
     66 const SkPoint gPoints5[] = {
     67     { -0.025f, -0.025f },
     68     { 0.025f, -0.025f },
     69     { 0.025f,  0.025f },
     70     { -0.025f,  0.025f }
     71 };
     72 // Triangle in which the first point should fuse with last
     73 const SkPoint gPoints6[] = {
     74     { -20.0f, -13.0f },
     75     { -20.0f, -13.05f },
     76     { 20.0f, -13.0f },
     77     { 20.0f,  27.0f }
     78 };
     79 // thin rect with colinear lines
     80 const SkPoint gPoints7[] = {
     81     { -10.0f, -50.0f },
     82     { 10.0f, -50.0f },
     83     { 10.0f, -25.0f },
     84     { 10.0f,   0.0f },
     85     { 10.0f,  25.0f },
     86     { 10.0f,  50.0f },
     87     { -10.0f,  50.0f }
     88 };
     89 // capped teardrop
     90 const SkPoint gPoints8[] = {
     91     { 50.00f,  50.00f },
     92     { 0.00f,  50.00f },
     93     { -15.45f,  47.55f },
     94     { -29.39f,  40.45f },
     95     { -40.45f,  29.39f },
     96     { -47.55f,  15.45f },
     97     { -50.00f,   0.00f },
     98     { -47.55f, -15.45f },
     99     { -40.45f, -29.39f },
    100     { -29.39f, -40.45f },
    101     { -15.45f, -47.55f },
    102     { 0.00f, -50.00f },
    103     { 50.00f, -50.00f }
    104 };
    105 // teardrop
    106 const SkPoint gPoints9[] = {
    107     { 4.39f,  40.45f },
    108     { -9.55f,  47.55f },
    109     { -25.00f,  50.00f },
    110     { -40.45f,  47.55f },
    111     { -54.39f,  40.45f },
    112     { -65.45f,  29.39f },
    113     { -72.55f,  15.45f },
    114     { -75.00f,   0.00f },
    115     { -72.55f, -15.45f },
    116     { -65.45f, -29.39f },
    117     { -54.39f, -40.45f },
    118     { -40.45f, -47.55f },
    119     { -25.0f,  -50.0f },
    120     { -9.55f, -47.55f },
    121     { 4.39f, -40.45f },
    122     { 75.00f,   0.00f }
    123 };
    124 // clipped triangle
    125 const SkPoint gPoints10[] = {
    126     { -10.0f, -50.0f },
    127     { 10.0f, -50.0f },
    128     { 50.0f,  31.0f },
    129     { 40.0f,  50.0f },
    130     { -40.0f,  50.0f },
    131     { -50.0f,  31.0f },
    132 };
    133 
    134 const SkPoint* gPoints[] = {
    135     gPoints0, gPoints1, gPoints2, gPoints3, gPoints4, gPoints5, gPoints6,
    136     gPoints7, gPoints8, gPoints9, gPoints10,
    137 };
    138 
    139 const size_t gSizes[] = {
    140     SK_ARRAY_COUNT(gPoints0),
    141     SK_ARRAY_COUNT(gPoints1),
    142     SK_ARRAY_COUNT(gPoints2),
    143     SK_ARRAY_COUNT(gPoints3),
    144     SK_ARRAY_COUNT(gPoints4),
    145     SK_ARRAY_COUNT(gPoints5),
    146     SK_ARRAY_COUNT(gPoints6),
    147     SK_ARRAY_COUNT(gPoints7),
    148     SK_ARRAY_COUNT(gPoints8),
    149     SK_ARRAY_COUNT(gPoints9),
    150     SK_ARRAY_COUNT(gPoints10),
    151 };
    152 static_assert(SK_ARRAY_COUNT(gSizes) == SK_ARRAY_COUNT(gPoints), "array_mismatch");
    153 }
    154 
    155 namespace skiagm {
    156 
    157 // This GM is intended to exercise Ganesh's handling of convex line-only
    158 // paths
    159 class ConvexLineOnlyPathsGM : public GM {
    160 public:
    161     ConvexLineOnlyPathsGM(bool doStrokeAndFill) : fDoStrokeAndFill(doStrokeAndFill) {
    162         this->setBGColor(0xFFFFFFFF);
    163     }
    164 
    165 protected:
    166     SkString onShortName() override {
    167         if (fDoStrokeAndFill) {
    168             return SkString("convex-lineonly-paths-stroke-and-fill");
    169         }
    170         return SkString("convex-lineonly-paths");
    171     }
    172     SkISize onISize() override { return SkISize::Make(kGMWidth, kGMHeight); }
    173     bool runAsBench() const override { return true; }
    174 
    175     static SkPath GetPath(int index, SkPath::Direction dir) {
    176         std::unique_ptr<SkPoint[]> data(nullptr);
    177         const SkPoint* points;
    178         int numPts;
    179         if (index < (int) SK_ARRAY_COUNT(ConvexLineOnlyData::gPoints)) {
    180             // manually specified
    181             points = ConvexLineOnlyData::gPoints[index];
    182             numPts = (int)ConvexLineOnlyData::gSizes[index];
    183         } else {
    184             // procedurally generated
    185             SkScalar width = kMaxPathHeight/2;
    186             SkScalar height = kMaxPathHeight/2;
    187             switch (index-SK_ARRAY_COUNT(ConvexLineOnlyData::gPoints)) {
    188             case 0:
    189                 numPts = 3;
    190                 break;
    191             case 1:
    192                 numPts = 4;
    193                 break;
    194             case 2:
    195                 numPts = 5;
    196                 break;
    197             case 3:             // squashed pentagon
    198                 numPts = 5;
    199                 width = kMaxPathHeight/5;
    200                 break;
    201             case 4:
    202                 numPts = 6;
    203                 break;
    204             case 5:
    205                 numPts = 8;
    206                 break;
    207             case 6:              // squashed octogon
    208                 numPts = 8;
    209                 width = kMaxPathHeight/5;
    210                 break;
    211             case 7:
    212                 numPts = 20;
    213                 break;
    214             case 8:
    215                 numPts = 100;
    216                 break;
    217             default:
    218                 numPts = 3;
    219                 break;
    220             }
    221 
    222             data.reset(new SkPoint[numPts]);
    223 
    224             create_ngon(numPts, data.get(), width, height);
    225             points = data.get();
    226         }
    227 
    228         SkPath path;
    229 
    230         if (SkPath::kCW_Direction == dir) {
    231             path.moveTo(points[0]);
    232             for (int i = 1; i < numPts; ++i) {
    233                 path.lineTo(points[i]);
    234             }
    235         } else {
    236             path.moveTo(points[numPts-1]);
    237             for (int i = numPts-2; i >= 0; --i) {
    238                 path.lineTo(points[i]);
    239             }
    240         }
    241 
    242         path.close();
    243 #ifdef SK_DEBUG
    244         // Each path this method returns should be convex, only composed of
    245         // lines, wound the right direction, and short enough to fit in one
    246         // of the GMs rows.
    247         SkASSERT(path.isConvex());
    248         SkASSERT(SkPath::kLine_SegmentMask == path.getSegmentMasks());
    249         SkPathPriv::FirstDirection actualDir;
    250         SkASSERT(SkPathPriv::CheapComputeFirstDirection(path, &actualDir));
    251         SkASSERT(SkPathPriv::AsFirstDirection(dir) == actualDir);
    252         SkRect bounds = path.getBounds();
    253         SkASSERT(SkScalarNearlyEqual(bounds.centerX(), 0.0f));
    254         SkASSERT(bounds.height() <= kMaxPathHeight);
    255 #endif
    256         return path;
    257     }
    258 
    259     // Draw a single path several times, shrinking it, flipping its direction
    260     // and changing its start vertex each time.
    261     void drawPath(SkCanvas* canvas, int index, SkPoint* offset) {
    262 
    263         SkPoint center;
    264         {
    265             SkPath path = GetPath(index, SkPath::kCW_Direction);
    266             if (offset->fX+path.getBounds().width() > kGMWidth) {
    267                 offset->fX = 0;
    268                 offset->fY += kMaxPathHeight;
    269                 if (fDoStrokeAndFill) {
    270                     offset->fX += kStrokeWidth / 2.0f;
    271                     offset->fY += kStrokeWidth / 2.0f;
    272                 }
    273             }
    274             center = { offset->fX + SkScalarHalf(path.getBounds().width()), offset->fY};
    275             offset->fX += path.getBounds().width();
    276             if (fDoStrokeAndFill) {
    277                 offset->fX += kStrokeWidth;
    278             }
    279         }
    280 
    281         const SkColor colors[2] = { SK_ColorBLACK, SK_ColorWHITE };
    282         const SkPath::Direction dirs[2] = { SkPath::kCW_Direction, SkPath::kCCW_Direction };
    283         const float scales[] = { 1.0f, 0.75f, 0.5f, 0.25f, 0.1f, 0.01f, 0.001f };
    284         const SkPaint::Join joins[3] = { SkPaint::kRound_Join,
    285                                          SkPaint::kBevel_Join,
    286                                          SkPaint::kMiter_Join };
    287 
    288         SkPaint paint;
    289         paint.setAntiAlias(true);
    290 
    291         for (size_t i = 0; i < SK_ARRAY_COUNT(scales); ++i) {
    292             SkPath path = GetPath(index, dirs[i%2]);
    293             if (fDoStrokeAndFill) {
    294                 paint.setStyle(SkPaint::kStrokeAndFill_Style);
    295                 paint.setStrokeJoin(joins[i%3]);
    296                 paint.setStrokeWidth(SkIntToScalar(kStrokeWidth));
    297             }
    298 
    299             canvas->save();
    300                 canvas->translate(center.fX, center.fY);
    301                 canvas->scale(scales[i], scales[i]);
    302                 paint.setColor(colors[i%2]);
    303                 canvas->drawPath(path, paint);
    304             canvas->restore();
    305         }
    306     }
    307 
    308     void onDraw(SkCanvas* canvas) override {
    309         // the right edge of the last drawn path
    310         SkPoint offset = { 0, SkScalarHalf(kMaxPathHeight) };
    311         if (fDoStrokeAndFill) {
    312             offset.fX += kStrokeWidth / 2.0f;
    313             offset.fY += kStrokeWidth / 2.0f;
    314         }
    315 
    316         for (int i = 0; i < kNumPaths; ++i) {
    317             this->drawPath(canvas, i, &offset);
    318         }
    319 
    320         // Repro for crbug.com/472723 (Missing AA on portions of graphic with GPU rasterization)
    321         {
    322             canvas->translate(356.0f, 50.0f);
    323 
    324             SkPaint p;
    325             p.setAntiAlias(true);
    326             if (fDoStrokeAndFill) {
    327                 p.setStyle(SkPaint::kStrokeAndFill_Style);
    328                 p.setStrokeJoin(SkPaint::kMiter_Join);
    329                 p.setStrokeWidth(SkIntToScalar(kStrokeWidth));
    330             }
    331 
    332             SkPath p1;
    333             p1.moveTo(60.8522949f, 364.671021f);
    334             p1.lineTo(59.4380493f, 364.671021f);
    335             p1.lineTo(385.414276f, 690.647217f);
    336             p1.lineTo(386.121399f, 689.940125f);
    337             canvas->drawPath(p1, p);
    338         }
    339     }
    340 
    341 private:
    342     static constexpr int kStrokeWidth   = 10;
    343     static constexpr int kNumPaths      = 20;
    344     static constexpr int kMaxPathHeight = 100;
    345     static constexpr int kGMWidth       = 512;
    346     static constexpr int kGMHeight      = 512;
    347 
    348     bool fDoStrokeAndFill;
    349 
    350     typedef GM INHERITED;
    351 };
    352 
    353 // This GM is intended to exercise the insetting of convex polygons
    354 class ConvexPolygonInsetGM : public GM {
    355 public:
    356     ConvexPolygonInsetGM() {
    357         this->setBGColor(0xFFFFFFFF);
    358     }
    359 
    360 protected:
    361     SkString onShortName() override {
    362         return SkString("convex-polygon-inset");
    363     }
    364     SkISize onISize() override { return SkISize::Make(kGMWidth, kGMHeight); }
    365     bool runAsBench() const override { return true; }
    366 
    367     static void GetPath(int index, SkPath::Direction dir,
    368                         std::unique_ptr<SkPoint[]>* data, int* numPts) {
    369         if (index < (int)SK_ARRAY_COUNT(ConvexLineOnlyData::gPoints)) {
    370             // manually specified
    371             *numPts = (int)ConvexLineOnlyData::gSizes[index];
    372             data->reset(new SkPoint[*numPts]);
    373             if (SkPath::kCW_Direction == dir) {
    374                 for (int i = 0; i < *numPts; ++i) {
    375                     (*data)[i] = ConvexLineOnlyData::gPoints[index][i];
    376                 }
    377             } else {
    378                 for (int i = 0; i < *numPts; ++i) {
    379                     (*data)[i] = ConvexLineOnlyData::gPoints[index][*numPts - i - 1];
    380                 }
    381             }
    382         } else {
    383             // procedurally generated
    384             SkScalar width = kMaxPathHeight / 2;
    385             SkScalar height = kMaxPathHeight / 2;
    386             switch (index - SK_ARRAY_COUNT(ConvexLineOnlyData::gPoints)) {
    387                 case 0:
    388                     *numPts = 3;
    389                     break;
    390                 case 1:
    391                     *numPts = 4;
    392                     break;
    393                 case 2:
    394                     *numPts = 5;
    395                     break;
    396                 case 3:             // squashed pentagon
    397                     *numPts = 5;
    398                     width = kMaxPathHeight / 5;
    399                     break;
    400                 case 4:
    401                     *numPts = 6;
    402                     break;
    403                 case 5:
    404                     *numPts = 8;
    405                     break;
    406                 case 6:              // squashed octogon
    407                     *numPts = 8;
    408                     width = kMaxPathHeight / 5;
    409                     break;
    410                 case 7:
    411                     *numPts = 20;
    412                     break;
    413                 case 8:
    414                     *numPts = 100;
    415                     break;
    416                 default:
    417                     *numPts = 3;
    418                     break;
    419             }
    420 
    421             data->reset(new SkPoint[*numPts]);
    422 
    423             create_ngon(*numPts, data->get(), width, height);
    424             if (SkPath::kCCW_Direction == dir) {
    425                 // reverse it
    426                 for (int i = 0; i < *numPts/2; ++i) {
    427                     SkPoint tmp = (*data)[i];
    428                     (*data)[i] = (*data)[*numPts - i - 1];
    429                     (*data)[*numPts - i - 1] = tmp;
    430                 }
    431             }
    432         }
    433     }
    434 
    435     // Draw a single path several times, shrinking it, flipping its direction
    436     // and changing its start vertex each time.
    437     void drawPath(SkCanvas* canvas, int index, SkPoint* offset) {
    438 
    439         SkPoint center;
    440         {
    441             std::unique_ptr<SkPoint[]> data(nullptr);
    442             int numPts;
    443             GetPath(index, SkPath::kCW_Direction, &data, &numPts);
    444             SkRect bounds;
    445             bounds.set(data.get(), numPts);
    446             if (offset->fX + bounds.width() > kGMWidth) {
    447                 offset->fX = 0;
    448                 offset->fY += kMaxPathHeight;
    449             }
    450             center = { offset->fX + SkScalarHalf(bounds.width()), offset->fY };
    451             offset->fX += bounds.width();
    452         }
    453 
    454         const SkPath::Direction dirs[2] = { SkPath::kCW_Direction, SkPath::kCCW_Direction };
    455         const float insets[] = { 5, 10, 15, 20, 25, 30, 35, 40 };
    456         const SkColor colors[] = { 0xFF901313, 0xFF8D6214, 0xFF698B14, 0xFF1C8914,
    457                                    0xFF148755, 0xFF146C84, 0xFF142482, 0xFF4A1480 };
    458 
    459         SkPaint paint;
    460         paint.setAntiAlias(true);
    461         paint.setStyle(SkPaint::kStroke_Style);
    462         paint.setStrokeWidth(1);
    463 
    464         std::unique_ptr<SkPoint[]> data(nullptr);
    465         int numPts;
    466         GetPath(index, dirs[index % 2], &data, &numPts);
    467         {
    468             SkPath path;
    469             path.moveTo(data.get()[0]);
    470             for (int i = 1; i < numPts; ++i) {
    471                 path.lineTo(data.get()[i]);
    472             }
    473             path.close();
    474             canvas->save();
    475             canvas->translate(center.fX, center.fY);
    476             canvas->drawPath(path, paint);
    477             canvas->restore();
    478         }
    479 
    480         SkTDArray<SkPoint> insetPoly;
    481         for (size_t i = 0; i < SK_ARRAY_COUNT(insets); ++i) {
    482             if (SkInsetConvexPolygon(data.get(), numPts, insets[i], &insetPoly)) {
    483                 SkPath path;
    484                 path.moveTo(insetPoly[0]);
    485                 for (int i = 1; i < insetPoly.count(); ++i) {
    486                     path.lineTo(insetPoly[i]);
    487                 }
    488                 path.close();
    489 
    490                 paint.setColor(colors[i]);
    491                 canvas->save();
    492                 canvas->translate(center.fX, center.fY);
    493                 canvas->drawPath(path, paint);
    494                 canvas->restore();
    495             }
    496         }
    497     }
    498 
    499     void onDraw(SkCanvas* canvas) override {
    500         // the right edge of the last drawn path
    501         SkPoint offset = { 0, SkScalarHalf(kMaxPathHeight) };
    502 
    503         for (int i = 0; i < kNumPaths; ++i) {
    504             this->drawPath(canvas, i, &offset);
    505         }
    506     }
    507 
    508 private:
    509     static constexpr int kNumPaths = 20;
    510     static constexpr int kMaxPathHeight = 100;
    511     static constexpr int kGMWidth = 512;
    512     static constexpr int kGMHeight = 512;
    513 
    514     typedef GM INHERITED;
    515 };
    516 
    517 //////////////////////////////////////////////////////////////////////////////
    518 
    519 DEF_GM(return new ConvexLineOnlyPathsGM(false);)
    520 DEF_GM(return new ConvexLineOnlyPathsGM(true);)
    521 DEF_GM(return new ConvexPolygonInsetGM();)
    522 }
    523