BasicBitmapDrawable.java revision a23358fbfd62c7aa1c84bfa8395b4dc427a71ce6
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.DecodeOptions; 31import com.android.bitmap.NamedThreadFactory; 32import com.android.bitmap.RequestKey; 33import com.android.bitmap.ReusableBitmap; 34import com.android.bitmap.util.BitmapUtils; 35import com.android.bitmap.util.RectUtils; 36import com.android.bitmap.util.Trace; 37import com.android.bitmap.view.BasicImageView; 38 39import java.util.concurrent.Executor; 40import java.util.concurrent.LinkedBlockingQueue; 41import java.util.concurrent.ThreadPoolExecutor; 42import java.util.concurrent.TimeUnit; 43 44/** 45 * This class encapsulates the basic functionality needed to display a single image bitmap, 46 * including request creation/cancelling, and data unbinding and re-binding. 47 * <p> 48 * The actual bitmap decode work is handled by {@link DecodeTask}. 49 * <p> 50 * If being used with a long-lived cache (static cache, attached to the Application instead of the 51 * Activity, etc) then make sure to call {@link BasicBitmapDrawable#unbind()} at the appropriate 52 * times so the cache has accurate unref counts. The {@link BasicImageView} class has been created 53 * to do the appropriate unbind operation when the view is detached from the window. 54 */ 55public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCallback, 56 Drawable.Callback { 57 protected static Rect sRect; 58 59 protected RequestKey mCurrKey; 60 protected final Paint mPaint = new Paint(); 61 62 private final BitmapCache mCache; 63 private final boolean mLimitDensity; 64 private final float mDensity; 65 private ReusableBitmap mBitmap; 66 private DecodeTask mTask; 67 private int mDecodeWidth; 68 private int mDecodeHeight; 69 70 // based on framework CL:I015d77 71 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 72 private static final int CORE_POOL_SIZE = CPU_COUNT + 1; 73 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; 74 75 private static final Executor SMALL_POOL_EXECUTOR = new ThreadPoolExecutor( 76 CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 1, TimeUnit.SECONDS, 77 new LinkedBlockingQueue<Runnable>(128), new NamedThreadFactory("decode")); 78 private static final Executor EXECUTOR = SMALL_POOL_EXECUTOR; 79 80 private static final int MAX_BITMAP_DENSITY = DisplayMetrics.DENSITY_HIGH; 81 private static final float VERTICAL_CENTER = 1f / 2; 82 83 private static final String TAG = BasicBitmapDrawable.class.getSimpleName(); 84 private static final boolean DEBUG = DecodeTask.DEBUG; 85 86 public BasicBitmapDrawable(final Resources res, final BitmapCache cache, 87 final boolean limitDensity) { 88 mDensity = res.getDisplayMetrics().density; 89 mCache = cache; 90 mLimitDensity = limitDensity; 91 mPaint.setFilterBitmap(true); 92 mPaint.setAntiAlias(true); 93 mPaint.setDither(true); 94 95 if (sRect == null) { 96 sRect = new Rect(); 97 } 98 } 99 100 public RequestKey getKey() { 101 return mCurrKey; 102 } 103 104 /** 105 * Set the dimensions to decode into. 106 */ 107 public void setDecodeDimensions(int w, int h) { 108 mDecodeWidth = w; 109 mDecodeHeight = h; 110 decode(); 111 } 112 113 public void unbind() { 114 setImage(null); 115 } 116 117 public void bind(RequestKey key) { 118 setImage(key); 119 } 120 121 private void setImage(final RequestKey key) { 122 if (mCurrKey != null && mCurrKey.equals(key)) { 123 return; 124 } 125 126 Trace.beginSection("set image"); 127 Trace.beginSection("release reference"); 128 if (mBitmap != null) { 129 mBitmap.releaseReference(); 130 mBitmap = null; 131 } 132 Trace.endSection(); 133 mCurrKey = key; 134 135 if (mTask != null) { 136 mTask.cancel(); 137 mTask = null; 138 } 139 140 if (key == null) { 141 invalidateSelf(); 142 Trace.endSection(); 143 return; 144 } 145 146 // find cached entry here and skip decode if found. 147 final ReusableBitmap cached = mCache.get(key, true /* incrementRefCount */); 148 if (cached != null) { 149 setBitmap(cached); 150 if (DEBUG) { 151 Log.d(TAG, String.format("CACHE HIT key=%s", mCurrKey)); 152 } 153 } else { 154 decode(); 155 if (DEBUG) { 156 Log.d(TAG, String.format( 157 "CACHE MISS key=%s\ncache=%s", mCurrKey, mCache.toDebugString())); 158 } 159 } 160 Trace.endSection(); 161 } 162 163 @Override 164 public void draw(final Canvas canvas) { 165 final Rect bounds = getBounds(); 166 if (bounds.isEmpty()) { 167 return; 168 } 169 170 if (mBitmap != null && mBitmap.bmp != null) { 171 BitmapUtils.calculateCroppedSrcRect( 172 mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight(), 173 bounds.width(), bounds.height(), 174 bounds.height(), Integer.MAX_VALUE, 175 VERTICAL_CENTER, false /* absoluteFraction */, 176 1, sRect); 177 178 final int orientation = mBitmap.getOrientation(); 179 // calculateCroppedSrcRect() gave us the source rectangle "as if" the orientation has 180 // been corrected. We need to decode the uncorrected source rectangle. Calculate true 181 // coordinates. 182 RectUtils.rotateRectForOrientation(orientation, 183 new Rect(0, 0, mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight()), 184 sRect); 185 186 // We may need to rotate the canvas, so we also have to rotate the bounds. 187 final Rect rotatedBounds = new Rect(bounds); 188 RectUtils.rotateRect(orientation, bounds.centerX(), bounds.centerY(), rotatedBounds); 189 190 // Rotate the canvas. 191 canvas.save(); 192 canvas.rotate(orientation, bounds.centerX(), bounds.centerY()); 193 canvas.drawBitmap(mBitmap.bmp, sRect, rotatedBounds, mPaint); 194 canvas.restore(); 195 } 196 } 197 198 @Override 199 public void setAlpha(int alpha) { 200 final int old = mPaint.getAlpha(); 201 mPaint.setAlpha(alpha); 202 if (alpha != old) { 203 invalidateSelf(); 204 } 205 } 206 207 @Override 208 public void setColorFilter(ColorFilter cf) { 209 mPaint.setColorFilter(cf); 210 invalidateSelf(); 211 } 212 213 @Override 214 public int getOpacity() { 215 return (mBitmap != null && (mBitmap.bmp.hasAlpha() || mPaint.getAlpha() < 255)) ? 216 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 217 } 218 219 @Override 220 public void onDecodeBegin(final RequestKey key) { } 221 222 @Override 223 public void onDecodeComplete(final RequestKey key, final ReusableBitmap result) { 224 if (key.equals(mCurrKey)) { 225 setBitmap(result); 226 } else { 227 // if the requests don't match (i.e. this request is stale), decrement the 228 // ref count to allow the bitmap to be pooled 229 if (result != null) { 230 result.releaseReference(); 231 } 232 } 233 } 234 235 @Override 236 public void onDecodeCancel(final RequestKey key) { } 237 238 protected ReusableBitmap getBitmap() { 239 return mBitmap; 240 } 241 242 private void setBitmap(ReusableBitmap bmp) { 243 if (mBitmap != null && mBitmap != bmp) { 244 mBitmap.releaseReference(); 245 } 246 mBitmap = bmp; 247 invalidateSelf(); 248 } 249 250 private void decode() { 251 final int bufferW; 252 final int bufferH; 253 254 if (mCurrKey == null) { 255 return; 256 } 257 258 Trace.beginSection("decode"); 259 if (mLimitDensity) { 260 final float scale = 261 Math.min(1f, (float) MAX_BITMAP_DENSITY / DisplayMetrics.DENSITY_DEFAULT 262 / mDensity); 263 bufferW = (int) (mDecodeWidth * scale); 264 bufferH = (int) (mDecodeHeight * scale); 265 } else { 266 bufferW = mDecodeWidth; 267 bufferH = mDecodeHeight; 268 } 269 270 if (bufferW == 0 || bufferH == 0) { 271 Trace.endSection(); 272 return; 273 } 274 if (mTask != null) { 275 mTask.cancel(); 276 } 277 final DecodeOptions opts = new DecodeOptions(bufferW, bufferH, VERTICAL_CENTER, 278 DecodeOptions.STRATEGY_ROUND_NEAREST); 279 mTask = new DecodeTask(mCurrKey, opts, this, mCache); 280 mTask.executeOnExecutor(EXECUTOR); 281 Trace.endSection(); 282 } 283 284 @Override 285 public void invalidateDrawable(Drawable who) { 286 invalidateSelf(); 287 } 288 289 @Override 290 public void scheduleDrawable(Drawable who, Runnable what, long when) { 291 scheduleSelf(what, when); 292 } 293 294 @Override 295 public void unscheduleDrawable(Drawable who, Runnable what) { 296 unscheduleSelf(what); 297 } 298} 299