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 Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig) { 307 // Unused method; no implementation provided. 308 assert false; 309 return null; 310 } 311 312 @LayoutlibDelegate 313 /*package*/ static long nativeGetNativeFinalizer() { 314 synchronized (Bitmap_Delegate.class) { 315 if (sFinalizer == -1) { 316 sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor); 317 } 318 return sFinalizer; 319 } 320 } 321 322 @LayoutlibDelegate 323 /*package*/ static boolean nativeRecycle(long nativeBitmap) { 324 // In our case reycle() is a no-op. We will let the finalizer to dispose the bitmap. 325 return true; 326 } 327 328 @LayoutlibDelegate 329 /*package*/ static void nativeReconfigure(long nativeBitmap, int width, int height, 330 int config, int allocSize, boolean isPremultiplied) { 331 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 332 "Bitmap.reconfigure() is not supported", null /*data*/); 333 } 334 335 @LayoutlibDelegate 336 /*package*/ static boolean nativeCompress(long nativeBitmap, int format, int quality, 337 OutputStream stream, byte[] tempStorage) { 338 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 339 "Bitmap.compress() is not supported", null /*data*/); 340 return true; 341 } 342 343 @LayoutlibDelegate 344 /*package*/ static void nativeErase(long nativeBitmap, int color) { 345 // get the delegate from the native int. 346 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 347 if (delegate == null) { 348 return; 349 } 350 351 BufferedImage image = delegate.mImage; 352 353 Graphics2D g = image.createGraphics(); 354 try { 355 g.setColor(new java.awt.Color(color, true)); 356 357 g.fillRect(0, 0, image.getWidth(), image.getHeight()); 358 } finally { 359 g.dispose(); 360 } 361 } 362 363 @LayoutlibDelegate 364 /*package*/ static int nativeRowBytes(long nativeBitmap) { 365 // get the delegate from the native int. 366 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 367 if (delegate == null) { 368 return 0; 369 } 370 371 return delegate.mImage.getWidth(); 372 } 373 374 @LayoutlibDelegate 375 /*package*/ static int nativeConfig(long nativeBitmap) { 376 // get the delegate from the native int. 377 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 378 if (delegate == null) { 379 return 0; 380 } 381 382 return delegate.mConfig.nativeInt; 383 } 384 385 @LayoutlibDelegate 386 /*package*/ static boolean nativeHasAlpha(long nativeBitmap) { 387 // get the delegate from the native int. 388 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 389 return delegate == null || delegate.mHasAlpha; 390 391 } 392 393 @LayoutlibDelegate 394 /*package*/ static boolean nativeHasMipMap(long nativeBitmap) { 395 // get the delegate from the native int. 396 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 397 return delegate == null || delegate.mHasMipMap; 398 399 } 400 401 @LayoutlibDelegate 402 /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y) { 403 // get the delegate from the native int. 404 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 405 if (delegate == null) { 406 return 0; 407 } 408 409 return delegate.mImage.getRGB(x, y); 410 } 411 412 @LayoutlibDelegate 413 /*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset, 414 int stride, int x, int y, int width, int height) { 415 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 416 if (delegate == null) { 417 return; 418 } 419 420 delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride); 421 } 422 423 424 @LayoutlibDelegate 425 /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color) { 426 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 427 if (delegate == null) { 428 return; 429 } 430 431 delegate.getImage().setRGB(x, y, color); 432 } 433 434 @LayoutlibDelegate 435 /*package*/ static void nativeSetPixels(long nativeBitmap, int[] colors, int offset, 436 int stride, int x, int y, int width, int height) { 437 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 438 if (delegate == null) { 439 return; 440 } 441 442 delegate.getImage().setRGB(x, y, width, height, colors, offset, stride); 443 } 444 445 @LayoutlibDelegate 446 /*package*/ static void nativeCopyPixelsToBuffer(long nativeBitmap, Buffer dst) { 447 // FIXME implement native delegate 448 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 449 "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/); 450 } 451 452 @LayoutlibDelegate 453 /*package*/ static void nativeCopyPixelsFromBuffer(long nb, Buffer src) { 454 // FIXME implement native delegate 455 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, 456 "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/); 457 } 458 459 @LayoutlibDelegate 460 /*package*/ static int nativeGenerationId(long nativeBitmap) { 461 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 462 if (delegate == null) { 463 return 0; 464 } 465 466 return delegate.mGenerationId; 467 } 468 469 @LayoutlibDelegate 470 /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) { 471 // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only 472 // used during aidl call so really this should not be called. 473 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 474 "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.", 475 null /*data*/); 476 return null; 477 } 478 479 @LayoutlibDelegate 480 /*package*/ static boolean nativeWriteToParcel(long nativeBitmap, boolean isMutable, 481 int density, Parcel p) { 482 // This is only called when sending a bitmap through aidl, so really this should not 483 // be called. 484 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, 485 "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.", 486 null /*data*/); 487 return false; 488 } 489 490 @LayoutlibDelegate 491 /*package*/ static Bitmap nativeExtractAlpha(long nativeBitmap, long nativePaint, 492 int[] offsetXY) { 493 Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap); 494 if (bitmap == null) { 495 return null; 496 } 497 498 // get the paint which can be null if nativePaint is 0. 499 Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint); 500 501 if (paint != null && paint.getMaskFilter() != null) { 502 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, 503 "MaskFilter not supported in Bitmap.extractAlpha", 504 null, null /*data*/); 505 } 506 507 int alpha = paint != null ? paint.getAlpha() : 0xFF; 508 BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha); 509 510 // create the delegate. The actual Bitmap config is only an alpha channel 511 Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8); 512 513 // the density doesn't matter, it's set by the Java method. 514 return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE), 515 Density.DEFAULT_DENSITY /*density*/); 516 } 517 518 @LayoutlibDelegate 519 /*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) { 520 // get the delegate from the native int. 521 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 522 return delegate != null && delegate.mIsPremultiplied; 523 524 } 525 526 @LayoutlibDelegate 527 /*package*/ static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) { 528 // get the delegate from the native int. 529 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 530 if (delegate == null) { 531 return; 532 } 533 534 delegate.mIsPremultiplied = isPremul; 535 } 536 537 @LayoutlibDelegate 538 /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha, 539 boolean isPremul) { 540 // get the delegate from the native int. 541 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 542 if (delegate == null) { 543 return; 544 } 545 546 delegate.mHasAlpha = hasAlpha; 547 } 548 549 @LayoutlibDelegate 550 /*package*/ static void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap) { 551 // get the delegate from the native int. 552 Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); 553 if (delegate == null) { 554 return; 555 } 556 557 delegate.mHasMipMap = hasMipMap; 558 } 559 560 @LayoutlibDelegate 561 /*package*/ static boolean nativeSameAs(long nb0, long nb1) { 562 Bitmap_Delegate delegate1 = sManager.getDelegate(nb0); 563 if (delegate1 == null) { 564 return false; 565 } 566 567 Bitmap_Delegate delegate2 = sManager.getDelegate(nb1); 568 if (delegate2 == null) { 569 return false; 570 } 571 572 BufferedImage image1 = delegate1.getImage(); 573 BufferedImage image2 = delegate2.getImage(); 574 if (delegate1.mConfig != delegate2.mConfig || 575 image1.getWidth() != image2.getWidth() || 576 image1.getHeight() != image2.getHeight()) { 577 return false; 578 } 579 580 // get the internal data 581 int w = image1.getWidth(); 582 int h = image2.getHeight(); 583 int[] argb1 = new int[w*h]; 584 int[] argb2 = new int[w*h]; 585 586 image1.getRGB(0, 0, w, h, argb1, 0, w); 587 image2.getRGB(0, 0, w, h, argb2, 0, w); 588 589 // compares 590 if (delegate1.mConfig == Config.ALPHA_8) { 591 // in this case we have to manually compare the alpha channel as the rest is garbage. 592 final int length = w*h; 593 for (int i = 0 ; i < length ; i++) { 594 if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) { 595 return false; 596 } 597 } 598 return true; 599 } 600 601 return Arrays.equals(argb1, argb2); 602 } 603 604 // Only used by AssetAtlasService, which we don't care about. 605 @LayoutlibDelegate 606 /*package*/ static long nativeRefPixelRef(long nativeBitmap) { 607 // Hack: This is called by Bitmap.refSkPixelRef() and LayoutLib uses that method to get 608 // the native pointer from a Bitmap. So, we return nativeBitmap here. 609 return nativeBitmap; 610 } 611 612 // ---- Private delegate/helper methods ---- 613 614 private Bitmap_Delegate(BufferedImage image, Config config) { 615 mImage = image; 616 mConfig = config; 617 } 618 619 private static Bitmap createBitmap(Bitmap_Delegate delegate, 620 Set<BitmapCreateFlags> createFlags, int density) { 621 // get its native_int 622 long nativeInt = sManager.addNewDelegate(delegate); 623 624 int width = delegate.mImage.getWidth(); 625 int height = delegate.mImage.getHeight(); 626 boolean isMutable = createFlags.contains(BitmapCreateFlags.MUTABLE); 627 boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED); 628 629 // and create/return a new Bitmap with it 630 return new Bitmap(nativeInt, null /* buffer */, width, height, density, isMutable, 631 isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */); 632 } 633 634 private static Set<BitmapCreateFlags> getPremultipliedBitmapCreateFlags(boolean isMutable) { 635 Set<BitmapCreateFlags> createFlags = EnumSet.of(BitmapCreateFlags.PREMULTIPLIED); 636 if (isMutable) { 637 createFlags.add(BitmapCreateFlags.MUTABLE); 638 } 639 return createFlags; 640 } 641 642 /** 643 * Creates and returns a copy of a given BufferedImage. 644 * <p/> 645 * if alpha is different than 255, then it is applied to the alpha channel of each pixel. 646 * 647 * @param image the image to copy 648 * @param imageType the type of the new image 649 * @param alpha an optional alpha modifier 650 * @return a new BufferedImage 651 */ 652 /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) { 653 int w = image.getWidth(); 654 int h = image.getHeight(); 655 656 BufferedImage result = new BufferedImage(w, h, imageType); 657 658 int[] argb = new int[w * h]; 659 image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); 660 661 if (alpha != 255) { 662 final int length = argb.length; 663 for (int i = 0 ; i < length; i++) { 664 int a = (argb[i] >>> 24 * alpha) / 255; 665 argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF); 666 } 667 } 668 669 result.setRGB(0, 0, w, h, argb, 0, w); 670 671 return result; 672 } 673 674} 675