BasicBitmapDrawable.java revision 2e4d0863dba53435372ec96538f2ef3e1c3675bf
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.DecodeCallback;
31import com.android.bitmap.DecodeTask.DecodeOptions;
32import com.android.bitmap.NamedThreadFactory;
33import com.android.bitmap.RequestKey;
34import com.android.bitmap.RequestKey.Cancelable;
35import com.android.bitmap.RequestKey.FileDescriptorFactory;
36import com.android.bitmap.ReusableBitmap;
37import com.android.bitmap.util.BitmapUtils;
38import com.android.bitmap.util.RectUtils;
39import com.android.bitmap.util.Trace;
40
41import java.util.concurrent.Executor;
42import java.util.concurrent.LinkedBlockingQueue;
43import java.util.concurrent.ThreadPoolExecutor;
44import java.util.concurrent.TimeUnit;
45
46/**
47 * This class encapsulates the basic functionality needed to display a single image bitmap,
48 * including request creation/cancelling, and data unbinding and re-binding.
49 * <p>
50 * The actual bitmap decode work is handled by {@link DecodeTask}.
51 * <p>
52 * If being used with a long-lived cache (static cache, attached to the Application instead of the
53 * Activity, etc) then make sure to call {@link BasicBitmapDrawable#unbind()} at the appropriate
54 * times so the cache has accurate unref counts. The
55 * {@link com.android.bitmap.view.BitmapDrawableImageView} class has been created to do the
56 * appropriate unbind operation when the view is detached from the window.
57 */
58public class BasicBitmapDrawable extends Drawable implements DecodeCallback,
59        Drawable.Callback, RequestKey.Callback {
60
61    protected static Rect sRect;
62
63    protected RequestKey mCurrKey;
64    protected RequestKey mPrevKey;
65
66    protected final Paint mPaint = new Paint();
67    private final BitmapCache mCache;
68
69    private final boolean mLimitDensity;
70    private final float mDensity;
71    private ReusableBitmap mBitmap;
72    private DecodeTask mTask;
73    private Cancelable mCreateFileDescriptorFactoryTask;
74    private int mDecodeWidth;
75    private int mDecodeHeight;
76
77    // based on framework CL:I015d77
78    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
79    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
80    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
81
82    private static final Executor SMALL_POOL_EXECUTOR = new ThreadPoolExecutor(
83            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 1, TimeUnit.SECONDS,
84            new LinkedBlockingQueue<Runnable>(128), new NamedThreadFactory("decode"));
85    private static final Executor EXECUTOR = SMALL_POOL_EXECUTOR;
86
87    private static final int MAX_BITMAP_DENSITY = DisplayMetrics.DENSITY_HIGH;
88    private static final float VERTICAL_CENTER = 1f / 2;
89    private static final float NO_MULTIPLIER = 1f;
90
91    private static final String TAG = BasicBitmapDrawable.class.getSimpleName();
92    private static final boolean DEBUG = DecodeTask.DEBUG;
93
94    public BasicBitmapDrawable(final Resources res, final BitmapCache cache,
95            final boolean limitDensity) {
96        mDensity = res.getDisplayMetrics().density;
97        mCache = cache;
98        mLimitDensity = limitDensity;
99        mPaint.setFilterBitmap(true);
100        mPaint.setAntiAlias(true);
101        mPaint.setDither(true);
102
103        if (sRect == null) {
104            sRect = new Rect();
105        }
106    }
107
108    public RequestKey getKey() {
109        return mCurrKey;
110    }
111
112    protected ReusableBitmap getBitmap() {
113        return mBitmap;
114    }
115
116    /**
117     * Set the dimensions to decode into.
118     */
119    public void setDecodeDimensions(int w, int h) {
120        mDecodeWidth = w;
121        mDecodeHeight = h;
122        loadFileDescriptorFactory();
123    }
124
125    public void unbind() {
126        setImage(null);
127    }
128
129    public void bind(RequestKey key) {
130        setImage(key);
131    }
132
133    protected void setImage(final RequestKey key) {
134        if (mCurrKey != null && mCurrKey.equals(key)) {
135            return;
136        }
137
138        Trace.beginSection("set image");
139        Trace.beginSection("release reference");
140        if (mBitmap != null) {
141            mBitmap.releaseReference();
142            mBitmap = null;
143        }
144        Trace.endSection();
145
146        mPrevKey = mCurrKey;
147        mCurrKey = key;
148
149        if (mTask != null) {
150            mTask.cancel();
151            mTask = null;
152        }
153        if (mCreateFileDescriptorFactoryTask != null) {
154            mCreateFileDescriptorFactoryTask.cancel();
155            mCreateFileDescriptorFactoryTask = null;
156        }
157
158        if (key == null) {
159            invalidateSelf();
160            Trace.endSection();
161            return;
162        }
163
164        // find cached entry here and skip decode if found.
165        final ReusableBitmap cached = mCache.get(key, true /* incrementRefCount */);
166        if (cached != null) {
167            setBitmap(cached);
168            if (DEBUG) {
169                Log.d(TAG, String.format("CACHE HIT key=%s", mCurrKey));
170            }
171        } else {
172            loadFileDescriptorFactory();
173            if (DEBUG) {
174                Log.d(TAG, String.format(
175                        "CACHE MISS key=%s\ncache=%s", mCurrKey, mCache.toDebugString()));
176            }
177        }
178        Trace.endSection();
179    }
180
181    protected void setBitmap(ReusableBitmap bmp) {
182        if (mBitmap != null && mBitmap != bmp) {
183            mBitmap.releaseReference();
184        }
185        mBitmap = bmp;
186        invalidateSelf();
187    }
188
189    protected void loadFileDescriptorFactory() {
190        if (mCurrKey == null || mDecodeWidth == 0 || mDecodeHeight == 0) {
191            return;
192        }
193
194        // Create file descriptor if request supports it.
195        mCreateFileDescriptorFactoryTask = mCurrKey
196                .createFileDescriptorFactoryAsync(mCurrKey, this);
197        if (mCreateFileDescriptorFactoryTask == null) {
198            // Use input stream if request does not.
199            decode(null);
200        }
201    }
202
203    @Override
204    public void fileDescriptorFactoryCreated(final RequestKey key,
205            final FileDescriptorFactory factory) {
206        if (mCreateFileDescriptorFactoryTask == null) {
207            // Cancelled.
208            return;
209        }
210        mCreateFileDescriptorFactoryTask = null;
211
212        if (key.equals(mCurrKey)) {
213            decode(factory);
214        }
215    }
216
217    protected void decode(final FileDescriptorFactory factory) {
218        Trace.beginSection("decode");
219        final int bufferW;
220        final int bufferH;
221        if (mLimitDensity) {
222            final float scale =
223                    Math.min(1f, (float) MAX_BITMAP_DENSITY / DisplayMetrics.DENSITY_DEFAULT
224                            / mDensity);
225            bufferW = (int) (mDecodeWidth * scale);
226            bufferH = (int) (mDecodeHeight * scale);
227        } else {
228            bufferW = mDecodeWidth;
229            bufferH = mDecodeHeight;
230        }
231
232        if (mTask != null) {
233            mTask.cancel();
234        }
235        final DecodeOptions opts = new DecodeOptions(bufferW, bufferH, getDecodeVerticalCenter(),
236                DecodeOptions.STRATEGY_ROUND_NEAREST);
237        mTask = new DecodeTask(mCurrKey, opts, factory, this, mCache);
238        mTask.executeOnExecutor(EXECUTOR);
239        Trace.endSection();
240    }
241
242    protected float getDrawVerticalCenter() {
243        return VERTICAL_CENTER;
244    }
245
246    protected float getDrawVerticalOffsetMultiplier() {
247        return NO_MULTIPLIER;
248    }
249
250    protected float getDecodeVerticalCenter() {
251        return VERTICAL_CENTER;
252    }
253
254    @Override
255    public void draw(final Canvas canvas) {
256        final Rect bounds = getBounds();
257        if (bounds.isEmpty()) {
258            return;
259        }
260
261        if (mBitmap != null && mBitmap.bmp != null) {
262            BitmapUtils.calculateCroppedSrcRect(
263                    mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight(),
264                    bounds.width(), bounds.height(),
265                    bounds.height(), Integer.MAX_VALUE,
266                    getDrawVerticalCenter(), false /* absoluteFraction */,
267                    getDrawVerticalOffsetMultiplier(), sRect);
268
269            final int orientation = mBitmap.getOrientation();
270            // calculateCroppedSrcRect() gave us the source rectangle "as if" the orientation has
271            // been corrected. We need to decode the uncorrected source rectangle. Calculate true
272            // coordinates.
273            RectUtils.rotateRectForOrientation(orientation,
274                    new Rect(0, 0, mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight()),
275                    sRect);
276
277            // We may need to rotate the canvas, so we also have to rotate the bounds.
278            final Rect rotatedBounds = new Rect(bounds);
279            RectUtils.rotateRect(orientation, bounds.centerX(), bounds.centerY(), rotatedBounds);
280
281            // Rotate the canvas.
282            canvas.save();
283            canvas.rotate(orientation, bounds.centerX(), bounds.centerY());
284            canvas.drawBitmap(mBitmap.bmp, sRect, rotatedBounds, mPaint);
285            canvas.restore();
286        }
287    }
288
289    @Override
290    public void setAlpha(int alpha) {
291        final int old = mPaint.getAlpha();
292        mPaint.setAlpha(alpha);
293        if (alpha != old) {
294            invalidateSelf();
295        }
296    }
297
298    @Override
299    public void setColorFilter(ColorFilter cf) {
300        mPaint.setColorFilter(cf);
301        invalidateSelf();
302    }
303
304    @Override
305    public int getOpacity() {
306        return (mBitmap != null && (mBitmap.bmp.hasAlpha() || mPaint.getAlpha() < 255)) ?
307                PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
308    }
309
310    @Override
311    public void onDecodeBegin(final RequestKey key) { }
312
313    @Override
314    public void onDecodeComplete(final RequestKey key, final ReusableBitmap result) {
315        if (key.equals(mCurrKey)) {
316            setBitmap(result);
317        } else {
318            // if the requests don't match (i.e. this request is stale), decrement the
319            // ref count to allow the bitmap to be pooled
320            if (result != null) {
321                result.releaseReference();
322            }
323        }
324    }
325
326    @Override
327    public void onDecodeCancel(final RequestKey key) { }
328
329    @Override
330    public void invalidateDrawable(Drawable who) {
331        invalidateSelf();
332    }
333
334    @Override
335    public void scheduleDrawable(Drawable who, Runnable what, long when) {
336        scheduleSelf(what, when);
337    }
338
339    @Override
340    public void unscheduleDrawable(Drawable who, Runnable what) {
341        unscheduleSelf(what);
342    }
343}
344