Bitmap_Delegate.java revision 6d60df19df21854a555f76ab46d73fa43a9af43f
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        NONE, 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        return bitmap == null ? null : getDelegate(bitmap.getNativeInstance());
96    }
97
98    /**
99     * Creates and returns a {@link Bitmap} initialized with the given file content.
100     *
101     * @param input the file from which to read the bitmap content
102     * @param isMutable whether the bitmap is mutable
103     * @param density the density associated with the bitmap
104     *
105     * @see Bitmap#isMutable()
106     * @see Bitmap#getDensity()
107     */
108    public static Bitmap createBitmap(File input, boolean isMutable, Density density)
109            throws IOException {
110        return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
111    }
112
113    /**
114     * Creates and returns a {@link Bitmap} initialized with the given file content.
115     *
116     * @param input the file from which to read the bitmap content
117     * @param density the density associated with the bitmap
118     *
119     * @see Bitmap#isPremultiplied()
120     * @see Bitmap#isMutable()
121     * @see Bitmap#getDensity()
122     */
123    private static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
124            Density density) throws IOException {
125        // create a delegate with the content of the file.
126        BufferedImage image = ImageIO.read(input);
127        if (image == null && input.exists()) {
128            // There was a problem decoding the image, or the decoder isn't registered. Webp maybe.
129            // Replace with a broken image icon.
130            BridgeContext currentContext = RenderAction.getCurrentContext();
131            if (currentContext != null) {
132                RenderResources resources = currentContext.getRenderResources();
133                ResourceValue broken = resources.getFrameworkResource(ResourceType.DRAWABLE,
134                        "ic_menu_report_image");
135                File brokenFile = new File(broken.getValue());
136                if (brokenFile.exists()) {
137                    image = ImageIO.read(brokenFile);
138                }
139            }
140        }
141        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
142
143        return createBitmap(delegate, createFlags, density.getDpiValue());
144    }
145
146    /**
147     * Creates and returns a {@link Bitmap} initialized with the given stream content.
148     *
149     * @param input the stream from which to read the bitmap content
150     * @param isMutable whether the bitmap is mutable
151     * @param density the density associated with the bitmap
152     *
153     * @see Bitmap#isMutable()
154     * @see Bitmap#getDensity()
155     */
156    public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
157            throws IOException {
158        return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
159    }
160
161    /**
162     * Creates and returns a {@link Bitmap} initialized with the given stream content.
163     *
164     * @param input the stream from which to read the bitmap content
165     * @param density the density associated with the bitmap
166     *
167     * @see Bitmap#isPremultiplied()
168     * @see Bitmap#isMutable()
169     * @see Bitmap#getDensity()
170     */
171    public static Bitmap createBitmap(InputStream input, Set<BitmapCreateFlags> createFlags,
172            Density density) throws IOException {
173        // create a delegate with the content of the stream.
174        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
175
176        return createBitmap(delegate, createFlags, density.getDpiValue());
177    }
178
179    /**
180     * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
181     *
182     * @param image the bitmap content
183     * @param isMutable whether the bitmap is mutable
184     * @param density the density associated with the bitmap
185     *
186     * @see Bitmap#isMutable()
187     * @see Bitmap#getDensity()
188     */
189    public static Bitmap createBitmap(BufferedImage image, boolean isMutable, Density density) {
190        return createBitmap(image, getPremultipliedBitmapCreateFlags(isMutable), density);
191    }
192
193    /**
194     * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
195     *
196     * @param image the bitmap content
197     * @param density the density associated with the bitmap
198     *
199     * @see Bitmap#isPremultiplied()
200     * @see Bitmap#isMutable()
201     * @see Bitmap#getDensity()
202     */
203    public static Bitmap createBitmap(BufferedImage image, Set<BitmapCreateFlags> createFlags,
204            Density density) {
205        // create a delegate with the given image.
206        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
207
208        return createBitmap(delegate, createFlags, density.getDpiValue());
209    }
210
211    private static int getBufferedImageType() {
212        return BufferedImage.TYPE_INT_ARGB;
213    }
214
215    /**
216     * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
217     */
218    public BufferedImage getImage() {
219        return mImage;
220    }
221
222    /**
223     * Returns the Android bitmap config. Note that this not the config of the underlying
224     * Java2D bitmap.
225     */
226    public Config getConfig() {
227        return mConfig;
228    }
229
230    /**
231     * Returns the hasAlpha rendering hint
232     * @return true if the bitmap alpha should be used at render time
233     */
234    public boolean hasAlpha() {
235        return mHasAlpha && mConfig != Config.RGB_565;
236    }
237
238    /**
239     * Update the generationId.
240     *
241     * @see Bitmap#getGenerationId()
242     */
243    public void change() {
244        mGenerationId++;
245    }
246
247    // ---- native methods ----
248
249    @LayoutlibDelegate
250    /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
251            int height, int nativeConfig, boolean isMutable) {
252        int imageType = getBufferedImageType();
253
254        // create the image
255        BufferedImage image = new BufferedImage(width, height, imageType);
256
257        if (colors != null) {
258            image.setRGB(0, 0, width, height, colors, offset, stride);
259        }
260
261        // create a delegate with the content of the stream.
262        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
263
264        return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
265                            Bitmap.getDefaultDensity());
266    }
267
268    @LayoutlibDelegate
269    /*package*/ static Bitmap nativeCopy(long srcBitmap, int nativeConfig, boolean isMutable) {
270        Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap);
271        if (srcBmpDelegate == null) {
272            return null;
273        }
274
275        BufferedImage srcImage = srcBmpDelegate.getImage();
276
277        int width = srcImage.getWidth();
278        int height = srcImage.getHeight();
279
280        int imageType = getBufferedImageType();
281
282        // create the image
283        BufferedImage image = new BufferedImage(width, height, imageType);
284
285        // copy the source image into the image.
286        int[] argb = new int[width * height];
287        srcImage.getRGB(0, 0, width, height, argb, 0, width);
288        image.setRGB(0, 0, width, height, argb, 0, width);
289
290        // create a delegate with the content of the stream.
291        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
292
293        return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
294                Bitmap.getDefaultDensity());
295    }
296
297    @LayoutlibDelegate
298    /*package*/ static Bitmap nativeCopyAshmem(long nativeSrcBitmap) {
299        // Unused method; no implementation provided.
300        assert false;
301        return null;
302    }
303
304    @LayoutlibDelegate
305    /*package*/ static Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig) {
306        // Unused method; no implementation provided.
307        assert false;
308        return null;
309    }
310
311    @LayoutlibDelegate
312    /*package*/ static long nativeGetNativeFinalizer() {
313        synchronized (Bitmap_Delegate.class) {
314            if (sFinalizer == -1) {
315                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
316            }
317            return sFinalizer;
318        }
319    }
320
321    @LayoutlibDelegate
322    /*package*/ static boolean nativeRecycle(long nativeBitmap) {
323        // In our case reycle() is a no-op. We will let the finalizer to dispose the bitmap.
324        return true;
325    }
326
327    @LayoutlibDelegate
328    /*package*/ static void nativeReconfigure(long nativeBitmap, int width, int height,
329            int config, boolean isPremultiplied) {
330        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
331                "Bitmap.reconfigure() is not supported", null /*data*/);
332    }
333
334    @LayoutlibDelegate
335    /*package*/ static boolean nativeCompress(long nativeBitmap, int format, int quality,
336            OutputStream stream, byte[] tempStorage) {
337        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
338                "Bitmap.compress() is not supported", null /*data*/);
339        return true;
340    }
341
342    @LayoutlibDelegate
343    /*package*/ static void nativeErase(long nativeBitmap, int color) {
344        // get the delegate from the native int.
345        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
346        if (delegate == null) {
347            return;
348        }
349
350        BufferedImage image = delegate.mImage;
351
352        Graphics2D g = image.createGraphics();
353        try {
354            g.setColor(new java.awt.Color(color, true));
355
356            g.fillRect(0, 0, image.getWidth(), image.getHeight());
357        } finally {
358            g.dispose();
359        }
360    }
361
362    @LayoutlibDelegate
363    /*package*/ static int nativeRowBytes(long nativeBitmap) {
364        // get the delegate from the native int.
365        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
366        if (delegate == null) {
367            return 0;
368        }
369
370        return delegate.mImage.getWidth();
371    }
372
373    @LayoutlibDelegate
374    /*package*/ static int nativeConfig(long nativeBitmap) {
375        // get the delegate from the native int.
376        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
377        if (delegate == null) {
378            return 0;
379        }
380
381        return delegate.mConfig.nativeInt;
382    }
383
384    @LayoutlibDelegate
385    /*package*/ static boolean nativeHasAlpha(long nativeBitmap) {
386        // get the delegate from the native int.
387        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
388        return delegate == null || delegate.mHasAlpha;
389
390    }
391
392    @LayoutlibDelegate
393    /*package*/ static boolean nativeHasMipMap(long nativeBitmap) {
394        // get the delegate from the native int.
395        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
396        return delegate == null || delegate.mHasMipMap;
397
398    }
399
400    @LayoutlibDelegate
401    /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y) {
402        // get the delegate from the native int.
403        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
404        if (delegate == null) {
405            return 0;
406        }
407
408        return delegate.mImage.getRGB(x, y);
409    }
410
411    @LayoutlibDelegate
412    /*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset,
413            int stride, int x, int y, int width, int height) {
414        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
415        if (delegate == null) {
416            return;
417        }
418
419        delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride);
420    }
421
422
423    @LayoutlibDelegate
424    /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color) {
425        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
426        if (delegate == null) {
427            return;
428        }
429
430        delegate.getImage().setRGB(x, y, color);
431    }
432
433    @LayoutlibDelegate
434    /*package*/ static void nativeSetPixels(long nativeBitmap, int[] colors, int offset,
435            int stride, int x, int y, int width, int height) {
436        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
437        if (delegate == null) {
438            return;
439        }
440
441        delegate.getImage().setRGB(x, y, width, height, colors, offset, stride);
442    }
443
444    @LayoutlibDelegate
445    /*package*/ static void nativeCopyPixelsToBuffer(long nativeBitmap, Buffer dst) {
446        // FIXME implement native delegate
447        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
448                "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/);
449    }
450
451    @LayoutlibDelegate
452    /*package*/ static void nativeCopyPixelsFromBuffer(long nb, Buffer src) {
453        // FIXME implement native delegate
454        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
455                "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/);
456    }
457
458    @LayoutlibDelegate
459    /*package*/ static int nativeGenerationId(long nativeBitmap) {
460        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
461        if (delegate == null) {
462            return 0;
463        }
464
465        return delegate.mGenerationId;
466    }
467
468    @LayoutlibDelegate
469    /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
470        // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
471        // used during aidl call so really this should not be called.
472        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
473                "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.",
474                null /*data*/);
475        return null;
476    }
477
478    @LayoutlibDelegate
479    /*package*/ static boolean nativeWriteToParcel(long nativeBitmap, boolean isMutable,
480            int density, Parcel p) {
481        // This is only called when sending a bitmap through aidl, so really this should not
482        // be called.
483        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
484                "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.",
485                null /*data*/);
486        return false;
487    }
488
489    @LayoutlibDelegate
490    /*package*/ static Bitmap nativeExtractAlpha(long nativeBitmap, long nativePaint,
491            int[] offsetXY) {
492        Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
493        if (bitmap == null) {
494            return null;
495        }
496
497        // get the paint which can be null if nativePaint is 0.
498        Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
499
500        if (paint != null && paint.getMaskFilter() != null) {
501            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
502                    "MaskFilter not supported in Bitmap.extractAlpha",
503                    null, null /*data*/);
504        }
505
506        int alpha = paint != null ? paint.getAlpha() : 0xFF;
507        BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
508
509        // create the delegate. The actual Bitmap config is only an alpha channel
510        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
511
512        // the density doesn't matter, it's set by the Java method.
513        return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE),
514                Density.DEFAULT_DENSITY /*density*/);
515    }
516
517    @LayoutlibDelegate
518    /*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) {
519        // get the delegate from the native int.
520        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
521        return delegate != null && delegate.mIsPremultiplied;
522
523    }
524
525    @LayoutlibDelegate
526    /*package*/ static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) {
527        // get the delegate from the native int.
528        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
529        if (delegate == null) {
530            return;
531        }
532
533        delegate.mIsPremultiplied = isPremul;
534    }
535
536    @LayoutlibDelegate
537    /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha,
538            boolean isPremul) {
539        // get the delegate from the native int.
540        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
541        if (delegate == null) {
542            return;
543        }
544
545        delegate.mHasAlpha = hasAlpha;
546    }
547
548    @LayoutlibDelegate
549    /*package*/ static void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap) {
550        // get the delegate from the native int.
551        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
552        if (delegate == null) {
553            return;
554        }
555
556        delegate.mHasMipMap = hasMipMap;
557    }
558
559    @LayoutlibDelegate
560    /*package*/ static boolean nativeSameAs(long nb0, long nb1) {
561        Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
562        if (delegate1 == null) {
563            return false;
564        }
565
566        Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
567        if (delegate2 == null) {
568            return false;
569        }
570
571        BufferedImage image1 = delegate1.getImage();
572        BufferedImage image2 = delegate2.getImage();
573        if (delegate1.mConfig != delegate2.mConfig ||
574                image1.getWidth() != image2.getWidth() ||
575                image1.getHeight() != image2.getHeight()) {
576            return false;
577        }
578
579        // get the internal data
580        int w = image1.getWidth();
581        int h = image2.getHeight();
582        int[] argb1 = new int[w*h];
583        int[] argb2 = new int[w*h];
584
585        image1.getRGB(0, 0, w, h, argb1, 0, w);
586        image2.getRGB(0, 0, w, h, argb2, 0, w);
587
588        // compares
589        if (delegate1.mConfig == Config.ALPHA_8) {
590            // in this case we have to manually compare the alpha channel as the rest is garbage.
591            final int length = w*h;
592            for (int i = 0 ; i < length ; i++) {
593                if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
594                    return false;
595                }
596            }
597            return true;
598        }
599
600        return Arrays.equals(argb1, argb2);
601    }
602
603    @LayoutlibDelegate
604    /*package*/ static int nativeGetAllocationByteCount(long nativeBitmap) {
605        // get the delegate from the native int.
606        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
607        if (delegate == null) {
608            return 0;
609        }
610        return nativeRowBytes(nativeBitmap) * delegate.mImage.getHeight();
611
612    }
613
614    @LayoutlibDelegate
615    /*package*/ static void nativePrepareToDraw(long nativeBitmap) {
616        // do nothing as Bitmap_Delegate does not have caches
617    }
618
619    @LayoutlibDelegate
620    /*package*/ static Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap) {
621        Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(nativeBitmap);
622        if (srcBmpDelegate == null) {
623            return null;
624        }
625
626        BufferedImage srcImage = srcBmpDelegate.getImage();
627
628        // create the image
629        BufferedImage image = new BufferedImage(srcImage.getColorModel(), srcImage.copyData(null),
630                srcImage.isAlphaPremultiplied(), null);
631
632        // create a delegate with the content of the stream.
633        Bitmap_Delegate delegate = new Bitmap_Delegate(image, srcBmpDelegate.getConfig());
634
635        return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.NONE),
636                Bitmap.getDefaultDensity());
637    }
638
639    // ---- Private delegate/helper methods ----
640
641    private Bitmap_Delegate(BufferedImage image, Config config) {
642        mImage = image;
643        mConfig = config;
644    }
645
646    private static Bitmap createBitmap(Bitmap_Delegate delegate,
647            Set<BitmapCreateFlags> createFlags, int density) {
648        // get its native_int
649        long nativeInt = sManager.addNewDelegate(delegate);
650
651        int width = delegate.mImage.getWidth();
652        int height = delegate.mImage.getHeight();
653        boolean isMutable = createFlags.contains(BitmapCreateFlags.MUTABLE);
654        boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED);
655
656        // and create/return a new Bitmap with it
657        return new Bitmap(nativeInt, width, height, density, isMutable,
658                          isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */);
659    }
660
661    private static Set<BitmapCreateFlags> getPremultipliedBitmapCreateFlags(boolean isMutable) {
662        Set<BitmapCreateFlags> createFlags = EnumSet.of(BitmapCreateFlags.PREMULTIPLIED);
663        if (isMutable) {
664            createFlags.add(BitmapCreateFlags.MUTABLE);
665        }
666        return createFlags;
667    }
668
669    /**
670     * Creates and returns a copy of a given BufferedImage.
671     * <p/>
672     * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
673     *
674     * @param image the image to copy
675     * @param imageType the type of the new image
676     * @param alpha an optional alpha modifier
677     * @return a new BufferedImage
678     */
679    /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
680        int w = image.getWidth();
681        int h = image.getHeight();
682
683        BufferedImage result = new BufferedImage(w, h, imageType);
684
685        int[] argb = new int[w * h];
686        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
687
688        if (alpha != 255) {
689            final int length = argb.length;
690            for (int i = 0 ; i < length; i++) {
691                int a = (argb[i] >>> 24 * alpha) / 255;
692                argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
693            }
694        }
695
696        result.setRGB(0, 0, w, h, argb, 0, w);
697
698        return result;
699    }
700
701}
702