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