/* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkCanvas.h" #include "include/core/SkColorPriv.h" #include "include/core/SkFont.h" #include "include/core/SkPaint.h" #include "include/core/SkPathBuilder.h" #include "include/core/SkRRect.h" #include "include/utils/SkRandom.h" #include "samplecode/Sample.h" #include "src/core/SkPathPriv.h" #include "tools/Resources.h" constexpr int W = 150; constexpr int H = 200; static void show_text(SkCanvas* canvas, bool doAA) { SkRandom rand; SkPaint paint; SkFont font(nullptr, 20); font.setEdging(doAA ? SkFont::Edging::kSubpixelAntiAlias : SkFont::Edging::kAlias); for (int i = 0; i < 200; ++i) { paint.setColor((SK_A32_MASK << SK_A32_SHIFT) | rand.nextU()); canvas->drawString("Hamburgefons", rand.nextSScalar1() * W, rand.nextSScalar1() * H + 20, font, paint); } } static void show_fill(SkCanvas* canvas, bool doAA) { SkRandom rand; SkPaint paint; paint.setAntiAlias(doAA); for (int i = 0; i < 50; ++i) { SkRect r; r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, rand.nextUScalar1() * W, rand.nextUScalar1() * H); paint.setColor(rand.nextU()); canvas->drawRect(r, paint); r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, rand.nextUScalar1() * W, rand.nextUScalar1() * H); paint.setColor(rand.nextU()); canvas->drawOval(r, paint); } } static SkScalar randRange(SkRandom& rand, SkScalar min, SkScalar max) { SkASSERT(min <= max); return min + rand.nextUScalar1() * (max - min); } static void show_stroke(SkCanvas* canvas, bool doAA, SkScalar strokeWidth, int n) { SkRandom rand; SkPaint paint; paint.setAntiAlias(doAA); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(strokeWidth); for (int i = 0; i < n; ++i) { SkRect r; r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, rand.nextUScalar1() * W, rand.nextUScalar1() * H); paint.setColor(rand.nextU()); canvas->drawRect(r, paint); r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H, rand.nextUScalar1() * W, rand.nextUScalar1() * H); paint.setColor(rand.nextU()); canvas->drawOval(r, paint); const SkScalar minx = -SkIntToScalar(W)/4; const SkScalar maxx = 5*SkIntToScalar(W)/4; const SkScalar miny = -SkIntToScalar(H)/4; const SkScalar maxy = 5*SkIntToScalar(H)/4; paint.setColor(rand.nextU()); canvas->drawLine(randRange(rand, minx, maxx), randRange(rand, miny, maxy), randRange(rand, minx, maxx), randRange(rand, miny, maxy), paint); } } static void show_hair(SkCanvas* canvas, bool doAA) { show_stroke(canvas, doAA, 0, 150); } static void show_thick(SkCanvas* canvas, bool doAA) { show_stroke(canvas, doAA, SkIntToScalar(5), 50); } typedef void (*CanvasProc)(SkCanvas*, bool); class ClipView : public Sample { SkString name() override { return SkString("Clip"); } void onDrawContent(SkCanvas* canvas) override { canvas->drawColor(SK_ColorWHITE); canvas->translate(SkIntToScalar(20), SkIntToScalar(20)); static const CanvasProc gProc[] = { show_text, show_thick, show_hair, show_fill }; SkRect r = { 0, 0, SkIntToScalar(W), SkIntToScalar(H) }; r.inset(SK_Scalar1 / 4, SK_Scalar1 / 4); SkPath clipPath = SkPathBuilder().addRRect(SkRRect::MakeRectXY(r, 20, 20)).detach(); // clipPath.toggleInverseFillType(); for (int aa = 0; aa <= 1; ++aa) { canvas->save(); for (size_t i = 0; i < SK_ARRAY_COUNT(gProc); ++i) { canvas->save(); canvas->clipPath(clipPath, SkClipOp::kIntersect, SkToBool(aa)); // canvas->drawColor(SK_ColorWHITE); gProc[i](canvas, SkToBool(aa)); canvas->restore(); canvas->translate(W * SK_Scalar1 * 8 / 7, 0); } canvas->restore(); canvas->translate(0, H * SK_Scalar1 * 8 / 7); } } }; DEF_SAMPLE( return new ClipView(); ) /////////////////////////////////////////////////////////////////////////////// struct SkHalfPlane { SkScalar fA, fB, fC; SkScalar eval(SkScalar x, SkScalar y) const { return fA * x + fB * y + fC; } SkScalar operator()(SkScalar x, SkScalar y) const { return this->eval(x, y); } bool twoPts(SkPoint pts[2]) const { // normalize plane to help with the perpendicular step, below SkScalar len = SkScalarSqrt(fA*fA + fB*fB); if (!len) { return false; } SkScalar denom = SkScalarInvert(len); SkScalar a = fA * denom; SkScalar b = fB * denom; SkScalar c = fC * denom; // We compute p0 on the half-plane by setting one of the components to 0 // We compute p1 by stepping from p0 along a perpendicular to the normal if (b) { pts[0] = { 0, -c / b }; pts[1] = { b, pts[0].fY - a}; } else if (a) { pts[0] = { -c / a, 0 }; pts[1] = { pts[0].fX + b, -a }; } else { return false; } SkASSERT(SkScalarNearlyZero(this->operator()(pts[0].fX, pts[0].fY))); SkASSERT(SkScalarNearlyZero(this->operator()(pts[1].fX, pts[1].fY))); return true; } enum Result { kAllNegative, kAllPositive, kMixed }; Result test(const SkRect& bounds) const { SkPoint diagMin, diagMax; if (fA >= 0) { diagMin.fX = bounds.fLeft; diagMax.fX = bounds.fRight; } else { diagMin.fX = bounds.fRight; diagMax.fX = bounds.fLeft; } if (fB >= 0) { diagMin.fY = bounds.fTop; diagMax.fY = bounds.fBottom; } else { diagMin.fY = bounds.fBottom; diagMax.fY = bounds.fTop; } SkScalar test = this->eval(diagMin.fX, diagMin.fY); SkScalar sign = test*this->eval(diagMax.fX, diagMin.fY); if (sign > 0) { // the path is either all on one side of the half-plane or the other if (test < 0) { return kAllNegative; } else { return kAllPositive; } } return kMixed; } }; #include "src/core/SkEdgeClipper.h" static SkPath clip(const SkPath& path, SkPoint p0, SkPoint p1) { SkMatrix mx, inv; SkVector v = p1 - p0; mx.setAll(v.fX, -v.fY, p0.fX, v.fY, v.fX, p0.fY, 0, 0, 1); SkAssertResult(mx.invert(&inv)); SkPath rotated; path.transform(inv, &rotated); SkScalar big = 1e28f; SkRect clip = {-big, 0, big, big }; struct Rec { SkPathBuilder fResult; SkPoint fPrev = {0, 0}; } rec; SkEdgeClipper::ClipPath(rotated, clip, false, [](SkEdgeClipper* clipper, bool newCtr, void* ctx) { Rec* rec = (Rec*)ctx; bool addLineTo = false; SkPoint pts[4]; SkPath::Verb verb; while ((verb = clipper->next(pts)) != SkPath::kDone_Verb) { if (newCtr) { rec->fResult.moveTo(pts[0]); rec->fPrev = pts[0]; newCtr = false; } if (addLineTo || pts[0] != rec->fPrev) { rec->fResult.lineTo(pts[0]); } switch (verb) { case SkPath::kLine_Verb: rec->fResult.lineTo(pts[1]); rec->fPrev = pts[1]; break; case SkPath::kQuad_Verb: rec->fResult.quadTo(pts[1], pts[2]); rec->fPrev = pts[2]; break; case SkPath::kCubic_Verb: rec->fResult.cubicTo(pts[1], pts[2], pts[3]); rec->fPrev = pts[3]; break; default: break; } addLineTo = true; } }, &rec); return rec.fResult.detach().makeTransform(mx); } static void draw_halfplane(SkCanvas* canvas, SkPoint p0, SkPoint p1, SkColor c) { SkVector v = p1 - p0; p0 = p0 - v * 1000; p1 = p1 + v * 1000; SkPaint paint; paint.setColor(c); canvas->drawLine(p0, p1, paint); } static SkPath make_path() { SkRandom rand; auto rand_pt = [&rand]() { auto x = rand.nextF(); auto y = rand.nextF(); return SkPoint{x * 400, y * 400}; }; SkPathBuilder path; for (int i = 0; i < 4; ++i) { SkPoint pts[6]; for (auto& p : pts) { p = rand_pt(); } path.moveTo(pts[0]).quadTo(pts[1], pts[2]).quadTo(pts[3], pts[4]).lineTo(pts[5]); } return path.detach(); } class HalfPlaneView : public Sample { SkPoint fPts[2]; SkPath fPath; SkString name() override { return SkString("halfplane"); } void onOnceBeforeDraw() override { fPts[0] = {0, 0}; fPts[1] = {3, 2}; fPath = make_path(); } void onDrawContent(SkCanvas* canvas) override { SkPaint paint; paint.setColor({0.5f, 0.5f, 0.5f, 1.0f}, nullptr); canvas->drawPath(fPath, paint); paint.setColor({0, 0, 0, 1}, nullptr); canvas->drawPath(clip(fPath, fPts[0], fPts[1]), paint); draw_halfplane(canvas, fPts[0], fPts[1], SK_ColorRED); } Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { return new Click; } bool onClick(Click* click) override { fPts[0] = click->fCurr; fPts[1] = fPts[0] + SkPoint{3, 2}; return true; } }; DEF_SAMPLE( return new HalfPlaneView(); ) static void draw_halfplane(SkCanvas* canvas, const SkHalfPlane& p, SkColor c) { SkPoint pts[2]; p.twoPts(pts); draw_halfplane(canvas, pts[0], pts[1], c); } static void compute_half_planes(const SkMatrix& mx, SkScalar width, SkScalar height, SkHalfPlane planes[4]) { SkScalar a = mx[0], b = mx[1], c = mx[2], d = mx[3], e = mx[4], f = mx[5], g = mx[6], h = mx[7], i = mx[8]; planes[0] = { 2*g - 2*a/width, 2*h - 2*b/width, 2*i - 2*c/width }; planes[1] = { 2*a/width, 2*b/width, 2*c/width }; planes[2] = { 2*g - 2*d/height, 2*h - 2*e/height, 2*i - 2*f/height }; planes[3] = { 2*d/height, 2*e/height, 2*f/height }; } class HalfPlaneView2 : public Sample { SkPoint fPts[4]; SkPath fPath; SkString name() override { return SkString("halfplane2"); } void onOnceBeforeDraw() override { fPath = make_path(); SkRect r = fPath.getBounds(); r.toQuad(fPts); } void onDrawContent(SkCanvas* canvas) override { SkMatrix mx; { SkRect r = fPath.getBounds(); SkPoint src[4]; r.toQuad(src); mx.setPolyToPoly(src, fPts, 4); } SkPaint paint; canvas->drawPath(fPath, paint); canvas->save(); canvas->concat(mx); paint.setColor(0x40FF0000); canvas->drawPath(fPath, paint); canvas->restore(); // draw the frame paint.setStrokeWidth(10); paint.setColor(SK_ColorGREEN); canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, fPts, paint); // draw the half-planes SkHalfPlane planes[4]; compute_half_planes(mx, 400, 400, planes); for (auto& p : planes) { draw_halfplane(canvas, p, SK_ColorRED); } } Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { SkScalar r = 8; SkRect rect = SkRect::MakeXYWH(x - r, y - r, 2*r, 2*r); for (int i = 0; i < 4; ++i) { if (rect.contains(fPts[i].fX, fPts[i].fY)) { Click* c = new Click; c->fMeta.setS32("index", i); return c; } } return nullptr; } bool onClick(Click* click) override { int32_t index; SkAssertResult(click->fMeta.findS32("index", &index)); SkASSERT(index >= 0 && index < 4); fPts[index] = click->fCurr; return true; } }; DEF_SAMPLE( return new HalfPlaneView2(); ) static SkM44 inv(const SkM44& m) { SkM44 inverse; SkAssertResult(m.invert(&inverse)); return inverse; } static SkHalfPlane half_plane_w0(const SkMatrix& m) { return { m[SkMatrix::kMPersp0], m[SkMatrix::kMPersp1], m[SkMatrix::kMPersp2] - 0.05f }; } class SampleCameraView : public Sample { float fNear = 0.05f; float fFar = 4; float fAngle = SK_ScalarPI / 4; SkV3 fEye { 0, 0, 1.0f/tan(fAngle/2) - 1 }; SkV3 fCOA { 0, 0, 0 }; SkV3 fUp { 0, 1, 0 }; SkM44 fRot; SkV3 fTrans; void rotate(float x, float y, float z) { SkM44 r; if (x) { r.setRotateUnit({1, 0, 0}, x); } else if (y) { r.setRotateUnit({0, 1, 0}, y); } else { r.setRotateUnit({0, 0, 1}, z); } fRot = r * fRot; } public: SkM44 get44(const SkRect& r) const { SkScalar w = r.width(); SkScalar h = r.height(); SkM44 camera = SkM44::LookAt(fEye, fCOA, fUp), perspective = SkM44::Perspective(fNear, fFar, fAngle), translate = SkM44::Translate(fTrans.x, fTrans.y, fTrans.z), viewport = SkM44::Translate(r.centerX(), r.centerY(), 0) * SkM44::Scale(w*0.5f, h*0.5f, 1); return viewport * perspective * camera * translate * fRot * inv(viewport); } bool onChar(SkUnichar uni) override { float delta = SK_ScalarPI / 30; switch (uni) { case '8': this->rotate( delta, 0, 0); return true; case '2': this->rotate(-delta, 0, 0); return true; case '4': this->rotate(0, delta, 0); return true; case '6': this->rotate(0, -delta, 0); return true; case '-': this->rotate(0, 0, delta); return true; case '+': this->rotate(0, 0, -delta); return true; case 'i': fTrans.z += 0.1f; SkDebugf("z %g\n", fTrans.z); return true; case 'k': fTrans.z -= 0.1f; SkDebugf("z %g\n", fTrans.z); return true; case 'n': fNear += 0.1f; SkDebugf("near %g\n", fNear); return true; case 'N': fNear -= 0.1f; SkDebugf("near %g\n", fNear); return true; case 'f': fFar += 0.1f; SkDebugf("far %g\n", fFar); return true; case 'F': fFar -= 0.1f; SkDebugf("far %g\n", fFar); return true; default: break; } return false; } }; class HalfPlaneView3 : public SampleCameraView { SkPath fPath; sk_sp fShader; bool fShowUnclipped = false; SkString name() override { return SkString("halfplane3"); } void onOnceBeforeDraw() override { fPath = make_path(); fShader = GetResourceAsImage("images/mandrill_128.png") ->makeShader(SkSamplingOptions(), SkMatrix::Scale(3, 3)); } bool onChar(SkUnichar uni) override { switch (uni) { case 'u': fShowUnclipped = !fShowUnclipped; return true; default: break; } return this->SampleCameraView::onChar(uni); } void onDrawContent(SkCanvas* canvas) override { SkM44 mx = this->get44({0, 0, 400, 400}); SkPaint paint; paint.setColor({0.75, 0.75, 0.75, 1}); canvas->drawPath(fPath, paint); paint.setShader(fShader); if (fShowUnclipped) { canvas->save(); canvas->concat(mx); paint.setAlphaf(0.33f); canvas->drawPath(fPath, paint); paint.setAlphaf(1.f); canvas->restore(); } SkColor planeColor = SK_ColorBLUE; SkPath clippedPath, *path = &fPath; if (SkPathPriv::PerspectiveClip(fPath, mx.asM33(), &clippedPath)) { path = &clippedPath; planeColor = SK_ColorRED; } canvas->save(); canvas->concat(mx); canvas->drawPath(*path, paint); canvas->restore(); SkHalfPlane hpw = half_plane_w0(mx.asM33()); draw_halfplane(canvas, hpw, planeColor); } }; DEF_SAMPLE( return new HalfPlaneView3(); ) class HalfPlaneCoons : public SampleCameraView { SkPoint fPatch[12]; SkColor fColors[4] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorBLACK }; SkPoint fTex[4] = {{0, 0}, {256, 0}, {256, 256}, {0, 256}}; sk_sp fShader; bool fShowHandles = false; bool fShowSkeleton = false; bool fShowTex = false; SkString name() override { return SkString("halfplane-coons"); } void onOnceBeforeDraw() override { fPatch[0] = { 0, 0 }; fPatch[1] = { 100, 0 }; fPatch[2] = { 200, 0 }; fPatch[3] = { 300, 0 }; fPatch[4] = { 300, 100 }; fPatch[5] = { 300, 200 }; fPatch[6] = { 300, 300 }; fPatch[7] = { 200, 300 }; fPatch[8] = { 100, 300 }; fPatch[9] = { 0, 300 }; fPatch[10] = { 0, 200 }; fPatch[11] = { 0, 100 }; fShader = GetResourceAsImage("images/mandrill_256.png")->makeShader(SkSamplingOptions()); } void onDrawContent(SkCanvas* canvas) override { SkPaint paint; canvas->save(); canvas->concat(this->get44({0, 0, 300, 300})); const SkPoint* tex = nullptr; const SkColor* col = nullptr; if (!fShowSkeleton) { if (fShowTex) { paint.setShader(fShader); tex = fTex; } else { col = fColors; } } canvas->drawPatch(fPatch, col, tex, SkBlendMode::kSrc, paint); paint.setShader(nullptr); if (fShowHandles) { paint.setAntiAlias(true); paint.setStrokeCap(SkPaint::kRound_Cap); paint.setStrokeWidth(8); canvas->drawPoints(SkCanvas::kPoints_PointMode, 12, fPatch, paint); paint.setColor(SK_ColorWHITE); paint.setStrokeWidth(6); canvas->drawPoints(SkCanvas::kPoints_PointMode, 12, fPatch, paint); } canvas->restore(); } Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { auto dist = [](SkPoint a, SkPoint b) { return (b - a).length(); }; const float tol = 15; for (int i = 0; i < 12; ++i) { if (dist({x,y}, fPatch[i]) <= tol) { return new Click([this, i](Click* c) { fPatch[i] = c->fCurr; return true; }); } } return nullptr; } bool onChar(SkUnichar uni) override { switch (uni) { case 'h': fShowHandles = !fShowHandles; return true; case 'k': fShowSkeleton = !fShowSkeleton; return true; case 't': fShowTex = !fShowTex; return true; default: break; } return this->SampleCameraView::onChar(uni); } }; DEF_SAMPLE( return new HalfPlaneCoons(); )