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