skia2/tests/CanvasStateTest.cpp
Mike Reed 8240750718 Change SkCanvas to *not* inherit from SkRefCnt
Definitely tricky for classes like SkNWayCanvas, where the caller (today)
need not pay attention to ownership of the canvases it gave the NWay
(after this CL, the caller *must* managed ownership)

BUG=skia:

GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=4441

DOCS_PREVIEW= https://skia.org/?cl=4441

Change-Id: Ib1ac07a3cdf0686d78e7aaa4735d45cc90bea081
Reviewed-on: https://skia-review.googlesource.com/4441
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
Reviewed-by: Robert Phillips <robertphillips@google.com>
2016-11-09 19:40:06 +00:00

351 lines
13 KiB
C++

/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "CanvasStateHelpers.h"
#include "SkCanvas.h"
#include "SkCanvasStateUtils.h"
#include "SkCommandLineFlags.h"
#include "SkDrawFilter.h"
#include "SkPaint.h"
#include "SkRRect.h"
#include "SkRect.h"
#include "SkTLazy.h"
#include "Test.h"
// dlopen and the library flag are only used for tests which require this flag.
#ifdef SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
#include <dlfcn.h>
DEFINE_string(library, "", "Support library to use for CanvasState test. If empty (the default), "
"the test will be run without crossing a library boundary. Otherwise, "
"it is expected to be a full path to a shared library file, which will"
" be dynamically loaded. Functions from the library will be called to "
"test SkCanvasState. Instructions for generating the library are in "
"gyp/canvas_state_lib.gyp");
// This class calls dlopen on the library passed in to the command line flag library, and handles
// calling dlclose when it goes out of scope.
class OpenLibResult {
public:
// If the flag library was passed to this run of the test, attempt to open it using dlopen and
// report whether it succeeded.
OpenLibResult(skiatest::Reporter* reporter) {
if (FLAGS_library.count() == 1) {
fHandle = dlopen(FLAGS_library[0], RTLD_LAZY | RTLD_LOCAL);
REPORTER_ASSERT_MESSAGE(reporter, fHandle != nullptr, "Failed to open library!");
} else {
fHandle = nullptr;
}
}
// Automatically call dlclose when going out of scope.
~OpenLibResult() {
if (fHandle) {
dlclose(fHandle);
}
}
// Pointer to the shared library object.
void* handle() { return fHandle; }
private:
void* fHandle;
};
DEF_TEST(CanvasState_test_complex_layers, reporter) {
const int WIDTH = 400;
const int HEIGHT = 400;
const int SPACER = 10;
SkRect rect = SkRect::MakeXYWH(SkIntToScalar(SPACER), SkIntToScalar(SPACER),
SkIntToScalar(WIDTH-(2*SPACER)),
SkIntToScalar((HEIGHT-(2*SPACER)) / 7));
const SkColorType colorTypes[] = {
kRGB_565_SkColorType, kN32_SkColorType
};
const int layerAlpha[] = { 255, 255, 0 };
const SkCanvas::SaveLayerFlags flags[] = {
static_cast<SkCanvas::SaveLayerFlags>(SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag),
0,
static_cast<SkCanvas::SaveLayerFlags>(SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag),
};
REPORTER_ASSERT(reporter, sizeof(layerAlpha) == sizeof(flags));
bool (*drawFn)(SkCanvasState* state, float l, float t,
float r, float b, int32_t s);
OpenLibResult openLibResult(reporter);
if (openLibResult.handle() != nullptr) {
*(void**) (&drawFn) = dlsym(openLibResult.handle(),
"complex_layers_draw_from_canvas_state");
} else {
drawFn = complex_layers_draw_from_canvas_state;
}
REPORTER_ASSERT(reporter, drawFn);
if (!drawFn) {
return;
}
for (size_t i = 0; i < SK_ARRAY_COUNT(colorTypes); ++i) {
SkBitmap bitmaps[2];
for (int j = 0; j < 2; ++j) {
bitmaps[j].allocPixels(SkImageInfo::Make(WIDTH, HEIGHT,
colorTypes[i],
kPremul_SkAlphaType));
SkCanvas canvas(bitmaps[j]);
canvas.drawColor(SK_ColorRED);
for (size_t k = 0; k < SK_ARRAY_COUNT(layerAlpha); ++k) {
SkTLazy<SkPaint> paint;
if (layerAlpha[k] != 0xFF) {
paint.init()->setAlpha(layerAlpha[k]);
}
// draw a rect within the layer's bounds and again outside the layer's bounds
canvas.saveLayer(SkCanvas::SaveLayerRec(&rect, paint.getMaybeNull(), flags[k]));
if (j) {
// Capture from the first Skia.
SkCanvasState* state = SkCanvasStateUtils::CaptureCanvasState(&canvas);
REPORTER_ASSERT(reporter, state);
// And draw to it in the second Skia.
bool success = complex_layers_draw_from_canvas_state(state,
rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, SPACER);
REPORTER_ASSERT(reporter, success);
// And release it in the *first* Skia.
SkCanvasStateUtils::ReleaseCanvasState(state);
} else {
// Draw in the first Skia.
complex_layers_draw(&canvas, rect.fLeft, rect.fTop,
rect.fRight, rect.fBottom, SPACER);
}
canvas.restore();
// translate the canvas for the next iteration
canvas.translate(0, 2*(rect.height() + SPACER));
}
}
// now we memcmp the two bitmaps
REPORTER_ASSERT(reporter, bitmaps[0].getSize() == bitmaps[1].getSize());
REPORTER_ASSERT(reporter, !memcmp(bitmaps[0].getPixels(),
bitmaps[1].getPixels(),
bitmaps[0].getSize()));
}
}
#endif
////////////////////////////////////////////////////////////////////////////////
#ifdef SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
DEF_TEST(CanvasState_test_complex_clips, reporter) {
const int WIDTH = 400;
const int HEIGHT = 400;
const int SPACER = 10;
SkIRect layerRect = SkIRect::MakeWH(WIDTH, HEIGHT / 4);
layerRect.inset(2*SPACER, 2*SPACER);
SkIRect clipRect = layerRect;
clipRect.fRight = clipRect.fLeft + (clipRect.width() / 2) - (2*SPACER);
clipRect.outset(SPACER, SPACER);
SkIRect regionBounds = clipRect;
regionBounds.offset(clipRect.width() + (2*SPACER), 0);
SkIRect regionInterior = regionBounds;
regionInterior.inset(SPACER*3, SPACER*3);
SkRegion clipRegion;
clipRegion.setRect(regionBounds);
clipRegion.op(regionInterior, SkRegion::kDifference_Op);
const SkRegion::Op clipOps[] = { SkRegion::kIntersect_Op,
SkRegion::kIntersect_Op,
SkRegion::kReplace_Op,
};
const SkCanvas::SaveLayerFlags flags[] = {
static_cast<SkCanvas::SaveLayerFlags>(SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag),
0,
static_cast<SkCanvas::SaveLayerFlags>(SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag),
};
REPORTER_ASSERT(reporter, sizeof(clipOps) == sizeof(flags));
bool (*drawFn)(SkCanvasState* state, int32_t l, int32_t t,
int32_t r, int32_t b, int32_t clipOp,
int32_t regionRects, int32_t* rectCoords);
OpenLibResult openLibResult(reporter);
if (openLibResult.handle() != nullptr) {
*(void**) (&drawFn) = dlsym(openLibResult.handle(),
"complex_clips_draw_from_canvas_state");
} else {
drawFn = complex_clips_draw_from_canvas_state;
}
REPORTER_ASSERT(reporter, drawFn);
if (!drawFn) {
return;
}
SkBitmap bitmaps[2];
for (int i = 0; i < 2; ++i) {
bitmaps[i].allocN32Pixels(WIDTH, HEIGHT);
SkCanvas canvas(bitmaps[i]);
canvas.drawColor(SK_ColorRED);
SkRegion localRegion = clipRegion;
SkPaint paint;
paint.setAlpha(128);
for (size_t j = 0; j < SK_ARRAY_COUNT(flags); ++j) {
SkRect layerBounds = SkRect::Make(layerRect);
canvas.saveLayer(SkCanvas::SaveLayerRec(&layerBounds, &paint, flags[j]));
if (i) {
SkCanvasState* state = SkCanvasStateUtils::CaptureCanvasState(&canvas);
REPORTER_ASSERT(reporter, state);
SkRegion::Iterator iter(localRegion);
SkTDArray<int32_t> rectCoords;
for (; !iter.done(); iter.next()) {
const SkIRect& rect = iter.rect();
*rectCoords.append() = rect.fLeft;
*rectCoords.append() = rect.fTop;
*rectCoords.append() = rect.fRight;
*rectCoords.append() = rect.fBottom;
}
bool success = drawFn(state, clipRect.fLeft, clipRect.fTop,
clipRect.fRight, clipRect.fBottom, clipOps[j],
rectCoords.count() / 4, rectCoords.begin());
REPORTER_ASSERT(reporter, success);
SkCanvasStateUtils::ReleaseCanvasState(state);
} else {
complex_clips_draw(&canvas, clipRect.fLeft, clipRect.fTop,
clipRect.fRight, clipRect.fBottom, clipOps[j],
localRegion);
}
canvas.restore();
// translate the canvas and region for the next iteration
canvas.translate(0, SkIntToScalar(2*(layerRect.height() + (SPACER))));
localRegion.translate(0, 2*(layerRect.height() + SPACER));
}
}
// now we memcmp the two bitmaps
REPORTER_ASSERT(reporter, bitmaps[0].getSize() == bitmaps[1].getSize());
REPORTER_ASSERT(reporter, !memcmp(bitmaps[0].getPixels(),
bitmaps[1].getPixels(),
bitmaps[0].getSize()));
}
#endif
////////////////////////////////////////////////////////////////////////////////
#ifdef SK_SUPPORT_LEGACY_DRAWFILTER
class TestDrawFilter : public SkDrawFilter {
public:
bool filter(SkPaint*, Type) override { return true; }
};
DEF_TEST(CanvasState_test_draw_filters, reporter) {
TestDrawFilter drawFilter;
SkBitmap bitmap;
bitmap.allocN32Pixels(10, 10);
SkCanvas canvas(bitmap);
canvas.setDrawFilter(&drawFilter);
SkCanvasState* state = SkCanvasStateUtils::CaptureCanvasState(&canvas);
REPORTER_ASSERT(reporter, state);
std::unique_ptr<SkCanvas> tmpCanvas = SkCanvasStateUtils::MakeFromCanvasState(state);
REPORTER_ASSERT(reporter, tmpCanvas);
REPORTER_ASSERT(reporter, canvas.getDrawFilter());
REPORTER_ASSERT(reporter, nullptr == tmpCanvas->getDrawFilter());
SkCanvasStateUtils::ReleaseCanvasState(state);
}
#endif
////////////////////////////////////////////////////////////////////////////////
DEF_TEST(CanvasState_test_soft_clips, reporter) {
SkBitmap bitmap;
bitmap.allocN32Pixels(10, 10);
SkCanvas canvas(bitmap);
SkRRect roundRect;
roundRect.setOval(SkRect::MakeWH(5, 5));
canvas.clipRRect(roundRect, SkCanvas::kIntersect_Op, true);
SkCanvasState* state = SkCanvasStateUtils::CaptureCanvasState(&canvas);
REPORTER_ASSERT(reporter, !state);
}
#ifdef SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
#include "SkClipStack.h"
DEF_TEST(CanvasState_test_saveLayer_clip, reporter) {
const int WIDTH = 100;
const int HEIGHT = 100;
const int LAYER_WIDTH = 50;
const int LAYER_HEIGHT = 50;
SkBitmap bitmap;
bitmap.allocN32Pixels(WIDTH, HEIGHT);
SkCanvas canvas(bitmap);
SkRect bounds = SkRect::MakeWH(SkIntToScalar(LAYER_WIDTH), SkIntToScalar(LAYER_HEIGHT));
canvas.clipRect(SkRect::MakeWH(SkIntToScalar(WIDTH), SkIntToScalar(HEIGHT)));
// Check that saveLayer without the kClipToLayer_SaveFlag leaves the
// clip stack unchanged.
canvas.saveLayer(SkCanvas::SaveLayerRec(&bounds,
nullptr,
SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag));
SkRect clipStackBounds;
SkClipStack::BoundsType boundsType;
canvas.getClipStack()->getBounds(&clipStackBounds, &boundsType);
// The clip stack will return its bounds, or it may be "full" : i.e. empty + inside_out.
// Either result is consistent with this test, since the canvas' size is WIDTH/HEIGHT
if (SkClipStack::kInsideOut_BoundsType == boundsType) {
REPORTER_ASSERT(reporter, clipStackBounds.isEmpty());
} else {
REPORTER_ASSERT(reporter, clipStackBounds.width() == WIDTH);
REPORTER_ASSERT(reporter, clipStackBounds.height() == HEIGHT);
}
canvas.restore();
// Check that saveLayer with the kClipToLayer_SaveFlag sets the clip
// stack to the layer bounds.
canvas.saveLayer(&bounds, nullptr);
canvas.getClipStack()->getBounds(&clipStackBounds, &boundsType);
REPORTER_ASSERT(reporter, clipStackBounds.width() == LAYER_WIDTH);
REPORTER_ASSERT(reporter, clipStackBounds.height() == LAYER_HEIGHT);
canvas.restore();
}
#endif