I have inputted an image from file and stored it in a int array
private int[] dataBuffInt;
BufferedImage image;
image = ImageIO.read(getClass().getResource("/res/" + type + ".png"));
int w = image.getWidth();
int h = image.getHeight();
this.dataBuffInt = image.getRGB(0, 0, w, h, null, 0, w);
I now want to take that array and pass it over to generate and bind my texture, however I am stuck as GLuint does not work for me.
GL11.glGenTextures(tex);
for (int i = 0; i < tex.length; i++) {
GL11.glBindTexture(GL_TEXTURE_2D, tex[i]); }
This is where I am at I assume the for loop is completely wrong, I feel I need to get the colour from the int[] using Color c = new Color(dataBuffInt[100]) with c.getRed() etc but I am lost.
I hope someone understands my intentions, I am simply trying to read an image from a file and place it on screen.
There are no exceptions for reading the file in and the values are being successfully printed out for RGBA. I assume the Gen/Bind process has gone wrong.
Related
I have the following code to read a black-white picture in java.
imageg = ImageIO.read(new File(path));
BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_USHORT_GRAY);
Graphics g = bufferedImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
int w = img.getWidth();
int h = img.getHeight();
int[][] array = new int[w][h];
for (int j = 0; j < w; j++) {
for (int k = 0; k < h; k++) {
array[j][k] = img.getRGB(j, k);
System.out.print(array[j][k]);
}
}
As you can see I have set the type of BufferedImage into TYPE_USHORT_GRAY and I expect that I see the numbers between 0 and 255 in the two D array mattrix. but I will see '-1' and another large integer. Can anyone highlight my mistake please?
As already mentioned in comments and answers, the mistake is using the getRGB() method which converts your pixel values to packed int format in default sRGB color space (TYPE_INT_ARGB). In this format, -1 is the same as ยด0xffffffff`, which means pure white.
To access your unsigned short pixel data directly, try:
int w = img.getWidth();
int h = img.getHeight();
DataBufferUShort buffer = (DataBufferUShort) img.getRaster().getDataBuffer(); // Safe cast as img is of type TYPE_USHORT_GRAY
// Conveniently, the buffer already contains the data array
short[] arrayUShort = buffer.getData();
// Access it like:
int grayPixel = arrayUShort[x + y * w] & 0xffff;
// ...or alternatively, if you like to re-arrange the data to a 2-dimensional array:
int[][] array = new int[w][h];
// Note: I switched the loop order to access pixels in more natural order
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
array[x][y] = buffer.getElem(x + y * w);
System.out.print(array[x][y]);
}
}
// Access it like:
grayPixel = array[x][y];
PS: It's probably still a good idea to look at the second link provided by #blackSmith, for proper color to gray conversion. ;-)
A BufferedImage of type TYPE_USHORT_GRAY as its name says stores pixels using 16 bits (size of short is 16 bits). The range 0..255 is only 8 bits, so the colors may be well beyond 255.
And BufferedImage.getRGB() does not return these 16 pixel data bits but quoting from its javadoc:
Returns an integer pixel in the default RGB color model (TYPE_INT_ARGB) and default sRGB colorspace.
getRGB() will always return the pixel in RGB format regardless of the type of the BufferedImage.
I'm coding a Java LWJGL game, and everything's going along great, except whenever I try to figure out a way to create a BufferedImage of the current game area. I've searched the internet, browsed all of the opengl functions, and I am getting no where... Anyone have any ideas? Here's all I have so far, but it only makes a blank .png:
if(Input.getKeyDown(Input.KEY_F2)) {
try {
String fileName = "screenshot-" + Util.getSystemTime(false);
File imageToSave = new File(MainComponent.screenshotsFolder, fileName + ".png");
int duplicate = 0;
while(true) {
duplicate++;
if(imageToSave.exists() == false) {
imageToSave.createNewFile();
break;
}
imageToSave = new File(MainComponent.screenshotsFolder, fileName + "_" + duplicate + ".png");
}
imageToSave.createNewFile();
// Create a buffered image:
BufferedImage image = new BufferedImage(MainComponent.WIDTH, MainComponent.HEIGHT, BufferedImage.TYPE_INT_ARGB);
//Wrtie the new buffered image to file:
ImageIO.write(image, "png", imageToSave);
} catch (IOException e) {
e.printStackTrace();
}
}
You never actually write something into your BufferedImage.
Read the Buffer
You can use glReadPixels to access the selected buffer. (I assume WIDTH and HEIGHT as your OpenGLContext dimensions.)
FloatBuffer imageData = BufferUtils.createFloatBuffer(WIDTH * HEIGHT * 3);
GL11.glReadPixels(0, 0, WIDTH, HEIGHT, GL11.GL_RGB, GL11.GL_FLOAT, imageData);
imageData.rewind();
Use whatever parameters suit your needs best, I just picked floats randomly.
Set the Image Data
You already figured out how to create and save your image, but in between you should also set some content to the image. You can do this with BufferedImage().setRGB() (Note that I don't use a good naming as you do, to keep this example concise.)
// create image
BufferedImage image = new BufferedImage(
WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB
);
// set content
image.setRGB(0, 0, WIDTH, HEIGHT, rgbArray, 0, WIDTH);
// save it
File outputfile = new File("Screenshot.png");
try {
ImageIO.write(image, "png", outputfile);
} catch (IOException e) {
e.printStackTrace();
}
The most tricky part is now getting the rgbArray. The problems are that
OpenGL gives you three values (in this case, i.e. using GL11.GL_RGB), while the BufferedImage expects one value.
OpenGL counts the rows from bottom to top while BufferedImage counts from top to bottom.
Calculate one Integer from three Floats
To get rid of problem one you have to calculate the integer value which fits the three number you get.
I will show this with a simple example, the color red which is (1.0f, 0.0f, 0.0f) in your FloatBuffer.
For the integer value it might be easy to think of numbers in hex values, as you might know from CSS where it's very common to name colors with those. Red would be #ff0000 in CSS or in Java of course 0xff0000.
Colors in RGB with integers are usually represented from 0 to 255 (or 00 to ff in hex), while you use 0 to 1 with floats or doubles. So first you have to map them to the correct range by simply multiplying the values by 255 and casting them to integers:
int r = (int)(fR * 255);
Now you can think of the hex value as just putting those numbers next to each other:
rgb = 255 0 0 = ff 00 00
To achieve this you can bitshift the integer values. Since one hex value (0-f) is 4 byte long, you have to shift the value of green 8 bytes to the left (two hex values) and the value of red 16 bytes. After that you can simply add them up.
int rgb = (r << 16) + (g << 8) + b;
Getting from BottomUp to TopDown
I know the terminology bottom-up -> top-down is not correct here, but it was catchy.
To access 2D data in a 1D array you usually use some formula (this case row-major order) like
int index = offset + (y - yOffset) * stride + (x - xOffset);
Since you want to have the complete image the offsets can be left out and the formula simplified to
int index = y * stride + x;
Of course the stride is simply the WIDTH, i.e. the maximum achievable x value (or in other terms the row length).
The problem you now face is that OpenGL uses the bottom row as row 0 while the BufferedImage uses the top row as row 0. To get rid of that problem just invert y:
int index = ((HEIGHT - 1) - y) * WIDTH + x;
Filling the int[]-array with the Buffer's Data
Now you know how to calculate the rgb value, the correct index and you have all data you need. Let's fill the int[]-array with those information.
int[] rgbArray = new int[WIDTH * HEIGHT];
for(int y = 0; y < HEIGHT; ++y) {
for(int x = 0; x < WIDTH; ++x) {
int r = (int)(imageData.get() * 255) << 16;
int g = (int)(imageData.get() * 255) << 8;
int b = (int)(imageData.get() * 255);
int i = ((HEIGHT - 1) - y) * WIDTH + x;
rgbArray[i] = r + g + b;
}
}
Note three things about this little piece of code.
The size of the array. Obviously it's just WIDTH * HEIGHT and not WIDTH * HEIGHT * 3 as the buffer's size was.
Since OpenGL uses row-major order, you have to use the column value (x) as the inner loop for this 2D array (and of course there are other ways to write this, but this seemed to be the most intuitive one).
Accessing imageData with imageData.get() is probably not the safest way to do it, but since the calculations are carefully done it should do the job just fine. Just remember to flip() or rewind() the buffer before calling get() the first time!
Putting it all together
So with all the information available now we can just put a method saveScreenshot() together.
private void saveScreenshot() {
// read current buffer
FloatBuffer imageData = BufferUtils.createFloatBuffer(WIDTH * HEIGHT * 3);
GL11.glReadPixels(
0, 0, WIDTH, HEIGHT, GL11.GL_RGB, GL11.GL_FLOAT, imageData
);
imageData.rewind();
// fill rgbArray for BufferedImage
int[] rgbArray = new int[WIDTH * HEIGHT];
for(int y = 0; y < HEIGHT; ++y) {
for(int x = 0; x < WIDTH; ++x) {
int r = (int)(imageData.get() * 255) << 16;
int g = (int)(imageData.get() * 255) << 8;
int b = (int)(imageData.get() * 255);
int i = ((HEIGHT - 1) - y) * WIDTH + x;
rgbArray[i] = r + g + b;
}
}
// create and save image
BufferedImage image = new BufferedImage(
WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB
);
image.setRGB(0, 0, WIDTH, HEIGHT, rgbArray, 0, WIDTH);
File outputfile = getNextScreenFile();
try {
ImageIO.write(image, "png", outputfile);
} catch (IOException e) {
e.printStackTrace();
System.err.println("Can not save screenshot!");
}
}
private File getNextScreenFile() {
// create image name
String fileName = "screenshot_" + getSystemTime(false);
File imageToSave = new File(fileName + ".png");
// check for duplicates
int duplicate = 0;
while(imageToSave.exists()) {
imageToSave = new File(fileName + "_" + ++duplicate + ".png");
}
return imageToSave;
}
// format the time
public static String getSystemTime(boolean getTimeOnly) {
SimpleDateFormat dateFormat = new SimpleDateFormat(
getTimeOnly?"HH-mm-ss":"yyyy-MM-dd'T'HH-mm-ss"
);
return dateFormat.format(new Date());
}
I also uploaded a very simple full working example.
I'm using a flood fill algorithm to sort through an image. If it encounters the same color, I want it copy that pixel over into an identically sized array called filled. The array filled is then transformed back into an image and saved as a jpg. However, when I open the jpg, it appears entirely black.
public static void findFace(int[][] image) throws IOException {
int height = image.length;
int width = image[0].length;
Color centerStart = new Color(image[width / 2][height / 2]);
int[][] filled = new int[width][height];
floodFill(width / 2, height / 2, centerStart, image, filled);
//construct the filled array as image. Show if the face was found.
BufferedImage bufferImage2 = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int Pixel = filled[x][y] << 16 | filled[x][y] << 8 | filled[x][y];
bufferImage2.setRGB(x, y, Pixel);
}
}
//save filled array as image file
File outputfile = new File("/home/lily/Pictures/APicaDay/saved.jpg");
ImageIO.write(bufferImage2, "jpg", outputfile);
}
public static int[][] floodFill(int x, int y, Color targetColor, int[][] image, int[][] filled) {
if (image[x][y] != targetColor.getRGB()) {
return filled;
}
filled[x][y] = image[x][y];
floodFill(x - 1, y, targetColor, image, filled);
floodFill(x + 1, y, targetColor, image, filled);
floodFill(x, y - 1, targetColor, image, filled);
floodFill(x, y + 1, targetColor, image, filled);
return filled;
}
bonus question: I would like the flood fill to also accept colors that are similar, but not the exact same, since I'm dealing with a photograph.
The floodFill function you've posted is missing two important elements:
If the area containing the same color as the first pixel extends all the way to the boundary of the image, the function will try to access image at an invalid index. You can fix this by first checking the x and y coordinates of the pixel you are checking, and returning immediately if they are out of bounds.
If there is more than one adjacent pixel of the same color, the function will cause recurse infinitely, since the initial call will call floodFill on the second pixel, which will then proceed to call floodFill on the first pixel, and so on. You need a way to make sure that you only call floodFill on a particular pixel once.
Since you're not observing either of these two symptoms, and you don't observe anything from the resulting image, I guess that the initial pixel's color check is not correct. When you pass an integer to the Color constructor, are you sure that it uses an RBG interpretation of that integer?
Here is my painting method:
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
bi.setRGB(0, 0, width, height, rgbIntArray, 0, width);
ImageIO.write(bi, "bmp", new File("C:/Users/Felipe/Desktop/img2.bmp"));
This is how I populate the rgbIntArray:
rgbIntArray = new int[(rgbArray.length / 3)];
int j = 0;
for (int i = 0; i < rgbArray.length; i += 3)
{
rgbIntArray[j] = unsignedToBytes(rgbArray[i]) +
unsignedToBytes(rgbArray[i + 1]) * 256 +
unsignedToBytes(rgbArray[i + 2]) * 65536;
j++;
}
I tested these values, they seem to be correct.
I think the problem is on the last parameter of setRGB, it asks for the "scanline stride", what to be honest I don`t have a clue what it is. (but I found somewhere it could be the width of the image). I'm assuming the other parameters are correct.
Here are the results:
Original image:
Original image http://i.minus.com/jy7iVQxtghO0l.bmp
Result:
Result http://i.minus.com/jz86D3YkuPPhG.bmp
I will manipulate the image after. I'm just opening and saving the same image.
I don't know how you initialize rgbArray, but each row ends with a black pixel (outside the image). It might represent a new line if you initialized rgbArray by reading the bytes directly from the image file. Or you didn't initialize rgbArray correctly.
The black pixels show on the sheared image as a diagonal line.
You can skip the black pixels at the end of each row by changing this:
bi.setRGB(0, 0, width, height, rgbIntArray, 0, width);
to this:
bi.setRGB(0, 0, width, height, rgbIntArray, 0, width + 1);
The last parameter, width + 1, basically says that if a given row starts at a certain index in the array, then the next row will start at the index which is width + 1 higher in the same array.
I have to do a Java program that contains a panel with an image in it. After the user clicks twice on the image, the program must increase the contrast of the part of the image that is enclosed between these two points and decrease the rest of it. I need some general instructions on how to do this.
I know that I will have to use Java 2D and I know how to increase or decrease the contrast of the image. However, I am not sure how can I separate the image in two parts.
Thanks in advance everybody who answers :)
You can use this piece of code. It splits the image into cells and it does the job very well :)
public static BufferedImage[] splitImage(BufferedImage img, int cols, int rows) {
int wCell = img.getWidth()/cols;
int hCell = img.getHeight()/rows;
int imageBlockIndex = 0;
BufferedImage imgs[] = new BufferedImage[wCell *hCell ];
for(int y = 0; y < rows; y++) {
for(int x = 0; x < cols; x++) {
imgs[imageBlockIndex] = new BufferedImage(wCell , hCell , img.getType());
// Draw only one portion/cell of the image
Graphics2D g = imgs[imageBlockIndex].createGraphics();
g.drawImage(img, 0, 0, wCell , hCell , wCell *x,
hCell *y, wCell *x+wCell , hCell *y+hCell , null);
g.dispose();
imageBlockIndex++;
}
}
return imgs;
}