Reputation: 67
This is in small part a question (because I don't understand why it makes a difference), and in large part a post to hopefully help some other poor programmer out there.
I have a library of code that I wrote about 5-7 years ago when first starting Android programming, and it has functions to read a file into memory (which get used for a number of things), and a function to load a bitmap from a .png file. The latter function used the "read file" function to read the bytes from the file into a byte array and then creates a Bitmap from it. I know this isn't really clean code, nor is it probably the best way to do it, but it works, and why muck with something that works?
Except... the last few revs of Android (and Oreo in particular) have caused the drawing (from Bitmaps) in my apps to REALLY slow WAYYYYYY down. I struggled for days, trying to figure out what was going on, and finally stumbled upon a fix... which I don't understand.
Here's the core of the "load .png file into Bitmap" function (as it was). Note that the whole "if-for-for" transparency stuff can probably be completely ignored, it's a special case for converting pixels of one or two colors into transparent pixels. At entry to this block, the .png file has already been read into 'bits'.
BitmapFactory.Options bfo = new BitmapFactory.Options();
bfo.inPreferredConfig = Bitmap.Config.valueOf("ARGB_8888");
Bitmap imbm = BitmapFactory.decodeByteArray(bits, 0, flen, bfo);// this makes an IMmutable bitmap
bits = null;
System.gc();
Bitmap bm = imbm.copy(Bitmap.Config.valueOf("ARGB_8888"), true);// convert it to a MUTABLE bitmap
imbm.recycle();
imbm = null;
w = bm.getWidth();
h = bm.getHeight();
// do transparency stuff
if (transX1 >= 0 && transX1 < w && transY1 >= 0 && transY1 < h) {
clr1 = bm.getPixel(transX1, transY1);
clr2 = bm.getPixel(transX2, transY2);
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
clr = bm.getPixel(x, y);
// convert any 0x00818181 (middle-gray) pixels to 0x00808080
// so that 0x00818181 is guaranteed to never be used in customer-created bitmaps
if (clr1 != 0xFF818181 && clr == 0xFF818181) bm.setPixel(x, y, 0xFF808080);
else if (clr == clr1 || clr == clr2) bm.setPixel(x, y, clr & 0x00000000);
}
}
}
System.gc(); // request garbage-collection to try to avoid out-of-memory when loading/creating bitmap
return bm;
The returned Bitmap was then used as the source from which to draw bits and pieces into a Context using dC.drawBitmap(bm, sRect, dRect, kPaint). However, changes in Android caused this to slow way down. The fix turned out to be replacing the final "return bm" above with this code:
Bitmap tBM = Bitmap.createBitmap(w, h, Bitmap.Config.valueOf("ARGB_8888"));
Rect sRect = new Rect();
Rect dRect = new Rect();
sRect.set(0, 0, w, h);
dRect.set(0, 0, w, h);
Canvas dC = new Canvas(tBM);
dC.drawBitmap(bm, sRect, dRect, kPaint);
dC = null;
bm = null;
return tBM;
In other words, it just takes the 'bm' Bitmap that resulted from the previous version, creates another Bitmap of the same size, draws 'bm' into the new Bitmap, and returns the new Bitmap instead of the previous version.
SOMETHING about the previous way of constructing the Bitmap appears to leave it in a "bad form" that makes drawing FROM it very slow, while doing a createBitmap() and a drawBitmap() into it results in a Bitmap that you can draw FROM very FAST.
Any ideas why that would be? (And yes, I tried all the usual RGB_565 things, etc.) THIS works and works well. But if I understood WHY this happened, that would be good, and like I said, my travails might prove useful to someone in the future.
Upvotes: 2
Views: 2157
Reputation: 67
I was right in my suspicion that the slow-down was somehow caused by the decodeByteArray() (sort of).
First, with more research (and advanced age and knowledge) I realized that the immutable/mutable issue could have been more easily fixed by setting BitmapFactory.Options.inMutable=true.
But, the real solution to the problem was changing the 'core' of the code to this:
BitmapFactory.Options bfo = new BitmapFactory.Options();
bfo.inPreferredConfig = Bitmap.Config.valueOf("ARGB_8888");
bfo.inMutable = true; // this makes an mutable bitmap
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
bfo.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}
Bitmap bm = BitmapFactory.decodeByteArray(bits, 0, flen, bfo);
bits = null;
In other words, I got rid of the immutable/mutable bitmap copy as mentioned above, and then I test for running on Oreo or later and, if so, set the inPreferredColorSpace member. This was added in Oreo (API 26). I would THINK when they add something like that that it would be set to a reasonable default, and I suppose it was, just not one that works well with drawing the bitmap to the display apparently. The default value is 'null', but changing that to SRGB is sufficient to make my drawing to the display from the resulting Bitmap lightning fast, so I assume (always dangerous) that this color space issue is what was getting straightened out by the kludge copy of the bitmap that I did in the original post.
Upvotes: 2