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