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