/** * Copyright (c) 2007-2011, JAGaToo Project Group all rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the 'Xith3D Project Group' nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.jagatoo.image; import java.awt.Graphics2D; import java.awt.Transparency; 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.PixelInterleavedSampleModel; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.ByteBuffer; import javax.imageio.ImageIO; /** * This is a BufferedImage extension, that uses a DataBuffer, that stores its * data directly in a ByteBuffer.<br> * This allows for minimum memory usage, since no data is stored more than once. * * @author David Yazel * @author Marvin Froehlich (aka Qudus) */ public class DirectBufferedImage extends BufferedImage { public enum Type { DIRECT_RGB, DIRECT_RGBA, DIRECT_TWO_BYTES, DIRECT_ONE_BYTE; } private final Type directType; private int numBytes; //private byte[] data; /* public void setDirectType( Type directType ) { this.directType = directType; } */ public final Type getDirectType() { return ( directType ); } public final int getNumBytes() { return ( numBytes ); } public final ByteBuffer getByteBuffer() { final DirectDataBufferByte dataBuffer = (DirectDataBufferByte)getRaster().getDataBuffer(); return ( dataBuffer.getByteBuffer() ); } private static class DirectWritableRaster extends WritableRaster { /* private final int scanLine; private final int bytesPerPixel; private final ByteBuffer bb; */ private static SampleModel createSampleModel( int width, int height, int bytesPerPixel, int[] bandOffsets ) { PixelInterleavedSampleModel csm = new PixelInterleavedSampleModel( DataBuffer.TYPE_BYTE, width, height, bytesPerPixel, width * bytesPerPixel, bandOffsets ); return ( csm ); } public DirectWritableRaster( int width, int height, int bytesPerPixel, int[] bandOffsets, DirectDataBufferByte dataBuffer ) { super( createSampleModel( width, height, bytesPerPixel, bandOffsets ), dataBuffer, new java.awt.Point( 0, 0 ) ); /* this.scanLine = width * bytesPerPixel; this.bytesPerPixel = bytesPerPixel; this.bb = dataBuffer.getByteBuffer(); */ } } private DirectBufferedImage( Type type/*, byte[] buffer*/, ColorModel model, WritableRaster raster, boolean rasterPremultiplied ) { super( model, raster, rasterPremultiplied, null ); this.directType = type; this.numBytes = raster.getDataBuffer().getSize(); //this.data = buffer; } private static final int[] createBandOffsets( int bytesPerPixel ) { int[] bandOffsets = new int[ bytesPerPixel ]; for ( int i = 0; i < bandOffsets.length; i++ ) { bandOffsets[ i ] = i; } return ( bandOffsets ); } private static final int[] createNumBitsArray( int bytesPerPixel ) { int[] numBits = new int[ bytesPerPixel ]; for ( int i = 0; i < numBits.length; i++ ) { numBits[ i ] = 8; } return ( numBits ); } /** * Creates a buffered image which is backed by a NIO byte buffer * * @param width * @param height * @param bandOffsets * @param bb * @return the made image. */ public static DirectBufferedImage makeDirectImageRGBA( int width, int height, int[] bandOffsets, ByteBuffer bb ) { final int pixelSize = bb.limit() * 8 / ( width * height ); final int bytesPerPixel = pixelSize / 8; //int[] bandOffsets = createBandOffsets( bytesPerPixel ); if ( bandOffsets == null ) bandOffsets = new int[] { 3, 2, 1, 0 }; // create the backing store DirectDataBufferByte buffer = new DirectDataBufferByte( bb ); // build the raster with 4 bytes per pixel WritableRaster newRaster = new DirectWritableRaster( width, height, bytesPerPixel, bandOffsets, buffer ); // create a standard sRGB color space ColorSpace cs = ColorSpace.getInstance( ColorSpace.CS_sRGB ); // create a color model which has three 8 bit values for RGB int[] nBits = createNumBitsArray( bytesPerPixel ); ColorModel cm = new ComponentColorModel( cs, nBits, true, false, Transparency.TRANSLUCENT, 0 ); // create the buffered image DirectBufferedImage newImage = new DirectBufferedImage( Type.DIRECT_RGBA/*, bb*/, cm, newRaster, false ); return ( newImage ); } /** * Creates a buffered image which is backed by a NIO byte buffer * * @param width * @param height * @param pixelSize * @return the made image. */ public static DirectBufferedImage makeDirectImageRGBA( int width, int height, int pixelSize ) { final int bytesPerPixel = pixelSize / 8; //int[] bandOffsets = createBandOffsets( bytesPerPixel ); int[] bandOffsets = { 3, 2, 1, 0 }; // create the backing store //byte[] bb = new byte[ width * height * bytesPerPixel ]; // create a data buffer wrapping the byte array //DataBufferByte buffer = new DataBufferByte( width * height * bytesPerPixel ); DirectDataBufferByte buffer = new DirectDataBufferByte( width * height * bytesPerPixel ); // build the raster with 4 bytes per pixel //WritableRaster newRaster = java.awt.image.Raster.createInterleavedRaster( buffer, width, height, width * bytesPerPixel, bytesPerPixel, bandOffsets, null ); //WritableRaster newRaster = java.awt.image.Raster.createInterleavedRaster( 0, width, height, width * bytesPerPixel, bytesPerPixel, bandOffsets, null ); //WritableRaster newRaster = new ByteBufferInterleavedRaster( DirectWritableRaster.createSampleModel( width, height, bytesPerPixel, bandOffsets ), new java.awt.Point( 0, 0 ) ); //SampleModel sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_BYTE, width, height, bytesPerPixel, width * bytesPerPixel, new int[] { 0 } ); //WritableRaster newRaster = Raster.createWritableRaster( sm, buffer, null ); WritableRaster newRaster = new DirectWritableRaster( width, height, bytesPerPixel, bandOffsets, buffer ); // create a standard sRGB color space ColorSpace cs = ColorSpace.getInstance( ColorSpace.CS_sRGB ); // create a color model which has three 8 bit values for RGB int[] nBits = createNumBitsArray( bytesPerPixel ); ColorModel cm = new ComponentColorModel( cs, nBits, true, false, Transparency.TRANSLUCENT, 0 ); // create the buffered image DirectBufferedImage newImage = new DirectBufferedImage( Type.DIRECT_RGBA/*, bb*/, cm, newRaster, false ); return ( newImage ); } /** * Creates a buffered image which is backed by a NIO byte buffer * * @param width * @param height * @return the made image. */ public static DirectBufferedImage makeDirectImageRGBA( int width, int height ) { return ( makeDirectImageRGBA( width, height, 32 ) ); } /** * Creates a buffered image which is backed by a NIO byte buffer * * @param width * @param height * @param bandOffsets * @param bb * @return the made image. */ public static DirectBufferedImage makeDirectImageRGB( int width, int height, int[] bandOffsets, ByteBuffer bb ) { final int pixelSize = bb.limit() * 8 / ( width * height ); final int bytesPerPixel = pixelSize / 8; if ( bandOffsets == null ) bandOffsets = createBandOffsets( bytesPerPixel ); // create the backing store // create a data buffer wrapping the byte array DirectDataBufferByte buffer = new DirectDataBufferByte( bb ); // build the raster with 3 bytes per pixel WritableRaster newRaster = new DirectWritableRaster( width, height, bytesPerPixel, bandOffsets, buffer ); // create a standard sRGB color space ColorSpace cs = ColorSpace.getInstance( ColorSpace.CS_sRGB ); // create a color model which has three 8 bit values for RGB int[] nBits = createNumBitsArray( bytesPerPixel ); ColorModel cm = new ComponentColorModel( cs, nBits, false, false, Transparency.OPAQUE, 0 ); // create the buffered image DirectBufferedImage newImage = new DirectBufferedImage( Type.DIRECT_RGB/*, bb*/, cm, newRaster, false ); return ( newImage ); } /** * Creates a buffered image which is backed by a NIO byte buffer * * @param width * @param height * @param pixelSize * @return the made image. */ public static DirectBufferedImage makeDirectImageRGB( int width, int height, int pixelSize ) { final int bytesPerPixel = pixelSize / 8; int[] bandOffsets = createBandOffsets( bytesPerPixel ); // create the backing store //byte bb[] = (backingStore == null) ? new byte[ width * height * bytesPerPixel ] : backingStore; // create a data buffer wrapping the byte array //DataBuffer buffer = new DataBufferByte( bb, width * height * bytesPerPixel ); DirectDataBufferByte buffer = new DirectDataBufferByte( width * height * bytesPerPixel ); // build the raster with 3 bytes per pixel //WritableRaster newRaster = java.awt.image.Raster.createInterleavedRaster( buffer, width, height, width * bytesPerPixel, bytesPerPixel, bandOffsets, null ); WritableRaster newRaster = new DirectWritableRaster( width, height, bytesPerPixel, bandOffsets, buffer ); // create a standard sRGB color space ColorSpace cs = ColorSpace.getInstance( ColorSpace.CS_sRGB ); // create a color model which has three 8 bit values for RGB int[] nBits = createNumBitsArray( bytesPerPixel ); ColorModel cm = new ComponentColorModel( cs, nBits, false, false, Transparency.OPAQUE, 0 ); // create the buffered image DirectBufferedImage newImage = new DirectBufferedImage( Type.DIRECT_RGB/*, bb*/, cm, newRaster, false ); return ( newImage ); } /** * Creates a buffered image which is backed by a NIO byte buffer * * @param width * @param height * @return the made image. */ public static DirectBufferedImage makeDirectImageRGB( int width, int height ) { return ( makeDirectImageRGB( width, height, 24 ) ); } /** * Takes the source buffered image and converts it to a buffered image which * is backed by a direct byte buffer * * @param source * @return the DirectBufferedImage */ public static DirectBufferedImage makeDirectImageRGB( BufferedImage source ) { DirectBufferedImage dest = makeDirectImageRGB( source.getWidth(), source.getHeight() ); source.copyData( dest.getRaster() ); return ( dest ); } public static DirectBufferedImage makeDirectImageTwoBytes( int width, int height, int pixelSize ) { final int bytesPerPixel = pixelSize / 8; int[] bandOffsets = createBandOffsets( bytesPerPixel ); // create the backing store //byte bb[] = new byte[ width * height * bytesPerPixel ]; // create a data buffer wrapping the byte array //DataBuffer buffer = new DataBufferByte( bb, width * height * bytesPerPixel ); DirectDataBufferByte buffer = new DirectDataBufferByte( width * height * bytesPerPixel ); // build the raster with 2 bytes per pixel //WritableRaster newRaster = Raster.createInterleavedRaster( buffer, width, height, width * bytesPerPixel, bytesPerPixel, bandOffsets, null ); WritableRaster newRaster = new DirectWritableRaster( width, height, bytesPerPixel, bandOffsets, buffer ); // create a standard sRGB color space ColorSpace cs = ColorSpace.getInstance( ColorSpace.CS_GRAY ); // FIXME: We actually need two bytes!!! // create a color model which has two 8 bit values for luminance and alpha int[] nBits = createNumBitsArray( bytesPerPixel ); ColorModel cm = new ComponentColorModel( cs, nBits, false, false, Transparency.OPAQUE, 0 ); // create the buffered image DirectBufferedImage newImage = new DirectBufferedImage( Type.DIRECT_ONE_BYTE/*, bb*/, cm, newRaster, false ); return ( newImage ); } /** * Creates a buffered image which is backed by a NIO byte buffer * * @param width * @param height * @return the made image. */ public static DirectBufferedImage makeDirectImageTwoBytes( int width, int height ) { return ( makeDirectImageRGBA( width, height, 16 ) ); } public static DirectBufferedImage makeDirectImageOneByte( int width, int height ) { final int bytesPerPixel = 1; int[] bandOffsets = createBandOffsets( bytesPerPixel ); // create the backing store //byte bb[] = new byte[ width * height * bytesPerPixel ]; // create a data buffer wrapping the byte array //DataBuffer buffer = new DataBufferByte( bb, width * height * bytesPerPixel ); DirectDataBufferByte buffer = new DirectDataBufferByte( width * height * bytesPerPixel ); // build the raster with 1 byte per pixel //WritableRaster newRaster = Raster.createInterleavedRaster( buffer, width, height, width * bytesPerPixel, bytesPerPixel, bandOffsets, null ); WritableRaster newRaster = new DirectWritableRaster( width, height, bytesPerPixel, bandOffsets, buffer ); // create a standard sRGB color space ColorSpace cs = ColorSpace.getInstance( ColorSpace.CS_GRAY ); // create a color model which has one 8 bit value for GRAY int[] nBits = createNumBitsArray( bytesPerPixel ); ColorModel cm = new ComponentColorModel( cs, nBits, false, false, Transparency.OPAQUE, 0 ); // create the buffered image DirectBufferedImage newImage = new DirectBufferedImage( Type.DIRECT_ONE_BYTE/*, bb*/, cm, newRaster, false ); return ( newImage ); } /** * takes the source buffered image and converts it to a buffered image which * is backed by a direct byte buffer * * @param source * * @return the made image. */ public static DirectBufferedImage makeDirectImageRGBA( BufferedImage source ) { DirectBufferedImage dest = makeDirectImageRGBA( source.getWidth(), source.getHeight() ); source.copyData( dest.getRaster() ); return ( dest ); } public static DirectBufferedImage make( Type type, int width, int height ) { switch ( type ) { case DIRECT_RGBA: return ( makeDirectImageRGBA( width, height ) ); case DIRECT_RGB: return ( makeDirectImageRGB( width, height ) ); case DIRECT_TWO_BYTES: return ( makeDirectImageTwoBytes( width, height ) ); case DIRECT_ONE_BYTE: return ( makeDirectImageOneByte( width, height ) ); } throw new Error( "Unknown direct image type " + type ); } private static DirectBufferedImage convertViaDrawing( BufferedImage source, DirectBufferedImage dest ) { Graphics2D g = (Graphics2D)dest.getGraphics(); g.drawImage( source, 0, 0, dest.getWidth(), dest.getHeight(), null ); return ( dest ); } public static DirectBufferedImage make( BufferedImage bi, boolean allowAlpha ) { boolean hasAlpha = bi.getColorModel().hasAlpha() && !bi.getColorModel().isAlphaPremultiplied(); if ( hasAlpha && allowAlpha ) { return ( convertViaDrawing( bi, makeDirectImageRGBA( bi.getWidth(), bi.getHeight() ) ) ); } return ( convertViaDrawing( bi, makeDirectImageRGB( bi.getWidth(), bi.getHeight() ) ) ); } public static DirectBufferedImage make( BufferedImage bi ) { return ( make( bi, true ) ); } /** * reads in an image using image io. It then detects if this is a RGBA or * RGB image and converts it to the appropriate direct image. Unfortunly * this does mean we are loading a buffered image which is thrown away, but * there is no help for that currently. * * @param in * @param allowAlpha * * @return the image. * * @throws java.io.IOException */ public static DirectBufferedImage loadDirectImage( InputStream in, boolean allowAlpha ) throws IOException { if ( !( in instanceof BufferedInputStream ) ) in = new BufferedInputStream( in ); BufferedImage bi = ImageIO.read( in ); return ( make( bi, allowAlpha ) ); } /** * reads in an image using image io. It then detects if this is a RGBA or * RGB image and converts it to the appropriate direct image. Unfortunly * this does mean we are loading a buffered image which is thrown away, but * there is no help for that currently. * * @param in * * @return the image. * * @throws java.io.IOException */ public static DirectBufferedImage loadDirectImage( InputStream in ) throws IOException { return ( loadDirectImage( in, true ) ); } /** * reads in an image using image io. It then detects if this is a RGBA or * RGB image and converts it to the appropriate direct image. Unfortunly * this does mean we are loading a buffered image which is thrown away, but * there is no help for that currently. * * @param url * @param allowAlpha * * @return the image. * * @throws java.io.IOException */ public static DirectBufferedImage loadDirectImage( URL url, boolean allowAlpha ) throws IOException { return ( loadDirectImage( url.openStream(), allowAlpha ) ); } public static BufferedImage loadDirectImage( URL url ) throws java.io.IOException { return ( loadDirectImage( url, true ) ); } /** * reads in an image using image io. It then detects if this is a RGBA or * RGB image and converts it to the appropriate direct image. Unfortunly * this does mean we are loading a buffered image which is thrown away, but * there is no help for that currently. * * @param file * @param allowAlpha * * @return the image. * * @throws java.io.IOException */ public static DirectBufferedImage loadDirectImage( File file, boolean allowAlpha ) throws IOException { return ( loadDirectImage( new FileInputStream( file ), allowAlpha ) ); } /** * reads in an image using image io. It then detects if this is a RGBA or * RGB image and converts it to the appropriate direct image. Unfortunly * this does mean we are loading a buffered image which is thrown away, but * there is no help for that currently. * * @param file * * @return the image. * * @throws java.io.IOException */ public static DirectBufferedImage loadDirectImage( File file ) throws IOException { return ( loadDirectImage( file, true ) ); } /** * reads in an image using image io. It then detects if this is a RGBA or * RGB image and converts it to the appropriate direct image. Unfortunly * this does mean we are loading a buffered image which is thrown away, but * there is no help for that currently. * * @param name * @param allowAlpha * * @return the image. * * @throws java.io.IOException */ public static DirectBufferedImage loadDirectImage( String name, boolean allowAlpha ) throws IOException { return ( loadDirectImage( new File( name ), allowAlpha ) ); } public static DirectBufferedImage loadDirectImage( String name ) throws IOException { return ( loadDirectImage( name, true ) ); } }