Java tutorial
/******************************************************************************* * This file is part of the GeOxygene project source files. * * GeOxygene aims at providing an open framework which implements OGC/ISO * specifications for the development and deployment of geographic (GIS) * applications. It is a open source contribution of the COGIT laboratory at the * Institut Gographique National (the French National Mapping Agency). * * See: http://oxygene-project.sourceforge.net * * Copyright (C) 2005 Institut Gographique National * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this library (see file LICENSE if present); if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA *******************************************************************************/ package fr.ign.cogit.geoxygene.appli.gl; // GL import import org.lwjgl.opengl.GL11; //import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL30; //import org.lwjgl.opengl.GL44; // Specific GL import import static org.lwjgl.opengl.GL11.GL_LINEAR; import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER; import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER; import static org.lwjgl.opengl.GL11.glBindTexture; import static org.lwjgl.opengl.GL11.glEnable; import static org.lwjgl.opengl.GL11.glGenTextures; import static org.lwjgl.opengl.GL11.glTexImage2D; import static org.lwjgl.opengl.GL11.glTexParameteri; import static org.lwjgl.opengl.GL20.glUniform1i; import java.net.MalformedURLException; import java.net.URL; // Java import import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.awt.Color; //import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferFloat; import java.awt.image.DataBufferInt; import java.awt.image.DataBufferShort; import java.awt.image.DataBufferDouble; import java.io.ByteArrayOutputStream; import java.io.IOException; import javax.imageio.ImageIO; // Geotools import import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.processing.Operation2D; import org.geotools.coverage.processing.operation.*; // Intern import fr.ign.cogit.geoxygene.style.RasterSymbolizer; import fr.ign.cogit.geoxygene.util.gl.BasicTexture; /** * @author amasse Raster image Do some cool stuff with Raster Image Implemented * : Opacity, Colormap, ChannelSelection */ public class RasterImage extends BasicTexture { private String imageDataType = null; private int GLinternalFormat = GL11.GL_RGBA; private int GLinternalDataType = GL11.GL_UNSIGNED_BYTE; private int format = GL11.GL_RGB; private boolean isRead = false; private int width = 0; private int height = 0; private int nbBands = 0; private int nbBandsSelected = 0; private int[] bandsSelected = null; private int size = 0; // width * height private ByteBuffer bufferImage; // colormap private boolean defColormap = false; private ImageColormap imageColormap = null; // animation private int animate = 0; // Tide information private boolean defTide = false; private double waterHeightMean = 0.0; private double tideRange = 0.0; private double timeAcceleration = 1.0; private double tideCycleLength = 43200.0; private double tidePhase = 0.0; /** * Constructor */ public RasterImage() { super(); } /** * Constructor with an image to read * * @param textureFilename */ public RasterImage(final String imageFilename) { this(); try { super.setTextureURL(new URL(imageFilename)); } catch (MalformedURLException e) { e.printStackTrace(); } } /** * @return the width */ public int getNbBands() { return this.nbBands; } /** * @return the width */ public int getWidth() { return this.width; } /** * @return the height */ public int getHeight() { return this.height; } /** * @return the size (width*height*nbBands) */ public int getSize() { return this.size; } /** * @return the generated texture id */ protected final Integer getImageId() { if (this.textureId < 0) { // Declaration and initialization int target = GL_TEXTURE_2D; int levels = 0; // MipMap disabled // We generate a texture ID this.textureId = glGenTextures(); // We bind the texture glBindTexture(target, this.textureId); // Give the buffer to the GPU glTexImage2D(target, levels, GLinternalFormat, width, height, 0, format, GLinternalDataType, bufferImage); // TODO : MipMap ? // glGenerateMipmap(GL_TEXTURE_2D); // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, // GL_LINEAR_MIPMAP_LINEAR); // Define the rule for image rendering between 2 pixels // Be careful, GL_Linear is not always the solution glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, // GL11.GL_NEAREST); // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, // GL11.GL_NEAREST); // TODO : unload buffer, is it working ? bufferImage.clear(); // Too soon ? // GL44.glClearTexImage(target, levels, format, GLinternalDataType, // bufferImage); } // Return the texture ID so we can bind it later again return this.textureId; } public boolean getDefColormap() { return defColormap; } public boolean getDefTide() { return defTide; } /** * initialize the texture rendering * * @param programId */ public boolean initializeRendering(int programId) { // Enable GL texture // glEnable(GL_TEXTURE_2D); // Go find imageID and pass buffer of data to GPU Integer imageIndex = this.getImageId(); // Very important, activate the texture Slot GL13.glActiveTexture(this.getTextureSlot()); // Send the uniform to shader and bind it // glUniform1i(GL20.glGetUniformLocation(programId, "bufferImage"), imageIndex); glBindTexture(GL_TEXTURE_2D, imageIndex); return true; } /* * (non-Javadoc) * * @see fr.ign.cogit.geoxygene.appli.gl.Texture#finalizeRendering() */ public void finalizeRendering() { // nope // GL11.glDeleteTextures(getImageId()); } /** * read a raster Image * * @param image * @param symbolizer */ public void readImage(GridCoverage2D image, RasterSymbolizer symbolizer) { System.out.println("READING THE RASTER"); // To avoid multiple read if ((isRead == false) || (symbolizer.getChannelSelection() != null)) // &&(imageFilename // != // null)) { // Declaration and initialization // Set width, height, nbBands and compute size width = image.getRenderedImage().getWidth(); height = image.getRenderedImage().getHeight(); nbBands = image.getNumSampleDimensions(); // size of the input image !! size = width * height * nbBands; // Determine data type of image imageDataType = image.getSampleDimension(0).getSampleDimensionType().name(); // Channel Selection // TODO : VALIDATION of channel selection from symbolizer selectBands(image, symbolizer); // Determine the format format = generateFormatFromNbBandSelected(); // Determine the data type GLinternalDataType = generateGLinternalDataTypeFromImageDataType(); // Automatically detect the best GLinternalFormat GLinternalFormat = generateOptimalGLinternalFormat(); // Buffer allocation and acquisition // INFO : bufferImage.order(ByteOrder.nativeOrder()); // to adjust // the ByteBuffer instance's endianness to match the current // platform. if ((GLinternalDataType == GL11.GL_BYTE) || (GLinternalDataType == GL11.GL_UNSIGNED_BYTE)) { byte[] byteData = ((DataBufferByte) image.getRenderedImage().getData().getDataBuffer()).getData(); bufferImage = ByteBuffer.allocateDirect(byteData.length); bufferImage.order(ByteOrder.nativeOrder()); bufferImage.put(ByteBuffer.wrap(byteData)); } else if ((GLinternalDataType == GL11.GL_SHORT) || (GLinternalDataType == GL11.GL_UNSIGNED_SHORT)) { short[] shortData = ((DataBufferShort) image.getRenderedImage().getData().getDataBuffer()) .getData(); bufferImage = ByteBuffer.allocateDirect(shortData.length * 2); bufferImage.order(ByteOrder.nativeOrder()); bufferImage.asShortBuffer().put(ShortBuffer.wrap(shortData)); } else if ((GLinternalDataType == GL11.GL_INT) || (GLinternalDataType == GL11.GL_UNSIGNED_INT)) { int[] intData = ((DataBufferInt) image.getRenderedImage().getData().getDataBuffer()).getData(); bufferImage = ByteBuffer.allocateDirect(intData.length * 4); bufferImage.order(ByteOrder.nativeOrder()); bufferImage.asIntBuffer().put(IntBuffer.wrap(intData)); } else if (GLinternalDataType == GL11.GL_FLOAT) { float[] floatData = ((DataBufferFloat) image.getRenderedImage().getData().getDataBuffer()) .getData(); bufferImage = ByteBuffer.allocateDirect(floatData.length * 4); bufferImage.order(ByteOrder.nativeOrder()); bufferImage.asFloatBuffer().put(FloatBuffer.wrap(floatData)); } else if (GLinternalDataType == GL11.GL_DOUBLE) { double[] doubleData = ((DataBufferDouble) image.getRenderedImage().getData().getDataBuffer()) .getData(); bufferImage = ByteBuffer.allocateDirect(doubleData.length * 8); bufferImage.order(ByteOrder.nativeOrder()); bufferImage.asDoubleBuffer().put(DoubleBuffer.wrap(doubleData)); } else { System.err.println("This type of data : " + GLinternalDataType + "is not recognized."); } // Rewind the buffer, not very with our way-to-put but why not bufferImage.rewind(); // Now, bufferImage is ok, reading is complete isRead = true; // TEMP ?? animation animate = symbolizer.getAnimate(); } } private int generateGLinternalDataTypeFromImageDataType() { if (imageDataType == "REAL_32BITS") { return GL11.GL_FLOAT; } else if (imageDataType == "REAL_64BITS") { return GL11.GL_DOUBLE; } else if (imageDataType == "SIGNED_8BITS") { return GL11.GL_BYTE; } else if (imageDataType == "SIGNED_16BITS") { return GL11.GL_SHORT; } else if (imageDataType == "SIGNED_32BITS") { return GL11.GL_INT; } else if (imageDataType == "UNSIGNED_1BIT") { System.err.println("This type of data : " + imageDataType + "is not implemented."); return -1; /* not done */ } else if (imageDataType == "UNSIGNED_2BITS") { System.err.println("This type of data : " + imageDataType + "is not implemented."); return -1; /* not done */ } else if (imageDataType == "UNSIGNED_4BITS") { System.err.println("This type of data : " + imageDataType + "is not implemented."); return -1; /* not done */ } else if (imageDataType == "UNSIGNED_8BITS") { return GL11.GL_UNSIGNED_BYTE; } else if (imageDataType == "UNSIGNED_16BITS") { return GL11.GL_UNSIGNED_SHORT; } else if (imageDataType == "UNSIGNED_32BITS") { return GL11.GL_UNSIGNED_INT; } else { System.err.println("This type of data : " + imageDataType + "is not recognized."); return -1; } } private int generateFormatFromNbBandSelected() { if (nbBandsSelected == 1) { return GL11.GL_RED; } else if (nbBandsSelected == 2) { return GL30.GL_RG; } else if (nbBandsSelected == 3) { return GL11.GL_RGB; } else if (nbBandsSelected == 4) { return GL11.GL_RGBA; } else { /* nothing */ return -1; } } private void selectBands(GridCoverage2D image, RasterSymbolizer symbolizer) { // Select bands of image from rastersymbolizer if (symbolizer.getChannelSelection() != null) { // TODO : not working yet: // We must read again the initial image and select the bands we want if (symbolizer.getChannelSelection().isGrayChannel()) { nbBandsSelected = 1; bandsSelected = new int[nbBandsSelected]; // INFO : -1 is for conversion to 1...n to 0...n-1 // representation bandsSelected[0] = symbolizer.getChannelSelection().getGrayChannel().getSourceChannelName() - 1; } else if (symbolizer.getChannelSelection().isRGBChannels()) { nbBandsSelected = 3; bandsSelected = new int[nbBandsSelected]; bandsSelected[0] = symbolizer.getChannelSelection().getRedChannel().getSourceChannelName() - 1; bandsSelected[1] = symbolizer.getChannelSelection().getGreenChannel().getSourceChannelName() - 1; bandsSelected[2] = symbolizer.getChannelSelection().getBlueChannel().getSourceChannelName() - 1; } } else { // no explicit ChannelSelection in the SLD, default one ! switch (nbBands) { case 1: nbBandsSelected = 1; bandsSelected = new int[nbBandsSelected]; bandsSelected[0] = 0; break; case 2: nbBandsSelected = 2; bandsSelected = new int[nbBandsSelected]; bandsSelected[0] = 0; bandsSelected[1] = 1; break; case 3: nbBandsSelected = 3; bandsSelected = new int[nbBandsSelected]; bandsSelected[0] = 0; bandsSelected[1] = 1; bandsSelected[2] = 2; break; case 4: nbBandsSelected = 4; bandsSelected = new int[nbBandsSelected]; bandsSelected[0] = 0; bandsSelected[1] = 1; bandsSelected[2] = 2; bandsSelected[3] = 3; break; default: nbBandsSelected = 3; bandsSelected = new int[nbBandsSelected]; bandsSelected[0] = 0; bandsSelected[1] = 1; bandsSelected[2] = 2; } } } /* * Use this function to generate the best GLinternalFormat and thus optimize * GPU memory not easy to do better, because of the GL**.GL_*_* * specification, especially the GL version */ private int generateOptimalGLinternalFormat() { if (defColormap) { // With a colormap, we must keep pixel information in the shader, // only solution, give float to shader if (nbBandsSelected == 1) return GL30.GL_R32F; else if (nbBandsSelected == 2) return GL30.GL_RG32F; else if (nbBandsSelected == 3) return GL30.GL_RGB32F; else return GL30.GL_RGBA32F; } else if (GLinternalDataType == GL11.GL_BYTE) { if (nbBandsSelected == 1) return GL30.GL_R8; else if (nbBandsSelected == 2) return GL30.GL_RG8; else if (nbBandsSelected == 3) return GL11.GL_RGB8; else return GL11.GL_RGBA8; } else if (GLinternalDataType == GL11.GL_SHORT) { if (nbBandsSelected == 1) return GL30.GL_R16; else if (nbBandsSelected == 2) return GL30.GL_RG16; else if (nbBandsSelected == 3) return GL11.GL_RGB16; else return GL11.GL_RGBA16; } else if (GLinternalDataType == GL11.GL_INT) { if (nbBandsSelected == 1) return GL30.GL_R32I; else if (nbBandsSelected == 2) return GL30.GL_RG32I; else if (nbBandsSelected == 3) return GL30.GL_RGB32I; else return GL30.GL_RGBA32I; } else if (GLinternalDataType == GL11.GL_FLOAT) { if (nbBandsSelected == 1) return GL30.GL_R32F; else if (nbBandsSelected == 2) return GL30.GL_RG32F; else if (nbBandsSelected == 3) return GL30.GL_RGB32F; else return GL30.GL_RGBA32F; } else if (GLinternalDataType == GL11.GL_DOUBLE) { // TODO : check that if (nbBandsSelected == 1) return GL30.GL_R32F; else if (nbBandsSelected == 2) return GL30.GL_RG32F; else if (nbBandsSelected == 3) return GL30.GL_RGB32F; else return GL30.GL_RGBA32F; } else if (GLinternalDataType == GL11.GL_UNSIGNED_BYTE) { // TODO check that too if (nbBandsSelected == 1) return GL30.GL_R8; else if (nbBandsSelected == 2) return GL30.GL_RG8; else if (nbBandsSelected == 3) return GL11.GL_RGB8; else return GL11.GL_RGBA8; } else if (GLinternalDataType == GL11.GL_UNSIGNED_SHORT) { if (nbBandsSelected == 1) return GL30.GL_R16; else if (nbBandsSelected == 2) return GL30.GL_RG16; else if (nbBandsSelected == 3) return GL11.GL_RGB16; else return GL11.GL_RGBA16; } else if (GLinternalDataType == GL11.GL_UNSIGNED_INT) { if (nbBandsSelected == 1) return GL30.GL_R32UI; else if (nbBandsSelected == 2) return GL30.GL_RG32UI; else if (nbBandsSelected == 3) return GL30.GL_RGB32UI; else return GL30.GL_RGBA32UI; } else { System.err.println("generateOptimalGLinternalFormat() failed to find the correct format"); return -1; // nothing to do here with that } } public void readColormap(RasterSymbolizer rasterSymbolizer) { // if (rasterSymbolizer.getColorMap() != null) { defColormap = true; if (rasterSymbolizer.getColorMap().getInterpolate() != null) { // Case of "Interpolate" code 1 // TODO : sort the list of points int nb_points = rasterSymbolizer.getColorMap().getInterpolate().getNbInterpolationPoint(); imageColormap = new ImageColormap(1, nb_points); for (int i = 0; i < nb_points; i++) { imageColormap.setValue(i, (float) rasterSymbolizer.getColorMap().getInterpolate() .getInterpolationPoint().get(i).getData()); Color color = rasterSymbolizer.getColorMap().getInterpolate().getInterpolationPoint().get(i) .getColor(); imageColormap.setColor(i, 0, color.getRed()); imageColormap.setColor(i, 1, color.getGreen()); imageColormap.setColor(i, 2, color.getBlue()); imageColormap.setColor(i, 3, color.getAlpha()); } } else if (rasterSymbolizer.getColorMap().getCategorize() != null) { // Case of "Categorize" code 2 int nb_points = rasterSymbolizer.getColorMap().getCategorize().getNbCategorizePoint(); imageColormap = new ImageColormap(2, nb_points); for (int i = 0; i < nb_points; i++) { imageColormap.setValue(i, (float) rasterSymbolizer.getColorMap().getCategorize().getThreshold(i)); Color color = rasterSymbolizer.getColorMap().getCategorize().getColor(i); imageColormap.setColor(i, 0, color.getRed()); imageColormap.setColor(i, 1, color.getGreen()); imageColormap.setColor(i, 2, color.getBlue()); imageColormap.setColor(i, 3, color.getAlpha()); } } else if (rasterSymbolizer.getColorMap().getIntervals() != null) { // Case of "Interpolate" code 3 int nb_points = rasterSymbolizer.getColorMap().getIntervals().getNbIntervalsPoint(); imageColormap = new ImageColormap(3, nb_points); for (int i = 0; i < nb_points; i++) { imageColormap.setValue(i, (float) rasterSymbolizer.getColorMap().getIntervals() .getIntervalsPoint().get(i).getData()); Color color = rasterSymbolizer.getColorMap().getIntervals().getIntervalsPoint().get(i) .getColor(); imageColormap.setColor(i, 0, color.getRed()); imageColormap.setColor(i, 1, color.getGreen()); imageColormap.setColor(i, 2, color.getBlue()); imageColormap.setColor(i, 3, color.getAlpha()); } } else { // colormap defined without categorize or interpolate section defColormap = false; } } else { // No colormap defined defColormap = false; } } public void readTide(RasterSymbolizer rasterSymbolizer) { // Tide information if (rasterSymbolizer.getTide() != null) { defTide = true; waterHeightMean = rasterSymbolizer.getTide().getWaterHeightMean(); tideRange = rasterSymbolizer.getTide().getTideRange(); timeAcceleration = rasterSymbolizer.getTide().getTimeAcceleration(); tideCycleLength = rasterSymbolizer.getTide().getTideCycleLength(); tidePhase = rasterSymbolizer.getTide().getTidePhase(); } else { defTide = false; } } public ImageColormap getImageColorMap() { return imageColormap; } public int getAnimate() { return animate; } public double getTideRange() { return tideRange; } public double getTidePhase() { return tidePhase; } public double getTideCycleLength() { return tideCycleLength; } public double getTimeAcceleration() { return timeAcceleration; } public double getWaterHeightMean() { return waterHeightMean; } }