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