aa0c837667
Having the picture contents not actually reside in the 0,0..W,H range wrecks havoc with the layer hoisting. The hoisting works correctly but since the picture don't fulfill their contract the results look incorrect. This CL just translates the picture's contents to the right so they are within the picture bound. R=egdaniel@google.com Author: robertphillips@google.com Review URL: https://codereview.chromium.org/594363003
487 lines
16 KiB
C++
487 lines
16 KiB
C++
/*
|
|
* Copyright 2014 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 "SkColorFilter.h"
|
|
#include "SkMultiPictureDraw.h"
|
|
#include "SkPictureRecorder.h"
|
|
#include "SkSurface.h"
|
|
|
|
static const SkScalar kRoot3Over2 = 0.86602545f; // sin(60)
|
|
static const SkScalar kRoot3 = 1.73205081f;
|
|
|
|
static const int kHexSide = 30;
|
|
static const int kNumHexX = 6;
|
|
static const int kNumHexY = 6;
|
|
static const int kPicWidth = kNumHexX * kHexSide;
|
|
static const int kPicHeight = SkScalarCeilToInt((kNumHexY - 0.5f) * 2 * kHexSide * kRoot3Over2);
|
|
static const SkScalar kInset = 20.0f;
|
|
static const int kNumPictures = 3;
|
|
|
|
static const int kTriSide = 40;
|
|
|
|
// Create a hexagon centered at (originX, originY)
|
|
static SkPath make_hex_path(SkScalar originX, SkScalar originY) {
|
|
SkPath hex;
|
|
hex.moveTo(originX-kHexSide, originY);
|
|
hex.rLineTo(SkScalarHalf(kHexSide), kRoot3Over2 * kHexSide);
|
|
hex.rLineTo(SkIntToScalar(kHexSide), 0);
|
|
hex.rLineTo(SkScalarHalf(kHexSide), -kHexSide * kRoot3Over2);
|
|
hex.rLineTo(-SkScalarHalf(kHexSide), -kHexSide * kRoot3Over2);
|
|
hex.rLineTo(-SkIntToScalar(kHexSide), 0);
|
|
hex.close();
|
|
return hex;
|
|
}
|
|
|
|
// Make a picture that is a tiling of the plane with stroked hexagons where
|
|
// each hexagon is in its own layer. The layers are to exercise Ganesh's
|
|
// layer hoisting.
|
|
static const SkPicture* make_hex_plane_picture(SkColor fillColor) {
|
|
|
|
// Create a hexagon with its center at the origin
|
|
SkPath hex = make_hex_path(0, 0);
|
|
|
|
SkPaint fill;
|
|
fill.setStyle(SkPaint::kFill_Style);
|
|
fill.setColor(fillColor);
|
|
|
|
SkPaint stroke;
|
|
stroke.setStyle(SkPaint::kStroke_Style);
|
|
stroke.setStrokeWidth(3);
|
|
|
|
SkPictureRecorder recorder;
|
|
|
|
SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
|
|
SkIntToScalar(kPicHeight));
|
|
|
|
SkScalar xPos, yPos = 0;
|
|
|
|
for (int y = 0; y < kNumHexY; ++y) {
|
|
xPos = 0;
|
|
|
|
for (int x = 0; x < kNumHexX; ++x) {
|
|
canvas->saveLayer(NULL, NULL);
|
|
canvas->translate(xPos, yPos + ((x % 2) ? kRoot3Over2 * kHexSide : 0));
|
|
canvas->drawPath(hex, fill);
|
|
canvas->drawPath(hex, stroke);
|
|
canvas->restore();
|
|
|
|
xPos += 1.5f * kHexSide;
|
|
}
|
|
|
|
yPos += 2 * kHexSide * kRoot3Over2;
|
|
}
|
|
|
|
return recorder.endRecording();
|
|
}
|
|
|
|
// Make an equilateral triangle path with its top corner at (originX, originY)
|
|
static SkPath make_tri_path(SkScalar originX, SkScalar originY) {
|
|
SkPath tri;
|
|
tri.moveTo(originX, originY);
|
|
tri.rLineTo(SkScalarHalf(kTriSide), 1.5f * kTriSide / kRoot3);
|
|
tri.rLineTo(-kTriSide, 0);
|
|
tri.close();
|
|
return tri;
|
|
}
|
|
|
|
static const SkPicture* make_tri_picture() {
|
|
SkPath tri = make_tri_path(SkScalarHalf(kTriSide), 0);
|
|
|
|
SkPaint fill;
|
|
fill.setStyle(SkPaint::kFill_Style);
|
|
fill.setColor(SK_ColorLTGRAY);;
|
|
|
|
SkPaint stroke;
|
|
stroke.setStyle(SkPaint::kStroke_Style);
|
|
stroke.setStrokeWidth(3);
|
|
|
|
SkPictureRecorder recorder;
|
|
|
|
SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
|
|
SkIntToScalar(kPicHeight));
|
|
// The saveLayer/restore block is to exercise layer hoisting
|
|
canvas->saveLayer(NULL, NULL);
|
|
canvas->drawPath(tri, fill);
|
|
canvas->drawPath(tri, stroke);
|
|
canvas->restore();
|
|
|
|
return recorder.endRecording();
|
|
}
|
|
|
|
static const SkPicture* make_sub_picture(const SkPicture* tri) {
|
|
SkPictureRecorder recorder;
|
|
|
|
SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
|
|
SkIntToScalar(kPicHeight));
|
|
|
|
canvas->scale(1.0f/2.0f, 1.0f/2.0f);
|
|
|
|
canvas->save();
|
|
canvas->translate(SkScalarHalf(kTriSide), 0);
|
|
canvas->drawPicture(tri);
|
|
canvas->restore();
|
|
|
|
canvas->save();
|
|
canvas->translate(SkIntToScalar(kTriSide), 1.5f * kTriSide / kRoot3);
|
|
canvas->drawPicture(tri);
|
|
canvas->restore();
|
|
|
|
canvas->save();
|
|
canvas->translate(0, 1.5f * kTriSide / kRoot3);
|
|
canvas->drawPicture(tri);
|
|
canvas->restore();
|
|
|
|
return recorder.endRecording();
|
|
}
|
|
|
|
// Create a Sierpinkski-like picture that starts with a top row with a picture
|
|
// that just contains a triangle. Subsequent rows take the prior row's picture,
|
|
// shrinks it and replicates it 3 times then draws and appropriate number of
|
|
// copies of it.
|
|
static const SkPicture* make_sierpinski_picture() {
|
|
SkAutoTUnref<const SkPicture> pic(make_tri_picture());
|
|
|
|
SkPictureRecorder recorder;
|
|
|
|
SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
|
|
SkIntToScalar(kPicHeight));
|
|
|
|
static const int kNumLevels = 4;
|
|
for (int i = 0; i < kNumLevels; ++i) {
|
|
canvas->save();
|
|
canvas->translate(kPicWidth/2 - (i+1) * (kTriSide/2.0f), 0.0f);
|
|
for (int j = 0; j < i+1; ++j) {
|
|
canvas->drawPicture(pic);
|
|
canvas->translate(SkIntToScalar(kTriSide), 0);
|
|
}
|
|
canvas->restore();
|
|
|
|
pic.reset(make_sub_picture(pic));
|
|
|
|
canvas->translate(0, 1.5f * kTriSide / kRoot3);
|
|
}
|
|
|
|
return recorder.endRecording();
|
|
}
|
|
|
|
static SkSurface* create_compat_surface(SkCanvas* canvas, int width, int height) {
|
|
SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
|
|
|
|
SkSurface* surface = canvas->newSurface(info);
|
|
if (NULL == surface) {
|
|
// picture canvas returns NULL so fall back to raster
|
|
surface = SkSurface::NewRaster(info);
|
|
}
|
|
|
|
return surface;
|
|
}
|
|
|
|
// This class stores the information required to compose all the result
|
|
// fragments potentially generated by the MultiPictureDraw object
|
|
class ComposeStep {
|
|
public:
|
|
ComposeStep() : fSurf(NULL), fX(0.0f), fY(0.0f), fPaint(NULL) { }
|
|
~ComposeStep() { SkSafeUnref(fSurf); SkDELETE(fPaint); }
|
|
|
|
SkSurface* fSurf;
|
|
SkScalar fX;
|
|
SkScalar fY;
|
|
SkPaint* fPaint;
|
|
};
|
|
|
|
typedef void (*PFContentMtd)(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]);
|
|
|
|
// Just a single picture with no clip
|
|
static void no_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
|
|
canvas->drawPicture(pictures[0]);
|
|
}
|
|
|
|
// Two pictures with a rect clip on the second one
|
|
static void rect_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
|
|
canvas->drawPicture(pictures[0]);
|
|
|
|
SkRect rect = pictures[0]->cullRect();
|
|
rect.inset(kInset, kInset);
|
|
|
|
canvas->clipRect(rect);
|
|
|
|
canvas->drawPicture(pictures[1]);
|
|
}
|
|
|
|
// Two pictures with a round rect clip on the second one
|
|
static void rrect_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
|
|
canvas->drawPicture(pictures[0]);
|
|
|
|
SkRect rect = pictures[0]->cullRect();
|
|
rect.inset(kInset, kInset);
|
|
|
|
SkRRect rrect;
|
|
rrect.setRectXY(rect, kInset, kInset);
|
|
|
|
canvas->clipRRect(rrect);
|
|
|
|
canvas->drawPicture(pictures[1]);
|
|
}
|
|
|
|
// Two pictures with a clip path on the second one
|
|
static void path_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
|
|
canvas->drawPicture(pictures[0]);
|
|
|
|
// Create a hexagon centered on the middle of the hex grid
|
|
SkPath hex = make_hex_path((kNumHexX / 2.0f) * kHexSide, kNumHexY * kHexSide * kRoot3Over2);
|
|
|
|
canvas->clipPath(hex);
|
|
|
|
canvas->drawPicture(pictures[1]);
|
|
}
|
|
|
|
// Two pictures with an inverse clip path on the second one
|
|
static void invpath_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
|
|
canvas->drawPicture(pictures[0]);
|
|
|
|
// Create a hexagon centered on the middle of the hex grid
|
|
SkPath hex = make_hex_path((kNumHexX / 2.0f) * kHexSide, kNumHexY * kHexSide * kRoot3Over2);
|
|
hex.setFillType(SkPath::kInverseEvenOdd_FillType);
|
|
|
|
canvas->clipPath(hex);
|
|
|
|
canvas->drawPicture(pictures[1]);
|
|
}
|
|
|
|
// Reuse a single base (triangular) picture a _lot_ (rotated, scaled and translated).
|
|
static void sierpinski(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
|
|
canvas->save();
|
|
canvas->drawPicture(pictures[2]);
|
|
|
|
canvas->rotate(180.0f);
|
|
canvas->translate(-SkIntToScalar(kPicWidth), -SkIntToScalar(kPicHeight));
|
|
canvas->drawPicture(pictures[2]);
|
|
canvas->restore();
|
|
}
|
|
|
|
static const PFContentMtd gContentMthds[] = {
|
|
no_clip,
|
|
rect_clip,
|
|
rrect_clip,
|
|
path_clip,
|
|
invpath_clip,
|
|
sierpinski
|
|
};
|
|
|
|
static void create_content(SkMultiPictureDraw* mpd, PFContentMtd pfGen,
|
|
const SkPicture* pictures[kNumPictures],
|
|
SkCanvas* dest, const SkMatrix& xform) {
|
|
SkAutoTUnref<SkPicture> composite;
|
|
|
|
{
|
|
SkPictureRecorder recorder;
|
|
|
|
SkCanvas* pictureCanvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
|
|
SkIntToScalar(kPicHeight));
|
|
|
|
(*pfGen)(pictureCanvas, pictures);
|
|
|
|
composite.reset(recorder.endRecording());
|
|
}
|
|
|
|
mpd->add(dest, composite, &xform);
|
|
}
|
|
|
|
typedef void(*PFLayoutMtd)(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
|
|
PFContentMtd pfGen, const SkPicture* pictures[kNumPictures],
|
|
SkTArray<ComposeStep>* composeSteps);
|
|
|
|
// Draw the content into a single canvas
|
|
static void simple(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
|
|
PFContentMtd pfGen,
|
|
const SkPicture* pictures[kNumPictures],
|
|
SkTArray<ComposeStep> *composeSteps) {
|
|
|
|
ComposeStep& step = composeSteps->push_back();
|
|
|
|
step.fSurf = create_compat_surface(finalCanvas, kPicWidth, kPicHeight);
|
|
|
|
SkCanvas* subCanvas = step.fSurf->getCanvas();
|
|
|
|
create_content(mpd, pfGen, pictures, subCanvas, SkMatrix::I());
|
|
}
|
|
|
|
// Draw the content into multiple canvases/tiles
|
|
static void tiled(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
|
|
PFContentMtd pfGen,
|
|
const SkPicture* pictures[kNumPictures],
|
|
SkTArray<ComposeStep> *composeSteps) {
|
|
static const int kNumTilesX = 2;
|
|
static const int kNumTilesY = 2;
|
|
static const int kTileWidth = kPicWidth / kNumTilesX;
|
|
static const int kTileHeight = kPicHeight / kNumTilesY;
|
|
|
|
SkASSERT(kPicWidth == kNumTilesX * kTileWidth);
|
|
SkASSERT(kPicHeight == kNumTilesY * kTileHeight);
|
|
|
|
static const SkColor colors[kNumTilesX][kNumTilesY] = {
|
|
{ SK_ColorCYAN, SK_ColorMAGENTA },
|
|
{ SK_ColorYELLOW, SK_ColorGREEN }
|
|
};
|
|
|
|
for (int y = 0; y < kNumTilesY; ++y) {
|
|
for (int x = 0; x < kNumTilesX; ++x) {
|
|
ComposeStep& step = composeSteps->push_back();
|
|
|
|
step.fX = SkIntToScalar(x*kTileWidth);
|
|
step.fY = SkIntToScalar(y*kTileHeight);
|
|
step.fPaint = SkNEW(SkPaint);
|
|
step.fPaint->setColorFilter(
|
|
SkColorFilter::CreateModeFilter(colors[x][y], SkXfermode::kModulate_Mode))->unref();
|
|
|
|
step.fSurf = create_compat_surface(finalCanvas, kTileWidth, kTileHeight);
|
|
|
|
SkCanvas* subCanvas = step.fSurf->getCanvas();
|
|
|
|
SkMatrix trans;
|
|
trans.setTranslate(-SkIntToScalar(x*kTileWidth), -SkIntToScalar(y*kTileHeight));
|
|
|
|
create_content(mpd, pfGen, pictures, subCanvas, trans);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const PFLayoutMtd gLayoutMthds[] = { simple, tiled };
|
|
|
|
namespace skiagm {
|
|
/**
|
|
* This GM exercises the SkMultiPictureDraw object. It tests the
|
|
* cross product of:
|
|
* tiled vs. all-at-once rendering (e.g., into many or just 1 canvas)
|
|
* different clips (e.g., none, rect, rrect)
|
|
* single vs. multiple pictures (e.g., normal vs. picture-pile-style content)
|
|
*/
|
|
class MultiPictureDraw : public GM {
|
|
public:
|
|
enum Content {
|
|
kNoClipSingle_Content,
|
|
kRectClipMulti_Content,
|
|
kRRectClipMulti_Content,
|
|
kPathClipMulti_Content,
|
|
kInvPathClipMulti_Content,
|
|
kSierpinski_Content,
|
|
|
|
kLast_Content = kSierpinski_Content
|
|
};
|
|
|
|
static const int kContentCnt = kLast_Content + 1;
|
|
|
|
enum Layout {
|
|
kSimple_Layout,
|
|
kTiled_Layout,
|
|
|
|
kLast_Layout = kTiled_Layout
|
|
};
|
|
|
|
static const int kLayoutCnt = kLast_Layout + 1;
|
|
|
|
MultiPictureDraw(Content content, Layout layout) : fContent(content), fLayout(layout) {
|
|
SkASSERT(SK_ARRAY_COUNT(gLayoutMthds) == kLayoutCnt);
|
|
SkASSERT(SK_ARRAY_COUNT(gContentMthds) == kContentCnt);
|
|
|
|
for (int i = 0; i < kNumPictures; ++i) {
|
|
fPictures[i] = NULL;
|
|
}
|
|
}
|
|
|
|
virtual ~MultiPictureDraw() {
|
|
for (int i = 0; i < kNumPictures; ++i) {
|
|
SkSafeUnref(fPictures[i]);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
Content fContent;
|
|
Layout fLayout;
|
|
const SkPicture* fPictures[kNumPictures];
|
|
|
|
virtual void onOnceBeforeDraw() SK_OVERRIDE {
|
|
fPictures[0] = make_hex_plane_picture(SK_ColorWHITE);
|
|
fPictures[1] = make_hex_plane_picture(SK_ColorGRAY);
|
|
fPictures[2] = make_sierpinski_picture();
|
|
}
|
|
|
|
virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
|
|
SkMultiPictureDraw mpd;
|
|
SkTArray<ComposeStep> composeSteps;
|
|
|
|
// Fill up the MultiPictureDraw
|
|
(*gLayoutMthds[fLayout])(canvas, &mpd,
|
|
gContentMthds[fContent],
|
|
fPictures, &composeSteps);
|
|
|
|
mpd.draw();
|
|
|
|
// Compose all the drawn canvases into the final canvas
|
|
for (int i = 0; i < composeSteps.count(); ++i) {
|
|
const ComposeStep& step = composeSteps[i];
|
|
|
|
SkAutoTUnref<SkImage> image(step.fSurf->newImageSnapshot());
|
|
|
|
canvas->drawImage(image, step.fX, step.fY, step.fPaint);
|
|
}
|
|
}
|
|
|
|
virtual SkISize onISize() SK_OVERRIDE { return SkISize::Make(kPicWidth, kPicHeight); }
|
|
|
|
virtual SkString onShortName() SK_OVERRIDE {
|
|
static const char* gContentNames[] = {
|
|
"noclip", "rectclip", "rrectclip", "pathclip", "invpathclip", "sierpinski"
|
|
};
|
|
static const char* gLayoutNames[] = { "simple", "tiled" };
|
|
|
|
SkASSERT(SK_ARRAY_COUNT(gLayoutNames) == kLayoutCnt);
|
|
SkASSERT(SK_ARRAY_COUNT(gContentNames) == kContentCnt);
|
|
|
|
SkString name("multipicturedraw_");
|
|
|
|
name.append(gContentNames[fContent]);
|
|
name.append("_");
|
|
name.append(gLayoutNames[fLayout]);
|
|
return name;
|
|
}
|
|
|
|
virtual uint32_t onGetFlags() const SK_OVERRIDE { return kAsBench_Flag | kSkipTiled_Flag; }
|
|
|
|
private:
|
|
typedef GM INHERITED;
|
|
};
|
|
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kNoClipSingle_Content,
|
|
MultiPictureDraw::kSimple_Layout));)
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRectClipMulti_Content,
|
|
MultiPictureDraw::kSimple_Layout));)
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRRectClipMulti_Content,
|
|
MultiPictureDraw::kSimple_Layout));)
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kPathClipMulti_Content,
|
|
MultiPictureDraw::kSimple_Layout));)
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kInvPathClipMulti_Content,
|
|
MultiPictureDraw::kSimple_Layout));)
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kSierpinski_Content,
|
|
MultiPictureDraw::kSimple_Layout));)
|
|
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kNoClipSingle_Content,
|
|
MultiPictureDraw::kTiled_Layout));)
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRectClipMulti_Content,
|
|
MultiPictureDraw::kTiled_Layout));)
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRRectClipMulti_Content,
|
|
MultiPictureDraw::kTiled_Layout));)
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kPathClipMulti_Content,
|
|
MultiPictureDraw::kTiled_Layout));)
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kInvPathClipMulti_Content,
|
|
MultiPictureDraw::kTiled_Layout));)
|
|
DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kSierpinski_Content,
|
|
MultiPictureDraw::kTiled_Layout));)
|
|
}
|