1/* 2 * Copyright 2015 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "SkPath.h" 9#include "SkStream.h" 10#include "gm.h" 11 12 13// Test how short paths are stroked with various caps 14class StrokeZeroGM : public skiagm::GM { 15 SkPath fPaths[8]; 16 SkPath fClipL, fClipR, fClipS; 17 18protected: 19 void onOnceBeforeDraw() override { 20 fClipL.moveTo(0, 0); 21 fClipL.lineTo(3, 0); 22 fClipL.lineTo(2.5f, 1); 23 fClipL.lineTo(3.5f, 2.5f); 24 fClipL.lineTo(2.5f, 4); 25 fClipL.lineTo(3, 5); 26 fClipL.lineTo(0, 5); 27 fClipL.close(); 28 29 fClipR.moveTo(34, 0); 30 fClipR.lineTo(34, 5); 31 fClipR.lineTo(31, 5); 32 fClipR.lineTo(30.5, 4); 33 fClipR.lineTo(31.5, 2.5); 34 fClipR.lineTo(30.5, 1); 35 fClipR.lineTo(31, 0); 36 fClipR.close(); 37 38 fClipS.addRect(SkRect::MakeIWH(4, 5)); 39 40 fPaths[0].moveTo(30, 0); // single line segment 41 fPaths[0].rLineTo(30, 0); 42 43 fPaths[1].moveTo(90, 0); // single line segment with close (does not draw caps) 44 fPaths[1].rLineTo(30, 0); 45 fPaths[1].close(); 46 47 fPaths[2].moveTo(150, 0); // zero-length line 48 fPaths[2].rLineTo(0, 0); 49 50 fPaths[3].moveTo(180, 0); // zero-length line with close (expected not to draw) 51 fPaths[3].rLineTo(0, 0); 52 fPaths[3].close(); 53 54 fPaths[4].moveTo(210, 0); // close only, no line 55 fPaths[4].close(); 56 57 fPaths[5].moveTo(30, 90); // all combos below should draw two caps 58 fPaths[5].rLineTo(0, 0); 59 fPaths[5].moveTo(60, 90); 60 fPaths[5].rLineTo(0, 0); 61 62 fPaths[6].moveTo(90, 90); 63 fPaths[6].close(); 64 fPaths[6].moveTo(120, 90); 65 fPaths[6].close(); 66 67 fPaths[7].moveTo(150, 90); 68 fPaths[7].rLineTo(0, 0); 69 fPaths[7].moveTo(180, 90); 70 fPaths[7].close(); 71 } 72 73 74 SkString onShortName() override { 75 return SkString("path_stroke_with_zero_length"); 76 } 77 78 SkISize onISize() override { 79 return SkISize::Make(1120, 840); 80 } 81 82 void onDraw(SkCanvas* canvas) override { 83 SkPaint bkgrnd; 84 bkgrnd.setColor(SK_ColorWHITE); 85 canvas->drawRect(SkRect::MakeIWH(onISize().fWidth, onISize().fHeight), bkgrnd); 86 87 auto drawPaths = [&](SkPaint& paint, int indexMask) { 88 canvas->translate(0, 30.0f); 89 int index = 0; 90 for (const SkPath& path : fPaths) { 91 if (indexMask & (1 << index)) { 92 canvas->drawPath(path, paint); 93 } 94 if (paint.getStrokeWidth() < 2) { 95 drawFat(canvas, path, paint, index); 96 } 97 ++index; 98 } 99 }; 100 101 if (false) { // debugging variant that draws a single element 102 SkScalar width = 0; 103 bool antialias = true; 104 105 SkPaint butt; 106 butt.setAntiAlias(antialias); 107 butt.setStyle(SkPaint::kStroke_Style); 108 butt.setStrokeWidth(width); 109 110 SkPaint round(butt); 111 round.setStrokeCap(SkPaint::kRound_Cap); 112 drawPaths(round, 1 << 7); 113 return; 114 } 115 116 SkScalar widths[] = { 0, .999f, 1, 1.001f, 20 }; 117 bool aliases[] = { false, true }; 118 for (bool antialias : aliases) { 119 canvas->save(); 120 for (SkScalar width : widths) { 121 canvas->save(); 122 SkPaint butt; 123 butt.setAntiAlias(antialias); 124 butt.setStyle(SkPaint::kStroke_Style); 125 butt.setStrokeWidth(width); 126 drawPaths(butt, -1); 127 128 SkPaint round(butt); 129 round.setStrokeCap(SkPaint::kRound_Cap); 130 drawPaths(round, -1); 131 132 SkPaint square(butt); 133 square.setStrokeCap(SkPaint::kSquare_Cap); 134 drawPaths(square, -1); 135 canvas->restore(); 136 canvas->translate(220, 0); 137 } 138 canvas->restore(); 139 canvas->translate(0, 210); 140 } 141 } 142 143private: 144 void drawFat(SkCanvas* canvas, const SkPath& path, const SkPaint& paint, int index) { 145 const SkScalar scale = 10; 146 SkRect bounds = path.getBounds(); 147 SkBitmap offscreen; 148 offscreen.allocN32Pixels(SkScalarRoundToInt(bounds.width() + 4), 149 SkScalarRoundToInt(bounds.height() + 4)); 150 offscreen.eraseColor(SK_ColorWHITE); 151 SkScalar pathX = bounds.fLeft - 2; 152 SkScalar pathY = bounds.fTop - 2; 153 SkMatrix cMatrix = canvas->getTotalMatrix(); 154 if (!canvas->readPixels(&offscreen, SkScalarRoundToInt(pathX + cMatrix.getTranslateX()), 155 SkScalarRoundToInt(pathY + cMatrix.getTranslateY()))) { 156 return; 157 } 158 159 canvas->save(); 160 SkMatrix clipM; 161 clipM.reset(); 162 clipM.preScale(scale, scale); 163 clipM.postTranslate(bounds.fLeft - 17, bounds.fTop - 24.5f + 420); 164 SkPath clip; 165 if (index < 2) { 166 fClipL.transform(clipM, &clip); 167 } else { 168 fClipS.transform(clipM, &clip); 169 } 170 canvas->clipPath(clip, SkRegion::kIntersect_Op, true); 171 canvas->scale(scale, scale); 172 canvas->drawBitmap(offscreen, (bounds.fLeft - 17) / scale, 173 (bounds.fTop - 20 + 420) / scale); 174 canvas->restore(); 175 176 if (bounds.width() > 20) { 177 canvas->save(); 178 clipM.reset(); 179 clipM.preScale(scale, scale); 180 clipM.postTranslate(bounds.fLeft - 17 - 275, bounds.fTop - 24.5f + 420); 181 SkPath clip; 182 fClipR.transform(clipM, &clip); 183 canvas->clipPath(clip, SkRegion::kIntersect_Op, true); 184 canvas->scale(10.f, 10.f); 185 canvas->drawBitmap(offscreen, (bounds.fLeft - 17 - 275 186 + (index >= 5 ? 5 : 0)) / scale, (bounds.fTop - 20 + 420) / scale); 187 canvas->restore(); 188 } 189 } 190 191}; 192 193/////////////////////////////////////////////////////////////////////////////// 194 195DEF_GM( return new StrokeZeroGM(); ) 196 197