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