|  | /* | 
|  | * Copyright 2012 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include "SampleCode.h" | 
|  | #include "SkView.h" | 
|  | #include "SkCanvas.h" | 
|  | #include "SkRandom.h" | 
|  | #include "SkRRect.h" | 
|  | #include "SkColorPriv.h" | 
|  | #include "SkStrokerPriv.h" | 
|  |  | 
|  | static void rotateAbout(SkCanvas* canvas, SkScalar degrees, | 
|  | SkScalar cx, SkScalar cy) { | 
|  | canvas->translate(cx, cy); | 
|  | canvas->rotate(degrees); | 
|  | canvas->translate(-cx, -cy); | 
|  | } | 
|  |  | 
|  | class RotateCirclesView : public SampleView { | 
|  | public: | 
|  | RotateCirclesView() { | 
|  | this->setBGColor(SK_ColorLTGRAY); | 
|  |  | 
|  | fAngle = 0; | 
|  | } | 
|  |  | 
|  | protected: | 
|  | // overrides from SkEventSink | 
|  | virtual bool onQuery(SkEvent* evt) { | 
|  | if (SampleCode::TitleQ(*evt)) { | 
|  | SampleCode::TitleR(evt, "RotateCircles"); | 
|  | return true; | 
|  | } | 
|  | return this->INHERITED::onQuery(evt); | 
|  | } | 
|  |  | 
|  | virtual void onDrawContent(SkCanvas* canvas) { | 
|  | SkRandom rand; | 
|  | SkPaint paint; | 
|  | paint.setAntiAlias(true); | 
|  | paint.setStrokeWidth(20); | 
|  |  | 
|  | SkScalar cx = 240; | 
|  | SkScalar cy = 240; | 
|  | SkScalar DX = 240 * 2; | 
|  | SkColor color = 0; | 
|  |  | 
|  | float scale = 1; | 
|  | float sign = 0.3f; | 
|  | for (SkScalar rad = 200; rad >= 20; rad -= 15) { | 
|  | sign = -sign; | 
|  | scale += 0.2f; | 
|  |  | 
|  | paint.setColor(rand.nextU()); | 
|  | paint.setAlpha(0xFF); | 
|  | color = ~color; | 
|  |  | 
|  | paint.setStyle(SkPaint::kFill_Style); | 
|  |  | 
|  | canvas->save(); | 
|  | rotateAbout(canvas, fAngle * scale * sign, cx, cy); | 
|  | canvas->drawCircle(cx, cy, rad, paint); | 
|  | canvas->restore(); | 
|  |  | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  | paint.setStrokeWidth(rad*2); | 
|  |  | 
|  | canvas->save(); | 
|  | rotateAbout(canvas, fAngle * scale * sign, cx + DX, cy); | 
|  | canvas->drawCircle(cx + DX, cy, 10, paint); | 
|  | canvas->restore(); | 
|  |  | 
|  | canvas->save(); | 
|  | rotateAbout(canvas, fAngle * scale * sign, cx + DX, cy + DX); | 
|  | canvas->drawCircle(cx + DX, cy + DX, 10, paint); | 
|  | canvas->restore(); | 
|  |  | 
|  | } | 
|  |  | 
|  | fAngle = (fAngle + 1) % 360; | 
|  | this->inval(NULL); | 
|  | } | 
|  |  | 
|  | private: | 
|  | int fAngle; | 
|  | typedef SkView INHERITED; | 
|  | }; | 
|  |  | 
|  | class TestCirclesView : public SampleView { | 
|  | public: | 
|  | TestCirclesView() { | 
|  | } | 
|  |  | 
|  | protected: | 
|  | virtual bool onQuery(SkEvent* evt) SK_OVERRIDE { | 
|  | if (SampleCode::TitleQ(*evt)) { | 
|  | SampleCode::TitleR(evt, "RotateCircles2"); | 
|  | return true; | 
|  | } | 
|  | return this->INHERITED::onQuery(evt); | 
|  | } | 
|  |  | 
|  | void draw_real_circle(SkCanvas* canvas, SkScalar radius) { | 
|  | int w = SkScalarCeilToInt(radius * 2); | 
|  | int h = w; | 
|  |  | 
|  | SkBitmap bm; | 
|  | bm.allocN32Pixels(w, h); | 
|  | bm.eraseColor(0); | 
|  |  | 
|  | SkAutoLockPixels alp(bm); | 
|  |  | 
|  | SkScalar cx = radius; | 
|  | SkScalar cy = radius; | 
|  | for (int y = 0; y < h; y += 1) { | 
|  | for (int x = 0; x < w; x += 1) { | 
|  | float d = sqrtf((x - cx)*(x - cx) + (y - cy)*(y - cy)); | 
|  | if (d <= radius) { | 
|  | *bm.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0, 0); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | canvas->drawBitmap(bm, 0, 0, NULL); | 
|  | } | 
|  |  | 
|  | virtual void onDrawContent(SkCanvas* canvas) { | 
|  | SkScalar radius = 256; | 
|  | canvas->translate(10, 10); | 
|  |  | 
|  | draw_real_circle(canvas, radius); | 
|  |  | 
|  | SkPaint paint; | 
|  | paint.setAntiAlias(true); | 
|  |  | 
|  | paint.setColor(0x80FF0000); | 
|  | canvas->drawCircle(radius, radius, radius, paint); | 
|  |  | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  | paint.setStrokeWidth(radius); | 
|  | paint.setColor(0x8000FF00); | 
|  | canvas->drawCircle(radius, radius, radius/2, paint); | 
|  | } | 
|  |  | 
|  | private: | 
|  | typedef SkView INHERITED; | 
|  | }; | 
|  |  | 
|  | static bool hittest(const SkPoint& target, SkScalar x, SkScalar y) { | 
|  | const SkScalar TOL = 7; | 
|  | return SkPoint::Distance(target, SkPoint::Make(x, y)) <= TOL; | 
|  | } | 
|  |  | 
|  | static int getOnCurvePoints(const SkPath& path, SkPoint storage[]) { | 
|  | SkPath::RawIter iter(path); | 
|  | SkPoint pts[4]; | 
|  | SkPath::Verb verb; | 
|  |  | 
|  | int count = 0; | 
|  | while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { | 
|  | switch (verb) { | 
|  | case SkPath::kMove_Verb: | 
|  | case SkPath::kLine_Verb: | 
|  | case SkPath::kQuad_Verb: | 
|  | case SkPath::kCubic_Verb: | 
|  | storage[count++] = pts[0]; | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | #include "SkPathMeasure.h" | 
|  |  | 
|  | struct StrokeTypeButton { | 
|  | SkRect fBounds; | 
|  | char fLabel; | 
|  | bool fEnabled; | 
|  | }; | 
|  |  | 
|  | class TestStrokeView : public SampleView { | 
|  | enum { | 
|  | SKELETON_COLOR = 0xFF0000FF, | 
|  | WIREFRAME_COLOR = 0x80FF0000 | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | kCount = 9 | 
|  | }; | 
|  | SkPoint fPts[kCount]; | 
|  | SkRect fErrorControl; | 
|  | SkRect fWidthControl; | 
|  | StrokeTypeButton fCubicButton; | 
|  | StrokeTypeButton fQuadButton; | 
|  | StrokeTypeButton fRRectButton; | 
|  | SkScalar fWidth, fDWidth; | 
|  | bool fAnimate; | 
|  | #if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) | 
|  | #define kStrokerErrorMin 0.001f | 
|  | #define kStrokerErrorMax 5 | 
|  | #endif | 
|  | #define kWidthMin 1 | 
|  | #define kWidthMax 100 | 
|  | public: | 
|  | TestStrokeView() { | 
|  | this->setBGColor(SK_ColorLTGRAY); | 
|  |  | 
|  | fPts[0].set(50, 200); | 
|  | fPts[1].set(50, 100); | 
|  | fPts[2].set(150, 50); | 
|  | fPts[3].set(300, 50); | 
|  |  | 
|  | fPts[4].set(350, 200); | 
|  | fPts[5].set(350, 100); | 
|  | fPts[6].set(450, 50); | 
|  |  | 
|  | fPts[7].set(200, 200); | 
|  | fPts[8].set(400, 400); | 
|  |  | 
|  | fWidth = 50; | 
|  | fDWidth = 0.25f; | 
|  |  | 
|  | fCubicButton.fLabel = 'C'; | 
|  | fCubicButton.fEnabled = true; | 
|  | fQuadButton.fLabel = 'Q'; | 
|  | fQuadButton.fEnabled = true; | 
|  | fRRectButton.fLabel = 'R'; | 
|  | fRRectButton.fEnabled = true; | 
|  | fAnimate = true; | 
|  | } | 
|  |  | 
|  | protected: | 
|  | virtual bool onQuery(SkEvent* evt) SK_OVERRIDE { | 
|  | if (SampleCode::TitleQ(*evt)) { | 
|  | SampleCode::TitleR(evt, "RotateCircles3"); | 
|  | return true; | 
|  | } | 
|  | return this->INHERITED::onQuery(evt); | 
|  | } | 
|  |  | 
|  | virtual void onSizeChange() { | 
|  | fErrorControl.setXYWH(this->width() - 100, 30, 30, 400); | 
|  | fWidthControl.setXYWH(this->width() -  50, 30, 30, 400); | 
|  | fCubicButton.fBounds.setXYWH(this->width() - 50, 450, 30, 30); | 
|  | fQuadButton.fBounds.setXYWH(this->width() - 50, 500, 30, 30); | 
|  | fRRectButton.fBounds.setXYWH(this->width() - 50, 550, 30, 30); | 
|  | this->INHERITED::onSizeChange(); | 
|  | } | 
|  |  | 
|  | void draw_points(SkCanvas* canvas, const SkPath& path, SkColor color, | 
|  | bool show_lines) { | 
|  | SkPaint paint; | 
|  | paint.setColor(color); | 
|  | paint.setAlpha(0x80); | 
|  | paint.setAntiAlias(true); | 
|  | int n = path.countPoints(); | 
|  | SkAutoSTArray<32, SkPoint> pts(n); | 
|  | if (show_lines) { | 
|  | path.getPoints(pts.get(), n); | 
|  | canvas->drawPoints(SkCanvas::kPolygon_PointMode, n, pts.get(), paint); | 
|  | } else { | 
|  | n = getOnCurvePoints(path, pts.get()); | 
|  | } | 
|  | paint.setStrokeWidth(5); | 
|  | canvas->drawPoints(SkCanvas::kPoints_PointMode, n, pts.get(), paint); | 
|  | } | 
|  |  | 
|  | void draw_ribs(SkCanvas* canvas, const SkPath& path, SkScalar width, | 
|  | SkColor color) { | 
|  | const SkScalar radius = width / 2; | 
|  |  | 
|  | SkPathMeasure meas(path, false); | 
|  | SkScalar total = meas.getLength(); | 
|  |  | 
|  | SkScalar delta = 8; | 
|  | SkPaint paint; | 
|  | paint.setColor(color); | 
|  |  | 
|  | SkPoint pos, tan; | 
|  | for (SkScalar dist = 0; dist <= total; dist += delta) { | 
|  | if (meas.getPosTan(dist, &pos, &tan)) { | 
|  | tan.scale(radius); | 
|  | tan.rotateCCW(); | 
|  | canvas->drawLine(pos.x() + tan.x(), pos.y() + tan.y(), | 
|  | pos.x() - tan.x(), pos.y() - tan.y(), paint); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width) { | 
|  | SkPaint paint; | 
|  | paint.setAntiAlias(true); | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  |  | 
|  | paint.setColor(SKELETON_COLOR); | 
|  | canvas->drawPath(path, paint); | 
|  | draw_points(canvas, path, SKELETON_COLOR, true); | 
|  |  | 
|  | draw_ribs(canvas, path, width, 0xFF00FF00); | 
|  |  | 
|  | SkPath fill; | 
|  |  | 
|  | SkPaint p; | 
|  | p.setStyle(SkPaint::kStroke_Style); | 
|  | p.setStrokeWidth(width); | 
|  | p.getFillPath(path, &fill); | 
|  |  | 
|  | paint.setColor(WIREFRAME_COLOR); | 
|  | canvas->drawPath(fill, paint); | 
|  | draw_points(canvas, fill, WIREFRAME_COLOR, false); | 
|  | } | 
|  |  | 
|  | void draw_button(SkCanvas* canvas, const StrokeTypeButton& button) { | 
|  | SkPaint paint; | 
|  | paint.setAntiAlias(true); | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  | paint.setColor(button.fEnabled ? 0xFF3F0000 : 0x6F3F0000); | 
|  | canvas->drawRect(button.fBounds, paint); | 
|  | paint.setTextSize(25.0f); | 
|  | paint.setColor(button.fEnabled ? 0xFF3F0000 : 0x6F3F0000); | 
|  | paint.setTextAlign(SkPaint::kCenter_Align); | 
|  | paint.setStyle(SkPaint::kFill_Style); | 
|  | canvas->drawText(&button.fLabel, 1, button.fBounds.centerX(), button.fBounds.fBottom - 5, | 
|  | paint); | 
|  | } | 
|  |  | 
|  | void draw_control(SkCanvas* canvas, const SkRect& bounds, SkScalar value, | 
|  | SkScalar min, SkScalar max, const char* name) { | 
|  | SkPaint paint; | 
|  | paint.setAntiAlias(true); | 
|  | paint.setStyle(SkPaint::kStroke_Style); | 
|  | canvas->drawRect(bounds, paint); | 
|  | SkScalar scale = max - min; | 
|  | SkScalar yPos = bounds.fTop + (value - min) * bounds.height() / scale; | 
|  | paint.setColor(0xFFFF0000); | 
|  | canvas->drawLine(bounds.fLeft - 5, yPos, bounds.fRight + 5, yPos, paint); | 
|  | SkString label; | 
|  | label.printf("%0.3g", value); | 
|  | paint.setColor(0xFF000000); | 
|  | paint.setTextSize(11.0f); | 
|  | paint.setStyle(SkPaint::kFill_Style); | 
|  | canvas->drawText(label.c_str(), label.size(), bounds.fLeft + 5, yPos - 5, paint); | 
|  | paint.setTextSize(13.0f); | 
|  | canvas->drawText(name, strlen(name), bounds.fLeft, bounds.bottom() + 11, paint); | 
|  | } | 
|  |  | 
|  | virtual void onDrawContent(SkCanvas* canvas) { | 
|  | SkPath path; | 
|  | SkScalar width = fWidth; | 
|  |  | 
|  | if (fCubicButton.fEnabled) { | 
|  | path.moveTo(fPts[0]); | 
|  | path.cubicTo(fPts[1], fPts[2], fPts[3]); | 
|  | draw_stroke(canvas, path, width); | 
|  | } | 
|  |  | 
|  | if (fQuadButton.fEnabled) { | 
|  | path.reset(); | 
|  | path.moveTo(fPts[4]); | 
|  | path.quadTo(fPts[5], fPts[6]); | 
|  | draw_stroke(canvas, path, width); | 
|  | } | 
|  |  | 
|  | if (fRRectButton.fEnabled) { | 
|  | SkScalar rad = 32; | 
|  | SkRect r; | 
|  | r.set(&fPts[7], 2); | 
|  | path.reset(); | 
|  | SkRRect rr; | 
|  | rr.setRectXY(r, rad, rad); | 
|  | path.addRRect(rr); | 
|  | draw_stroke(canvas, path, width); | 
|  |  | 
|  | path.reset(); | 
|  | SkRRect rr2; | 
|  | rr.inset(width/2, width/2, &rr2); | 
|  | path.addRRect(rr2, SkPath::kCCW_Direction); | 
|  | rr.inset(-width/2, -width/2, &rr2); | 
|  | path.addRRect(rr2, SkPath::kCW_Direction); | 
|  | SkPaint paint; | 
|  | paint.setAntiAlias(true); | 
|  | paint.setColor(0x40FF8844); | 
|  | canvas->drawPath(path, paint); | 
|  | } | 
|  |  | 
|  | if (fAnimate) { | 
|  | fWidth += fDWidth; | 
|  | if (fDWidth > 0 && fWidth > kWidthMax) { | 
|  | fDWidth = -fDWidth; | 
|  | } else if (fDWidth < 0 && fWidth < kWidthMin) { | 
|  | fDWidth = -fDWidth; | 
|  | } | 
|  | } | 
|  | #if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) | 
|  | draw_control(canvas, fErrorControl, gDebugStrokerError, kStrokerErrorMin, kStrokerErrorMax, | 
|  | "error"); | 
|  | #endif | 
|  | draw_control(canvas, fWidthControl, fWidth, kWidthMin, kWidthMax, "width"); | 
|  | draw_button(canvas, fQuadButton); | 
|  | draw_button(canvas, fCubicButton); | 
|  | draw_button(canvas, fRRectButton); | 
|  | this->inval(NULL); | 
|  | } | 
|  |  | 
|  | class MyClick : public Click { | 
|  | public: | 
|  | int fIndex; | 
|  | MyClick(SkView* target, int index) : Click(target), fIndex(index) {} | 
|  | }; | 
|  |  | 
|  | virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, | 
|  | unsigned modi) SK_OVERRIDE { | 
|  | for (size_t i = 0; i < SK_ARRAY_COUNT(fPts); ++i) { | 
|  | if (hittest(fPts[i], x, y)) { | 
|  | return new MyClick(this, (int)i); | 
|  | } | 
|  | } | 
|  | const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1); | 
|  | #if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) | 
|  | if (fErrorControl.contains(rectPt)) { | 
|  | return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 1); | 
|  | } | 
|  | #endif | 
|  | if (fWidthControl.contains(rectPt)) { | 
|  | return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 3); | 
|  | } | 
|  | if (fCubicButton.fBounds.contains(rectPt)) { | 
|  | fCubicButton.fEnabled ^= true; | 
|  | return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 4); | 
|  | } | 
|  | if (fQuadButton.fBounds.contains(rectPt)) { | 
|  | fQuadButton.fEnabled ^= true; | 
|  | return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 5); | 
|  | } | 
|  | if (fRRectButton.fBounds.contains(rectPt)) { | 
|  | fRRectButton.fEnabled ^= true; | 
|  | return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 6); | 
|  | } | 
|  | return this->INHERITED::onFindClickHandler(x, y, modi); | 
|  | } | 
|  |  | 
|  | static SkScalar MapScreenYtoValue(int y, const SkRect& control, SkScalar min, | 
|  | SkScalar max) { | 
|  | return (SkIntToScalar(y) - control.fTop) / control.height() * (max - min) + min; | 
|  | } | 
|  |  | 
|  | virtual bool onClick(Click* click) { | 
|  | int index = ((MyClick*)click)->fIndex; | 
|  | if (index < (int) SK_ARRAY_COUNT(fPts)) { | 
|  | fPts[index].offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX), | 
|  | SkIntToScalar(click->fICurr.fY - click->fIPrev.fY)); | 
|  | this->inval(NULL); | 
|  | } | 
|  | #if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) | 
|  | else if (index == (int) SK_ARRAY_COUNT(fPts) + 1) { | 
|  | gDebugStrokerError = MapScreenYtoValue(click->fICurr.fY, fErrorControl, | 
|  | kStrokerErrorMin, kStrokerErrorMax); | 
|  | gDebugStrokerErrorSet = true; | 
|  | } | 
|  | #endif | 
|  | else if (index == (int) SK_ARRAY_COUNT(fPts) + 3) { | 
|  | fWidth = MapScreenYtoValue(click->fICurr.fY, fWidthControl, kWidthMin, kWidthMax); | 
|  | fAnimate = fWidth <= kWidthMin; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | typedef SkView INHERITED; | 
|  | }; | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | static SkView* F0() { return new RotateCirclesView; } | 
|  | static SkViewRegister gR0(F0); | 
|  | static SkView* F1() { return new TestCirclesView; } | 
|  | static SkViewRegister gR1(F1); | 
|  | static SkView* F2() { return new TestStrokeView; } | 
|  | static SkViewRegister gR2(F2); |