Kaynağa Gözat

* Added new ImageRaster thing, it can let you read and write pixels on jME3 images without caring about the underlying format. NOTE: None of the jME3 internal classes use it yet, the code has yet to be ported.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9655 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
Sha..rd 13 yıl önce
ebeveyn
işleme
f5f3a85042

+ 73 - 0
engine/src/core/com/jme3/texture/image/BitMaskImageCodec.java

@@ -0,0 +1,73 @@
+package com.jme3.texture.image;
+
+import java.nio.ByteBuffer;
+
+class BitMaskImageCodec extends ImageCodec {
+    
+    // Shifts
+    final int as, rs, gs, bs;
+    boolean be = false;
+    
+    public BitMaskImageCodec(int bpp, int flags, int ac, int rc, int gc, int bc, int as, int rs, int gs, int bs) {
+        super(bpp, flags,
+                (int) (((long) 1 << ac) - 1),
+                (int) (((long) 1 << rc) - 1),
+                (int) (((long) 1 << gc) - 1),
+                (int) (((long) 1 << bc) - 1));
+
+        if (bpp > 4) {
+            throw new UnsupportedOperationException("Use ByteAlignedImageCodec for codecs with pixel sizes larger than 4 bytes");
+        }
+        
+        this.as = as;
+        this.rs = rs;
+        this.gs = gs;
+        this.bs = bs;
+    }
+    
+    private static int readPixelRaw(ByteBuffer buf, int idx, int bpp) {
+        idx += bpp;
+        int original = buf.get(--idx) & 0xff;
+        while ((--bpp) > 0) {
+            original = (original << 8) | (buf.get(--idx) & 0xff);
+        }
+        return original;
+    }
+    
+    private void writePixelRaw(ByteBuffer buf, int idx, int pixel, int bpp){
+//        buf.position(idx);
+//        if (!be){
+            while ((--bpp) >= 0){
+                byte bt = (byte) ((pixel >> (bpp * 8)) & 0xff);
+                buf.put(idx + bpp, bt);
+            }
+//        } else {
+//            for (int i = bpp - 1; i >= 0; i--) {
+//                byte bt = (byte) ((pixel >> (i * 8)) & 0xff);
+//                buf.put(idx + i, bt);
+//            }
+//        }
+    }
+
+    @Override
+    public void readComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp) {
+        int inputPixel = readPixelRaw(buf, (x + y * width) * bpp, bpp);
+        components[0] = (inputPixel >> as) & maxAlpha;
+        components[1] = (inputPixel >> rs) & maxRed;
+        components[2] = (inputPixel >> gs) & maxGreen;
+        components[3] = (inputPixel >> bs) & maxBlue;
+    }
+
+    public void writeComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp) {
+        // Shift components then mask them
+        // Map all components into a single bitspace
+        int outputPixel = ((components[0] & maxAlpha) << as)
+                        | ((components[1] & maxRed) << rs)
+                        | ((components[2] & maxGreen) << gs)
+                        | ((components[3] & maxBlue) << bs);
+        
+        // Find index in image where to write pixel.
+        // Write the resultant bitspace into the pixel.
+        writePixelRaw(buf, (x + y * width) * bpp, outputPixel, bpp);
+    }
+}

+ 96 - 0
engine/src/core/com/jme3/texture/image/ByteAlignedImageCodec.java

@@ -0,0 +1,96 @@
+package com.jme3.texture.image;
+
+import java.nio.ByteBuffer;
+
+class ByteAlignedImageCodec extends ImageCodec {
+    
+    private final int ap, az, rp, rz, gp, gz, bp, bz;
+    boolean be;
+    
+    public ByteAlignedImageCodec(int bpp, int flags, int az, int rz, int gz, int bz, int ap, int rp, int gp, int bp) {
+        // Cast to long to compute max vals, since some components could be as high as 32 bits.
+        super(bpp, flags, 
+                (int)(((long)1 << (az << 3)) - 1), 
+                (int)(((long)1 << (rz << 3)) - 1), 
+                (int)(((long)1 << (gz << 3)) - 1), 
+                (int)(((long)1 << (bz << 3)) - 1));
+
+        this.ap = ap;
+        this.az = az;
+        this.rp = rp;
+        this.rz = rz;
+
+        this.gp = gp;
+        this.gz = gz;
+        this.bp = bp;
+        this.bz = bz;
+    }
+    
+    private static void readPixelRaw(ByteBuffer buf, int idx, int bpp, byte[] result) {
+        buf.position(idx);
+        buf.get(result, 0, bpp);
+    }
+    
+    private static void writePixelRaw(ByteBuffer buf, int idx, byte[] pixel, int bpp) {
+//        try {
+        buf.position(idx);
+        buf.put(pixel, 0, bpp);
+//        } catch (IndexOutOfBoundsException ex) {
+//            System.out.println("!");
+//        }
+    }
+    
+    private static int readComponent(byte[] encoded, int position, int size) {
+//        int component = encoded[position] & 0xff;
+//        while ((--size) > 0){
+//            component = (component << 8) | (encoded[++position] & 0xff);
+//        }
+//        return component;
+        try {
+            int component = 0;
+            for (int i = size - 1; i >= 0; i--) {
+                component = (component << 8) | (encoded[position + i] & 0xff);
+            }
+            return component;
+//        position += size - 1;
+//        
+//        while ((--size) >= 0) {
+//            component = (component << 8) | (encoded[position--] & 0xff);
+//        }
+//        return component;
+        } catch (ArrayIndexOutOfBoundsException ex){
+            ex.printStackTrace();
+            return 0;
+        }
+    }
+    
+    private void writeComponent(int component, int position, int size, byte[] result) {
+//        if (!be) {
+//            while ((--size) >= 0){
+//                byte bt = (byte) ((component >> (size * 8)) & 0xff);
+//                result[position++] = bt;
+//            }
+//        } else {
+            for (int i = 0; i < size; i++) {
+                byte bt = (byte) ((component >> (i * 8)) & 0xff);
+                result[position++] = bt;
+            }
+//        }
+    }
+    
+    public void readComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp) {
+        readPixelRaw(buf, (x + y * width) * bpp, bpp, tmp);
+        components[0] = readComponent(tmp, ap, az);
+        components[1] = readComponent(tmp, rp, rz);
+        components[2] = readComponent(tmp, gp, gz);
+        components[3] = readComponent(tmp, bp, bz);
+    }
+    
+    public void writeComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp) {
+        writeComponent(components[0], ap, az, tmp);
+        writeComponent(components[1], rp, rz, tmp);
+        writeComponent(components[2], gp, gz, tmp);
+        writeComponent(components[3], bp, bz, tmp);
+        writePixelRaw(buf, (x + y * width) * bpp, tmp, bpp);
+    }
+}

+ 140 - 0
engine/src/core/com/jme3/texture/image/ImageCodec.java

@@ -0,0 +1,140 @@
+package com.jme3.texture.image;
+
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import java.nio.ByteBuffer;
+import java.util.EnumMap;
+
+abstract class ImageCodec {
+    
+    public static final int FLAG_F16 = 1, FLAG_F32 = 2, FLAG_GRAY = 4, FLAG_ALPHAONLY = 8, FLAG_SHAREDEXP = 16;
+    private static final EnumMap<Image.Format, ImageCodec> params = new EnumMap<Image.Format, ImageCodec>(Image.Format.class);
+    
+    protected final int bpp, flags, maxAlpha, maxRed, maxGreen, maxBlue;
+
+    public ImageCodec(int bpp, int flags, int maxAlpha, int maxRed, int maxGreen, int maxBlue) {
+        this.bpp = bpp;
+        this.flags = flags;
+        this.maxAlpha = maxAlpha;
+        this.maxRed = maxRed;
+        this.maxGreen = maxGreen;
+        this.maxBlue = maxBlue;
+    }
+
+    static {       
+        // == ALPHA ==
+        params.put(Format.Alpha8,   new BitMaskImageCodec(1, 0, 8, 0, 0, 0,
+                                                                0, 0, 0, 0));
+        
+        params.put(Format.Alpha16,  new BitMaskImageCodec(2, 0, 16, 0, 0, 0,
+                                                                0,  0, 0, 0));
+        
+        // == LUMINANCE ==
+        params.put(Format.Luminance8, new BitMaskImageCodec(1, FLAG_GRAY, 0, 8, 0, 0,
+                                                                          0, 0, 0, 0));
+        params.put(Format.Luminance16, new BitMaskImageCodec(2, FLAG_GRAY, 0, 16, 0, 0,
+                                                                           0, 0, 0, 0));
+        params.put(Format.Luminance16F, new BitMaskImageCodec(2, FLAG_GRAY | FLAG_F16, 0, 16, 0, 0,
+                                                                                        0, 0, 0, 0));
+        params.put(Format.Luminance32F, new BitMaskImageCodec(4, FLAG_GRAY | FLAG_F32, 0, 32, 0, 0,
+                                                                                        0, 0, 0, 0));
+        
+        // == INTENSITY ==
+        // ??
+        
+        // == LUMINANCA ALPHA ==
+        params.put(Format.Luminance8Alpha8, new BitMaskImageCodec(2, FLAG_GRAY, 
+                                                                  8, 8, 0, 0,
+                                                                  8, 0, 0, 0));
+        
+        params.put(Format.Luminance16Alpha16, new BitMaskImageCodec(4, FLAG_GRAY, 
+                                                                  16, 16, 0, 0,
+                                                                  16, 0, 0, 0));
+        
+        params.put(Format.Luminance16FAlpha16F, new BitMaskImageCodec(4, FLAG_GRAY | FLAG_F16, 
+                                                                   16, 16, 0, 0,
+                                                                   16, 0, 0, 0));
+        
+        // == RGB ==
+        params.put(Format.BGR8,     new BitMaskImageCodec(3, 0, 
+                                                          0, 8,  8,  8,
+                                                          0, 16, 8,  0));
+        
+        params.put(Format.RGB565,       new BitMaskImageCodec(2, 0,
+                                                            0, 5,  6, 5,
+                                                            0, 11, 5, 0));
+        
+        params.put(Format.RGB8,         new BitMaskImageCodec(3, 0,
+                                                            0, 8, 8, 8,
+                                                            0, 0, 8, 16));
+        
+        params.put(Format.RGB16,        new ByteAlignedImageCodec(6, 0,
+                                                                  0, 2, 2, 2,
+                                                                  0, 0, 2, 4));
+       
+        params.put(Format.RGB32F,        new ByteAlignedImageCodec(12, FLAG_F32,
+                                                                   0,  4, 4, 4,
+                                                                   0,  0, 4, 8));
+        
+        ByteAlignedImageCodec rgb16f = new ByteAlignedImageCodec(6, FLAG_F16,
+                                                            0, 2, 2, 2,
+                                                            0, 0, 2, 4); 
+        params.put(Format.RGB16F, rgb16f);
+        params.put(Format.RGB16F_to_RGB111110F, rgb16f);
+        params.put(Format.RGB16F_to_RGB9E5, rgb16f);
+        
+        
+        // == RGBA ==
+        params.put(Format.ABGR8,    new BitMaskImageCodec(4, 0,
+                                                          0, 8, 8, 8,
+                                                          0, 24, 16, 8));
+        
+        params.put(Format.ARGB4444, new BitMaskImageCodec(2, 0,
+                                                          4, 4, 4, 4,
+                                                          12, 0, 4, 8));
+
+        params.put(Format.RGB5A1,   new BitMaskImageCodec(2, 0, 
+                                                          1, 5, 5, 5,
+                                                          0, 11, 6, 1));
+        ((BitMaskImageCodec)params.get(Format.RGB5A1)).be = true;
+       
+        params.put(Format.RGBA8,    new ByteAlignedImageCodec(4, 0,
+                                                              0, 1, 1, 1,
+                                                              0, 0, 1, 2));
+                
+                //new BitMaskImageCodec(4, 0,
+                                    //                      8,  8, 8, 8,
+                                    //                      24,  0, 8, 16));
+        
+        params.put(Format.RGBA16,        new ByteAlignedImageCodec(8, 0,
+                                                                   2, 2, 2, 2,
+                                                                   6, 0, 2, 4));
+        
+        params.put(Format.RGBA16F,        new ByteAlignedImageCodec(8, FLAG_F16,
+                                                            2, 2, 2,  2,
+                                                            6, 0, 2,  4));
+        
+        params.put(Format.RGBA32F,        new ByteAlignedImageCodec(16, FLAG_F32,
+                                                            4, 4, 4, 4,
+                                                            12, 0, 4, 8));
+    }
+    
+    public abstract void readComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp);
+    
+    public abstract void writeComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp);
+    
+    /**
+     * Looks up the format in the codec registry.
+     * The codec will be able to decode the given format.
+     * 
+     * @param format The format to lookup.
+     * @return The codec capable of decoding it, or null if not found.
+     */
+    public static ImageCodec lookup(Format format) {
+        ImageCodec codec = params.get(format);
+        if (codec == null) {
+            throw new UnsupportedOperationException("The format " + format + " is not supported");
+        }
+        return codec;
+    }
+}

+ 231 - 0
engine/src/core/com/jme3/texture/image/ImageRaster.java

@@ -0,0 +1,231 @@
+package com.jme3.texture.image;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.texture.Image;
+import java.nio.ByteBuffer;
+
+/**
+ * Utility class for reading and writing from jME3 {@link Image images}.
+ * <br>
+ * Allows directly manipulating pixels of the image by writing and 
+ * reading {@link ColorRGBA colors} at any coordinate, without
+ * regard to the underlying {@link Image.Format format} of the image.
+ * NOTE: compressed and depth formats are <strong>not supported</strong>.
+ * Special RGB formats like RGB111110F and RGB9E5 are not supported
+ * at the moment, but may be added later on. For now 
+ * use RGB16F_to_RGB111110F and RGB16F_to_RGB9E5 to handle
+ * the conversion on the GPU.
+ * <p>
+ * If direct manipulations are done to the image, such as replacing
+ * the image data, or changing the width, height, or format, then
+ * all current instances of <code>ImageReadWrite</code> become invalid, and
+ * new instances must be created in order to properly access
+ * the image data.
+ * 
+ * Usage example:<br>
+ * <code>
+ * Image myImage = ...
+ * ImageReadWrite imageRW = new ImageReadWrite(myImage);
+ * imageRW.setPixel(1, 5, ColorRGBA.Green);
+ * System.out.println( imageRW.getPixel(1, 5) ); // Will print [0.0, 1.0, 0.0, 1.0].
+ * </code>
+ * 
+ * @author Kirill Vainer
+ */
+public final class ImageRaster {
+
+    private final int[] components = new int[4];
+    private final ByteBuffer buffer;
+    private final Image image;
+    private final ImageCodec codec;
+    private final int width;
+    private final int height;
+    private final byte[] temp;
+
+    /**
+     * Create new image reader / writer.
+     *
+     * @param image The image to read / write to.
+     * @param slice Which slice to use. Only applies to 3D images, 2D image
+     * arrays or cubemaps.
+     */
+    public ImageRaster(Image image, int slice) {
+        this.image = image;
+        this.buffer = image.getData(slice);
+        this.codec = ImageCodec.lookup(image.getFormat());
+        this.width = image.getWidth();
+        this.height = image.getHeight();
+        if (codec instanceof ByteAlignedImageCodec) {
+            this.temp = new byte[codec.bpp];
+        } else {
+            this.temp = null;
+        }
+    }
+    
+    /**
+     * Create new image reader / writer for 2D images.
+     * 
+     * @param image The image to read / write to.
+     */
+    public ImageRaster(Image image) {
+        this(image, 0);
+        if (image.getData().size() > 1) {
+            throw new IllegalStateException("Use constructor that takes slices argument to read from multislice image");
+        }
+    }
+    
+    private void rangeCheck(int x, int y) {
+        if (x < 0 || y < 0 || x >= width || y >= height) {
+            throw new IllegalArgumentException("x and y must be inside the image dimensions");
+        }
+    }
+    
+    /**
+     * Sets the pixel at the given coordinate to the given color.
+     * <p>
+     * For all integer based formats (those not ending in "F"), the 
+     * color is first clamped to 0.0 - 1.0 before converting it to
+     * an integer to avoid overflow. For floating point based formats, 
+     * components larger than 1.0 can be represented, but components
+     * lower than 0.0 are still not allowed (as all formats are unsigned).
+     * <p>
+     * If the underlying format is grayscale (e.g. one of the luminance formats,
+     * such as {@link Image.Format#Luminance8}) then a color to grayscale
+     * conversion is done first, before writing the result into the image.
+     * <p>
+     * If the image does not have some of the components in the color (such
+     * as alpha, or any of the color components), then these components
+     * will be ignored. The only exception to this is luminance formats
+     * for which the color is converted to luminance first (see above).
+     * <p>
+     * After writing the color, the image shall be marked as requiring an
+     * update. The next time it is used for rendering, all pixel changes
+     * will be reflected when the image is rendered.
+     * 
+     * @param x The x coordinate, from 0 to width - 1.
+     * @param y The y coordinate, from 0 to height - 1.
+     * @param color The color to write. 
+     * @throws IllegalArgumentException If x or y are outside the image dimensions.
+     */
+    public void setPixel(int x, int y, ColorRGBA color) {
+        rangeCheck(x, y);
+        
+        // Check flags for grayscale
+        if ((codec.flags & ImageCodec.FLAG_GRAY) != 0) {
+            float gray = color.r * 0.27f + color.g * 0.67f + color.b * 0.06f;
+            color = new ColorRGBA(gray, gray, gray, color.a);
+        }
+
+        if ((codec.flags & ImageCodec.FLAG_F16) != 0) {
+            components[0] = (int) FastMath.convertFloatToHalf(color.a);
+            components[1] = (int) FastMath.convertFloatToHalf(color.r);
+            components[2] = (int) FastMath.convertFloatToHalf(color.g);
+            components[3] = (int) FastMath.convertFloatToHalf(color.b);
+        } else if ((codec.flags & ImageCodec.FLAG_F32) != 0) {
+            components[0] = (int) Float.floatToIntBits(color.a);
+            components[1] = (int) Float.floatToIntBits(color.r);
+            components[2] = (int) Float.floatToIntBits(color.g);
+            components[3] = (int) Float.floatToIntBits(color.b);
+        } else {
+            // Convert color to bits by multiplying by size
+            components[0] = Math.min( (int) (color.a * codec.maxAlpha + 0.5f), codec.maxAlpha);
+            components[1] = Math.min( (int) (color.r * codec.maxRed + 0.5f), codec.maxRed);
+            components[2] = Math.min( (int) (color.g * codec.maxGreen + 0.5f), codec.maxGreen);
+            components[3] = Math.min( (int) (color.b * codec.maxBlue + 0.5f), codec.maxBlue);
+        }
+
+        codec.writeComponents(buffer, x, y, width, components, temp);
+        
+        image.setUpdateNeeded();
+    }
+    
+    /**
+     * Retrieve the color at the given coordinate.
+     * <p>
+     * Any components that are not defined in the image format
+     * will be set to 1.0 in the returned color. For example,
+     * reading from an {@link Image.Format#Alpha8} format will
+     * return a ColorRGBA with the R, G, and B components set to 1.0, and
+     * the A component set to the alpha in the image.
+     * <p>
+     * For grayscale or luminance formats, the luminance value is replicated
+     * in the R, G, and B components. 
+     * <p>
+     * Integer formats are converted to the range 0.0 - 1.0, based
+     * on the maximum possible integer value that can be represented
+     * by the number of bits the component has.
+     * For example, the {@link Image.Format#RGB5A1} format can
+     * contain the integer values 0 - 31, a conversion to floating point
+     * is done by diving the integer value by 31 (done with floating point
+     * precision).
+     * 
+     * @param x The x coordinate, from 0 to width - 1.
+     * @param y The y coordinate, from 0 to height - 1.
+     * @param store Storage location for the read color, if <code>null</code>, 
+     * then a new ColorRGBA is created and returned with the read color.
+     * @return The store parameter, if it is null, then a new ColorRGBA
+     * with the read color.
+     * @throws IllegalArgumentException If x or y are outside the image dimensions.
+     */
+    public ColorRGBA getPixel(int x, int y, ColorRGBA store) {
+        rangeCheck(x, y);
+        
+        codec.readComponents(buffer, x, y, width, components, temp);
+     
+        if (store == null) {
+            store = new ColorRGBA();
+        }
+        if ((codec.flags & ImageCodec.FLAG_F16) != 0) {
+            store.set(FastMath.convertHalfToFloat((short)components[1]),
+                      FastMath.convertHalfToFloat((short)components[2]),
+                      FastMath.convertHalfToFloat((short)components[3]),
+                      FastMath.convertHalfToFloat((short)components[0]));
+        } else if ((codec.flags & ImageCodec.FLAG_F32) != 0) {
+            store.set(Float.intBitsToFloat((int)components[1]),
+                      Float.intBitsToFloat((int)components[2]),
+                      Float.intBitsToFloat((int)components[3]),
+                      Float.intBitsToFloat((int)components[0]));
+        } else {
+            // Convert to float and divide by bitsize to get into range 0.0 - 1.0.
+            store.set((float)components[1] / codec.maxRed,
+                      (float)components[2] / codec.maxGreen,
+                      (float)components[3] / codec.maxBlue,
+                      (float)components[0] / codec.maxAlpha);
+        }
+        if ((codec.flags & ImageCodec.FLAG_GRAY) != 0) {
+            store.g = store.b = store.r;
+        } else {
+            if (codec.maxRed == 0) {
+                store.r = 1;
+            }
+            if (codec.maxGreen == 0) {
+                store.g = 1;
+            }
+            if (codec.maxBlue == 0) {
+                store.b = 1;
+            }
+            if (codec.maxAlpha == 0) {
+                store.a = 1;
+            }
+        }
+        return store;
+    }
+    
+    /**
+     * Retrieve the color at the given coordinate.
+     * <p>
+     * Convenience method that does not take a store argument. Equivalent
+     * to calling getPixel(x, y, null). 
+     * See {@link #getPixel(int, int, com.jme3.math.ColorRGBA) } for
+     * more information.
+     * 
+     * @param x The x coordinate, from 0 to width - 1.
+     * @param y The y coordinate, from 0 to height - 1.
+     * @return A new ColorRGBA with the read color.
+     * @throws IllegalArgumentException If x or y are outside the image dimensions
+     */
+    public ColorRGBA getPixel(int x, int y) { 
+        return getPixel(x, y, null);
+    }
+}

+ 169 - 0
engine/src/test/jme3test/texture/TestImageRaster.java

@@ -0,0 +1,169 @@
+package jme3test.texture;
+
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.font.Rectangle;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.image.ImageRaster;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+
+public class TestImageRaster extends SimpleApplication {
+    
+    private Image convertImage(Image image, Format newFormat) {
+        int width = image.getWidth();
+        int height = image.getHeight();
+        ByteBuffer data = BufferUtils.createByteBuffer( (int)Math.ceil(newFormat.getBitsPerPixel() / 8.0) * width * height);
+        Image convertedImage = new Image(newFormat, width, height, data);
+        
+        ImageRaster sourceReader = new ImageRaster(image);
+        ImageRaster targetWriter = new ImageRaster(convertedImage);
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                ColorRGBA color = sourceReader.getPixel(x, y);
+                targetWriter.setPixel(x, y, color);
+            }
+        }
+        
+        return convertedImage;
+    }
+    
+    private void convertAndPutImage(Image image, float posX, float posY) {
+        Texture tex = new Texture2D(image);
+        tex.setMagFilter(MagFilter.Nearest);
+        tex.setMinFilter(MinFilter.NearestNoMipMaps);
+        tex.setAnisotropicFilter(16);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", tex);
+
+        Quad q = new Quad(5, 5);
+        Geometry g = new Geometry("quad", q);
+        g.setLocalTranslation(posX, posY - 5, -0.0001f);
+        g.setMaterial(mat);
+        rootNode.attachChild(g);
+
+        BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        BitmapText txt = new BitmapText(fnt);
+        txt.setBox(new Rectangle(0, 0, 5, 5));
+        txt.setQueueBucket(RenderQueue.Bucket.Transparent);
+        txt.setSize(0.5f);
+        txt.setText(image.getFormat().name());
+        txt.setLocalTranslation(posX, posY, 0);
+        rootNode.attachChild(txt);
+    }
+    
+    private Image createTestImage() {
+        Image testImage = new Image(Format.BGR8, 4, 3, BufferUtils.createByteBuffer(4 * 4 * 3));
+        
+        ImageRaster io = new ImageRaster(testImage);
+        io.setPixel(0, 0, ColorRGBA.Black);
+        io.setPixel(1, 0, ColorRGBA.Gray);
+        io.setPixel(2, 0, ColorRGBA.White);
+        io.setPixel(3, 0, ColorRGBA.White.mult(4)); // HDR color
+
+        io.setPixel(0, 1, ColorRGBA.Red);
+        io.setPixel(1, 1, ColorRGBA.Green);
+        io.setPixel(2, 1, ColorRGBA.Blue);
+        io.setPixel(3, 1, new ColorRGBA(0, 0, 0, 0));
+
+        io.setPixel(0, 2, ColorRGBA.Yellow);
+        io.setPixel(1, 2, ColorRGBA.Magenta);
+        io.setPixel(2, 2, ColorRGBA.Cyan);
+        io.setPixel(3, 2, new ColorRGBA(1, 1, 1, 0));
+        
+        return testImage;
+    }
+    
+    @Override
+    public void simpleInitApp() {
+        cam.setLocation(new Vector3f(16, 6, 36));
+        flyCam.setMoveSpeed(10);
+        
+        Texture tex = assetManager.loadTexture("com/jme3/app/Monkey.png");
+//        Texture tex = assetManager.loadTexture("Textures/HdrTest/Memorial.hdr");
+        Image originalImage = tex.getImage();
+        
+        Image image = convertImage(originalImage, Format.RGBA32F);
+        convertAndPutImage(image, 0, 0);
+        
+        image = convertImage(originalImage, Format.RGB32F);
+        convertAndPutImage(image, 5, 0);
+        
+        image = convertImage(originalImage, Format.RGBA16F);
+        convertAndPutImage(image, 10, 0);
+        
+        image = convertImage(originalImage, Format.RGB16F);
+        convertAndPutImage(image, 15, 0);
+        
+        image = convertImage(originalImage, Format.RGB16F_to_RGB9E5);
+        convertAndPutImage(image, 20, 0);
+        
+        image = convertImage(originalImage, Format.RGB16F_to_RGB111110F);
+        convertAndPutImage(image, 25, 0);
+        
+        image = convertImage(originalImage, Format.RGBA16);
+        convertAndPutImage(image, 0, 5);
+        
+        image = convertImage(originalImage, Format.RGB16);
+        convertAndPutImage(image, 5, 5);
+        
+        image = convertImage(originalImage, Format.RGBA8);
+        convertAndPutImage(image, 10, 5);
+        
+        image = convertImage(originalImage, Format.RGB8);
+        convertAndPutImage(image, 15, 5);
+        
+        image = convertImage(originalImage, Format.ABGR8);
+        convertAndPutImage(image, 20, 5);
+        
+        image = convertImage(originalImage, Format.BGR8);
+        convertAndPutImage(image, 25, 5);
+        
+        image = convertImage(originalImage, Format.RGB5A1);
+        convertAndPutImage(image, 0, 10);
+        
+        image = convertImage(originalImage, Format.ARGB4444);
+        convertAndPutImage(image, 5, 10);
+        
+        image = convertImage(originalImage, Format.Luminance32F);
+        convertAndPutImage(image, 0, 15);
+        
+        image = convertImage(originalImage, Format.Luminance16FAlpha16F);
+        convertAndPutImage(image, 5, 15);
+        
+        image = convertImage(originalImage, Format.Luminance16F);
+        convertAndPutImage(image, 10, 15);
+        
+        image = convertImage(originalImage, Format.Luminance16Alpha16);
+        convertAndPutImage(image, 15, 15);
+        
+        image = convertImage(originalImage, Format.Luminance16);
+        convertAndPutImage(image, 20, 15);
+        
+        image = convertImage(originalImage, Format.Luminance8Alpha8);
+        convertAndPutImage(image, 25, 15);
+        
+        image = convertImage(originalImage, Format.Luminance8);
+        convertAndPutImage(image, 30, 15);
+    }
+    
+    public static void main(String[] args) {
+        TestImageRaster app = new TestImageRaster();
+        app.start();
+    }
+}