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