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