我正在编写一个Java LWJGL游戏,一切都很顺利,除非我试图找到一种方法来创建当前游戏区域的BufferedImage。我搜索了互联网,浏览了所有的opengl函数,我没有在哪里......任何人都有任何想法?这是迄今为止我所拥有的一切,但它只是一个空白.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();
}
}
答案 0 :(得分:4)
你永远不会在你的BufferedImage中写点东西。
您可以使用glReadPixels访问所选缓冲区。 (我假设WIDTH和HEIGHT为您的OpenGLContext维度。)
FloatBuffer imageData = BufferUtils.createFloatBuffer(WIDTH * HEIGHT * 3);
GL11.glReadPixels(0, 0, WIDTH, HEIGHT, GL11.GL_RGB, GL11.GL_FLOAT, imageData);
imageData.rewind();
使用最适合您需求的参数,我只是随机选择浮动。
您已经了解了如何创建和保存图像,但在两者之间,您还应该为图像设置一些内容。您可以使用BufferedImage().setRGB()执行此操作(请注意,我没有像您一样使用良好的命名,以保持此示例简洁。)
// 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();
}
最棘手的部分是现在获得rgbArray。问题在于
GL11.GL_RGB
),而BufferedImage则需要一个值。要摆脱问题,你必须计算出适合你得到的三个数字的整数值
我将通过一个简单的例子来展示这一点,红色是FloatBuffer中的(1.0f, 0.0f, 0.0f)
。
对于整数值,可能很容易想到十六进制值中的数字,正如您可能从CSS中知道的那样,用这些数字命名颜色是很常见的。红色在CSS或Java中#ff0000
当然是0xff0000
。
RGB中带有整数的颜色通常表示为0到255(或十六进制中的00到ff),而使用浮点数或双精度时使用0到1。首先,您必须将值乘以255并将它们转换为整数,将它们映射到正确的范围:
int r = (int)(fR * 255);
现在您可以将十六进制值视为将这些数字放在一起:
rgb = 255 0 0 = ff 00 00
要实现此目的,您可以对整数值进行位移。由于一个十六进制值(0-f)长度为4个字节,因此必须将绿色8字节的值向左移动(两个十六进制值),并将红色16字节的值移位。之后,您可以简单地添加它们。
int rgb = (r << 16) + (g << 8) + b;
我知道自下而上的术语 - &gt;自上而下在这里不正确,但它很吸引人。
要访问一维数组中的二维数据,您通常会使用一些公式(例如行主要顺序),如
int index = offset + (y - yOffset) * stride + (x - xOffset);
由于您想拥有完整的图像,因此可以省略偏移,并将公式简化为
int index = y * stride + x;
当然,stride
只是WIDTH
,即最大可实现x
值(或其他方面的行长度)。
你现在面临的问题是OpenGL使用底行作为行0,而BufferedImage使用顶行作为行0.要摆脱这个问题,只需反转y:
int index = ((HEIGHT - 1) - y) * WIDTH + x;
现在您知道如何计算rgb值,正确的索引并获得所需的所有数据。让我们用这些信息填充int [] - 数组。
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;
}
}
注意关于这段小代码的三件事。
WIDTH * HEIGHT
而不是WIDTH * HEIGHT * 3
,因为缓冲区的大小是。imageData.get()
访问imageData可能不是最安全的方法,但由于仔细完成计算,它应该可以正常工作。在第一次致电flip()
之前,请记住rewind()
或get()
缓冲区!因此,现在可以获得所有可用信息,我们可以将方法saveScreenshot()
放在一起。
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());
}
我还上传了一个非常简单的full working example。