SampleThinAA.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /*
  2. * Copyright 2019 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. #include "samplecode/Sample.h"
  8. #include "include/core/SkCanvas.h"
  9. #include "include/core/SkColorFilter.h"
  10. #include "include/core/SkFont.h"
  11. #include "include/core/SkImage.h"
  12. #include "include/core/SkPath.h"
  13. #include "include/core/SkSurface.h"
  14. namespace skiagm {
  15. class ShapeRenderer : public SkRefCntBase {
  16. public:
  17. static constexpr SkScalar kTileWidth = 20.f;
  18. static constexpr SkScalar kTileHeight = 20.f;
  19. virtual ~ShapeRenderer() {}
  20. // Draw the shape, limited to kTileWidth x kTileHeight. It must apply the local subpixel (tx,
  21. // ty) translation and rotation by angle. Prior to these transform adjustments, the SkCanvas
  22. // will only have pixel aligned translations (these are separated to make super-sampling
  23. // renderers easier).
  24. virtual void draw(SkCanvas* canvas, SkPaint* paint,
  25. SkScalar tx, SkScalar ty, SkScalar angle) = 0;
  26. virtual SkString name() = 0;
  27. virtual sk_sp<ShapeRenderer> toHairline() = 0;
  28. void applyLocalTransform(SkCanvas* canvas, SkScalar tx, SkScalar ty, SkScalar angle) {
  29. canvas->translate(tx, ty);
  30. canvas->rotate(angle, kTileWidth / 2.f, kTileHeight / 2.f);
  31. }
  32. };
  33. class RectRenderer : public ShapeRenderer {
  34. public:
  35. static sk_sp<ShapeRenderer> Make() {
  36. return sk_sp<ShapeRenderer>(new RectRenderer());
  37. }
  38. SkString name() override { return SkString("rect"); }
  39. sk_sp<ShapeRenderer> toHairline() override {
  40. // Not really available but can't return nullptr
  41. return Make();
  42. }
  43. void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override {
  44. SkScalar width = paint->getStrokeWidth();
  45. paint->setStyle(SkPaint::kFill_Style);
  46. this->applyLocalTransform(canvas, tx, ty, angle);
  47. canvas->drawRect(SkRect::MakeLTRB(kTileWidth / 2.f - width / 2.f, 2.f,
  48. kTileWidth / 2.f + width / 2.f, kTileHeight - 2.f),
  49. *paint);
  50. }
  51. private:
  52. RectRenderer() {}
  53. typedef ShapeRenderer INHERITED;
  54. };
  55. class PathRenderer : public ShapeRenderer {
  56. public:
  57. static sk_sp<ShapeRenderer> MakeLine(bool hairline = false) {
  58. return MakeCurve(0.f, hairline);
  59. }
  60. static sk_sp<ShapeRenderer> MakeLines(SkScalar depth, bool hairline = false) {
  61. return MakeCurve(-depth, hairline);
  62. }
  63. static sk_sp<ShapeRenderer> MakeCurve(SkScalar depth, bool hairline = false) {
  64. return sk_sp<ShapeRenderer>(new PathRenderer(depth, hairline));
  65. }
  66. SkString name() override {
  67. SkString name;
  68. if (fHairline) {
  69. name.append("hairline");
  70. if (fDepth > 0.f) {
  71. name.appendf("-curve-%.2f", fDepth);
  72. }
  73. } else if (fDepth > 0.f) {
  74. name.appendf("curve-%.2f", fDepth);
  75. } else if (fDepth < 0.f) {
  76. name.appendf("line-%.2f", -fDepth);
  77. } else {
  78. name.append("line");
  79. }
  80. return name;
  81. }
  82. sk_sp<ShapeRenderer> toHairline() override {
  83. return sk_sp<ShapeRenderer>(new PathRenderer(fDepth, true));
  84. }
  85. void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override {
  86. SkPath path;
  87. path.moveTo(kTileWidth / 2.f, 2.f);
  88. if (fDepth > 0.f) {
  89. path.quadTo(kTileWidth / 2.f + fDepth, kTileHeight / 2.f,
  90. kTileWidth / 2.f, kTileHeight - 2.f);
  91. } else {
  92. if (fDepth < 0.f) {
  93. path.lineTo(kTileWidth / 2.f + fDepth, kTileHeight / 2.f);
  94. }
  95. path.lineTo(kTileWidth / 2.f, kTileHeight - 2.f);
  96. }
  97. if (fHairline) {
  98. // Fake thinner hairlines by making it transparent, conflating coverage and alpha
  99. SkColor4f color = paint->getColor4f();
  100. SkScalar width = paint->getStrokeWidth();
  101. if (width > 1.f) {
  102. // Can't emulate width larger than a pixel
  103. return;
  104. }
  105. paint->setColor4f({color.fR, color.fG, color.fB, width}, nullptr);
  106. paint->setStrokeWidth(0.f);
  107. }
  108. // Adding round caps forces Ganesh to use the path renderer for lines instead of converting
  109. // them to rectangles (which are already explicitly tested). However, when not curved, the
  110. // GrShape will still find a way to turn it into a rrect draw so it doesn't hit the
  111. // path renderer in that condition.
  112. paint->setStrokeCap(SkPaint::kRound_Cap);
  113. paint->setStrokeJoin(SkPaint::kMiter_Join);
  114. paint->setStyle(SkPaint::kStroke_Style);
  115. this->applyLocalTransform(canvas, tx, ty, angle);
  116. canvas->drawPath(path, *paint);
  117. }
  118. private:
  119. SkScalar fDepth; // 0.f to make a line, otherwise outset of curve from end points
  120. bool fHairline;
  121. PathRenderer(SkScalar depth, bool hairline)
  122. : fDepth(depth)
  123. , fHairline(hairline) {}
  124. typedef ShapeRenderer INHERITED;
  125. };
  126. class OffscreenShapeRenderer : public ShapeRenderer {
  127. public:
  128. ~OffscreenShapeRenderer() override = default;
  129. static sk_sp<OffscreenShapeRenderer> Make(sk_sp<ShapeRenderer> renderer, int supersample,
  130. bool forceRaster = false) {
  131. SkASSERT(supersample > 0);
  132. return sk_sp<OffscreenShapeRenderer>(new OffscreenShapeRenderer(std::move(renderer),
  133. supersample, forceRaster));
  134. }
  135. SkString name() override {
  136. SkString name = fRenderer->name();
  137. if (fSupersampleFactor != 1) {
  138. name.prependf("%dx-", fSupersampleFactor * fSupersampleFactor);
  139. }
  140. return name;
  141. }
  142. sk_sp<ShapeRenderer> toHairline() override {
  143. return Make(fRenderer->toHairline(), fSupersampleFactor, fForceRasterBackend);
  144. }
  145. void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override {
  146. // Subpixel translation+angle are applied in the offscreen buffer
  147. this->prepareBuffer(canvas, paint, tx, ty, angle);
  148. this->redraw(canvas);
  149. }
  150. // Exposed so that it's easy to fill the offscreen buffer, then draw zooms/filters of it before
  151. // drawing the original scale back into the canvas.
  152. void prepareBuffer(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) {
  153. auto info = SkImageInfo::Make(fSupersampleFactor * kTileWidth,
  154. fSupersampleFactor * kTileHeight,
  155. kRGBA_8888_SkColorType, kPremul_SkAlphaType);
  156. auto surface = fForceRasterBackend ? SkSurface::MakeRaster(info)
  157. : canvas->makeSurface(info);
  158. surface->getCanvas()->save();
  159. // Make fully transparent so it is easy to determine pixels that are touched by partial cov.
  160. surface->getCanvas()->clear(SK_ColorTRANSPARENT);
  161. // Set up scaling to fit supersampling amount
  162. surface->getCanvas()->scale(fSupersampleFactor, fSupersampleFactor);
  163. fRenderer->draw(surface->getCanvas(), paint, tx, ty, angle);
  164. surface->getCanvas()->restore();
  165. // Save image so it can be drawn zoomed in or to visualize touched pixels; only valid until
  166. // the next call to draw()
  167. fLastRendered = surface->makeImageSnapshot();
  168. }
  169. void redraw(SkCanvas* canvas, SkScalar scale = 1.f, bool debugMode = false) {
  170. SkASSERT(fLastRendered);
  171. // Use medium quality filter to get mipmaps when drawing smaller, or use nearest filtering
  172. // when upscaling
  173. SkPaint blit;
  174. blit.setFilterQuality(scale > 1.f ? kNone_SkFilterQuality : kMedium_SkFilterQuality);
  175. if (debugMode) {
  176. // Makes anything that's > 1/255 alpha fully opaque and sets color to medium green.
  177. static constexpr float kFilter[] = {
  178. 0.f, 0.f, 0.f, 0.f, 16.f/255,
  179. 0.f, 0.f, 0.f, 0.f, 200.f/255,
  180. 0.f, 0.f, 0.f, 0.f, 16.f/255,
  181. 0.f, 0.f, 0.f, 255.f, 0.f
  182. };
  183. blit.setColorFilter(SkColorFilters::Matrix(kFilter));
  184. }
  185. canvas->scale(scale, scale);
  186. canvas->drawImageRect(fLastRendered, SkRect::MakeWH(kTileWidth, kTileHeight), &blit);
  187. }
  188. private:
  189. bool fForceRasterBackend;
  190. sk_sp<SkImage> fLastRendered;
  191. sk_sp<ShapeRenderer> fRenderer;
  192. int fSupersampleFactor;
  193. OffscreenShapeRenderer(sk_sp<ShapeRenderer> renderer, int supersample, bool forceRaster)
  194. : fForceRasterBackend(forceRaster)
  195. , fLastRendered(nullptr)
  196. , fRenderer(std::move(renderer))
  197. , fSupersampleFactor(supersample) { }
  198. typedef ShapeRenderer INHERITED;
  199. };
  200. class ThinAASample : public Sample {
  201. public:
  202. ThinAASample() {
  203. this->setBGColor(0xFFFFFFFF);
  204. }
  205. protected:
  206. void onOnceBeforeDraw() override {
  207. // Setup all base renderers
  208. fShapes.push_back(RectRenderer::Make());
  209. fShapes.push_back(PathRenderer::MakeLine());
  210. fShapes.push_back(PathRenderer::MakeLines(4.f)); // 2 segments
  211. fShapes.push_back(PathRenderer::MakeCurve(2.f)); // Shallow curve
  212. fShapes.push_back(PathRenderer::MakeCurve(8.f)); // Deep curve
  213. for (int i = 0; i < fShapes.count(); ++i) {
  214. fNative.push_back(OffscreenShapeRenderer::Make(fShapes[i], 1));
  215. fRaster.push_back(OffscreenShapeRenderer::Make(fShapes[i], 1, /* raster */ true));
  216. fSS4.push_back(OffscreenShapeRenderer::Make(fShapes[i], 4)); // 4x4 -> 16 samples
  217. fSS16.push_back(OffscreenShapeRenderer::Make(fShapes[i], 8)); // 8x8 -> 64 samples
  218. fHairline.push_back(OffscreenShapeRenderer::Make(fRaster[i]->toHairline(), 1));
  219. }
  220. // Start it at something subpixel
  221. fStrokeWidth = 0.5f;
  222. fSubpixelX = 0.f;
  223. fSubpixelY = 0.f;
  224. fAngle = 0.f;
  225. fCurrentStage = AnimStage::kMoveLeft;
  226. fLastFrameTime = -1.f;
  227. // Don't animate in the beginning
  228. fAnimTranslate = false;
  229. fAnimRotate = false;
  230. }
  231. void onDrawContent(SkCanvas* canvas) override {
  232. // Move away from screen edge and add instructions
  233. SkPaint text;
  234. SkFont font(nullptr, 12);
  235. canvas->translate(60.f, 20.f);
  236. canvas->drawString("Each row features a rendering command under different AA strategies. "
  237. "Native refers to the current backend of the viewer, e.g. OpenGL.",
  238. 0, 0, font, text);
  239. canvas->drawString(SkStringPrintf("Stroke width: %.2f ('-' to decrease, '=' to increase)",
  240. fStrokeWidth), 0, 24, font, text);
  241. canvas->drawString(SkStringPrintf("Rotation: %.3f ('r' to animate, 'y' sets to 90, 'u' sets"
  242. " to 0, 'space' adds 15)", fAngle), 0, 36, font, text);
  243. canvas->drawString(SkStringPrintf("Translation: %.3f, %.3f ('t' to animate)",
  244. fSubpixelX, fSubpixelY), 0, 48, font, text);
  245. canvas->translate(0.f, 100.f);
  246. // Draw with surface matching current viewer surface type
  247. this->drawShapes(canvas, "Native", 0, fNative);
  248. // Draw with forced raster backend so it's easy to compare side-by-side
  249. this->drawShapes(canvas, "Raster", 1, fRaster);
  250. // Draw paths as hairlines + alpha hack
  251. this->drawShapes(canvas, "Hairline", 2, fHairline);
  252. // Draw at 4x supersampling in bottom left
  253. this->drawShapes(canvas, "SSx16", 3, fSS4);
  254. // And lastly 16x supersampling in bottom right
  255. this->drawShapes(canvas, "SSx64", 4, fSS16);
  256. }
  257. bool onAnimate(double nanos) override {
  258. SkScalar t = 1e-9 * nanos;
  259. SkScalar dt = fLastFrameTime < 0.f ? 0.f : t - fLastFrameTime;
  260. fLastFrameTime = t;
  261. if (!fAnimRotate && !fAnimTranslate) {
  262. // Keep returning true so that the last frame time is tracked
  263. fLastFrameTime = -1.f;
  264. return false;
  265. }
  266. switch(fCurrentStage) {
  267. case AnimStage::kMoveLeft:
  268. fSubpixelX += 2.f * dt;
  269. if (fSubpixelX >= 1.f) {
  270. fSubpixelX = 1.f;
  271. fCurrentStage = AnimStage::kMoveDown;
  272. }
  273. break;
  274. case AnimStage::kMoveDown:
  275. fSubpixelY += 2.f * dt;
  276. if (fSubpixelY >= 1.f) {
  277. fSubpixelY = 1.f;
  278. fCurrentStage = AnimStage::kMoveRight;
  279. }
  280. break;
  281. case AnimStage::kMoveRight:
  282. fSubpixelX -= 2.f * dt;
  283. if (fSubpixelX <= -1.f) {
  284. fSubpixelX = -1.f;
  285. fCurrentStage = AnimStage::kMoveUp;
  286. }
  287. break;
  288. case AnimStage::kMoveUp:
  289. fSubpixelY -= 2.f * dt;
  290. if (fSubpixelY <= -1.f) {
  291. fSubpixelY = -1.f;
  292. fCurrentStage = fAnimRotate ? AnimStage::kRotate : AnimStage::kMoveLeft;
  293. }
  294. break;
  295. case AnimStage::kRotate: {
  296. SkScalar newAngle = fAngle + dt * 15.f;
  297. bool completed = SkScalarMod(newAngle, 15.f) < SkScalarMod(fAngle, 15.f);
  298. fAngle = SkScalarMod(newAngle, 360.f);
  299. if (completed) {
  300. // Make sure we're on a 15 degree boundary
  301. fAngle = 15.f * SkScalarRoundToScalar(fAngle / 15.f);
  302. if (fAnimTranslate) {
  303. fCurrentStage = this->getTranslationStage();
  304. }
  305. }
  306. } break;
  307. }
  308. return true;
  309. }
  310. SkString name() override { return SkString("Thin-AA"); }
  311. bool onChar(SkUnichar key) override {
  312. switch(key) {
  313. case 't':
  314. // Toggle translation animation.
  315. fAnimTranslate = !fAnimTranslate;
  316. if (!fAnimTranslate && fAnimRotate && fCurrentStage != AnimStage::kRotate) {
  317. // Turned off an active translation so go to rotating
  318. fCurrentStage = AnimStage::kRotate;
  319. } else if (fAnimTranslate && !fAnimRotate &&
  320. fCurrentStage == AnimStage::kRotate) {
  321. // Turned on translation, rotation had been paused too, so reset the stage
  322. fCurrentStage = this->getTranslationStage();
  323. }
  324. return true;
  325. case 'r':
  326. // Toggle rotation animation.
  327. fAnimRotate = !fAnimRotate;
  328. if (!fAnimRotate && fAnimTranslate && fCurrentStage == AnimStage::kRotate) {
  329. // Turned off an active rotation so go back to translation
  330. fCurrentStage = this->getTranslationStage();
  331. } else if (fAnimRotate && !fAnimTranslate &&
  332. fCurrentStage != AnimStage::kRotate) {
  333. // Turned on rotation, translation had been paused too, so reset to rotate
  334. fCurrentStage = AnimStage::kRotate;
  335. }
  336. return true;
  337. case 'u': fAngle = 0.f; return true;
  338. case 'y': fAngle = 90.f; return true;
  339. case ' ': fAngle = SkScalarMod(fAngle + 15.f, 360.f); return true;
  340. case '-': fStrokeWidth = SkMaxScalar(0.1f, fStrokeWidth - 0.05f); return true;
  341. case '=': fStrokeWidth = SkMinScalar(1.f, fStrokeWidth + 0.05f); return true;
  342. }
  343. return false;
  344. }
  345. private:
  346. // Base renderers that get wrapped on the offscreen renderers so that they can be transformed
  347. // for visualization, or supersampled.
  348. SkTArray<sk_sp<ShapeRenderer>> fShapes;
  349. SkTArray<sk_sp<OffscreenShapeRenderer>> fNative;
  350. SkTArray<sk_sp<OffscreenShapeRenderer>> fRaster;
  351. SkTArray<sk_sp<OffscreenShapeRenderer>> fHairline;
  352. SkTArray<sk_sp<OffscreenShapeRenderer>> fSS4;
  353. SkTArray<sk_sp<OffscreenShapeRenderer>> fSS16;
  354. SkScalar fStrokeWidth;
  355. // Animated properties to stress the AA algorithms
  356. enum class AnimStage {
  357. kMoveRight, kMoveDown, kMoveLeft, kMoveUp, kRotate
  358. } fCurrentStage;
  359. SkScalar fLastFrameTime;
  360. bool fAnimRotate;
  361. bool fAnimTranslate;
  362. // Current frame's animation state
  363. SkScalar fSubpixelX;
  364. SkScalar fSubpixelY;
  365. SkScalar fAngle;
  366. AnimStage getTranslationStage() {
  367. // For paused translations (i.e. fAnimTranslate toggled while translating), the current
  368. // stage moves to kRotate, but when restarting the translation animation, we want to
  369. // go back to where we were without losing any progress.
  370. if (fSubpixelX > -1.f) {
  371. if (fSubpixelX >= 1.f) {
  372. // Can only be moving down on right edge, given our transition states
  373. return AnimStage::kMoveDown;
  374. } else if (fSubpixelY > 0.f) {
  375. // Can only be moving right along top edge
  376. return AnimStage::kMoveRight;
  377. } else {
  378. // Must be moving left along bottom edge
  379. return AnimStage::kMoveLeft;
  380. }
  381. } else {
  382. // Moving up along the left edge, or is at the very top so start moving left
  383. return fSubpixelY > -1.f ? AnimStage::kMoveUp : AnimStage::kMoveLeft;
  384. }
  385. }
  386. void drawShapes(SkCanvas* canvas, const char* name, int gridX,
  387. SkTArray<sk_sp<OffscreenShapeRenderer>> shapes) {
  388. SkAutoCanvasRestore autoRestore(canvas, /* save */ true);
  389. for (int i = 0; i < shapes.count(); ++i) {
  390. this->drawShape(canvas, name, gridX, shapes[i].get(), i == 0);
  391. // drawShape positions the canvas properly for the next iteration
  392. }
  393. }
  394. void drawShape(SkCanvas* canvas, const char* name, int gridX,
  395. OffscreenShapeRenderer* shape, bool drawNameLabels) {
  396. static constexpr SkScalar kZoomGridWidth = 8 * ShapeRenderer::kTileWidth + 8.f;
  397. static constexpr SkRect kTile = SkRect::MakeWH(ShapeRenderer::kTileWidth,
  398. ShapeRenderer::kTileHeight);
  399. static constexpr SkRect kZoomTile = SkRect::MakeWH(8 * ShapeRenderer::kTileWidth,
  400. 8 * ShapeRenderer::kTileHeight);
  401. // Labeling per shape and detailed labeling that isn't per-stroke
  402. canvas->save();
  403. SkPaint text;
  404. SkFont font(nullptr, 12);
  405. if (gridX == 0) {
  406. SkString name = shape->name();
  407. SkScalar centering = name.size() * 4.f; // ad-hoc
  408. canvas->save();
  409. canvas->translate(-10.f, 4 * ShapeRenderer::kTileHeight + centering);
  410. canvas->rotate(-90.f);
  411. canvas->drawString(shape->name(), 0.f, 0.f, font, text);
  412. canvas->restore();
  413. }
  414. if (drawNameLabels) {
  415. canvas->drawString(name, gridX * kZoomGridWidth, -10.f, font, text);
  416. }
  417. canvas->restore();
  418. // Paints for outlines and actual shapes
  419. SkPaint outline;
  420. outline.setStyle(SkPaint::kStroke_Style);
  421. SkPaint clear;
  422. clear.setColor(SK_ColorWHITE);
  423. SkPaint paint;
  424. paint.setAntiAlias(true);
  425. paint.setStrokeWidth(fStrokeWidth);
  426. // Generate a saved image of the correct stroke width, but don't put it into the canvas
  427. // yet since we want to draw the "original" size on top of the zoomed in version
  428. shape->prepareBuffer(canvas, &paint, fSubpixelX, fSubpixelY, fAngle);
  429. // Draw it at 8X zoom
  430. SkScalar x = gridX * kZoomGridWidth;
  431. canvas->save();
  432. canvas->translate(x, 0.f);
  433. canvas->drawRect(kZoomTile, outline);
  434. shape->redraw(canvas, 8.0f);
  435. canvas->restore();
  436. // Draw the original
  437. canvas->save();
  438. canvas->translate(x + 4.f, 4.f);
  439. canvas->drawRect(kTile, clear);
  440. canvas->drawRect(kTile, outline);
  441. shape->redraw(canvas, 1.f);
  442. canvas->restore();
  443. // Now redraw it into the coverage location (just to the right of the original scale)
  444. canvas->save();
  445. canvas->translate(x + ShapeRenderer::kTileWidth + 8.f, 4.f);
  446. canvas->drawRect(kTile, clear);
  447. canvas->drawRect(kTile, outline);
  448. shape->redraw(canvas, 1.f, /* debug */ true);
  449. canvas->restore();
  450. // Lastly, shift the canvas translation down by 8 * kTH + padding for the next set of shapes
  451. canvas->translate(0.f, 8.f * ShapeRenderer::kTileHeight + 20.f);
  452. }
  453. typedef Sample INHERITED;
  454. };
  455. //////////////////////////////////////////////////////////////////////////////
  456. DEF_SAMPLE( return new ThinAASample; )
  457. }