diff options
author | Sam Blitzstein <sblitz@google.com> | 2013-10-09 14:11:27 -0700 |
---|---|---|
committer | Sam Blitzstein <sblitz@google.com> | 2013-10-15 17:34:58 -0700 |
commit | 93a35b93dc582e38ff8ee5979754a16b4bf4da0c (patch) | |
tree | 9034ab3155e8781b0cd77fb70882f911080f6f89 /src/com/android/bitmap/drawable/BasicBitmapDrawable.java | |
parent | ce2b0fdc1e9c9d083faab75b6bdfbea27bf574e2 (diff) | |
download | bitmap-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.java | 276 |
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); + } +} |