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