I have a single-channel PNG file I'd like to use as an alpha mask for Porter-Duff drawing operations. If I load it without any options, the resulting Bitmap has an RGB_565 config, i.e. treated as grayscale. If I set the preferred config to ALPHA_8, it loads it as a grayscale ARGB_8888 instead.
How can I convince Android to treat this file as an alpha mask instead of a grayscale image?
mask1 = BitmapFactory.decodeStream(pngStream);
// mask1.getConfig() is now RGB_565
BitmapFactory.Options maskOpts = new BitmapFactory.Options();
maskOpts.inPreferredConfig = Bitmap.Config.ALPHA_8;
mask2 = BitmapFactory.decodeStream(pngStream, null, maskOpts);
// mask2.getConfig() is now ARGB_8888 (the alpha channel is fully opaque)
More of a workaround than a solution:
I'm now including the alpha channel in an RGBA PNG file with the RGB channels all zeroes. I can load this file with a preferred config of ARGB_8888 and then extract its alpha channel. This wastes a few KB in the mask file, and a lot of memory while decoding the image.
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap source = BitmapFactory.decodeStream(pngStream, null, opts);
Bitmap mask = source.extractAlpha();
source.recycle();
// mask.getConfig() is now ALPHA_8
Related
Newbie question. I have a byte array (of total length 1920 X 1080 X 4), with every 4 bytes holding constant alpha value of 0x80 (~ 50% translucency) and RGB triplet, I would like to convert it to a bitmap using BitmapFactory. BitmapFactory always returns a null bitmap in the code below.
byte[] rgbImage; // allocated and populated with data elsewhere
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.outWidth = 1920;
opts.outHeight = 1080;
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeByteArray(rgbImage, 0, 1920 * 1080 * 4, opts);
if(bitmap == null){
Log.e("TAG","bitmap is null");
}
What am I doing wrong? It seems that since any byte takes values in the range 0..255, even an arbitrary byte array would qualify as a RGB image, provided its dimensions made sense.
BitmapFactory.decodeByteArray doesn't accept unsupported formats, so, ensure that the rgbImage byte data is one of the supported image formats. Follow the link to see which ones:
https://developer.android.com/guide/topics/media/media-formats#image-formats
In addition, althouh not the cause of the failure, outWidth / outHeight are not meant to be populated by you, instead these are populated by BitmapFactory when inJustDecodeBounds is set to True in order to find out an image dimensions without decoding it. Setting them does nothing at all, as these are output parameters. Also note that when setting inJustDecodeBounds to True, the return value will be always Null, as the request becomes for the image dimensions and not the image itself.
Also note that for the length argument in decodeByteArray, you can pass rgbImage.length instead of 1920 * 1080 * 4, if such buffer has the same length.
I have a loaded Bitmap which I would like to convert to set the config to Bitmap.Config.RGB_565. Is there a simple way of converting a Bitmap to this configuration after the Bitmap is already loaded into memory? For example, below I have a bitmap being decoded from the application resources, however, how would I convert an already loaded Bitmap to RGB_565? I'm sure it's something simple, however, I'm fairly new to working with Bitmaps and after a few hours of looking online, unfortunately I couldn't find what I needed specifically.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig=Bitmap.Config.RGB_565
bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.myphoto ,options);
You can also try this:
Bitmap converted = original.copy(Config.RGB_565, false);
From the documentation of Bitmap.copy():
Tries to make a new bitmap based on the dimensions of this bitmap, setting the new bitmap's config to the one specified, and then copying this bitmap's pixels into the new bitmap. If the conversion is not supported, or the allocator fails, then this returns NULL.
Looking through the native source code, you should be fine converting between any values of Bitmap.Config.
I haven't tested this but it should work:
private Bitmap convert(Bitmap bitmap, Bitmap.Config config) {
Bitmap convertedBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), config);
Canvas canvas = new Canvas(convertedBitmap);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
canvas.drawBitmap(bitmap, 0, 0, paint);
return convertedBitmap;
}
call the methods like this:
Bitmap convertedBitmap = convert(bitmap, Bitmap.Config.RGB_565);
You can do all kinds of additional transformations like rotating, stretching etc. if you use the drawBitmap with a Matrix.
Found the answer here https://stackoverflow.com/a/12148450/1364673, thanks to
siliconeagle.
The solution is to create a new bitmap with the required encoding as per link above example.
I'm getting an image (.png) from SQLiteDatabase and using this code to decode the bytearray into a bitmap:
Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inDither = true;
options.inScaled = true;
options.inDensity = 240;
options.inTargetDensity = metrics.densityDpi;
Bitmap bmp = BitmapFactory.decodeStream(new ByteArrayInputStream(imageAsBytes), null, options);
As you can see, image (3) should be like (2), but it doesn't.
1) = Image with no scale (metrics.densityDpi = 240);
2) = same .png above, but compiled in res/drawable;
3) = Image with down scale (with metrics.densityDpi = 120);
I also tried options.inDither = false;, but I see no difference.
So what's wrong with my code?
There a few other things I would try:
Load the png with no scale, when you come to draw the Image (either from within an ImageView or directly onto the canvas) set a Matrix to scale the image
Alternatively, load the image in the required density and try drawing the Bitmap directly to the canvas with a Paint object. After instantiating your Paint, enable Bitmap filtering (this will increase the image quality)
setFilterBitmap(true)
Finally, you could always load the Bitmap (density independent) and resize the Bitmap manually using Bitmap.createScaledBitmap, make sure you set the third paramenter to true (this enabled bitmap filtering for increased quality). Below is an example of scaling a bitmap where 100 is the desired size:
Bitmap.createScaledBitmap ( original_bitmap, 100, 100, true);
Briefly, the best quality downscaling algorithm consists of 2 steps:
downscale using BitmapFactory.Options::inSampleSize->BitmapFactory.decodeResource() as close as possible to the resolution that you need but not less than it
get to the exact resolution by downscaling a little bit using Canvas::drawBitmap()
Here is detailed explanation how SonyMobile resolved this task: http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/
Here is the source code of SonyMobile scale utils: http://developer.sonymobile.com/downloads/code-example-module/image-scaling-code-example-for-android/
In my app, the bitmap is drawn as if the color is some lower quality type. If i load up the background image using the gallery app, it loads just fine and does not look like it's super low quality. The code i am using to load and draw my images is simple:
//Code for initializing the Bitmap
Bitmap bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.none), (int) (canvas.getWidth() * compression), (int) (canvas.getHeight() * compression), true);
//...
//Code for drawing this Bitmap
canvas.drawBitmap(bitmap, null, new RectF(0, 0, canvas.getWidth(), canvas.getHeight()), null);
If nothing in the code tells you what is wrong, i made an image comparing what the image actually looks like on a computer or other image viewer, and what it looks like in the app.
question is somewhat similar to Bad image quality after resizing/scaling bitmap
try disabling scaling, resize in an offscreen bitmap and make sure that Bitmap is 32 bits (ARGB888):
Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap source = BitmapFactory.decodeResource(a.getResources(), path, options);
another good and complete answer about image scaling/processing can be found at Quality problems when resizing an image at runtime
I try to load jpeg resource image to Bitmap of ARGB_8888 format:
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap b = BitmapFactory.decodeResource(resources, resId, opts);
Log.d("test", b.getConfig().toString());
Here resId is the id of a jpeg image resource.
And the output is "RGB_565".
Tried this in emulators of android 2.2 and 2.3.
Docs of 'inPreferredConfig' say:
If this is non-null, the decoder will try to decode into this internal
configuration. If it is null, or the request cannot be met, the
decoder will try to pick the best matching config based on the
system's screen depth, and characteristics of the original image such
as if it has per-pixel alpha (requiring a config that also does).
Image are loaded with the ARGB_8888 config by default.
So am I hitting the case of "the request cannot be met"? :)
But I honestly can't see how it is very difficult to decode RGB_565 into an ARGB_8888.
So I thought maybe I am doing wrong or this is a bug in Android...
When loading jpeg you must set alpha channel to true:
bitmap.setHasAlpha(true);
After looking through the source I can only tell that the decision to follow the defined inPreferredConfig is done in native code. ref: source
I would assume because a jpg cannot have an alpha channel that it is decoding it RGB_565 because it is the most efficient config for a non-alpha image. If you really want it to be decoded to ARGB_8888 convert the image into a png.