blob: fb05d19bfb589a3548efab1ac37b75f217443a93 [file] [log] [blame]
/*
* 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 "SkColorPriv.h"
#include "SkImage.h"
#include "SkPath.h"
#include "SkSurface.h"
namespace skiagm {
skiagm::DrawResult draw_diff(SkCanvas* canvas, SkImage* imgA, SkImage* imgB, SkString* errorMsg) {
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)) {
*errorMsg = "Failed to read pixels.";
return skiagm::DrawResult::kFail;
}
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);
return skiagm::DrawResult::kOk;
}
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 skiagm::DrawResult draw_rect_geom_diff_grid(SkCanvas* canvas, ShapeDrawFunc f1,
ShapeDrawFunc f2, SkString* errorMsg) {
// 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();
skiagm::DrawResult drawResult = draw_diff(canvas, imgA.get(), imgB.get(), errorMsg);
if (skiagm::DrawResult::kOk != drawResult) {
return drawResult;
}
canvas->translate(160, 0);
}
canvas->restore();
canvas->translate(0, 60);
}
}
return skiagm::DrawResult::kOk;
}
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_CAN_FAIL(rects_as_paths, canvas, errorMsg, 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);
};
return draw_rect_geom_diff_grid(canvas, rectDrawFunc, pathDrawFunc, errorMsg);
}
DEF_SIMPLE_GM_BG_CAN_FAIL(ovals_as_paths, canvas, errorMsg, 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);
};
return draw_rect_geom_diff_grid(canvas, ovalDrawFunc, pathDrawFunc, errorMsg);
}
DEF_SIMPLE_GM_BG_CAN_FAIL(arcs_as_paths, canvas, errorMsg, 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);
};
return draw_rect_geom_diff_grid(canvas, arcDrawFunc, pathDrawFunc, errorMsg);
}
}