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 void nativeReconfigure(int nativeBitmap, int width, int height, 318 int config, int allocSize) { 319 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 320 "Bitmap.reconfigure() is not supported", null /*data*/); 321 } 322 323 @LayoutlibDelegate 324 /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality, 325 OutputStream stream, byte[] tempStorage) { 326 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 327 "Bitmap.compress() is not supported", null /*data*/); 328 return true; 329 } 330 331 @LayoutlibDelegate 332 /*package*/ static void nativeErase(int nativeBitmap, int color) { 333 // get the delegate from the native int. 334 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 335 if (delegate == null) { 336 return; 337 } 338 339 BufferedImage image = delegate.mImage; 340 341 Graphics2D g = image.createGraphics(); 342 try { 343 g.setColor(new java.awt.Color(color, true)); 344 345 g.fillRect(0, 0, image.getWidth(), image.getHeight()); 346 } finally { 347 g.dispose(); 348 } 349 } 350 351 @LayoutlibDelegate 352 /*package*/ static int nativeRowBytes(int nativeBitmap) { 353 // get the delegate from the native int. 354 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 355 if (delegate == null) { 356 return 0; 357 } 358 359 return delegate.mImage.getWidth(); 360 } 361 362 @LayoutlibDelegate 363 /*package*/ static int nativeConfig(int nativeBitmap) { 364 // get the delegate from the native int. 365 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 366 if (delegate == null) { 367 return 0; 368 } 369 370 return delegate.mConfig.nativeInt; 371 } 372 373 @LayoutlibDelegate 374 /*package*/ static boolean nativeHasAlpha(int nativeBitmap) { 375 // get the delegate from the native int. 376 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 377 if (delegate == null) { 378 return true; 379 } 380 381 return delegate.mHasAlpha; 382 } 383 384 @LayoutlibDelegate 385 /*package*/ static boolean nativeHasMipMap(int nativeBitmap) { 386 // get the delegate from the native int. 387 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 388 if (delegate == null) { 389 return true; 390 } 391 392 return delegate.mHasMipMap; 393 } 394 395 @LayoutlibDelegate 396 /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y, 397 boolean isPremultiplied) { 398 // get the delegate from the native int. 399 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 400 if (delegate == null) { 401 return 0; 402 } 403 404 // TODO: Support isPremultiplied. 405 return delegate.mImage.getRGB(x, y); 406 } 407 408 @LayoutlibDelegate 409 /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset, 410 int stride, int x, int y, int width, int height, boolean isPremultiplied) { 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(int nativeBitmap, int x, int y, int color, 422 boolean isPremultiplied) { 423 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 424 if (delegate == null) { 425 return; 426 } 427 428 delegate.getImage().setRGB(x, y, color); 429 } 430 431 @LayoutlibDelegate 432 /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset, 433 int stride, int x, int y, int width, int height, boolean isPremultiplied) { 434 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 435 if (delegate == null) { 436 return; 437 } 438 439 delegate.getImage().setRGB(x, y, width, height, colors, offset, stride); 440 } 441 442 @LayoutlibDelegate 443 /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) { 444 // FIXME implement native delegate 445 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 446 "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/); 447 } 448 449 @LayoutlibDelegate 450 /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) { 451 // FIXME implement native delegate 452 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 453 "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/); 454 } 455 456 @LayoutlibDelegate 457 /*package*/ static int nativeGenerationId(int nativeBitmap) { 458 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 459 if (delegate == null) { 460 return 0; 461 } 462 463 return delegate.mGenerationId; 464 } 465 466 @LayoutlibDelegate 467 /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) { 468 // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only 469 // used during aidl call so really this should not be called. 470 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 471 "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.", 472 null /*data*/); 473 return null; 474 } 475 476 @LayoutlibDelegate 477 /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable, 478 int density, Parcel p) { 479 // This is only called when sending a bitmap through aidl, so really this should not 480 // be called. 481 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 482 "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.", 483 null /*data*/); 484 return false; 485 } 486 487 @LayoutlibDelegate 488 /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint, 489 int[] offsetXY) { 490 Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap); 491 if (bitmap == null) { 492 return null; 493 } 494 495 // get the paint which can be null if nativePaint is 0. 496 Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint); 497 498 if (paint != null && paint.getMaskFilter() != null) { 499 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, 500 "MaskFilter not supported in Bitmap.extractAlpha", 501 null, null /*data*/); 502 } 503 504 int alpha = paint != null ? paint.getAlpha() : 0xFF; 505 BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha); 506 507 // create the delegate. The actual Bitmap config is only an alpha channel 508 Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8); 509 510 // the density doesn't matter, it's set by the Java method. 511 return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE), 512 Density.DEFAULT_DENSITY /*density*/); 513 } 514 515 @LayoutlibDelegate 516 /*package*/ static void nativePrepareToDraw(int nativeBitmap) { 517 // nothing to be done here. 518 } 519 520 @LayoutlibDelegate 521 /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) { 522 // get the delegate from the native int. 523 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 524 if (delegate == null) { 525 return; 526 } 527 528 delegate.mHasAlpha = hasAlpha; 529 } 530 531 @LayoutlibDelegate 532 /*package*/ static void nativeSetHasMipMap(int nativeBitmap, boolean hasMipMap) { 533 // get the delegate from the native int. 534 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 535 if (delegate == null) { 536 return; 537 } 538 539 delegate.mHasMipMap = hasMipMap; 540 } 541 542 @LayoutlibDelegate 543 /*package*/ static boolean nativeSameAs(int nb0, int nb1) { 544 Bitmap_Delegate delegate1 = sManager.getDelegate(nb0); 545 if (delegate1 == null) { 546 return false; 547 } 548 549 Bitmap_Delegate delegate2 = sManager.getDelegate(nb1); 550 if (delegate2 == null) { 551 return false; 552 } 553 554 BufferedImage image1 = delegate1.getImage(); 555 BufferedImage image2 = delegate2.getImage(); 556 if (delegate1.mConfig != delegate2.mConfig || 557 image1.getWidth() != image2.getWidth() || 558 image1.getHeight() != image2.getHeight()) { 559 return false; 560 } 561 562 // get the internal data 563 int w = image1.getWidth(); 564 int h = image2.getHeight(); 565 int[] argb1 = new int[w*h]; 566 int[] argb2 = new int[w*h]; 567 568 image1.getRGB(0, 0, w, h, argb1, 0, w); 569 image2.getRGB(0, 0, w, h, argb2, 0, w); 570 571 // compares 572 if (delegate1.mConfig == Config.ALPHA_8) { 573 // in this case we have to manually compare the alpha channel as the rest is garbage. 574 final int length = w*h; 575 for (int i = 0 ; i < length ; i++) { 576 if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) { 577 return false; 578 } 579 } 580 return true; 581 } 582 583 return Arrays.equals(argb1, argb2); 584 } 585 586 // ---- Private delegate/helper methods ---- 587 588 private Bitmap_Delegate(BufferedImage image, Config config) { 589 mImage = image; 590 mConfig = config; 591 } 592 593 private static Bitmap createBitmap(Bitmap_Delegate delegate, 594 Set<BitmapCreateFlags> createFlags, int density) { 595 // get its native_int 596 int nativeInt = sManager.addNewDelegate(delegate); 597 598 int width = delegate.mImage.getWidth(); 599 int height = delegate.mImage.getHeight(); 600 boolean isMutable = createFlags.contains(BitmapCreateFlags.MUTABLE); 601 boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED); 602 603 // and create/return a new Bitmap with it 604 return new Bitmap(nativeInt, null /* buffer */, width, height, density, isMutable, 605 isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */); 606 } 607 608 private static Set<BitmapCreateFlags> getPremultipliedBitmapCreateFlags(boolean isMutable) { 609 Set<BitmapCreateFlags> createFlags = EnumSet.of(BitmapCreateFlags.PREMULTIPLIED); 610 if (isMutable) { 611 createFlags.add(BitmapCreateFlags.MUTABLE); 612 } 613 return createFlags; 614 } 615 616 /** 617 * Creates and returns a copy of a given BufferedImage. 618 * <p/> 619 * if alpha is different than 255, then it is applied to the alpha channel of each pixel. 620 * 621 * @param image the image to copy 622 * @param imageType the type of the new image 623 * @param alpha an optional alpha modifier 624 * @return a new BufferedImage 625 */ 626 /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) { 627 int w = image.getWidth(); 628 int h = image.getHeight(); 629 630 BufferedImage result = new BufferedImage(w, h, imageType); 631 632 int[] argb = new int[w * h]; 633 image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); 634 635 if (alpha != 255) { 636 final int length = argb.length; 637 for (int i = 0 ; i < length; i++) { 638 int a = (argb[i] >>> 24 * alpha) / 255; 639 argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF); 640 } 641 } 642 643 result.setRGB(0, 0, w, h, argb, 0, w); 644 645 return result; 646 } 647 648 } 649