diff options
-rw-r--r-- | gm/shapes_as_paths.cpp | 239 | ||||
-rw-r--r-- | gn/gm.gni | 1 | ||||
-rw-r--r-- | src/core/SkCanvas.cpp | 23 |
3 files changed, 252 insertions, 11 deletions
diff --git a/gm/shapes_as_paths.cpp b/gm/shapes_as_paths.cpp new file mode 100644 index 0000000000..20c097f4e1 --- /dev/null +++ b/gm/shapes_as_paths.cpp @@ -0,0 +1,239 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm.h" + +#include "SkAutoPixmapStorage.h" +#include "SkImage.h" +#include "SkPath.h" +#include "SkSurface.h" + +namespace skiagm { + +static void draw_diff(SkCanvas* canvas, SkImage* imgA, SkImage* imgB) { + SkASSERT(imgA->dimensions() == imgB->dimensions()); + + int w = imgA->width(), h = imgA->height(); + + // First, draw the two images faintly overlaid + SkPaint paint; + paint.setAlpha(64); + paint.setBlendMode(SkBlendMode::kPlus); + canvas->drawImage(imgA, 0, 0, &paint); + canvas->drawImage(imgB, 0, 0, &paint); + + // Next, read the pixels back, figure out if there are any differences + SkImageInfo info = SkImageInfo::MakeN32Premul(w, h); + SkAutoPixmapStorage pmapA; + SkAutoPixmapStorage pmapB; + pmapA.alloc(info); + pmapB.alloc(info); + if (!imgA->readPixels(pmapA, 0, 0) || !imgB->readPixels(pmapB, 0, 0)) { + return; + } + + int maxDiffX = 0, maxDiffY = 0, maxDiff = 0; + SkBitmap highlight; + highlight.allocN32Pixels(w, h); + highlight.eraseColor(SK_ColorTRANSPARENT); + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + uint32_t pixelA = *pmapA.addr32(x, y); + uint32_t pixelB = *pmapB.addr32(x, y); + if (pixelA != pixelB) { + int diff = + SkTAbs((int)(SkColorGetR(pixelA) - SkColorGetR(pixelB))) + + SkTAbs((int)(SkColorGetG(pixelA) - SkColorGetG(pixelB))) + + SkTAbs((int)(SkColorGetB(pixelA) - SkColorGetB(pixelB))) + + SkTAbs((int)(SkColorGetA(pixelA) - SkColorGetA(pixelB))); + if (diff > maxDiff) { + maxDiffX = x; + maxDiffY = y; + maxDiff = diff; + } + *highlight.getAddr32(x, y) = SkPackARGB32(0xA0, 0xA0, 0x00, 0x00); + } + } + } + + SkPaint outline; + outline.setStyle(SkPaint::kStroke_Style); + outline.setColor(maxDiff == 0 ? 0xFF007F00 : 0xFF7F0000); + + if (maxDiff > 0) { + // Call extra attention to the region we're going to zoom + SkPMColor yellow = SkPackARGB32(0xFF, 0xFF, 0xFF, 0x00); + *highlight.getAddr32(maxDiffX, maxDiffY) = yellow; + *highlight.getAddr32(SkTMax(maxDiffX - 1, 0), maxDiffY) = yellow; + *highlight.getAddr32(maxDiffX, SkTMax(maxDiffY - 1, 0)) = yellow; + *highlight.getAddr32(SkTMin(maxDiffX + 1, w - 1), maxDiffY) = yellow; + *highlight.getAddr32(maxDiffX, SkTMin(maxDiffY + 1, h - 1)) = yellow; + + // Draw the overlay + canvas->drawBitmap(highlight, 0, 0); + + // Draw zoom of largest pixel diff + canvas->drawImageRect(imgA, SkRect::MakeXYWH(maxDiffX - 5, maxDiffY - 5, 10, 10), + SkRect::MakeXYWH(w, 0, w, h), nullptr); + canvas->drawImageRect(imgB, SkRect::MakeXYWH(maxDiffX - 5, maxDiffY - 5, 10, 10), + SkRect::MakeXYWH(2 * w, 0, w, h), nullptr); + + // Add lines to separate zoom boxes + canvas->drawLine(w, 0, w, h, outline); + canvas->drawLine(2 * w, 0, 2 * w, h, outline); + } + + // Draw outline of whole test region + canvas->drawRect(SkRect::MakeWH(3 * w, h), outline); +} + +namespace { +typedef std::function<void(SkCanvas*, const SkRect&, const SkPaint&)> ShapeDrawFunc; +} + +/** + * Iterates over a variety of rect shapes, paint parameters, and matrices, calling two different + * user-supplied draw callbacks. Produces a grid clearly showing if the two callbacks produce the + * same visual results in all cases. + */ +static void draw_rect_geom_diff_grid(SkCanvas* canvas, ShapeDrawFunc f1, ShapeDrawFunc f2) { + // Variables: + // - Fill, hairline, wide stroke + // - Axis aligned, rotated, scaled, scaled negative, perspective + // - Source geometry (normal, collapsed, inverted) + // + // Things not (yet?) tested: + // - AntiAlias on/off + // - StrokeAndFill + // - Cap/join + // - Anything even more elaborate... + + const SkRect kRects[] = { + SkRect::MakeXYWH(10, 10, 30, 30), // Normal + SkRect::MakeXYWH(10, 25, 30, 0), // Collapsed + SkRect::MakeXYWH(10, 40, 30, -30), // Inverted + }; + + const struct { SkPaint::Style fStyle; SkScalar fStrokeWidth; } kStyles[] = { + { SkPaint::kFill_Style, 0 }, // Filled + { SkPaint::kStroke_Style, 0 }, // Hairline + { SkPaint::kStroke_Style, 5 }, // Wide stroke + }; + + SkMatrix mI = SkMatrix::I(); + SkMatrix mRot; + mRot.setRotate(30, 25, 25); + SkMatrix mScale; + mScale.setScaleTranslate(0.5f, 1, 12.5f, 0); + SkMatrix mFlipX; + mFlipX.setScaleTranslate(-1, 1, 50, 0); + SkMatrix mFlipY; + mFlipY.setScaleTranslate(1, -1, 0, 50); + SkMatrix mFlipXY; + mFlipXY.setScaleTranslate(-1, -1, 50, 50); + SkMatrix mPersp; + mPersp.setIdentity(); + mPersp.setPerspY(0.002f); + + const SkMatrix* kMatrices[] = { &mI, &mRot, &mScale, &mFlipX, &mFlipY, &mFlipXY, &mPersp, }; + + canvas->translate(10, 10); + + SkImageInfo info = canvas->imageInfo().makeWH(50, 50); + if (kUnknown_SkColorType == info.colorType()) { + info = SkImageInfo::MakeN32Premul(50, 50); + } + auto surface = canvas->makeSurface(info); + if (!surface) { + surface = SkSurface::MakeRaster(info); + } + + for (const SkRect& rect : kRects) { + for (const auto& style : kStyles) { + canvas->save(); + + for (const SkMatrix* mat : kMatrices) { + SkPaint paint; + paint.setColor(SK_ColorWHITE); + paint.setAntiAlias(true); + paint.setStyle(style.fStyle); + paint.setStrokeWidth(style.fStrokeWidth); + + // Do first draw + surface->getCanvas()->clear(SK_ColorBLACK); + surface->getCanvas()->save(); + surface->getCanvas()->concat(*mat); + f1(surface->getCanvas(), rect, paint); + surface->getCanvas()->restore(); + auto imgA = surface->makeImageSnapshot(); + + // Do second draw + surface->getCanvas()->clear(SK_ColorBLACK); + surface->getCanvas()->save(); + surface->getCanvas()->concat(*mat); + f2(surface->getCanvas(), rect, paint); + surface->getCanvas()->restore(); + auto imgB = surface->makeImageSnapshot(); + + draw_diff(canvas, imgA.get(), imgB.get()); + canvas->translate(160, 0); + } + canvas->restore(); + canvas->translate(0, 60); + } + } +} + +static const int kNumRows = 9; +static const int kNumColumns = 7; +static const int kTotalWidth = kNumColumns * 160 + 10; +static const int kTotalHeight = kNumRows * 60 + 10; + +DEF_SIMPLE_GM_BG(rects_as_paths, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) { + // Drawing a rect vs. adding it to a path and drawing the path, should produce same results. + auto rectDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) { + canvas->drawRect(rect, paint); + }; + auto pathDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) { + SkPath path; + path.addRect(rect); + canvas->drawPath(path, paint); + }; + + draw_rect_geom_diff_grid(canvas, rectDrawFunc, pathDrawFunc); +} + +DEF_SIMPLE_GM_BG(ovals_as_paths, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) { + // Drawing an oval vs. adding it to a path and drawing the path, should produce same results. + auto ovalDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) { + canvas->drawOval(rect, paint); + }; + auto pathDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) { + SkPath path; + path.addOval(rect); + canvas->drawPath(path, paint); + }; + + draw_rect_geom_diff_grid(canvas, ovalDrawFunc, pathDrawFunc); +} + +DEF_SIMPLE_GM_BG(arcs_as_paths, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) { + // Drawing an arc vs. adding it to a path and drawing the path, should produce same results. + auto arcDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) { + canvas->drawArc(rect, 10, 200, false, paint); + }; + auto pathDrawFunc = [](SkCanvas* canvas, const SkRect& rect, const SkPaint& paint) { + SkPath path; + path.addArc(rect, 10, 200); + canvas->drawPath(path, paint); + }; + + draw_rect_geom_diff_grid(canvas, arcDrawFunc, pathDrawFunc); +} + +} @@ -259,6 +259,7 @@ gm_sources = [ "$_gm/shadowutils.cpp", "$_gm/shallowgradient.cpp", "$_gm/shapes.cpp", + "$_gm/shapes_as_paths.cpp", "$_gm/showmiplevels.cpp", "$_gm/simpleaaclip.cpp", "$_gm/simple_magnification.cpp", diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp index afc1203e6b..216d8fa952 100644 --- a/src/core/SkCanvas.cpp +++ b/src/core/SkCanvas.cpp @@ -1711,7 +1711,9 @@ void SkCanvas::drawPaint(const SkPaint& paint) { } void SkCanvas::drawRect(const SkRect& r, const SkPaint& paint) { - this->onDrawRect(r, paint); + // To avoid redundant logic in our culling code and various backends, we always sort rects + // before passing them along. + this->onDrawRect(r.makeSorted(), paint); } void SkCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { @@ -1727,7 +1729,9 @@ void SkCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { } void SkCanvas::drawOval(const SkRect& r, const SkPaint& paint) { - this->onDrawOval(r, paint); + // To avoid redundant logic in our culling code and various backends, we always sort rects + // before passing them along. + this->onDrawOval(r.makeSorted(), paint); } void SkCanvas::drawRRect(const SkRRect& rrect, const SkPaint& paint) { @@ -1993,11 +1997,10 @@ static bool needs_autodrawlooper(SkCanvas* canvas, const SkPaint& paint) { void SkCanvas::onDrawRect(const SkRect& r, const SkPaint& paint) { TRACE_EVENT0("disabled-by-default-skia", "SkCanvas::drawRect()"); + SkASSERT(r.isSorted()); if (paint.canComputeFastBounds()) { - // Skia will draw an inverted rect, because it explicitly "sorts" it downstream. - // To prevent accidental rejecting at this stage, we have to sort it before we check. SkRect storage; - if (this->quickReject(paint.computeFastBounds(r.makeSorted(), &storage))) { + if (this->quickReject(paint.computeFastBounds(r, &storage))) { return; } } @@ -2039,11 +2042,10 @@ void SkCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) { void SkCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint) { TRACE_EVENT0("disabled-by-default-skia", "SkCanvas::drawOval()"); + SkASSERT(oval.isSorted()); if (paint.canComputeFastBounds()) { - // Skia will draw an inverted rect, because it explicitly "sorts" it downstream. - // To prevent accidental rejecting at this stage, we have to sort it before we check. SkRect storage; - if (this->quickReject(paint.computeFastBounds(oval.makeSorted(), &storage))) { + if (this->quickReject(paint.computeFastBounds(oval, &storage))) { return; } } @@ -2061,12 +2063,11 @@ void SkCanvas::onDrawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, const SkPaint& paint) { TRACE_EVENT0("disabled-by-default-skia", "SkCanvas::drawArc()"); + SkASSERT(oval.isSorted()); if (paint.canComputeFastBounds()) { - // Skia will draw an inverted rect, because it explicitly "sorts" it downstream. - // To prevent accidental rejecting at this stage, we have to sort it before we check. SkRect storage; // Note we're using the entire oval as the bounds. - if (this->quickReject(paint.computeFastBounds(oval.makeSorted(), &storage))) { + if (this->quickReject(paint.computeFastBounds(oval, &storage))) { return; } } |