BasicBitmapDrawable.java revision ad97cd0e0753930ec68f189e6be1023dc0f62f75
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 * All UI operations should be called from the UI thread. 139 */ 140 public void bind(RequestKey key) { 141 Trace.beginSection("bind"); 142 if (mCurrKey != null && mCurrKey.equals(key)) { 143 return; 144 } 145 setImage(key); 146 Trace.endSection(); 147 } 148 149 /** 150 * Unbinds the current key and bitmap from the drawable. This will cause the bitmap to decrement 151 * its ref count. 152 * 153 * All UI operations should be called from the UI thread. 154 */ 155 public void unbind() { 156 Trace.beginSection("unbind"); 157 setImage(null); 158 Trace.endSection(); 159 } 160 161 /** 162 * Should only be overriden, not called. 163 */ 164 protected void setImage(final RequestKey key) { 165 Trace.beginSection("set image"); 166 Trace.beginSection("release reference"); 167 if (mBitmap != null) { 168 mBitmap.releaseReference(); 169 mBitmap = null; 170 } 171 Trace.endSection(); 172 173 mPrevKey = mCurrKey; 174 mCurrKey = key; 175 176 if (mTask != null) { 177 mTask.cancel(); 178 mTask = null; 179 } 180 if (mCreateFileDescriptorFactoryTask != null) { 181 mCreateFileDescriptorFactoryTask.cancel(); 182 mCreateFileDescriptorFactoryTask = null; 183 } 184 185 if (key == null) { 186 invalidateSelf(); 187 Trace.endSection(); 188 return; 189 } 190 191 // find cached entry here and skip decode if found. 192 final ReusableBitmap cached = mCache.get(key, true /* incrementRefCount */); 193 if (cached != null) { 194 setBitmap(cached); 195 if (DEBUG) { 196 Log.d(TAG, String.format("CACHE HIT key=%s", mCurrKey)); 197 } 198 } else { 199 loadFileDescriptorFactory(); 200 if (DEBUG) { 201 Log.d(TAG, String.format( 202 "CACHE MISS key=%s\ncache=%s", mCurrKey, mCache.toDebugString())); 203 } 204 } 205 Trace.endSection(); 206 } 207 208 /** 209 * Should only be overriden, not called. 210 */ 211 protected void setBitmap(ReusableBitmap bmp) { 212 if (hasBitmap()) { 213 mBitmap.releaseReference(); 214 } 215 mBitmap = bmp; 216 invalidateSelf(); 217 } 218 219 /** 220 * Should only be overriden, not called. 221 */ 222 protected void loadFileDescriptorFactory() { 223 if (mCurrKey == null || mDecodeWidth == 0 || mDecodeHeight == 0) { 224 return; 225 } 226 227 // Create file descriptor if request supports it. 228 mCreateFileDescriptorFactoryTask = mCurrKey 229 .createFileDescriptorFactoryAsync(mCurrKey, this); 230 if (mCreateFileDescriptorFactoryTask == null) { 231 // Use input stream if request does not. 232 decode(null); 233 } 234 } 235 236 @Override 237 public void fileDescriptorFactoryCreated(final RequestKey key, 238 final FileDescriptorFactory factory) { 239 if (mCreateFileDescriptorFactoryTask == null) { 240 // Cancelled. 241 return; 242 } 243 mCreateFileDescriptorFactoryTask = null; 244 245 if (key.equals(mCurrKey)) { 246 decode(factory); 247 } 248 } 249 250 /** 251 * Should only be overriden, not called. 252 */ 253 protected void decode(final FileDescriptorFactory factory) { 254 Trace.beginSection("decode"); 255 final int bufferW; 256 final int bufferH; 257 if (mLimitDensity) { 258 final float scale = 259 Math.min(1f, (float) MAX_BITMAP_DENSITY / DisplayMetrics.DENSITY_DEFAULT 260 / mDensity); 261 bufferW = (int) (mDecodeWidth * scale); 262 bufferH = (int) (mDecodeHeight * scale); 263 } else { 264 bufferW = mDecodeWidth; 265 bufferH = mDecodeHeight; 266 } 267 268 if (mTask != null) { 269 mTask.cancel(); 270 } 271 final DecodeOptions opts = new DecodeOptions(bufferW, bufferH, getDecodeVerticalCenter(), 272 DecodeOptions.STRATEGY_ROUND_NEAREST); 273 mTask = new DecodeTask(mCurrKey, opts, factory, this, mCache); 274 mTask.executeOnExecutor(getExecutor()); 275 Trace.endSection(); 276 } 277 278 protected Executor getExecutor() { 279 return EXECUTOR; 280 } 281 282 protected float getDrawVerticalCenter() { 283 return VERTICAL_CENTER; 284 } 285 286 protected float getDrawVerticalOffsetMultiplier() { 287 return NO_MULTIPLIER; 288 } 289 290 /** 291 * Clients can override this to specify which section of the source image to decode from. 292 * Possible applications include using face detection to always decode around facial features. 293 */ 294 protected float getDecodeVerticalCenter() { 295 return VERTICAL_CENTER; 296 } 297 298 @Override 299 public void draw(final Canvas canvas) { 300 final Rect bounds = getBounds(); 301 if (bounds.isEmpty()) { 302 return; 303 } 304 305 if (hasBitmap()) { 306 BitmapUtils.calculateCroppedSrcRect( 307 mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight(), 308 bounds.width(), bounds.height(), 309 bounds.height(), Integer.MAX_VALUE, 310 getDrawVerticalCenter(), false /* absoluteFraction */, 311 getDrawVerticalOffsetMultiplier(), sRect); 312 313 final int orientation = mBitmap.getOrientation(); 314 // calculateCroppedSrcRect() gave us the source rectangle "as if" the orientation has 315 // been corrected. We need to decode the uncorrected source rectangle. Calculate true 316 // coordinates. 317 RectUtils.rotateRectForOrientation(orientation, 318 new Rect(0, 0, mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight()), 319 sRect); 320 321 // We may need to rotate the canvas, so we also have to rotate the bounds. 322 final Rect rotatedBounds = new Rect(bounds); 323 RectUtils.rotateRect(orientation, bounds.centerX(), bounds.centerY(), rotatedBounds); 324 325 // Rotate the canvas. 326 canvas.save(); 327 canvas.rotate(orientation, bounds.centerX(), bounds.centerY()); 328 onDrawBitmap(canvas, sRect, rotatedBounds); 329 canvas.restore(); 330 } 331 } 332 333 protected boolean hasBitmap() { 334 return mBitmap != null && mBitmap.bmp != null; 335 } 336 337 /** 338 * Override this method to customize how to draw the bitmap to the canvas for the given bounds. 339 * The bitmap to be drawn can be found at {@link #getBitmap()}. 340 */ 341 protected void onDrawBitmap(final Canvas canvas, final Rect src, final Rect dst) { 342 if (hasBitmap()) { 343 canvas.drawBitmap(mBitmap.bmp, src, dst, mPaint); 344 } 345 } 346 347 @Override 348 public void setAlpha(int alpha) { 349 final int old = mPaint.getAlpha(); 350 mPaint.setAlpha(alpha); 351 if (alpha != old) { 352 invalidateSelf(); 353 } 354 } 355 356 @Override 357 public void setColorFilter(ColorFilter cf) { 358 mPaint.setColorFilter(cf); 359 invalidateSelf(); 360 } 361 362 @Override 363 public int getOpacity() { 364 return (hasBitmap() && (mBitmap.bmp.hasAlpha() || mPaint.getAlpha() < 255)) ? 365 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 366 } 367 368 @Override 369 public void onDecodeBegin(final RequestKey key) { } 370 371 @Override 372 public void onDecodeComplete(final RequestKey key, final ReusableBitmap result) { 373 if (key.equals(mCurrKey)) { 374 setBitmap(result); 375 } else { 376 // if the requests don't match (i.e. this request is stale), decrement the 377 // ref count to allow the bitmap to be pooled 378 if (result != null) { 379 result.releaseReference(); 380 } 381 } 382 } 383 384 @Override 385 public void onDecodeCancel(final RequestKey key) { } 386 387 @Override 388 public void invalidateDrawable(Drawable who) { 389 invalidateSelf(); 390 } 391 392 @Override 393 public void scheduleDrawable(Drawable who, Runnable what, long when) { 394 scheduleSelf(what, when); 395 } 396 397 @Override 398 public void unscheduleDrawable(Drawable who, Runnable what) { 399 unscheduleSelf(what); 400 } 401} 402