| /* |
| * 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 |
| SkBitmap bmpA, bmpB; |
| SkAssertResult(bmpA.installPixels(pmapA)); |
| SkAssertResult(bmpB.installPixels(pmapB)); |
| canvas->drawBitmapRect(bmpA, SkRect::MakeXYWH(maxDiffX - 5, maxDiffY - 5, 10, 10), |
| SkRect::MakeXYWH(w, 0, w, h), nullptr); |
| canvas->drawBitmapRect(bmpB, 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); |
| auto surface = canvas->makeSurface(info); |
| if (!surface) { |
| surface = SkSurface::MakeRasterN32Premul(50, 50); |
| } |
| |
| 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); |
| } |
| |
| } |