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