Bitmap_Delegate.java revision 918aaa5717fce6081557c82ce1c439b6922737d5
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.ResourceDensity; 21import com.android.layoutlib.bridge.Bridge; 22import com.android.layoutlib.bridge.impl.DelegateManager; 23 24import android.graphics.Bitmap.Config; 25import android.os.Parcel; 26 27import java.awt.Graphics2D; 28import java.awt.image.BufferedImage; 29import java.io.File; 30import java.io.IOException; 31import java.io.InputStream; 32import java.io.OutputStream; 33import java.nio.Buffer; 34import java.util.Arrays; 35 36import javax.imageio.ImageIO; 37 38/** 39 * Delegate implementing the native methods of android.graphics.Bitmap 40 * 41 * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced 42 * by calls to methods of the same name in this delegate class. 43 * 44 * This class behaves like the original native implementation, but in Java, keeping previously 45 * native data into its own objects and mapping them to int that are sent back and forth between 46 * it and the original Bitmap class. 47 * 48 * @see DelegateManager 49 * 50 */ 51public final class Bitmap_Delegate { 52 53 // ---- delegate manager ---- 54 private static final DelegateManager<Bitmap_Delegate> sManager = 55 new DelegateManager<Bitmap_Delegate>(); 56 57 // ---- delegate helper data ---- 58 59 // ---- delegate data ---- 60 private final Config mConfig; 61 private BufferedImage mImage; 62 private boolean mHasAlpha = true; 63 private int mGenerationId = 0; 64 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, ResourceDensity 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.getDpi()); 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, ResourceDensity 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.getDpi()); 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 ResourceDensity 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.getDpi()); 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 /** 188 * Update the generationId. 189 * 190 * @see Bitmap#getGenerationId() 191 */ 192 public void change() { 193 mGenerationId++; 194 } 195 196 // ---- native methods ---- 197 198 /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width, 199 int height, int nativeConfig, boolean mutable) { 200 int imageType = getBufferedImageType(nativeConfig); 201 202 // create the image 203 BufferedImage image = new BufferedImage(width, height, imageType); 204 205 if (colors != null) { 206 image.setRGB(0, 0, width, height, colors, offset, stride); 207 } 208 209 // create a delegate with the content of the stream. 210 Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]); 211 212 return createBitmap(delegate, mutable, Bitmap.getDefaultDensity()); 213 } 214 215 /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) { 216 Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap); 217 if (srcBmpDelegate == null) { 218 return null; 219 } 220 221 BufferedImage srcImage = srcBmpDelegate.getImage(); 222 223 int width = srcImage.getWidth(); 224 int height = srcImage.getHeight(); 225 226 int imageType = getBufferedImageType(nativeConfig); 227 228 // create the image 229 BufferedImage image = new BufferedImage(width, height, imageType); 230 231 // copy the source image into the image. 232 int[] argb = new int[width * height]; 233 srcImage.getRGB(0, 0, width, height, argb, 0, width); 234 image.setRGB(0, 0, width, height, argb, 0, width); 235 236 // create a delegate with the content of the stream. 237 Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]); 238 239 return createBitmap(delegate, isMutable, Bitmap.getDefaultDensity()); 240 } 241 242 /*package*/ static void nativeDestructor(int nativeBitmap) { 243 sManager.removeDelegate(nativeBitmap); 244 } 245 246 /*package*/ static void nativeRecycle(int nativeBitmap) { 247 sManager.removeDelegate(nativeBitmap); 248 } 249 250 /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality, 251 OutputStream stream, byte[] tempStorage) { 252 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 253 "Bitmap.compress() is not supported"); 254 return true; 255 } 256 257 /*package*/ static void nativeErase(int nativeBitmap, int color) { 258 // get the delegate from the native int. 259 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 260 if (delegate == null) { 261 return; 262 } 263 264 BufferedImage image = delegate.mImage; 265 266 Graphics2D g = image.createGraphics(); 267 try { 268 g.setColor(new java.awt.Color(color, true)); 269 270 g.fillRect(0, 0, image.getWidth(), image.getHeight()); 271 } finally { 272 g.dispose(); 273 } 274 } 275 276 /*package*/ static int nativeWidth(int nativeBitmap) { 277 // get the delegate from the native int. 278 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 279 if (delegate == null) { 280 return 0; 281 } 282 283 return delegate.mImage.getWidth(); 284 } 285 286 /*package*/ static int nativeHeight(int nativeBitmap) { 287 // get the delegate from the native int. 288 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 289 if (delegate == null) { 290 return 0; 291 } 292 293 return delegate.mImage.getHeight(); 294 } 295 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 /*package*/ static int nativeConfig(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.mConfig.nativeInt; 314 } 315 316 /*package*/ static boolean nativeHasAlpha(int nativeBitmap) { 317 // get the delegate from the native int. 318 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 319 if (delegate == null) { 320 return true; 321 } 322 323 return delegate.mHasAlpha; 324 } 325 326 /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) { 327 // get the delegate from the native int. 328 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 329 if (delegate == null) { 330 return 0; 331 } 332 333 return delegate.mImage.getRGB(x, y); 334 } 335 336 /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset, 337 int stride, int x, int y, int width, int height) { 338 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 339 if (delegate == null) { 340 return; 341 } 342 343 delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride); 344 } 345 346 347 /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) { 348 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 349 if (delegate == null) { 350 return; 351 } 352 353 delegate.getImage().setRGB(x, y, color); 354 } 355 356 /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset, 357 int stride, int x, int y, int width, int height) { 358 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 359 if (delegate == null) { 360 return; 361 } 362 363 delegate.getImage().setRGB(x, y, width, height, colors, offset, stride); 364 } 365 366 /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) { 367 // FIXME implement native delegate 368 throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeCopyPixelsToBuffer"); 369 } 370 371 /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) { 372 // FIXME implement native delegate 373 throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeCopyPixelsFromBuffer"); 374 } 375 376 /*package*/ static int nativeGenerationId(int nativeBitmap) { 377 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 378 if (delegate == null) { 379 return 0; 380 } 381 382 return delegate.mGenerationId; 383 } 384 385 /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) { 386 // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only 387 // used during aidl call so really this should not be called. 388 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 389 "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels."); 390 return null; 391 } 392 393 /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable, 394 int density, Parcel p) { 395 // This is only called when sending a bitmap through aidl, so really this should not 396 // be called. 397 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 398 "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels."); 399 return false; 400 } 401 402 /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint, 403 int[] offsetXY) { 404 Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap); 405 if (bitmap == null) { 406 return null; 407 } 408 409 // get the paint which can be null if nativePaint is 0. 410 Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint); 411 412 if (paint != null && paint.getMaskFilter() != null) { 413 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, 414 "MaskFilter not supported in Bitmap.extractAlpha", 415 null); 416 } 417 418 int alpha = paint != null ? paint.getAlpha() : 0xFF; 419 BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha); 420 421 // create the delegate. The actual Bitmap config is only an alpha channel 422 Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8); 423 424 // the density doesn't matter, it's set by the Java method. 425 return createBitmap(delegate, false /*isMutable*/, 426 ResourceDensity.DEFAULT_DENSITY /*density*/); 427 } 428 429 /*package*/ static void nativePrepareToDraw(int nativeBitmap) { 430 // nothing to be done here. 431 } 432 433 /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) { 434 // get the delegate from the native int. 435 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 436 if (delegate == null) { 437 return; 438 } 439 440 delegate.mHasAlpha = hasAlpha; 441 } 442 443 /*package*/ static boolean nativeSameAs(int nb0, int nb1) { 444 Bitmap_Delegate delegate1 = sManager.getDelegate(nb0); 445 if (delegate1 == null) { 446 return false; 447 } 448 449 Bitmap_Delegate delegate2 = sManager.getDelegate(nb1); 450 if (delegate2 == null) { 451 return false; 452 } 453 454 BufferedImage image1 = delegate1.getImage(); 455 BufferedImage image2 = delegate2.getImage(); 456 if (delegate1.mConfig != delegate2.mConfig || 457 image1.getWidth() != image2.getWidth() || 458 image1.getHeight() != image2.getHeight()) { 459 return false; 460 } 461 462 // get the internal data 463 int w = image1.getWidth(); 464 int h = image2.getHeight(); 465 int[] argb1 = new int[w*h]; 466 int[] argb2 = new int[w*h]; 467 468 image1.getRGB(0, 0, w, h, argb1, 0, w); 469 image2.getRGB(0, 0, w, h, argb2, 0, w); 470 471 // compares 472 if (delegate1.mConfig == Config.ALPHA_8) { 473 // in this case we have to manually compare the alpha channel as the rest is garbage. 474 final int length = w*h; 475 for (int i = 0 ; i < length ; i++) { 476 if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) { 477 return false; 478 } 479 } 480 return true; 481 } 482 483 return Arrays.equals(argb1, argb2); 484 } 485 486 // ---- Private delegate/helper methods ---- 487 488 private Bitmap_Delegate(BufferedImage image, Config config) { 489 mImage = image; 490 mConfig = config; 491 } 492 493 private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) { 494 // get its native_int 495 int nativeInt = sManager.addDelegate(delegate); 496 497 // and create/return a new Bitmap with it 498 return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, density); 499 } 500 501 /** 502 * Creates and returns a copy of a given BufferedImage. 503 * <p/> 504 * if alpha is different than 255, then it is applied to the alpha channel of each pixel. 505 * 506 * @param image the image to copy 507 * @param imageType the type of the new image 508 * @param alpha an optional alpha modifier 509 * @return a new BufferedImage 510 */ 511 /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) { 512 int w = image.getWidth(); 513 int h = image.getHeight(); 514 515 BufferedImage result = new BufferedImage(w, h, imageType); 516 517 int[] argb = new int[w * h]; 518 image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); 519 520 if (alpha != 255) { 521 final int length = argb.length; 522 for (int i = 0 ; i < length; i++) { 523 int a = (argb[i] >>> 24 * alpha) / 255; 524 argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF); 525 } 526 } 527 528 result.setRGB(0, 0, w, h, argb, 0, w); 529 530 return result; 531 } 532 533} 534