1 /* 2 * Copyright (C) 2006 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 */ 16 17 package android.graphics.drawable; 18 19 import android.content.res.Resources; 20 import android.content.res.TypedArray; 21 import android.graphics.Bitmap; 22 import android.graphics.BitmapFactory; 23 import android.graphics.Canvas; 24 import android.graphics.ColorFilter; 25 import android.graphics.Paint; 26 import android.graphics.PixelFormat; 27 import android.graphics.Rect; 28 import android.graphics.Shader; 29 import android.graphics.BitmapShader; 30 import android.util.AttributeSet; 31 import android.util.DisplayMetrics; 32 import android.view.Gravity; 33 34 import org.xmlpull.v1.XmlPullParser; 35 import org.xmlpull.v1.XmlPullParserException; 36 37 import java.io.IOException; 38 39 /** 40 * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a 41 * BitmapDrawable from a file path, an input stream, through XML inflation, or from 42 * a {@link android.graphics.Bitmap} object. 43 * <p>It can be defined in an XML file with the <code><bitmap></code> element. For more 44 * information, see the guide to <a 45 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 46 * <p> 47 * Also see the {@link android.graphics.Bitmap} class, which handles the management and 48 * transformation of raw bitmap graphics, and should be used when drawing to a 49 * {@link android.graphics.Canvas}. 50 * </p> 51 * 52 * @attr ref android.R.styleable#BitmapDrawable_src 53 * @attr ref android.R.styleable#BitmapDrawable_antialias 54 * @attr ref android.R.styleable#BitmapDrawable_filter 55 * @attr ref android.R.styleable#BitmapDrawable_dither 56 * @attr ref android.R.styleable#BitmapDrawable_gravity 57 * @attr ref android.R.styleable#BitmapDrawable_tileMode 58 */ 59 public class BitmapDrawable extends Drawable { 60 61 private static final int DEFAULT_PAINT_FLAGS = 62 Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG; 63 private BitmapState mBitmapState; 64 private Bitmap mBitmap; 65 private int mTargetDensity; 66 67 private final Rect mDstRect = new Rect(); // Gravity.apply() sets this 68 69 private boolean mApplyGravity; 70 private boolean mRebuildShader; 71 private boolean mMutated; 72 73 // These are scaled to match the target density. 74 private int mBitmapWidth; 75 private int mBitmapHeight; 76 77 /** 78 * Create an empty drawable, not dealing with density. 79 * @deprecated Use {@link #BitmapDrawable(Resources)} to ensure 80 * that the drawable has correctly set its target density. 81 */ 82 @Deprecated 83 public BitmapDrawable() { 84 mBitmapState = new BitmapState((Bitmap) null); 85 } 86 87 /** 88 * Create an empty drawable, setting initial target density based on 89 * the display metrics of the resources. 90 */ 91 public BitmapDrawable(Resources res) { 92 mBitmapState = new BitmapState((Bitmap) null); 93 mBitmapState.mTargetDensity = mTargetDensity; 94 } 95 96 /** 97 * Create drawable from a bitmap, not dealing with density. 98 * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure 99 * that the drawable has correctly set its target density. 100 */ 101 @Deprecated 102 public BitmapDrawable(Bitmap bitmap) { 103 this(new BitmapState(bitmap), null); 104 } 105 106 /** 107 * Create drawable from a bitmap, setting initial target density based on 108 * the display metrics of the resources. 109 */ 110 public BitmapDrawable(Resources res, Bitmap bitmap) { 111 this(new BitmapState(bitmap), res); 112 mBitmapState.mTargetDensity = mTargetDensity; 113 } 114 115 /** 116 * Create a drawable by opening a given file path and decoding the bitmap. 117 * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure 118 * that the drawable has correctly set its target density. 119 */ 120 @Deprecated 121 public BitmapDrawable(String filepath) { 122 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); 123 if (mBitmap == null) { 124 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 125 } 126 } 127 128 /** 129 * Create a drawable by opening a given file path and decoding the bitmap. 130 */ 131 public BitmapDrawable(Resources res, String filepath) { 132 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); 133 mBitmapState.mTargetDensity = mTargetDensity; 134 if (mBitmap == null) { 135 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 136 } 137 } 138 139 /** 140 * Create a drawable by decoding a bitmap from the given input stream. 141 * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure 142 * that the drawable has correctly set its target density. 143 */ 144 @Deprecated 145 public BitmapDrawable(java.io.InputStream is) { 146 this(new BitmapState(BitmapFactory.decodeStream(is)), null); 147 if (mBitmap == null) { 148 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 149 } 150 } 151 152 /** 153 * Create a drawable by decoding a bitmap from the given input stream. 154 */ 155 public BitmapDrawable(Resources res, java.io.InputStream is) { 156 this(new BitmapState(BitmapFactory.decodeStream(is)), null); 157 mBitmapState.mTargetDensity = mTargetDensity; 158 if (mBitmap == null) { 159 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 160 } 161 } 162 163 public final Paint getPaint() { 164 return mBitmapState.mPaint; 165 } 166 167 public final Bitmap getBitmap() { 168 return mBitmap; 169 } 170 171 private void computeBitmapSize() { 172 mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity); 173 mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity); 174 } 175 176 private void setBitmap(Bitmap bitmap) { 177 mBitmap = bitmap; 178 if (bitmap != null) { 179 computeBitmapSize(); 180 } else { 181 mBitmapWidth = mBitmapHeight = -1; 182 } 183 } 184 185 /** 186 * Set the density scale at which this drawable will be rendered. This 187 * method assumes the drawable will be rendered at the same density as the 188 * specified canvas. 189 * 190 * @param canvas The Canvas from which the density scale must be obtained. 191 * 192 * @see android.graphics.Bitmap#setDensity(int) 193 * @see android.graphics.Bitmap#getDensity() 194 */ 195 public void setTargetDensity(Canvas canvas) { 196 setTargetDensity(canvas.getDensity()); 197 } 198 199 /** 200 * Set the density scale at which this drawable will be rendered. 201 * 202 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 203 * 204 * @see android.graphics.Bitmap#setDensity(int) 205 * @see android.graphics.Bitmap#getDensity() 206 */ 207 public void setTargetDensity(DisplayMetrics metrics) { 208 mTargetDensity = metrics.densityDpi; 209 if (mBitmap != null) { 210 computeBitmapSize(); 211 } 212 } 213 214 /** 215 * Set the density at which this drawable will be rendered. 216 * 217 * @param density The density scale for this drawable. 218 * 219 * @see android.graphics.Bitmap#setDensity(int) 220 * @see android.graphics.Bitmap#getDensity() 221 */ 222 public void setTargetDensity(int density) { 223 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 224 if (mBitmap != null) { 225 computeBitmapSize(); 226 } 227 } 228 229 /** Get the gravity used to position/stretch the bitmap within its bounds. 230 * See android.view.Gravity 231 * @return the gravity applied to the bitmap 232 */ 233 public int getGravity() { 234 return mBitmapState.mGravity; 235 } 236 237 /** Set the gravity used to position/stretch the bitmap within its bounds. 238 See android.view.Gravity 239 * @param gravity the gravity 240 */ 241 public void setGravity(int gravity) { 242 mBitmapState.mGravity = gravity; 243 mApplyGravity = true; 244 } 245 246 public void setAntiAlias(boolean aa) { 247 mBitmapState.mPaint.setAntiAlias(aa); 248 } 249 250 @Override 251 public void setFilterBitmap(boolean filter) { 252 mBitmapState.mPaint.setFilterBitmap(filter); 253 } 254 255 @Override 256 public void setDither(boolean dither) { 257 mBitmapState.mPaint.setDither(dither); 258 } 259 260 public Shader.TileMode getTileModeX() { 261 return mBitmapState.mTileModeX; 262 } 263 264 public Shader.TileMode getTileModeY() { 265 return mBitmapState.mTileModeY; 266 } 267 268 public void setTileModeX(Shader.TileMode mode) { 269 setTileModeXY(mode, mBitmapState.mTileModeY); 270 } 271 272 public final void setTileModeY(Shader.TileMode mode) { 273 setTileModeXY(mBitmapState.mTileModeX, mode); 274 } 275 276 public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { 277 final BitmapState state = mBitmapState; 278 if (state.mPaint.getShader() == null || 279 state.mTileModeX != xmode || state.mTileModeY != ymode) { 280 state.mTileModeX = xmode; 281 state.mTileModeY = ymode; 282 mRebuildShader = true; 283 } 284 } 285 286 @Override 287 public int getChangingConfigurations() { 288 return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; 289 } 290 291 @Override 292 protected void onBoundsChange(Rect bounds) { 293 super.onBoundsChange(bounds); 294 mApplyGravity = true; 295 } 296 297 @Override 298 public void draw(Canvas canvas) { 299 Bitmap bitmap = mBitmap; 300 if (bitmap != null) { 301 final BitmapState state = mBitmapState; 302 if (mRebuildShader) { 303 Shader.TileMode tmx = state.mTileModeX; 304 Shader.TileMode tmy = state.mTileModeY; 305 306 if (tmx == null && tmy == null) { 307 state.mPaint.setShader(null); 308 } else { 309 Shader s = new BitmapShader(bitmap, 310 tmx == null ? Shader.TileMode.CLAMP : tmx, 311 tmy == null ? Shader.TileMode.CLAMP : tmy); 312 state.mPaint.setShader(s); 313 } 314 mRebuildShader = false; 315 copyBounds(mDstRect); 316 } 317 318 Shader shader = state.mPaint.getShader(); 319 if (shader == null) { 320 if (mApplyGravity) { 321 Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight, 322 getBounds(), mDstRect); 323 mApplyGravity = false; 324 } 325 canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint); 326 } else { 327 if (mApplyGravity) { 328 mDstRect.set(getBounds()); 329 mApplyGravity = false; 330 } 331 canvas.drawRect(mDstRect, state.mPaint); 332 } 333 } 334 } 335 336 @Override 337 public void setAlpha(int alpha) { 338 mBitmapState.mPaint.setAlpha(alpha); 339 } 340 341 @Override 342 public void setColorFilter(ColorFilter cf) { 343 mBitmapState.mPaint.setColorFilter(cf); 344 } 345 346 /** 347 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 348 * that comes from the same resource. 349 * 350 * @return This drawable. 351 */ 352 @Override 353 public Drawable mutate() { 354 if (!mMutated && super.mutate() == this) { 355 mBitmapState = new BitmapState(mBitmapState); 356 mMutated = true; 357 } 358 return this; 359 } 360 361 @Override 362 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 363 throws XmlPullParserException, IOException { 364 super.inflate(r, parser, attrs); 365 366 TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable); 367 368 final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0); 369 if (id == 0) { 370 throw new XmlPullParserException(parser.getPositionDescription() + 371 ": <bitmap> requires a valid src attribute"); 372 } 373 final Bitmap bitmap = BitmapFactory.decodeResource(r, id); 374 if (bitmap == null) { 375 throw new XmlPullParserException(parser.getPositionDescription() + 376 ": <bitmap> requires a valid src attribute"); 377 } 378 mBitmapState.mBitmap = bitmap; 379 setBitmap(bitmap); 380 setTargetDensity(r.getDisplayMetrics()); 381 382 final Paint paint = mBitmapState.mPaint; 383 paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, 384 paint.isAntiAlias())); 385 paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter, 386 paint.isFilterBitmap())); 387 paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither, 388 paint.isDither())); 389 setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL)); 390 int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1); 391 if (tileMode != -1) { 392 switch (tileMode) { 393 case 0: 394 setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 395 break; 396 case 1: 397 setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); 398 break; 399 case 2: 400 setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); 401 break; 402 } 403 } 404 405 a.recycle(); 406 } 407 408 @Override 409 public int getIntrinsicWidth() { 410 return mBitmapWidth; 411 } 412 413 @Override 414 public int getIntrinsicHeight() { 415 return mBitmapHeight; 416 } 417 418 @Override 419 public int getOpacity() { 420 if (mBitmapState.mGravity != Gravity.FILL) { 421 return PixelFormat.TRANSLUCENT; 422 } 423 Bitmap bm = mBitmap; 424 return (bm == null || bm.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? 425 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 426 } 427 428 @Override 429 public final ConstantState getConstantState() { 430 mBitmapState.mChangingConfigurations = super.getChangingConfigurations(); 431 return mBitmapState; 432 } 433 434 final static class BitmapState extends ConstantState { 435 Bitmap mBitmap; 436 int mChangingConfigurations; 437 int mGravity = Gravity.FILL; 438 Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); 439 Shader.TileMode mTileModeX; 440 Shader.TileMode mTileModeY; 441 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 442 443 BitmapState(Bitmap bitmap) { 444 mBitmap = bitmap; 445 } 446 447 BitmapState(BitmapState bitmapState) { 448 this(bitmapState.mBitmap); 449 mChangingConfigurations = bitmapState.mChangingConfigurations; 450 mGravity = bitmapState.mGravity; 451 mTileModeX = bitmapState.mTileModeX; 452 mTileModeY = bitmapState.mTileModeY; 453 mTargetDensity = bitmapState.mTargetDensity; 454 mPaint = new Paint(bitmapState.mPaint); 455 } 456 457 @Override 458 public Drawable newDrawable() { 459 return new BitmapDrawable(this, null); 460 } 461 462 @Override 463 public Drawable newDrawable(Resources res) { 464 return new BitmapDrawable(this, res); 465 } 466 467 @Override 468 public int getChangingConfigurations() { 469 return mChangingConfigurations; 470 } 471 } 472 473 private BitmapDrawable(BitmapState state, Resources res) { 474 mBitmapState = state; 475 if (res != null) { 476 mTargetDensity = res.getDisplayMetrics().densityDpi; 477 } else if (state != null) { 478 mTargetDensity = state.mTargetDensity; 479 } else { 480 mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 481 } 482 setBitmap(state.mBitmap); 483 } 484 } 485