add SkPictureUtils::GatherPixelRefs()
authorreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Thu, 29 Nov 2012 21:00:39 +0000 (21:00 +0000)
committerreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Thu, 29 Nov 2012 21:00:39 +0000 (21:00 +0000)
Review URL: https://codereview.appspot.com/6845106

git-svn-id: http://skia.googlecode.com/svn/trunk@6615 2bbb7eff-a529-9590-31e7-b0007b416f81

gyp/utils.gyp
include/utils/SkPictureUtils.h [new file with mode: 0644]
src/utils/SkPictureUtils.cpp [new file with mode: 0644]
tests/PictureTest.cpp
tools/PictureRenderer.cpp
tools/PictureRenderer.h
tools/bench_pictures_main.cpp

index 706b851..7bbe67c 100644 (file)
@@ -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 (file)
index 0000000..56318cd
--- /dev/null
@@ -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 (file)
index 0000000..16cf11d
--- /dev/null
@@ -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;
+}
+
index 44cf59a..567a8d2 100644 (file)
  */
 #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"
index 132f88d..69ce6f8 100644 (file)
@@ -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
index 8d9a6bd..8bce978 100644 (file)
@@ -367,6 +367,8 @@ private:
     typedef PictureRenderer INHERITED;
 };
 
+extern PictureRenderer* CreateGatherPixelRefsRenderer();
+
 }
 
 #endif  // PictureRenderer_DEFINED
index 409439e..c1d0e1c 100644 (file)
@@ -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);