/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008).
* Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org).
* This source code is released under a BSD License, which allows redistribution
* in source and binary form, with or without modification, provided copyright
* and license information are included, and with no warranty or guarantee of
* any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt
*/
package vmm.core3D;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
/**
* This class provides support for "Anaglyph Stereo" rendering. Left- and right-eye views are drawn to separate
* grayscale images. Then these images are composed into a single RGB image in which the left-eye view becomes
* the green color component, the right-eye view becomes the red color component, and the blue color component is
* zero. The resulting composite image can be viewed with red/green or red/blue stereo glasses.
* (There should really be some way of doing all this directly in Java graphics, but support for this
* type of composition does not seem to exist.)
*/
public class StereoComposite {
private int width, height;
private BufferedImage view; // the composite image
private BufferedImage leftEyeView;
private BufferedImage rightEyeView;
private int chunkSize; /* When the composition is done, this is how many pixels are processed in a batch.
A large chunk size uses more memory, but a very small chunk size would be inefficient.
The chunk size is set to a multiple of the width of the image, with a value not greater
than 10000*/
private int[] leftSamples; // These three arrays are used for doing the compositing.
private int[] rightSamples; // leftSamples and rightSamples are used to read pixels from the left and right views.
private int[] viewInts; // This is the actual memory for the composed view's WritableRaster.
/**
* Sets the size of the image that is to be composed. Buffered images are allocated for the left, right,
* and composed views as well as for some arrays that are used to do the compostion. This method
* must be called before any drawing or compositing can be done.
* @param width the width of the image
* @param height the height of the image
*/
public void setSize(int width, int height) {
this.width = width;
this.height = height;
chunkSize = 10000 / width;
if (chunkSize < 1)
chunkSize = 1;
view = leftEyeView = rightEyeView = null;
leftSamples = rightSamples = viewInts = null;
try {
leftEyeView = new BufferedImage(width,height,BufferedImage.TYPE_BYTE_GRAY);
rightEyeView = new BufferedImage(width,height,BufferedImage.TYPE_BYTE_GRAY);
view = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
leftSamples = new int[chunkSize*width];
rightSamples = new int[chunkSize*width];
}
catch (OutOfMemoryError e) {
view = leftEyeView = rightEyeView = null;
leftSamples = rightSamples = null;
throw e;
}
WritableRaster viewRaster = view.getRaster();
DataBufferInt viewData = (DataBufferInt)viewRaster.getDataBuffer();
viewInts = viewData.getData();
}
/**
* Returns the image width, as specified in {@link #setSize(int, int)}. If setSize
* has not yet been called, the return value is 0.
*/
public int getWidth() {
return width;
}
/**
* Returns the image height, as specified in {@link #setSize(int, int)}. If setSize
* has not yet been called, the return value is 0.
*/
public int getHeight() {
return height;
}
/**
* Releases the memory allocated by {@link #setSize(int, int)}. The width and the height are also reset to 0.
*/
public void releaseMemory() {
view = leftEyeView = rightEyeView = null;
leftSamples = rightSamples = viewInts = null;
width = height = 0;
}
/**
* Returns a newly created graphics context for drawing into the left eye view. The {@link #setSize(int, int)} method
* must already have been called to allocate memory for the views. If setSize has not been
* called, the return value is null.
*/
public Graphics2D getLeftEyeGraphics() {
return leftEyeView == null? null : (Graphics2D)leftEyeView.getGraphics();
}
/**
* Returns a newly created graphics context for drawing into the right eye view. The {@link #setSize(int, int)} method
* must already have been called to allocate memory for the views. If setSize has not been
* called, the return value is null.
*/
public Graphics2D getRightEyeGraphics() {
return rightEyeView == null? null : (Graphics2D)rightEyeView.getGraphics();
}
/**
* Returns the BufferedImage where the left-eye view is rendered. If setSize has not been
* called, the return value is null.
*/
public BufferedImage getLeftEyeImage() {
return leftEyeView;
}
/**
* Returns the BufferedImage where the right view is rendered. If setSize has not been
* called, the return value is null.
*/
public BufferedImage getRightEyeImage() {
return rightEyeView;
}
/**
* Returns the BufferedImage that contains the composed view. Note that {@link #compose()} must
* be called in order to combine the left- and righ-eye views into the composite image -- this is
* not done automatically. The return value can be null if {@link #setSize(int, int)} has
* not been called to allocate memory.
*/
public BufferedImage getImage() { // does not do compose -- call compose() before using the image.
return view; // can be null
}
public void compose() {
// long start = System.currentTimeMillis();
Raster leftRaster = leftEyeView.getData();
Raster rightRaster = rightEyeView.getData();
int row = 0;
while (row < height) {
int rowCount = chunkSize;
if (row + rowCount > height)
rowCount = height - row;
int sampleCount = rowCount*width;
int firstIndex = row*width;
leftRaster.getSamples(0,row,width,rowCount,0,leftSamples);
rightRaster.getSamples(0,row,width,rowCount,0,rightSamples);
for (int i = 0; i < sampleCount; i++)
viewInts[i + firstIndex] = (rightSamples[i] << 16) + (leftSamples[i] << 8);
row += chunkSize;
}
// long time = System.currentTimeMillis() - start;
// System.out.println("Compsition took " + time / 1000.0 + " seconds for " + (width*height) + " pixels");
}
// public void compose_old() {
// long start = System.currentTimeMillis();
// Raster leftRaster = leftEyeView.getData();
// DataBufferByte leftData = (DataBufferByte)leftRaster.getDataBuffer();
// byte[] leftBytes = leftData.getData();
// Raster rightRaster = rightEyeView.getData();
// DataBufferByte rightData = (DataBufferByte)rightRaster.getDataBuffer();
// byte[] rightBytes = rightData.getData();
// WritableRaster viewRaster = view.getRaster();
// DataBufferInt viewData = (DataBufferInt)viewRaster.getDataBuffer();
// int[] viewInts = viewData.getData();
// for (int i = 0; i < viewInts.length; i++) {
// int a = leftBytes[i];
// int b = rightBytes[i];
// a &= 0xFF;
// b &= 0xFF;
// viewInts[i] = (a << 16) + (b << 8);
// }
// long time = System.currentTimeMillis() - start;
// System.out.println("Compsition took " + time / 1000.0 + " seconds for " + (width*height) + " pixels");
// }
}