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