Contents / Previous / Next


Imaging Example Program: Imaging.java

import java.io.*;
import java.nio.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import net.java.games.jogl.*;

class Imaging {

  //////////////// Variables /////////////////////////

  final String defaultImageFilename = 
  new String("duke_wave.gif");
  final String defaultFrameImageFilename = 
  new String("frame.png");

  // Databuffer that holds the loaded image.
  byte[] imgRGBA = null;
  // Image size retrieved durung loading,
  // re-used when image is drawn.
  int imgHeight;
  int imgWidth;

  // To copy the content of the current frame.
  int frameWidth;
  int frameHeight;

  ///////////////// Functions /////////////////////////

  public Imaging() {}

  public void init( int width, int height ) 
    {
      frameWidth  = width;
      frameHeight = height;
      
      File outputFile = new File( defaultFrameImageFilename );
      if( outputFile.exists() ) { outputFile.delete(); }
    }

  ///////////// load Image ////////////////////////////////////

  // Format: int[] of pixels from a Java
  // image with BufferedImage.getRGB()
  // returns data in Java's default ARGB format, meaning
  // the 32-bit ints are packed with 8 bits each of alpha
  // mask (translucency): red, green, and blue, in that
  // order. GL does not define a constant for this color 
  // model.
  //
  // Moreover, the use of int is potentially hazardous on
  // machines with "little-endian" architectures, such as
  // Intel x86 CPUs. In the "little-endian" world, the least
  // significant 16 bits of a 32-bit integer come first,
  // followed by the most significant 16 bits. It is not a
  // problem when we are entirely in Java, but when we pass
  // such an array from Java to the native OpenGL, the
  // endianness can turn what we thought was ARGB into GBAR,
  // meaning our colors and transparency get scrambled.
  //
  // One more problem: OpenGL puts (0,0) at the bottom
  // left. Java images have (0,0) at the upper left, what
  // makes the image look upside down in the OpenGL world.
  //
  // To provide glDrawPixels() with a suitable array, 
  // we need to do the following:
  // 1. Convert to a color model that OpenGL understands.
  // 2. Use a byte array to keep the color values 
  //    properly arranged.
  // 3. Flip the image vertically so that the row order 
  //    puts the bottom of the image on the first row.
  //
  // to flip the image, we define an AffineTransform that
  // moves the image into negative y coefficients, then
  // scales the pixels into a reverse ordering by
  // multiplying their y coefficients by -1, which also
  // moves them back into positive values. This
  // transformation is applied to the BufferedImage
  // offscreen Graphics2D, and the image is drawn into the
  // Graphics2D, picking up the transformation in the
  // process.
  //
  // Objects in the Processing chain:
  // img -> bufImg(raster, colorModel) -> imgBuf -> imgRGBA
  //
  public void loadImage( String filename ) 
    {
      // Load image and get height and width for raster.
      //
      if( filename == null ) {
	filename = new String( defaultImageFilename ); }
      Image img = Toolkit.getDefaultToolkit().
	createImage( filename );
      MediaTracker tracker = new MediaTracker(new Canvas());
      tracker.addImage ( img, 0 );
      try {
	//Starts loading all images tracked by 
	// this media tracker (wait max para ms).
	tracker.waitForAll( 1000 );
      } catch (InterruptedException ie) {
	System.out.println("MediaTracker Exception");
      }
      
      imgHeight = img.getHeight(null);
      imgWidth = img.getWidth(null);
      System.out.println( "Image, width=" + imgWidth + 
			  ", height=" + imgHeight ); //ddd
       
      // Create a raster with correct size,
      // and a colorModel and finally a bufImg.
      //
      WritableRaster raster =
	Raster.createInterleavedRaster( DataBuffer.TYPE_BYTE,
					imgWidth,
					imgHeight,
					4,
					null );
      ComponentColorModel colorModel =
	new ComponentColorModel( ColorSpace.getInstance(ColorSpace.CS_sRGB),
				 new int[] {8,8,8,8},
				 true,
				 false,
				 ComponentColorModel.TRANSLUCENT,
				 DataBuffer.TYPE_BYTE );
      BufferedImage bufImg =
	new BufferedImage (colorModel, // color model
			   raster,
			   false, // isRasterPremultiplied
			   null); // properties
  
      // Filter img into bufImg and perform
      // Coordinate Transformations on the way.
      //
      Graphics2D g = bufImg.createGraphics();
      AffineTransform gt = new AffineTransform();
      gt.translate (0, imgHeight);
      gt.scale (1, -1d);
      g.transform ( gt );
      g.drawImage ( img, null, null );
      // Retrieve underlying byte array (imgBuf) 
      // from bufImg.
      DataBufferByte imgBuf = 
	(DataBufferByte)raster.getDataBuffer();
      imgRGBA = imgBuf.getData();
      g.dispose();
    }


  ///////////// save Image ///////////////////////////////

  private ByteBuffer getFrameData( GL gl, 
				   ByteBuffer pixelsRGB ) 
    {
      // Read Frame back into our ByteBuffer.
      gl.glReadBuffer( GL.GL_BACK );
      gl.glPixelStorei( GL.GL_PACK_ALIGNMENT, 1 );
      gl.glReadPixels( 0, 0, frameWidth, frameHeight, 
		       GL.GL_RGB, GL.GL_UNSIGNED_BYTE, 
		       pixelsRGB );
 
      return pixelsRGB;
    }
  

  private BufferedImage copyFrame( GL gl ) 
    {  
      // Create a ByteBuffer to hold the frame data.
      java.nio.ByteBuffer pixelsRGB = 
	//BufferUtils.newByteBuffer
	ByteBuffer.allocateDirect
	( frameWidth * frameHeight * 3 ); 

      // Get date from frame as ByteBuffer.
      getFrameData( gl, pixelsRGB );

      return transformPixelsRGBBuffer2ARGB_ByHand
	( pixelsRGB );
    }


  // Copies the Frame to an integer array.
  // Do the necessary conversion by hand.
  //
  private BufferedImage transformPixelsRGBBuffer2ARGB_ByHand
  ( ByteBuffer pixelsRGB)
    {
      // Transform the ByteBuffer and get it as pixeldata.

      int[] pixelInts = new int[ frameWidth * frameHeight ];
 
      // Convert RGB bytes to ARGB ints with no transparency. 
      // Flip image vertically by reading the
      // rows of pixels in the byte buffer in reverse 
      // - (0,0) is at bottom left in OpenGL.
      //
      // Points to first byte (red) in each row.
      int p = frameWidth * frameHeight * 3; 
      int q; // Index into ByteBuffer
      int i = 0; // Index into target int[]
      int w3 = frameWidth * 3; // Number of bytes in each row
      for (int row = 0; row < frameHeight; row++) {
	p -= w3;
	q = p;
	for (int col = 0; col < frameWidth; col++) {
	  int iR = pixelsRGB.get(q++);
	  int iG = pixelsRGB.get(q++);
	  int iB = pixelsRGB.get(q++);
	  pixelInts[i++] = 
	    0xFF000000 | ((iR & 0x000000FF) << 16) | 
	    ((iG & 0x000000FF) << 8) | (iB & 0x000000FF);
	}
      }

      // Create a new BufferedImage from the pixeldata.
      BufferedImage bufferedImage = 
	new BufferedImage( frameWidth, frameHeight, 
			   BufferedImage.TYPE_INT_ARGB);
      bufferedImage.setRGB( 0, 0, frameWidth, frameHeight, 
			    pixelInts, 0, frameWidth );

      return bufferedImage;
    }


  // Function returns if filename already exsits.
  // In this way it does not save each frame, when
  // calles from the display() function.
  public void saveFrameAsPNG( GL gl, String fileName ) 
    {
      // Open File
      if( fileName == null ) {
	fileName = new String( defaultFrameImageFilename ); }
      File outputFile = new File( fileName );
      // Do not overwrite existing image file.
      if( outputFile.exists() ) { return; }

      // Write file.
      try {
	javax.imageio.ImageIO.write( 
	  copyFrame( gl ), "PNG", outputFile );
      } catch (IOException e) {
	System.out.println( "Error: ImageIO.write." );
	e.printStackTrace();
      }
    }

  ///////////// draw /////////////////////////////////////

  public void draw( GL gl ) 
    {
      // Load image, if necessary.
      if( imgRGBA == null ) {
	loadImage( defaultImageFilename ); }

      gl.glPushAttrib( GL.GL_DEPTH_BUFFER_BIT );
      gl.glPushAttrib( GL.GL_COLOR_BUFFER_BIT ); {

	gl.glDisable( GL.GL_DEPTH_TEST );
	    
	// enable alpha mask (import from gif sets alpha bits)
	gl.glEnable (GL.GL_BLEND);
	gl.glBlendFunc (GL.GL_SRC_ALPHA, 
			GL.GL_ONE_MINUS_SRC_ALPHA);
        
	// Draw a rectangle under part of image 
	// to prove alpha works.
	gl.glColor4f( .5f, 0.1f, 0.2f, .5f );
	gl.glRecti( 0, 0, 100, 330 );
	gl.glColor3f( 0.0f, 0.0f, 0.0f );


	// Draw image as bytes.
        // gl.glRasterPos2i( 150, 100 );
	gl.glWindowPos2i( 600, 600 );
	gl.glPixelZoom( 1.0f, 1.0f ); // x-factor, y-factor
	gl.glDrawPixels( imgWidth, imgHeight, 
			 gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, 
			 imgRGBA );
	gl.glPixelZoom( -2.0f, 3.0f ); // x-factor, y-factor
	gl.glWindowPos2i( 600, 300 );
	gl.glDrawPixels( imgWidth, imgHeight, 
			 gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, 
			 imgRGBA );

// 	// Draw a rectangle under part of image 
// 	// to prove alpha works.
// 	gl.glColor4f( .5f, 0.1f, 0.2f, .5f );
// 	gl.glRecti( 0, 0, 100, 330 );

	// Copy the Image: FrameBuf to FrameBuf
	gl.glPixelZoom( 1.0f, 1.0f ); // x-factor, y-factor
	gl.glWindowPos2i( 500, 0 );
	gl.glCopyPixels( 400, 300, 500, 600, GL.GL_COLOR );
      
      } gl.glPopAttrib(); 
      gl.glPopAttrib(); 
    }
  

}