summaryrefslogtreecommitdiff
path: root/src/com/android/bitmap/drawable/BasicBitmapDrawable.java
diff options
context:
space:
mode:
authorSam Blitzstein <sblitz@google.com>2013-10-09 14:11:27 -0700
committerSam Blitzstein <sblitz@google.com>2013-10-15 17:34:58 -0700
commit93a35b93dc582e38ff8ee5979754a16b4bf4da0c (patch)
tree9034ab3155e8781b0cd77fb70882f911080f6f89 /src/com/android/bitmap/drawable/BasicBitmapDrawable.java
parentce2b0fdc1e9c9d083faab75b6bdfbea27bf574e2 (diff)
downloadbitmap-93a35b93dc582e38ff8ee5979754a16b4bf4da0c.tar.gz
Initial commit from Gmail's Cache system.
Change-Id: I14168ab3bc02b77399a1812f62bd77ac797232c5
Diffstat (limited to 'src/com/android/bitmap/drawable/BasicBitmapDrawable.java')
-rw-r--r--src/com/android/bitmap/drawable/BasicBitmapDrawable.java276
1 files changed, 276 insertions, 0 deletions
diff --git a/src/com/android/bitmap/drawable/BasicBitmapDrawable.java b/src/com/android/bitmap/drawable/BasicBitmapDrawable.java
new file mode 100644
index 0000000..b16618b
--- /dev/null
+++ b/src/com/android/bitmap/drawable/BasicBitmapDrawable.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bitmap.drawable;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import com.android.bitmap.BitmapCache;
+import com.android.bitmap.DecodeTask;
+import com.android.bitmap.DecodeTask.Request;
+import com.android.bitmap.ReusableBitmap;
+import com.android.bitmap.util.BitmapUtils;
+import com.android.bitmap.util.RectUtils;
+import com.android.bitmap.util.Trace;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class encapsulates the basic functionality needed to display a single image bitmap,
+ * including request creation/cancelling, and data unbinding and re-binding.
+ * <p>
+ * The actual bitmap decode work is handled by {@link DecodeTask}.
+ */
+public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCallback,
+ Drawable.Callback {
+
+ private BitmapRequestKey mCurrKey;
+ private ReusableBitmap mBitmap;
+ private final BitmapCache mCache;
+ private DecodeTask mTask;
+ private int mDecodeWidth;
+ private int mDecodeHeight;
+
+ // based on framework CL:I015d77
+ private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+ private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
+ private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
+ private static final Executor SMALL_POOL_EXECUTOR = new ThreadPoolExecutor(
+ CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 1, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>(128));
+
+ private static final Executor EXECUTOR = SMALL_POOL_EXECUTOR;
+
+ private static final boolean LIMIT_BITMAP_DENSITY = true;
+
+ private static final int MAX_BITMAP_DENSITY = DisplayMetrics.DENSITY_HIGH;
+
+ private final float mDensity;
+ private final Paint mPaint = new Paint();
+ private final Rect mSrcRect = new Rect();
+
+ private static final String TAG = BasicBitmapDrawable.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ public BasicBitmapDrawable(final Resources res, final BitmapCache cache) {
+ mDensity = res.getDisplayMetrics().density;
+ mCache = cache;
+ mPaint.setFilterBitmap(true);
+ }
+
+ public DecodeTask.Request getKey() {
+ return mCurrKey;
+ }
+
+ /**
+ * Set the dimensions to decode into.
+ */
+ public void setDecodeDimensions(int w, int h) {
+ mDecodeWidth = w;
+ mDecodeHeight = h;
+ decode();
+ }
+
+ public void unbind() {
+ setImage(null);
+ }
+
+ public void bind(BitmapRequestKey key) {
+ setImage(key);
+ }
+
+ private void setImage(final BitmapRequestKey key) {
+ if (mCurrKey != null && mCurrKey.equals(key)) {
+ return;
+ }
+
+ Trace.beginSection("set image");
+ Trace.beginSection("release reference");
+ if (mBitmap != null) {
+ mBitmap.releaseReference();
+ mBitmap = null;
+ }
+ Trace.endSection();
+ mCurrKey = key;
+
+ if (mTask != null) {
+ mTask.cancel();
+ mTask = null;
+ }
+
+ if (key == null) {
+ invalidateSelf();
+ Trace.endSection();
+ return;
+ }
+
+ // find cached entry here and skip decode if found.
+ final ReusableBitmap cached = mCache.get(key, true /* incrementRefCount */);
+ if (cached != null) {
+ setBitmap(cached);
+ if (DEBUG) {
+ Log.d(TAG, String.format("CACHE HIT key=%s", mCurrKey));
+ }
+ } else {
+ decode();
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "CACHE MISS key=%s\ncache=%s", mCurrKey, mCache.toDebugString()));
+ }
+ }
+ Trace.endSection();
+ }
+
+ @Override
+ public void draw(final Canvas canvas) {
+ final Rect bounds = getBounds();
+ if (bounds.isEmpty()) {
+ return;
+ }
+
+ if (mBitmap != null && mBitmap.bmp != null) {
+ BitmapUtils.calculateCroppedSrcRect(
+ mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight(),
+ bounds.width(), bounds.height(),
+ bounds.height(), Integer.MAX_VALUE,
+ 0.5f, false /* absoluteFraction */,
+ 1, mSrcRect);
+
+ final int orientation = mBitmap.getOrientation();
+ // calculateCroppedSrcRect() gave us the source rectangle "as if" the orientation has
+ // been corrected. We need to decode the uncorrected source rectangle. Calculate true
+ // coordinates.
+ RectUtils.rotateRectForOrientation(orientation,
+ new Rect(0, 0, mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight()),
+ mSrcRect);
+
+ // We may need to rotate the canvas, so we also have to rotate the bounds.
+ final Rect rotatedBounds = new Rect(bounds);
+ RectUtils.rotateRect(orientation, bounds.centerX(), bounds.centerY(), rotatedBounds);
+
+ // Rotate the canvas.
+ canvas.save();
+ canvas.rotate(orientation, bounds.centerX(), bounds.centerY());
+ canvas.drawBitmap(mBitmap.bmp, mSrcRect, rotatedBounds, mPaint);
+ canvas.restore();
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ final int old = mPaint.getAlpha();
+ mPaint.setAlpha(alpha);
+ if (alpha != old) {
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mPaint.setColorFilter(cf);
+ invalidateSelf();
+ }
+
+ @Override
+ public int getOpacity() {
+ return (mBitmap != null && (mBitmap.bmp.hasAlpha() || mPaint.getAlpha() < 255)) ?
+ PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+ }
+
+ @Override
+ public void onDecodeBegin(final Request key) { }
+
+ @Override
+ public void onDecodeComplete(final Request key, final ReusableBitmap result) {
+ if (key.equals(mCurrKey)) {
+ setBitmap(result);
+ } else {
+ // if the requests don't match (i.e. this request is stale), decrement the
+ // ref count to allow the bitmap to be pooled
+ if (result != null) {
+ result.releaseReference();
+ }
+ }
+ }
+
+ @Override
+ public void onDecodeCancel(final Request key) { }
+
+ private void setBitmap(ReusableBitmap bmp) {
+ if (mBitmap != null && mBitmap != bmp) {
+ mBitmap.releaseReference();
+ }
+ mBitmap = bmp;
+ invalidateSelf();
+ }
+
+ private void decode() {
+ final int bufferW;
+ final int bufferH;
+
+ if (mCurrKey == null) {
+ return;
+ }
+
+ Trace.beginSection("decode");
+ if (LIMIT_BITMAP_DENSITY) {
+ final float scale =
+ Math.min(1f, (float) MAX_BITMAP_DENSITY / DisplayMetrics.DENSITY_DEFAULT
+ / mDensity);
+ bufferW = (int) (mDecodeWidth * scale);
+ bufferH = (int) (mDecodeHeight * scale);
+ } else {
+ bufferW = mDecodeWidth;
+ bufferH = mDecodeHeight;
+ }
+
+ if (bufferW == 0 || bufferH == 0) {
+ Trace.endSection();
+ return;
+ }
+ if (mTask != null) {
+ mTask.cancel();
+ }
+ mTask = new DecodeTask(mCurrKey, bufferW, bufferH, this, mCache);
+ mTask.executeOnExecutor(EXECUTOR);
+ Trace.endSection();
+ }
+
+ @Override
+ public void invalidateDrawable(Drawable who) {
+ invalidateSelf();
+ }
+
+ @Override
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(Drawable who, Runnable what) {
+ unscheduleSelf(what);
+ }
+}