1 /*
2  * Copyright (C) 2006 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.drawable;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.RecordingCanvas;
24 import android.graphics.Region;
25 import android.os.Handler;
26 import android.os.HandlerThread;
27 import android.os.Parcel;
28 import android.test.AndroidTestCase;
29 import android.util.Log;
30 
31 import androidx.test.filters.SmallTest;
32 
33 import com.android.frameworks.coretests.R;
34 
35 import java.io.ByteArrayOutputStream;
36 import java.io.File;
37 import java.io.FileOutputStream;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 
41 public class IconTest extends AndroidTestCase {
42     public static final String TAG = IconTest.class.getSimpleName();
L(String s, Object... parts)43     public static void L(String s, Object... parts) {
44         Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
45     }
46 
47     @SmallTest
testWithBitmap()48     public void testWithBitmap() throws Exception {
49         final Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
50         final Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
51         final Bitmap bm3 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
52                 .getBitmap();
53 
54         final Canvas can1 = new Canvas(bm1);
55         can1.drawColor(0xFFFF0000);
56         final Canvas can2 = new Canvas(bm2);
57         can2.drawColor(0xFF00FF00);
58 
59         final Icon im1 = Icon.createWithBitmap(bm1);
60         final Icon im2 = Icon.createWithBitmap(bm2);
61         final Icon im3 = Icon.createWithBitmap(bm3);
62 
63         final Drawable draw1 = im1.loadDrawable(mContext);
64         final Drawable draw2 = im2.loadDrawable(mContext);
65         final Drawable draw3 = im3.loadDrawable(mContext);
66 
67         final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
68                 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
69         final Bitmap test2 = Bitmap.createBitmap(draw2.getIntrinsicWidth(),
70                 draw2.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
71         final Bitmap test3 = Bitmap.createBitmap(draw3.getIntrinsicWidth(),
72                 draw3.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
73 
74         draw1.setBounds(0, 0, draw1.getIntrinsicWidth(), draw1.getIntrinsicHeight());
75         draw1.draw(new Canvas(test1));
76 
77         draw2.setBounds(0, 0, draw2.getIntrinsicWidth(), draw2.getIntrinsicHeight());
78         draw2.draw(new Canvas(test2));
79 
80         draw3.setBounds(0, 0, draw3.getIntrinsicWidth(), draw3.getIntrinsicHeight());
81         draw3.draw(new Canvas(test3));
82 
83         final File dir = getContext().getExternalFilesDir(null);
84         L("writing temp bitmaps to %s...", dir);
85 
86         bm1.compress(Bitmap.CompressFormat.PNG, 100,
87                 new FileOutputStream(new File(dir, "bitmap1-original.png")));
88         test1.compress(Bitmap.CompressFormat.PNG, 100,
89                 new FileOutputStream(new File(dir, "bitmap1-test.png")));
90         if (!equalBitmaps(bm1, test1)) {
91             findBitmapDifferences(bm1, test1);
92             fail("bitmap1 differs, check " + dir);
93         }
94 
95         bm2.compress(Bitmap.CompressFormat.PNG, 100,
96                 new FileOutputStream(new File(dir, "bitmap2-original.png")));
97         test2.compress(Bitmap.CompressFormat.PNG, 100,
98                 new FileOutputStream(new File(dir, "bitmap2-test.png")));
99         if (!equalBitmaps(bm2, test2)) {
100             findBitmapDifferences(bm2, test2);
101             fail("bitmap2 differs, check " + dir);
102         }
103 
104         bm3.compress(Bitmap.CompressFormat.PNG, 100,
105                 new FileOutputStream(new File(dir, "bitmap3-original.png")));
106         test3.compress(Bitmap.CompressFormat.PNG, 100,
107                 new FileOutputStream(new File(dir, "bitmap3-test.png")));
108         if (!equalBitmaps(bm3, test3)) {
109             findBitmapDifferences(bm3, test3);
110             fail("bitmap3 differs, check " + dir);
111         }
112     }
113 
114     @SmallTest
testScaleDownIfNecessary()115     public void testScaleDownIfNecessary() throws Exception {
116         final Bitmap bm = Bitmap.createBitmap(4321, 78, Bitmap.Config.ARGB_8888);
117         final Icon ic = Icon.createWithBitmap(bm);
118         ic.scaleDownIfNecessary(40, 20);
119 
120         assertThat(bm.getWidth()).isEqualTo(4321);
121         assertThat(bm.getHeight()).isEqualTo(78);
122 
123         assertThat(ic.getBitmap().getWidth()).isLessThan(41);
124         assertThat(ic.getBitmap().getHeight()).isLessThan(21);
125     }
126 
127     @SmallTest
testWithAdaptiveBitmap()128     public void testWithAdaptiveBitmap() throws Exception {
129         final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
130 
131         final Canvas can1 = new Canvas(bm1);
132         can1.drawColor(0xFFFF0000);
133 
134         final Icon im1 = Icon.createWithAdaptiveBitmap(bm1);
135 
136         final AdaptiveIconDrawable draw1 = (AdaptiveIconDrawable) im1.loadDrawable(mContext);
137 
138         final Bitmap test1 = Bitmap.createBitmap(
139             (int)(draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
140             (int)(draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
141             Bitmap.Config.ARGB_8888);
142 
143         draw1.setBounds(0, 0,
144             (int) (draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
145             (int) (draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())));
146         draw1.draw(new Canvas(test1));
147 
148         final File dir = getContext().getExternalFilesDir(null);
149         L("writing temp bitmaps to %s...", dir);
150 
151         bm1.compress(Bitmap.CompressFormat.PNG, 100,
152             new FileOutputStream(new File(dir, "adaptive-bitmap1-original.png")));
153         test1.compress(Bitmap.CompressFormat.PNG, 100,
154             new FileOutputStream(new File(dir, "adaptive-bitmap1-test.png")));
155         if (!equalBitmaps(bm1, test1, draw1.getSafeZone())) {
156             findBitmapDifferences(bm1, test1);
157             fail("adaptive bitmap1 differs, check " + dir);
158         }
159     }
160 
161     @SmallTest
testWithBitmapResource()162     public void testWithBitmapResource() throws Exception {
163         final Bitmap res1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
164                 .getBitmap();
165 
166         final Icon im1 = Icon.createWithResource(getContext(), R.drawable.landscape);
167         final Drawable draw1 = im1.loadDrawable(mContext);
168         final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
169                 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
170         draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
171         draw1.draw(new Canvas(test1));
172 
173         final File dir = getContext().getExternalFilesDir(null);
174         res1.compress(Bitmap.CompressFormat.PNG, 100,
175                 new FileOutputStream(new File(dir, "res1-original.png")));
176         test1.compress(Bitmap.CompressFormat.PNG, 100,
177                 new FileOutputStream(new File(dir, "res1-test.png")));
178         if (!equalBitmaps(res1, test1)) {
179             findBitmapDifferences(res1, test1);
180             fail("res1 differs, check " + dir);
181         }
182     }
183 
184     /**
185      * Icon resource test that ensures we can load and draw non-bitmaps. (In this case,
186      * stat_sys_adb is assumed, and asserted, to be a vector drawable.)
187      */
188     @SmallTest
testWithStatSysAdbResource()189     public void testWithStatSysAdbResource() throws Exception {
190         // establish reference bitmap
191         final float dp = getContext().getResources().getDisplayMetrics().density;
192         final int stat_sys_adb_width = (int) (24 * dp);
193         final int stat_sys_adb_height = (int) (24 * dp);
194 
195         final Drawable stat_sys_adb = getContext()
196                 .getDrawable(com.android.internal.R.drawable.stat_sys_adb);
197         if (!(stat_sys_adb instanceof VectorDrawable)) {
198             fail("stat_sys_adb is a " + stat_sys_adb.toString()
199                     + ", not a VectorDrawable; stat_sys_adb malformed");
200         }
201 
202         if (stat_sys_adb.getIntrinsicWidth() != stat_sys_adb_width) {
203             fail("intrinsic width of stat_sys_adb is not 24dp; stat_sys_adb malformed");
204         }
205         if (stat_sys_adb.getIntrinsicHeight() != stat_sys_adb_height) {
206             fail("intrinsic height of stat_sys_adb is not 24dp; stat_sys_adb malformed");
207         }
208         final Bitmap referenceBitmap = Bitmap.createBitmap(
209                 stat_sys_adb_width,
210                 stat_sys_adb_height,
211                 Bitmap.Config.ARGB_8888);
212         stat_sys_adb.setBounds(0, 0, stat_sys_adb_width, stat_sys_adb_height);
213         stat_sys_adb.draw(new Canvas(referenceBitmap));
214 
215         final Icon im1 = Icon.createWithResource(getContext(),
216                 com.android.internal.R.drawable.stat_sys_adb);
217         final Drawable draw1 = im1.loadDrawable(getContext());
218 
219         assertEquals(stat_sys_adb.getIntrinsicWidth(), draw1.getIntrinsicWidth());
220         assertEquals(stat_sys_adb.getIntrinsicHeight(), draw1.getIntrinsicHeight());
221         assertEquals(im1.getResId(), com.android.internal.R.drawable.stat_sys_adb);
222 
223         final Bitmap test1 = Bitmap.createBitmap(
224                 draw1.getIntrinsicWidth(),
225                 draw1.getIntrinsicHeight(),
226                 Bitmap.Config.ARGB_8888);
227         draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
228         draw1.draw(new Canvas(test1));
229 
230         final File dir = getContext().getExternalFilesDir(null);
231         test1.compress(Bitmap.CompressFormat.PNG, 100,
232                 new FileOutputStream(new File(dir, "testWithVectorDrawableResource-test.png")));
233         if (!equalBitmaps(referenceBitmap, test1)) {
234             findBitmapDifferences(referenceBitmap, test1);
235             fail("testWithFile: file1 differs, check " + dir);
236         }
237     }
238 
239     @SmallTest
testWithFile()240     public void testWithFile() throws Exception {
241         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
242                 .getBitmap();
243         final File dir = getContext().getExternalFilesDir(null);
244         final File file1 = new File(dir, "file1-original.png");
245         bit1.compress(Bitmap.CompressFormat.PNG, 100,
246                 new FileOutputStream(file1));
247 
248         final Icon im1 = Icon.createWithFilePath(file1.toString());
249         final Drawable draw1 = im1.loadDrawable(mContext);
250         final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
251                 draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
252         draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
253         draw1.draw(new Canvas(test1));
254 
255         test1.compress(Bitmap.CompressFormat.PNG, 100,
256                 new FileOutputStream(new File(dir, "file1-test.png")));
257         if (!equalBitmaps(bit1, test1)) {
258             findBitmapDifferences(bit1, test1);
259             fail("testWithFile: file1 differs, check " + dir);
260         }
261     }
262 
263     @SmallTest
testAsync()264     public void testAsync() throws Exception {
265         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
266                 .getBitmap();
267         final File dir = getContext().getExternalFilesDir(null);
268         final File file1 = new File(dir, "async-original.png");
269         bit1.compress(Bitmap.CompressFormat.PNG, 100,
270                 new FileOutputStream(file1));
271 
272         final Icon im1 = Icon.createWithFilePath(file1.toString());
273         final HandlerThread thd = new HandlerThread("testAsync");
274         thd.start();
275         final Handler h = new Handler(thd.getLooper());
276         L(TAG, "asyncTest: dispatching load to thread: " + thd);
277         im1.loadDrawableAsync(mContext, new Icon.OnDrawableLoadedListener() {
278             @Override
279             public void onDrawableLoaded(Drawable draw1) {
280                 L(TAG, "asyncTest: thread: loading drawable");
281                 L(TAG, "asyncTest: thread: loaded: %dx%d", draw1.getIntrinsicWidth(),
282                     draw1.getIntrinsicHeight());
283                 final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
284                         draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
285                 draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
286                 draw1.draw(new Canvas(test1));
287 
288                 try {
289                     test1.compress(Bitmap.CompressFormat.PNG, 100,
290                             new FileOutputStream(new File(dir, "async-test.png")));
291                 } catch (java.io.FileNotFoundException ex) {
292                     fail("couldn't create test file: " + ex);
293                 }
294                 if (!equalBitmaps(bit1, test1)) {
295                     findBitmapDifferences(bit1, test1);
296                     fail("testAsync: file1 differs, check " + dir);
297                 }
298             }
299         }, h);
300         L(TAG, "asyncTest: awaiting result");
301         Thread.sleep(500); // ;_;
302         assertTrue("async-test.png does not exist!", new File(dir, "async-test.png").exists());
303         L(TAG, "asyncTest: done");
304     }
305 
306     @SmallTest
testParcel()307     public void testParcel() throws Exception {
308         final Bitmap originalbits = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
309                 .getBitmap();
310 
311         final ByteArrayOutputStream ostream = new ByteArrayOutputStream(
312                 originalbits.getWidth() * originalbits.getHeight() * 2); // guess 50% compression
313         originalbits.compress(Bitmap.CompressFormat.PNG, 100, ostream);
314         final byte[] pngdata = ostream.toByteArray();
315 
316         L("starting testParcel; bitmap: %d bytes, PNG: %d bytes",
317                 originalbits.getByteCount(),
318                 pngdata.length);
319 
320         final File dir = getContext().getExternalFilesDir(null);
321         final File originalfile = new File(dir, "parcel-original.png");
322         new FileOutputStream(originalfile).write(pngdata);
323 
324         ArrayList<Icon> imgs = new ArrayList<>();
325         final Icon file1 = Icon.createWithFilePath(originalfile.getAbsolutePath());
326         imgs.add(file1);
327         final Icon bit1 = Icon.createWithBitmap(originalbits);
328         imgs.add(bit1);
329         final Icon data1 = Icon.createWithData(pngdata, 0, pngdata.length);
330         imgs.add(data1);
331         final Icon res1 = Icon.createWithResource(getContext(), R.drawable.landscape);
332         imgs.add(res1);
333 
334         ArrayList<Icon> test = new ArrayList<>();
335         final Parcel parcel = Parcel.obtain();
336         int pos = 0;
337         parcel.writeInt(imgs.size());
338         for (Icon img : imgs) {
339             img.writeToParcel(parcel, 0);
340             L("used %d bytes parceling: %s", parcel.dataPosition() - pos, img);
341             pos = parcel.dataPosition();
342         }
343 
344         parcel.setDataPosition(0); // rewind
345         final int N = parcel.readInt();
346         for (int i=0; i<N; i++) {
347             Icon img = Icon.CREATOR.createFromParcel(parcel);
348             L("test %d: read from parcel: %s", i, img);
349             final File testfile = new File(dir,
350                     String.format("parcel-test%02d.png", i));
351 
352             final Drawable draw1 = img.loadDrawable(mContext);
353             if (draw1 == null) {
354                 fail("null drawable from img: " + img);
355             }
356             final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
357                     draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
358             draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
359             draw1.draw(new Canvas(test1));
360 
361             try {
362                 test1.compress(Bitmap.CompressFormat.PNG, 100,
363                         new FileOutputStream(testfile));
364             } catch (java.io.FileNotFoundException ex) {
365                 fail("couldn't create test file " + testfile + ": " + ex);
366             }
367             if (!equalBitmaps(originalbits, test1)) {
368                 findBitmapDifferences(originalbits, test1);
369                 fail(testfile + " differs from original: " + originalfile);
370             }
371 
372         }
373     }
374 
getMaxWidth(int origWidth, int origHeight, int maxNumPixels)375     private int getMaxWidth(int origWidth, int origHeight, int maxNumPixels) {
376         float aspRatio = (float) origWidth / (float) origHeight;
377         int newHeight = (int) Math.sqrt(maxNumPixels / aspRatio);
378         return (int) (newHeight * aspRatio);
379     }
380 
getMaxHeight(int origWidth, int origHeight, int maxNumPixels)381     private int getMaxHeight(int origWidth, int origHeight, int maxNumPixels) {
382         float aspRatio = (float) origWidth / (float) origHeight;
383         return (int) Math.sqrt(maxNumPixels / aspRatio);
384     }
385 
386     @SmallTest
testScaleDownMaxSizeWithBitmap()387     public void testScaleDownMaxSizeWithBitmap() throws Exception {
388         final int bmpWidth = 13_000;
389         final int bmpHeight = 10_000;
390         final int bmpBpp = 4;
391         final int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bmpBpp;
392         final int maxWidth = getMaxWidth(bmpWidth, bmpHeight, maxNumPixels);
393         final int maxHeight = getMaxHeight(bmpWidth, bmpHeight, maxNumPixels);
394 
395         final Bitmap bm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
396         final Icon ic = Icon.createWithBitmap(bm);
397         final Drawable drawable = ic.loadDrawable(mContext);
398 
399         assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth);
400         assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
401     }
402 
403     @SmallTest
testScaleDownMaxSizeWithAdaptiveBitmap()404     public void testScaleDownMaxSizeWithAdaptiveBitmap() throws Exception {
405         final int bmpWidth = 20_000;
406         final int bmpHeight = 10_000;
407         final int bmpBpp = 4;
408         final int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bmpBpp;
409         final int maxWidth = getMaxWidth(bmpWidth, bmpHeight, maxNumPixels);
410         final int maxHeight = getMaxHeight(bmpWidth, bmpHeight, maxNumPixels);
411 
412         final Bitmap bm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
413         final Icon ic = Icon.createWithAdaptiveBitmap(bm);
414         final AdaptiveIconDrawable adaptiveDrawable = (AdaptiveIconDrawable) ic.loadDrawable(
415                 mContext);
416         final Drawable drawable = adaptiveDrawable.getForeground();
417 
418         assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth);
419         assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
420     }
421 
422     @SmallTest
testScaleDownMaxSizeWithResource()423     public void testScaleDownMaxSizeWithResource() throws Exception {
424         final Icon ic = Icon.createWithResource(getContext(), R.drawable.test_too_big);
425         final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
426 
427         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
428     }
429 
430     @SmallTest
testScaleDownMaxSizeWithFile()431     public void testScaleDownMaxSizeWithFile() throws Exception {
432         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.test_too_big))
433                 .getBitmap();
434         final File dir = getContext().getExternalFilesDir(null);
435         final File file1 = new File(dir, "file1-too-big.png");
436         bit1.compress(Bitmap.CompressFormat.PNG, 100,
437                 new FileOutputStream(file1));
438 
439         final Icon ic = Icon.createWithFilePath(file1.toString());
440         final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
441 
442         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
443     }
444 
445     @SmallTest
testScaleDownMaxSizeWithData()446     public void testScaleDownMaxSizeWithData() throws Exception {
447         final int bmpBpp = 4;
448         final Bitmap originalBits = ((BitmapDrawable) getContext().getDrawable(
449                 R.drawable.test_too_big)).getBitmap();
450         final ByteArrayOutputStream ostream = new ByteArrayOutputStream(
451                 originalBits.getWidth() * originalBits.getHeight() * bmpBpp);
452         originalBits.compress(Bitmap.CompressFormat.PNG, 100, ostream);
453         final byte[] pngdata = ostream.toByteArray();
454         final Icon ic = Icon.createWithData(pngdata, 0, pngdata.length);
455         final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
456 
457         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
458     }
459 
460     // ======== utils ========
461 
462     static final char[] GRADIENT = " .:;+=xX$#".toCharArray();
463     static float[] hsv = new float[3];
colorToChar(int color)464     static char colorToChar(int color) {
465         int sum = ((color >> 16) & 0xff)
466                 + ((color >> 8)  & 0xff)
467                 + ((color)       & 0xff);
468         return GRADIENT[sum * (GRADIENT.length-1) / (3*0xff)];
469     }
printBits(int[] a, int w, int h)470     static void printBits(int[] a, int w, int h) {
471         final StringBuilder sb = new StringBuilder();
472         for (int i=0; i<w; i++) {
473             for (int j=0; j<h; j++) {
474                 sb.append(colorToChar(a[i+w*j]));
475             }
476             sb.append('\n');
477         }
478         L(sb.toString());
479     }
printBits(Bitmap a)480     static void printBits(Bitmap a) {
481         final int w = a.getWidth();
482         final int h = a.getHeight();
483         int[] aPix = new int[w * h];
484         printBits(aPix, w, h);
485     }
equalBitmaps(Bitmap a, Bitmap b)486     boolean equalBitmaps(Bitmap a, Bitmap b) {
487         return equalBitmaps(a, b, null);
488     }
489 
equalBitmaps(Bitmap a, Bitmap b, Region region)490     boolean equalBitmaps(Bitmap a, Bitmap b, Region region) {
491         if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false;
492 
493         final int w = a.getWidth();
494         final int h = a.getHeight();
495         int[] aPix = new int[w * h];
496         int[] bPix = new int[w * h];
497 
498         if (region != null) {
499             for (int i = 0; i < w; i++) {
500                 for (int j = 0; j < h; j++) {
501                     if (region.contains(i, j) && a.getPixel(i, j) != b.getPixel(i, j)) {
502                         return false;
503                     }
504                 }
505             }
506             return true;
507         } else {
508             a.getPixels(aPix, 0, w, 0, 0, w, h);
509             b.getPixels(bPix, 0, w, 0, 0, w, h);
510             return Arrays.equals(aPix, bPix);
511         }
512     }
513 
findBitmapDifferences(Bitmap a, Bitmap b)514     void findBitmapDifferences(Bitmap a, Bitmap b) {
515         if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) {
516             L("different sizes: %dx%d vs %dx%d",
517                         a.getWidth(), a.getHeight(), b.getWidth(), b.getHeight());
518             return;
519         }
520 
521         final int w = a.getWidth();
522         final int h = a.getHeight();
523         int[] aPix = new int[w * h];
524         int[] bPix = new int[w * h];
525 
526         a.getPixels(aPix, 0, w, 0, 0, w, h);
527         b.getPixels(bPix, 0, w, 0, 0, w, h);
528 
529         L("bitmap a (%dx%d)", w, h);
530         printBits(aPix, w, h);
531         L("bitmap b (%dx%d)", w, h);
532         printBits(bPix, w, h);
533 
534         StringBuffer sb = new StringBuffer("Different pixels: ");
535         for (int i=0; i<w; i++) {
536             for (int j=0; j<h; j++) {
537                 if (aPix[i+w*j] != bPix[i+w*j]) {
538                     sb.append(" ").append(i).append(",").append(j);
539                 }
540             }
541         }
542         L(sb.toString());
543     }
544 }
545