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