text: /*
   * 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 "sk_tool_utils.h"
  #include "SampleCode.h"
  #include "SkView.h"
  #include "SkCanvas.h"
  #include "SkPath.h"
  #include "SkRegion.h"
  #include "SkShader.h"
  #include "SkUtils.h"
  #include "SkImage.h"
  #include "SkSurface.h"
  
  #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;
          }
      }
  
      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 = sk_tool_utils::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<SkShader>     fShader0;
      sk_sp<SkShader>     fShader1;
      sk_sp<SkShader>     fShader;
      sk_sp<SkSurface>    fMinSurface;
      sk_sp<SkSurface>    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(SK_Scalar1);
                  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(SK_Scalar1 * 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, SkCanvas::kIntersect_Op, true);
      }
      fMinSurface->getCanvas()->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, 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 SkView::Click {
      int fIndex;
  public:
      IndexClick(SkView* v, int index) : SkView::Click(v), fIndex(index) {}
  
      static int GetIndex(SkView::Click* click) {
          return ((IndexClick*)click)->fIndex;
      }
  };
  
  class DrawLineView : public SampleView {
      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);
          this->inval(nullptr);
      }
  
  protected:
      bool onQuery(SkEvent* evt) override {
          if (SampleCode::TitleQ(*evt)) {
              SampleCode::TitleR(evt, "FatBits");
              return true;
          }
          SkUnichar uni;
          if (SampleCode::CharQ(*evt, &uni)) {
              switch (uni) {
                  case 'c':
                      fFB.setUseClip(!fFB.getUseClip());
                      this->inval(nullptr);
                      return true;
                  case 'r':
                      fIsRect = !fIsRect;
                      this->inval(nullptr);
                      return true;
                  case 'o':
                      fFB.toggleRectAsOval();
                      this->inval(nullptr);
                      return true;
                  case 'x':
                      fFB.setGrid(!fFB.getGrid());
                      this->inval(nullptr);
                      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];
                      this->inval(nullptr);
                      return true;
                  } break;
                  case 'a':
                      fFB.setAA(!fFB.getAA());
                      this->inval(nullptr);
                      return true;
                  case 'w':
                      fFB.setShowSkeleton(!fFB.getShowSkeleton());
                      this->inval(nullptr);
                      return true;
                  case 'g':
                      fFB.togglePixelColors();
                      this->inval(nullptr);
                      return true;
                  case 't':
                      fFB.setTriangle(!fFB.getTriangle());
                      this->inval(nullptr);
                      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.setAntiAlias(true);
              paint.setTextSize(16);
              paint.setColor(SK_ColorBLUE);
              canvas->drawText(str.c_str(), str.size(), 10, 16, paint);
          }
      }
  
      SkView::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 (fPts[i].equalsWithinTolerance(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);
          }
          this->inval(nullptr);
          return true;
      }
  
  private:
  
      typedef SampleView INHERITED;
  };
  
  //////////////////////////////////////////////////////////////////////////////
  
  static SkView* MyFactory() { return new DrawLineView; }
  static SkViewRegister reg(MyFactory);
  