/* * 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 "Sample.h" #include "SkBlendMode.h" #include "SkCanvas.h" #include "SkClipOpPriv.h" #include "SkColor.h" #include "SkImageInfo.h" #include "SkMatrix.h" #include "SkPaint.h" #include "SkPath.h" #include "SkPoint.h" #include "SkPointPriv.h" #include "SkRect.h" #include "SkRefCnt.h" #include "SkScalar.h" #include "SkShader.h" #include "SkString.h" #include "SkSurface.h" #include "SkTypes.h" #include "ToolUtils.h" class SkEvent; #define FAT_PIXEL_COLOR SK_ColorBLACK #define PIXEL_CENTER_SIZE 3 #define WIRE_FRAME_COLOR 0xFFFF0000 /*0xFF00FFFF*/ #define WIRE_FRAME_SIZE 1.5f static SkScalar apply_grid(SkScalar x) { const SkScalar grid = 2; return SkScalarRoundToScalar(x * grid) / grid; } static void apply_grid(SkPoint pts[], int count) { for (int i = 0; i < count; ++i) { pts[i].set(apply_grid(pts[i].fX), apply_grid(pts[i].fY)); } } static void erase(SkSurface* surface) { surface->getCanvas()->clear(SK_ColorTRANSPARENT); } class FatBits { public: FatBits() { fAA = false; fStyle = kHair_Style; fGrid = false; fShowSkeleton = true; fUseClip = false; fRectAsOval = false; fUseTriangle = false; fStrokeCap = SkPaint::kButt_Cap; fClipRect.set(2, 2, 11, 8 ); } int getZoom() const { return fZoom; } bool getAA() const { return fAA; } void setAA(bool aa) { fAA = aa; } bool getGrid() const { return fGrid; } void setGrid(bool g) { fGrid = g; } bool getShowSkeleton() const { return fShowSkeleton; } void setShowSkeleton(bool ss) { fShowSkeleton = ss; } bool getTriangle() const { return fUseTriangle; } void setTriangle(bool ut) { fUseTriangle = ut; } void toggleRectAsOval() { fRectAsOval = !fRectAsOval; } void togglePixelColors() { if (fShader == fShader0) { fShader = fShader1; } else { fShader = fShader0; } } float fStrokeWidth = 1; bool getUseClip() const { return fUseClip; } void setUseClip(bool uc) { fUseClip = uc; } enum Style { kHair_Style, kStroke_Style, }; Style getStyle() const { return fStyle; } void setStyle(Style s) { fStyle = s; } void setWHZ(int width, int height, int zoom) { fW = width; fH = height; fZoom = zoom; fBounds.set(0, 0, SkIntToScalar(width * zoom), SkIntToScalar(height * zoom)); fMatrix.setScale(SkIntToScalar(zoom), SkIntToScalar(zoom)); fInverse.setScale(SK_Scalar1 / zoom, SK_Scalar1 / zoom); fShader0 = ToolUtils::create_checkerboard_shader(0xFFDDDDDD, 0xFFFFFFFF, zoom); fShader1 = SkShader::MakeColorShader(SK_ColorWHITE); fShader = fShader0; SkImageInfo info = SkImageInfo::MakeN32Premul(width, height); fMinSurface = SkSurface::MakeRaster(info); info = info.makeWH(width * zoom, height * zoom); fMaxSurface = SkSurface::MakeRaster(info); } void drawBG(SkCanvas*); void drawFG(SkCanvas*); void drawLine(SkCanvas*, SkPoint pts[2]); void drawRect(SkCanvas* canvas, SkPoint pts[2]); void drawTriangle(SkCanvas* canvas, SkPoint pts[3]); SkPaint::Cap fStrokeCap; private: bool fAA, fGrid, fShowSkeleton, fUseClip, fRectAsOval, fUseTriangle; Style fStyle; int fW, fH, fZoom; SkMatrix fMatrix, fInverse; SkRect fBounds, fClipRect; sk_sp fShader0; sk_sp fShader1; sk_sp fShader; sk_sp fMinSurface; sk_sp fMaxSurface; void setupPaint(SkPaint* paint) { bool aa = this->getAA(); paint->setStrokeCap(fStrokeCap); switch (fStyle) { case kHair_Style: paint->setStrokeWidth(0); break; case kStroke_Style: paint->setStrokeWidth(fStrokeWidth); break; } paint->setAntiAlias(aa); } void setupSkeletonPaint(SkPaint* paint) { paint->setStyle(SkPaint::kStroke_Style); paint->setStrokeWidth(WIRE_FRAME_SIZE); paint->setColor(fShowSkeleton ? WIRE_FRAME_COLOR : 0); paint->setAntiAlias(true); } void drawTriangleSkeleton(SkCanvas* max, const SkPoint pts[]); void drawLineSkeleton(SkCanvas* max, const SkPoint pts[]); void drawRectSkeleton(SkCanvas* max, const SkRect& r) { SkPaint paint; this->setupSkeletonPaint(&paint); SkPath path; fRectAsOval ? path.addOval(r) : path.addRect(r); max->drawPath(path, paint); } void copyMinToMax() { erase(fMaxSurface.get()); SkCanvas* canvas = fMaxSurface->getCanvas(); canvas->save(); canvas->concat(fMatrix); fMinSurface->draw(canvas, 0, 0, nullptr); canvas->restore(); SkPaint paint; paint.setBlendMode(SkBlendMode::kClear); for (int iy = 1; iy < fH; ++iy) { SkScalar y = SkIntToScalar(iy * fZoom); canvas->drawLine(0, y - SK_ScalarHalf, 999, y - SK_ScalarHalf, paint); } for (int ix = 1; ix < fW; ++ix) { SkScalar x = SkIntToScalar(ix * fZoom); canvas->drawLine(x - SK_ScalarHalf, 0, x - SK_ScalarHalf, 999, paint); } } }; void FatBits::drawBG(SkCanvas* canvas) { SkPaint paint; paint.setShader(fShader); canvas->drawRect(fBounds, paint); paint.setShader(nullptr); } void FatBits::drawFG(SkCanvas* canvas) { SkPaint inner, outer; inner.setAntiAlias(true); inner.setColor(SK_ColorBLACK); inner.setStrokeWidth(PIXEL_CENTER_SIZE); outer.setAntiAlias(true); outer.setColor(SK_ColorWHITE); outer.setStrokeWidth(PIXEL_CENTER_SIZE + 2); SkScalar half = SkIntToScalar(fZoom) / 2; for (int iy = 0; iy < fH; ++iy) { SkScalar y = SkIntToScalar(iy * fZoom) + half; for (int ix = 0; ix < fW; ++ix) { SkScalar x = SkIntToScalar(ix * fZoom) + half; canvas->drawPoint(x, y, outer); canvas->drawPoint(x, y, inner); } } if (fUseClip) { SkPaint p; p.setStyle(SkPaint::kStroke_Style); p.setColor(SK_ColorLTGRAY); SkRect r = { fClipRect.fLeft * fZoom, fClipRect.fTop * fZoom, fClipRect.fRight * fZoom, fClipRect.fBottom * fZoom }; canvas->drawRect(r, p); } } void FatBits::drawLineSkeleton(SkCanvas* max, const SkPoint pts[]) { SkPaint paint; this->setupSkeletonPaint(&paint); SkPath path; path.moveTo(pts[0]); path.lineTo(pts[1]); if (fStyle == kStroke_Style) { SkPaint p; p.setStyle(SkPaint::kStroke_Style); p.setStrokeWidth(fStrokeWidth * fZoom); p.setStrokeCap(fStrokeCap); SkPath dst; p.getFillPath(path, &dst); path = dst; path.moveTo(pts[0]); path.lineTo(pts[1]); } max->drawPath(path, paint); } void FatBits::drawLine(SkCanvas* canvas, SkPoint pts[]) { SkPaint paint; fInverse.mapPoints(pts, 2); if (fGrid) { apply_grid(pts, 2); } erase(fMinSurface.get()); this->setupPaint(&paint); paint.setColor(FAT_PIXEL_COLOR); if (fUseClip) { fMinSurface->getCanvas()->save(); SkRect r = fClipRect; r.inset(SK_Scalar1/3, SK_Scalar1/3); fMinSurface->getCanvas()->clipRect(r, kIntersect_SkClipOp, true); } fMinSurface->getCanvas()->drawLine(pts[0], pts[1], paint); if (fUseClip) { fMinSurface->getCanvas()->restore(); } this->copyMinToMax(); SkCanvas* max = fMaxSurface->getCanvas(); fMatrix.mapPoints(pts, 2); this->drawLineSkeleton(max, pts); fMaxSurface->draw(canvas, 0, 0, nullptr); } void FatBits::drawRect(SkCanvas* canvas, SkPoint pts[2]) { SkPaint paint; fInverse.mapPoints(pts, 2); if (fGrid) { apply_grid(pts, 2); } SkRect r; r.set(pts, 2); erase(fMinSurface.get()); this->setupPaint(&paint); paint.setColor(FAT_PIXEL_COLOR); { SkCanvas* c = fMinSurface->getCanvas(); fRectAsOval ? c->drawOval(r, paint) : c->drawRect(r, paint); } this->copyMinToMax(); SkCanvas* max = fMaxSurface->getCanvas(); fMatrix.mapPoints(pts, 2); r.set(pts, 2); this->drawRectSkeleton(max, r); fMaxSurface->draw(canvas, 0, 0, nullptr); } void FatBits::drawTriangleSkeleton(SkCanvas* max, const SkPoint pts[]) { SkPaint paint; this->setupSkeletonPaint(&paint); SkPath path; path.moveTo(pts[0]); path.lineTo(pts[1]); path.lineTo(pts[2]); path.close(); max->drawPath(path, paint); } void FatBits::drawTriangle(SkCanvas* canvas, SkPoint pts[3]) { SkPaint paint; fInverse.mapPoints(pts, 3); if (fGrid) { apply_grid(pts, 3); } SkPath path; path.moveTo(pts[0]); path.lineTo(pts[1]); path.lineTo(pts[2]); path.close(); erase(fMinSurface.get()); this->setupPaint(&paint); paint.setColor(FAT_PIXEL_COLOR); fMinSurface->getCanvas()->drawPath(path, paint); this->copyMinToMax(); SkCanvas* max = fMaxSurface->getCanvas(); fMatrix.mapPoints(pts, 3); this->drawTriangleSkeleton(max, pts); fMaxSurface->draw(canvas, 0, 0, nullptr); } /////////////////////////////////////////////////////////////////////////////////////////////////// class IndexClick : public Sample::Click { int fIndex; public: IndexClick(Sample* v, int index) : Sample::Click(v), fIndex(index) {} static int GetIndex(Sample::Click* click) { return ((IndexClick*)click)->fIndex; } }; class DrawLineView : public Sample { FatBits fFB; SkPoint fPts[3]; bool fIsRect; int fZoom = 64; public: DrawLineView() { fFB.setWHZ(24*2, 16*2, fZoom); fPts[0].set(1, 1); fPts[1].set(5, 4); fPts[2].set(2, 6); SkMatrix::MakeScale(SkIntToScalar(fZoom)).mapPoints(fPts, 3); fIsRect = false; } void setStyle(FatBits::Style s) { fFB.setStyle(s); } protected: bool onQuery(Sample::Event* evt) override { if (Sample::TitleQ(*evt)) { Sample::TitleR(evt, "FatBits"); return true; } SkUnichar uni; if (Sample::CharQ(*evt, &uni)) { switch (uni) { case 'c': fFB.setUseClip(!fFB.getUseClip()); return true; case 'r': fIsRect = !fIsRect; return true; case 'o': fFB.toggleRectAsOval(); return true; case 'x': fFB.setGrid(!fFB.getGrid()); return true; case 's': if (FatBits::kStroke_Style == fFB.getStyle()) { this->setStyle(FatBits::kHair_Style); } else { this->setStyle(FatBits::kStroke_Style); } return true; case 'k': { const SkPaint::Cap caps[] = { SkPaint::kButt_Cap, SkPaint::kRound_Cap, SkPaint::kSquare_Cap, }; fFB.fStrokeCap = caps[(fFB.fStrokeCap + 1) % 3]; return true; } break; case 'a': fFB.setAA(!fFB.getAA()); return true; case 'w': fFB.setShowSkeleton(!fFB.getShowSkeleton()); return true; case 'g': fFB.togglePixelColors(); return true; case 't': fFB.setTriangle(!fFB.getTriangle()); return true; case '-': fFB.fStrokeWidth -= 0.125f; return true; case '=': fFB.fStrokeWidth += 0.125f; return true; } } return this->INHERITED::onQuery(evt); } void onDrawContent(SkCanvas* canvas) override { fFB.drawBG(canvas); if (fFB.getTriangle()) { fFB.drawTriangle(canvas, fPts); } else if (fIsRect) { fFB.drawRect(canvas, fPts); } else { fFB.drawLine(canvas, fPts); } fFB.drawFG(canvas); { SkString str; str.printf("%s %s %s", fFB.getAA() ? "AA" : "BW", FatBits::kHair_Style == fFB.getStyle() ? "Hair" : "Stroke", fFB.getUseClip() ? "clip" : "noclip"); SkPaint paint; paint.setColor(SK_ColorBLUE); SkFont font(nullptr, 16); canvas->drawString(str, 10, 16, font, paint); } } Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override { SkPoint pt = { x, y }; int index = -1; int count = fFB.getTriangle() ? 3 : 2; SkScalar tol = 12; for (int i = 0; i < count; ++i) { if (SkPointPriv::EqualsWithinTolerance(fPts[i], pt, tol)) { index = i; break; } } return new IndexClick(this, index); } bool onClick(Click* click) override { int index = IndexClick::GetIndex(click); if (index >= 0 && index <= 2) { fPts[index] = click->fCurr; } else { SkScalar dx = click->fCurr.fX - click->fPrev.fX; SkScalar dy = click->fCurr.fY - click->fPrev.fY; fPts[0].offset(dx, dy); fPts[1].offset(dx, dy); fPts[2].offset(dx, dy); } return true; } private: typedef Sample INHERITED; }; ////////////////////////////////////////////////////////////////////////////// DEF_SAMPLE( return new DrawLineView(); )