在 java OpenGL 中从屏幕(视口)导出图像
Exporting an image from screen(viewport) in java OpenGL
我是 OpenGL 编程的新手。我正在用 OpenGL 制作一个 java 程序。我在里面画了很多立方体。我现在想在我的程序中实现一个屏幕截图功能,但我无法让它工作。情况如下:
- 我使用 FPSanimator 以 60 fps 的速度刷新我的可绘制对象
- 我在显示器里画了几十个立方体。
我在我的面板上添加了一个 KeyListener,如果我按下 alt 键,程序将运行以下方法:
public static void exportImage() {
int[] bb = new int[Constants.PanelSize.width*Constants.PanelSize.height*4];
IntBuffer ib = IntBuffer.wrap(bb);
ib.position(0);
Constants.gl.glPixelStorei(GL2.GL_UNPACK_ALIGNMENT, 1);
Constants.gl.glReadPixels(0,0,Constants.PanelSize.width,Constants.PanelSize.height,GL2.GL_RGBA,GL2.GL_UNSIGNED_BYTE,ib);
System.out.println(Constants.gl.glGetError());
ImageExport.savePixelsToPNG(bb,Constants.PanelSize.width,Constants.PanelSize.height, "imageFilename.png");
}
// Constant is a class which I store all my global variables in static type
控制台输出0,表示没有错误。我把buffer里的内容打印出来,都是0
- 我检查了输出文件,它只有 1kB。
我该怎么办?对于使用 OpenGL 将屏幕内容导出到图像文件,有什么好的建议吗?我听说有几个库可用,但我不知道哪个合适。感谢任何帮助T_T(如果我有任何语法错误,请原谅我...)
您可以使用机器人 class 进行截图:
BufferedImage screenshot = new Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
ImageIO.write(screenshot, "png", new File("screenshot.png"));
有两点需要考虑:
你从屏幕截图,你可以确定你的视口坐标在哪里,这样你就可以只抓到感兴趣的部分。
某些东西可以位于您视口的顶部(另一个 window),因此视口可能会被另一个 window 隐藏,这种情况不太可能发生,但它可能会发生。
当您将缓冲区与 LWJGL 一起使用时,它们几乎总是需要直接分配。 OpenGL 库并不真正了解如何与 Java Arrays™ 交互,并且为了使底层内存操作正常工作,它们需要应用于本地分配(或者,在这种情况下,直接分配)内存。
如果您使用的是 LWJGL 3.x,那很简单:
//Check the math, because for an image array, given that Ints are 4 bytes, I think you can just allocate without multiplying by 4.
IntBuffer ib = org.lwjgl.BufferUtils.createIntBuffer(Constants.PanelSize.width * Constants.PanelSize.height);
如果该功能不可用,这应该足够了:
//Here you actually *do* have to multiply by 4.
IntBuffer ib = java.nio.ByteBuffer.allocateDirect(Constants.PanelSize.width * Constants.PanelSize.height * 4).asIntBuffer();
然后你执行正常的代码:
Constants.gl.glPixelStorei(GL2.GL_UNPACK_ALIGNMENT, 1);
Constants.gl.glReadPixels(0, 0, Constants.PanelSize.width, Constants.PanelSize.height, GL2.GL_RGBA, GL2.GL_UNSIGNED_BYTE, ib);
System.out.println(Constants.gl.glGetError());
int[] bb = new int[Constants.PanelSize.width * Constants.PanelSize.height];
ib.get(bb); //Stores the contents of the buffer into the int array.
ImageExport.savePixelsToPNG(bb, Constants.PanelSize.width, Constants.PanelSize.height, "imageFilename.png");
你可以这样做,假设你正在绘制到默认的帧缓冲区:
protected void saveImage(GL4 gl4, int width, int height) {
try {
GL4 gl4 = GLContext.getCurrentGL().getGL4();
BufferedImage screenshot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics graphics = screenshot.getGraphics();
ByteBuffer buffer = GLBuffers.newDirectByteBuffer(width * height * 4);
gl4.glReadBuffer(GL_BACK);
gl4.glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
graphics.setColor(new Color((buffer.get() & 0xff), (buffer.get() & 0xff),
(buffer.get() & 0xff)));
buffer.get();
graphics.drawRect(w, height - h, 1, 1);
}
}
BufferUtils.destroyDirectBuffer(buffer);
File outputfile = new File("D:\Downloads\texture.png");
ImageIO.write(screenshot, "png", outputfile);
} catch (IOException ex) {
Logger.getLogger(EC_DepthPeeling.class.getName()).log(Level.SEVERE, null, ex);
}
}
本质上,您创建了一个 bufferedImage 和一个直接缓冲区。然后你使用Graphics
将后台缓冲区的内容逐像素渲染到bufferedImage。
您需要一个额外的 buffer.get();
,因为它代表 alpha 值,您还需要 height - h
来翻转图像。
编辑:有你要找的当然要看
您有多种选择:
- 触发一个布尔变量并直接从
display
方法中调用它,最后,当你想要的一切都已呈现时
- 禁用自动缓冲区交换,从关键侦听器调用
display()
方法,读取后台缓冲区并再次启用交换
- 从关键侦听器调用与在显示中调用的代码相同的代码
我是 OpenGL 编程的新手。我正在用 OpenGL 制作一个 java 程序。我在里面画了很多立方体。我现在想在我的程序中实现一个屏幕截图功能,但我无法让它工作。情况如下:
- 我使用 FPSanimator 以 60 fps 的速度刷新我的可绘制对象
- 我在显示器里画了几十个立方体。
我在我的面板上添加了一个 KeyListener,如果我按下 alt 键,程序将运行以下方法:
public static void exportImage() { int[] bb = new int[Constants.PanelSize.width*Constants.PanelSize.height*4]; IntBuffer ib = IntBuffer.wrap(bb); ib.position(0); Constants.gl.glPixelStorei(GL2.GL_UNPACK_ALIGNMENT, 1); Constants.gl.glReadPixels(0,0,Constants.PanelSize.width,Constants.PanelSize.height,GL2.GL_RGBA,GL2.GL_UNSIGNED_BYTE,ib); System.out.println(Constants.gl.glGetError()); ImageExport.savePixelsToPNG(bb,Constants.PanelSize.width,Constants.PanelSize.height, "imageFilename.png"); } // Constant is a class which I store all my global variables in static type
控制台输出0,表示没有错误。我把buffer里的内容打印出来,都是0
- 我检查了输出文件,它只有 1kB。
我该怎么办?对于使用 OpenGL 将屏幕内容导出到图像文件,有什么好的建议吗?我听说有几个库可用,但我不知道哪个合适。感谢任何帮助T_T(如果我有任何语法错误,请原谅我...)
您可以使用机器人 class 进行截图:
BufferedImage screenshot = new Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
ImageIO.write(screenshot, "png", new File("screenshot.png"));
有两点需要考虑:
你从屏幕截图,你可以确定你的视口坐标在哪里,这样你就可以只抓到感兴趣的部分。
某些东西可以位于您视口的顶部(另一个 window),因此视口可能会被另一个 window 隐藏,这种情况不太可能发生,但它可能会发生。
当您将缓冲区与 LWJGL 一起使用时,它们几乎总是需要直接分配。 OpenGL 库并不真正了解如何与 Java Arrays™ 交互,并且为了使底层内存操作正常工作,它们需要应用于本地分配(或者,在这种情况下,直接分配)内存。
如果您使用的是 LWJGL 3.x,那很简单:
//Check the math, because for an image array, given that Ints are 4 bytes, I think you can just allocate without multiplying by 4.
IntBuffer ib = org.lwjgl.BufferUtils.createIntBuffer(Constants.PanelSize.width * Constants.PanelSize.height);
如果该功能不可用,这应该足够了:
//Here you actually *do* have to multiply by 4.
IntBuffer ib = java.nio.ByteBuffer.allocateDirect(Constants.PanelSize.width * Constants.PanelSize.height * 4).asIntBuffer();
然后你执行正常的代码:
Constants.gl.glPixelStorei(GL2.GL_UNPACK_ALIGNMENT, 1);
Constants.gl.glReadPixels(0, 0, Constants.PanelSize.width, Constants.PanelSize.height, GL2.GL_RGBA, GL2.GL_UNSIGNED_BYTE, ib);
System.out.println(Constants.gl.glGetError());
int[] bb = new int[Constants.PanelSize.width * Constants.PanelSize.height];
ib.get(bb); //Stores the contents of the buffer into the int array.
ImageExport.savePixelsToPNG(bb, Constants.PanelSize.width, Constants.PanelSize.height, "imageFilename.png");
你可以这样做,假设你正在绘制到默认的帧缓冲区:
protected void saveImage(GL4 gl4, int width, int height) {
try {
GL4 gl4 = GLContext.getCurrentGL().getGL4();
BufferedImage screenshot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics graphics = screenshot.getGraphics();
ByteBuffer buffer = GLBuffers.newDirectByteBuffer(width * height * 4);
gl4.glReadBuffer(GL_BACK);
gl4.glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
graphics.setColor(new Color((buffer.get() & 0xff), (buffer.get() & 0xff),
(buffer.get() & 0xff)));
buffer.get();
graphics.drawRect(w, height - h, 1, 1);
}
}
BufferUtils.destroyDirectBuffer(buffer);
File outputfile = new File("D:\Downloads\texture.png");
ImageIO.write(screenshot, "png", outputfile);
} catch (IOException ex) {
Logger.getLogger(EC_DepthPeeling.class.getName()).log(Level.SEVERE, null, ex);
}
}
本质上,您创建了一个 bufferedImage 和一个直接缓冲区。然后你使用Graphics
将后台缓冲区的内容逐像素渲染到bufferedImage。
您需要一个额外的 buffer.get();
,因为它代表 alpha 值,您还需要 height - h
来翻转图像。
编辑:有你要找的当然要看
您有多种选择:
- 触发一个布尔变量并直接从
display
方法中调用它,最后,当你想要的一切都已呈现时 - 禁用自动缓冲区交换,从关键侦听器调用
display()
方法,读取后台缓冲区并再次启用交换 - 从关键侦听器调用与在显示中调用的代码相同的代码