I am using code similar to Java - get pixel array from image to get low-level access to pixel data of a BMP image, along the lines of:
BufferedImage image = ImageIO.read(is);
DataBuffer buffer = image.getRaster().getDataBuffer();
byte[] rawPixels = ((DataBufferByte) buffer).getData();
The resulting array is laid bottom to top (ie. its first bytes are the beginning of the last image line), which makes sense considering that BMP files usually have the same layout.
I would like to hide this low-level detail from callers by flipping the lines in this situation. Is there a way I can query the pixels orientation/layout of the loaded BufferedImage?
I have checked the source code of the Java 7 BMPImageReader, and it does translate from bottom-up to top-down order while reading, as I expected it to do. The DataBuffers backing array will thus be in the normal top-down order. I cannot reproduce this behavior using Oracle Java 7 JRE on Windows.
The OP has verified that the problem was indeed in another part of the code, not posted as part of the question.
I think what is described just might be possible, using a special subclass of SampleModel that translates all incoming y-coordinates, but there's no standard method to query for orientation (all Rasters are assumed to be top-down).
Anyway, just for fun, I created some code, to test if it is at all possible. Below is a fully runnable example.
public class SampleModelOrientationTest {
public static void main(String[] args) {
BufferedImage image = new BufferedImage(16, 9, BufferedImage.TYPE_3BYTE_BGR);
WritableRaster raster = image.getRaster();
DataBuffer dataBuffer = raster.getDataBuffer();
SampleModel sampleModel = image.getSampleModel();
QueryingDataBuffer queryBuffer = new QueryingDataBuffer(dataBuffer, sampleModel.getWidth(), sampleModel.getNumDataElements());
sampleModel.getDataElements(0, 0, null, queryBuffer);
System.out.println(queryBuffer.getOrientation());
queryBuffer.resetOrientation();
SampleModel bottomUpSampleModel = new BottomUpSampleModel(sampleModel);
bottomUpSampleModel.getDataElements(0, 0, null, queryBuffer);
System.out.println(queryBuffer.getOrientation());
}
private static class QueryingDataBuffer extends DataBuffer {
enum Orientation {
Undefined,
TopDown,
BottomUp,
Unsupported
}
private final int width;
private final int numDataElements;
private Orientation orientation = Orientation.Undefined;
public QueryingDataBuffer(final DataBuffer dataBuffer, final int width, final int numDataElements) {
super(dataBuffer.getDataType(), dataBuffer.getSize());
this.width = width;
this.numDataElements = numDataElements;
}
#Override public int getElem(final int bank, final int i) {
if (bank == 0 && i < numDataElements && isOrientationUndefinedOrEqualTo(Orientation.TopDown)) {
orientation = Orientation.TopDown;
}
else if (bank == 0 && i >= (size - (width * numDataElements) - numDataElements) && isOrientationUndefinedOrEqualTo(Orientation.BottomUp)) {
orientation = Orientation.BottomUp;
}
else {
// TODO: Expand with more options as apropriate
orientation = Orientation.Unsupported;
}
return 0;
}
private boolean isOrientationUndefinedOrEqualTo(final Orientation orientation) {
return this.orientation == Orientation.Undefined || this.orientation == orientation;
}
#Override public void setElem(final int bank, final int i, final int val) {
}
public final void resetOrientation() {
orientation = Orientation.Undefined;
}
public final Orientation getOrientation() {
return orientation;
}
}
// TODO: This has to be generalized to be used for any BufferedImage type.
// I justy happen to know that 3BYTE_BGR uses PixelInterleavedSampleModel and has BGR order.
private static class BottomUpSampleModel extends PixelInterleavedSampleModel {
public BottomUpSampleModel(final SampleModel sampleModel) {
super(sampleModel.getDataType(), sampleModel.getWidth(), sampleModel.getHeight(),
sampleModel.getNumDataElements(), sampleModel.getNumDataElements() * sampleModel.getWidth(),
new int[] {2, 1, 0} // B, G, R
);
}
#Override public Object getDataElements(final int x, final int y, final Object obj, final DataBuffer data) {
return super.getDataElements(x, getHeight() - 1 - y, obj, data);
}
#Override public int getSample(final int x, final int y, final int b, final DataBuffer data) {
return super.getSample(x, getHeight() - 1 - y, b, data);
}
}
}
Related
I've got a byte array storing 16-bit pixel data from an already-deconstructed DICOM file. What I need to do now is convert/export that pixel data somehow into a TIFF file format. I'm using the imageio-tiff-3.3.2.jar plugin to handle the tiff conversion/header data. But now I need to pack that image data array into a BufferedImage of the original image dimensions so it can be exported to TIFF. But it seems that BufferedImage doesn't support 16-bit images. Is there a way around this problem, such as an external library? Is there another way I can pack that image data into a TIFF image of the original DICOM dimensions? Keep in mind, this process has to be completely lossless. I've looked around and tried out some things for the last few days, but so far nothing has worked for me.
Let me know if you have any questions or if there's anything I can do to clear up any confusion.
EDIT: Intended and Current image
Given your input data of a raw byte array, containing unsigned 16 bit image data, here's two ways to create a BufferedImage.
The first one will be slower, as it involves copying the byte array into a short array. It will also need twice the amount of memory. The upside is that it creates a standard TYPE_USHORT_GRAY BufferedImage, which may be faster to display and may be more compatible.
private static BufferedImage createCopyUsingByteBuffer(int w, int h, byte[] rawBytes) {
short[] rawShorts = new short[rawBytes.length / 2];
ByteBuffer.wrap(rawBytes)
// .order(ByteOrder.LITTLE_ENDIAN) // Depending on the data's endianness
.asShortBuffer()
.get(rawShorts);
DataBuffer dataBuffer = new DataBufferUShort(rawShorts, rawShorts.length);
int stride = 1;
WritableRaster raster = Raster.createInterleavedRaster(dataBuffer, w, h, w * stride, stride, new int[] {0}, null);
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
}
A variant that is much faster (previous version takes 4-5x more time) to create, but results in a TYPE_CUSTOM image, that might be slower to display (it does seem to perform reasonable though, in my tests). It's much faster, and uses very little extra memory, as it does no copying/conversion of the input data at creation time.
Instead, it uses a custom sample model, that has DataBuffer.TYPE_USHORT as transfer type, but uses DataBufferByte as data buffer.
private static BufferedImage createNoCopy(int w, int h, byte[] rawBytes) {
DataBuffer dataBuffer = new DataBufferByte(rawBytes, rawBytes.length);
int stride = 2;
SampleModel sampleModel = new MyComponentSampleModel(w, h, stride);
WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer, null);
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
}
private static class MyComponentSampleModel extends ComponentSampleModel {
public MyComponentSampleModel(int w, int h, int stride) {
super(DataBuffer.TYPE_USHORT, w, h, stride, w * stride, new int[] {0});
}
#Override
public Object getDataElements(int x, int y, Object obj, DataBuffer data) {
if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) {
throw new ArrayIndexOutOfBoundsException("Coordinate out of bounds!");
}
// Simplified, as we only support TYPE_USHORT
int numDataElems = getNumDataElements();
int pixelOffset = y * scanlineStride + x * pixelStride;
short[] sdata;
if (obj == null) {
sdata = new short[numDataElems];
}
else {
sdata = (short[]) obj;
}
for (int i = 0; i < numDataElems; i++) {
sdata[i] = (short) (data.getElem(0, pixelOffset) << 8 | data.getElem(0, pixelOffset + 1));
// If little endian, swap the element order, like this:
// sdata[i] = (short) (data.getElem(0, pixelOffset + 1) << 8 | data.getElem(0, pixelOffset));
}
return sdata;
}
}
If your image looks strange after this conversion, try flipping the endianness, as commented in the code.
And finally, some code to exercise the above:
public static void main(String[] args) {
int w = 1760;
int h = 2140;
byte[] rawBytes = new byte[w * h * 2]; // This will be your input array, 7532800 bytes
ShortBuffer buffer = ByteBuffer.wrap(rawBytes)
// .order(ByteOrder.LITTLE_ENDIAN) // Try swapping the byte order to see sharp edges
.asShortBuffer();
// Let's make a simple gradient, from black UL to white BR
int max = 65535; // Unsigned short max value
for (int y = 0; y < h; y++) {
double v = max * y / (double) h;
for (int x = 0; x < w; x++) {
buffer.put((short) Math.round((v + max * x / (double) w) / 2.0));
}
}
final BufferedImage image = createNoCopy(w, h, rawBytes);
// final BufferedImage image = createCopyUsingByteBuffer(w, h, rawBytes);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(new JScrollPane(new JLabel(new ImageIcon(image))));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
Here's what the output should look like (scaled down to 1/10th):
The easiest thing to do is to create a BufferedImage of type TYPE_USHORT_GRAY, which is type to use for 16 bits encoding.
public BufferedImage Convert(short[] array, final int width, final int height)
{
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY) ;
short[] sb = ((DataBufferUShort) image.getRaster().getDataBuffer()).getData() ;
System.arraycopy(array, 0, sb, 0, array.length) ;
return image ;
}
Then you can use Java.imageio to save your image as a TIFF or a PNG. I think that the Twelve Monkey Project allows a better TIFF support for imageio, but you have to check first.
[EDIT] In your case because you deal with huge DICOM images that cannot be stored into a regular BufferedImage, you have to create your own type using the Unsafe class to allocated the DataBuffer.
Create a new class DataBufferLongShort that will allocate the needed array/DataBuffer using the Unsafe class. Then you can use Long indexes instead of Integer
Create a new class DataBuffer that extends the classical DataBuffer in order to add a type TYPE_LONG_USHORT
Then you can create the ColorModel with the new DataBuffer.
I would like to find the each pixel of image, and then I will get the total of pixel value, then I will find the mean value. I compare the each pixel value with the mean I get, if it is >255, pixel value will become 1 (represent black colour), if <255 then will become 0 (represent white colour). After that I set the new RGB colour, and draw the output image. Input
Based on my concept, I thought the output image will be black and white image, but it just show black in colour. Output
public class Imej {
public void mapping(BufferedImage image) throws IOException {
BufferedImage binary = new BufferedImage(image.getWidth(),
image.getHeight(), BufferedImage.TYPE_BYTE_BINARY);
int i, j;
int w = image.getWidth();
int h = image.getHeight();
image.setRGB(i, j, new Color(pixel[i][j]).getRGB());
ImageIO.write(binary,"png",output);
}
}
This is readimage.
public void readimage() {
BufferedImage image = null;
File f = null;
try {
image = ImageIO.read(new File(/** path **/));
//System.out.println(image);
mapping(image);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public static void main(String[] args) {
Imej a = new Imej();
a.readimage();
}
You should create a mean function, that returns the mean of the pixel. Something like int mean(int[][] pixels, int i, int j). Then you should change the first line in your if statement to be if (mean(pixel, i, j) > mean) { //...
if (pixel[i][j] > mean)
pixel[i][j]=1;
Should be
if (mean(pixel, i, j) > mean)
pixel[i][j]=0xFFFFFF;
I'm recreating the level data structure for my 2D game. Previously I've used large 2D byte arrays for the levels and therefore I was able to keep them inside the memory without any problems, but now as I'm expanding the game and I cannot store all the data inside memory. So I've recreated the level structure like this.
Code for a single tile:
public class Tile {
public static final int SIZE = 16;
private short id;
private short health;
private boolean solid;
...
}
Instead of storing all the tiles into one single array, I split the large array into smaller arrays - chunks:
public class Chunk {
public static final int WIDTH = 16;
public static final int HEIGHT = 16;
private Tile[][] tiles;
private int chunkX;
private int chunkY;
...
}
And finally where I keep the chunks:
public class Map {
public static final int EXTRA_DRAW_WIDTH = 0;
public static final int EXTRA_DRAW_HEIGHT = 0;
private Chunk[][] chunks;
private int width;
private int height;
...
}
The problem I am facing now is that I can't figure out on how to properly store these chunks onto the disk and later on read them one by one as I traverse the level (I want to load only the nearest chunks to the game entities). So far I have tried:
Store each chunk in a separate file. However for larger worlds the file count became way too big, for example 4096 (I have to keep the chunks of small size in order to update as least game entities as possible).
Store all chunks into a single text file, though I could not figure out a fast way on how to get the specific chunks I need.
I've looked into Fast-serialization but couldn't work out on how to read only specific chunks from the file as well. I also ran into some memory problems when using Fast-serialization and Serialization.
Ideally I'd like to have all the chunks inside a single file and so that I could easily specify which ones to load. Are there any libraries or specific methods of doing this?
If you can ensure that every Tile and every Chunk has the same size on disk, you can map a Chunk directly onto a certain position in your file.
Example:
SeekableByteChannel channel;
ByteBuffer chunkBuffer;
public void open(Path path) {
channel = Files.newByteChannel(path, EnumSet.of(READ, WRITE, SPARSE)));
chunkBuffer = ByteBuffer.allocate(Chunk.SIZE);
}
public void close() {
channel.close();
chunkBuffer = null;
}
public void write(Chunk chunk) {
int index = chunkIndex(chunk.getX(), chunk.getY());
chunkBuffer.clear();
chunk.saveInto(chunkBuffer);
chunkBuffer.flip();
channel.position(HEADER_SIZE + Chunk.SIZE * index);
channel.write(chunkBuffer);
}
public Chunk read(int x, int y) {
int index = chunkIndex(x, y);
chunkBuffer.clear();
channel.position(HEADER_SIZE + Chunk.SIZE * index);
if (channel.read(chunkBuffer) < 0) {
/* end-of-file or chunk at given index not written yet */
return null;
} else {
chunkBuffer.flip();
return Chunk.loadFrom(chunkBuffer);
}
}
/** compute linar index of chunk at position x/y */
private int chunkIndex(int x, int y) {
return y * MAX_CHUNKS_X + x;
}
Saving and loading Chunk objects:
public class Chunk {
public static final int WIDTH = 16;
public static final int HEIGHT = 16;
public static final int SIZE = WIDTH * HEIGHT * Tile.SIZE;
private Tile[][] tiles;
public void saveInto(ByteBuffer buf) {
for (int x = 0; x < WIDTH; ++x) {
for (int y = 0; y < HEIGHT; ++y) {
tiles[x][y].saveInto(buf);
}
}
}
public static Chunk loadFrom(ByteBuffer buf) {
Chunk chunk = new Chunk();
for (int x = 0; x < WIDTH; ++x) {
for (int y = 0; y < HEIGHT; ++y) {
tiles[x][y] = Tile.loadFrom(buf);
}
}
}
...
}
Saving and loading Tile objects:
public class Tile {
public static final int SIZE = 16;
private short id;
private short health;
private boolean solid;
public void saveInto(ByteBuffer buf) {
buf.putShort(id);
buf.putShort(health);
buf.put(solid ? 1 : 0);
...
// make sure to always write the same tile size!
// fill up with placeholder if necessary!
}
public static Tile loadFrom(ByteBuffer buf) {
Tile tile = new Tile();
tile.id = buf.getShort();
tile.health = buf.getShort();
tile.solid = buf.get() == 1;
...
}
}
Of course you might add some range checks and proper exception handling!
Backstory:
I'm trying to draw as many squares the the screen as possible using a single draw call. I'm using a custom glsl vertex shader that is specialized for 2D drawing, and that is supposed to be pulling position data for the vertices of the squares from a samplerBuffer. Since I don't need to worry about rotating or scaling the squares all I should need to do is load the position data into a buffer, bind a texture to that buffer, and then use the sampler to get each vertex's position in the shader. In order to get an index into the texture I store each elements index as the z-component of the vertices.
Everything seems to work really well for a thousand or so squares, but after that I start to get weird blinking. It sort of seems like it's not drawing all of the squares every draw step, or possibly not using all of the positions so that many of the squares are overlapping.
The weird thing is, that if I use drawElements instead of drawElementsMulti, the blinking goes away (but of course then all the squares are drawn as one single object, which I don't want)
One question I have is if my position data is limited to the max texture size, or the max texture buffer size. And if I am limited to the much smaller max texture size, how do I get around it? There's got to be a reason all of that texture buffer space is there, but I obviously don't get how to properly use it.
I'm also thinking maybe glMultiDrawElements is doing something I'm not accounting for with the sampler somehow. Idk, I'm really lost at this point, and yet..it works perfectly for smaller numbers of squares, so I must be doing something right.
[EDIT] Code had changed to reflect suggestions below (and for readability), but the problem persists.
Ok, so here's some code. First the vertex shader:
uniform mat3 projection;
attribute vec3 vertex;
uniform samplerBuffer positionSampler;
attribute vec4 vertex_color;
varying vec4 color;
float positionFetch(int index)
{
// I've tried texelFetch here as well, same effect
float value = texelFetchBuffer(positionSampler, index).r;
return value;
}
void main(void)
{
color = vec4(1, 1, 1, 1);
// use the z-component of the vertex to look up the position of this instance in the texture
vec3 real_position = vec3(vertex.x + positionFetch(int(vertex.z)*2), vertex.y + positionFetch(int(vertex.z)*2+1), 1);
gl_Position = vec4(projection * real_position, 1);
}
And now my GLRenderer, sorry there is so much code, I just really want to make sure there's enough info here to get an answer. This has really been driving me nuts, and examples for java seem to be hard to come by (maybe this code will help someone else on their quest):
public class GLRenderer extends GLCanvas implements GLEventListener, WindowListener
{
private static final long serialVersionUID = -8513201172428486833L;
private static final int bytesPerFloat = Float.SIZE / Byte.SIZE;
private static final int bytesPerShort = Short.SIZE / Byte.SIZE;
public float viewWidth, viewHeight;
public float screenWidth, screenHeight;
private FPSAnimator animator;
private boolean didInit = false;
JFrame the_frame;
SquareGeometry geometry;
// Thought power of 2 might be required, doesn't seem to make a difference
private static final int NUM_THINGS = 2*2*2*2*2*2*2*2*2*2*2*2*2*2;
float[] position = new float[NUM_THINGS*2];
// Shader attributes
private int shaderProgram, projectionAttribute, vertexAttribute, positionAttribute;
public static void main(String[] args)
{
new GLRenderer();
}
public GLRenderer()
{
// setup OpenGL Version 2
super(new GLCapabilities(GLProfile.get(GLProfile.GL2)));
addGLEventListener(this);
setSize(1800, 1000);
the_frame = new JFrame("Hello World");
the_frame.getContentPane().add(this);
the_frame.setSize(the_frame.getContentPane().getPreferredSize());
the_frame.setVisible(true);
the_frame.addWindowListener(this);
animator = new FPSAnimator(this, 60);
animator.start();
}
// Called by the drivers when the gl context is first made available
public void init(GLAutoDrawable d)
{
final GL2 gl = d.getGL().getGL2();
IntBuffer asd = IntBuffer.allocate(1);
gl.glGetIntegerv(GL2.GL_MAX_TEXTURE_BUFFER_SIZE, asd);
System.out.println(asd.get(0));
asd = IntBuffer.allocate(1);
gl.glGetIntegerv(GL2.GL_MAX_TEXTURE_SIZE, asd);
System.out.println(asd.get(0));
shaderProgram = ShaderLoader.compileProgram(gl, "default");
gl.glLinkProgram(shaderProgram);
_getShaderAttributes(gl);
gl.glUseProgram(shaderProgram);
_checkGLCapabilities(gl);
_initGLSettings(gl);
// Calculate batch of vertex data from dirt geometry
geometry = new SquareGeometry(.1f);
geometry.buildGeometry(viewWidth, viewHeight);
geometry.finalizeGeometry(NUM_THINGS);
geometry.vertexBufferID = _generateBufferID(gl);
_loadVertexBuffer(gl, geometry);
geometry.indexBufferID = _generateBufferID(gl);
_loadIndexBuffer(gl, geometry);
geometry.positionBufferID = _generateBufferID(gl);
// initialize buffer object
int size = NUM_THINGS * 2 * bytesPerFloat;
System.out.println(size);
IntBuffer bla = IntBuffer.allocate(1);
gl.glGenTextures(1, bla);
geometry.positionTextureID = bla.get(0);
gl.glUniform1i(positionAttribute, 0);
gl.glActiveTexture(GL2.GL_TEXTURE0);
gl.glBindTexture(GL2.GL_TEXTURE_BUFFER, geometry.positionTextureID);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
gl.glBufferData(GL2.GL_TEXTURE_BUFFER, size, null, GL2.GL_DYNAMIC_DRAW);
gl.glTexBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_R32F, geometry.positionBufferID);
}
private void _initGLSettings(GL2 gl)
{
gl.glClearColor(0f, 0f, 0f, 1f);
}
private void _loadIndexBuffer(GL2 gl, SquareGeometry geometry)
{
gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, geometry.indexBufferID);
gl.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, bytesPerShort*NUM_THINGS*geometry.getNumPoints(), geometry.indexBuffer, GL2.GL_STATIC_DRAW);
}
private void _loadVertexBuffer(GL2 gl, SquareGeometry geometry)
{
int numBytes = geometry.getNumPoints() * 3 * bytesPerFloat * NUM_THINGS;
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, geometry.vertexBufferID);
gl.glBufferData(GL2.GL_ARRAY_BUFFER, numBytes, geometry.vertexBuffer, GL2.GL_STATIC_DRAW);
gl.glEnableVertexAttribArray(vertexAttribute);
gl.glVertexAttribPointer(vertexAttribute, 3, GL2.GL_FLOAT, false, 0, 0);
}
private int _generateBufferID(GL2 gl)
{
IntBuffer bufferIDBuffer = IntBuffer.allocate(1);
gl.glGenBuffers(1, bufferIDBuffer);
return bufferIDBuffer.get(0);
}
private void _checkGLCapabilities(GL2 gl)
{
// TODO: Respond to this information in a meaningful way.
boolean VBOsupported = gl.isFunctionAvailable("glGenBuffersARB") && gl.isFunctionAvailable("glBindBufferARB")
&& gl.isFunctionAvailable("glBufferDataARB") && gl.isFunctionAvailable("glDeleteBuffersARB");
System.out.println("VBO Supported: " + VBOsupported);
}
private void _getShaderAttributes(GL2 gl)
{
vertexAttribute = gl.glGetAttribLocation(shaderProgram, "vertex");
projectionAttribute = gl.glGetUniformLocation(shaderProgram, "projection");
positionAttribute = gl.glGetUniformLocation(shaderProgram, "positionSampler");
}
// Called by me on the first resize call, useful for things that can't be initialized until the screen size is known
public void viewInit(GL2 gl)
{
for(int i = 0; i < NUM_THINGS; i++)
{
position[i*2] = (float) (Math.random()*viewWidth);
position[i*2+1] = (float) (Math.random()*viewHeight);
}
gl.glUniformMatrix3fv(projectionAttribute, 1, false, Matrix.projection3f, 0);
// Load position data into a texture buffer
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
ByteBuffer textureBuffer = gl.glMapBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_WRITE_ONLY);
FloatBuffer textureFloatBuffer = textureBuffer.order(ByteOrder.nativeOrder()).asFloatBuffer();
for(int i = 0; i < position.length; i++)
{
textureFloatBuffer.put(position[i]);
}
gl.glUnmapBuffer(GL2.GL_TEXTURE_BUFFER);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, 0);
}
public void display(GLAutoDrawable d)
{
if (!didInit || geometry.vertexBufferID == 0)
{
return;
}
//long startDrawTime = System.currentTimeMillis();
final GL2 gl = d.getGL().getGL2();
gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
// If we were drawing any other buffers here we'd need to set this every time
// but instead we just leave them bound after initialization, saves a little render time
// No combination of these seems to fix the problem
//gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, geometry.vertexBufferID);
//gl.glVertexAttribPointer(vertexAttribute, 3, GL2.GL_FLOAT, false, 0, 0);
//gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, geometry.indexBufferID);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
//gl.glActiveTexture(GL2.GL_TEXTURE0);
//gl.glTexBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_R32F, geometry.positionBufferID);
_render(gl, geometry);
// Also tried these
//gl.glFlush();
//gl.glFinish();
}
public void _render(GL2 gl, SquareGeometry geometry)
{
gl.glMultiDrawElements(geometry.drawMode, geometry.countBuffer, GL2.GL_UNSIGNED_SHORT, geometry.offsetBuffer, NUM_THINGS);
// This one works, but isn't what I want
//gl.glDrawElements(GL2.GL_LINE_LOOP, count, GL2.GL_UNSIGNED_SHORT, 0);
}
public void reshape(GLAutoDrawable d, int x, int y, int width, int height)
{
final GL2 gl = d.getGL().getGL2();
gl.glViewport(0, 0, width, height);
float ratio = (float) height / width;
screenWidth = width;
screenHeight = height;
viewWidth = 100;
viewHeight = viewWidth * ratio;
Matrix.ortho3f(0, viewWidth, 0, viewHeight);
if (!didInit)
{
viewInit(gl);
didInit = true;
}
else
{
// respond to view size changing
}
}
}
The final bit is the SquareGeometry class which holds all the bufferIDs and vertex data, but also is responsible for filling the vertex buffer correctly so that each vertex's z component can function as an index into the position texture:
public class SquareGeometry
{
public float[] vertices = null;
ShortBuffer indexBuffer;
IntBuffer countBuffer;
PointerBuffer offsetBuffer;
FloatBuffer vertexBuffer;
public int vertexBufferID = 0;
public int indexBufferID = 0;
public int positionBufferID = 0;
public int positionTextureID = 0;
public int drawMode;
protected float width = 0;
protected float height = 0;
public SquareGeometry(float size)
{
width = size;
height = size;
}
public void buildGeometry(float viewWidth, float viewHeight)
{
vertices = new float[4 * 2];
vertices[0] = -width/2;
vertices[1] = -height/2;
vertices[2] = -width/2;
vertices[3] = height/2;
vertices[4] = width/2;
vertices[5] = height/2;
vertices[6] = width/2;
vertices[7] = -height/2;
drawMode = GL2.GL_POLYGON;
}
public void finalizeGeometry(int numInstances)
{
if(vertices == null) return;
int num_vertices = this.getNumPoints();
int total_num_vertices = numInstances * num_vertices;
// initialize vertex Buffer (# of coordinate values * 4 bytes per float)
ByteBuffer vbb = ByteBuffer.allocateDirect(total_num_vertices * 3 * Float.SIZE);
vbb.order(ByteOrder.nativeOrder());
vertexBuffer = vbb.asFloatBuffer();
for(int i = 0; i < numInstances; i++)
{
for(int v = 0; v < num_vertices; v++)
{
int vertex_index = v * 2;
vertexBuffer.put(vertices[vertex_index]);
vertexBuffer.put(vertices[vertex_index+1]);
vertexBuffer.put(i);
}
}
vertexBuffer.rewind();
// Create the indices
vbb = ByteBuffer.allocateDirect(total_num_vertices * Short.SIZE);
vbb.order(ByteOrder.nativeOrder());
indexBuffer = vbb.asShortBuffer();
for(int i = 0; i < total_num_vertices; i++)
{
indexBuffer.put((short) (i));
}
indexBuffer.rewind();
// Create the counts
vbb = ByteBuffer.allocateDirect(numInstances * Integer.SIZE);
vbb.order(ByteOrder.nativeOrder());
countBuffer = vbb.asIntBuffer();
for(int i = 0; i < numInstances; i++)
{
countBuffer.put(num_vertices);
}
countBuffer.rewind();
// create the offsets
offsetBuffer = PointerBuffer.allocateDirect(numInstances);
for(int i = 0; i < numInstances; i++)
{
offsetBuffer.put(num_vertices*i*2);
}
offsetBuffer.rewind();
}
public int getNumPoints()
{
return vertices.length/2;
}
}
Ok first things first, you are not setting gl_Color in the shader maybe that can be the issue here and you only lucky with small numbers. It is a varying, but do you also have fragment shader that picks up the value?
At no point do you ensure that NUM_THINGS*2 < GL_MAX_TEXTURE_SIZE. I don't know how FloatBuffer.put reacts; being Java probably / hopefully an exception.
Also you bind the positionBufferID buffer, then unbind it but never rebind it.
You create positionTextureID but never put any data there. This also what you put into the sampler positionSampler and try to access.
Yea well lots of issues but my gut tells me the last one may be the real issue here.
Alright, I've got it solved, though I'm still really not clear on what the original problem was. I fixed it by simplifying the drawing to use drawArrays instead of drawElements or multiDrawElements. I'm really not sure why I thought I needed them, as I really don't in this case. I'm pretty sure I was messing up a few things with the indexes and offsets.
Furthermore, as far as the proper way to bind the texture buffer, neither the code I have above, nor example found at the link I posted in a comment are correct at all.
If anyone is interested in the correct way to use the texture buffer like this I just did a pretty extensive write-up on it here http://zebadiah.me/?p=44. Thanks all for the help.
Situation:
I use Vertex Buffer Objects in OpenGL (LWJGL java binding, GL11) to render rectangles. I can render textures (vertices and texture coordinates) without any problems, also adding color to it leads to the desired effect (like making it semi-translucent). Only using vertices and color makes it invisible.
Researchs:
Disabling alpha test and blending leads to a black rect, which is obviously the vertex and color VBO without alpha testing and blending -> It is rendered.
Disabling only alpha testing leads to the same result like before.
Disabling only blending does also not help.
I can't find any error in my code, neither I can find the source of the problem.
States activated when rendering:
GL_TEXTURE_2D, GL_SMOOTH (Shade Model), GL_DEPTH_TEST, GL_LEQUAL (Depth Func), GL_BACK (Cull Face)
Blend Func: GL_SRC_ALPHA and GL_ONE_MINUS_SRC_ALPHA
Alpha Func: GL_GREATER and 0.1F
Projection: glOrtho(0, 1, 1, 0, 0, 1000);
All rectangles are on the Z axis at -1 (I called once glTranslatef(0,0,-1) before rendering the GUI), on the X and Y axis between 0 and 1. Additionally I have to say that a texture is bound, but this should not be the problem.
Important Code
Note: The Color class basically wraps an RGBA color and has been written by me, so it is not the Color class of the java.awt package.
Note 2: I know that my VertexBufferObject class is not optimized with dynamic draw VBOs, but I could not find time to handle that.
Vertex Buffer Object
private boolean isStatic;
private boolean hasColor;
private boolean hasTexture;
private boolean hasNormals;
private int renderMode;
private int vertices;
private int vertexSize;
private ByteBuffer buffer;
private int vboId;
private boolean isDirty;
public VertexBufferObject (int renderMode, boolean isStatic, boolean hasColor, boolean hasTexture, boolean hasNormals, int vertices) {
this.isStatic = isStatic;
this.hasColor = hasColor;
this.hasTexture = hasTexture;
this.hasNormals = hasNormals;
this.vertices = vertices;
this.renderMode = renderMode;
vertexSize = calculateVertexSize();
}
public void markDirty () {
isDirty = true;
}
public void createBuffer () {
buffer = BufferUtils.createByteBuffer(getVertexSize());
markDirty();
}
public void createVBO () {
buffer.flip();
vboId = ARBVertexBufferObject.glGenBuffersARB();
// Buffer into vbo
ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, vboId);
ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, buffer, (isStatic ? ARBVertexBufferObject.GL_STATIC_DRAW_ARB : ARBVertexBufferObject.GL_DYNAMIC_DRAW_ARB));
// Unbind
ARBVertexBufferObject.glUnmapBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB);
ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, 0);
if (isStatic) buffer = null;
}
public void addVertex (float x, float y, float z) {
buffer.putFloat(x);
buffer.putFloat(y);
buffer.putFloat(z);
}
public void addTextureCoordinates (float u, float v) {
buffer.putFloat(u);
buffer.putFloat(v);
}
public void addColor (Color color) {
addColor(color.getRGBAInt());
}
public void addColor (int rgba) {
buffer.putInt(rgba);
System.out.println(Integer.toBinaryString(rgba));
}
public void addNormal (byte x, byte y, byte z) {
buffer.put(x);
buffer.put(y);
buffer.put(z);
}
public void setVertex (int index, float x, float y, float z) {
index *= vertexSize;
buffer.position(index);
buffer.putFloat(x);
buffer.putFloat(y);
buffer.putFloat(z);
markDirty();
}
public void setTextureCoordinates (int index, float u, float v) {
index *= vertexSize + 12;
buffer.position(index);
buffer.putFloat(u);
buffer.putFloat(v);
markDirty();
}
public void setColor (int index, Color color) {
setColor(index, color.getRGBAInt());
}
public void setColor (int index, int rgba) {
index *= vertexSize + (hasTexture ? 20 : 12);
buffer.position(index);
buffer.putInt(rgba);
markDirty();
}
public void setNormal (int index, byte x, byte y, byte z) {
index *= vertexSize - 3;
buffer.position(index);
buffer.put(x);
buffer.put(y);
buffer.put(z);
markDirty();
}
public void draw () {
draw(0, vertices);
}
public void draw (int start, int vertexNumber) {
if (vboId == 0) return;
ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, vboId);
// Update Dynamic VBO
if (isDirty && !isStatic) {
buffer.position(0);
int vboType = isStatic ? ARBVertexBufferObject.GL_STATIC_DRAW_ARB : ARBVertexBufferObject.GL_DYNAMIC_DRAW_ARB;
ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, 0, vboType);
ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, buffer, vboType);
isDirty = false;
}
// Stride
int stride = 12;
if (hasTexture) stride += 8;
if (hasColor) stride += 4;
if (hasNormals) stride += 3;
// Apply Pointers
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GL11.glVertexPointer(3, GL11.GL_FLOAT, stride, 0);
if (hasTexture) {
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GL11.glTexCoordPointer(2, GL11.GL_FLOAT, stride, 12);
}
if (hasColor) {
GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, stride, hasTexture ? 20 : 12);
}
if (hasNormals) {
GL11.glEnableClientState(GL11.GL_NORMAL_ARRAY);
GL11.glNormalPointer(GL11.GL_BYTE, stride, stride - 3);
}
// Draw with specified render mode
GL11.glDrawArrays(renderMode, start, vertexNumber);
// Unbind VBO
if (hasTexture) GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
if (hasColor) GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
if (hasNormals) GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY);
GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, 0);
}
public void destroy () {
ARBVertexBufferObject.glDeleteBuffersARB(vboId);
}
private int calculateVertexSize () {
return vertices * 12 + vertices * (hasTexture ? 8 : 0) + vertices * (hasColor ? 4 : 0) + vertices * (hasNormals ? 4 : 0);
}
ColorFrameRenderer (The Rectangle, basically)
private VertexBufferObject vbo;
private Color color;
public ColorFrameRenderer(int sizeX, int sizeY, Color color) {
super(sizeX, sizeY);
this.color = color;
}
#Override
public void render() {
if (!render) return;
vbo.draw();
}
#Override
public void init() {
vbo = new VertexBufferObject(GL11.GL_QUADS, false, true, false, false, 4);
vbo.createBuffer();
vbo.addVertex(x, y, 0);
vbo.addColor(color);
vbo.addVertex(x, y + sizeY, 0);
vbo.addColor(color);
vbo.addVertex(x + sizeX, y + sizeY, 0);
vbo.addColor(color);
vbo.addVertex(x + sizeX, y, 0);
vbo.addColor(color);
vbo.createVBO();
}
#Override
public void setPosition (float x, float y, float z) {
super.setPosition(x, y, z);
if (vbo != null) {
vbo.setVertex(0, this.x, this.y, 0);
vbo.setVertex(1, this.x, this.y + sizeY, 0);
vbo.setVertex(2, this.x + sizeX, this.y + sizeY, 0);
vbo.setVertex(3, this.x + sizeX, this.y, 0);
}
}
Thanks for any help and advice!
Please understand that I can not provide the whole code due to a more or less secret project it belongs to.
States activated when rendering:
GL_TEXTURE_2D
I think that's your problem there: If you don't want to texture, disable GL_TEXTURE_…. Otherwise the whole primitive will sample from the last used texture coordinates, possibly a alpha=0 sample.