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