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