diff options
-rw-r--r-- | gyp/utils.gyp | 1 | ||||
-rw-r--r-- | include/utils/SkPictureUtils.h | 31 | ||||
-rw-r--r-- | src/utils/SkPictureUtils.cpp | 213 | ||||
-rw-r--r-- | tests/PictureTest.cpp | 222 | ||||
-rw-r--r-- | tools/PictureRenderer.cpp | 25 | ||||
-rw-r--r-- | tools/PictureRenderer.h | 2 | ||||
-rw-r--r-- | tools/bench_pictures_main.cpp | 2 |
7 files changed, 496 insertions, 0 deletions
diff --git a/gyp/utils.gyp b/gyp/utils.gyp index 706b851903..7bbe67cf90 100644 --- a/gyp/utils.gyp +++ b/gyp/utils.gyp @@ -70,6 +70,7 @@ '../src/utils/SkParse.cpp', '../src/utils/SkParseColor.cpp', '../src/utils/SkParsePath.cpp', + '../src/utils/SkPictureUtils.cpp', '../src/utils/SkProxyCanvas.cpp', '../src/utils/SkThreadUtils.h', '../src/utils/SkThreadUtils_pthread.cpp', diff --git a/include/utils/SkPictureUtils.h b/include/utils/SkPictureUtils.h new file mode 100644 index 0000000000..56318cdb70 --- /dev/null +++ b/include/utils/SkPictureUtils.h @@ -0,0 +1,31 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPictureUtils_DEFINED +#define SkPictureUtils_DEFINED + +#include "SkPicture.h" + +class SkData; +struct SkRect; + +class SkPictureUtils { +public: + /** + * Given a rectangular visible "window" into the picture, return an array + * of SkPixelRefs that might intersect that area. To keep the call fast, + * the returned list is not guaranteed to be exact, so it may miss some, + * and it may return false positives. + * + * The pixelrefs returned in the SkData are already owned by the picture, + * so the returned pointers are only valid while the picture is in scope + * and remains unchanged. + */ + static SkData* GatherPixelRefs(SkPicture* pict, const SkRect& area); +}; + +#endif diff --git a/src/utils/SkPictureUtils.cpp b/src/utils/SkPictureUtils.cpp new file mode 100644 index 0000000000..16cf11d2e0 --- /dev/null +++ b/src/utils/SkPictureUtils.cpp @@ -0,0 +1,213 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkPictureUtils.h" +#include "SkCanvas.h" +#include "SkData.h" +#include "SkDevice.h" +#include "SkPixelRef.h" +#include "SkShader.h" + +class PixelRefSet { +public: + PixelRefSet(SkTDArray<SkPixelRef*>* array) : fArray(array) {} + + // This does a linear search on existing pixelrefs, so if this list gets big + // we should use a more complex sorted/hashy thing. + // + void add(SkPixelRef* pr) { + uint32_t genID = pr->getGenerationID(); + if (fGenID.find(genID) < 0) { + *fArray->append() = pr; + *fGenID.append() = genID; +// SkDebugf("--- adding [%d] %x %d\n", fArray->count() - 1, pr, genID); + } else { +// SkDebugf("--- already have %x %d\n", pr, genID); + } + } + +private: + SkTDArray<SkPixelRef*>* fArray; + SkTDArray<uint32_t> fGenID; +}; + +static void not_supported() { + SkASSERT(!"this method should never be called"); +} + +static void nothing_to_do() {} + +/** + * This device will route all bitmaps (primitives and in shaders) to its PRSet. + * It should never actually draw anything, so there need not be any pixels + * behind its device-bitmap. + */ +class GatherPixelRefDevice : public SkDevice { +private: + PixelRefSet* fPRSet; + + void addBitmap(const SkBitmap& bm) { + fPRSet->add(bm.pixelRef()); + } + + void addBitmapFromPaint(const SkPaint& paint) { + SkShader* shader = paint.getShader(); + if (shader) { + SkBitmap bm; + if (shader->asABitmap(&bm, NULL, NULL)) { + fPRSet->add(bm.pixelRef()); + } + } + } + +public: + GatherPixelRefDevice(const SkBitmap& bm, PixelRefSet* prset) : SkDevice(bm) { + fPRSet = prset; + } + + virtual void clear(SkColor color) SK_OVERRIDE { + nothing_to_do(); + } + virtual void writePixels(const SkBitmap& bitmap, int x, int y, + SkCanvas::Config8888 config8888) SK_OVERRIDE { + not_supported(); + } + + virtual void drawPaint(const SkDraw&, const SkPaint& paint) SK_OVERRIDE { + this->addBitmapFromPaint(paint); + } + virtual void drawPoints(const SkDraw&, SkCanvas::PointMode mode, size_t count, + const SkPoint[], const SkPaint& paint) SK_OVERRIDE { + this->addBitmapFromPaint(paint); + } + virtual void drawRect(const SkDraw&, const SkRect& r, + const SkPaint& paint) SK_OVERRIDE { + this->addBitmapFromPaint(paint); + } + virtual void drawPath(const SkDraw&, const SkPath& path, + const SkPaint& paint, const SkMatrix* prePathMatrix, + bool pathIsMutable) SK_OVERRIDE { + this->addBitmapFromPaint(paint); + } + virtual void drawBitmap(const SkDraw&, const SkBitmap& bitmap, + const SkIRect* srcRectOrNull, + const SkMatrix&, const SkPaint&) SK_OVERRIDE { + this->addBitmap(bitmap); + } + virtual void drawBitmapRect(const SkDraw&, const SkBitmap& bitmap, + const SkRect* srcOrNull, const SkRect& dst, + const SkPaint&) SK_OVERRIDE { + this->addBitmap(bitmap); + } + virtual void drawSprite(const SkDraw&, const SkBitmap& bitmap, + int x, int y, const SkPaint& paint) SK_OVERRIDE { + this->addBitmap(bitmap); + } + virtual void drawText(const SkDraw&, const void* text, size_t len, + SkScalar x, SkScalar y, + const SkPaint& paint) SK_OVERRIDE { + this->addBitmapFromPaint(paint); + } + virtual void drawPosText(const SkDraw&, const void* text, size_t len, + const SkScalar pos[], SkScalar constY, + int, const SkPaint& paint) SK_OVERRIDE { + this->addBitmapFromPaint(paint); + } + virtual void drawTextOnPath(const SkDraw&, const void* text, size_t len, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) SK_OVERRIDE { + this->addBitmapFromPaint(paint); + } + virtual void drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCount, + const SkPoint verts[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint) SK_OVERRIDE { + this->addBitmapFromPaint(paint); + } + virtual void drawDevice(const SkDraw&, SkDevice*, int x, int y, + const SkPaint&) SK_OVERRIDE { + nothing_to_do(); + } + +protected: + virtual bool onReadPixels(const SkBitmap& bitmap, + int x, int y, + SkCanvas::Config8888 config8888) SK_OVERRIDE { + not_supported(); + return false; + } +}; + +class NoSaveLayerCanvas : public SkCanvas { +public: + NoSaveLayerCanvas(SkDevice* device) : INHERITED(device) {} + + // turn saveLayer() into save() for speed, should not affect correctness. + virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, + SaveFlags flags) SK_OVERRIDE { + + // Like SkPictureRecord, we don't want to create layers, but we do need + // to respect the save and (possibly) its rect-clip. + + int count = this->INHERITED::save(flags); + if (bounds) { + this->INHERITED::clipRectBounds(bounds, flags, NULL); + } + return count; + } + + // disable aa for speed + virtual bool clipRect(const SkRect& rect, SkRegion::Op op, + bool doAA) SK_OVERRIDE { + return this->INHERITED::clipRect(rect, op, false); + } + + // for speed, just respect the bounds, and disable AA. May give us a few + // false positives and negatives. + virtual bool clipPath(const SkPath& path, SkRegion::Op op, + bool doAA) SK_OVERRIDE { + return this->INHERITED::clipRect(path.getBounds(), op, false); + } + +private: + typedef SkCanvas INHERITED; +}; + +SkData* SkPictureUtils::GatherPixelRefs(SkPicture* pict, const SkRect& area) { + if (NULL == pict) { + return NULL; + } + + // this test also handles if either area or pict's width/height are empty + if (!SkRect::Intersects(area, + SkRect::MakeWH(SkIntToScalar(pict->width()), + SkIntToScalar(pict->height())))) { + return NULL; + } + + SkTDArray<SkPixelRef*> array; + PixelRefSet prset(&array); + + SkBitmap emptyBitmap; + emptyBitmap.setConfig(SkBitmap::kARGB_8888_Config, pict->width(), pict->height()); + // note: we do not set any pixels (shouldn't need to) + + GatherPixelRefDevice device(emptyBitmap, &prset); + NoSaveLayerCanvas canvas(&device); + + canvas.clipRect(area, SkRegion::kIntersect_Op, false); + canvas.drawPicture(*pict); + + SkData* data = NULL; + int count = array.count(); + if (count > 0) { + data = SkData::NewFromMalloc(array.detach(), count * sizeof(SkPixelRef*)); + } + return data; +} + diff --git a/tests/PictureTest.cpp b/tests/PictureTest.cpp index 44cf59a889..567a8d2006 100644 --- a/tests/PictureTest.cpp +++ b/tests/PictureTest.cpp @@ -6,11 +6,232 @@ */ #include "Test.h" #include "SkCanvas.h" +#include "SkColorPriv.h" +#include "SkData.h" #include "SkPaint.h" #include "SkPicture.h" #include "SkRandom.h" +#include "SkShader.h" #include "SkStream.h" +#include "SkPictureUtils.h" + +static void make_bm(SkBitmap* bm, int w, int h, SkColor color, bool immutable) { + bm->setConfig(SkBitmap::kARGB_8888_Config, w, h); + bm->allocPixels(); + bm->eraseColor(color); + if (immutable) { + bm->setImmutable(); + } +} + +typedef void (*DrawBitmapProc)(SkCanvas*, const SkBitmap&, const SkPoint&); + +static void drawbitmap_proc(SkCanvas* canvas, const SkBitmap& bm, + const SkPoint& pos) { + canvas->drawBitmap(bm, pos.fX, pos.fY, NULL); +} + +static void drawbitmaprect_proc(SkCanvas* canvas, const SkBitmap& bm, + const SkPoint& pos) { + SkRect r = { + 0, 0, SkIntToScalar(bm.width()), SkIntToScalar(bm.height()) + }; + r.offset(pos.fX, pos.fY); + canvas->drawBitmapRectToRect(bm, NULL, r, NULL); +} + +static void drawshader_proc(SkCanvas* canvas, const SkBitmap& bm, + const SkPoint& pos) { + SkRect r = { + 0, 0, SkIntToScalar(bm.width()), SkIntToScalar(bm.height()) + }; + r.offset(pos.fX, pos.fY); + + SkShader* s = SkShader::CreateBitmapShader(bm, + SkShader::kClamp_TileMode, + SkShader::kClamp_TileMode); + SkPaint paint; + paint.setShader(s)->unref(); + canvas->drawRect(r, paint); +} + +// Return a picture with the bitmaps drawn at the specified positions. +static SkPicture* record_bitmaps(const SkBitmap bm[], const SkPoint pos[], + int count, DrawBitmapProc proc) { + SkPicture* pic = new SkPicture; + SkCanvas* canvas = pic->beginRecording(1000, 1000); + for (int i = 0; i < count; ++i) { + proc(canvas, bm[i], pos[i]); + } + pic->endRecording(); + return pic; +} + +static void rand_rect(SkRect* rect, SkRandom& rand, SkScalar W, SkScalar H) { + rect->fLeft = rand.nextRangeScalar(-W, 2*W); + rect->fTop = rand.nextRangeScalar(-H, 2*H); + rect->fRight = rect->fLeft + rand.nextRangeScalar(0, W); + rect->fBottom = rect->fTop + rand.nextRangeScalar(0, H); + + // we integralize rect to make our tests more predictable, since Gather is + // a little sloppy. + SkIRect ir; + rect->round(&ir); + rect->set(ir); +} + +// Allocate result to be large enough to hold subset, and then draw the picture +// into it, offsetting by subset's top/left corner. +static void draw(SkPicture* pic, const SkRect& subset, SkBitmap* result) { + SkIRect ir; + subset.roundOut(&ir); + int w = ir.width(); + int h = ir.height(); + make_bm(result, w, h, 0, false); + + SkCanvas canvas(*result); + canvas.translate(-SkIntToScalar(ir.left()), -SkIntToScalar(ir.top())); + canvas.drawPicture(*pic); +} + +template <typename T> int find_index(const T* array, T elem, int count) { + for (int i = 0; i < count; ++i) { + if (array[i] == elem) { + return i; + } + } + return -1; +} + +// Return true if 'ref' is found in array[] +static bool find(SkPixelRef const * const * array, SkPixelRef const * ref, int count) { + return find_index<const SkPixelRef*>(array, ref, count) >= 0; +} + +// Look at each pixel in bm, and if its color appears in colors[], find the +// corresponding value in refs[] and append that ref into array, skipping +// duplicates of the same value. +static void gather_from_colors(const SkBitmap& bm, SkPixelRef* const refs[], + int count, SkTDArray<SkPixelRef*>* array) { + // Since we only want to return unique values in array, when we scan we just + // set a bit for each index'd color found. In practice we only have a few + // distinct colors, so we just use an int's bits as our array. Hence the + // assert that count <= number-of-bits-in-our-int. + SkASSERT((unsigned)count <= 32); + uint32_t bitarray = 0; + + SkAutoLockPixels alp(bm); + + for (int y = 0; y < bm.height(); ++y) { + for (int x = 0; x < bm.width(); ++x) { + SkPMColor pmc = *bm.getAddr32(x, y); + // the only good case where the color is not found would be if + // the color is transparent, meaning no bitmap was drawn in that + // pixel. + if (pmc) { + int index = SkGetPackedR32(pmc); + SkASSERT(SkGetPackedG32(pmc) == index); + SkASSERT(SkGetPackedB32(pmc) == index); + SkASSERT(index < count); + bitarray |= 1 << index; + } + } + } + + for (int i = 0; i < count; ++i) { + if (bitarray & (1 << i)) { + *array->append() = refs[i]; + } + } +} + +static void test_gatherpixelrefs(skiatest::Reporter* reporter) { + const int IW = 8; + const int IH = IW; + const SkScalar W = SkIntToScalar(IW); + const SkScalar H = W; + + static const int N = 4; + SkBitmap bm[N]; + SkPMColor pmcolors[N]; + SkPixelRef* refs[N]; + + const SkPoint pos[] = { + { 0, 0 }, { W, 0 }, { 0, H }, { W, H } + }; + + // Our convention is that the color components contain the index of their + // corresponding bitmap/pixelref + for (int i = 0; i < N; ++i) { + make_bm(&bm[i], IW, IH, SkColorSetARGB(0xFF, i, i, i), true); + refs[i] = bm[i].pixelRef(); + } + + static const DrawBitmapProc procs[] = { + drawbitmap_proc, drawbitmaprect_proc, drawshader_proc + }; + + SkRandom rand; + for (size_t k = 0; k < SK_ARRAY_COUNT(procs); ++k) { + SkAutoTUnref<SkPicture> pic(record_bitmaps(bm, pos, N, procs[k])); + + // quick check for a small piece of each quadrant, which should just + // contain 1 bitmap. + for (size_t i = 0; i < SK_ARRAY_COUNT(pos); ++i) { + SkRect r; + r.set(2, 2, W - 2, H - 2); + r.offset(pos[i].fX, pos[i].fY); + SkAutoDataUnref data(SkPictureUtils::GatherPixelRefs(pic, r)); + REPORTER_ASSERT(reporter, data); + int count = data->size() / sizeof(SkPixelRef*); + REPORTER_ASSERT(reporter, 1 == count); + REPORTER_ASSERT(reporter, *(SkPixelRef**)data->data() == refs[i]); + } + + // Test a bunch of random (mostly) rects, and compare the gather results + // with a deduced list of refs by looking at the colors drawn. + for (int j = 0; j < 100; ++j) { + SkRect r; + rand_rect(&r, rand, 2*W, 2*H); + + SkBitmap result; + draw(pic, r, &result); + SkTDArray<SkPixelRef*> array; + + SkData* data = SkPictureUtils::GatherPixelRefs(pic, r); + size_t dataSize = data ? data->size() : 0; + int gatherCount = dataSize / sizeof(SkPixelRef*); + SkASSERT(gatherCount * sizeof(SkPixelRef*) == dataSize); + SkPixelRef** gatherRefs = data ? (SkPixelRef**)(data->data()) : NULL; + SkAutoDataUnref adu(data); + + gather_from_colors(result, refs, N, &array); + + /* + * GatherPixelRefs is conservative, so it can return more bitmaps + * that we actually can see (usually because of conservative bounds + * inflation for antialiasing). Thus our check here is only that + * Gather didn't miss any that we actually saw. Even that isn't + * a strict requirement on Gather, which is meant to be quick and + * only mostly-correct, but at the moment this test should work. + */ + for (int i = 0; i < array.count(); ++i) { + bool found = find(gatherRefs, array[i], gatherCount); + REPORTER_ASSERT(reporter, found); +#if 0 + // enable this block of code to debug failures, as it will rerun + // the case that failed. + if (!found) { + SkData* data = SkPictureUtils::GatherPixelRefs(pic, r); + size_t dataSize = data ? data->size() : 0; + } +#endif + } + } + } +} + #ifdef SK_DEBUG // Ensure that deleting SkPicturePlayback does not assert. Asserts only fire in debug mode, so only // run in debug mode. @@ -91,6 +312,7 @@ static void TestPicture(skiatest::Reporter* reporter) { test_serializing_empty_picture(); #endif test_peephole(reporter); + test_gatherpixelrefs(reporter); } #include "TestClassDef.h" diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp index 132f88d852..69ce6f86fa 100644 --- a/tools/PictureRenderer.cpp +++ b/tools/PictureRenderer.cpp @@ -28,6 +28,8 @@ #include "SkTDArray.h" #include "SkThreadUtils.h" #include "SkTypes.h" +#include "SkData.h" +#include "SkPictureUtils.h" namespace sk_tools { @@ -658,4 +660,27 @@ SkPicture* PictureRenderer::createPicture() { return NULL; } +/////////////////////////////////////////////////////////////////////////////// + +class GatherRenderer : public PictureRenderer { +public: + virtual bool render(const SkString* path) SK_OVERRIDE { + SkRect bounds = SkRect::MakeWH(SkIntToScalar(fPicture->width()), + SkIntToScalar(fPicture->height())); + SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds); + SkSafeUnref(data); + + return NULL == path; // we don't have anything to write + } + +private: + virtual SkString getConfigNameInternal() SK_OVERRIDE { + return SkString("gather_pixelrefs"); + } +}; + +PictureRenderer* CreateGatherPixelRefsRenderer() { + return SkNEW(GatherRenderer); +} + } // namespace sk_tools diff --git a/tools/PictureRenderer.h b/tools/PictureRenderer.h index 8d9a6bd978..8bce978bf9 100644 --- a/tools/PictureRenderer.h +++ b/tools/PictureRenderer.h @@ -367,6 +367,8 @@ private: typedef PictureRenderer INHERITED; }; +extern PictureRenderer* CreateGatherPixelRefsRenderer(); + } #endif // PictureRenderer_DEFINED diff --git a/tools/bench_pictures_main.cpp b/tools/bench_pictures_main.cpp index 409439e768..c1d0e1c0b3 100644 --- a/tools/bench_pictures_main.cpp +++ b/tools/bench_pictures_main.cpp @@ -413,6 +413,8 @@ static void parse_commandline(int argc, char* const argv[], SkTArray<SkString>* } else if (0 == strcmp(*argv, "playbackCreation")) { renderer.reset(SkNEW(sk_tools::PlaybackCreationRenderer)); gridSupported = true; + } else if (0 == strcmp(*argv, "gatherPixelRefs")) { + renderer.reset(sk_tools::CreateGatherPixelRefsRenderer()); } else { SkString err; err.printf("%s is not a valid mode for --mode\n", *argv); |