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