、昔仕事でお世話になった人から依頼で、JavaでOpenGLを使った調査が必要になったので、メモ。
JavaでOpenGLを使う
ライブラリがいくつかあるらしいが、とりあえず今回はJOGLを使う。
ITPProの「OpenGLを使ってJavaでも3Dを楽しもう」を参考にしたが、記事が2006年と古いのでリンク切れがあったりして微妙。
まぁ、気を取り直して、JOGLのサイトに行って、ITProの記事を見ながらやってたけど、なんITか違う感じがしたので、Googlingで新しめの記事を探す。
- ma38su's blog JOGLことはじめ。(2012年の記事)
- 趣味の記録保存館 JOGLアプレットが動いたー!! (2011年の記事)
- OGL (Java OpenGL) - 3D アプレットの作成まで(2012年の記事)
とりあえず、JOGLことはじめ。を参考に環境を構築する。
- http://jogamp.org/deployment/jogamp-current/archive/からjogamp-all-platforms.7zをダウンロード
- ダウンロードしたファイルを解凍する。どこでもいいらしいけど、Javaなので、日本語や半角スペースを含むパスは避けた方がよいでしょう。
- Eclipseでプロジェクトを作って、クラスパス(ビルドパス)に解凍したフォルダの中から、gluegen.jarとjogl-all.jarを登録する。
- 解凍したフォルダの中のgluegen-rt-natives-xxxx.jarとjoal-natives-xxxx.jarを環境にあったものを選んで、その中のdllをEclipseで作ったプロジェクトにいれる。
あとは、ITProのソースを入力して、実行。
ちなみに、ITProのソースはJOGL2のソースではないので、若干の手直しが発生する。
手直ししたソースは以下の通り。
import java.awt.Frame; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.media.opengl.GL; import javax.media.opengl.GL2; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLEventListener; import javax.media.opengl.awt.GLCanvas; import com.jogamp.opengl.util.gl2.GLUT; public class SimpleCube implements GLEventListener { private GL gl; private GL2 gl2; private GLUT glut; public SimpleCube() { Frame frame = new Frame("Simple Cube"); // 3Dを描画するコンポーネント GLCanvas canvas = new GLCanvas(); canvas.addGLEventListener(this); frame.add(canvas); frame.setSize(300, 300); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.setVisible(true); } public void init(GLAutoDrawable drawable) { // 初期化処理 gl = drawable.getGL(); gl2 = gl.getGL2(); glut = new GLUT(); } public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { // 描画領域変更処理 float ratio = (float)height / (float)width; gl2.glViewport(0, 0, width, height); gl2.glMatrixMode(GL2.GL_PROJECTION); gl2.glLoadIdentity(); gl2.glFrustum(-1.0f, 1.0f, -ratio, ratio, 5.0f, 40.0f); gl2.glMatrixMode(GL2.GL_MODELVIEW); gl2.glLoadIdentity(); gl2.glTranslatef(0.0f, 0.0f, -20.0f); } public void display(GLAutoDrawable drawable) { // 描画処理 gl2.glClear(GL.GL_COLOR_BUFFER_BIT); // 大きさ 2 の線画の立方体を描画 glut.glutWireCube(2.0f); } public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { } public static void main(String[] args) { new SimpleCube(); } @Override public void dispose(GLAutoDrawable arg0) { } }
手直しに参考にしたサイトは以下のところ
http://stackoverflow.com/questions/7606006/cant-use-glloadidentity-as-expected-after-a-translation-in-jogl
OpenGLで作った画像の保存
現在調査中だけど、以下のサイトを参考にしたらできそうな予感。
JOGLの情報ではないけど、OpenGLと同じメソッドが使えるという前提(でないと困る)
ちょこちょこっとやった感じではいけそうだけど、バッファの部分とかをどうしていいかわからない。
とりあえず、glReadPixels()当たりで取ってきたデータをつかってbmpやpng,jpgにできるのではないかと予想。
調べてみると、タイミングによっては真っ暗になるらしい。
で、やってみたのが以下のソース。
うまくかなかったりしたけど、とりあえず、保存できるようになった。が、色がおかしい・・・
import java.awt.Dimension; import java.awt.Frame; import java.awt.Insets; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.Raster; import java.awt.image.SampleModel; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.util.Calendar; import javax.imageio.ImageIO; import javax.media.opengl.GL; import javax.media.opengl.GL2; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLEventListener; import javax.media.opengl.awt.GLCanvas; import com.jogamp.opengl.util.gl2.GLUT; public class SimpleCube implements GLEventListener, KeyListener { private GL gl; private GL2 gl2; private GLUT glut; private boolean isSaveGL = false; private GLCanvas canvas = null; private Frame frame = null; public SimpleCube() { frame = new Frame("Simple Cube"); // 3Dを描画するコンポーネント canvas = new GLCanvas(); canvas.addGLEventListener(this); canvas.addKeyListener(this); frame.add(canvas); frame.setSize(300, 300); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.setVisible(true); } public void init(GLAutoDrawable drawable) { // 初期化処理 gl = drawable.getGL(); gl2 = gl.getGL2(); glut = new GLUT(); } public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { // 描画領域変更処理 float ratio = (float) height / (float) width; // 透視投影(遠近投影法)の行列を指定する。 gl2.glViewport(0, 0, width, height); // 射影行列を設定 gl2.glMatrixMode(GL2.GL_PROJECTION); // 現在選択されている行列に単位行列をロードする。 gl2.glLoadIdentity(); // ビューボリューム(視体積)を指定します。 gl2.glFrustum(-1.0f, 1.0f, -ratio, ratio, 5.0f, 40.0f); // モデルビューの行列を用いて作業する事を指定する。 gl2.glMatrixMode(GL2.GL_MODELVIEW); // 現在選択されている行列に単位行列をロードする。 gl2.glLoadIdentity(); // 物体を平行移動をさせる gl2.glTranslatef(0.0f, 0.0f, -20.0f); // 物体を回転させる場合。 gl2.glRotatef(15, 1.0f, 0.0f, 0.0f); } public void display(GLAutoDrawable drawable) { // 描画処理 gl2.glClear(GL.GL_COLOR_BUFFER_BIT); // 大きさ 2 の線画の立方体を描画 // glut.glutWireCube(2.0f); glut.glutWireTeapot(2.0f); if (isSaveGL == true) { try { Calendar cal = Calendar.getInstance(); long mil = cal.getTimeInMillis(); Dimension dim = frame.getSize(); Insets insets = frame.getInsets(); int imgWidth = dim.width - (insets.left + insets.right); int imgHeight = dim.height - (insets.top + insets.bottom); saveGL("./test1_" + Long.toString(mil) + ".jpg", 300, 300); saveGL("./test2_" + Long.toString(mil) + ".jpg", imgWidth, imgHeight); System.out.println("OK"); } catch (Exception e) { System.out.println("faild"); e.printStackTrace(); } finally { isSaveGL = false; } } } public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { } public static void main(String[] args) { new SimpleCube(); } @Override public void dispose(GLAutoDrawable arg0) { } /** * http://d.hatena.ne.jp/npal_shared/20121107/1352284053 を参考に保存処理を作成 保存は * http://www.geocities.jp/inu_poti/java/meida/image05.html * http://www.metareal * .org/2007/04/02/convert-awt-image-to-buffered-image-or-byte-array/ を参考 * * @param filename */ public void saveGL(String filename, int imageWidth, int imageHeight) { // RGBなら3, RGBAなら4 int channelNum = 4; int allocSize = imageWidth * imageHeight * channelNum; ByteBuffer byteBuffer = ByteBuffer.allocate(allocSize); //gl2.glFlush(); // 読み取るOpneGLのバッファを指定 GL_FRONT:フロントバッファ GL_BACK:バックバッファ gl2.glReadBuffer( GL2.GL_BACK ); // OpenGLで画面に描画されている内容をバッファに格納 gl2.glReadPixels(0, // 読み取る領域の左下隅のx座標 0, // 読み取る領域の左下隅のy座標 imageWidth, // 読み取る領域の幅 imageHeight, // 読み取る領域の高さ GL2.GL_BGRA, // 取得したい色情報の形式 GL2.GL_UNSIGNED_BYTE, // 読み取ったデータを保存する配列の型 (Buffer) byteBuffer // ビットマップのピクセルデータ(実際にはバイト配列)へのポインタ ); printGLError(); // glReadBufferで取得したデータ(ByteBuffer)をDataBufferに変換する byte[] buff = byteBuffer.array(); // データがBGRAをABGR ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] abgrBuff = new byte[4]; try{ for(int y = imageHeight-1; y >= 0; y--){ for(int x = 0; x < imageWidth; x++){ // ここがおかしいので色もおかしい // 0 1 2 3 // from B G R A // to A B G R int offset = imageWidth * y * channelNum; // A abgrBuff[0] = buff[x * channelNum + offset + 3]; // B abgrBuff[1] = buff[x * channelNum + offset + 0]; // G abgrBuff[2] = buff[x * channelNum + offset + 1]; // R abgrBuff[3] = buff[x * channelNum + offset + 2]; baos.write(abgrBuff); } } }catch(IOException ie){ return; } buff = baos.toByteArray(); DataBufferByte dataBuffer = new DataBufferByte(buff, buff.length); // DtaaBuffereをつかって、BufferedImageを作成 BufferedImage imageBuffer = new BufferedImage( imageWidth, imageHeight, BufferedImage.TYPE_4BYTE_ABGR); // BufferedImageのSampleModelを取得 SampleModel sm = imageBuffer.getSampleModel(); // DataBuffereとSampleModelをつかってRasterを作成 Raster raster = Raster.createRaster(sm, dataBuffer, null); // BufferedImageにRasterを設定 imageBuffer.setData(raster); try { // ImageIOクラスを使って、画像を保存 String[] fnSplit = filename.split("\\."); String ext = fnSplit[fnSplit.length - 1].toLowerCase(); String fileType = "bmp"; if(ext.equals("jpg") || ext.equals("jpeg")){ fileType = "jpg"; } else if(ext.equals("gif")){ fileType = "gif"; } else if(ext.equals("bmp")){ fileType = "bmp"; } ImageIO.write(imageBuffer, fileType, new File(filename)); } catch (IOException e1) { e1.printStackTrace(); } } private int printGLError(){ int err = gl2.glGetError(); System.out.println(err); return err; } @Override public void keyPressed(KeyEvent ke) { } @Override public void keyReleased(KeyEvent ke) { switch (ke.getKeyCode()) { case KeyEvent.VK_S: isSaveGL = true; // 再描画要求 canvas.display(); break; } } @Override public void keyTyped(KeyEvent arg0) { } }