From 073c9744fa4a8982850a0f8d61275f8782497bbb Mon Sep 17 00:00:00 2001 From: Sven Gothel 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; /**

Reads and writes SGI RGB/RGBA images.

@@ -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 Jimi 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