BasicBitmapDrawable.java revision d05e64cf9f9b1542ccdac8675c63b8b185c97a48
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 protected int mDecodeWidth; 66 protected int mDecodeHeight; 67 68 protected final Paint mPaint = new Paint(); 69 private final BitmapCache mCache; 70 71 private final boolean mLimitDensity; 72 private final float mDensity; 73 private ReusableBitmap mBitmap; 74 private DecodeTask mTask; 75 private Cancelable mCreateFileDescriptorFactoryTask; 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 final RequestKey getKey() { 109 return mCurrKey; 110 } 111 112 public final RequestKey getPreviousKey() { 113 return mPrevKey; 114 } 115 116 protected ReusableBitmap getBitmap() { 117 return mBitmap; 118 } 119 120 /** 121 * Set the dimensions to decode into. These dimensions should never change while the drawable is 122 * attached to the same cache, because caches can only contain bitmaps of one size for re-use. 123 * 124 * All UI operations should be called from the UI thread. 125 */ 126 public void setDecodeDimensions(int width, int height) { 127 if (mDecodeWidth == 0 || mDecodeHeight == 0) { 128 mDecodeWidth = width; 129 mDecodeHeight = height; 130 setImage(mCurrKey); 131 } 132 } 133 134 /** 135 * Binds to the given key and start the decode process. This will first look in the cache, then 136 * decode from the request key if not found. 137 * 138 * The key being replaced will be kept in {@link #mPrevKey}. 139 * 140 * All UI operations should be called from the UI thread. 141 */ 142 public void bind(RequestKey key) { 143 Trace.beginSection("bind"); 144 if (mCurrKey != null && mCurrKey.equals(key)) { 145 Trace.endSection(); 146 return; 147 } 148 setImage(key); 149 Trace.endSection(); 150 } 151 152 /** 153 * Unbinds the current key and bitmap from the drawable. This will cause the bitmap to decrement 154 * its ref count. 155 * 156 * This will assume that you do not want to keep the unbound key in {@link #mPrevKey}. 157 * 158 * All UI operations should be called from the UI thread. 159 */ 160 public void unbind() { 161 unbind(false); 162 } 163 164 /** 165 * Unbinds the current key and bitmap from the drawable. This will cause the bitmap to decrement 166 * its ref count. 167 * 168 * If the temporary parameter is true, we will keep the unbound key in {@link #mPrevKey}. 169 * 170 * All UI operations should be called from the UI thread. 171 */ 172 public void unbind(boolean temporary) { 173 Trace.beginSection("unbind"); 174 setImage(null); 175 if (!temporary) { 176 mPrevKey = null; 177 } 178 Trace.endSection(); 179 } 180 181 /** 182 * Should only be overriden, not called. 183 */ 184 protected void setImage(final RequestKey key) { 185 Trace.beginSection("set image"); 186 Trace.beginSection("release reference"); 187 if (mBitmap != null) { 188 mBitmap.releaseReference(); 189 mBitmap = null; 190 } 191 Trace.endSection(); 192 193 mPrevKey = mCurrKey; 194 mCurrKey = key; 195 196 if (mTask != null) { 197 mTask.cancel(); 198 mTask = null; 199 } 200 if (mCreateFileDescriptorFactoryTask != null) { 201 mCreateFileDescriptorFactoryTask.cancel(); 202 mCreateFileDescriptorFactoryTask = null; 203 } 204 205 if (key == null) { 206 onDecodeFailed(); 207 Trace.endSection(); 208 return; 209 } 210 211 // find cached entry here and skip decode if found. 212 final ReusableBitmap cached = mCache.get(key, true /* incrementRefCount */); 213 if (cached != null) { 214 setBitmap(cached); 215 if (DEBUG) { 216 Log.d(TAG, String.format("CACHE HIT key=%s", mCurrKey)); 217 } 218 } else { 219 loadFileDescriptorFactory(); 220 if (DEBUG) { 221 Log.d(TAG, String.format( 222 "CACHE MISS key=%s\ncache=%s", mCurrKey, mCache.toDebugString())); 223 } 224 } 225 Trace.endSection(); 226 } 227 228 /** 229 * Should only be overriden, not called. 230 */ 231 protected void setBitmap(ReusableBitmap bmp) { 232 if (hasBitmap()) { 233 mBitmap.releaseReference(); 234 } 235 mBitmap = bmp; 236 invalidateSelf(); 237 } 238 239 /** 240 * Should only be overriden, not called. 241 */ 242 protected void loadFileDescriptorFactory() { 243 if (mCurrKey == null || mDecodeWidth == 0 || mDecodeHeight == 0) { 244 onDecodeFailed(); 245 return; 246 } 247 248 // Create file descriptor if request supports it. 249 mCreateFileDescriptorFactoryTask = mCurrKey 250 .createFileDescriptorFactoryAsync(mCurrKey, this); 251 if (mCreateFileDescriptorFactoryTask == null) { 252 // Use input stream if request does not. 253 decode(null); 254 } 255 } 256 257 @Override 258 public void fileDescriptorFactoryCreated(final RequestKey key, 259 final FileDescriptorFactory factory) { 260 if (mCreateFileDescriptorFactoryTask == null) { 261 // Cancelled. 262 onDecodeFailed(); 263 return; 264 } 265 mCreateFileDescriptorFactoryTask = null; 266 267 if (factory == null) { 268 // Failed. 269 onDecodeFailed(); 270 return; 271 } 272 273 if (key.equals(mCurrKey)) { 274 decode(factory); 275 } 276 } 277 278 /** 279 * Called when the decode process is cancelled at any time. 280 */ 281 protected void onDecodeFailed() { 282 invalidateSelf(); 283 } 284 285 /** 286 * Should only be overriden, not called. 287 */ 288 protected void decode(final FileDescriptorFactory factory) { 289 Trace.beginSection("decode"); 290 final int bufferW; 291 final int bufferH; 292 if (mLimitDensity) { 293 final float scale = 294 Math.min(1f, (float) MAX_BITMAP_DENSITY / DisplayMetrics.DENSITY_DEFAULT 295 / mDensity); 296 bufferW = (int) (mDecodeWidth * scale); 297 bufferH = (int) (mDecodeHeight * scale); 298 } else { 299 bufferW = mDecodeWidth; 300 bufferH = mDecodeHeight; 301 } 302 303 if (mTask != null) { 304 mTask.cancel(); 305 } 306 final DecodeOptions opts = new DecodeOptions(bufferW, bufferH, getDecodeVerticalCenter(), 307 getDecodeStrategy()); 308 mTask = new DecodeTask(mCurrKey, opts, factory, this, mCache); 309 mTask.executeOnExecutor(getExecutor()); 310 Trace.endSection(); 311 } 312 313 /** 314 * Return one of the STRATEGY constants in {@link DecodeOptions}. 315 */ 316 protected int getDecodeStrategy() { 317 return DecodeOptions.STRATEGY_ROUND_NEAREST; 318 } 319 320 protected Executor getExecutor() { 321 return EXECUTOR; 322 } 323 324 protected float getDrawVerticalCenter() { 325 return VERTICAL_CENTER; 326 } 327 328 protected float getDrawVerticalOffsetMultiplier() { 329 return NO_MULTIPLIER; 330 } 331 332 /** 333 * Clients can override this to specify which section of the source image to decode from. 334 * Possible applications include using face detection to always decode around facial features. 335 */ 336 protected float getDecodeVerticalCenter() { 337 return VERTICAL_CENTER; 338 } 339 340 @Override 341 public void draw(final Canvas canvas) { 342 final Rect bounds = getBounds(); 343 if (bounds.isEmpty()) { 344 return; 345 } 346 347 if (hasBitmap()) { 348 BitmapUtils.calculateCroppedSrcRect( 349 mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight(), 350 bounds.width(), bounds.height(), 351 bounds.height(), Integer.MAX_VALUE, 352 getDrawVerticalCenter(), false /* absoluteFraction */, 353 getDrawVerticalOffsetMultiplier(), sRect); 354 355 final int orientation = mBitmap.getOrientation(); 356 // calculateCroppedSrcRect() gave us the source rectangle "as if" the orientation has 357 // been corrected. We need to decode the uncorrected source rectangle. Calculate true 358 // coordinates. 359 RectUtils.rotateRectForOrientation(orientation, 360 new Rect(0, 0, mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight()), 361 sRect); 362 363 // We may need to rotate the canvas, so we also have to rotate the bounds. 364 final Rect rotatedBounds = new Rect(bounds); 365 RectUtils.rotateRect(orientation, bounds.centerX(), bounds.centerY(), rotatedBounds); 366 367 // Rotate the canvas. 368 canvas.save(); 369 canvas.rotate(orientation, bounds.centerX(), bounds.centerY()); 370 onDrawBitmap(canvas, sRect, rotatedBounds); 371 canvas.restore(); 372 } 373 } 374 375 protected boolean hasBitmap() { 376 return mBitmap != null && mBitmap.bmp != null; 377 } 378 379 /** 380 * Override this method to customize how to draw the bitmap to the canvas for the given bounds. 381 * The bitmap to be drawn can be found at {@link #getBitmap()}. 382 */ 383 protected void onDrawBitmap(final Canvas canvas, final Rect src, final Rect dst) { 384 if (hasBitmap()) { 385 canvas.drawBitmap(mBitmap.bmp, src, dst, mPaint); 386 } 387 } 388 389 @Override 390 public void setAlpha(int alpha) { 391 final int old = mPaint.getAlpha(); 392 mPaint.setAlpha(alpha); 393 if (alpha != old) { 394 invalidateSelf(); 395 } 396 } 397 398 @Override 399 public void setColorFilter(ColorFilter cf) { 400 mPaint.setColorFilter(cf); 401 invalidateSelf(); 402 } 403 404 @Override 405 public int getOpacity() { 406 return (hasBitmap() && (mBitmap.bmp.hasAlpha() || mPaint.getAlpha() < 255)) ? 407 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 408 } 409 410 @Override 411 public void onDecodeBegin(final RequestKey key) { } 412 413 @Override 414 public void onDecodeComplete(final RequestKey key, final ReusableBitmap result) { 415 if (key.equals(mCurrKey)) { 416 setBitmap(result); 417 } else { 418 // if the requests don't match (i.e. this request is stale), decrement the 419 // ref count to allow the bitmap to be pooled 420 if (result != null) { 421 result.releaseReference(); 422 } 423 } 424 } 425 426 @Override 427 public void onDecodeCancel(final RequestKey key) { } 428 429 @Override 430 public void invalidateDrawable(Drawable who) { 431 invalidateSelf(); 432 } 433 434 @Override 435 public void scheduleDrawable(Drawable who, Runnable what, long when) { 436 scheduleSelf(what, when); 437 } 438 439 @Override 440 public void unscheduleDrawable(Drawable who, Runnable what) { 441 unscheduleSelf(what); 442 } 443} 444