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 12static 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 26namespace ConvexLineOnlyData { 27// narrow rect 28const 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 35const 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 42const 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 49const 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 56const 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 66const 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 73const 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 80const 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 90const 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 106const 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 125const 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 134const SkPoint* gPoints[] = { 135 gPoints0, gPoints1, gPoints2, gPoints3, gPoints4, gPoints5, gPoints6, 136 gPoints7, gPoints8, gPoints9, gPoints10, 137}; 138 139const 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}; 152static_assert(SK_ARRAY_COUNT(gSizes) == SK_ARRAY_COUNT(gPoints), "array_mismatch"); 153} 154 155namespace skiagm { 156 157// This GM is intended to exercise Ganesh's handling of convex line-only 158// paths 159class ConvexLineOnlyPathsGM : public GM { 160public: 161 ConvexLineOnlyPathsGM(bool doStrokeAndFill) : fDoStrokeAndFill(doStrokeAndFill) { 162 this->setBGColor(0xFFFFFFFF); 163 } 164 165protected: 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 341private: 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 354class ConvexPolygonInsetGM : public GM { 355public: 356 ConvexPolygonInsetGM() { 357 this->setBGColor(0xFFFFFFFF); 358 } 359 360protected: 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 508private: 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 519DEF_GM(return new ConvexLineOnlyPathsGM(false);) 520DEF_GM(return new ConvexLineOnlyPathsGM(true);) 521DEF_GM(return new ConvexPolygonInsetGM();) 522} 523