aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--bench/ImageCacheBench.cpp60
-rw-r--r--gyp/bench.gypi1
-rw-r--r--gyp/core.gypi1
-rw-r--r--gyp/tests.gyp1
-rw-r--r--include/config/SkUserConfig.h7
-rw-r--r--include/core/SkGraphics.h4
-rw-r--r--src/core/SkBitmapProcState.cpp37
-rw-r--r--src/core/SkBitmapProcState.h6
-rw-r--r--src/core/SkScaledImageCache.cpp421
-rw-r--r--src/core/SkScaledImageCache.h107
-rw-r--r--tests/ImageCacheTest.cpp65
11 files changed, 701 insertions, 9 deletions
diff --git a/bench/ImageCacheBench.cpp b/bench/ImageCacheBench.cpp
new file mode 100644
index 0000000000..3a22a47826
--- /dev/null
+++ b/bench/ImageCacheBench.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBenchmark.h"
+#include "SkScaledImageCache.h"
+
+class ImageCacheBench : public SkBenchmark {
+ SkScaledImageCache fCache;
+ SkBitmap fBM;
+
+ enum {
+ N = SkBENCHLOOP(1000),
+ DIM = 1,
+ CACHE_COUNT = 500
+ };
+public:
+ ImageCacheBench(void* param) : INHERITED(param) , fCache(CACHE_COUNT * 100) {
+ fBM.setConfig(SkBitmap::kARGB_8888_Config, DIM, DIM);
+ fBM.allocPixels();
+ }
+
+ void populateCache() {
+ SkScalar scale = 1;
+ for (int i = 0; i < CACHE_COUNT; ++i) {
+ SkBitmap tmp;
+ tmp.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
+ tmp.allocPixels();
+ fCache.unlock(fCache.addAndLock(fBM, scale, scale, tmp));
+ scale += 1;
+ }
+ }
+
+protected:
+ virtual const char* onGetName() SK_OVERRIDE {
+ return "imagecache";
+ }
+
+ virtual void onDraw(SkCanvas*) SK_OVERRIDE {
+ if (fCache.getBytesUsed() == 0) {
+ this->populateCache();
+ }
+
+ SkBitmap tmp;
+ // search for a miss (-1 scale)
+ for (int i = 0; i < N; ++i) {
+ (void)fCache.findAndLock(fBM, -1, -1, &tmp);
+ }
+ }
+
+private:
+ typedef SkBenchmark INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+DEF_BENCH( return new ImageCacheBench(p); )
diff --git a/gyp/bench.gypi b/gyp/bench.gypi
index 4d9dcbe908..a2d5369f7a 100644
--- a/gyp/bench.gypi
+++ b/gyp/bench.gypi
@@ -29,6 +29,7 @@
'../bench/GameBench.cpp',
'../bench/GradientBench.cpp',
'../bench/GrMemoryPoolBench.cpp',
+ '../bench/ImageCacheBench.cpp',
'../bench/ImageDecodeBench.cpp',
'../bench/InterpBench.cpp',
'../bench/HairlinePathBench.cpp',
diff --git a/gyp/core.gypi b/gyp/core.gypi
index 51cf640429..5c3fdf575d 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -150,6 +150,7 @@
'<(skia_src_path)/core/SkRRect.cpp',
'<(skia_src_path)/core/SkRTree.h',
'<(skia_src_path)/core/SkRTree.cpp',
+ '<(skia_src_path)/core/SkScaledImageCache.cpp',
'<(skia_src_path)/core/SkScalar.cpp',
'<(skia_src_path)/core/SkScalerContext.cpp',
'<(skia_src_path)/core/SkScalerContext.h',
diff --git a/gyp/tests.gyp b/gyp/tests.gyp
index e93a12f493..3f4cbe733e 100644
--- a/gyp/tests.gyp
+++ b/gyp/tests.gyp
@@ -67,6 +67,7 @@
'../tests/GrMemoryPoolTest.cpp',
'../tests/GrSurfaceTest.cpp',
'../tests/HashCacheTest.cpp',
+ '../tests/ImageCacheTest.cpp',
'../tests/ImageDecodingTest.cpp',
'../tests/InfRectTest.cpp',
'../tests/LListTest.cpp',
diff --git a/include/config/SkUserConfig.h b/include/config/SkUserConfig.h
index 63fc90db72..64263e2d83 100644
--- a/include/config/SkUserConfig.h
+++ b/include/config/SkUserConfig.h
@@ -122,6 +122,13 @@
*/
//#define SK_DEFAULT_FONT_CACHE_LIMIT (1024 * 1024)
+/*
+ * To specify the default size of the image cache, undefine this and set it to
+ * the desired value (in bytes). SkGraphics.h as a runtime API to set this
+ * value as well. If this is undefined, a built-in value will be used.
+ */
+//#define SK_DEFAULT_IMAGE_CACHE_LIMIT (1024 * 1024)
+
/* If defined, use CoreText instead of ATSUI on OS X.
*/
//#define SK_USE_MAC_CORE_TEXT
diff --git a/include/core/SkGraphics.h b/include/core/SkGraphics.h
index 87d66ad1f0..c876042590 100644
--- a/include/core/SkGraphics.h
+++ b/include/core/SkGraphics.h
@@ -60,6 +60,10 @@ public:
*/
static void PurgeFontCache();
+ static size_t GetImageCacheBytesUsed();
+ static size_t GetImageCacheByteLimit();
+ static size_t SetImageCacheByteLimit(size_t newLimit);
+
/**
* Applications with command line options may pass optional state, such
* as cache sizes, here, for instance:
diff --git a/src/core/SkBitmapProcState.cpp b/src/core/SkBitmapProcState.cpp
index 991521a95d..f880a86196 100644
--- a/src/core/SkBitmapProcState.cpp
+++ b/src/core/SkBitmapProcState.cpp
@@ -12,6 +12,7 @@
#include "SkShader.h" // for tilemodes
#include "SkUtilsArm.h"
#include "SkBitmapScaler.h"
+#include "SkScaledImageCache.h"
#if !SK_ARM_NEON_IS_NONE
// These are defined in src/opts/SkBitmapProcState_arm_neon.cpp
@@ -142,14 +143,29 @@ void SkBitmapProcState::possiblyScaleImage() {
fInvMatrix.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask) &&
fOrigBitmap.config() == SkBitmap::kARGB_8888_Config) {
- int dest_width = SkScalarCeilToInt(fOrigBitmap.width() / fInvMatrix.getScaleX());
- int dest_height = SkScalarCeilToInt(fOrigBitmap.height() / fInvMatrix.getScaleY());
-
- // All the criteria are met; let's make a new bitmap.
-
- fScaledBitmap = SkBitmapScaler::Resize( fOrigBitmap, SkBitmapScaler::RESIZE_BEST,
- dest_width, dest_height, fConvolutionProcs );
-
+ SkScalar invScaleX = fInvMatrix.getScaleX();
+ SkScalar invScaleY = fInvMatrix.getScaleY();
+
+ SkASSERT(NULL == fScaledCacheID);
+ fScaledCacheID = SkScaledImageCache::FindAndLock(fOrigBitmap,
+ invScaleX, invScaleY,
+ &fScaledBitmap);
+ if (NULL == fScaledCacheID) {
+ int dest_width = SkScalarCeilToInt(fOrigBitmap.width() / invScaleX);
+ int dest_height = SkScalarCeilToInt(fOrigBitmap.height() / invScaleY);
+
+ // All the criteria are met; let's make a new bitmap.
+
+ fScaledBitmap = SkBitmapScaler::Resize(fOrigBitmap,
+ SkBitmapScaler::RESIZE_BEST,
+ dest_width,
+ dest_height,
+ fConvolutionProcs);
+ fScaledCacheID = SkScaledImageCache::AddAndLock(fOrigBitmap,
+ invScaleX,
+ invScaleY,
+ fScaledBitmap);
+ }
fScaledBitmap.lockPixels();
fBitmap = &fScaledBitmap;
@@ -234,6 +250,11 @@ void SkBitmapProcState::endContext() {
SkDELETE(fBitmapFilter);
fBitmapFilter = NULL;
fScaledBitmap.reset();
+
+ if (fScaledCacheID) {
+ SkScaledImageCache::Unlock(fScaledCacheID);
+ fScaledCacheID = NULL;
+ }
}
bool SkBitmapProcState::chooseProcs(const SkMatrix& inv, const SkPaint& paint) {
diff --git a/src/core/SkBitmapProcState.h b/src/core/SkBitmapProcState.h
index 349194f3b4..70d7c0cb04 100644
--- a/src/core/SkBitmapProcState.h
+++ b/src/core/SkBitmapProcState.h
@@ -13,6 +13,7 @@
#include "SkBitmap.h"
#include "SkBitmapFilter.h"
#include "SkMatrix.h"
+#include "SkScaledImageCache.h"
#define FractionalInt_IS_64BIT
@@ -35,8 +36,9 @@ struct SkConvolutionProcs;
struct SkBitmapProcState {
- SkBitmapProcState(): fBitmapFilter(NULL) {}
+ SkBitmapProcState(): fScaledCacheID(NULL), fBitmapFilter(NULL) {}
~SkBitmapProcState() {
+ SkASSERT(NULL == fScaledCacheID);
SkDELETE(fBitmapFilter);
}
@@ -157,6 +159,8 @@ private:
SkBitmap fOrigBitmap; // CONSTRUCTOR
SkBitmap fScaledBitmap; // chooseProcs
+ SkScaledImageCache::ID* fScaledCacheID;
+
MatrixProc chooseMatrixProc(bool trivial_matrix);
bool chooseProcs(const SkMatrix& inv, const SkPaint&);
ShaderProc32 chooseShaderProc32();
diff --git a/src/core/SkScaledImageCache.cpp b/src/core/SkScaledImageCache.cpp
new file mode 100644
index 0000000000..993b81bbdf
--- /dev/null
+++ b/src/core/SkScaledImageCache.cpp
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkScaledImageCache.h"
+#include "SkPixelRef.h"
+#include "SkRect.h"
+
+#ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT
+ #define SK_DEFAULT_IMAGE_CACHE_LIMIT (2 * 1024 * 1024)
+#endif
+
+#if 1
+ // Implemented from en.wikipedia.org/wiki/MurmurHash.
+static uint32_t compute_hash(const uint32_t data[], int count) {
+ uint32_t hash = 0;
+
+ for (int i = 0; i < count; ++i) {
+ uint32_t k = data[i];
+ k *= 0xcc9e2d51;
+ k = (k << 15) | (k >> 17);
+ k *= 0x1b873593;
+
+ hash ^= k;
+ hash = (hash << 13) | (hash >> 19);
+ hash *= 5;
+ hash += 0xe6546b64;
+ }
+
+ // hash ^= size;
+ hash ^= hash >> 16;
+ hash *= 0x85ebca6b;
+ hash ^= hash >> 13;
+ hash *= 0xc2b2ae35;
+ hash ^= hash >> 16;
+
+ return hash;
+}
+#else
+static uint32_t mix(uint32_t a, uint32_t b) {
+ return ((a >> 1) | (a << 31)) ^ b;
+}
+
+static uint32_t compute_hash(const uint32_t data[], int count) {
+ uint32_t hash = 0;
+
+ for (int i = 0; i < count; ++i) {
+ hash = mix(hash, data[i]);
+ }
+ return hash;
+}
+#endif
+
+struct Key {
+ bool init(const SkBitmap& bm, SkScalar scaleX, SkScalar scaleY) {
+ SkPixelRef* pr = bm.pixelRef();
+ if (!pr) {
+ return false;
+ }
+
+ size_t offset = bm.pixelRefOffset();
+ size_t rowBytes = bm.rowBytes();
+ int x = (offset % rowBytes) >> 2;
+ int y = offset / rowBytes;
+
+ fGenID = pr->getGenerationID();
+ fBounds.set(x, y, x + bm.width(), y + bm.height());
+ fScaleX = scaleX;
+ fScaleY = scaleY;
+
+ fHash = compute_hash(&fGenID, 7);
+ return true;
+ }
+
+ bool operator<(const Key& other) {
+ const uint32_t* a = &fGenID;
+ const uint32_t* b = &other.fGenID;
+ for (int i = 0; i < 7; ++i) {
+ if (a[i] < b[i]) {
+ return true;
+ }
+ if (a[i] > b[i]) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ bool operator==(const Key& other) {
+ const uint32_t* a = &fHash;
+ const uint32_t* b = &other.fHash;
+ for (int i = 0; i < 8; ++i) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ uint32_t fHash;
+ uint32_t fGenID;
+ float fScaleX;
+ float fScaleY;
+ SkIRect fBounds;
+};
+
+struct SkScaledImageCache::Rec {
+ Rec(const Key& key, const SkBitmap& bm) : fKey(key), fBitmap(bm) {
+ fLockCount = 1;
+ }
+
+ size_t bytesUsed() const {
+ return fBitmap.getSize();
+ }
+
+ Rec* fNext;
+ Rec* fPrev;
+
+ // this guy wants to be 64bit aligned
+ Key fKey;
+
+ int32_t fLockCount;
+ SkBitmap fBitmap;
+};
+
+SkScaledImageCache::SkScaledImageCache(size_t byteLimit) {
+ fHead = NULL;
+ fTail = NULL;
+ fBytesUsed = 0;
+ fByteLimit = byteLimit;
+ fCount = 0;
+}
+
+SkScaledImageCache::~SkScaledImageCache() {
+ Rec* rec = fHead;
+ while (rec) {
+ Rec* next = rec->fNext;
+ SkDELETE(rec);
+ rec = next;
+ }
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::findAndLock(const SkBitmap& orig,
+ SkScalar scaleX,
+ SkScalar scaleY,
+ SkBitmap* scaled) {
+ Key key;
+ if (!key.init(orig, scaleX, scaleY)) {
+ return NULL;
+ }
+
+ Rec* rec = fHead;
+ while (rec != NULL) {
+ if (rec->fKey == key) {
+ this->moveToHead(rec); // for our LRU
+ rec->fLockCount += 1;
+ *scaled = rec->fBitmap;
+// SkDebugf("Found: [%d %d] %d\n", rec->fBitmap.width(), rec->fBitmap.height(), rec->fLockCount);
+ return (ID*)rec;
+ }
+ rec = rec->fNext;
+ }
+ return NULL;
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::addAndLock(const SkBitmap& orig,
+ SkScalar scaleX,
+ SkScalar scaleY,
+ const SkBitmap& scaled) {
+ Key key;
+ if (!key.init(orig, scaleX, scaleY)) {
+ return NULL;
+ }
+
+ Rec* rec = SkNEW_ARGS(Rec, (key, scaled));
+ this->addToHead(rec);
+ SkASSERT(1 == rec->fLockCount);
+
+// SkDebugf("Added: [%d %d]\n", rec->fBitmap.width(), rec->fBitmap.height());
+
+ // We may (now) be overbudget, so see if we need to purge something.
+ this->purgeAsNeeded();
+ return (ID*)rec;
+}
+
+void SkScaledImageCache::unlock(SkScaledImageCache::ID* id) {
+ SkASSERT(id);
+
+#ifdef SK_DEBUG
+ {
+ bool found = false;
+ Rec* rec = fHead;
+ while (rec != NULL) {
+ if ((ID*)rec == id) {
+ found = true;
+ break;
+ }
+ rec = rec->fNext;
+ }
+ SkASSERT(found);
+ }
+#endif
+ Rec* rec = (Rec*)id;
+ SkASSERT(rec->fLockCount > 0);
+ rec->fLockCount -= 1;
+
+// SkDebugf("Unlock: [%d %d] %d\n", rec->fBitmap.width(), rec->fBitmap.height(), rec->fLockCount);
+
+ // we may have been over-budget, but now have released something, so check
+ // if we should purge.
+ if (0 == rec->fLockCount) {
+ this->purgeAsNeeded();
+ }
+}
+
+void SkScaledImageCache::purgeAsNeeded() {
+ size_t byteLimit = fByteLimit;
+ size_t bytesUsed = fBytesUsed;
+
+ Rec* rec = fTail;
+ while (rec) {
+ if (bytesUsed < byteLimit) {
+ break;
+ }
+ Rec* prev = rec->fPrev;
+ if (0 == rec->fLockCount) {
+// SkDebugf("Purge: [%d %d] %d\n", rec->fBitmap.width(), rec->fBitmap.height(), fCount);
+ size_t used = rec->bytesUsed();
+ SkASSERT(used <= bytesUsed);
+ bytesUsed -= used;
+ this->detach(rec);
+ SkDELETE(rec);
+ fCount -= 1;
+ }
+ rec = prev;
+ }
+ fBytesUsed = bytesUsed;
+}
+
+size_t SkScaledImageCache::setByteLimit(size_t newLimit) {
+ size_t prevLimit = fByteLimit;
+ fByteLimit = newLimit;
+ if (newLimit < prevLimit) {
+ this->purgeAsNeeded();
+ }
+ return prevLimit;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkScaledImageCache::detach(Rec* rec) {
+ Rec* prev = rec->fPrev;
+ Rec* next = rec->fNext;
+
+ if (!prev) {
+ SkASSERT(fHead == rec);
+ fHead = next;
+ } else {
+ prev->fNext = next;
+ }
+
+ if (!next) {
+ fTail = prev;
+ } else {
+ next->fPrev = prev;
+ }
+
+ rec->fNext = rec->fPrev = NULL;
+}
+
+void SkScaledImageCache::moveToHead(Rec* rec) {
+ if (fHead == rec) {
+ return;
+ }
+
+ SkASSERT(fHead);
+ SkASSERT(fTail);
+
+ this->validate();
+
+ this->detach(rec);
+
+ fHead->fPrev = rec;
+ rec->fNext = fHead;
+ fHead = rec;
+
+ this->validate();
+}
+
+void SkScaledImageCache::addToHead(Rec* rec) {
+ this->validate();
+
+ rec->fPrev = NULL;
+ rec->fNext = fHead;
+ if (fHead) {
+ fHead->fPrev = rec;
+ }
+ fHead = rec;
+ if (!fTail) {
+ fTail = rec;
+ }
+ fBytesUsed += rec->bytesUsed();
+ fCount += 1;
+
+ this->validate();
+}
+
+#ifdef SK_DEBUG
+void SkScaledImageCache::validate() const {
+ if (NULL == fHead) {
+ SkASSERT(NULL == fTail);
+ SkASSERT(0 == fBytesUsed);
+ return;
+ }
+
+ if (fHead == fTail) {
+ SkASSERT(NULL == fHead->fPrev);
+ SkASSERT(NULL == fHead->fNext);
+ SkASSERT(fHead->bytesUsed() == fBytesUsed);
+ return;
+ }
+
+ SkASSERT(NULL == fHead->fPrev);
+ SkASSERT(NULL != fHead->fNext);
+ SkASSERT(NULL == fTail->fNext);
+ SkASSERT(NULL != fTail->fPrev);
+
+ size_t used = 0;
+ int count = 0;
+ const Rec* rec = fHead;
+ while (rec) {
+ count += 1;
+ used += rec->bytesUsed();
+ SkASSERT(used <= fBytesUsed);
+ rec = rec->fNext;
+ }
+ SkASSERT(fCount == count);
+
+ rec = fTail;
+ while (rec) {
+ SkASSERT(count > 0);
+ count -= 1;
+ SkASSERT(used >= rec->bytesUsed());
+ used -= rec->bytesUsed();
+ rec = rec->fPrev;
+ }
+
+ SkASSERT(0 == count);
+ SkASSERT(0 == used);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkThread.h"
+
+static SkMutex gMutex;
+
+static SkScaledImageCache* get_cache() {
+ static SkScaledImageCache* gCache;
+ if (!gCache) {
+ gCache = SkNEW_ARGS(SkScaledImageCache, (SK_DEFAULT_IMAGE_CACHE_LIMIT));
+ }
+ return gCache;
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::FindAndLock(const SkBitmap& orig,
+ SkScalar scaleX,
+ SkScalar scaleY,
+ SkBitmap* scaled) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->findAndLock(orig, scaleX, scaleY, scaled);
+}
+
+SkScaledImageCache::ID* SkScaledImageCache::AddAndLock(const SkBitmap& orig,
+ SkScalar scaleX,
+ SkScalar scaleY,
+ const SkBitmap& scaled) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->addAndLock(orig, scaleX, scaleY, scaled);
+}
+
+void SkScaledImageCache::Unlock(SkScaledImageCache::ID* id) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->unlock(id);
+}
+
+size_t SkScaledImageCache::GetBytesUsed() {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->getBytesUsed();
+}
+
+size_t SkScaledImageCache::GetByteLimit() {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->getByteLimit();
+}
+
+size_t SkScaledImageCache::SetByteLimit(size_t newLimit) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->setByteLimit(newLimit);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkGraphics.h"
+
+size_t SkGraphics::GetImageCacheBytesUsed() {
+ return SkScaledImageCache::GetBytesUsed();
+}
+
+size_t SkGraphics::GetImageCacheByteLimit() {
+ return SkScaledImageCache::GetByteLimit();
+}
+
+size_t SkGraphics::SetImageCacheByteLimit(size_t newLimit) {
+ return SkScaledImageCache::SetByteLimit(newLimit);
+}
+
diff --git a/src/core/SkScaledImageCache.h b/src/core/SkScaledImageCache.h
new file mode 100644
index 0000000000..6802043150
--- /dev/null
+++ b/src/core/SkScaledImageCache.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkScaledImageCache_DEFINED
+#define SkScaledImageCache_DEFINED
+
+#include "SkBitmap.h"
+
+/**
+ * Cache object for bitmaps (with possible scale in X Y as part of the key).
+ *
+ * Multiple caches can be instantiated, but each instance is not implicitly
+ * thread-safe, so if a given instance is to be shared across threads, the
+ * caller must manage the access itself (e.g. via a mutex).
+ *
+ * As a convenience, a global instance is also defined, which can be safely
+ * access across threads via the static methods (e.g. FindAndLock, etc.).
+ */
+class SkScaledImageCache {
+public:
+ struct ID;
+
+ /*
+ * The following static methods are thread-safe wrappers around a global
+ * instance of this cache.
+ */
+
+ static ID* FindAndLock(const SkBitmap& original, SkScalar scaleX,
+ SkScalar scaleY, SkBitmap* scaled);
+
+ static ID* AddAndLock(const SkBitmap& original, SkScalar scaleX,
+ SkScalar scaleY, const SkBitmap& scaled);
+
+ static void Unlock(ID*);
+
+ static size_t GetBytesUsed();
+ static size_t GetByteLimit();
+ static size_t SetByteLimit(size_t newLimit);
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ SkScaledImageCache(size_t byteLimit);
+ ~SkScaledImageCache();
+
+ /**
+ * Search the cache for a scaled version of original. If found, return it
+ * in scaled, and return its ID pointer. Use the returned ptr to unlock
+ * the cache when you are done using scaled.
+ *
+ * If a match is not found, scaled will be unmodifed, and NULL will be
+ * returned.
+ */
+ ID* findAndLock(const SkBitmap& original, SkScalar scaleX,
+ SkScalar scaleY, SkBitmap* scaled);
+
+ /**
+ * To add a new (scaled) bitmap to the cache, call AddAndLock. Use the
+ * returned ptr to unlock the cache when you are done using scaled.
+ */
+ ID* addAndLock(const SkBitmap& original, SkScalar scaleX,
+ SkScalar scaleY, const SkBitmap& scaled);
+
+ /**
+ * Given a non-null ID ptr returned by either findAndLock or addAndLock,
+ * this releases the associated resources to be available to be purged
+ * if needed. After this, the cached bitmap should no longer be
+ * referenced by the caller.
+ */
+ void unlock(ID*);
+
+ size_t getBytesUsed() const { return fBytesUsed; }
+ size_t getByteLimit() const { return fByteLimit; }
+
+ /**
+ * Set the maximum number of bytes available to this cache. If the current
+ * cache exceeds this new value, it will be purged to try to fit within
+ * this new limit.
+ */
+ size_t setByteLimit(size_t newLimit);
+
+private:
+ struct Rec;
+ Rec* fHead;
+ Rec* fTail;
+
+ size_t fBytesUsed;
+ size_t fByteLimit;
+ int fCount;
+
+ void purgeAsNeeded();
+
+ // linklist management
+ void moveToHead(Rec*);
+ void addToHead(Rec*);
+ void detach(Rec*);
+#ifdef SK_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+};
+
+#endif
diff --git a/tests/ImageCacheTest.cpp b/tests/ImageCacheTest.cpp
new file mode 100644
index 0000000000..63b18e8a07
--- /dev/null
+++ b/tests/ImageCacheTest.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Test.h"
+#include "SkScaledImageCache.h"
+
+static void make_bm(SkBitmap* bm, int w, int h) {
+ bm->setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ bm->allocPixels();
+}
+
+static void TestImageCache(skiatest::Reporter* reporter) {
+ static const int COUNT = 10;
+ static const int DIM = 256;
+ static const size_t defLimit = DIM * DIM * 4 * COUNT + 1024; // 1K slop
+ SkScaledImageCache cache(defLimit);
+ SkScaledImageCache::ID* id;
+
+ SkBitmap bm[COUNT];
+
+ SkScalar scale = 2;
+ for (size_t i = 0; i < COUNT; ++i) {
+ SkBitmap tmp;
+
+ make_bm(&bm[i], DIM, DIM);
+ id = cache.findAndLock(bm[i], scale, scale, &tmp);
+ REPORTER_ASSERT(reporter, NULL == id);
+
+ make_bm(&tmp, DIM, DIM);
+ id = cache.addAndLock(bm[i], scale, scale, tmp);
+ REPORTER_ASSERT(reporter, NULL != id);
+
+ SkBitmap tmp2;
+ SkScaledImageCache::ID* id2 = cache.findAndLock(bm[i], scale, scale,
+ &tmp2);
+ REPORTER_ASSERT(reporter, id == id2);
+ REPORTER_ASSERT(reporter, tmp.pixelRef() == tmp2.pixelRef());
+ REPORTER_ASSERT(reporter, tmp.width() == tmp2.width());
+ REPORTER_ASSERT(reporter, tmp.height() == tmp2.height());
+ cache.unlock(id2);
+
+ cache.unlock(id);
+ }
+
+ // stress test, should trigger purges
+ for (size_t i = 0; i < COUNT * 100; ++i) {
+ SkBitmap tmp;
+
+ make_bm(&tmp, DIM, DIM);
+ id = cache.addAndLock(bm[0], scale, scale, tmp);
+ REPORTER_ASSERT(reporter, NULL != id);
+ cache.unlock(id);
+
+ scale += 1;
+ }
+
+ cache.setByteLimit(0);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("ImageCache", TestImageCacheClass, TestImageCache)