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