| /* | |
| * 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 "include/core/SkBlendMode.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkFont.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPathBuilder.h" | |
| #include "include/core/SkPathUtils.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkShader.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/core/SkTypes.h" | |
| #include "src/core/SkPointPriv.h" | |
| #include "tools/ToolUtils.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| #include "tools/viewer/ClickHandlerSlide.h" | |
| #include "tools/viewer/Slide.h" | |
| #include <array> | |
| #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.setLTRB(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.setIWH(width * zoom, 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 = SkShaders::Color(SK_ColorWHITE); | |
| fShader = fShader0; | |
| SkImageInfo info = SkImageInfo::MakeN32Premul(width, height); | |
| fMinSurface = SkSurfaces::Raster(info); | |
| info = info.makeWH(width * zoom, height * zoom); | |
| fMaxSurface = SkSurfaces::Raster(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(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 ? SkPath::Oval(r) : SkPath::Rect(r); | |
| max->drawPath(path, paint); | |
| } | |
| void copyMinToMax() { | |
| erase(fMaxSurface.get()); | |
| SkCanvas* canvas = fMaxSurface->getCanvas(); | |
| canvas->save(); | |
| canvas->concat(fMatrix); | |
| fMinSurface->draw(canvas, 0, 0); | |
| 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 = SkPath::Line(pts[0], pts[1]); | |
| if (fStyle == kStroke_Style) { | |
| SkPaint p; | |
| p.setStyle(SkPaint::kStroke_Style); | |
| p.setStrokeWidth(fStrokeWidth * fZoom); | |
| p.setStrokeCap(fStrokeCap); | |
| SkPathBuilder dst; | |
| skpathutils::FillPathWithPaint(path, p, &dst); | |
| dst.moveTo(pts[0]); | |
| dst.lineTo(pts[1]); | |
| path = dst.detach(); | |
| } | |
| max->drawPath(path, paint); | |
| } | |
| void FatBits::drawLine(SkCanvas* canvas, SkPoint pts[2]) { | |
| 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, SkClipOp::kIntersect, 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); | |
| } | |
| void FatBits::drawRect(SkCanvas* canvas, SkPoint pts[2]) { | |
| SkPaint paint; | |
| fInverse.mapPoints({pts, 2}); | |
| if (fGrid) { | |
| apply_grid(pts, 2); | |
| } | |
| SkRect r = SkRect::BoundsOrEmpty({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 = SkRect::BoundsOrEmpty({pts, 2}); | |
| this->drawRectSkeleton(max, r); | |
| fMaxSurface->draw(canvas, 0, 0); | |
| } | |
| void FatBits::drawTriangleSkeleton(SkCanvas* max, const SkPoint pts[]) { | |
| SkPaint paint; | |
| this->setupSkeletonPaint(&paint); | |
| SkPath path = SkPath::Polygon({pts, 3}, true); | |
| 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 = SkPath::Polygon({pts, 3}, true); | |
| 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); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| class IndexClick : public ClickHandlerSlide::Click { | |
| int fIndex; | |
| public: | |
| IndexClick(int index) : fIndex(index) {} | |
| static int GetIndex(Click* click) { return ((IndexClick*)click)->fIndex; } | |
| }; | |
| class DrawLineSlide : public ClickHandlerSlide { | |
| FatBits fFB; | |
| SkPoint fPts[3]; | |
| bool fIsRect; | |
| int fZoom = 64; | |
| public: | |
| DrawLineSlide() { | |
| fFB.setWHZ(24*2, 16*2, fZoom); | |
| fPts[0].set(1, 1); | |
| fPts[1].set(5, 4); | |
| fPts[2].set(2, 6); | |
| SkMatrix::Scale(fZoom, fZoom).mapPoints(fPts); | |
| fIsRect = false; | |
| fName = "FatBits"; | |
| } | |
| void setStyle(FatBits::Style s) { fFB.setStyle(s); } | |
| bool onChar(SkUnichar uni) override { | |
| 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 auto caps = std::to_array<SkPaint::Cap>({ | |
| SkPaint::kButt_Cap, | |
| SkPaint::kRound_Cap, | |
| SkPaint::kSquare_Cap, | |
| }); | |
| fFB.fStrokeCap = caps[(fFB.fStrokeCap + 1) % 3]; | |
| return true; | |
| } | |
| 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 false; | |
| } | |
| void draw(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(ToolUtils::DefaultTypeface(), 16); | |
| canvas->drawString(str, 10, 16, font, paint); | |
| } | |
| } | |
| protected: | |
| Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey 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(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; | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| DEF_SLIDE(return new DrawLineSlide();) |