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
+