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