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