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