aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorKenneth Russel <[email protected]>2006-01-09 02:37:43 +0000
committerKenneth Russel <[email protected]>2006-01-09 02:37:43 +0000
commitb464faaae70ba12e5842b901a6c6d1601af0e1c7 (patch)
tree6411322c882c48dfe04870adf981c33609fabefa /src
parentc1e4d8a8937ecce5bbc88c6de604ce35e793b9cc (diff)
Added output support to TGAImage, SGIImage and newly-renamed DDSImage
classes. Added support to TextureIO for writing textures back to disk via new TextureWriter plug-in interface. Added TextureConvert demo which shows how an application might convert between arbitrary file formats using these APIs, including automatic compression to DXT3 format when available. git-svn-id: file:///usr/local/projects/SUN/JOGL/git-svn/svn-server-sync/jogl/trunk@525 232f8b59-042b-4e1e-8c03-345bb8c30851
Diffstat (limited to 'src')
-rwxr-xr-xsrc/classes/com/sun/opengl/utils/DDSImage.java (renamed from src/classes/com/sun/opengl/utils/DDSReader.java)264
-rwxr-xr-xsrc/classes/com/sun/opengl/utils/SGIImage.java283
-rwxr-xr-xsrc/classes/com/sun/opengl/utils/TGAImage.java112
-rwxr-xr-xsrc/classes/com/sun/opengl/utils/Texture.java41
-rwxr-xr-xsrc/classes/com/sun/opengl/utils/TextureData.java11
-rwxr-xr-xsrc/classes/com/sun/opengl/utils/TextureIO.java454
-rwxr-xr-xsrc/classes/com/sun/opengl/utils/TextureWriter.java55
7 files changed, 1106 insertions, 114 deletions
diff --git a/src/classes/com/sun/opengl/utils/DDSReader.java b/src/classes/com/sun/opengl/utils/DDSImage.java
index dbbf8e65b..305e77348 100755
--- a/src/classes/com/sun/opengl/utils/DDSReader.java
+++ b/src/classes/com/sun/opengl/utils/DDSImage.java
@@ -43,12 +43,12 @@ import java.io.*;
import java.nio.*;
import java.nio.channels.*;
-/** A reader for DirectDraw Surface (.dds) files, which are used to
- describe textures. These files can contain multiple mipmap levels
- in one file. This reader is currently minimal and does not support
- all of the possible file formats. */
+/** A reader and writer for DirectDraw Surface (.dds) files, which are
+ used to describe textures. These files can contain multiple mipmap
+ levels in one file. This class is currently minimal and does not
+ support all of the possible file formats. */
-public class DDSReader {
+public class DDSImage {
/** Simple class describing images and data; does not encapsulate
image format information. User is responsible for transmitting
@@ -134,30 +134,90 @@ public class DDSReader {
public static final int D3DFMT_DXT4 = 0x34545844;
public static final int D3DFMT_DXT5 = 0x35545844;
- public void loadFile(String filename) throws IOException {
- loadFile(new File(filename));
+ /** Reads a DirectDraw surface from the specified file name,
+ returning the resulting DDSImage. */
+ public static DDSImage read(String filename) throws IOException {
+ return read(new File(filename));
}
-
- public void loadFile(File file) throws IOException {
- fis = new FileInputStream(file);
- chan = fis.getChannel();
- buf = chan.map(FileChannel.MapMode.READ_ONLY,
- 0, (int) file.length());
- buf.order(ByteOrder.LITTLE_ENDIAN);
- header = new Header();
- header.read(buf);
- fixupHeader();
+
+ /** Reads a DirectDraw surface from the specified file, returning
+ the resulting DDSImage. */
+ public static DDSImage read(File file) throws IOException {
+ DDSImage image = new DDSImage();
+ image.readFromFile(file);
+ return image;
}
+ /** Closes open files and resources associated with the open
+ DDSImage. No other methods may be called on this object once
+ this is called. */
public void close() {
try {
- chan.close();
- fis.close();
+ if (chan != null) {
+ chan.close();
+ chan = null;
+ }
+ if (fis != null) {
+ fis.close();
+ fis = null;
+ }
+ buf = null;
} catch (IOException e) {
e.printStackTrace();
}
}
+ /**
+ * Creates a new DDSImage from data supplied by the user. The
+ * resulting DDSImage can be written to disk using the write()
+ * method.
+ *
+ * @param d3dFormat the D3DFMT_ constant describing the data; it is
+ * assumed that it is packed tightly
+ * @param width the width in pixels of the topmost mipmap image
+ * @param height the height in pixels of the topmost mipmap image
+ * @param mipmapData the data for each mipmap level of the resulting
+ * DDSImage; either only one mipmap level should
+ * be specified, or they all must be
+ * @throws IllegalArgumentException if the data does not match the
+ * specified arguments
+ */
+ public static DDSImage createFromData(int d3dFormat,
+ int width,
+ int height,
+ ByteBuffer[] mipmapData) throws IllegalArgumentException {
+ DDSImage image = new DDSImage();
+ image.initFromData(d3dFormat, width, height, mipmapData);
+ return image;
+ }
+
+ /**
+ * Writes this DDSImage to the specified file name.
+ */
+ public void write(String filename) throws IOException {
+ write(new File(filename));
+ }
+
+ /**
+ * Writes this DDSImage to the specified file name.
+ */
+ public void write(File file) throws IOException {
+ FileOutputStream stream = new FileOutputStream(file);
+ FileChannel chan = stream.getChannel();
+ // Create ByteBuffer for header in case the start of our
+ // ByteBuffer isn't actually memory-mapped
+ ByteBuffer hdr = ByteBuffer.allocate(Header.writtenSize());
+ hdr.order(ByteOrder.LITTLE_ENDIAN);
+ header.write(hdr);
+ hdr.rewind();
+ chan.write(hdr);
+ buf.position(Header.writtenSize());
+ chan.write(buf);
+ chan.force(true);
+ chan.close();
+ stream.close();
+ }
+
/** Test for presence/absence of surface description flags (DDSD_*) */
public boolean isSurfaceDescFlagSet(int flag) {
return ((header.flags & flag) != 0);
@@ -246,7 +306,7 @@ public class DDSReader {
}
// Figure out how far to seek
- int seek = 4 + header.size;
+ int seek = Header.writtenSize();
for (int i = 0; i < map; i++) {
seek += mipMapSizeInBytes(i);
}
@@ -439,6 +499,152 @@ public class DDSReader {
ddsCapsReserved2 = buf.getInt();
textureStage = buf.getInt();
}
+
+ // buf must be in little-endian byte order
+ void write(ByteBuffer buf) {
+ buf.putInt(MAGIC);
+ buf.putInt(size);
+ buf.putInt(flags);
+ buf.putInt(height);
+ buf.putInt(width);
+ buf.putInt(pitchOrLinearSize);
+ buf.putInt(backBufferCountOrDepth);
+ buf.putInt(mipMapCountOrAux);
+ buf.putInt(alphaBitDepth);
+ buf.putInt(reserved1);
+ buf.putInt(surface);
+ buf.putInt(colorSpaceLowValue);
+ buf.putInt(colorSpaceHighValue);
+ buf.putInt(destBltColorSpaceLowValue);
+ buf.putInt(destBltColorSpaceHighValue);
+ buf.putInt(srcOverlayColorSpaceLowValue);
+ buf.putInt(srcOverlayColorSpaceHighValue);
+ buf.putInt(srcBltColorSpaceLowValue);
+ buf.putInt(srcBltColorSpaceHighValue);
+ buf.putInt(pfSize);
+ buf.putInt(pfFlags);
+ buf.putInt(pfFourCC);
+ buf.putInt(pfRGBBitCount);
+ buf.putInt(pfRBitMask);
+ buf.putInt(pfGBitMask);
+ buf.putInt(pfBBitMask);
+ buf.putInt(pfABitMask);
+ buf.putInt(ddsCaps1);
+ buf.putInt(ddsCaps2);
+ buf.putInt(ddsCapsReserved1);
+ buf.putInt(ddsCapsReserved2);
+ buf.putInt(textureStage);
+ }
+
+ private static final int size() {
+ return 124;
+ }
+
+ private static final int pfSize() {
+ return 32;
+ }
+
+ private static final int writtenSize() {
+ return 128;
+ }
+ }
+
+ private DDSImage() {
+ }
+
+ private void readFromFile(File file) throws IOException {
+ fis = new FileInputStream(file);
+ chan = fis.getChannel();
+ buf = chan.map(FileChannel.MapMode.READ_ONLY,
+ 0, (int) file.length());
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ header = new Header();
+ header.read(buf);
+ fixupHeader();
+ }
+
+ private void initFromData(int d3dFormat,
+ int width,
+ int height,
+ ByteBuffer[] mipmapData) throws IllegalArgumentException {
+ // Check size of mipmap data compared against format, width and
+ // height
+ int topmostMipmapSize = width * height;
+ int pitchOrLinearSize = width;
+ boolean isCompressed = false;
+ switch (d3dFormat) {
+ case D3DFMT_R8G8B8: topmostMipmapSize *= 3; pitchOrLinearSize *= 3; break;
+ case D3DFMT_A8R8G8B8: topmostMipmapSize *= 4; pitchOrLinearSize *= 4; break;
+ case D3DFMT_X8R8G8B8: topmostMipmapSize *= 4; pitchOrLinearSize *= 4; break;
+ case D3DFMT_DXT1:
+ case D3DFMT_DXT2:
+ case D3DFMT_DXT3:
+ case D3DFMT_DXT4:
+ case D3DFMT_DXT5:
+ topmostMipmapSize = computeCompressedBlockSize(width, height, 1, d3dFormat);
+ pitchOrLinearSize = topmostMipmapSize;
+ isCompressed = true;
+ break;
+ default:
+ throw new IllegalArgumentException("d3dFormat must be one of the known formats");
+ }
+
+ // Now check the mipmaps against this size
+ int curSize = topmostMipmapSize;
+ int totalSize = 0;
+ for (int i = 0; i < mipmapData.length; i++) {
+ if (mipmapData[i].remaining() != curSize) {
+ throw new IllegalArgumentException("Mipmap level " + i +
+ " didn't match expected data size (expected " + curSize + ", got " +
+ mipmapData[i].remaining() + ")");
+ }
+ curSize /= 4;
+ totalSize += mipmapData[i].remaining();
+ }
+
+ // OK, create one large ByteBuffer to hold all of the mipmap data
+ totalSize += Header.writtenSize();
+ ByteBuffer buf = ByteBuffer.allocate(totalSize);
+ buf.position(Header.writtenSize());
+ for (int i = 0; i < mipmapData.length; i++) {
+ buf.put(mipmapData[i]);
+ }
+ this.buf = buf;
+
+ // Allocate and initialize a Header
+ header = new Header();
+ header.size = Header.size();
+ header.flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
+ if (mipmapData.length > 1) {
+ header.flags |= DDSD_MIPMAPCOUNT;
+ header.mipMapCountOrAux = mipmapData.length;
+ }
+ header.width = width;
+ header.height = height;
+ if (isCompressed) {
+ header.flags |= DDSD_LINEARSIZE;
+ header.pfFlags |= DDPF_FOURCC;
+ header.pfFourCC = d3dFormat;
+ } else {
+ header.flags |= DDSD_PITCH;
+ // Figure out the various settings from the pixel format
+ header.pfFlags |= DDPF_RGB;
+ switch (d3dFormat) {
+ case D3DFMT_R8G8B8: header.pfRGBBitCount = 24; break;
+ case D3DFMT_A8R8G8B8: header.pfRGBBitCount = 32; header.pfFlags |= DDPF_ALPHAPIXELS; break;
+ case D3DFMT_X8R8G8B8: header.pfRGBBitCount = 32; break;
+ }
+ header.pfRBitMask = 0x00FF0000;
+ header.pfGBitMask = 0x0000FF00;
+ header.pfBBitMask = 0x000000FF;
+ if (d3dFormat == D3DFMT_A8R8G8B8) {
+ header.pfABitMask = 0xFF000000;
+ }
+ }
+ header.pitchOrLinearSize = pitchOrLinearSize;
+ header.pfSize = Header.pfSize();
+ // Not sure whether we can get away with leaving the rest of the
+ // header blank
}
// Microsoft doesn't follow their own specifications and the
@@ -454,17 +660,25 @@ public class DDSReader {
depth = 1;
}
- int blockSize = ((getWidth() + 3)/4) * ((getHeight() + 3)/4) * ((depth + 3)/4);
- switch (getCompressionFormat()) {
- case D3DFMT_DXT1: blockSize *= 8; break;
- default: blockSize *= 16; break;
- }
+ int blockSize = computeCompressedBlockSize(getWidth(), getHeight(), depth, getCompressionFormat());
header.pitchOrLinearSize = blockSize;
header.flags |= DDSD_LINEARSIZE;
}
}
+ private static int computeCompressedBlockSize(int width,
+ int height,
+ int depth,
+ int compressionFormat) {
+ int blockSize = ((width + 3)/4) * ((height + 3)/4) * ((depth + 3)/4);
+ switch (compressionFormat) {
+ case D3DFMT_DXT1: blockSize *= 8; break;
+ default: blockSize *= 16; break;
+ }
+ return blockSize;
+ }
+
private int mipMapWidth(int map) {
int width = getWidth();
for (int i = 0; i < map; i++) {
diff --git a/src/classes/com/sun/opengl/utils/SGIImage.java b/src/classes/com/sun/opengl/utils/SGIImage.java
index d115604b1..f06c10b56 100755
--- a/src/classes/com/sun/opengl/utils/SGIImage.java
+++ b/src/classes/com/sun/opengl/utils/SGIImage.java
@@ -47,7 +47,7 @@ import com.sun.opengl.utils.*;
import java.awt.image.*;
import javax.swing.*;
-/** <p> Reads SGI RGB/RGBA images. </p>
+/** <p> Reads and writes SGI RGB/RGBA images. </p>
<p> Written from <a href =
"http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/">Paul
@@ -104,6 +104,10 @@ public class SGIImage {
// 404 bytes char DUMMY Ignored
// Should be set to 0, makes the header 512 bytes.
+ Header() {
+ magic = MAGIC;
+ }
+
Header(DataInputStream in) throws IOException {
magic = in.readShort();
storage = in.readByte();
@@ -159,6 +163,35 @@ public class SGIImage {
return res;
}
+ /** Writes this SGIImage to the specified file name. If
+ flipVertically is set, outputs the scanlines from top to bottom
+ rather than the default bottom to top order. */
+ public void write(String filename, boolean flipVertically) throws IOException {
+ write(new File(filename), flipVertically);
+ }
+
+ /** Writes this SGIImage to the specified file. If flipVertically is
+ set, outputs the scanlines from top to bottom rather than the
+ default bottom to top order. */
+ public void write(File file, boolean flipVertically) throws IOException {
+ writeImage(file, data, header.xsize, header.ysize, header.zsize, flipVertically);
+ }
+
+ /** Creates an SGIImage from the specified data in either RGB or
+ RGBA format. */
+ public static SGIImage createFromData(int width,
+ int height,
+ boolean hasAlpha,
+ byte[] data) {
+ Header header = new Header();
+ header.xsize = (short) width;
+ header.ysize = (short) height;
+ header.zsize = (short) (hasAlpha ? 4 : 3);
+ SGIImage image = new SGIImage(header);
+ image.data = data;
+ return image;
+ }
+
/** Determines from the magic number whether the given InputStream
points to an SGI RGB image. The given InputStream must return
true from markSupported() and support a minimum of two bytes
@@ -258,6 +291,7 @@ public class SGIImage {
tmpData = null;
tmpRead = null;
format = GL.GL_RGBA;
+ header.zsize = 4;
}
private void getRow(byte[] buf, int y, int z) {
@@ -325,6 +359,250 @@ public class SGIImage {
}
}
+ private static byte imgref(byte[] i,
+ int x,
+ int y,
+ int z,
+ int xs,
+ int ys,
+ int zs) {
+ return i[(xs*ys*z)+(xs*y)+x];
+ }
+
+
+ private void writeHeader(DataOutputStream stream,
+ int xsize, int ysize, int zsize, boolean rle) throws IOException {
+ // effects: outputs the 512-byte IRIS RGB header to STREAM, using xsize,
+ // ysize, and depth as the dimensions of the image. NOTE that
+ // the following defaults are used:
+ // STORAGE = 1 (storage format = RLE)
+ // BPC = 1 (# bytes/channel)
+ // DIMENSION = 3
+ // PIXMIN = 0
+ // PIXMAX = 255
+ // IMAGENAME = <80 nulls>
+ // COLORMAP = 0
+ // See ftp://ftp.sgi.com/pub/sgi/SGIIMAGESPEC for more details.
+
+ // write out MAGIC, STORAGE, BPC
+ stream.writeShort(474);
+ stream.write((rle ? 1 : 0));
+ stream.write(1);
+
+ // write out DIMENSION
+ stream.writeShort(3);
+
+ // write XSIZE, YSIZE, ZSIZE
+ stream.writeShort(xsize);
+ stream.writeShort(ysize);
+ stream.writeShort(zsize);
+
+ // write PIXMIN, PIXMAX
+ stream.writeInt(0);
+ stream.writeInt(255);
+
+ // write DUMMY
+ stream.writeInt(0);
+
+ // write IMAGENAME
+ for (int i = 0; i < 80; i++)
+ stream.write(0);
+
+ // write COLORMAP
+ stream.writeInt(0);
+
+ // write DUMMY (404 bytes)
+ for (int i = 0; i < 404; i++)
+ stream.write(0);
+ }
+
+ private void writeImage(File file,
+ byte[] data,
+ int xsize,
+ int ysize,
+ int zsize,
+ boolean yflip) throws IOException {
+ // Input data is in RGBRGBRGB or RGBARGBARGBA format; first unswizzle it
+ byte[] tmpData = new byte[xsize * ysize * zsize];
+ int dest = 0;
+ for (int i = 0; i < zsize; i++) {
+ for (int j = i; j < (xsize * ysize * zsize); j += zsize) {
+ tmpData[dest++] = data[j];
+ }
+ }
+ data = tmpData;
+
+ // requires: DATA must be an array of size XSIZE * YSIZE * ZSIZE,
+ // indexed in the following manner:
+ // data[0] ...data[xsize-1] == first row of first channel
+ // data[xsize]...data[2*xsize-1] == second row of first channel
+ // ... data[(ysize - 1) * xsize]...data[(ysize * xsize) - 1] ==
+ // last row of first channel
+ // Later channels follow the same format.
+ // *** NOTE that "first row" is defined by the BOTTOM ROW of
+ // the image. That is, the origin is in the lower left corner.
+ // effects: writes out an SGI image to FILE, RLE-compressed, INCLUDING
+ // header, of dimensions (xsize, ysize, zsize), and containing
+ // the data in DATA. If YFLIP is set, outputs the data in DATA
+ // in reverse order vertically (equivalent to a flip about the
+ // x axis).
+
+ // Build the offset tables
+ int[] starttab = new int[ysize * zsize];
+ int[] lengthtab = new int[ysize * zsize];
+
+ // Temporary buffer for holding RLE data.
+ // Note that this makes the assumption that RLE-compressed data will
+ // never exceed twice the size of the input data.
+ // There are surely formal proofs about how big the RLE buffer should
+ // be, as well as what the optimal look-ahead size is (i.e. don't switch
+ // copy/repeat modes for less than N repeats). However, I'm going from
+ // empirical evidence here; the break-even point seems to be a look-
+ // ahead of 3. (That is, if the three values following this one are all
+ // the same as the current value, switch to repeat mode.)
+ int lookahead = 3;
+ byte[] rlebuf = new byte[2 * xsize * ysize * zsize];
+
+ int cur_loc = 0; // current offset location.
+ int ptr = 0;
+ int total_size = 0;
+ int ystart = 0;
+ int yincr = 1;
+ int yend = ysize;
+
+ if (yflip) {
+ ystart = ysize - 1;
+ yend = -1;
+ yincr = -1;
+ }
+
+ boolean DEBUG = false;
+
+ for (int z = 0; z < zsize; z++) {
+ for (int y = ystart; y != yend; y += yincr) {
+ // RLE-compress each row.
+
+ int x = 0;
+ byte count = 0;
+ boolean repeat_mode = false;
+ boolean should_switch = false;
+ int start_ptr = ptr;
+ int num_ptr = ptr++;
+ byte repeat_val = 0;
+
+ while (x < xsize) {
+ // see if we should switch modes
+ should_switch = false;
+ if (repeat_mode) {
+ if (imgref(data, x, y, z, xsize, ysize, zsize) != repeat_val) {
+ should_switch = true;
+ }
+ } else {
+ // look ahead to see if we should switch to repeat mode.
+ // stay within the scanline for the lookahead
+ if ((x + lookahead) < xsize) {
+ should_switch = true;
+ for (int i = 1; i <= lookahead; i++) {
+ if (DEBUG)
+ System.err.println("left side was " + ((int) imgref(data, x, y, z, xsize, ysize, zsize)) +
+ ", right side was " + (int)imgref(data, x+i, y, z, xsize, ysize, zsize));
+
+ if (imgref(data, x, y, z, xsize, ysize, zsize) !=
+ imgref(data, x+i, y, z, xsize, ysize, zsize))
+ should_switch = false;
+ }
+ }
+ }
+
+ if (should_switch || (count == 127)) {
+ // update the number of elements we repeated/copied
+ if (x > 0) {
+ if (repeat_mode)
+ rlebuf[num_ptr] = count;
+ else
+ rlebuf[num_ptr] = (byte) (count | 0x80);
+ }
+ // perform mode switch if necessary; output repeat_val if
+ // switching FROM repeat mode, and set it if switching
+ // TO repeat mode.
+ if (repeat_mode) {
+ if (should_switch)
+ repeat_mode = false;
+ rlebuf[ptr++] = repeat_val;
+ } else {
+ if (should_switch)
+ repeat_mode = true;
+ repeat_val = imgref(data, x, y, z, xsize, ysize, zsize);
+ }
+
+ if (x > 0) {
+ // reset the number pointer
+ num_ptr = ptr++;
+ // reset number of bytes copied
+ count = 0;
+ }
+ }
+
+ // if not in repeat mode, copy element to ptr
+ if (!repeat_mode) {
+ rlebuf[ptr++] = imgref(data, x, y, z, xsize, ysize, zsize);
+ }
+ count++;
+
+ if (x == xsize - 1) {
+ // Need to store the number of pixels we copied/repeated.
+ if (repeat_mode) {
+ rlebuf[num_ptr] = count;
+ // If we ended the row in repeat mode, store the
+ // repeated value
+ rlebuf[ptr++] = repeat_val;
+ }
+ else
+ rlebuf[num_ptr] = (byte) (count | 0x80);
+
+ // output zero counter for the last value in the row
+ rlebuf[ptr++] = 0;
+ }
+
+ x++;
+ }
+ // output this row's length into the length table
+ int rowlen = ptr - start_ptr;
+ if (yflip)
+ lengthtab[ysize*z+(ysize-y-1)] = rowlen;
+ else
+ lengthtab[ysize*z+y] = rowlen;
+ // add to the start table, and update the current offset
+ if (yflip)
+ starttab[ysize*z+(ysize-y-1)] = cur_loc;
+ else
+ starttab[ysize*z+y] = cur_loc;
+ cur_loc += rowlen;
+ }
+ }
+
+ // Now we have the offset tables computed, as well as the RLE data.
+ // Output this information to the file.
+ total_size = ptr;
+
+ if (DEBUG)
+ System.err.println("total_size was " + total_size);
+
+ DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
+
+ writeHeader(stream, xsize, ysize, zsize, true);
+
+ int SIZEOF_INT = 4;
+ for (int i = 0; i < (ysize * zsize); i++)
+ stream.writeInt(starttab[i] + 512 + (2 * ysize * zsize * SIZEOF_INT));
+ for (int i = 0; i < (ysize * zsize); i++)
+ stream.writeInt(lengthtab[i]);
+ for (int i = 0; i < total_size; i++)
+ stream.write(rlebuf[i]);
+
+ stream.close();
+ }
+
private byte[] readAll(DataInputStream in) throws IOException {
byte[] dest = new byte[16384];
int pos = 0;
@@ -357,6 +635,8 @@ public class SGIImage {
return dest;
}
+ // Test case
+ /*
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
try {
@@ -387,4 +667,5 @@ public class SGIImage {
}
}
}
+ */
}
diff --git a/src/classes/com/sun/opengl/utils/TGAImage.java b/src/classes/com/sun/opengl/utils/TGAImage.java
index d5505afd5..c676d1a6a 100755
--- a/src/classes/com/sun/opengl/utils/TGAImage.java
+++ b/src/classes/com/sun/opengl/utils/TGAImage.java
@@ -40,11 +40,13 @@
package com.sun.opengl.utils;
import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
import javax.media.opengl.*;
import com.sun.opengl.utils.*;
/**
- * Targa image reader adapted from sources of the <a href =
+ * 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.
*
* <P>
@@ -70,7 +72,7 @@ import com.sun.opengl.utils.*;
public class TGAImage {
private Header header;
private int format;
- private byte[] data;
+ private ByteBuffer data;
private TGAImage(Header header) {
this.header = header;
@@ -100,7 +102,7 @@ public class TGAImage {
/** Field image descriptor bitfield values definitions */
public final static int ID_ATTRIBPERPIXEL = 0xF;
- public final static int ID_LEFTTORIGHT = 0x10;
+ public final static int ID_RIGHTTOLEFT = 0x10;
public final static int ID_TOPTOBOTTOM = 0x20;
public final static int ID_INTERLEAVE = 0xC0;
@@ -130,15 +132,14 @@ public class TGAImage {
private byte pixelDepth;
private byte imageDescriptor;
- /** bitfields in imageDescriptor */
- private byte attribPerPixel; // how many attribute bits per pixel
- private boolean leftToRight; // order of data on scan line
- private boolean topToBottom; // order scan lines stored
- private byte interleave; // how rows are stored in image data
-
private byte[] imageIDbuf;
private String imageID;
+ // For construction from user data
+ Header() {
+ tgaType = TYPE_OLD; // dont try and get footer.
+ }
+
Header(LEDataInputStream in) throws IOException {
int ret;
@@ -146,7 +147,7 @@ public class TGAImage {
// initial header fields
idLength = in.readUnsignedByte();
- colorMapType = in.readUnsignedByte();
+ colorMapType = in.readUnsignedByte();
imageType = in.readUnsignedByte();
// color map header fields
@@ -162,11 +163,6 @@ public class TGAImage {
pixelDepth = in.readByte();
imageDescriptor = in.readByte();
- attribPerPixel = (byte)(imageDescriptor & ID_ATTRIBPERPIXEL);
- leftToRight = (imageDescriptor & ID_LEFTTORIGHT) != 0; // not used ?
- topToBottom = (imageDescriptor & ID_TOPTOBOTTOM) != 0;
- interleave = (byte)((imageDescriptor & ID_INTERLEAVE) >> 6);
-
if (idLength > 0) {
imageIDbuf = new byte[idLength];
in.read(imageIDbuf, 0, idLength);
@@ -195,10 +191,10 @@ public class TGAImage {
public byte imageDescriptor() { return imageDescriptor; }
/** bitfields in imageDescriptor */
- public byte attribPerPixel() { return attribPerPixel; }
- public boolean leftToRight() { return leftToRight; }
- public boolean topToBottom() { return topToBottom; }
- public byte interleave() { return interleave; }
+ public byte attribPerPixel() { return (byte)(imageDescriptor & ID_ATTRIBPERPIXEL); }
+ public boolean rightToLeft() { return ((imageDescriptor & ID_RIGHTTOLEFT) != 0); }
+ public boolean topToBottom() { return ((imageDescriptor & ID_TOPTOBOTTOM) != 0); }
+ public byte interleave() { return (byte)((imageDescriptor & ID_INTERLEAVE) >> 6); }
public byte[] imageIDbuf() { return imageIDbuf; }
public String imageID() { return imageID; }
@@ -219,6 +215,32 @@ public class TGAImage {
" image descriptor: "+ imageDescriptor +
(imageIDbuf == null ? "" : (" ID String: " + imageID));
}
+
+ public int size() { return 18 + idLength; }
+
+ // buf must be in little-endian byte order
+ private void write(ByteBuffer buf) {
+ buf.put((byte) idLength);
+ buf.put((byte) colorMapType);
+ buf.put((byte) imageType);
+ buf.putShort((short) firstEntryIndex);
+ buf.putShort((short) colorMapLength);
+ buf.put((byte) colorMapEntrySize);
+ buf.putShort((short) xOrigin);
+ buf.putShort((short) yOrigin);
+ buf.putShort((short) width);
+ buf.putShort((short) height);
+ buf.put((byte) pixelDepth);
+ buf.put((byte) imageDescriptor);
+ if (idLength > 0) {
+ try {
+ byte[] chars = imageID.getBytes("US-ASCII");
+ buf.put(chars);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
}
@@ -269,7 +291,7 @@ public class TGAImage {
int raw; // index through the raw input buffer
int rawWidth = header.width() * (header.pixelDepth() / 8);
byte[] rawBuf = new byte[rawWidth];
- data = new byte[rawWidth * header.height()];
+ byte[] tmpData = new byte[rawWidth * header.height()];
if (header.pixelDepth() == 24) {
format = GL.GL_BGR;
@@ -281,13 +303,15 @@ public class TGAImage {
for (i = 0; i < header.height(); ++i) {
dIn.readFully(rawBuf, 0, rawWidth);
- if (header.topToBottom)
+ if (header.topToBottom())
y = header.height - i - 1; // range 0 to (header.height - 1)
else
y = i;
- System.arraycopy(rawBuf, 0, data, y * rawWidth, rawBuf.length);
+ System.arraycopy(rawBuf, 0, tmpData, y * rawWidth, rawBuf.length);
}
+
+ data = ByteBuffer.wrap(tmpData);
}
/** Returns the width of the image. */
@@ -301,7 +325,7 @@ public class TGAImage {
/** Returns the raw data for this texture in the correct
(bottom-to-top) order for calls to glTexImage2D. */
- public byte[] getData() { return data; }
+ public ByteBuffer getData() { return data; }
/** Reads a Targa image from the specified file. */
public static TGAImage read(String filename) throws IOException {
@@ -317,4 +341,46 @@ public class TGAImage {
res.decodeImage(dIn);
return res;
}
+
+ /** Writes the image in Targa format to the specified file name. */
+ public void write(String filename) throws IOException {
+ write(new File(filename));
+ }
+
+ /** Writes the image in Targa format to the specified file. */
+ public void write(File file) throws IOException {
+ FileOutputStream stream = new FileOutputStream(file);
+ FileChannel chan = stream.getChannel();
+ ByteBuffer buf = ByteBuffer.allocate(header.size());
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ header.write(buf);
+ buf.rewind();
+ chan.write(buf);
+ chan.write(data);
+ data.rewind();
+ chan.force(true);
+ chan.close();
+ stream.close();
+ }
+
+ /** Creates a TGAImage 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., BGR or
+ BGRA. */
+ public static TGAImage createFromData(int width,
+ int height,
+ boolean hasAlpha,
+ boolean topToBottom,
+ ByteBuffer data) {
+ Header header = new Header();
+ header.imageType = Header.UTRUECOLOR;
+ header.width = width;
+ header.height = height;
+ header.pixelDepth = (byte) (hasAlpha ? 32 : 24);
+ header.imageDescriptor = (byte) (topToBottom ? Header.ID_TOPTOBOTTOM : 0);
+ // Note ID not supported
+ TGAImage ret = new TGAImage(header);
+ ret.data = data;
+ return ret;
+ }
}
diff --git a/src/classes/com/sun/opengl/utils/Texture.java b/src/classes/com/sun/opengl/utils/Texture.java
index efecdffea..924161d69 100755
--- a/src/classes/com/sun/opengl/utils/Texture.java
+++ b/src/classes/com/sun/opengl/utils/Texture.java
@@ -79,7 +79,7 @@ public class Texture {
// For now make Texture constructor package-private to limit the
// number of public APIs we commit to
Texture(TextureData data) throws GLException {
- GL gl = getCurrentGL();
+ GL gl = GLU.getCurrentGL();
texID = createTextureID(gl);
updateImage(data);
@@ -88,7 +88,7 @@ public class Texture {
// Constructor for use when creating e.g. cube maps, where there is
// no initial texture data
Texture(int target) throws GLException {
- GL gl = getCurrentGL();
+ GL gl = GLU.getCurrentGL();
texID = createTextureID(gl);
this.target = target;
}
@@ -101,7 +101,7 @@ public class Texture {
* OpenGL-related errors occurred
*/
public void enable() throws GLException {
- getCurrentGL().glEnable(target);
+ GLU.getCurrentGL().glEnable(target);
}
/**
@@ -112,7 +112,7 @@ public class Texture {
* OpenGL-related errors occurred
*/
public void disable() throws GLException {
- getCurrentGL().glDisable(target);
+ GLU.getCurrentGL().glDisable(target);
}
/**
@@ -122,7 +122,7 @@ public class Texture {
* OpenGL-related errors occurred
*/
public void bind() throws GLException {
- getCurrentGL().glBindTexture(target, texID);
+ GLU.getCurrentGL().glBindTexture(target, texID);
}
/**
@@ -132,7 +132,7 @@ public class Texture {
* OpenGL-related errors occurred
*/
public void dispose() throws GLException {
- getCurrentGL().glDeleteTextures(1, new int[] {texID}, 0);
+ GLU.getCurrentGL().glDeleteTextures(1, new int[] {texID}, 0);
texID = 0;
}
@@ -242,6 +242,17 @@ public class Texture {
}
/**
+ * Indicates whether this texture's texture coordinates must be
+ * flipped vertically in order to properly display the texture. This
+ * is handled automatically by {@link #getImageTexCoords} and {@link
+ * #getSubImageTexCoords}, but applications may generate or
+ * otherwise produce texture coordinates which must be corrected.
+ */
+ public boolean getMustFlipVertically() {
+ return mustFlipVertically;
+ }
+
+ /**
* Updates the content area of the specified target of this texture
* using the data in the given image. In general this is intended
* for construction of cube maps.
@@ -250,7 +261,7 @@ public class Texture {
* OpenGL-related errors occurred
*/
public void updateImage(TextureData data, int target) throws GLException {
- GL gl = getCurrentGL();
+ GL gl = GLU.getCurrentGL();
imgWidth = data.getWidth();
imgHeight = data.getHeight();
@@ -395,7 +406,7 @@ public class Texture {
public void setTexParameteri(int parameterName,
int value) {
bind();
- GL gl = getCurrentGL();
+ GL gl = GLU.getCurrentGL();
gl.glTexParameteri(target, parameterName, value);
}
@@ -413,18 +424,6 @@ public class Texture {
//
/**
- * Returns the current GL object. Throws GLException if no OpenGL
- * context was current.
- */
- private static GL getCurrentGL() throws GLException {
- GLContext context = GLContext.getCurrent();
- if (context == null) {
- throw new GLException("No OpenGL context current on current thread");
- }
- return context.getGL();
- }
-
- /**
* Returns true if the given value is a power of two.
*
* @return true if the given value is a power of two, false otherwise
@@ -475,7 +474,7 @@ public class Texture {
}
private void updateSubImageImpl(TextureData data, int newTarget, int mipmapLevel, int x, int y) throws GLException {
- GL gl = getCurrentGL();
+ GL gl = GLU.getCurrentGL();
gl.glBindTexture(newTarget, texID);
int width = data.getWidth();
int height = data.getHeight();
diff --git a/src/classes/com/sun/opengl/utils/TextureData.java b/src/classes/com/sun/opengl/utils/TextureData.java
index a6fcd9073..6bb459432 100755
--- a/src/classes/com/sun/opengl/utils/TextureData.java
+++ b/src/classes/com/sun/opengl/utils/TextureData.java
@@ -316,16 +316,7 @@ public class TextureData {
private void createNIOBufferFromImage(BufferedImage image, boolean flipVertically) {
if (flipVertically) {
- WritableRaster raster = image.getRaster();
- Object scanline1 = null;
- Object scanline2 = null;
-
- for (int i = 0; i < image.getHeight() / 2; i++) {
- scanline1 = raster.getDataElements(0, i, image.getWidth(), 1, scanline1);
- scanline2 = raster.getDataElements(0, image.getHeight() - i - 1, image.getWidth(), 1, scanline2);
- raster.setDataElements(0, i, image.getWidth(), 1, scanline2);
- raster.setDataElements(0, image.getHeight() - i - 1, image.getWidth(), 1, scanline1);
- }
+ TextureIO.flipImageVertically(image);
}
//
diff --git a/src/classes/com/sun/opengl/utils/TextureIO.java b/src/classes/com/sun/opengl/utils/TextureIO.java
index 07bb67ce4..d1f62cd5c 100755
--- a/src/classes/com/sun/opengl/utils/TextureIO.java
+++ b/src/classes/com/sun/opengl/utils/TextureIO.java
@@ -39,6 +39,7 @@
package com.sun.opengl.utils;
+import java.awt.Graphics;
import java.awt.image.*;
import java.io.*;
import java.net.*;
@@ -47,25 +48,28 @@ import java.util.*;
import javax.imageio.*;
import javax.media.opengl.*;
+import javax.media.opengl.glu.*;
/** <P> Provides input and output facilities for both loading OpenGL
textures from disk and streams as well as writing textures already
in memory back to disk. </P>
<P> The TextureIO class supports an arbitrary number of plug-in
- TextureProviders which know how to produce TextureData objects
- from files, InputStreams and URLs. The TextureData class
- represents the raw data of the texture before it has been
- converted to an OpenGL texture object. The Texture class
+ readers and writers via TextureProviders and TextureWriters.
+ TextureProviders know how to produce TextureData objects from
+ files, InputStreams and URLs. TextureWriters know how to write
+ TextureData objects to disk in various file formats. The
+ TextureData class represents the raw data of the texture before it
+ has been converted to an OpenGL texture object. The Texture class
represents the OpenGL texture object and provides easy facilities
for using the texture. </P>
- <P> There are several built-in TextureProviders supplied with the
- TextureIO implementation. The most basic provider uses the
- platform's Image I/O facilities to read in a BufferedImage and
- convert it to a texture. This is the baseline provider and is
- registered so that it is the last one consulted. All others are
- asked first to open a given file. </P>
+ <P> There are several built-in TextureProviders and TextureWriters
+ supplied with the TextureIO implementation. The most basic
+ provider uses the platform's Image I/O facilities to read in a
+ BufferedImage and convert it to a texture. This is the baseline
+ provider and is registered so that it is the last one consulted.
+ All others are asked first to open a given file. </P>
<P> There are three other providers registered by default as of
the time of this writing. One handles SGI RGB (".sgi", ".rgb")
@@ -84,6 +88,18 @@ import javax.media.opengl.*;
when probing for e.g. magic numbers at the head of the file to
make sure not to disturb the state of the InputStream for
downstream TextureProviders. </P>
+
+ <P> There are analogous TextureWriters provided for writing
+ textures back to disk if desired. As of this writing, there are
+ four TextureWriters registered by default: one for Targa files,
+ one for SGI RGB files, one for DirectDraw surface (.dds) files,
+ and one for ImageIO-supplied formats such as .jpg and .png. Some
+ of these writers have certain limitations such as only being able
+ to write out textures stored in GL_RGB or GL_RGBA format. The DDS
+ writer supports fetching and writing to disk of texture data in
+ DXTn compressed format. Whether this will occur is dependent on
+ whether the texture's internal format is one of the DXTn
+ compressed formats and whether the target file is .dds format.
*/
public class TextureIO {
@@ -396,10 +412,11 @@ public class TextureIO {
* @param data the texture data to turn into an OpenGL texture
* @throws GLException if no OpenGL context is current or if an
* OpenGL error occurred
+ * @throws IllegalArgumentException if the passed TextureData was null
*/
- public static Texture newTexture(TextureData data) throws GLException {
+ public static Texture newTexture(TextureData data) throws GLException, IllegalArgumentException {
if (data == null) {
- return null;
+ throw new IllegalArgumentException("Null TextureData");
}
return new Texture(data);
}
@@ -513,6 +530,120 @@ public class TextureIO {
return new Texture(target);
}
+ /**
+ * Writes the given texture to a file. The type of the file is
+ * inferred from its suffix. An OpenGL context must be current in
+ * order to fetch the texture data back from the OpenGL pipeline.
+ * This method causes the specified Texture to be bound to the
+ * GL_TEXTURE_2D state. If no suitable writer for the requested file
+ * format was found, throws an IOException. <P>
+ *
+ * Reasonable attempts are made to produce good results in the
+ * resulting images. The Targa, SGI and ImageIO writers produce
+ * results in the correct vertical orientation for those file
+ * formats. The DDS writer performs no vertical flip of the data,
+ * even in uncompressed mode. (It is impossible to perform such a
+ * vertical flip with compressed data.) Applications should keep
+ * this in mind when using this routine to save textures to disk for
+ * later re-loading. <P>
+ *
+ * Any mipmaps for the specified texture are currently discarded
+ * when it is written to disk, regardless of whether the underlying
+ * file format supports multiple mipmaps in a given file.
+ *
+ * @throws IOException if an error occurred during writing or no
+ * suitable writer was found
+ * @throws GLException if no OpenGL context was current or an
+ * OpenGL-related error occurred
+ */
+ public static void write(Texture texture, File file) throws IOException, GLException {
+ if (texture.getTarget() != GL.GL_TEXTURE_2D) {
+ throw new GLException("Only GL_TEXTURE_2D textures are supported");
+ }
+
+ // First fetch the texture data
+ GL gl = GLU.getCurrentGL();
+
+ texture.bind();
+ int internalFormat = glGetTexLevelParameteri(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_INTERNAL_FORMAT);
+ int width = glGetTexLevelParameteri(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_WIDTH);
+ int height = glGetTexLevelParameteri(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_HEIGHT);
+ int border = glGetTexLevelParameteri(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_BORDER);
+ TextureData data = null;
+ if (internalFormat == GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT ||
+ internalFormat == GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ||
+ internalFormat == GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
+ internalFormat == GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) {
+ // Fetch using glGetCompressedTexImage
+ int size = glGetTexLevelParameteri(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_COMPRESSED_IMAGE_SIZE);
+ ByteBuffer res = ByteBuffer.allocate(size);
+ gl.glGetCompressedTexImage(GL.GL_TEXTURE_2D, 0, res);
+ data = new TextureData(internalFormat, width, height, border, internalFormat, GL.GL_UNSIGNED_BYTE,
+ false, true, true, res, null);
+ } else {
+ int bytesPerPixel = 0;
+ int fetchedFormat = 0;
+ switch (internalFormat) {
+ case GL.GL_RGB:
+ case GL.GL_BGR:
+ case GL.GL_RGB8:
+ bytesPerPixel = 3;
+ fetchedFormat = GL.GL_RGB;
+ break;
+ case GL.GL_RGBA:
+ case GL.GL_BGRA:
+ case GL.GL_ABGR_EXT:
+ case GL.GL_RGBA8:
+ bytesPerPixel = 4;
+ fetchedFormat = GL.GL_RGBA;
+ break;
+ default:
+ throw new IOException("Unsupported texture internal format 0x" + Integer.toHexString(internalFormat));
+ }
+
+ // Fetch using glGetTexImage
+ int packAlignment = glGetInteger(GL.GL_PACK_ALIGNMENT);
+ int packRowLength = glGetInteger(GL.GL_PACK_ROW_LENGTH);
+ int packSkipRows = glGetInteger(GL.GL_PACK_SKIP_ROWS);
+ int packSkipPixels = glGetInteger(GL.GL_PACK_SKIP_PIXELS);
+ int packSwapBytes = glGetInteger(GL.GL_PACK_SWAP_BYTES);
+
+ gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1);
+ gl.glPixelStorei(GL.GL_PACK_ROW_LENGTH, 0);
+ gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, 0);
+ gl.glPixelStorei(GL.GL_PACK_SKIP_PIXELS, 0);
+ gl.glPixelStorei(GL.GL_PACK_SWAP_BYTES, 0);
+
+ ByteBuffer res = ByteBuffer.allocate((width + (2 * border)) *
+ (height + (2 * border)) *
+ bytesPerPixel);
+ System.err.println("Allocated buffer of size " + res.remaining() + " for fetched image (" +
+ ((fetchedFormat == GL.GL_RGB) ? "GL_RGB" : "GL_RGBA") + ")");
+ gl.glGetTexImage(GL.GL_TEXTURE_2D, 0, fetchedFormat, GL.GL_UNSIGNED_BYTE, res);
+
+ gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, packAlignment);
+ gl.glPixelStorei(GL.GL_PACK_ROW_LENGTH, packRowLength);
+ gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, packSkipRows);
+ gl.glPixelStorei(GL.GL_PACK_SKIP_PIXELS, packSkipPixels);
+ gl.glPixelStorei(GL.GL_PACK_SWAP_BYTES, packSwapBytes);
+
+ data = new TextureData(internalFormat, width, height, border, fetchedFormat, GL.GL_UNSIGNED_BYTE,
+ false, false, false, res, null);
+
+ System.out.println("data.getPixelFormat() = " +
+ ((data.getPixelFormat() == GL.GL_RGB) ? "GL_RGB" : "GL_RGBA"));
+ }
+
+ for (Iterator iter = textureWriters.iterator(); iter.hasNext(); ) {
+ TextureWriter writer = (TextureWriter) iter.next();
+ if (writer.write(file, data)) {
+ return;
+ }
+ }
+
+ throw new IOException("No suitable texture writer found");
+ }
+
//----------------------------------------------------------------------
// Helper function for above TextureProviders
/**
@@ -540,8 +671,25 @@ public class TextureIO {
return toLowerCase(filename.substring(lastDot + 1));
}
- // FIXME: add texture writing capabilities
- // public void writeTextureToFile(Texture texture, File file, boolean saveUncompressed) throws IOException, GLException;
+ //----------------------------------------------------------------------
+ // Helper function which may be more generally useful
+ //
+
+ /** Flips the supplied BufferedImage vertically. This is often a
+ necessary conversion step to display a Java2D image correctly
+ with OpenGL and vice versa. */
+ public static void flipImageVertically(BufferedImage image) {
+ WritableRaster raster = image.getRaster();
+ Object scanline1 = null;
+ Object scanline2 = null;
+
+ for (int i = 0; i < image.getHeight() / 2; i++) {
+ scanline1 = raster.getDataElements(0, i, image.getWidth(), 1, scanline1);
+ scanline2 = raster.getDataElements(0, image.getHeight() - i - 1, image.getWidth(), 1, scanline2);
+ raster.setDataElements(0, i, image.getWidth(), 1, scanline2);
+ raster.setDataElements(0, image.getHeight() - i - 1, image.getWidth(), 1, scanline1);
+ }
+ }
//----------------------------------------------------------------------
// SPI support
@@ -556,11 +704,21 @@ public class TextureIO {
textureProviders.add(0, provider);
}
+ /** Adds a TextureWriter to support writing of a new file
+ format. */
+ public static void addTextureWriter(TextureWriter writer) {
+ // Must always add at the front so the ImageIO writer is last,
+ // so we don't accidentally use it instead of a user's possibly
+ // more optimal writer
+ textureWriters.add(0, writer);
+ }
+
//----------------------------------------------------------------------
// Internals only below this point
//
private static List/*<TextureProvider>*/ textureProviders = new ArrayList/*<TextureProvider>*/();
+ private static List/*<TextureWriter>*/ textureWriters = new ArrayList/*<TextureWriter>*/();
static {
// ImageIO provider, the fall-back, must be the first one added
@@ -570,6 +728,14 @@ public class TextureIO {
addTextureProvider(new DDSTextureProvider());
addTextureProvider(new SGITextureProvider());
addTextureProvider(new TGATextureProvider());
+
+ // ImageIO writer, the fall-back, must be the first one added
+ textureWriters.add(new IIOTextureWriter());
+
+ // Other special-case writers
+ addTextureWriter(new DDSTextureWriter());
+ addTextureWriter(new SGITextureWriter());
+ addTextureWriter(new TGATextureWriter());
}
// Implementation methods
@@ -595,7 +761,8 @@ public class TextureIO {
return data;
}
}
- return null;
+
+ throw new IOException("No suitable reader for given file");
}
private static TextureData newTextureDataImpl(InputStream stream,
@@ -626,7 +793,7 @@ public class TextureIO {
}
}
- return null;
+ throw new IOException("No suitable reader for given stream");
}
private static TextureData newTextureDataImpl(URL url,
@@ -652,7 +819,7 @@ public class TextureIO {
}
}
- return null;
+ throw new IOException("No suitable reader for given URL");
}
private static TextureData newTextureDataImpl(BufferedImage image,
@@ -713,12 +880,11 @@ public class TextureIO {
String fileSuffix) throws IOException {
if (DDS.equals(fileSuffix) ||
DDS.equals(getFileSuffix(file))) {
- final DDSReader reader = new DDSReader();
- reader.loadFile(file);
- DDSReader.ImageInfo info = reader.getMipMap(0);
+ final DDSImage image = DDSImage.read(file);
+ DDSImage.ImageInfo info = image.getMipMap(0);
if (pixelFormat == 0) {
- switch (reader.getPixelFormat()) {
- case DDSReader.D3DFMT_R8G8B8:
+ switch (image.getPixelFormat()) {
+ case DDSImage.D3DFMT_R8G8B8:
pixelFormat = GL.GL_RGB;
break;
default:
@@ -728,23 +894,23 @@ public class TextureIO {
}
if (info.isCompressed()) {
switch (info.getCompressionFormat()) {
- case DDSReader.D3DFMT_DXT1:
+ case DDSImage.D3DFMT_DXT1:
internalFormat = GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
break;
- case DDSReader.D3DFMT_DXT3:
+ case DDSImage.D3DFMT_DXT3:
internalFormat = GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
- case DDSReader.D3DFMT_DXT5:
+ case DDSImage.D3DFMT_DXT5:
internalFormat = GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
default:
throw new RuntimeException("Unsupported DDS compression format \"" +
- DDSReader.getCompressionFormatName(info.getCompressionFormat()) + "\"");
+ DDSImage.getCompressionFormatName(info.getCompressionFormat()) + "\"");
}
}
if (internalFormat == 0) {
- switch (reader.getPixelFormat()) {
- case DDSReader.D3DFMT_R8G8B8:
+ switch (image.getPixelFormat()) {
+ case DDSImage.D3DFMT_R8G8B8:
pixelFormat = GL.GL_RGB;
break;
default:
@@ -754,14 +920,14 @@ public class TextureIO {
}
TextureData.Flusher flusher = new TextureData.Flusher() {
public void flush() {
- reader.close();
+ image.close();
}
};
TextureData data;
- if (mipmap && reader.getNumMipMaps() > 0) {
- Buffer[] mipmapData = new Buffer[reader.getNumMipMaps()];
- for (int i = 0; i < reader.getNumMipMaps(); i++) {
- mipmapData[i] = reader.getMipMap(i).getData();
+ if (mipmap && image.getNumMipMaps() > 0) {
+ Buffer[] mipmapData = new Buffer[image.getNumMipMaps()];
+ for (int i = 0; i < image.getNumMipMaps(); i++) {
+ mipmapData[i] = image.getMipMap(i).getData();
}
data = new TextureData(internalFormat,
info.getWidth(),
@@ -909,7 +1075,7 @@ public class TextureIO {
mipmap,
false,
false,
- ByteBuffer.wrap(image.getData()),
+ image.getData(),
null);
}
@@ -918,9 +1084,229 @@ public class TextureIO {
}
//----------------------------------------------------------------------
+ // ImageIO texture writer
+ //
+ static class IIOTextureWriter implements TextureWriter {
+ public boolean write(File file,
+ TextureData data) throws IOException {
+ int pixelFormat = data.getPixelFormat();
+ int pixelType = data.getPixelType();
+ if ((pixelFormat == GL.GL_RGB ||
+ pixelFormat == GL.GL_RGBA) &&
+ (pixelType == GL.GL_BYTE ||
+ pixelType == GL.GL_UNSIGNED_BYTE)) {
+ // Convert TextureData to appropriate BufferedImage
+ // FIXME: almost certainly not obeying correct pixel order
+ BufferedImage image = new BufferedImage(data.getWidth(), data.getHeight(),
+ (pixelFormat == GL.GL_RGB) ?
+ BufferedImage.TYPE_3BYTE_BGR :
+ BufferedImage.TYPE_4BYTE_ABGR);
+ byte[] imageData = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
+ ByteBuffer buf = (ByteBuffer) data.getBuffer();
+ if (buf == null) {
+ buf = (ByteBuffer) data.getMipmapData()[0];
+ }
+ buf.rewind();
+ buf.get(imageData);
+ buf.rewind();
+
+ // Swizzle image components to be correct
+ if (pixelFormat == GL.GL_RGB) {
+ for (int i = 0; i < imageData.length; i += 3) {
+ byte red = imageData[i + 0];
+ byte blue = imageData[i + 2];
+ imageData[i + 0] = blue;
+ imageData[i + 2] = red;
+ }
+ } else {
+ for (int i = 0; i < imageData.length; i += 4) {
+ byte red = imageData[i + 0];
+ byte green = imageData[i + 1];
+ byte blue = imageData[i + 2];
+ byte alpha = imageData[i + 3];
+ imageData[i + 0] = alpha;
+ imageData[i + 1] = blue;
+ imageData[i + 2] = green;
+ imageData[i + 3] = red;
+ }
+ }
+
+ // Flip image vertically for the user's convenience
+ flipImageVertically(image);
+
+ // Happened to notice that writing RGBA images to JPEGS is broken
+ if (JPG.equals(getFileSuffix(file)) &&
+ image.getType() == BufferedImage.TYPE_4BYTE_ABGR) {
+ BufferedImage tmpImage = new BufferedImage(image.getWidth(), image.getHeight(),
+ BufferedImage.TYPE_3BYTE_BGR);
+ Graphics g = tmpImage.getGraphics();
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+ image = tmpImage;
+ }
+
+ return ImageIO.write(image, getFileSuffix(file), file);
+ }
+
+ throw new IOException("ImageIO writer doesn't support this pixel format / type (only GL_RGB/A + bytes)");
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // DDS texture writer
+ //
+ static class DDSTextureWriter implements TextureWriter {
+ public boolean write(File file,
+ TextureData data) throws IOException {
+ if (DDS.equals(getFileSuffix(file))) {
+ // See whether the DDS writer can handle this TextureData
+ int pixelFormat = data.getPixelFormat();
+ int pixelType = data.getPixelType();
+ if (pixelType != GL.GL_BYTE &&
+ pixelType != GL.GL_UNSIGNED_BYTE) {
+ throw new IOException("DDS writer only supports byte / unsigned byte textures");
+ }
+
+ int d3dFormat = 0;
+ // FIXME: some of these are probably not completely correct and would require swizzling
+ switch (pixelFormat) {
+ case GL.GL_RGB: d3dFormat = DDSImage.D3DFMT_R8G8B8; break;
+ case GL.GL_RGBA: d3dFormat = DDSImage.D3DFMT_A8R8G8B8; break;
+ case GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT: d3dFormat = DDSImage.D3DFMT_DXT1; break;
+ case GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: throw new IOException("RGBA DXT1 not yet supported");
+ case GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: d3dFormat = DDSImage.D3DFMT_DXT3; break;
+ case GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: d3dFormat = DDSImage.D3DFMT_DXT5; break;
+ default: throw new IOException("Unsupported pixel format 0x" + Integer.toHexString(pixelFormat) + " by DDS writer");
+ }
+
+ ByteBuffer[] mipmaps = null;
+ if (data.getMipmapData() != null) {
+ mipmaps = new ByteBuffer[data.getMipmapData().length];
+ for (int i = 0; i < mipmaps.length; i++) {
+ mipmaps[i] = (ByteBuffer) data.getMipmapData()[i];
+ }
+ } else {
+ mipmaps = new ByteBuffer[] { (ByteBuffer) data.getBuffer() };
+ }
+
+ DDSImage image = DDSImage.createFromData(d3dFormat,
+ data.getWidth(),
+ data.getHeight(),
+ mipmaps);
+ image.write(file);
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // SGI (rgb) texture writer
+ //
+ static class SGITextureWriter implements TextureWriter {
+ public boolean write(File file,
+ TextureData data) throws IOException {
+ String fileSuffix = getFileSuffix(file);
+ if (SGI.equals(fileSuffix) ||
+ SGI_RGB.equals(fileSuffix)) {
+ // See whether the SGI writer can handle this TextureData
+ int pixelFormat = data.getPixelFormat();
+ int pixelType = data.getPixelType();
+ if ((pixelFormat == GL.GL_RGB ||
+ pixelFormat == GL.GL_RGBA) &&
+ (pixelType == GL.GL_BYTE ||
+ pixelType == GL.GL_UNSIGNED_BYTE)) {
+ ByteBuffer buf = ((data.getBuffer() != null) ?
+ (ByteBuffer) data.getBuffer() :
+ (ByteBuffer) data.getMipmapData()[0]);
+ byte[] bytes;
+ if (buf.hasArray()) {
+ bytes = buf.array();
+ } else {
+ buf.rewind();
+ bytes = new byte[buf.remaining()];
+ buf.get(bytes);
+ buf.rewind();
+ }
+
+ SGIImage image = SGIImage.createFromData(data.getWidth(),
+ data.getHeight(),
+ (pixelFormat == GL.GL_RGBA),
+ bytes);
+ image.write(file, false);
+ return true;
+ }
+
+ throw new IOException("SGI writer doesn't support this pixel format / type (only GL_RGB/A + bytes)");
+ }
+
+ return false;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // TGA (Targa) texture writer
+
+ static class TGATextureWriter implements TextureWriter {
+ public boolean write(File file,
+ TextureData data) throws IOException {
+ if (TGA.equals(getFileSuffix(file))) {
+ // See whether the TGA writer can handle this TextureData
+ int pixelFormat = data.getPixelFormat();
+ int pixelType = data.getPixelType();
+ if ((pixelFormat == GL.GL_RGB ||
+ pixelFormat == GL.GL_RGBA) &&
+ (pixelType == GL.GL_BYTE ||
+ pixelType == GL.GL_UNSIGNED_BYTE)) {
+ ByteBuffer buf = ((data.getBuffer() != null) ?
+ (ByteBuffer) data.getBuffer() :
+ (ByteBuffer) data.getMipmapData()[0]);
+ // Must reverse order of red and blue channels to get correct results
+ int skip = ((pixelFormat == GL.GL_RGB) ? 3 : 4);
+ for (int i = 0; i < buf.remaining(); i += skip) {
+ byte red = buf.get(i + 0);
+ byte blue = buf.get(i + 2);
+ buf.put(i + 0, blue);
+ buf.put(i + 2, red);
+ }
+
+ TGAImage image = TGAImage.createFromData(data.getWidth(),
+ data.getHeight(),
+ (pixelFormat == GL.GL_RGBA),
+ false,
+ ((data.getBuffer() != null) ?
+ (ByteBuffer) data.getBuffer() :
+ (ByteBuffer) data.getMipmapData()[0]));
+ image.write(file);
+ return true;
+ }
+
+ throw new IOException("TGA writer doesn't support this pixel format / type (only GL_RGB/A + bytes)");
+ }
+
+ return false;
+ }
+ }
+
+ //----------------------------------------------------------------------
// Helper routines
//
+ private static int glGetInteger(int pname) {
+ int[] tmp = new int[1];
+ GL gl = GLU.getCurrentGL();
+ gl.glGetIntegerv(pname, tmp, 0);
+ return tmp[0];
+ }
+
+ private static int glGetTexLevelParameteri(int target, int level, int pname) {
+ int[] tmp = new int[1];
+ GL gl = GLU.getCurrentGL();
+ gl.glGetTexLevelParameteriv(target, 0, pname, tmp, 0);
+ return tmp[0];
+ }
+
private static String toLowerCase(String arg) {
if (arg == null) {
return null;
diff --git a/src/classes/com/sun/opengl/utils/TextureWriter.java b/src/classes/com/sun/opengl/utils/TextureWriter.java
new file mode 100755
index 000000000..38b976b9d
--- /dev/null
+++ b/src/classes/com/sun/opengl/utils/TextureWriter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ *
+ * Sun gratefully acknowledges that this software was originally authored
+ * and developed by Kenneth Bradley Russell and Christopher John Kline.
+ */
+
+package com.sun.opengl.utils;
+
+import java.io.*;
+
+/** Plug-in interface to TextureIO to support writing OpenGL textures
+ to new file formats. */
+
+public interface TextureWriter {
+ /** Writes the given TextureData to the passed file. Returns true if
+ this TextureWriter successfully handled the writing of the file,
+ otherwise false. May throw IOException if either this writer did
+ not support certain parameters of the TextureData or if an I/O
+ error occurred. */
+ public boolean write(File file,
+ TextureData data) throws IOException;
+}