glReadPixels returns more data than expected - java

I want to save a video of what I am showing with openGL using JOGL. To do this, I am writing my frames to pictures as follows and then, once I have saved all frames I'll use ffmpeg. I know that this is not the best approach but I still don't have much clear how to accelerate with tex2dimage and PBOs. Any help in that direction would be very useful.
Anyway, my problem is that if I run the opengl class it works but, if I call this class from another class, then I see that the glReadPixels is trhowing me an error. It always returns more data to buffer than memory has been allocated to my buffer "pixelsRGB". Does anyone know why?
As an example: width = 1042; height=998. Allocated=3.119.748 glPixels returned=3.121.742
public void display(GLAutoDrawable drawable) {
//Draw things.....
//bla bla bla
t++; //This is a time variable for the animation (it says to me the frame).
//Save frame
int width = drawable.getSurfaceWidth();
int height = drawable.getSurfaceHeight();
ByteBuffer pixelsRGB = Buffers.newDirectByteBuffer(width * height * 3);
gl.glReadPixels(0, 0, width,height, gl.GL_RGB, gl.GL_UNSIGNED_BYTE, pixelsRGB);
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
int[] pixels = new int[width * height];
int firstByte = width * height * 3;
int sourceIndex;
int targetIndex = 0;
int rowBytesNumber = width * 3;
for (int row = 0; row < height; row++) {
firstByte -= rowBytesNumber;
sourceIndex = firstByte;
for (int col = 0; col < width; col++) {
int iR = pixelsRGB.get(sourceIndex++);
int iG = pixelsRGB.get(sourceIndex++);
int iB = pixelsRGB.get(sourceIndex++);
pixels[targetIndex++] = 0xFF000000
| ((iR & 0x000000FF) << 16)
| ((iG & 0x000000FF) << 8)
| (iB & 0x000000FF);
}
}
bufferedImage.setRGB(0, 0, width, height, pixels, 0, width);
File a = new File(t+".png");
ImageIO.write(bufferedImage, "PNG", a);
}
NOTE: With pleluron's answer now it works. The good code is:
public void display(GLAutoDrawable drawable) {
//Draw things.....
//bla bla bla
t++; //This is a time variable for the animation (it says to me the frame).
//Save frame
int width = drawable.getSurfaceWidth();
int height = drawable.getSurfaceHeight();
ByteBuffer pixelsRGB = Buffers.newDirectByteBuffer(width * height * 4);
gl.glReadPixels(0, 0, width,height, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, pixelsRGB);
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
int[] pixels = new int[width * height];
int firstByte = width * height * 4;
int sourceIndex;
int targetIndex = 0;
int rowBytesNumber = width * 4;
for (int row = 0; row < height; row++) {
firstByte -= rowBytesNumber;
sourceIndex = firstByte;
for (int col = 0; col < width; col++) {
int iR = pixelsRGB.get(sourceIndex++);
int iG = pixelsRGB.get(sourceIndex++);
int iB = pixelsRGB.get(sourceIndex++);
sourceIndex++;
pixels[targetIndex++] = 0xFF000000
| ((iR & 0x000000FF) << 16)
| ((iG & 0x000000FF) << 8)
| (iB & 0x000000FF);
}
}
bufferedImage.setRGB(0, 0, width, height, pixels, 0, width);
File a = new File(t+".png");
ImageIO.write(bufferedImage, "PNG", a);
}

The default value of GL_PACK_ALIGNMENT set with glPixelStore is 4. It means that each row of pixelsRGB should start at an address that is a multiple of 4, and the width of your buffer (1042) times the number of bytes in a pixel (3) isn't a multiple of 4. Adding a little padding so the next row starts at a multiple of 4 will make the total byte size of your buffer larger than what you expected.
To fix it, set GL_PACK_ALIGNMENT to 1. You could also read the pixels with GL_RGBA and use a larger buffer, since the data is most likely to be stored that way both on the GPU and in BufferedImage.
Edit: BufferedImage doesn't have a convenient 'setRGBA', too bad.

Related

Detect & remove a range of colors from Java BufferedImage

I'm building an application that uses OCR to read text from an image (using Tess4J for Google's Tesseract), but I want to ignore the tan-colored text and only read the grey.
In the image below, for instance, I only want to read "Ricki" and ignore "AOA".
http://i.imgur.com/daCuTbB.png
To accomplish this, I figured removing the tan color from the image before performing OCR was my best option.
/* Remove RGB Value for Group Tag */
int width = image.getWidth();
int height = image.getHeight();
int[] pixels = new int[width * height];
image.getRGB(0, 0, width, height, pixels, 0, width);
for (int i = 0; i < pixels.length; i++) {
//If pixel is between dark-tan value and light-tan value
if (pixels[i] > 0xFF57513b && pixels[i] < 0xFF6b6145) {
// Set pixel to black
System.out.println("pixel found");
pixels[i] = 0xFF000000;
}
}
image.setRGB(0, 0, width, height, pixels, 0, width);
But this code removes almost all of the grey text as well. You aren't able to simply compare hex color values for a range of values the way I have. Is there another way to approach detecting a range of colors? Or a better different approach to this problem?
haraldK pointed me in the right direction by mentioning converting RGB. Bit shifting to get individual r, g, and b int values from the hex value allowed me to compare the color within a range and black out a range of colors from the image.
int baser = 108; //base red
int baseg = 96; //base green
int baseb = 68; //base blue
int range = 10; //threshold + and - from base values
/* Set all pixels within +- range of base RGB to black */
for (int i = 0; i < pixels.length; i++) {
int a = (pixels[i]>>24) &0xFF; //alpha
int r = (pixels[i]>>16) &0xFF; //red
int g = (pixels[i]>>8) &0xFF; //green
int b = (pixels[i]>>0) &0xFF; //blue
if ( (r > baser-range && r < baser+range) &&
(g > baseg-range && g < baseg+range) &&
(b > baseb-range && b < baseb+range) ) {
pixels[i] = 0xFF000000; //Set to black
}
}

Why BufferedImage is not working well?!! Is it because I misused it?

I want to copy a gray image using BufferedImage from getRGB() to int[][] and then to setRGB(). The problem is that the size of image is different from the size of the one that the program outputs it. The original image has file size = 176 KB, whereas the output image has file size = 154 KB. I have to say that when you see the two image, all of the human-being would say it is the same, but in terms of binary bits, there are different in something that I would like to know.
Maybe some of you will say it doesn't matter, as long as image is the same when you look at it. In fact, during the processing of some noise project, this is a huge problem, and I suspect that this is the reason why I have the problem.
I just want to know if there are other method than BufferedImage to produce int[][] and then to create the output?
This is the code that I'm using:
public int[][] Read_Image(BufferedImage image)
{
width = image.getWidth();
height = image.getHeight();
int[][] result = new int[height][width];
for (int row = 0; row < height; row++)
for (int col = 0; col < width; col++)
result[row][col] = image.getRGB(row, col);
return result;
}
public BufferedImage Create_Gray_Image(int [][] pixels)
{
BufferedImage Ima = new BufferedImage(512,512, BufferedImage.TYPE_BYTE_GRAY);
for (int x = 0; x < 512; x++)
{
for (int y = 0; y < 512; y++)
{
int rgb = pixels[x][y];
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = (rgb & 0xFF);
int grayLevel = (r + g + b) / 3;
int gray = (grayLevel << 16) + (grayLevel << 8) + grayLevel;
Ima.setRGB(x, y, pixels[x][y]);
}
}
return Ima;
}
public void Write_Image(int [][] pixels) throws IOException
{
File outputfile;
outputfile = new File("Y0111.png");
BufferedImage BI = this.Create_Gray_Image(pixels);
ImageIO.write(BI, "png", outputfile);
System.out.println("We finished writing the file");
}
See the figure, you see file size = 176 KB (this is the original image) and file size = 154 KB (this is the output image).
The difference of size is not a problem. It's certainly because of different compression/encoding.
A BufferedImage is in fact a 1D array of size width * height * channel. getRGB is not the easiest/fastest way to manipulate a BufferedImage. You can use the Raster (faster than getRGB, not the fastest, but it takes care of the encoding for you). For a gray level image:
int[][] my array = new int[myimage.getHeight()][myimage.getWidth()] ;
for (int y=0 ; y < myimage.getHeight() ; y++)
for (int x=0 ; x < myimage.getWidth() ; x++)
myarray[y][x] = myimage.getRaster().getSample(x, y, 0) ;
The opposite way:
for (int y=0 ; y < myimage.getHeight() ; y++)
for (int x=0 ; x < myimage.getWidth() ; x++)
myimage.getRaster().setSample(x, y, 0, myarray[y][x]) ;
The fastest way to do it is to use the DataBuffer, but then you have to handle the image encoding.

Why raster.setPixels() is returning a grayscale image

Here i'm trying to do a fastest method to save 3 matrix(R, G and B) into a BufferedImage.
I've found this method here at StackExchange, but it doesn't work for me because the image it's being saved in a grayscale color.
If I'm doing something wrong or if there's a way of doing this faster than bufferimage.setRGB(), please help me. Thanks!
public static BufferedImage array_rasterToBuffer(int[][] imgR,
int[][]imgG, int[][] imgB) {
final int width = imgR[0].length;
final int height = imgR.length;
int numBandas = 3;
int[] pixels = new int[width*height*numBandas];
int cont=0;
System.out.println("max: "+width*height*3);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
for (int band = 0; band < numBandas; band++) {
pixels[(((i*width)+j)*numBandas +band)] =Math.abs(( (imgR[i][j] & 0xff) >> 16 | (imgG[i][j] & 0xff) >> 8 | (imgB[i][j] & 0xff)));
cont+=1;
}
}
}
BufferedImage bufferImg = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);
WritableRaster rast = (WritableRaster) bufferImg.getData();
rast.setPixels(0, 0, width, height, pixels);
bufferImg.setData(rast);
return bufferImg;
}
I think you are getting grey because the expression
Math.abs(( (imgR[i][j] & 0xff) >> 16 | (imgG[i][j] & 0xff) >> 8 | (imgB[i][j] & 0xff)));
does not depend on band, so your rgb values are all the same.
The expression looks dodgy anyway because you normally use the left shift operator << when packing rgb values into a single int.
I don't know for sure, as I'm not familiar with the classes you are using, but I'm guessing something like this might work
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
pixels[(((i*width)+j)*numBandas)] = imgR[i][j] & 0xFF;
pixels[(((i*width)+j)*numBandas + 1)] = imgG[i][j] & 0xFF;
pixels[(((i*width)+j)*numBandas + 2)] = imgB[i][j] & 0xFF;
}
}
If you want a faster approach, you need to get the "live" WritableRaster from the BufferedImage and set pixels in the "native" format of the image, which is "pixel packed" for TYPE_INT_RGB. This will save you multiple (at least two) array copies and some data conversion. It will also save you 2/3rds of the memory used for the conversion, as we only need a single array component per pixel.
The below method should be quite a bit faster:
public static BufferedImage array_rasterToBuffer(int[][] imgR, int[][] imgG, int[][] imgB) {
final int width = imgR[0].length;
final int height = imgR.length;
// The bands are "packed" for TYPE_INT_RGB Raster,
// so we need only one array component per pixel
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// "Pack" RGB values to native TYPE_INT_RGB format
// (NOTE: Do not use Math.abs on these values, and without alpha there won't be negative values)
pixels[((y * width) + x)] = ((imgR[y][x] & 0xff) << 16 | (imgG[y][x] & 0xff) << 8 | (imgB[y][x] & 0xff));
}
}
BufferedImage bufferImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// NOTE: getRaster rather than getData for "live" view
WritableRaster rast = bufferImg.getRaster();
// NOTE: setDataElements rather than setPixels to avoid conversion
// This requires pixels to be in "native" packed RGB format (as above)
rast.setDataElements(0, 0, width, height, pixels);
// No need for setData as we were already working on the live data
// thus saving at least two expensive array copies
return bufferImg;
}
// Test method, displaying red/green/blue stripes
public static void main(String[] args) {
int[][] fooR = new int[99][99];
int[][] fooG = new int[99][99];
int[][] fooB = new int[99][99];
for (int i = 0; i < 33; i++) {
Arrays.fill(fooR[i], 0xff);
Arrays.fill(fooG[i + 33], 0xff);
Arrays.fill(fooB[i + 66], 0xff);
}
BufferedImage image = array_rasterToBuffer(fooR, fooG, fooB);
showIt(image);
}
// For demonstration only
private static void showIt(final BufferedImage image) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("JPEGTest");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JScrollPane scroll = new JScrollPane(new JLabel(new ImageIcon(image)));
scroll.setBorder(BorderFactory.createEmptyBorder());
frame.add(scroll);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
It is possible to optimize this further, if you don't need a "managed" (possible hardware accelerated for display) image. The trick is to create the image directly "around" your pixels array, thus saving one more array allocation and array copy in setDataElements. The downside is that in some cases the image will be a little slower to draw onto the screen. This is mainly a concern for games or smooth animations though.
Replace the lines from BufferedImage bufferImg = new BufferedImage... until the return statement, with the following code:
DataBufferInt buffer = new DataBufferInt(pixels, pixels.length);
int[] bandMasks = {0xFF0000, 0xFF00, 0xFF}; // RGB (no alpha)
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, width, bandMasks, null);
ColorModel cm = new DirectColorModel(32,
0x00ff0000, // Red
0x0000ff00, // Green
0x000000ff, // Blue
0x00000000 // No Alpha
);
BufferedImage bufferImg = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
PS: Note that I also changed the shifts inside the x/y loop, from right to left shifts. Might have been just a minor typo. :-)

Why isn't my BufferedImage being converted to a LWJGL Image properly?

so I'm trying to add TrueTypeFonts to my game. And basically, I've got it to read a TrueTypeFont from a file, and it correctly reads it to a BufferedImage. But when I pass the BufferedImage to my Sprite constructor (for loading images to be used with OpenGL), it completely breaks it. Have a look at the following images:
So here's the constructor for my Sprite utility class, which will take the BufferedImage and convert it to a ByteBuffer and bind it to an ID.
public Sprite(BufferedImage img) {
// this will show the BufferedIamge in a modal dialog, see Fig. 2
JOptionPane.showMessageDialog(null, null, "Another", JOptionPane.YES_NO_OPTION, new ImageIcon(img.getScaledInstance(img.getWidth() * 4, img.getHeight() * 4, Image.SCALE_REPLICATE)));
int[] pixels = new int[img.getWidth() * img.getHeight()];
img.getRGB(0, 0, img.getWidth(), img.getHeight(), pixels, 0, img.getWidth());
this.width = img.getWidth();
this.height = img.getHeight();
ByteBuffer buff = BufferUtils.createByteBuffer(img.getWidth() * img.getHeight() * 4);
for (int x = 0; x < img.getWidth(); x++) {
for (int y = 0; y < img.getHeight(); y++) {
int pixel = pixels[y * img.getWidth() + x];
buff.put((byte) ((pixel >> 16) & 0xFF));
buff.put((byte) ((pixel >> 8) & 0xFF));
buff.put((byte) (pixel & 0xFF));
buff.put((byte) ((pixel >> 24) & 0xFF));
}
}
buff.flip();
id = GL.glGenTextures();
GL.glEnable(GL_TEXTURE_2D);
GL.glBindTexture(GL_TEXTURE_2D, id);
GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
GL.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1);
GL.glTexImage2D(GL_TEXTURE_2D, 0, GL.GL_RGBA, width, height, 0, GL_RGBA, GL.GL_UNSIGNED_BYTE, buff);
}
Fig. 2 - The Image that we pass through, looks good!
Fig. 3 - When I render the image, note I can render any other image and it turns out ok. Even transparent ones, too.
Ignore the blob at the bottom, that's me trying to render a string of text!
Any help greatly appreciated, I've spent a while trying to suss out how to implement fonts.
Without actually running a test project, I would suspect that the reason that it looks like this is that the following code:
for (int x = 0; x < img.getWidth(); x++) {
for (int y = 0; y < img.getHeight(); y++) {
causes the pixels to be added by scanning down first and not across. To go across, flip the for loops:
for (int y = 0; y < img.getHeight(); y++) {
for (int x = 0; x < img.getWidth(); x++) {

Reading pixels from images using Java

I'm trying to convert from RGB to GrayScale Image.
The method that does this task is the following:
public BufferedImage rgbToGrayscale(BufferedImage in)
{
int width = in.getWidth();
int height = in.getHeight();
BufferedImage grayImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
WritableRaster raster = grayImage.getRaster();
int [] rgbArray = new int[width * height];
in.getRGB(0, 0, width, height, rgbArray, 0, width);
int [] outputArray = new int[width * height];
int red, green, blue, gray;
for(int i = 0; i < (height * width); i++)
{
red = (rgbArray[i] >> 16) & 0xff;
green = (rgbArray[i] >> 8) & 0xff;
blue = (rgbArray[i]) & 0xff;
gray = (int)( (0.30 * red) + (0.59 * green) + (0.11 * blue));
if(gray < 0)
gray = 0;
if(gray > 255)
gray = 255;
outputArray[i] = (gray & 0xff);
}
}
raster.setPixels(0, 0, width, height, outputArray);
return grayImage;
}
I have a method that saves the pixels value in a file:
public void writeImageValueToFile(BufferedImage in, String fileName)
{
int width = in.getWidth();
int height = in.getHeight();
try
{
FileWriter fstream = new FileWriter(fileName + ".txt");
BufferedWriter out = new BufferedWriter(fstream);
int [] grayArray = new int[width * height];
in.getRGB(0, 0, width, height, grayArray, 0, width);
for(int i = 0; i < (height * width); i++)
{
out.write((grayArray[i] & 0xff) + "\n");
}
out.close();
} catch (Exception e)
{
System.err.println("Error: " + e.getMessage());
}
}
The problem that I have is that, the RGB value I get from my method, is always bigger than the expected one.
I created an image and I filled it with color 128, 128, 128. According to the first method, if I print the outputArray's data, I get:
r, g, b = 128, 128, 128. Final = 127 ---> correct :D
However, when I called the second method, I got the RGB value 187 which is incorrect.
Any suggestion?
Thanks!!!
Take a look at javax.swing.GrayFilter, it uses the RBGImageFilter class to accomplish the same thing and has very similar implementation. It may make your life simpler.
I'm not an expert at these things but aren't RGB values stored as hex (base16)? If so, theproblem lies in your assumption that the operation & 0xff will cause your int to be stored/handled as base16. It is just a notation and default int usage in strings will always be base10.
int a = 200;
a = a & 0xff;
System.out.println(a);
// output
200
You need to use an explicit base16 toString() method.
System.out.println(Integer.toHexString(200));
// output
c8

Categories