/* * 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.DecodeOptions; import com.android.bitmap.NamedThreadFactory; import com.android.bitmap.RequestKey; 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. *

* The actual bitmap decode work is handled by {@link DecodeTask}. */ public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCallback, Drawable.Callback { protected RequestKey mCurrKey; private ReusableBitmap mBitmap; private final BitmapCache mCache; private final boolean mLimitDensity; private DecodeTask mTask; private int mDecodeWidth; private int mDecodeHeight; private static final String TAG = BasicBitmapDrawable.class.getSimpleName(); // 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(128), new NamedThreadFactory("decode")); private static final Executor EXECUTOR = SMALL_POOL_EXECUTOR; private static final int MAX_BITMAP_DENSITY = DisplayMetrics.DENSITY_HIGH; private static final float VERTICAL_CENTER = 1f / 2; private final float mDensity; private final Paint mPaint = new Paint(); private final Rect mSrcRect = new Rect(); private static final boolean DEBUG = DecodeTask.DEBUG; public BasicBitmapDrawable(final Resources res, final BitmapCache cache, final boolean limitDensity) { mDensity = res.getDisplayMetrics().density; mCache = cache; mLimitDensity = limitDensity; mPaint.setFilterBitmap(true); } public RequestKey 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(RequestKey key) { setImage(key); } private void setImage(final RequestKey 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, VERTICAL_CENTER, 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 RequestKey key) { } @Override public void onDecodeComplete(final RequestKey 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 RequestKey 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 (mLimitDensity) { 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(); } final DecodeOptions opts = new DecodeOptions(bufferW, bufferH, VERTICAL_CENTER, DecodeOptions.STRATEGY_ROUND_NEAREST); mTask = new DecodeTask(mCurrKey, opts, 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); } }