Linear-time implementation of willPlaybackBitmaps(), computed & cached
on construction in SkPicture. Unit test.

Template trickery thanks to mtklein@.

BUG=skia:2702
R=mtklein@google.com, reed@android.com, reed@google.com, tomhudson@google.com, mtklein, reed

Author: tomhudson@chromium.org

Review URL: https://codereview.chromium.org/366443002
diff --git a/gyp/core.gypi b/gyp/core.gypi
index 4919140..31479ca 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -151,6 +151,8 @@
         '<(skia_src_path)/core/SkRasterClip.cpp',
         '<(skia_src_path)/core/SkRasterizer.cpp',
         '<(skia_src_path)/core/SkReadBuffer.cpp',
+        '<(skia_src_path)/core/SkRecordAnalysis.cpp',
+        '<(skia_src_path)/core/SkRecordAnalysis.h',
         '<(skia_src_path)/core/SkRecordDraw.cpp',
         '<(skia_src_path)/core/SkRecordOpts.cpp',
         '<(skia_src_path)/core/SkRecorder.cpp',
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 8fcf667..62fc633 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -313,6 +313,7 @@
 
     SkPicture(int width, int height, SkRecord*);  // Takes ownership.
     SkAutoTDelete<SkRecord> fRecord;
+    bool fRecordWillPlayBackBitmaps; // TODO: const
 };
 
 #endif
diff --git a/src/core/SkPicture.cpp b/src/core/SkPicture.cpp
index 8d196a1..0c6f31b 100644
--- a/src/core/SkPicture.cpp
+++ b/src/core/SkPicture.cpp
@@ -19,6 +19,7 @@
 #include "SkDrawPictureCallback.h"
 #include "SkPaintPriv.h"
 #include "SkPicture.h"
+#include "SkRecordAnalysis.h"
 #include "SkRegion.h"
 #include "SkStream.h"
 #include "SkTDArray.h"
@@ -132,7 +133,8 @@
 // fRecord OK
 SkPicture::SkPicture()
     : fWidth(0)
-    , fHeight(0) {
+    , fHeight(0)
+    , fRecordWillPlayBackBitmaps(false) {
     this->needsNewGenID();
 }
 
@@ -141,7 +143,8 @@
                      const SkPictureRecord& record,
                      bool deepCopyOps)
     : fWidth(width)
-    , fHeight(height) {
+    , fHeight(height)
+    , fRecordWillPlayBackBitmaps(false) {
     this->needsNewGenID();
 
     SkPictInfo info;
@@ -170,6 +173,7 @@
     this->needsNewGenID();
     fWidth = src.fWidth;
     fHeight = src.fHeight;
+    fRecordWillPlayBackBitmaps = src.fRecordWillPlayBackBitmaps;
 
     if (NULL != src.fData.get()) {
         fData.reset(SkNEW_ARGS(SkPictureData, (*src.fData)));
@@ -204,6 +208,7 @@
         clone->fWidth = fWidth;
         clone->fHeight = fHeight;
         clone->fData.reset(NULL);
+        clone->fRecordWillPlayBackBitmaps = fRecordWillPlayBackBitmaps;
 
         /*  We want to copy the src's playback. However, if that hasn't been built
             yet, we need to fake a call to endRecording() without actually calling
@@ -381,7 +386,8 @@
 SkPicture::SkPicture(SkPictureData* data, int width, int height)
     : fData(data)
     , fWidth(width)
-    , fHeight(height) {
+    , fHeight(height)
+    , fRecordWillPlayBackBitmaps(false) {
     this->needsNewGenID();
 }
 
@@ -521,8 +527,11 @@
 }
 #endif
 
-// fRecord TODO
+// fRecord OK
 bool SkPicture::willPlayBackBitmaps() const {
+    if (fRecord.get()) {
+        return fRecordWillPlayBackBitmaps;
+    }
     if (!fData.get()) {
         return false;
     }
@@ -563,6 +572,7 @@
 SkPicture::SkPicture(int width, int height, SkRecord* record)
     : fWidth(width)
     , fHeight(height)
-    , fRecord(record) {
+    , fRecord(record)
+    , fRecordWillPlayBackBitmaps(SkRecordWillPlaybackBitmaps(*record)) {
     this->needsNewGenID();
 }
diff --git a/src/core/SkRecordAnalysis.cpp b/src/core/SkRecordAnalysis.cpp
new file mode 100644
index 0000000..0bfbaef
--- /dev/null
+++ b/src/core/SkRecordAnalysis.cpp
@@ -0,0 +1,66 @@
+#include "SkRecordAnalysis.h"
+
+#include "SkShader.h"
+#include "SkTLogic.h"
+
+/** SkRecords visitor to determine whether an instance may require an
+    "external" bitmap to rasterize. May return false positives.
+    Does not return true for bitmap text.
+
+    Expected use is to determine whether images need to be decoded before
+    rasterizing a particular SkRecord.
+ */
+struct BitmapTester {
+    // Helpers.  These create HasMember_bitmap and HasMember_paint.
+    SK_CREATE_MEMBER_DETECTOR(bitmap);
+    SK_CREATE_MEMBER_DETECTOR(paint);
+
+    // Some commands have a paint, some have an optional paint.  Either way, get back a pointer.
+    static const SkPaint* AsPtr(const SkPaint& p) { return &p; }
+    static const SkPaint* AsPtr(const SkRecords::Optional<SkPaint>& p) { return p; }
+
+
+    // Main entry for visitor:
+    // If the command has a bitmap directly, return true.
+    // If the command has a paint and the paint has a bitmap, return true.
+    // Otherwise, return false.
+    template <typename T>
+    bool operator()(const T& r) { return CheckBitmap(r); }
+
+
+    // If the command has a bitmap, of course we're going to play back bitmaps.
+    template <typename T>
+    static SK_WHEN(HasMember_bitmap<T>, bool) CheckBitmap(const T&) { return true; }
+
+    // If not, look for one in its paint (if it has a paint).
+    template <typename T>
+    static SK_WHEN(!HasMember_bitmap<T>, bool) CheckBitmap(const T& r) { return CheckPaint(r); }
+
+    // If we have a paint, dig down into the effects looking for a bitmap.
+    template <typename T>
+    static SK_WHEN(HasMember_paint<T>, bool) CheckPaint(const T& r) {
+        const SkPaint* paint = AsPtr(r.paint);
+        if (paint) {
+            const SkShader* shader = paint->getShader();
+            if (shader &&
+                shader->asABitmap(NULL, NULL, NULL) == SkShader::kDefault_BitmapType) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // If we don't have a paint, that non-paint has no bitmap.
+    template <typename T>
+    static SK_WHEN(!HasMember_paint<T>, bool) CheckPaint(const T&) { return false; }
+};
+
+bool SkRecordWillPlaybackBitmaps(const SkRecord& record) {
+    BitmapTester tester;
+    for (unsigned i = 0; i < record.count(); i++) {
+        if (record.visit<bool>(i, tester)) {
+            return true;
+        }
+    }
+    return false;
+}
diff --git a/src/core/SkRecordAnalysis.h b/src/core/SkRecordAnalysis.h
new file mode 100644
index 0000000..6bdd5bc
--- /dev/null
+++ b/src/core/SkRecordAnalysis.h
@@ -0,0 +1,8 @@
+#ifndef SkRecordAnalysis_DEFINED
+#define SkRecordAnalysis_DEFINED
+
+#include "SkRecord.h"
+
+bool SkRecordWillPlaybackBitmaps(const SkRecord& record);
+
+#endif // SkRecordAnalysis_DEFINED
diff --git a/tests/RecordTest.cpp b/tests/RecordTest.cpp
index 96f3ad4..8ec5bcb 100644
--- a/tests/RecordTest.cpp
+++ b/tests/RecordTest.cpp
@@ -7,7 +7,11 @@
 
 #include "Test.h"
 
+#include "SkBitmap.h"
+#include "SkImageInfo.h"
+#include "SkShader.h"
 #include "SkRecord.h"
+#include "SkRecordAnalysis.h"
 #include "SkRecords.h"
 
 // Sums the area of any DrawRect command it sees.
@@ -48,6 +52,8 @@
     }
 };
 
+#define APPEND(record, type, ...) SkNEW_PLACEMENT_ARGS(record.append<type>(), type, (__VA_ARGS__))
+
 // Basic tests for the low-level SkRecord code.
 DEF_TEST(Record, r) {
     SkRecord record;
@@ -55,7 +61,7 @@
     // Add a simple DrawRect command.
     SkRect rect = SkRect::MakeWH(10, 10);
     SkPaint paint;
-    SkNEW_PLACEMENT_ARGS(record.append<SkRecords::DrawRect>(), SkRecords::DrawRect, (paint, rect));
+    APPEND(record, SkRecords::DrawRect, paint, rect);
 
     // Its area should be 100.
     AreaSummer summer;
@@ -70,3 +76,38 @@
     summer.apply(record);
     REPORTER_ASSERT(r, summer.area() == 500);
 }
+
+DEF_TEST(RecordAnalysis, r) {
+    SkRecord record;
+
+    SkRect rect = SkRect::MakeWH(10, 10);
+    SkPaint paint;
+    APPEND(record, SkRecords::DrawRect, paint, rect);
+    REPORTER_ASSERT(r, !SkRecordWillPlaybackBitmaps(record));
+
+    SkBitmap bitmap;
+    APPEND(record, SkRecords::DrawBitmap, &paint, bitmap, 0.0f, 0.0f);
+    REPORTER_ASSERT(r, SkRecordWillPlaybackBitmaps(record));
+
+    SkNEW_PLACEMENT_ARGS(record.replace<SkRecords::DrawRect>(1),
+                         SkRecords::DrawRect, (paint, rect));
+    REPORTER_ASSERT(r, !SkRecordWillPlaybackBitmaps(record));
+
+    SkPaint paint2;
+    // CreateBitmapShader is too smart for us; an empty (or 1x1) bitmap shader
+    // gets optimized into a non-bitmap form, so we create a 2x2 bitmap here.
+    SkBitmap bitmap2;
+    bitmap2.allocPixels(SkImageInfo::MakeN32Premul(2, 2));
+    bitmap2.eraseColor(SK_ColorBLUE);
+    *(bitmap2.getAddr32(0, 0)) = SK_ColorGREEN;
+    SkShader* shader = SkShader::CreateBitmapShader(bitmap2, SkShader::kClamp_TileMode,
+                                                    SkShader::kClamp_TileMode);
+    paint2.setShader(shader);
+    REPORTER_ASSERT(r, shader->asABitmap(NULL, NULL, NULL) == SkShader::kDefault_BitmapType);
+
+    APPEND(record, SkRecords::DrawRect, paint2, rect);
+    REPORTER_ASSERT(r, SkRecordWillPlaybackBitmaps(record));
+}
+
+#undef APPEND
+