From 073c9744fa4a8982850a0f8d61275f8782497bbb Mon Sep 17 00:00:00 2001
From: Sven Gothel <sgothel@jausoft.com>
Date: Sat, 7 Apr 2012 15:31:06 +0200
Subject: TextureIO: Add PNG TextureProvider and TextureWriter for
 RGB[A]/BGR[A] - incl. unit tests; Test/Demos: Use PNG snapshots.

---
 .../com/jogamp/opengl/util/texture/TextureIO.java  |  97 +++++++++++-
 .../jogamp/opengl/util/texture/spi/DDSImage.java   |   3 +-
 .../util/texture/spi/NetPbmTextureWriter.java      |   2 +-
 .../jogamp/opengl/util/texture/spi/PNGImage.java   | 172 +++++++++++++++++++++
 .../jogamp/opengl/util/texture/spi/SGIImage.java   |   5 +-
 .../jogamp/opengl/util/texture/spi/TGAImage.java   |   4 +-
 6 files changed, 277 insertions(+), 6 deletions(-)
 create mode 100644 src/jogl/classes/com/jogamp/opengl/util/texture/spi/PNGImage.java

(limited to 'src/jogl/classes/com/jogamp/opengl/util')

diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java
index 89d840ac7..16e031f05 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java
@@ -64,6 +64,7 @@ import jogamp.opengl.Debug;
 import com.jogamp.common.util.IOUtil;
 import com.jogamp.opengl.util.texture.spi.DDSImage;
 import com.jogamp.opengl.util.texture.spi.NetPbmTextureWriter;
+import com.jogamp.opengl.util.texture.spi.PNGImage;
 import com.jogamp.opengl.util.texture.spi.SGIImage;
 import com.jogamp.opengl.util.texture.spi.TGAImage;
 import com.jogamp.opengl.util.texture.spi.TextureProvider;
@@ -775,6 +776,7 @@ public class TextureIO {
         }
 
         // Other special-case providers
+        addTextureProvider(new PNGTextureProvider());
         addTextureProvider(new DDSTextureProvider());
         addTextureProvider(new SGITextureProvider());
         addTextureProvider(new TGATextureProvider());
@@ -798,6 +800,7 @@ public class TextureIO {
         }
 
         // Other special-case writers
+        addTextureWriter(new PNGTextureWriter());
         addTextureWriter(new DDSTextureWriter());
         addTextureWriter(new SGITextureWriter());
         addTextureWriter(new TGATextureWriter());
@@ -1102,7 +1105,44 @@ public class TextureIO {
                     pixelFormat = image.getGLFormat();
                 }
                 if (internalFormat == 0) {
-                    if(glp.isGL2()) {
+                    if(glp.isGL2GL3()) {
+                        internalFormat = GL.GL_RGBA8;
+                    } else {
+                        internalFormat = (image.getBytesPerPixel()==4)?GL.GL_RGBA:GL.GL_RGB;
+                    }
+                }
+                return new TextureData(glp, internalFormat,
+                                       image.getWidth(),
+                                       image.getHeight(),
+                                       0,
+                                       pixelFormat,
+                                       GL.GL_UNSIGNED_BYTE,
+                                       mipmap,
+                                       false,
+                                       false,
+                                       image.getData(),
+                                       null);
+            }
+
+            return null;
+        }
+    }
+
+    //----------------------------------------------------------------------
+    // PNG image provider
+    static class PNGTextureProvider extends StreamBasedTextureProvider {
+        public TextureData newTextureData(GLProfile glp, InputStream stream,
+                                          int internalFormat,
+                                          int pixelFormat,
+                                          boolean mipmap,
+                                          String fileSuffix) throws IOException {
+            if (PNG.equals(fileSuffix)) {
+                PNGImage image = PNGImage.read(/*glp, */ stream);
+                if (pixelFormat == 0) {
+                    pixelFormat = image.getGLFormat();
+                }
+                if (internalFormat == 0) {
+                    if(glp.isGL2GL3()) {
                         internalFormat = GL.GL_RGBA8;
                     } else {
                         internalFormat = (image.getBytesPerPixel()==4)?GL.GL_RGBA:GL.GL_RGB;
@@ -1267,6 +1307,61 @@ public class TextureIO {
         }    
     }
 
+    //----------------------------------------------------------------------
+    // PNG texture writer
+  
+    static class PNGTextureWriter implements TextureWriter {
+        public boolean write(File file, TextureData data) throws IOException {
+            if (PNG.equals(IOUtil.getFileSuffix(file))) {
+                // See whether the PNG writer can handle this TextureData
+                int pixelFormat = data.getPixelFormat();
+                int pixelType   = data.getPixelType();
+                boolean reversedChannels;
+                int bytesPerPixel;
+                switch(pixelFormat) {
+                    case GL.GL_RGB:
+                        reversedChannels=false;
+                        bytesPerPixel=3;
+                        break;
+                    case GL.GL_RGBA:
+                        reversedChannels=false;
+                        bytesPerPixel=4;
+                        break;
+                    case GL2.GL_BGR:
+                        reversedChannels=true;
+                        bytesPerPixel=3;
+                        break;
+                    case GL.GL_BGRA:
+                        reversedChannels=true;
+                        bytesPerPixel=4;
+                        break;
+                    default:
+                        reversedChannels=false;
+                        bytesPerPixel=-1;
+                        break;
+                }
+                if ( 1 < bytesPerPixel &&
+                    (pixelType == GL.GL_BYTE ||
+                     pixelType == GL.GL_UNSIGNED_BYTE)) {
+                    
+                    ByteBuffer buf = (ByteBuffer) data.getBuffer();
+                    if (null == buf) {
+                        buf = (ByteBuffer) data.getMipmapData()[0];
+                    }
+                    buf.rewind();
+                    
+                    PNGImage image = PNGImage.createFromData(data.getWidth(), data.getHeight(), -1f, -1f,
+                                                             bytesPerPixel, reversedChannels, buf);
+                    image.write(file, true);
+                    return true;
+                }
+                throw new IOException("PNG writer doesn't support this pixel format 0x"+Integer.toHexString(pixelFormat)+
+                                      " / type 0x"+Integer.toHexString(pixelFormat)+" (only GL_RGB/A, GL_BGR/A + bytes)");
+            }
+            return false;
+        }    
+    }
+    
     //----------------------------------------------------------------------
     // Helper routines
     //
diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/DDSImage.java b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/DDSImage.java
index 674e53182..306e7d2fe 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/DDSImage.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/DDSImage.java
@@ -52,6 +52,7 @@ import java.nio.channels.FileChannel;
 
 import javax.media.opengl.GL;
 
+import com.jogamp.common.util.IOUtil;
 import com.jogamp.opengl.util.GLBuffers;
 
 /** A reader and writer for DirectDraw Surface (.dds) files, which are
@@ -281,7 +282,7 @@ public class DDSImage {
      * @throws java.io.IOException if an I/O exception occurred
      */
     public void write(File file) throws IOException {
-        FileOutputStream stream = new FileOutputStream(file);
+        FileOutputStream stream = IOUtil.getFileOutputStream(file, true);
         FileChannel chan = stream.getChannel();
         // Create ByteBuffer for header in case the start of our
         // ByteBuffer isn't actually memory-mapped
diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/NetPbmTextureWriter.java b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/NetPbmTextureWriter.java
index 216c994c0..c2b131b97 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/NetPbmTextureWriter.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/NetPbmTextureWriter.java
@@ -138,7 +138,7 @@ public class NetPbmTextureWriter implements TextureWriter {
                 throw new IOException("NetPbmTextureWriter magic 6 (PPM) doesn't RGBA pixel format, use magic 7 (PAM)");
             }
 
-            FileOutputStream fos = new FileOutputStream(file);
+            FileOutputStream fos = IOUtil.getFileOutputStream(file, true);
             
             StringBuilder header = new StringBuilder();
             header.append("P");
diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/PNGImage.java b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/PNGImage.java
new file mode 100644
index 000000000..a89418f84
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/PNGImage.java
@@ -0,0 +1,172 @@
+package com.jogamp.opengl.util.texture.spi;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+import javax.media.opengl.GL;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.ImageLine;
+import jogamp.opengl.util.pngj.PngReader;
+import jogamp.opengl.util.pngj.PngWriter;
+import jogamp.opengl.util.pngj.chunks.PngChunkTextVar;
+
+import com.jogamp.common.nio.Buffers;
+import com.jogamp.common.util.IOUtil;
+
+
+public class PNGImage {    
+    /** Creates a PNGImage from data supplied by the end user. Shares
+        data with the passed ByteBuffer. Assumes the data is already in
+        the correct byte order for writing to disk, i.e., RGB or RGBA bottom-to-top (OpenGL coord). */
+    public static PNGImage createFromData(int width, int height, double dpiX, double dpiY,
+                                          int bytesPerPixel, boolean reversedChannels, ByteBuffer data) {
+        return new PNGImage(width, height, dpiX, dpiY, bytesPerPixel, reversedChannels, data);
+    }
+    
+    /** Reads a PNG image from the specified InputStream. */
+    public static PNGImage read(InputStream in) throws IOException {
+        return new PNGImage(in);
+    }
+    
+    /** Reverse read and store, implicitly flip image to GL coords. */
+    private static final int getPixelRGBA8(ByteBuffer d, int dOff, ImageLine line, int lineOff, boolean hasAlpha) {
+        if(hasAlpha) {
+            d.put(dOff--, (byte)line.scanline[lineOff + 3]); // A
+        }
+        d.put(dOff--, (byte)line.scanline[lineOff + 2]); // B
+        d.put(dOff--, (byte)line.scanline[lineOff + 1]); // G        
+        d.put(dOff--, (byte)line.scanline[lineOff    ]); // R
+        return dOff;
+    }    
+    /** Reverse read and store, implicitly flip image from GL coords. */
+    private static int setPixelRGBA8(ImageLine line, int lineOff, ByteBuffer d, int dOff, boolean hasAlpha, boolean reversedChannels) {
+        if(reversedChannels) {
+            line.scanline[lineOff    ] = d.get(dOff--); // R, A
+            line.scanline[lineOff + 1] = d.get(dOff--); // G, B
+            line.scanline[lineOff + 2] = d.get(dOff--); // B, G
+            if(hasAlpha) {
+                line.scanline[lineOff + 3] = d.get(dOff--);// R
+            }
+        } else {
+            if(hasAlpha) {
+                line.scanline[lineOff + 3] = d.get(dOff--); // A
+            }
+            line.scanline[lineOff + 2] = d.get(dOff--); // B
+            line.scanline[lineOff + 1] = d.get(dOff--); // G
+            line.scanline[lineOff    ] = d.get(dOff--); // R
+        }
+        return dOff;
+    }
+
+    private PNGImage(int width, int height, double dpiX, double dpiY, int bytesPerPixel, boolean reversedChannels, ByteBuffer data) {
+        pixelWidth=width;
+        pixelHeight=height;
+        dpi = new double[] { dpiX, dpiY };
+        if(4 == bytesPerPixel) {
+            glFormat = GL.GL_RGBA;
+        } else if (3 == bytesPerPixel) {
+            glFormat = GL.GL_RGB;
+        } else {
+            throw new InternalError("XXX: bytesPerPixel "+bytesPerPixel);
+        }
+        this.bytesPerPixel = bytesPerPixel;
+        this.reversedChannels = reversedChannels;
+        this.data = data;        
+    }
+    
+    private PNGImage(InputStream in) {
+        final PngReader pngr = new PngReader(new BufferedInputStream(in), null);
+        final int channels = pngr.imgInfo.channels;
+        if (3 > channels || channels > 4 ) {
+            throw new RuntimeException("PNGImage can only handle RGB/RGBA images for now. Channels "+channels);
+        }
+        bytesPerPixel=pngr.imgInfo.bytesPixel;
+        if (3 > bytesPerPixel || bytesPerPixel > 4 ) {
+            throw new RuntimeException("PNGImage can only handle RGB/RGBA images for now. BytesPerPixel "+bytesPerPixel);
+        }
+        pixelWidth=pngr.imgInfo.cols;
+        pixelHeight=pngr.imgInfo.rows;
+        dpi = new double[2];
+        {
+            final double[] dpi2 = pngr.getMetadata().getDpi();
+            dpi[0]=dpi2[0];
+            dpi[1]=dpi2[1];
+        }        
+        glFormat= ( 4 == bytesPerPixel ) ? GL.GL_RGBA : GL.GL_RGB;
+        data = Buffers.newDirectByteBuffer(bytesPerPixel * pixelWidth * pixelHeight);
+        reversedChannels = false; // RGB[A]
+        final boolean hasAlpha = 4 == bytesPerPixel;
+        int dataOff = bytesPerPixel * pixelWidth * pixelHeight - 1; // start at end-of-buffer, reverse store
+        for (int row = 0; row < pixelHeight; row++) {
+            final ImageLine l1 = pngr.readRow(row);
+            int lineOff = ( pixelWidth - 1 ) * bytesPerPixel ;      // start w/ last pixel in line, reverse read
+            for (int j = pixelWidth - 1; j >= 0; j--) {
+                dataOff = getPixelRGBA8(data, dataOff, l1, lineOff, hasAlpha);
+                lineOff -= bytesPerPixel;
+            }
+        }
+        pngr.end();
+    }
+    private final int pixelWidth, pixelHeight, glFormat, bytesPerPixel;
+    private boolean reversedChannels;
+    private final double[] dpi;
+    private final ByteBuffer data;
+    
+    /** Returns the width of the image. */
+    public int getWidth()    { return pixelWidth; }
+
+    /** Returns the height of the image. */
+    public int getHeight()   { return pixelHeight; }
+
+    /** Returns true if data has the channels reversed to BGR or BGRA, otherwise RGB or RGBA is expected. */
+    public boolean getHasReversedChannels() { return reversedChannels; }
+    
+    /** Returns the dpi of the image. */
+    public double[] getDpi() { return dpi; }
+    
+    /** Returns the OpenGL format for this texture; e.g. GL.GL_BGR or GL.GL_BGRA. */
+    public int getGLFormat() { return glFormat; }
+
+    /** Returns the bytes per pixel */
+    public int getBytesPerPixel() { return bytesPerPixel; }
+
+    /** Returns the raw data for this texture in the correct
+        (bottom-to-top) order for calls to glTexImage2D. */
+    public ByteBuffer getData()  { return data; }
+
+    public void write(File out, boolean allowOverwrite) throws IOException {        
+        final ImageInfo imi = new ImageInfo(pixelWidth, pixelHeight, 8, (4 == bytesPerPixel) ? true : false); // 8 bits per channel, no alpha 
+        // open image for writing to a output stream
+        final OutputStream outs = new BufferedOutputStream(IOUtil.getFileOutputStream(out, allowOverwrite));
+        try {
+            final PngWriter png = new PngWriter(outs, imi); 
+            // add some optional metadata (chunks)
+            png.getMetadata().setDpi(dpi[0], dpi[1]);
+            png.getMetadata().setTimeNow(0); // 0 seconds fron now = now
+            png.getMetadata().setText(PngChunkTextVar.KEY_Title, "JogAmp PNGImage");
+            // png.getMetadata().setText("my key", "my text");
+            final boolean hasAlpha = 4 == bytesPerPixel;
+            final ImageLine l1 = new ImageLine(imi);
+            int dataOff = bytesPerPixel * pixelWidth * pixelHeight - 1; // start at end-of-buffer, reverse read
+            for (int row = 0; row < pixelHeight; row++) {
+                int lineOff = ( pixelWidth - 1 ) * bytesPerPixel ;      // start w/ last pixel in line, reverse store
+                for (int j = pixelWidth - 1; j >= 0; j--) {
+                    dataOff = setPixelRGBA8(l1, lineOff, data, dataOff, hasAlpha, reversedChannels);
+                    lineOff -= bytesPerPixel;
+                }
+                png.writeRow(l1, row);
+            }
+            png.end();
+        } finally {
+            IOUtil.close(outs, false);
+        }
+    }
+    
+    public String toString() { return "PNGImage["+pixelWidth+"x"+pixelHeight+", dpi "+dpi[0]+" x "+dpi[1]+", bytesPerPixel "+bytesPerPixel+", reversedChannels "+reversedChannels+", "+data+"]"; }       
+}
diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/SGIImage.java b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/SGIImage.java
index c60c91bda..d35330f58 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/SGIImage.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/SGIImage.java
@@ -41,7 +41,8 @@ package com.jogamp.opengl.util.texture.spi;
 
 import java.io.*;
 import javax.media.opengl.*;
-import com.jogamp.opengl.util.*;
+
+import com.jogamp.common.util.IOUtil;
 
 /** <p> Reads and writes SGI RGB/RGBA images. </p>
 
@@ -584,7 +585,7 @@ public class SGIImage {
         if (DEBUG) 
             System.err.println("total_size was " + total_size);
 
-        DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
+        DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(IOUtil.getFileOutputStream(file, true)));
 
         writeHeader(stream, xsize, ysize, zsize, true);
 
diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/TGAImage.java b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/TGAImage.java
index c64644350..e202c59b7 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/TGAImage.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/TGAImage.java
@@ -44,6 +44,8 @@ import java.nio.*;
 import java.nio.channels.*;
 import javax.media.opengl.*;
 
+import com.jogamp.common.util.IOUtil;
+
 /**
  * Targa image reader and writer adapted from sources of the <a href =
  * "http://java.sun.com/products/jimi/">Jimi</a> image I/O class library.
@@ -379,7 +381,7 @@ public class TGAImage {
 
     /** Writes the image in Targa format to the specified file. */
     public void write(File file) throws IOException {
-        FileOutputStream stream = new FileOutputStream(file);
+        FileOutputStream stream = IOUtil.getFileOutputStream(file, true);
         FileChannel chan = stream.getChannel();
         ByteBuffer buf = ByteBuffer.allocate(header.size());
         buf.order(ByteOrder.LITTLE_ENDIAN);
-- 
cgit v1.2.3