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