1/* 2 * Copyright 2011 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 "include/core/SkCanvas.h" 9#include "include/core/SkColorPriv.h" 10#include "include/core/SkFont.h" 11#include "include/core/SkPaint.h" 12#include "include/core/SkPathBuilder.h" 13#include "include/utils/SkRandom.h" 14#include "samplecode/Sample.h" 15#include "src/core/SkPathPriv.h" 16#include "tools/Resources.h" 17 18constexpr int W = 150; 19constexpr int H = 200; 20 21static void show_text(SkCanvas* canvas, bool doAA) { 22 SkRandom rand; 23 SkPaint paint; 24 SkFont font(nullptr, 20); 25 font.setEdging(doAA ? SkFont::Edging::kSubpixelAntiAlias : SkFont::Edging::kAlias); 26 27 for (int i = 0; i < 200; ++i) { 28 paint.setColor((SK_A32_MASK << SK_A32_SHIFT) | rand.nextU()); 29 canvas->drawString("Hamburgefons", rand.nextSScalar1() * W, rand.nextSScalar1() * H + 20, 30 font, paint); 31 } 32} 33 34static void show_fill(SkCanvas* canvas, bool doAA) { 35 SkRandom rand; 36 SkPaint paint; 37 paint.setAntiAlias(doAA); 38 39 for (int i = 0; i < 50; ++i) { 40 SkRect r; 41 42 r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, 43 rand.nextUScalar1() * W, rand.nextUScalar1() * H); 44 paint.setColor(rand.nextU()); 45 canvas->drawRect(r, paint); 46 47 r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, 48 rand.nextUScalar1() * W, rand.nextUScalar1() * H); 49 paint.setColor(rand.nextU()); 50 canvas->drawOval(r, paint); 51 } 52} 53 54static SkScalar randRange(SkRandom& rand, SkScalar min, SkScalar max) { 55 SkASSERT(min <= max); 56 return min + rand.nextUScalar1() * (max - min); 57} 58 59static void show_stroke(SkCanvas* canvas, bool doAA, SkScalar strokeWidth, int n) { 60 SkRandom rand; 61 SkPaint paint; 62 paint.setAntiAlias(doAA); 63 paint.setStyle(SkPaint::kStroke_Style); 64 paint.setStrokeWidth(strokeWidth); 65 66 for (int i = 0; i < n; ++i) { 67 SkRect r; 68 69 r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, 70 rand.nextUScalar1() * W, rand.nextUScalar1() * H); 71 paint.setColor(rand.nextU()); 72 canvas->drawRect(r, paint); 73 74 r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, 75 rand.nextUScalar1() * W, rand.nextUScalar1() * H); 76 paint.setColor(rand.nextU()); 77 canvas->drawOval(r, paint); 78 79 const SkScalar minx = -SkIntToScalar(W)/4; 80 const SkScalar maxx = 5*SkIntToScalar(W)/4; 81 const SkScalar miny = -SkIntToScalar(H)/4; 82 const SkScalar maxy = 5*SkIntToScalar(H)/4; 83 paint.setColor(rand.nextU()); 84 canvas->drawLine(randRange(rand, minx, maxx), randRange(rand, miny, maxy), 85 randRange(rand, minx, maxx), randRange(rand, miny, maxy), 86 paint); 87 } 88} 89 90static void show_hair(SkCanvas* canvas, bool doAA) { 91 show_stroke(canvas, doAA, 0, 150); 92} 93 94static void show_thick(SkCanvas* canvas, bool doAA) { 95 show_stroke(canvas, doAA, SkIntToScalar(5), 50); 96} 97 98typedef void (*CanvasProc)(SkCanvas*, bool); 99 100class ClipView : public Sample { 101 SkString name() override { return SkString("Clip"); } 102 103 void onDrawContent(SkCanvas* canvas) override { 104 canvas->drawColor(SK_ColorWHITE); 105 canvas->translate(SkIntToScalar(20), SkIntToScalar(20)); 106 107 static const CanvasProc gProc[] = { 108 show_text, show_thick, show_hair, show_fill 109 }; 110 111 SkRect r = { 0, 0, SkIntToScalar(W), SkIntToScalar(H) }; 112 r.inset(SK_Scalar1 / 4, SK_Scalar1 / 4); 113 SkPath clipPath = SkPathBuilder().addRRect(SkRRect::MakeRectXY(r, 20, 20)).detach(); 114 115// clipPath.toggleInverseFillType(); 116 117 for (int aa = 0; aa <= 1; ++aa) { 118 canvas->save(); 119 for (size_t i = 0; i < SK_ARRAY_COUNT(gProc); ++i) { 120 canvas->save(); 121 canvas->clipPath(clipPath, SkClipOp::kIntersect, SkToBool(aa)); 122// canvas->drawColor(SK_ColorWHITE); 123 gProc[i](canvas, SkToBool(aa)); 124 canvas->restore(); 125 canvas->translate(W * SK_Scalar1 * 8 / 7, 0); 126 } 127 canvas->restore(); 128 canvas->translate(0, H * SK_Scalar1 * 8 / 7); 129 } 130 } 131}; 132 133DEF_SAMPLE( return new ClipView(); ) 134 135/////////////////////////////////////////////////////////////////////////////// 136 137struct SkHalfPlane { 138 SkScalar fA, fB, fC; 139 140 SkScalar eval(SkScalar x, SkScalar y) const { 141 return fA * x + fB * y + fC; 142 } 143 SkScalar operator()(SkScalar x, SkScalar y) const { return this->eval(x, y); } 144 145 bool twoPts(SkPoint pts[2]) const { 146 // normalize plane to help with the perpendicular step, below 147 SkScalar len = SkScalarSqrt(fA*fA + fB*fB); 148 if (!len) { 149 return false; 150 } 151 SkScalar denom = SkScalarInvert(len); 152 SkScalar a = fA * denom; 153 SkScalar b = fB * denom; 154 SkScalar c = fC * denom; 155 156 // We compute p0 on the half-plane by setting one of the components to 0 157 // We compute p1 by stepping from p0 along a perpendicular to the normal 158 if (b) { 159 pts[0] = { 0, -c / b }; 160 pts[1] = { b, pts[0].fY - a}; 161 } else if (a) { 162 pts[0] = { -c / a, 0 }; 163 pts[1] = { pts[0].fX + b, -a }; 164 } else { 165 return false; 166 } 167 168 SkASSERT(SkScalarNearlyZero(this->operator()(pts[0].fX, pts[0].fY))); 169 SkASSERT(SkScalarNearlyZero(this->operator()(pts[1].fX, pts[1].fY))); 170 return true; 171 } 172 173 enum Result { 174 kAllNegative, 175 kAllPositive, 176 kMixed 177 }; 178 Result test(const SkRect& bounds) const { 179 SkPoint diagMin, diagMax; 180 if (fA >= 0) { 181 diagMin.fX = bounds.fLeft; 182 diagMax.fX = bounds.fRight; 183 } else { 184 diagMin.fX = bounds.fRight; 185 diagMax.fX = bounds.fLeft; 186 } 187 if (fB >= 0) { 188 diagMin.fY = bounds.fTop; 189 diagMax.fY = bounds.fBottom; 190 } else { 191 diagMin.fY = bounds.fBottom; 192 diagMax.fY = bounds.fTop; 193 } 194 SkScalar test = this->eval(diagMin.fX, diagMin.fY); 195 SkScalar sign = test*this->eval(diagMax.fX, diagMin.fY); 196 if (sign > 0) { 197 // the path is either all on one side of the half-plane or the other 198 if (test < 0) { 199 return kAllNegative; 200 } else { 201 return kAllPositive; 202 } 203 } 204 return kMixed; 205 } 206}; 207 208#include "src/core/SkEdgeClipper.h" 209 210static SkPath clip(const SkPath& path, SkPoint p0, SkPoint p1) { 211 SkMatrix mx, inv; 212 SkVector v = p1 - p0; 213 mx.setAll(v.fX, -v.fY, p0.fX, 214 v.fY, v.fX, p0.fY, 215 0, 0, 1); 216 SkAssertResult(mx.invert(&inv)); 217 218 SkPath rotated; 219 path.transform(inv, &rotated); 220 221 SkScalar big = 1e28f; 222 SkRect clip = {-big, 0, big, big }; 223 224 struct Rec { 225 SkPathBuilder fResult; 226 SkPoint fPrev = {0, 0}; 227 } rec; 228 229 SkEdgeClipper::ClipPath(rotated, clip, false, 230 [](SkEdgeClipper* clipper, bool newCtr, void* ctx) { 231 Rec* rec = (Rec*)ctx; 232 233 bool addLineTo = false; 234 SkPoint pts[4]; 235 SkPath::Verb verb; 236 while ((verb = clipper->next(pts)) != SkPath::kDone_Verb) { 237 if (newCtr) { 238 rec->fResult.moveTo(pts[0]); 239 rec->fPrev = pts[0]; 240 newCtr = false; 241 } 242 243 if (addLineTo || pts[0] != rec->fPrev) { 244 rec->fResult.lineTo(pts[0]); 245 } 246 247 switch (verb) { 248 case SkPath::kLine_Verb: 249 rec->fResult.lineTo(pts[1]); 250 rec->fPrev = pts[1]; 251 break; 252 case SkPath::kQuad_Verb: 253 rec->fResult.quadTo(pts[1], pts[2]); 254 rec->fPrev = pts[2]; 255 break; 256 case SkPath::kCubic_Verb: 257 rec->fResult.cubicTo(pts[1], pts[2], pts[3]); 258 rec->fPrev = pts[3]; 259 break; 260 default: break; 261 } 262 addLineTo = true; 263 } 264 }, &rec); 265 266 return rec.fResult.detach().makeTransform(mx); 267} 268 269static void draw_halfplane(SkCanvas* canvas, SkPoint p0, SkPoint p1, SkColor c) { 270 SkVector v = p1 - p0; 271 p0 = p0 - v * 1000; 272 p1 = p1 + v * 1000; 273 274 SkPaint paint; 275 paint.setColor(c); 276 canvas->drawLine(p0, p1, paint); 277} 278 279static SkPath make_path() { 280 SkRandom rand; 281 auto rand_pt = [&rand]() { 282 auto x = rand.nextF(); 283 auto y = rand.nextF(); 284 return SkPoint{x * 400, y * 400}; 285 }; 286 287 SkPathBuilder path; 288 for (int i = 0; i < 4; ++i) { 289 SkPoint pts[6]; 290 for (auto& p : pts) { 291 p = rand_pt(); 292 } 293 path.moveTo(pts[0]).quadTo(pts[1], pts[2]).quadTo(pts[3], pts[4]).lineTo(pts[5]); 294 } 295 return path.detach(); 296} 297 298class HalfPlaneView : public Sample { 299 SkPoint fPts[2]; 300 SkPath fPath; 301 302 SkString name() override { return SkString("halfplane"); } 303 304 void onOnceBeforeDraw() override { 305 fPts[0] = {0, 0}; 306 fPts[1] = {3, 2}; 307 fPath = make_path(); 308 } 309 310 void onDrawContent(SkCanvas* canvas) override { 311 SkPaint paint; 312 313 paint.setColor({0.5f, 0.5f, 0.5f, 1.0f}, nullptr); 314 canvas->drawPath(fPath, paint); 315 316 paint.setColor({0, 0, 0, 1}, nullptr); 317 318 canvas->drawPath(clip(fPath, fPts[0], fPts[1]), paint); 319 320 draw_halfplane(canvas, fPts[0], fPts[1], SK_ColorRED); 321 } 322 323 Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { 324 return new Click; 325 } 326 327 bool onClick(Click* click) override { 328 fPts[0] = click->fCurr; 329 fPts[1] = fPts[0] + SkPoint{3, 2}; 330 return true; 331 } 332}; 333DEF_SAMPLE( return new HalfPlaneView(); ) 334 335static void draw_halfplane(SkCanvas* canvas, const SkHalfPlane& p, SkColor c) { 336 SkPoint pts[2]; 337 p.twoPts(pts); 338 draw_halfplane(canvas, pts[0], pts[1], c); 339} 340 341static void compute_half_planes(const SkMatrix& mx, SkScalar width, SkScalar height, 342 SkHalfPlane planes[4]) { 343 SkScalar a = mx[0], b = mx[1], c = mx[2], 344 d = mx[3], e = mx[4], f = mx[5], 345 g = mx[6], h = mx[7], i = mx[8]; 346 347 planes[0] = { 2*g - 2*a/width, 2*h - 2*b/width, 2*i - 2*c/width }; 348 planes[1] = { 2*a/width, 2*b/width, 2*c/width }; 349 planes[2] = { 2*g - 2*d/height, 2*h - 2*e/height, 2*i - 2*f/height }; 350 planes[3] = { 2*d/height, 2*e/height, 2*f/height }; 351} 352 353class HalfPlaneView2 : public Sample { 354 SkPoint fPts[4]; 355 SkPath fPath; 356 357 SkString name() override { return SkString("halfplane2"); } 358 359 void onOnceBeforeDraw() override { 360 fPath = make_path(); 361 SkRect r = fPath.getBounds(); 362 r.toQuad(fPts); 363 } 364 365 void onDrawContent(SkCanvas* canvas) override { 366 SkMatrix mx; 367 { 368 SkRect r = fPath.getBounds(); 369 SkPoint src[4]; 370 r.toQuad(src); 371 mx.setPolyToPoly(src, fPts, 4); 372 } 373 374 SkPaint paint; 375 canvas->drawPath(fPath, paint); 376 377 canvas->save(); 378 canvas->concat(mx); 379 paint.setColor(0x40FF0000); 380 canvas->drawPath(fPath, paint); 381 canvas->restore(); 382 383 // draw the frame 384 paint.setStrokeWidth(10); 385 paint.setColor(SK_ColorGREEN); 386 canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, fPts, paint); 387 388 // draw the half-planes 389 SkHalfPlane planes[4]; 390 compute_half_planes(mx, 400, 400, planes); 391 for (auto& p : planes) { 392 draw_halfplane(canvas, p, SK_ColorRED); 393 } 394 } 395 396 Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { 397 SkScalar r = 8; 398 SkRect rect = SkRect::MakeXYWH(x - r, y - r, 2*r, 2*r); 399 for (int i = 0; i < 4; ++i) { 400 if (rect.contains(fPts[i].fX, fPts[i].fY)) { 401 Click* c = new Click; 402 c->fMeta.setS32("index", i); 403 return c; 404 } 405 } 406 return nullptr; 407 } 408 409 bool onClick(Click* click) override { 410 int32_t index; 411 SkAssertResult(click->fMeta.findS32("index", &index)); 412 SkASSERT(index >= 0 && index < 4); 413 fPts[index] = click->fCurr; 414 return true; 415 } 416}; 417DEF_SAMPLE( return new HalfPlaneView2(); ) 418 419static SkM44 inv(const SkM44& m) { 420 SkM44 inverse; 421 SkAssertResult(m.invert(&inverse)); 422 return inverse; 423} 424 425static SkHalfPlane half_plane_w0(const SkMatrix& m) { 426 return { m[SkMatrix::kMPersp0], m[SkMatrix::kMPersp1], m[SkMatrix::kMPersp2] - 0.05f }; 427} 428 429class SampleCameraView : public Sample { 430 float fNear = 0.05f; 431 float fFar = 4; 432 float fAngle = SK_ScalarPI / 4; 433 434 SkV3 fEye { 0, 0, 1.0f/tan(fAngle/2) - 1 }; 435 SkV3 fCOA { 0, 0, 0 }; 436 SkV3 fUp { 0, 1, 0 }; 437 438 SkM44 fRot; 439 SkV3 fTrans; 440 441 void rotate(float x, float y, float z) { 442 SkM44 r; 443 if (x) { 444 r.setRotateUnit({1, 0, 0}, x); 445 } else if (y) { 446 r.setRotateUnit({0, 1, 0}, y); 447 } else { 448 r.setRotateUnit({0, 0, 1}, z); 449 } 450 fRot = r * fRot; 451 } 452 453public: 454 SkM44 get44(const SkRect& r) const { 455 SkScalar w = r.width(); 456 SkScalar h = r.height(); 457 458 SkM44 camera = SkM44::LookAt(fEye, fCOA, fUp), 459 perspective = SkM44::Perspective(fNear, fFar, fAngle), 460 translate = SkM44::Translate(fTrans.x, fTrans.y, fTrans.z), 461 viewport = SkM44::Translate(r.centerX(), r.centerY(), 0) * 462 SkM44::Scale(w*0.5f, h*0.5f, 1); 463 464 return viewport * perspective * camera * translate * fRot * inv(viewport); 465 } 466 467 bool onChar(SkUnichar uni) override { 468 float delta = SK_ScalarPI / 30; 469 switch (uni) { 470 case '8': this->rotate( delta, 0, 0); return true; 471 case '2': this->rotate(-delta, 0, 0); return true; 472 case '4': this->rotate(0, delta, 0); return true; 473 case '6': this->rotate(0, -delta, 0); return true; 474 case '-': this->rotate(0, 0, delta); return true; 475 case '+': this->rotate(0, 0, -delta); return true; 476 477 case 'i': fTrans.z += 0.1f; SkDebugf("z %g\n", fTrans.z); return true; 478 case 'k': fTrans.z -= 0.1f; SkDebugf("z %g\n", fTrans.z); return true; 479 480 case 'n': fNear += 0.1f; SkDebugf("near %g\n", fNear); return true; 481 case 'N': fNear -= 0.1f; SkDebugf("near %g\n", fNear); return true; 482 case 'f': fFar += 0.1f; SkDebugf("far %g\n", fFar); return true; 483 case 'F': fFar -= 0.1f; SkDebugf("far %g\n", fFar); return true; 484 default: break; 485 } 486 return false; 487 } 488}; 489 490class HalfPlaneView3 : public SampleCameraView { 491 SkPath fPath; 492 sk_sp<SkShader> fShader; 493 bool fShowUnclipped = false; 494 495 SkString name() override { return SkString("halfplane3"); } 496 497 void onOnceBeforeDraw() override { 498 fPath = make_path(); 499 fShader = GetResourceAsImage("images/mandrill_128.png") 500 ->makeShader(SkSamplingOptions(), SkMatrix::Scale(3, 3)); 501 } 502 503 bool onChar(SkUnichar uni) override { 504 switch (uni) { 505 case 'u': fShowUnclipped = !fShowUnclipped; return true; 506 default: break; 507 } 508 return this->SampleCameraView::onChar(uni); 509 } 510 511 void onDrawContent(SkCanvas* canvas) override { 512 SkM44 mx = this->get44({0, 0, 400, 400}); 513 514 SkPaint paint; 515 paint.setColor({0.75, 0.75, 0.75, 1}); 516 canvas->drawPath(fPath, paint); 517 518 paint.setShader(fShader); 519 520 if (fShowUnclipped) { 521 canvas->save(); 522 canvas->concat(mx); 523 paint.setAlphaf(0.33f); 524 canvas->drawPath(fPath, paint); 525 paint.setAlphaf(1.f); 526 canvas->restore(); 527 } 528 529 530 SkColor planeColor = SK_ColorBLUE; 531 SkPath clippedPath, *path = &fPath; 532 if (SkPathPriv::PerspectiveClip(fPath, mx.asM33(), &clippedPath)) { 533 path = &clippedPath; 534 planeColor = SK_ColorRED; 535 } 536 canvas->save(); 537 canvas->concat(mx); 538 canvas->drawPath(*path, paint); 539 canvas->restore(); 540 541 SkHalfPlane hpw = half_plane_w0(mx.asM33()); 542 draw_halfplane(canvas, hpw, planeColor); 543 } 544}; 545DEF_SAMPLE( return new HalfPlaneView3(); ) 546 547class HalfPlaneCoons : public SampleCameraView { 548 SkPoint fPatch[12]; 549 SkColor fColors[4] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorBLACK }; 550 SkPoint fTex[4] = {{0, 0}, {256, 0}, {256, 256}, {0, 256}}; 551 sk_sp<SkShader> fShader; 552 553 bool fShowHandles = false; 554 bool fShowSkeleton = false; 555 bool fShowTex = false; 556 557 SkString name() override { return SkString("halfplane-coons"); } 558 559 void onOnceBeforeDraw() override { 560 fPatch[0] = { 0, 0 }; 561 fPatch[1] = { 100, 0 }; 562 fPatch[2] = { 200, 0 }; 563 fPatch[3] = { 300, 0 }; 564 fPatch[4] = { 300, 100 }; 565 fPatch[5] = { 300, 200 }; 566 fPatch[6] = { 300, 300 }; 567 fPatch[7] = { 200, 300 }; 568 fPatch[8] = { 100, 300 }; 569 fPatch[9] = { 0, 300 }; 570 fPatch[10] = { 0, 200 }; 571 fPatch[11] = { 0, 100 }; 572 573 fShader = GetResourceAsImage("images/mandrill_256.png")->makeShader(SkSamplingOptions()); 574 } 575 576 void onDrawContent(SkCanvas* canvas) override { 577 SkPaint paint; 578 579 canvas->save(); 580 canvas->concat(this->get44({0, 0, 300, 300})); 581 582 const SkPoint* tex = nullptr; 583 const SkColor* col = nullptr; 584 if (!fShowSkeleton) { 585 if (fShowTex) { 586 paint.setShader(fShader); 587 tex = fTex; 588 } else { 589 col = fColors; 590 } 591 } 592 canvas->drawPatch(fPatch, col, tex, SkBlendMode::kSrc, paint); 593 paint.setShader(nullptr); 594 595 if (fShowHandles) { 596 paint.setAntiAlias(true); 597 paint.setStrokeCap(SkPaint::kRound_Cap); 598 paint.setStrokeWidth(8); 599 canvas->drawPoints(SkCanvas::kPoints_PointMode, 12, fPatch, paint); 600 paint.setColor(SK_ColorWHITE); 601 paint.setStrokeWidth(6); 602 canvas->drawPoints(SkCanvas::kPoints_PointMode, 12, fPatch, paint); 603 } 604 605 canvas->restore(); 606 } 607 608 Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { 609 auto dist = [](SkPoint a, SkPoint b) { return (b - a).length(); }; 610 611 const float tol = 15; 612 for (int i = 0; i < 12; ++i) { 613 if (dist({x,y}, fPatch[i]) <= tol) { 614 return new Click([this, i](Click* c) { 615 fPatch[i] = c->fCurr; 616 return true; 617 }); 618 } 619 } 620 return nullptr; 621 } 622 623 bool onChar(SkUnichar uni) override { 624 switch (uni) { 625 case 'h': fShowHandles = !fShowHandles; return true; 626 case 'k': fShowSkeleton = !fShowSkeleton; return true; 627 case 't': fShowTex = !fShowTex; return true; 628 default: break; 629 } 630 return this->SampleCameraView::onChar(uni); 631 } 632 633}; 634DEF_SAMPLE( return new HalfPlaneCoons(); ) 635