RFC: ImageLoader

As i need easy interaction between Swing and JME3, i created some classes that currently work pretty cool and so they’re free for public flaming :wink: :



Now, what’s the benefit?



The AWTLoader loades BufferedImages, converts them into com.jme3.texture.Image’s and forgets the BufferedImage. Not cool if you sometimes later need a BufferedImage. Not cool, if you want to convert from BufferedImage yourself, because most methods are unneccearily unstatic in those classes.



So here’s another approach, that extends com.jme3.texture.Image and BufferedImage and provides an Imageloader that replaces AWTLoader.



First of all, you can easily extend those classes yourself and have them used by implementing the QPImageExCreator interface:

[java] @Override

public ImageEx createImageEx(AssetInfo i)

{return new MyImageExExtension();}



@Override

public BufferedImageEx createBufferedImageEx()

{return new MyBufferedImageExExtension();}

[/java]

and telling the ImageEx-class where to find those handlers:

[java]ImageEx.setCreationHandlers(ClassImplementingInterfaceQPImageExCreator);[/java]



You also have the ability to easily get the underlying BufferedImageEx of the ImageEx by simply calling

[java]ImageEx foo = (ImageEx) getAssetManager().loadAsset(url);

/* … /

foo.getBuffered();[/java]

…you then may use this as you like, for example, put it into a swing component or get a Grafic2D-object from it and draw into it! Afterwards, you can easily call

[java]foo.updateTexture();[/java]

to have the jme3-part of the image updated to your drawings. The flags are currently not fully implemented, i’ll update this code when those classes get finished.



You set up the loader by simply calling

[java]registerLoader(ImageLoader.class, “jpg”, “gif”, “png”);[/java]

on your Assetmanager. You may use any supported image-format-extension.





The classes:

First class is a replacement for the AWTloader:



[java]

package qpool;



import java.io.IOException;

import java.io.InputStream;



import javax.imageio.ImageIO;



import qpool.ImageEx;



import com.jme3.asset.AssetInfo;

import com.jme3.asset.AssetLoader;

import com.jme3.asset.TextureKey;



/
***************************************************************************

  • ImageEx-AssetLoader for JME3’s AssetManager. Allows loading of images that
  • can easily be used in combination with AWT/Swing components and Graphics2D
  • painting operations.
  • @author .rhavin grobert
  • @version 0.4.0 /

    public class ImageLoader implements AssetLoader

    {



    /
    ********************************************************************
  • Load ImageEx from given AssetInfo /

    @Override

    public Object load(AssetInfo i) throws IOException

    {

    InputStream in = i.openStream();

    if (in == null)

    return null;

    ImageEx img = ImageEx.createImageEx(i);

    if (img == null)

    return null;

    if (i.getKey() instanceof TextureKey)

    img.setFlags(ImageEx.FLAG_FLIPPEDY,

    ((TextureKey) i.getKey()).isFlipY());

    ImageIO.setUseCache(false);

    img.fromBufferedImage(ImageIO.read(in));

    in.close();

    return img;

    }

    }

    [/java]



    As you see, this class depends on a class called “ImageEx” which extends com.jme3.texture.Image:



    [java]package qpool;



    import java.awt.Graphics2D;

    import java.awt.image.BufferedImage;

    import java.awt.image.DataBuffer;

    import java.awt.image.DataBufferByte;

    import java.nio.ByteBuffer;

    import java.util.ArrayList;



    import javax.vecmath.Point4f;



    import com.jme3.asset.AssetInfo;

    import com.jme3.util.BufferUtils;



    /
    ***************************************************************************
  • Extension of JME3’s Image-class for easy interaction with AWT/Swing
  • components.
  • @author .rhavin grobert
  • @version 0.4.0 /

    public class ImageEx extends com.jme3.texture.Image

    {

    /
    ********************************************************************
  • Two handlers invoked on ImageEx and BufferedImageEx creation. This
  • allows you to derive from both classes and have them used in your
  • application. See setCreationHandlers(). /

    public interface QPImageExCreator

    {

    public ImageEx createImageEx(AssetInfo i);

    public BufferedImageEx createBufferedImageEx();

    }

    /
    ********************************************************************
  • Specifies a handler that gets called every time an ImageEx shall be
  • created. You may specify an Object that implements the interface
  • here to do the actual creation yourself, so you can easily use your
  • own ImageEx derived class.
  • @param iec The Object that implements the interface. /

    public static void setCreationHandlers(QPImageExCreator iec)

    {_iec = iec;}

    /
    ********************************************************************
  • Reference to handler implementing class /

    private static QPImageExCreator _iec = null;

    /
    ********************************************************************
  • Factory: Calls the creation handler for ImageEx-creation.
  • @param i The AssetInfo provided by the ImageLoader
  • @return The created Object. /

    public static ImageEx createImageEx(AssetInfo i)

    {

    if (_iec != null)

    return _iec.createImageEx(i);

    return new ImageEx();

    }

    /
    ********************************************************************
  • Factory: Calls the creation handler for BufferedImageEx-creation.
  • @return The created Object. */

    public static BufferedImageEx createBufferedImageEx()

    {

    if (_iec != null)

    return _iec.createBufferedImageEx();

    return new BufferedImageEx();

    }



    //

// definition of static constants
/** The JME3-texture-component of this image is used */
public static final int FLAG_TEXTURE = 0x0001;
/** The AWT/Swing BufferedImageEx-component of this image is used */
public static final int FLAG_BUFFERED = 0x0002;
/** The texture-component y-axis needs to be flipped. */
public static final int FLAG_FLIPPEDY = 0x0004;
/** The default flags if none were given at creation. */
protected static int FLAG_DEFAULT
= FLAG_TEXTURE | FLAG_FLIPPEDY | FLAG_BUFFERED;

/*********************************************************************
* Shift (<<) all user-defined flags by this constant, eg:<br/><code>
* static final int FLAG_XY = 0x0001 << ImageEx.FLAG_USER; </code> */
public static final int FLAG_USER = 0x0008;
/** Type returned from ImageEx if underlying BufferedImage is null */
public static int TYPE_NONE = -1;

//
// child classes and interfaces
/** This hack is necessary because Java's clone() is botch */
interface IsCloneable {public Object clone();};

//
// members
/** The underlying BufferedImage for graphic operations */
protected BufferedImageEx imgBuffered = null;
/** Image-flags . */
protected int flags = FLAG_DEFAULT;

//
// constructors
/** Constructor: create new ImageEx with default values. */
public ImageEx() {super();}
/** Constructor: create new ImageEx of given id. */
protected ImageEx(int id) {super(id);}
/** Constructor: create new ImageEx from given BufferedImageEx. */
public ImageEx(BufferedImageEx i)
{super(); setBuffered(i);}

/*********************************************************************
* Constructor: Create a new ImageEx from given parameters
* @param width The images width.
* @param height The images height.
* @param type The BufferedImage-type of the image. */
public ImageEx(int width, int height, int type)
{
super();
BufferedImageEx bi = createBufferedImageEx();
bi.newBI(width, height, type);
}

/*********************************************************************
* Constructor: create a new ImageEx object with given values. The
* attributes of the image are defined during construction.
* @param f The jme3-data format of the image.
* @param w The width of the image.
* @param h The height of the image.
* @param d The depth of the image.
* @param b The image data.
* @param mipMap The array of mipmap sizes or <b>null</b>. */
public ImageEx(Format f, int w, int h, int d, ArrayList<ByteBuffer> b,
int[] mipMap)
{super(f, w, h, d, b, mipMap);}

/*********************************************************************
* Constructor: create a new ImageEx object with given values. The
* attributes of the image are defined during construction.
* @param f The jme3-data format of the image.
* @param w The width of the image.
* @param h The height of the image.
* @param b The image data.
* @param mipMap The array of mipmap sizes or <b>null</b>. */
public ImageEx(Format f, int w, int h, ByteBuffer b, int[] mipMap)
{super(f, w, h, b, mipMap);}

public ImageEx(ImageEx i)
{
super(i.format, i.width, i.height, i.depth, i.data,
i.mipMapSizes.clone());
imgBuffered = i.imgBuffered.clone();
}

//
// public methods
/** Get the underlying BufferedImage */
public BufferedImageEx getBuffered()
{return imgBuffered;}
/** Test set flags against given flags */
public boolean testFlags(int flagsRequired)
{return ((flags & flagsRequired) == flagsRequired);}



/*********************************************************************
* Set the underlying BufferedImage directly. */
public void setBuffered(BufferedImageEx img)
{
imgBuffered = img;
updateTexture();
}

/*********************************************************************
* Set ImageEx's flags directly.
* @param flagsNew The new flags. */
public void setFlags(int flagsNew)
{flags = flagsNew;}
/*********************************************************************
* Modify ImageEx's flags. In conflict, add overwrites remove!
* @param flagsAdd The flags to set.
* @param flagsRemove The flags to remove. */
public void setFlags(int flagsAdd, int flagsRemove)
{
if (flagsRemove != 0)
flags &= ~flagsRemove;
flags |= flagsAdd;
}
/*********************************************************************
* Modify ImageEx's flags by given flag-mask.
* @param flagMask The flags to change.
* @param set Set to true to set flags or to false to remove them. */
public void setFlags(int flagMask, boolean set)
{
if (set) flags |= flagMask;
else flags &= ~flagMask;
}

/*********************************************************************
* Let this Images HotSpot reference the given point.
* @param p The new HotSpot. */
public boolean setHotSpot(Point4f p)
{
if (imgBuffered == null)
return false;
imgBuffered.setHotSpot(p);
return true;
}

/*********************************************************************
* Create a clone of this ImageEx */
public ImageEx clone()
{
ImageEx clone = (ImageEx) super.clone();
clone.imgBuffered = imgBuffered.clone();
return clone;
}

/*********************************************************************
* Get Object from images HashTable by name.
* @param name The String identifier of the Object.
* @return The Object. */
public Object getProperty(String name)
{
if (imgBuffered == null)
return null;
return imgBuffered.getProperty(name);
}

/*********************************************************************
* Get Object from images HashTable by name.
* @param name The String identifier of the Object.
* @return The Object. */
public boolean setProperty(String name, Object o)
{
if (imgBuffered == null)
return false;
imgBuffered.setProperty(name, o);
return true;
}

/*********************************************************************
* Get type of underlying BufferedImage.
* @return Type or BufferedImageEx.TYPE_NONE if not used. */
public int getType()
{
if (imgBuffered == null)
return ImageEx.TYPE_NONE;
return imgBuffered.getType();
}

/*********************************************************************
* Create a Graphics2D-Object for the underlying BufferedImage.
* Remember to call updateTexture() after drawing!
* @return The Graphics2D-Object. */
public Graphics2D createGraphics()
{
if (imgBuffered == null)
return null;
return imgBuffered.createGraphics();
}

/*********************************************************************
* Flip the given rectangular ArrayBuffer vertically.
* @param b The buffer to flip.
* @param w The rectangles width.
* @param h The rectangles height.
* @param p bits per element. */
public static void flipBuffer(byte[] b, int w, int h, int p)
{
int line = (w * p) / 8;
byte[] scan = new byte[line];
int dy = 0;
int mid = h / 2;
for (int y = 0; y < mid; y++)
{
dy = h - y - 1;
System.arraycopy(b, y * line, scan, 0, line);
System.arraycopy(b, dy * line, b, y * line, line);
System.arraycopy(scan, 0, b, dy * line, line);
}
}

/*********************************************************************
* This method updates the JM3-texture part of this Object to the
* currently set BufferedImage. */
public void updateTexture()
{
if (imgBuffered == null
|| testFlags(ImageEx.FLAG_TEXTURE) == false)
return;
width = imgBuffered.getWidth();
height = imgBuffered.getHeight();
depth = 1;
int bpp = 8;
int elmsize = 1;
boolean transform = false;
switch (imgBuffered.getType())
{
case BufferedImage.TYPE_INT_ARGB:
elmsize = 32;
format = Format.RGBA8;
break;
case BufferedImage.TYPE_INT_RGB:
elmsize = 24;
format = Format.RGB8;
break;
case BufferedImage.TYPE_3BYTE_BGR:
elmsize = 24;
format = Format.BGR8;
break;
case BufferedImage.TYPE_BYTE_GRAY:
elmsize = 8;
format = Format.Luminance8;
break;
default:
transform = true;
if (imgBuffered.getTransparency() == BufferedImage.OPAQUE)
format = Format.RGB8;
else
format = Format.RGBA8;
break;
}

if (format != null)
bpp = format.getBitsPerPixel();
boolean flip = testFlags(ImageEx.FLAG_FLIPPEDY);

ByteBuffer buf = BufferUtils.createByteBuffer(width * height *
depth * bpp);

if (transform)
{
if (format == Format.RGB8)
trans3Byte(buf, imgBuffered, flip);
else
trans4Byte(buf, imgBuffered, flip);
}
else
transDirect(buf, imgBuffered, elmsize, flip);

addData(buf);
setUpdateNeeded();
}

/*********************************************************************
* Extracts image-data from given BufferedImage and copy into provided
* destination buffer. Assumes 3 bytes per pixel format.
* @param dst The destination ByteBuffer.
* @param src The source BufferedImage.
* @param flip True if image shall be flipped horizontally.
* @return True if operation succeeded. */
public static boolean trans3Byte(ByteBuffer dst, BufferedImage src,
boolean flip)
{
if (dst == null || src == null)
return false;
int width = src.getWidth();
int height = src.getHeight();
int dy;
int pixel;
byte r, g, b;
dst.rewind();
for (int y = 0; y < height; y++)
{
if (flip)
dy = height - y - 1;
else
dy = y;
for (int x = 0; x < width; x++)
{
pixel = src.getRGB(x, dy);
b = (byte) pixel;
g = (byte) (pixel >>>= 8 );
r = (byte) (pixel >>>= 8 );
dst.put(r).put(g).put(b);
}
}
dst.flip();
return true;
}

/*********************************************************************
* Extracts image-data from given BufferedImage and copy into provided
* destination buffer. Assumes 4 bytes per pixel format.
* @param dst The destination ByteBuffer.
* @param src The source BufferedImage.
* @param flip True if image shall be flipped horizontally.
* @return True if operation succeeded. */
public static boolean trans4Byte(ByteBuffer dst, BufferedImage src,
boolean flip)
{
if (dst == null || src == null)
return false;
int width = src.getWidth();
int height = src.getHeight();
int dy, pixel;
byte r, g, b, a;
dst.rewind();
for (int y = 0; y < height; y++)
{
if (flip)
dy = height - y - 1;
else
dy = y;
for (int x = 0; x < width; x++)
{
pixel = src.getRGB(x, dy);
b = (byte) pixel;
g = (byte) (pixel >>>= 8 );
r = (byte) (pixel >>>= 8 );
a = (byte) (pixel >>>= 8 );
dst.put(r).put(g).put(b).put(a);
}
}
dst.flip();
return true;
}

/*********************************************************************
* Extracts image-data from given BufferedImage and copy into provided
* destination buffer.
* @param dst The destination ByteBuffer.
* @param src The source BufferedImage.
* @param bpp The images BitsPerPlane.
* @param flip True if image shall be flipped horizontally.
* @return True if operation succeeded. */
public static boolean transDirect(ByteBuffer dst, BufferedImage src,
int bpp, boolean flip)
{
if (dst == null || src == null)
return false;
int width = src.getWidth();
int height = src.getHeight();
DataBuffer dataBuf = src.getRaster().getDataBuffer();
byte[] data = null;

if (dataBuf.getDataType() == DataBuffer.TYPE_BYTE)
data = ((DataBufferByte)dataBuf).getData();
else
{
// TODO: add conversion, buffer is no DataBufferByte
return false;
}

if (flip)
flipBuffer(data, width, height, bpp);
dst.put(((DataBufferByte)dataBuf).getData());
return true;
}


/*********************************************************************
* Compare with another object */
public boolean equals(Object other)
{
if (other == this)
return true;
if (!super.equals(other))
return false;
if (!(other instanceof ImageEx))
return false;
if (!imgBuffered.equals(other))
return false;

return true;
}

/*********************************************************************
* Shallow-Copy content of BufferedImage into our BufferedImageEx */
public void fromBufferedImage(BufferedImage i)
{
if (imgBuffered == null)
imgBuffered = new BufferedImageEx(i, 0);
updateTexture();
}
}[/java]

The third class i'd have liked to be a simple extension of BufferedImage, but as that class is almost resistend to extension, it became a wrapper instead:

[java]/****************************************************************************
* © 2010 ShadowTec media / .rhavin Grobert for q-pool java enhancement *
***************************************************************************/
package qpool;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Vector;

import javax.vecmath.Point4f;

/****************************************************************************
* As BufferedImage has _serious_ design flaws, we need to define additional
* functionality here. To be honest, I think BufferedImage is just botch:
* there is useful code that should be accessible via public static methods,
* but instead it is resting in some constructors while the PropertyHashMap
* can only be set by one single constructor that doesn't provide any useful
* defaults. And it has a method getPropertyNames() that should give you the
* the keys to the HashMap but is card-coded to always return null 8-@!
* But as it is created by vital APIs and is widely used, it should be made
* ready for extension: this wrapper-class tries to fix that… !
* @author .rhavin Grobert
* @version 0.4.0 */
public class BufferedImageEx extends java.awt.image.BufferedImage
implements Cloneable, Serializable
{
private static final long serialVersionUID = 1L;

/** Token for the little self-referencing trick in constructors */
protected static final String _QP_SELFREF_ = "_QP_BIE_SELFREF_";

//
// members
/** The actual BufferedImage */
private BufferedImage _bi = null;
/** Offset-value of images hot-spot. */
protected Point4f hotspot = new Point4f();
/** A *usable* map, ref that has to stay in sync with underlying BI */
private Hashtable<String, Object> properties = null;

//
// constructors
/*********************************************************************
* Default-Constructor: Manually invoke newBI or setBufferedImage */
public BufferedImageEx()
{
super(1, 1, TYPE_INT_ARGB); // cheapest c-tor
properties = new Hashtable<String, Object>();
}

/*********************************************************************
* Constructor: Directly set ColorModel, Raster and HashTabe. */
public BufferedImageEx(ColorModel cm, WritableRaster r,
boolean isPremultiplied, Hashtable<String, Object> props)
{
super(1, 1, TYPE_INT_ARGB); // cheapest c-tor
newBI(cm, r, isPremultiplied, props);
}

/*********************************************************************
* Constructor: Create Image of given dimensions by specified Type.
* The <code>ColorSpace</code> for the image is default sRGB space. */
public BufferedImageEx(int width, int height, int imageType)
{this(new BufferedImage(width, height, imageType), 0);}

/*********************************************************************
* Constructor: Create Image of given dimensions by specified Type.
* You may also specify an appropriate IndexColorModel. */
public BufferedImageEx(int width, int height, int imageType,
IndexColorModel cm)
{this(new BufferedImage(width, height, imageType, cm), 0);}

/*********************************************************************
* Copy-Constructor: It is now finally possible to create Objects of
* class BufferedImageEx or any subclass from any given BufferedImage.
* So you can call this CC with any BufferedImage from System-APIs. */
public BufferedImageEx(java.awt.image.BufferedImage i)
{
super(1, 1, TYPE_INT_ARGB); // cheapest c-tor
newBI(i.getColorModel(), (WritableRaster) i.getData(),
i.isAlphaPremultiplied(), getHashTableClone(i));
}

/*********************************************************************
* Copy-Constructor: create new BufferedImageEx that shares the same
* data as given image. The int dummy is just to get another sig. */
public BufferedImageEx(java.awt.image.BufferedImage i, int dummy)
{
super(1, 1, TYPE_INT_ARGB); // cheapest c-tor
newBI(i.getColorModel(), (WritableRaster) i.getRaster(),
i.isAlphaPremultiplied(), getHashTableClone(i));
}

/*********************************************************************
* Copy-Constructor: create copy from BufferedImageEx. Call from CC
* of derived classes. */
public BufferedImageEx(BufferedImageEx i)
{
this((java.awt.image.BufferedImage)i);
hotspot = (Point4f) i.hotspot.clone();
}

//
// methods
/*********************************************************************
* Construct new underlying BufferedImage. */
public void newBI(ColorModel cm, WritableRaster r,
boolean isPremultiplied, Hashtable<String, Object> props)
{
_bi = new BufferedImage(cm, r, isPremultiplied, props);
properties = props;
}

/*********************************************************************
* Construct new underlying BufferedImage. */
public void newBI(int width, int height, int imageType)
{
_bi = new BufferedImage(width, height, imageType);
newBI(_bi.getColorModel(), _bi.getRaster(),
_bi.isAlphaPremultiplied(), createHashTable());
}

/*********************************************************************
* Get images hot-spot. The HotSpot may be used by derived classes
* for any purpose. */
public Point4f getHotSpot() {return hotspot;}

/*********************************************************************
* Set images hot-spot to the given reference. The HotSpot may be used
* by derived classes for any purpose. */
public void setHotSpot(Point4f p) {hotspot = p;}

/*********************************************************************
* Set a named property to the specified object reference. */
public void setProperty(String name, Object o)
{properties.put(name, o);}

/*********************************************************************
* Direct access to property-table. */
public Hashtable<String, Object> getPropertys()
{return properties;}

/*********************************************************************
* Get a copy of this object. The raster, HashMap and HotSpot is deep-
* copied while the ColorSpace (usually referencing a static anyway)
* is shallow-copied. */
@SuppressWarnings("unchecked")
public BufferedImageEx clone()
{
try
{
BufferedImageEx bi = (BufferedImageEx) super.clone();
bi.hotspot = (Point4f) hotspot.clone();
bi.setData(getData());
bi.properties = (Hashtable<String, Object>)
properties.clone();
return bi;
}
catch (CloneNotSupportedException e)
{return null;}
}

/*********************************************************************
* Really returns an array of names recognized by the BufferedImage's
* method {getProperty(String) ;~) */
public String[] getPropertyNames()
{
return (String[]) properties.keySet().toArray();
}

//
// simple WrapperFunctions
public ColorModel getColorModel() {return _bi.getColorModel();}
public WritableRaster getRaster() {return _bi.getRaster();}
public WritableRaster getAlphaRaster()
{return _bi.getAlphaRaster();}
public int getRGB(int x, int y) {return _bi.getRGB(x, y);}
public int[] getRGB(int x, int y, int w, int h,
int[] rgbArray, int off, int scansize)
{return _bi.getRGB(x, y, w, h, rgbArray, off, scansize);}
public synchronized void setRGB(int x, int y, int rgb)
{_bi.setRGB(x, y, rgb);}
public void setRGB(int x, int y, int w, int h, int[] rgbArray,
int off, int scansize)
{_bi.setRGB(x, y, w, h, rgbArray, off, scansize);}
public int getType() {return _bi.getType();}
public int getWidth() {return _bi.getWidth();}
public int getHeight() {return _bi.getHeight();}
public int getWidth(ImageObserver o) {return _bi.getWidth(o);}
public int getHeight(ImageObserver o) {return _bi.getHeight(o);}
public ImageProducer getSource() {return _bi.getSource();}
public Vector<RenderedImage> getSources() {return _bi.getSources();}
public Object getProperty(String name, ImageObserver observer)
{return _bi.getProperty(name, observer);}
public Object getProperty(String name)
{return _bi.getProperty(name);}
public java.awt.Graphics getGraphics() {return _bi.getGraphics();}
public Graphics2D createGraphics() {return _bi.createGraphics();}
public BufferedImage getSubimage (int x, int y, int w, int h)
{return _bi.getSubimage(x, y, w, h);}
public boolean isAlphaPremultiplied()
{return _bi.isAlphaPremultiplied();}
public void coerceData (boolean isAlphaPremultiplied)
{_bi.coerceData(isAlphaPremultiplied);}
public SampleModel getSampleModel() {return _bi.getSampleModel();}
public int getTileWidth() {return _bi.getTileWidth();}
public int getTileHeight() {return _bi.getTileHeight();}
public Raster getData() {return _bi.getData();}
public Raster getData(java.awt.Rectangle r) {return _bi.getData(r);}
public WritableRaster copyData(WritableRaster out)
{return _bi.copyData(out);}
public void setData(Raster r) {_bi.setData(r);}
public WritableRaster getWritableTile (int x, int y)
{return _bi.getWritableTile(x, y);}
public void releaseWritableTile (int tileX, int tileY)
{_bi.releaseWritableTile(tileX, tileY);}
public int getTransparency() {return _bi.getTransparency();}
public int getPixelBits()
{return _bi.getColorModel().getPixelSize();}

/*********************************************************************
* Thanx to snoracle, the properties field is package-visible and the
* java.*-package is protected, so in order to set our properties, we
* need this little trick of self-referencing them:
* @return a HashTable that references itself */
protected static Hashtable<String, Object> createHashTable()
{
Hashtable<String, Object> p = new Hashtable<String, Object>();
p.put(_QP_SELFREF_, p);
return p;
}
/*********************************************************************
* Thanx to snoracle, the properties field is package-visible and the
* java.*-package is protected, so in order to access them, we need
* this little trick of self-referencing:
* @param i A BufferedImage that you want to cheat.
* @return a HashTable that references itself */
@SuppressWarnings("unchecked")
protected static Hashtable<String, Object>
getHashTableClone(java.awt.image.BufferedImage i)
{
// we try to get the reference-field in case the
// HashMap already got a self-reference somehow.
Object o = i.getProperty(_QP_SELFREF_);
Hashtable<String, Object> ht = null;
if (o != null && o != BufferedImage.UndefinedProperty)
{
ht = (Hashtable<String, Object>) o;
return (Hashtable<String, Object>) ht.clone();
}
// As getPropertyNames() of BufferedImage *always*
// returns null (check source if u don't believe!)
// we can't copy content. So we just return a new
// HashMap.
return createHashTable();
}

/*********************************************************************
* Write object into stream
* @param out The stream to write to
* @throws IOException */
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
{
out.writeObject(getColorModel());
out.writeObject(getData());
out.writeBoolean(isAlphaPremultiplied());
out.writeObject(properties);
}

/*********************************************************************
* Read object from stream */
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
{
ColorModel cm = (ColorModel) in.readObject();
WritableRaster r = (WritableRaster) in.readObject();
boolean isAlphaPre = in.readBoolean();
properties = (Hashtable<String, Object>) in.readObject();
properties.put(_QP_SELFREF_, properties);
_bi = new BufferedImage(cm, r, isAlphaPre, properties);
}
}[/java]



comments appreciated.

Woudl that man that we have another copy of the same texture in memory all the time just as a bufferedimage this time?



In this case tihs is really problematic.

As it is intendend: no, it keeps the headers and transforms the data to something useable for both.

As it is now: yes for some formats, i’m not yet finished.