package org.newdawn.slick.opengl; import java.awt.Color; import java.awt.Graphics2D; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Hashtable; import javax.imageio.ImageIO; /** * An image data provider that uses ImageIO to retrieve image data in a format * suitable for creating OpenGL textures. This implementation is used when * formats not natively supported by the library are required. * * @author kevin */ public class ImageIOImageData implements LoadableImageData { /** The colour model for a image with the RGBA format. */ private static final ColorModel glColorModelRGBA = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8,8,8,8}, true, false, ComponentColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE); /** The colour model for a image with the RGB format. */ private static final ColorModel glColorModelRGB = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8,8,8,0}, false, false, ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE); /** The colour model for a image with the GRAY+ALPHA format. */ private static final ColorModel glColorModelGRAYALPHA = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {8,8,0,0}, true, false, ComponentColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE); /** The colour model for a image with the GRAY format. */ private static final ColorModel glColorModelGRAY = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {8,0,0,0}, false, false, ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE); /** The format of this image */ private Format format; /** The height of the image */ private int height; /** The width of the image */ private int width; /** The width of the texture that should be created for the image */ private int texWidth; /** The height of the texture that should be created for the image */ private int texHeight; /** True if we should edge */ private boolean edging = true; /** * @see org.newdawn.slick.opengl.ImageData#getFormat() */ public Format getFormat() { return format; } /** * @see org.newdawn.slick.opengl.ImageData#getHeight() */ public int getHeight() { return height; } /** * @see org.newdawn.slick.opengl.ImageData#getTexHeight() */ public int getTexHeight() { return texHeight; } /** * @see org.newdawn.slick.opengl.ImageData#getTexWidth() */ public int getTexWidth() { return texWidth; } /** * @see org.newdawn.slick.opengl.ImageData#getWidth() */ public int getWidth() { return width; } /** * @see org.newdawn.slick.opengl.LoadableImageData#loadImage(java.io.InputStream) */ public ByteBuffer loadImage(InputStream fis) throws IOException { return loadImage(fis, true, null); } /** * @see org.newdawn.slick.opengl.LoadableImageData#loadImage(java.io.InputStream, boolean, int[]) */ public ByteBuffer loadImage(InputStream fis, boolean flipped, int[] transparent) throws IOException { return loadImage(fis, flipped, false, transparent); } /** * @see org.newdawn.slick.opengl.LoadableImageData#loadImage(java.io.InputStream, boolean, boolean, int[]) */ public ByteBuffer loadImage(InputStream fis, boolean flipped, boolean forceAlpha, int[] transparent) throws IOException { if (transparent != null) { forceAlpha = true; } BufferedImage bufferedImage = ImageIO.read(fis); return imageToByteBuffer(bufferedImage, flipped, forceAlpha, transparent); } public ByteBuffer imageToByteBuffer(BufferedImage image, boolean flipped, boolean forceAlpha, int[] transparent) { ByteBuffer imageBuffer = null; WritableRaster raster; BufferedImage texImage; int texWidth = 2; int texHeight = 2; // find the closest power of 2 for the width and height // of the produced texture while (texWidth < image.getWidth()) { texWidth *= 2; } while (texHeight < image.getHeight()) { texHeight *= 2; } this.width = image.getWidth(); this.height = image.getHeight(); this.texHeight = texHeight; this.texWidth = texWidth; // create a raster that can be used by OpenGL as a source // for a texture boolean useAlpha = image.getColorModel().hasAlpha() || forceAlpha; boolean isRGB = image.getColorModel().getNumColorComponents() == 3; ColorModel usedModel; if (isRGB) { if (useAlpha) { usedModel = glColorModelRGBA; format = Format.RGBA; } else { usedModel = glColorModelRGB; format = Format.RGB; } } else { if (useAlpha) { usedModel = glColorModelGRAYALPHA; format = Format.GRAYALPHA; } else { usedModel = glColorModelGRAY; format = Format.GRAY; } } raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,texWidth,texHeight,format.getColorComponents(),null); texImage = new BufferedImage(usedModel,raster,false,new Hashtable()); // copy the source image into the produced image Graphics2D g = (Graphics2D) texImage.getGraphics(); // only need to blank the image for mac compatibility if we're using alpha if (useAlpha) { g.setColor(new Color(0f,0f,0f,0f)); g.fillRect(0,0,texWidth,texHeight); } if (flipped) { g.scale(1,-1); g.drawImage(image,0,-height,null); } else { g.drawImage(image,0,0,null); } if (edging) { if (height < texHeight - 1) { copyArea(texImage, 0, 0, width, 1, 0, texHeight-1); copyArea(texImage, 0, height-1, width, 1, 0, 1); } if (width < texWidth - 1) { copyArea(texImage, 0,0,1,height,texWidth-1,0); copyArea(texImage, width-1,0,1,height,1,0); } } // build a byte buffer from the temporary image // that be used by OpenGL to produce a texture. byte[] data = ((DataBufferByte) texImage.getRaster().getDataBuffer()).getData(); if (!format.hasAlpha() && transparent != null) { final int components = format.getColorComponents(); final int size = texWidth*texHeight*components; boolean match; for (int i = 0; i < size; i += components) { match = true; for (int c=0;c<components;c++) { if (toInt(data[i+c]) != transparent[c]) { match = false; break; } } if (match) { data[i+components] = 0; } } } imageBuffer = ByteBuffer.allocateDirect(data.length); imageBuffer.order(ByteOrder.nativeOrder()); imageBuffer.put(data, 0, data.length); imageBuffer.flip(); g.dispose(); return imageBuffer; } /** * Safe convert byte to int * * @param b The byte to convert * @return The converted byte */ private int toInt(byte b) { if (b < 0) { return 256+b; } return b; } /** * @see org.newdawn.slick.opengl.ImageData#getImageBufferData() */ public ByteBuffer getImageBufferData() { throw new RuntimeException("ImageIOImageData doesn't store it's image."); } /** * Implement of transform copy area for 1.4 * * @param image The image to copy * @param x The x position to copy to * @param y The y position to copy to * @param width The width of the image * @param height The height of the image * @param dx The transform on the x axis * @param dy The transform on the y axis */ private void copyArea(BufferedImage image, int x, int y, int width, int height, int dx, int dy) { Graphics2D g = (Graphics2D) image.getGraphics(); g.drawImage(image.getSubimage(x, y, width, height),x+dx,y+dy,null); } /** * @see org.newdawn.slick.opengl.LoadableImageData#configureEdging(boolean) */ public void configureEdging(boolean edging) { this.edging = edging; } }