123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547 |
- /*
- * Copyright 2019 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
- #include "samplecode/Sample.h"
- #include "include/core/SkCanvas.h"
- #include "include/core/SkColorFilter.h"
- #include "include/core/SkFont.h"
- #include "include/core/SkImage.h"
- #include "include/core/SkPath.h"
- #include "include/core/SkSurface.h"
- namespace skiagm {
- class ShapeRenderer : public SkRefCntBase {
- public:
- static constexpr SkScalar kTileWidth = 20.f;
- static constexpr SkScalar kTileHeight = 20.f;
- virtual ~ShapeRenderer() {}
- // Draw the shape, limited to kTileWidth x kTileHeight. It must apply the local subpixel (tx,
- // ty) translation and rotation by angle. Prior to these transform adjustments, the SkCanvas
- // will only have pixel aligned translations (these are separated to make super-sampling
- // renderers easier).
- virtual void draw(SkCanvas* canvas, SkPaint* paint,
- SkScalar tx, SkScalar ty, SkScalar angle) = 0;
- virtual SkString name() = 0;
- virtual sk_sp<ShapeRenderer> toHairline() = 0;
- void applyLocalTransform(SkCanvas* canvas, SkScalar tx, SkScalar ty, SkScalar angle) {
- canvas->translate(tx, ty);
- canvas->rotate(angle, kTileWidth / 2.f, kTileHeight / 2.f);
- }
- };
- class RectRenderer : public ShapeRenderer {
- public:
- static sk_sp<ShapeRenderer> Make() {
- return sk_sp<ShapeRenderer>(new RectRenderer());
- }
- SkString name() override { return SkString("rect"); }
- sk_sp<ShapeRenderer> toHairline() override {
- // Not really available but can't return nullptr
- return Make();
- }
- void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override {
- SkScalar width = paint->getStrokeWidth();
- paint->setStyle(SkPaint::kFill_Style);
- this->applyLocalTransform(canvas, tx, ty, angle);
- canvas->drawRect(SkRect::MakeLTRB(kTileWidth / 2.f - width / 2.f, 2.f,
- kTileWidth / 2.f + width / 2.f, kTileHeight - 2.f),
- *paint);
- }
- private:
- RectRenderer() {}
- typedef ShapeRenderer INHERITED;
- };
- class PathRenderer : public ShapeRenderer {
- public:
- static sk_sp<ShapeRenderer> MakeLine(bool hairline = false) {
- return MakeCurve(0.f, hairline);
- }
- static sk_sp<ShapeRenderer> MakeLines(SkScalar depth, bool hairline = false) {
- return MakeCurve(-depth, hairline);
- }
- static sk_sp<ShapeRenderer> MakeCurve(SkScalar depth, bool hairline = false) {
- return sk_sp<ShapeRenderer>(new PathRenderer(depth, hairline));
- }
- SkString name() override {
- SkString name;
- if (fHairline) {
- name.append("hairline");
- if (fDepth > 0.f) {
- name.appendf("-curve-%.2f", fDepth);
- }
- } else if (fDepth > 0.f) {
- name.appendf("curve-%.2f", fDepth);
- } else if (fDepth < 0.f) {
- name.appendf("line-%.2f", -fDepth);
- } else {
- name.append("line");
- }
- return name;
- }
- sk_sp<ShapeRenderer> toHairline() override {
- return sk_sp<ShapeRenderer>(new PathRenderer(fDepth, true));
- }
- void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override {
- SkPath path;
- path.moveTo(kTileWidth / 2.f, 2.f);
- if (fDepth > 0.f) {
- path.quadTo(kTileWidth / 2.f + fDepth, kTileHeight / 2.f,
- kTileWidth / 2.f, kTileHeight - 2.f);
- } else {
- if (fDepth < 0.f) {
- path.lineTo(kTileWidth / 2.f + fDepth, kTileHeight / 2.f);
- }
- path.lineTo(kTileWidth / 2.f, kTileHeight - 2.f);
- }
- if (fHairline) {
- // Fake thinner hairlines by making it transparent, conflating coverage and alpha
- SkColor4f color = paint->getColor4f();
- SkScalar width = paint->getStrokeWidth();
- if (width > 1.f) {
- // Can't emulate width larger than a pixel
- return;
- }
- paint->setColor4f({color.fR, color.fG, color.fB, width}, nullptr);
- paint->setStrokeWidth(0.f);
- }
- // Adding round caps forces Ganesh to use the path renderer for lines instead of converting
- // them to rectangles (which are already explicitly tested). However, when not curved, the
- // GrShape will still find a way to turn it into a rrect draw so it doesn't hit the
- // path renderer in that condition.
- paint->setStrokeCap(SkPaint::kRound_Cap);
- paint->setStrokeJoin(SkPaint::kMiter_Join);
- paint->setStyle(SkPaint::kStroke_Style);
- this->applyLocalTransform(canvas, tx, ty, angle);
- canvas->drawPath(path, *paint);
- }
- private:
- SkScalar fDepth; // 0.f to make a line, otherwise outset of curve from end points
- bool fHairline;
- PathRenderer(SkScalar depth, bool hairline)
- : fDepth(depth)
- , fHairline(hairline) {}
- typedef ShapeRenderer INHERITED;
- };
- class OffscreenShapeRenderer : public ShapeRenderer {
- public:
- ~OffscreenShapeRenderer() override = default;
- static sk_sp<OffscreenShapeRenderer> Make(sk_sp<ShapeRenderer> renderer, int supersample,
- bool forceRaster = false) {
- SkASSERT(supersample > 0);
- return sk_sp<OffscreenShapeRenderer>(new OffscreenShapeRenderer(std::move(renderer),
- supersample, forceRaster));
- }
- SkString name() override {
- SkString name = fRenderer->name();
- if (fSupersampleFactor != 1) {
- name.prependf("%dx-", fSupersampleFactor * fSupersampleFactor);
- }
- return name;
- }
- sk_sp<ShapeRenderer> toHairline() override {
- return Make(fRenderer->toHairline(), fSupersampleFactor, fForceRasterBackend);
- }
- void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override {
- // Subpixel translation+angle are applied in the offscreen buffer
- this->prepareBuffer(canvas, paint, tx, ty, angle);
- this->redraw(canvas);
- }
- // Exposed so that it's easy to fill the offscreen buffer, then draw zooms/filters of it before
- // drawing the original scale back into the canvas.
- void prepareBuffer(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) {
- auto info = SkImageInfo::Make(fSupersampleFactor * kTileWidth,
- fSupersampleFactor * kTileHeight,
- kRGBA_8888_SkColorType, kPremul_SkAlphaType);
- auto surface = fForceRasterBackend ? SkSurface::MakeRaster(info)
- : canvas->makeSurface(info);
- surface->getCanvas()->save();
- // Make fully transparent so it is easy to determine pixels that are touched by partial cov.
- surface->getCanvas()->clear(SK_ColorTRANSPARENT);
- // Set up scaling to fit supersampling amount
- surface->getCanvas()->scale(fSupersampleFactor, fSupersampleFactor);
- fRenderer->draw(surface->getCanvas(), paint, tx, ty, angle);
- surface->getCanvas()->restore();
- // Save image so it can be drawn zoomed in or to visualize touched pixels; only valid until
- // the next call to draw()
- fLastRendered = surface->makeImageSnapshot();
- }
- void redraw(SkCanvas* canvas, SkScalar scale = 1.f, bool debugMode = false) {
- SkASSERT(fLastRendered);
- // Use medium quality filter to get mipmaps when drawing smaller, or use nearest filtering
- // when upscaling
- SkPaint blit;
- blit.setFilterQuality(scale > 1.f ? kNone_SkFilterQuality : kMedium_SkFilterQuality);
- if (debugMode) {
- // Makes anything that's > 1/255 alpha fully opaque and sets color to medium green.
- static constexpr float kFilter[] = {
- 0.f, 0.f, 0.f, 0.f, 16.f/255,
- 0.f, 0.f, 0.f, 0.f, 200.f/255,
- 0.f, 0.f, 0.f, 0.f, 16.f/255,
- 0.f, 0.f, 0.f, 255.f, 0.f
- };
- blit.setColorFilter(SkColorFilters::Matrix(kFilter));
- }
- canvas->scale(scale, scale);
- canvas->drawImageRect(fLastRendered, SkRect::MakeWH(kTileWidth, kTileHeight), &blit);
- }
- private:
- bool fForceRasterBackend;
- sk_sp<SkImage> fLastRendered;
- sk_sp<ShapeRenderer> fRenderer;
- int fSupersampleFactor;
- OffscreenShapeRenderer(sk_sp<ShapeRenderer> renderer, int supersample, bool forceRaster)
- : fForceRasterBackend(forceRaster)
- , fLastRendered(nullptr)
- , fRenderer(std::move(renderer))
- , fSupersampleFactor(supersample) { }
- typedef ShapeRenderer INHERITED;
- };
- class ThinAASample : public Sample {
- public:
- ThinAASample() {
- this->setBGColor(0xFFFFFFFF);
- }
- protected:
- void onOnceBeforeDraw() override {
- // Setup all base renderers
- fShapes.push_back(RectRenderer::Make());
- fShapes.push_back(PathRenderer::MakeLine());
- fShapes.push_back(PathRenderer::MakeLines(4.f)); // 2 segments
- fShapes.push_back(PathRenderer::MakeCurve(2.f)); // Shallow curve
- fShapes.push_back(PathRenderer::MakeCurve(8.f)); // Deep curve
- for (int i = 0; i < fShapes.count(); ++i) {
- fNative.push_back(OffscreenShapeRenderer::Make(fShapes[i], 1));
- fRaster.push_back(OffscreenShapeRenderer::Make(fShapes[i], 1, /* raster */ true));
- fSS4.push_back(OffscreenShapeRenderer::Make(fShapes[i], 4)); // 4x4 -> 16 samples
- fSS16.push_back(OffscreenShapeRenderer::Make(fShapes[i], 8)); // 8x8 -> 64 samples
- fHairline.push_back(OffscreenShapeRenderer::Make(fRaster[i]->toHairline(), 1));
- }
- // Start it at something subpixel
- fStrokeWidth = 0.5f;
- fSubpixelX = 0.f;
- fSubpixelY = 0.f;
- fAngle = 0.f;
- fCurrentStage = AnimStage::kMoveLeft;
- fLastFrameTime = -1.f;
- // Don't animate in the beginning
- fAnimTranslate = false;
- fAnimRotate = false;
- }
- void onDrawContent(SkCanvas* canvas) override {
- // Move away from screen edge and add instructions
- SkPaint text;
- SkFont font(nullptr, 12);
- canvas->translate(60.f, 20.f);
- canvas->drawString("Each row features a rendering command under different AA strategies. "
- "Native refers to the current backend of the viewer, e.g. OpenGL.",
- 0, 0, font, text);
- canvas->drawString(SkStringPrintf("Stroke width: %.2f ('-' to decrease, '=' to increase)",
- fStrokeWidth), 0, 24, font, text);
- canvas->drawString(SkStringPrintf("Rotation: %.3f ('r' to animate, 'y' sets to 90, 'u' sets"
- " to 0, 'space' adds 15)", fAngle), 0, 36, font, text);
- canvas->drawString(SkStringPrintf("Translation: %.3f, %.3f ('t' to animate)",
- fSubpixelX, fSubpixelY), 0, 48, font, text);
- canvas->translate(0.f, 100.f);
- // Draw with surface matching current viewer surface type
- this->drawShapes(canvas, "Native", 0, fNative);
- // Draw with forced raster backend so it's easy to compare side-by-side
- this->drawShapes(canvas, "Raster", 1, fRaster);
- // Draw paths as hairlines + alpha hack
- this->drawShapes(canvas, "Hairline", 2, fHairline);
- // Draw at 4x supersampling in bottom left
- this->drawShapes(canvas, "SSx16", 3, fSS4);
- // And lastly 16x supersampling in bottom right
- this->drawShapes(canvas, "SSx64", 4, fSS16);
- }
- bool onAnimate(double nanos) override {
- SkScalar t = 1e-9 * nanos;
- SkScalar dt = fLastFrameTime < 0.f ? 0.f : t - fLastFrameTime;
- fLastFrameTime = t;
- if (!fAnimRotate && !fAnimTranslate) {
- // Keep returning true so that the last frame time is tracked
- fLastFrameTime = -1.f;
- return false;
- }
- switch(fCurrentStage) {
- case AnimStage::kMoveLeft:
- fSubpixelX += 2.f * dt;
- if (fSubpixelX >= 1.f) {
- fSubpixelX = 1.f;
- fCurrentStage = AnimStage::kMoveDown;
- }
- break;
- case AnimStage::kMoveDown:
- fSubpixelY += 2.f * dt;
- if (fSubpixelY >= 1.f) {
- fSubpixelY = 1.f;
- fCurrentStage = AnimStage::kMoveRight;
- }
- break;
- case AnimStage::kMoveRight:
- fSubpixelX -= 2.f * dt;
- if (fSubpixelX <= -1.f) {
- fSubpixelX = -1.f;
- fCurrentStage = AnimStage::kMoveUp;
- }
- break;
- case AnimStage::kMoveUp:
- fSubpixelY -= 2.f * dt;
- if (fSubpixelY <= -1.f) {
- fSubpixelY = -1.f;
- fCurrentStage = fAnimRotate ? AnimStage::kRotate : AnimStage::kMoveLeft;
- }
- break;
- case AnimStage::kRotate: {
- SkScalar newAngle = fAngle + dt * 15.f;
- bool completed = SkScalarMod(newAngle, 15.f) < SkScalarMod(fAngle, 15.f);
- fAngle = SkScalarMod(newAngle, 360.f);
- if (completed) {
- // Make sure we're on a 15 degree boundary
- fAngle = 15.f * SkScalarRoundToScalar(fAngle / 15.f);
- if (fAnimTranslate) {
- fCurrentStage = this->getTranslationStage();
- }
- }
- } break;
- }
- return true;
- }
- SkString name() override { return SkString("Thin-AA"); }
- bool onChar(SkUnichar key) override {
- switch(key) {
- case 't':
- // Toggle translation animation.
- fAnimTranslate = !fAnimTranslate;
- if (!fAnimTranslate && fAnimRotate && fCurrentStage != AnimStage::kRotate) {
- // Turned off an active translation so go to rotating
- fCurrentStage = AnimStage::kRotate;
- } else if (fAnimTranslate && !fAnimRotate &&
- fCurrentStage == AnimStage::kRotate) {
- // Turned on translation, rotation had been paused too, so reset the stage
- fCurrentStage = this->getTranslationStage();
- }
- return true;
- case 'r':
- // Toggle rotation animation.
- fAnimRotate = !fAnimRotate;
- if (!fAnimRotate && fAnimTranslate && fCurrentStage == AnimStage::kRotate) {
- // Turned off an active rotation so go back to translation
- fCurrentStage = this->getTranslationStage();
- } else if (fAnimRotate && !fAnimTranslate &&
- fCurrentStage != AnimStage::kRotate) {
- // Turned on rotation, translation had been paused too, so reset to rotate
- fCurrentStage = AnimStage::kRotate;
- }
- return true;
- case 'u': fAngle = 0.f; return true;
- case 'y': fAngle = 90.f; return true;
- case ' ': fAngle = SkScalarMod(fAngle + 15.f, 360.f); return true;
- case '-': fStrokeWidth = SkMaxScalar(0.1f, fStrokeWidth - 0.05f); return true;
- case '=': fStrokeWidth = SkMinScalar(1.f, fStrokeWidth + 0.05f); return true;
- }
- return false;
- }
- private:
- // Base renderers that get wrapped on the offscreen renderers so that they can be transformed
- // for visualization, or supersampled.
- SkTArray<sk_sp<ShapeRenderer>> fShapes;
- SkTArray<sk_sp<OffscreenShapeRenderer>> fNative;
- SkTArray<sk_sp<OffscreenShapeRenderer>> fRaster;
- SkTArray<sk_sp<OffscreenShapeRenderer>> fHairline;
- SkTArray<sk_sp<OffscreenShapeRenderer>> fSS4;
- SkTArray<sk_sp<OffscreenShapeRenderer>> fSS16;
- SkScalar fStrokeWidth;
- // Animated properties to stress the AA algorithms
- enum class AnimStage {
- kMoveRight, kMoveDown, kMoveLeft, kMoveUp, kRotate
- } fCurrentStage;
- SkScalar fLastFrameTime;
- bool fAnimRotate;
- bool fAnimTranslate;
- // Current frame's animation state
- SkScalar fSubpixelX;
- SkScalar fSubpixelY;
- SkScalar fAngle;
- AnimStage getTranslationStage() {
- // For paused translations (i.e. fAnimTranslate toggled while translating), the current
- // stage moves to kRotate, but when restarting the translation animation, we want to
- // go back to where we were without losing any progress.
- if (fSubpixelX > -1.f) {
- if (fSubpixelX >= 1.f) {
- // Can only be moving down on right edge, given our transition states
- return AnimStage::kMoveDown;
- } else if (fSubpixelY > 0.f) {
- // Can only be moving right along top edge
- return AnimStage::kMoveRight;
- } else {
- // Must be moving left along bottom edge
- return AnimStage::kMoveLeft;
- }
- } else {
- // Moving up along the left edge, or is at the very top so start moving left
- return fSubpixelY > -1.f ? AnimStage::kMoveUp : AnimStage::kMoveLeft;
- }
- }
- void drawShapes(SkCanvas* canvas, const char* name, int gridX,
- SkTArray<sk_sp<OffscreenShapeRenderer>> shapes) {
- SkAutoCanvasRestore autoRestore(canvas, /* save */ true);
- for (int i = 0; i < shapes.count(); ++i) {
- this->drawShape(canvas, name, gridX, shapes[i].get(), i == 0);
- // drawShape positions the canvas properly for the next iteration
- }
- }
- void drawShape(SkCanvas* canvas, const char* name, int gridX,
- OffscreenShapeRenderer* shape, bool drawNameLabels) {
- static constexpr SkScalar kZoomGridWidth = 8 * ShapeRenderer::kTileWidth + 8.f;
- static constexpr SkRect kTile = SkRect::MakeWH(ShapeRenderer::kTileWidth,
- ShapeRenderer::kTileHeight);
- static constexpr SkRect kZoomTile = SkRect::MakeWH(8 * ShapeRenderer::kTileWidth,
- 8 * ShapeRenderer::kTileHeight);
- // Labeling per shape and detailed labeling that isn't per-stroke
- canvas->save();
- SkPaint text;
- SkFont font(nullptr, 12);
- if (gridX == 0) {
- SkString name = shape->name();
- SkScalar centering = name.size() * 4.f; // ad-hoc
- canvas->save();
- canvas->translate(-10.f, 4 * ShapeRenderer::kTileHeight + centering);
- canvas->rotate(-90.f);
- canvas->drawString(shape->name(), 0.f, 0.f, font, text);
- canvas->restore();
- }
- if (drawNameLabels) {
- canvas->drawString(name, gridX * kZoomGridWidth, -10.f, font, text);
- }
- canvas->restore();
- // Paints for outlines and actual shapes
- SkPaint outline;
- outline.setStyle(SkPaint::kStroke_Style);
- SkPaint clear;
- clear.setColor(SK_ColorWHITE);
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setStrokeWidth(fStrokeWidth);
- // Generate a saved image of the correct stroke width, but don't put it into the canvas
- // yet since we want to draw the "original" size on top of the zoomed in version
- shape->prepareBuffer(canvas, &paint, fSubpixelX, fSubpixelY, fAngle);
- // Draw it at 8X zoom
- SkScalar x = gridX * kZoomGridWidth;
- canvas->save();
- canvas->translate(x, 0.f);
- canvas->drawRect(kZoomTile, outline);
- shape->redraw(canvas, 8.0f);
- canvas->restore();
- // Draw the original
- canvas->save();
- canvas->translate(x + 4.f, 4.f);
- canvas->drawRect(kTile, clear);
- canvas->drawRect(kTile, outline);
- shape->redraw(canvas, 1.f);
- canvas->restore();
- // Now redraw it into the coverage location (just to the right of the original scale)
- canvas->save();
- canvas->translate(x + ShapeRenderer::kTileWidth + 8.f, 4.f);
- canvas->drawRect(kTile, clear);
- canvas->drawRect(kTile, outline);
- shape->redraw(canvas, 1.f, /* debug */ true);
- canvas->restore();
- // Lastly, shift the canvas translation down by 8 * kTH + padding for the next set of shapes
- canvas->translate(0.f, 8.f * ShapeRenderer::kTileHeight + 20.f);
- }
- typedef Sample INHERITED;
- };
- //////////////////////////////////////////////////////////////////////////////
- DEF_SAMPLE( return new ThinAASample; )
- }
|