How to display HDR picture in Android? - java

I'm doing an android app to decompress, decode and display HDR pictures.
These HDR pictures use 2 bytes per component (A,R,G,B) so one pixel is represented by a 8 bytes value that can only fit with the long type.
I'm using android's Bitmap to display picture as they have a constructor allowing to do HDR by using Bitmap.Config.RGBA_F16:
int width = 1;
int height = 1;
Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.RGBA_F16);
Unfortunately I can't find any way to fill a pixel of the Bitmap. I use the recommended formula but it cannot be used in the setPixel(x,y,color) method of Bitmap because color has to be a int:
long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff);
image.setPixel(0,0,color); //Argument type error
.
I have also tried with Color (which has a HDR compatible method), Paint and Canvas but no Bitmap method accepts them to set only one pixel.
Thanks for any help!

If you need to open an HDR file, such as 16-bit png, and then display it, you can use ImageDecoder.setTargetColorSpace to create bitmap with format Bitmap.Config.RGBA_F16 like so:
File file = new File(...);
ImageDecoder.Source source = ImageDecoder.createSource(file);
Drawable drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
decoder.setTargetColorSpace(ColorSpace.Named.EXTENDED_SRGB);
});
If you need to display HDR image that is stored in memory you can use Bitmap.copyPixelsFromBuffer, as this method allows to set pixels of the bitmap without conversion of color space the way Bitmap.setPixel does. In this case you need to pack 4 channels represented by Half values into long for each pixel, then write these long values into a Buffer and finally copy pixels from the buffer to the bitmap.
LongBuffer buffer = LongBuffer.allocate(width * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// fill pixels values as needed
float r = (float)y / height;
float g = (float)y / height;
float b = (float)y / height;
float a = 1f;
long rBits = Half.halfToShortBits(Half.toHalf(r));
long gBits = Half.halfToShortBits(Half.toHalf(g));
long bBits = Half.halfToShortBits(Half.toHalf(b));
long aBits = Half.halfToShortBits(Half.toHalf(a));
long color = aBits << 48 | bBits << 32 | gBits << 16 | rBits;
buffer.put(color);
}
}
buffer.rewind();
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGBA_F16);
bitmap.copyPixelsFromBuffer(buffer);

Related

Android fast bitmap drawing

I need to optimize this portion of code which consume about 70ms (on my Samsung Galaxy Tab S2 - Android 7.0):
private static final int IR_FRAME_WIDTH = 160;
private static final int IR_FRAME_HEIGHT = 120;
...
final int[] bmp_data = NormalizeBmp(image_data, IR_FRAME_WIDTH, IR_FRAME_HEIGHT, Polarity.WhiteHot);
Bitmap bitmap = Bitmap.createBitmap(IR_FRAME_WIDTH, IR_FRAME_HEIGHT, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(bitmap, 0, 0, null);
final Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
for (int y = 0; y < IR_FRAME_HEIGHT; y++)
for(int x = 0; x < IR_FRAME_WIDTH; x++) {
final int _gray = bmp_data[(y * IR_FRAME_WIDTH) + x];
paint.setColor(Color.rgb(_gray, _gray, _gray));
canvas.drawPoint(x, y, paint);
}
I'm not used to native code so I would prefer not-to use it if there's a standard code way to improve this performances.
I think the code is pretty self-explaining NormalizeBmp create an array of 160x120 integers containing the gray tone to use. I can easily change NormalizeBmp to produce a different color output.
Instead of drawing pixel by pixel over the canvas, what you can do is to create a bitmap directly from the data returned by NormalizeBmp(). All you need is to modify the returned int array to pack the colors in RGB format. Since you are using the same color to set your Paint() in RGB, this would be pretty straightforward. Here's a simple way to pack the color:
retVal[i] = (0xFF << 24) | (retVal[i] & 0xFF)<< 16 | (retVal[i] & 0xFF) << 8 | retVal[i]
and then use the modified array to draw a bitmap using Bitmap.Create(int[] colors, width, height, bitmap_format).

Android bitmap pixels byte array without alpha channel

I'm trying to get an byte array of pixels. I'm using the ARGB_8888 for
decodeByteArray function. The getPixels() or copyPixelsToBuffer(),
return a array in R G B A form. Is it possible to get only R G B from them, without creating a new array and copying bytes that i don't need. I know there is a RGB_565, but it is not optimal for my case where i need a byte per color.
Thanks.
Usecolor=bitmap.getPixel(x,y) to obtain a Color integer at the specified location. Next, use the red(color), green(color) and blue(color) methods from the Color class, which represents each color value in the [0..255] range.
With regards to the alpha channel, one could multiply its ratio into every other color.
Here is an example implementation:
int width = bitmap.getWidth();
int height = bitmap.getHeight();
ByteBuffer b = ByteBuffer.allocate(width*height*3);
for (int y=0;y<height;y++)
for (int x=0;x<width;x++) {
int index = (y*width + x)*3;
int color = bitmap.getPixel(x,y);
float alpha = (float) Color.alpha(color)/255;
b.put(index, (byte) round(alpha*Color.red(color)));
b.put(index+1, (byte) round(alpha*Color.green(color)));
b.put(index+2, (byte) round(alpha*Color.blue(color)));
}
byte[] pixelArray = b.array();

Programmatically generate pixel art from an image

I want to generate a 64x64 pixel art for any image provided by the user. I'm currently lost on how to do it. OpenCV does not seem to provide any such functionality. Any pointers in the right direction would be gladly appreciated.
Edit
Pixel art is a pixelated image of a given image. The screen shot below shows what I've achieved so far.
Basically what I've done is as follows.
Scale the image with aspect ratios maintained so that it would fit within a 64x64 grid
Image sourceImage;
Rectangle imageBound = sourceImage.getBounds();
double sx = (double) 64 / (double) imageBound.width;
double sy = (double) 64 / (double) imageBound.height;
double s = Math.min(sx, sy);
int dx = (int) (s * imageBound.width);
int dy = (int) (s * imageBound.height);
Image scaledImage = new Image(d, sourceImage.getImageData().scaledTo(dx, dy));
Read each pixel from the scaled image and output it on the screen.
Following shows how I extract the color from each pixel. I use SWT framework.
Display d;
ImageData data = scaledImage.getImageData();
for(int i=0; i<data.width; i++) {
for(int j=0; j<data.height; j++) {
int pixel = data.getPixel(i,j);
int red = (pixel & 0x00ff0000) >> 16;
int green = (pixel & 0x0000ff00) >> 8;
int blue = pixel & 0x000000ff;
pixelGrid[i][j].setBackground(new Color(d, red, green, blue));
}
}
But as you can see, there is a major difference in the colors after resizing the image. What I want to know is whether I'm on the correct path to achieve this and if so, how can I retain the actual colors while scaling.
You probably have an image in RGBA format, and are extracting the wrong values from the image (as if it were ARGB). It looks very blue currently, and I fail to see any reds. That should have put you on the right track:
int red = (pixel>>24) & 0xff;
int green = (pixel>>16) & 0xff;
int blue = (pixel>>8) & 0xff;
int alpha = pixel & 0xff; // always 0xff for images without transparency

Android parse base64Binary pgm to Bitmap

I need to convert a base64Binary-string in the format pgm to a bitmap in android. So I don't have an usual base64-encoded Bitmap.
The base64binary-string came from a xml file
<ReferenceImage Type="base64Binary" Format="pgm" WidthPX="309" HeightPX="233" BytesPerPixel="1" >
NDY4Ojo9QEFDRUVHRklLTE9OUFFTU1VWV1hZWltZWVlZWlpbW1xdXmBgYmJjZGNlZWRkZGRlZmZnZ2ZnaWpqa21ub29ubm9vb3BwcHBxcHFyc3FzcnJzcnJydH[...]VlaW1xbWltcXFxcXFxd.
Pattern.compile("<ReferenceImage .*>((?s).*)<\\/ReferenceImage>");
...
String sub = r; //base64binary string pattern-matched from xml file
byte[] decodedString = Base64.decode(sub.getBytes(), Base64.NO_WRAP); //probably wrong decoding (needs to be ASCII to binary?)
Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length); //always null due to wrong byte-array
I think I understand that pgm images are typically stored as ASCII (like in my xml) or binary (0..255). I also think that Base64.decode needs the binary-variant, not the ASCII that I have.
However BitmapFactory.decodeByteArray doesn't understand the decoded byte-array and returns null.
So how can I convert my base64binary-pgm-ASCII-string to a valid byte-array in order to create a valid bitmap?
I think your Base64-decoding is fine. But Android's BitmapFactory probably has no direct support for PGM format. I'm not sure how to add support to it, but it seems you could create a Bitmap using one of the createBitmap(...) factory methods quite easily.
See PGM spec for details on how to parse the header, or see my implementation for Java SE (if you look around, you'll also find a class that supports ASCII reading, if needed).
Could also be that there's no header, and that you can get the height/width from the XML. In that case, dataOffset will be 0 below.
When the header is parsed, you know width, height and where the image data starts:
int width, height; // from header
int dataOffset; // == end of header
// Create pixel array, and expand 8 bit gray to ARGB_8888
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int gray = decodedString[dataOffset + i] & 0xff;
pixels[i] = 0xff000000 | gray << 16 | gray << 8 | gray;
}
}
Bitmap pgm = Bitmap.createBitmap(metrics, pixels, width, height, BitmapConfig.Config. ARGB_8888);
Thanks for your answer!
You solved me the day!
I found a little error in your code, index i is never initialized or incremented.
I corrected your code and tested it, here is my code:
private static Bitmap getBitmapFromPgm(byte[] decodedString, int width, int height, int dataOffset){
// Create pixel array, and expand 8 bit gray to ARGB_8888
int[] pixels = new int[width * height];
int i = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int gray = decodedString[dataOffset + i] & 0xff;
pixels[i] = 0xff000000 | gray << 16 | gray << 8 | gray;
i++;
}
}
Bitmap pgm = Bitmap.createBitmap(pixels, width, height, android.graphics.Bitmap.Config.ARGB_8888);
return pgm;
}

Converting grayscale image pixels to defined scale

I'm looking to use a very crude heightmap I've created in Photoshop to define a tiled isometric grid for me:
Map:
http://i.imgur.com/jKM7AgI.png
I'm aiming to loop through every pixel in the image and convert the colour of that pixel to a scale of my choosing, for example 0-100.
At the moment I'm using the following code:
try
{
final File file = new File("D:\\clouds.png");
final BufferedImage image = ImageIO.read(file);
for (int x = 0; x < image.getWidth(); x++)
{
for (int y = 0; y < image.getHeight(); y++)
{
int clr = image.getRGB(x, y) / 99999;
if (clr <= 0)
clr = -clr;
System.out.println(clr);
}
}
}
catch (IOException ex)
{
// Deal with exception
}
This works to an extent; the black pixel at position 0 is 167 and the white pixel at position 999 is 0. However when I insert certain pixels into the image I get slightly odd results, for example a gray pixel that's very close to white returns over 100 when I would expect it to be in single digits.
Is there an alternate solution I could use that would yield more reliable results?
Many thanks.
Since it's a grayscale map, the RGB parts will all be the same value (with range 0 - 255), so just take one out of the packed integer and find out what percent of 255 it is:
int clr = (int) ((image.getRGB(x, y) & 0xFF) / 255.0 * 100);
System.out.println(clr);
getRGB returns all channels packed into one int so you shouldn't do arithmetic with it. Maybe use the norm of the RGB-vector instead?
for (int x = 0; x < image.getWidth(); ++x) {
for (int y = 0; y < image.getHeight(); ++y) {
final int rgb = image.getRGB(x, y);
final int red = ((rgb & 0xFF0000) >> 16);
final int green = ((rgb & 0x00FF00) >> 8);
final int blue = ((rgb & 0x0000FF) >> 0);
// Norm of RGB vector mapped to the unit interval.
final double intensity =
Math.sqrt(red * red + green * green + blue * blue)
/ Math.sqrt(3 * 255 * 255);
}
}
Note that there is also the java.awt.Color class that can be instantiated with the int returned by getRGB and provides getRed, getGreen and getBlue methods if you don't want to do the bit manipulations yourself.

Categories