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