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