1 /* 2 * Copyright (C) 2010 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; 18 19 import com.android.ide.common.rendering.api.LayoutLog; 20 import com.android.layoutlib.bridge.Bridge; 21 import com.android.layoutlib.bridge.impl.DelegateManager; 22 import com.android.resources.Density; 23 import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 24 25 import android.graphics.Bitmap.Config; 26 import android.os.Parcel; 27 28 import java.awt.Graphics2D; 29 import java.awt.image.BufferedImage; 30 import java.io.File; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.OutputStream; 34 import java.nio.Buffer; 35 import java.util.Arrays; 36 37 import javax.imageio.ImageIO; 38 39 /** 40 * Delegate implementing the native methods of android.graphics.Bitmap 41 * 42 * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced 43 * by calls to methods of the same name in this delegate class. 44 * 45 * This class behaves like the original native implementation, but in Java, keeping previously 46 * native data into its own objects and mapping them to int that are sent back and forth between 47 * it and the original Bitmap class. 48 * 49 * @see DelegateManager 50 * 51 */ 52 public final class Bitmap_Delegate { 53 54 // ---- delegate manager ---- 55 private static final DelegateManager<Bitmap_Delegate> sManager = 56 new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class); 57 58 // ---- delegate helper data ---- 59 60 // ---- delegate data ---- 61 private final Config mConfig; 62 private BufferedImage mImage; 63 private boolean mHasAlpha = true; 64 private boolean mHasMipMap = false; // TODO: check the default. 65 private int mGenerationId = 0; 66 67 68 // ---- Public Helper methods ---- 69 70 /** 71 * Returns the native delegate associated to a given {@link Bitmap_Delegate} object. 72 */ 73 public static Bitmap_Delegate getDelegate(Bitmap bitmap) { 74 return sManager.getDelegate(bitmap.mNativeBitmap); 75 } 76 77 /** 78 * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object. 79 */ 80 public static Bitmap_Delegate getDelegate(int native_bitmap) { 81 return sManager.getDelegate(native_bitmap); 82 } 83 84 /** 85 * Creates and returns a {@link Bitmap} initialized with the given file content. 86 * 87 * @param input the file from which to read the bitmap content 88 * @param isMutable whether the bitmap is mutable 89 * @param density the density associated with the bitmap 90 * 91 * @see Bitmap#isMutable() 92 * @see Bitmap#getDensity() 93 */ 94 public static Bitmap createBitmap(File input, boolean isMutable, Density density) 95 throws IOException { 96 // create a delegate with the content of the file. 97 Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888); 98 99 return createBitmap(delegate, isMutable, density.getDpiValue()); 100 } 101 102 /** 103 * Creates and returns a {@link Bitmap} initialized with the given stream content. 104 * 105 * @param input the stream from which to read the bitmap content 106 * @param isMutable whether the bitmap is mutable 107 * @param density the density associated with the bitmap 108 * 109 * @see Bitmap#isMutable() 110 * @see Bitmap#getDensity() 111 */ 112 public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density) 113 throws IOException { 114 // create a delegate with the content of the stream. 115 Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888); 116 117 return createBitmap(delegate, isMutable, density.getDpiValue()); 118 } 119 120 /** 121 * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage} 122 * 123 * @param image the bitmap content 124 * @param isMutable whether the bitmap is mutable 125 * @param density the density associated with the bitmap 126 * 127 * @see Bitmap#isMutable() 128 * @see Bitmap#getDensity() 129 */ 130 public static Bitmap createBitmap(BufferedImage image, boolean isMutable, 131 Density density) throws IOException { 132 // create a delegate with the given image. 133 Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888); 134 135 return createBitmap(delegate, isMutable, density.getDpiValue()); 136 } 137 138 /** 139 * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}. 140 */ 141 public static BufferedImage getImage(Bitmap bitmap) { 142 // get the delegate from the native int. 143 Bitmap_Delegate delegate = sManager.getDelegate(bitmap.mNativeBitmap); 144 if (delegate == null) { 145 return null; 146 } 147 148 return delegate.mImage; 149 } 150 151 public static int getBufferedImageType(int nativeBitmapConfig) { 152 switch (Config.nativeToConfig(nativeBitmapConfig)) { 153 case ALPHA_8: 154 return BufferedImage.TYPE_INT_ARGB; 155 case RGB_565: 156 return BufferedImage.TYPE_INT_ARGB; 157 case ARGB_4444: 158 return BufferedImage.TYPE_INT_ARGB; 159 case ARGB_8888: 160 return BufferedImage.TYPE_INT_ARGB; 161 } 162 163 return BufferedImage.TYPE_INT_ARGB; 164 } 165 166 /** 167 * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}. 168 */ 169 public BufferedImage getImage() { 170 return mImage; 171 } 172 173 /** 174 * Returns the Android bitmap config. Note that this not the config of the underlying 175 * Java2D bitmap. 176 */ 177 public Config getConfig() { 178 return mConfig; 179 } 180 181 /** 182 * Returns the hasAlpha rendering hint 183 * @return true if the bitmap alpha should be used at render time 184 */ 185 public boolean hasAlpha() { 186 return mHasAlpha && mConfig != Config.RGB_565; 187 } 188 189 public boolean hasMipMap() { 190 // TODO: check if more checks are required as in hasAlpha. 191 return mHasMipMap; 192 } 193 /** 194 * Update the generationId. 195 * 196 * @see Bitmap#getGenerationId() 197 */ 198 public void change() { 199 mGenerationId++; 200 } 201 202 // ---- native methods ---- 203 204 @LayoutlibDelegate 205 /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width, 206 int height, int nativeConfig, boolean mutable) { 207 int imageType = getBufferedImageType(nativeConfig); 208 209 // create the image 210 BufferedImage image = new BufferedImage(width, height, imageType); 211 212 if (colors != null) { 213 image.setRGB(0, 0, width, height, colors, offset, stride); 214 } 215 216 // create a delegate with the content of the stream. 217 Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig)); 218 219 return createBitmap(delegate, mutable, Bitmap.getDefaultDensity()); 220 } 221 222 @LayoutlibDelegate 223 /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) { 224 Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap); 225 if (srcBmpDelegate == null) { 226 return null; 227 } 228 229 BufferedImage srcImage = srcBmpDelegate.getImage(); 230 231 int width = srcImage.getWidth(); 232 int height = srcImage.getHeight(); 233 234 int imageType = getBufferedImageType(nativeConfig); 235 236 // create the image 237 BufferedImage image = new BufferedImage(width, height, imageType); 238 239 // copy the source image into the image. 240 int[] argb = new int[width * height]; 241 srcImage.getRGB(0, 0, width, height, argb, 0, width); 242 image.setRGB(0, 0, width, height, argb, 0, width); 243 244 // create a delegate with the content of the stream. 245 Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig)); 246 247 return createBitmap(delegate, isMutable, Bitmap.getDefaultDensity()); 248 } 249 250 @LayoutlibDelegate 251 /*package*/ static void nativeDestructor(int nativeBitmap) { 252 sManager.removeJavaReferenceFor(nativeBitmap); 253 } 254 255 @LayoutlibDelegate 256 /*package*/ static void nativeRecycle(int nativeBitmap) { 257 sManager.removeJavaReferenceFor(nativeBitmap); 258 } 259 260 @LayoutlibDelegate 261 /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality, 262 OutputStream stream, byte[] tempStorage) { 263 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 264 "Bitmap.compress() is not supported", null /*data*/); 265 return true; 266 } 267 268 @LayoutlibDelegate 269 /*package*/ static void nativeErase(int nativeBitmap, int color) { 270 // get the delegate from the native int. 271 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 272 if (delegate == null) { 273 return; 274 } 275 276 BufferedImage image = delegate.mImage; 277 278 Graphics2D g = image.createGraphics(); 279 try { 280 g.setColor(new java.awt.Color(color, true)); 281 282 g.fillRect(0, 0, image.getWidth(), image.getHeight()); 283 } finally { 284 g.dispose(); 285 } 286 } 287 288 @LayoutlibDelegate 289 /*package*/ static int nativeWidth(int nativeBitmap) { 290 // get the delegate from the native int. 291 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 292 if (delegate == null) { 293 return 0; 294 } 295 296 return delegate.mImage.getWidth(); 297 } 298 299 @LayoutlibDelegate 300 /*package*/ static int nativeHeight(int nativeBitmap) { 301 // get the delegate from the native int. 302 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 303 if (delegate == null) { 304 return 0; 305 } 306 307 return delegate.mImage.getHeight(); 308 } 309 310 @LayoutlibDelegate 311 /*package*/ static int nativeRowBytes(int nativeBitmap) { 312 // get the delegate from the native int. 313 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 314 if (delegate == null) { 315 return 0; 316 } 317 318 return delegate.mImage.getWidth(); 319 } 320 321 @LayoutlibDelegate 322 /*package*/ static int nativeConfig(int nativeBitmap) { 323 // get the delegate from the native int. 324 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 325 if (delegate == null) { 326 return 0; 327 } 328 329 return delegate.mConfig.nativeInt; 330 } 331 332 @LayoutlibDelegate 333 /*package*/ static boolean nativeHasAlpha(int nativeBitmap) { 334 // get the delegate from the native int. 335 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 336 if (delegate == null) { 337 return true; 338 } 339 340 return delegate.mHasAlpha; 341 } 342 343 @LayoutlibDelegate 344 /*package*/ static boolean nativeHasMipMap(int nativeBitmap) { 345 // get the delegate from the native int. 346 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 347 if (delegate == null) { 348 return true; 349 } 350 351 return delegate.mHasMipMap; 352 } 353 354 @LayoutlibDelegate 355 /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) { 356 // get the delegate from the native int. 357 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 358 if (delegate == null) { 359 return 0; 360 } 361 362 return delegate.mImage.getRGB(x, y); 363 } 364 365 @LayoutlibDelegate 366 /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset, 367 int stride, int x, int y, int width, int height) { 368 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 369 if (delegate == null) { 370 return; 371 } 372 373 delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride); 374 } 375 376 377 @LayoutlibDelegate 378 /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) { 379 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 380 if (delegate == null) { 381 return; 382 } 383 384 delegate.getImage().setRGB(x, y, color); 385 } 386 387 @LayoutlibDelegate 388 /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset, 389 int stride, int x, int y, int width, int height) { 390 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 391 if (delegate == null) { 392 return; 393 } 394 395 delegate.getImage().setRGB(x, y, width, height, colors, offset, stride); 396 } 397 398 @LayoutlibDelegate 399 /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) { 400 // FIXME implement native delegate 401 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 402 "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/); 403 } 404 405 @LayoutlibDelegate 406 /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) { 407 // FIXME implement native delegate 408 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 409 "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/); 410 } 411 412 @LayoutlibDelegate 413 /*package*/ static int nativeGenerationId(int nativeBitmap) { 414 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 415 if (delegate == null) { 416 return 0; 417 } 418 419 return delegate.mGenerationId; 420 } 421 422 @LayoutlibDelegate 423 /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) { 424 // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only 425 // used during aidl call so really this should not be called. 426 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 427 "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.", 428 null /*data*/); 429 return null; 430 } 431 432 @LayoutlibDelegate 433 /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable, 434 int density, Parcel p) { 435 // This is only called when sending a bitmap through aidl, so really this should not 436 // be called. 437 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 438 "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.", 439 null /*data*/); 440 return false; 441 } 442 443 @LayoutlibDelegate 444 /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint, 445 int[] offsetXY) { 446 Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap); 447 if (bitmap == null) { 448 return null; 449 } 450 451 // get the paint which can be null if nativePaint is 0. 452 Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint); 453 454 if (paint != null && paint.getMaskFilter() != null) { 455 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, 456 "MaskFilter not supported in Bitmap.extractAlpha", 457 null, null /*data*/); 458 } 459 460 int alpha = paint != null ? paint.getAlpha() : 0xFF; 461 BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha); 462 463 // create the delegate. The actual Bitmap config is only an alpha channel 464 Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8); 465 466 // the density doesn't matter, it's set by the Java method. 467 return createBitmap(delegate, false /*isMutable*/, 468 Density.DEFAULT_DENSITY /*density*/); 469 } 470 471 @LayoutlibDelegate 472 /*package*/ static void nativePrepareToDraw(int nativeBitmap) { 473 // nothing to be done here. 474 } 475 476 @LayoutlibDelegate 477 /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) { 478 // get the delegate from the native int. 479 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 480 if (delegate == null) { 481 return; 482 } 483 484 delegate.mHasAlpha = hasAlpha; 485 } 486 487 @LayoutlibDelegate 488 /*package*/ static void nativeSetHasMipMap(int nativeBitmap, boolean hasMipMap) { 489 // get the delegate from the native int. 490 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 491 if (delegate == null) { 492 return; 493 } 494 495 delegate.mHasMipMap = hasMipMap; 496 } 497 498 @LayoutlibDelegate 499 /*package*/ static boolean nativeSameAs(int nb0, int nb1) { 500 Bitmap_Delegate delegate1 = sManager.getDelegate(nb0); 501 if (delegate1 == null) { 502 return false; 503 } 504 505 Bitmap_Delegate delegate2 = sManager.getDelegate(nb1); 506 if (delegate2 == null) { 507 return false; 508 } 509 510 BufferedImage image1 = delegate1.getImage(); 511 BufferedImage image2 = delegate2.getImage(); 512 if (delegate1.mConfig != delegate2.mConfig || 513 image1.getWidth() != image2.getWidth() || 514 image1.getHeight() != image2.getHeight()) { 515 return false; 516 } 517 518 // get the internal data 519 int w = image1.getWidth(); 520 int h = image2.getHeight(); 521 int[] argb1 = new int[w*h]; 522 int[] argb2 = new int[w*h]; 523 524 image1.getRGB(0, 0, w, h, argb1, 0, w); 525 image2.getRGB(0, 0, w, h, argb2, 0, w); 526 527 // compares 528 if (delegate1.mConfig == Config.ALPHA_8) { 529 // in this case we have to manually compare the alpha channel as the rest is garbage. 530 final int length = w*h; 531 for (int i = 0 ; i < length ; i++) { 532 if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) { 533 return false; 534 } 535 } 536 return true; 537 } 538 539 return Arrays.equals(argb1, argb2); 540 } 541 542 // ---- Private delegate/helper methods ---- 543 544 private Bitmap_Delegate(BufferedImage image, Config config) { 545 mImage = image; 546 mConfig = config; 547 } 548 549 private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) { 550 // get its native_int 551 int nativeInt = sManager.addNewDelegate(delegate); 552 553 // and create/return a new Bitmap with it 554 return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, 555 density); 556 } 557 558 /** 559 * Creates and returns a copy of a given BufferedImage. 560 * <p/> 561 * if alpha is different than 255, then it is applied to the alpha channel of each pixel. 562 * 563 * @param image the image to copy 564 * @param imageType the type of the new image 565 * @param alpha an optional alpha modifier 566 * @return a new BufferedImage 567 */ 568 /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) { 569 int w = image.getWidth(); 570 int h = image.getHeight(); 571 572 BufferedImage result = new BufferedImage(w, h, imageType); 573 574 int[] argb = new int[w * h]; 575 image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); 576 577 if (alpha != 255) { 578 final int length = argb.length; 579 for (int i = 0 ; i < length; i++) { 580 int a = (argb[i] >>> 24 * alpha) / 255; 581 argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF); 582 } 583 } 584 585 result.setRGB(0, 0, w, h, argb, 0, w); 586 587 return result; 588 } 589 590 } 591