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