BasicBitmapDrawable.java revision 93a35b93dc582e38ff8ee5979754a16b4bf4da0c
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.bitmap.drawable;
17
18import android.content.res.Resources;
19import android.graphics.Canvas;
20import android.graphics.ColorFilter;
21import android.graphics.Paint;
22import android.graphics.PixelFormat;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.util.DisplayMetrics;
26import android.util.Log;
27
28import com.android.bitmap.BitmapCache;
29import com.android.bitmap.DecodeTask;
30import com.android.bitmap.DecodeTask.Request;
31import com.android.bitmap.ReusableBitmap;
32import com.android.bitmap.util.BitmapUtils;
33import com.android.bitmap.util.RectUtils;
34import com.android.bitmap.util.Trace;
35
36import java.util.concurrent.Executor;
37import java.util.concurrent.LinkedBlockingQueue;
38import java.util.concurrent.ThreadPoolExecutor;
39import java.util.concurrent.TimeUnit;
40
41/**
42 * This class encapsulates the basic functionality needed to display a single image bitmap,
43 * including request creation/cancelling, and data unbinding and re-binding.
44 * <p>
45 * The actual bitmap decode work is handled by {@link DecodeTask}.
46 */
47public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCallback,
48        Drawable.Callback {
49
50    private BitmapRequestKey mCurrKey;
51    private ReusableBitmap mBitmap;
52    private final BitmapCache mCache;
53    private DecodeTask mTask;
54    private int mDecodeWidth;
55    private int mDecodeHeight;
56
57    // based on framework CL:I015d77
58    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
59    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
60    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
61    private static final Executor SMALL_POOL_EXECUTOR = new ThreadPoolExecutor(
62            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 1, TimeUnit.SECONDS,
63            new LinkedBlockingQueue<Runnable>(128));
64
65    private static final Executor EXECUTOR = SMALL_POOL_EXECUTOR;
66
67    private static final boolean LIMIT_BITMAP_DENSITY = true;
68
69    private static final int MAX_BITMAP_DENSITY = DisplayMetrics.DENSITY_HIGH;
70
71    private final float mDensity;
72    private final Paint mPaint = new Paint();
73    private final Rect mSrcRect = new Rect();
74
75    private static final String TAG = BasicBitmapDrawable.class.getSimpleName();
76    private static final boolean DEBUG = false;
77
78    public BasicBitmapDrawable(final Resources res, final BitmapCache cache) {
79        mDensity = res.getDisplayMetrics().density;
80        mCache = cache;
81        mPaint.setFilterBitmap(true);
82    }
83
84    public DecodeTask.Request getKey() {
85        return mCurrKey;
86    }
87
88    /**
89     * Set the dimensions to decode into.
90     */
91    public void setDecodeDimensions(int w, int h) {
92        mDecodeWidth = w;
93        mDecodeHeight = h;
94        decode();
95    }
96
97    public void unbind() {
98        setImage(null);
99    }
100
101    public void bind(BitmapRequestKey key) {
102        setImage(key);
103    }
104
105    private void setImage(final BitmapRequestKey key) {
106        if (mCurrKey != null && mCurrKey.equals(key)) {
107            return;
108        }
109
110        Trace.beginSection("set image");
111        Trace.beginSection("release reference");
112        if (mBitmap != null) {
113            mBitmap.releaseReference();
114            mBitmap = null;
115        }
116        Trace.endSection();
117        mCurrKey = key;
118
119        if (mTask != null) {
120            mTask.cancel();
121            mTask = null;
122        }
123
124        if (key == null) {
125            invalidateSelf();
126            Trace.endSection();
127            return;
128        }
129
130        // find cached entry here and skip decode if found.
131        final ReusableBitmap cached = mCache.get(key, true /* incrementRefCount */);
132        if (cached != null) {
133            setBitmap(cached);
134            if (DEBUG) {
135                Log.d(TAG, String.format("CACHE HIT key=%s", mCurrKey));
136            }
137        } else {
138            decode();
139            if (DEBUG) {
140                Log.d(TAG, String.format(
141                        "CACHE MISS key=%s\ncache=%s", mCurrKey, mCache.toDebugString()));
142            }
143        }
144        Trace.endSection();
145    }
146
147    @Override
148    public void draw(final Canvas canvas) {
149        final Rect bounds = getBounds();
150        if (bounds.isEmpty()) {
151            return;
152        }
153
154        if (mBitmap != null && mBitmap.bmp != null) {
155            BitmapUtils.calculateCroppedSrcRect(
156                    mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight(),
157                    bounds.width(), bounds.height(),
158                    bounds.height(), Integer.MAX_VALUE,
159                    0.5f, false /* absoluteFraction */,
160                    1, mSrcRect);
161
162            final int orientation = mBitmap.getOrientation();
163            // calculateCroppedSrcRect() gave us the source rectangle "as if" the orientation has
164            // been corrected. We need to decode the uncorrected source rectangle. Calculate true
165            // coordinates.
166            RectUtils.rotateRectForOrientation(orientation,
167                    new Rect(0, 0, mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight()),
168                    mSrcRect);
169
170            // We may need to rotate the canvas, so we also have to rotate the bounds.
171            final Rect rotatedBounds = new Rect(bounds);
172            RectUtils.rotateRect(orientation, bounds.centerX(), bounds.centerY(), rotatedBounds);
173
174            // Rotate the canvas.
175            canvas.save();
176            canvas.rotate(orientation, bounds.centerX(), bounds.centerY());
177            canvas.drawBitmap(mBitmap.bmp, mSrcRect, rotatedBounds, mPaint);
178            canvas.restore();
179        }
180    }
181
182    @Override
183    public void setAlpha(int alpha) {
184        final int old = mPaint.getAlpha();
185        mPaint.setAlpha(alpha);
186        if (alpha != old) {
187            invalidateSelf();
188        }
189    }
190
191    @Override
192    public void setColorFilter(ColorFilter cf) {
193        mPaint.setColorFilter(cf);
194        invalidateSelf();
195    }
196
197    @Override
198    public int getOpacity() {
199        return (mBitmap != null && (mBitmap.bmp.hasAlpha() || mPaint.getAlpha() < 255)) ?
200                PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
201    }
202
203    @Override
204    public void onDecodeBegin(final Request key) { }
205
206    @Override
207    public void onDecodeComplete(final Request key, final ReusableBitmap result) {
208        if (key.equals(mCurrKey)) {
209            setBitmap(result);
210        } else {
211            // if the requests don't match (i.e. this request is stale), decrement the
212            // ref count to allow the bitmap to be pooled
213            if (result != null) {
214                result.releaseReference();
215            }
216        }
217    }
218
219    @Override
220    public void onDecodeCancel(final Request key) { }
221
222    private void setBitmap(ReusableBitmap bmp) {
223        if (mBitmap != null && mBitmap != bmp) {
224            mBitmap.releaseReference();
225        }
226        mBitmap = bmp;
227        invalidateSelf();
228    }
229
230    private void decode() {
231        final int bufferW;
232        final int bufferH;
233
234        if (mCurrKey == null) {
235            return;
236        }
237
238        Trace.beginSection("decode");
239        if (LIMIT_BITMAP_DENSITY) {
240            final float scale =
241                    Math.min(1f, (float) MAX_BITMAP_DENSITY / DisplayMetrics.DENSITY_DEFAULT
242                            / mDensity);
243            bufferW = (int) (mDecodeWidth * scale);
244            bufferH = (int) (mDecodeHeight * scale);
245        } else {
246            bufferW = mDecodeWidth;
247            bufferH = mDecodeHeight;
248        }
249
250        if (bufferW == 0 || bufferH == 0) {
251            Trace.endSection();
252            return;
253        }
254        if (mTask != null) {
255            mTask.cancel();
256        }
257        mTask = new DecodeTask(mCurrKey, bufferW, bufferH, this, mCache);
258        mTask.executeOnExecutor(EXECUTOR);
259        Trace.endSection();
260    }
261
262    @Override
263    public void invalidateDrawable(Drawable who) {
264        invalidateSelf();
265    }
266
267    @Override
268    public void scheduleDrawable(Drawable who, Runnable what, long when) {
269        scheduleSelf(what, when);
270    }
271
272    @Override
273    public void unscheduleDrawable(Drawable who, Runnable what) {
274        unscheduleSelf(what);
275    }
276}
277