From b464faaae70ba12e5842b901a6c6d1601af0e1c7 Mon Sep 17 00:00:00 2001
From: Kenneth Russel <kbrussel@alum.mit.edu>
Date: Mon, 9 Jan 2006 02:37:43 +0000
Subject: 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
---
 src/classes/com/sun/opengl/utils/DDSImage.java     | 722 +++++++++++++++++++++
 src/classes/com/sun/opengl/utils/DDSReader.java    | 508 ---------------
 src/classes/com/sun/opengl/utils/SGIImage.java     | 283 +++++++-
 src/classes/com/sun/opengl/utils/TGAImage.java     | 112 +++-
 src/classes/com/sun/opengl/utils/Texture.java      |  41 +-
 src/classes/com/sun/opengl/utils/TextureData.java  |  11 +-
 src/classes/com/sun/opengl/utils/TextureIO.java    | 454 ++++++++++++-
 .../com/sun/opengl/utils/TextureWriter.java        |  55 ++
 8 files changed, 1589 insertions(+), 597 deletions(-)
 create mode 100755 src/classes/com/sun/opengl/utils/DDSImage.java
 delete mode 100755 src/classes/com/sun/opengl/utils/DDSReader.java
 create mode 100755 src/classes/com/sun/opengl/utils/TextureWriter.java

(limited to 'src/classes/com/sun')

diff --git a/src/classes/com/sun/opengl/utils/DDSImage.java b/src/classes/com/sun/opengl/utils/DDSImage.java
new file mode 100755
index 000000000..305e77348
--- /dev/null
+++ b/src/classes/com/sun/opengl/utils/DDSImage.java
@@ -0,0 +1,722 @@
+/*
+ * 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.*;
+import java.nio.*;
+import java.nio.channels.*;
+
+/** 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 DDSImage {
+
+  /** Simple class describing images and data; does not encapsulate
+      image format information. User is responsible for transmitting
+      that information in another way. */
+
+  public static class ImageInfo {
+    private ByteBuffer data;
+    private int width;
+    private int height;
+    private boolean isCompressed;
+    private int compressionFormat;
+
+    public ImageInfo(ByteBuffer data, int width, int height, boolean compressed, int compressionFormat) {
+      this.data = data; this.width = width; this.height = height;
+      this.isCompressed = compressed; this.compressionFormat = compressionFormat;
+    }
+    public int        getWidth()  { return width;  }
+    public int        getHeight() { return height; }
+    public ByteBuffer getData()   { return data;   }
+    public boolean    isCompressed() { return isCompressed; }
+    public int        getCompressionFormat() {
+      if (!isCompressed())
+        throw new RuntimeException("Should not call unless compressed");
+      return compressionFormat;
+    }
+  }
+
+  private FileInputStream fis;
+  private FileChannel     chan;
+  private ByteBuffer buf;
+  private Header header;
+
+  //
+  // Selected bits in header flags
+  //
+
+  public static final int DDSD_CAPS            = 0x00000001; // Capacities are valid
+  public static final int DDSD_HEIGHT          = 0x00000002; // Height is valid
+  public static final int DDSD_WIDTH           = 0x00000004; // Width is valid
+  public static final int DDSD_PITCH           = 0x00000008; // Pitch is valid
+  public static final int DDSD_BACKBUFFERCOUNT = 0x00000020; // Back buffer count is valid
+  public static final int DDSD_ZBUFFERBITDEPTH = 0x00000040; // Z-buffer bit depth is valid (shouldn't be used in DDSURFACEDESC2)
+  public static final int DDSD_ALPHABITDEPTH   = 0x00000080; // Alpha bit depth is valid
+  public static final int DDSD_LPSURFACE       = 0x00000800; // lpSurface is valid
+  public static final int DDSD_PIXELFORMAT     = 0x00001000; // ddpfPixelFormat is valid
+  public static final int DDSD_MIPMAPCOUNT     = 0x00020000; // Mip map count is valid
+  public static final int DDSD_LINEARSIZE      = 0x00080000; // dwLinearSize is valid
+  public static final int DDSD_DEPTH           = 0x00800000; // dwDepth is valid
+
+  public static final int DDPF_ALPHAPIXELS     = 0x00000001; // Alpha channel is present
+  public static final int DDPF_ALPHA           = 0x00000002; // Only contains alpha information
+  public static final int DDPF_FOURCC          = 0x00000004; // FourCC code is valid
+  public static final int DDPF_PALETTEINDEXED4 = 0x00000008; // Surface is 4-bit color indexed
+  public static final int DDPF_PALETTEINDEXEDTO8 = 0x00000010; // Surface is indexed into a palette which stores indices
+                                                               // into the destination surface's 8-bit palette
+  public static final int DDPF_PALETTEINDEXED8 = 0x00000020; // Surface is 8-bit color indexed
+  public static final int DDPF_RGB             = 0x00000040; // RGB data is present
+  public static final int DDPF_COMPRESSED      = 0x00000080; // Surface will accept pixel data in the format specified
+                                                             // and compress it during the write
+  public static final int DDPF_RGBTOYUV        = 0x00000100; // Surface will accept RGB data and translate it during
+                                                             // the write to YUV data. The format of the data to be written
+                                                             // will be contained in the pixel format structure. The DDPF_RGB
+                                                             // flag will be set.
+  public static final int DDPF_YUV             = 0x00000200; // Pixel format is YUV - YUV data in pixel format struct is valid
+  public static final int DDPF_ZBUFFER         = 0x00000400; // Pixel format is a z buffer only surface
+  public static final int DDPF_PALETTEINDEXED1 = 0x00000800; // Surface is 1-bit color indexed
+  public static final int DDPF_PALETTEINDEXED2 = 0x00001000; // Surface is 2-bit color indexed
+  public static final int DDPF_ZPIXELS         = 0x00002000; // Surface contains Z information in the pixels
+
+  // Selected bits in DDS capabilities flags
+  public static final int DDSCAPS_TEXTURE      = 0x00001000; // Can be used as a texture
+  public static final int DDSCAPS_MIPMAP       = 0x00400000; // Is one level of a mip-map
+
+  // Known pixel formats
+  public static final int D3DFMT_UNKNOWN   =  0;
+  public static final int D3DFMT_R8G8B8    =  20;
+  public static final int D3DFMT_A8R8G8B8  =  21;
+  public static final int D3DFMT_X8R8G8B8  =  22;
+  // The following are also valid FourCC codes
+  public static final int D3DFMT_DXT1      =  0x31545844;
+  public static final int D3DFMT_DXT2      =  0x32545844;
+  public static final int D3DFMT_DXT3      =  0x33545844;
+  public static final int D3DFMT_DXT4      =  0x34545844;
+  public static final int D3DFMT_DXT5      =  0x35545844;
+
+  /** 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));
+  }
+  
+  /** 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 {
+      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);
+  }
+
+  /** Test for presence/absence of pixel format flags (DDPF_*) */
+  public boolean isPixelFormatFlagSet(int flag) {
+    return ((header.pfFlags & flag) != 0);
+  }
+
+  /** Gets the pixel format of this texture (D3DFMT_*) based on some
+      heuristics. Returns D3DFMT_UNKNOWN if could not recognize the
+      pixel format. */
+  public int getPixelFormat() {
+    if (isCompressed()) {
+      return getCompressionFormat();
+    } else if (isPixelFormatFlagSet(DDPF_RGB)) {
+      if (isPixelFormatFlagSet(DDPF_ALPHAPIXELS)) {
+        if (getDepth() == 32 &&
+            header.pfRBitMask == 0x00FF0000 &&
+            header.pfGBitMask == 0x0000FF00 &&
+            header.pfBBitMask == 0x000000FF &&
+            header.pfABitMask == 0xFF000000) {
+          return D3DFMT_A8R8G8B8;
+        }
+      } else {
+        if (getDepth() == 24 &&
+            header.pfRBitMask == 0x00FF0000 &&
+            header.pfGBitMask == 0x0000FF00 &&
+            header.pfBBitMask == 0x000000FF) {
+          return D3DFMT_R8G8B8;
+        } else if (getDepth() == 32 &&
+                   header.pfRBitMask == 0x00FF0000 &&
+                   header.pfGBitMask == 0x0000FF00 &&
+                   header.pfBBitMask == 0x000000FF) {
+          return D3DFMT_X8R8G8B8;
+        }
+      }
+    }
+
+    return D3DFMT_UNKNOWN;
+  }
+
+  /** Indicates whether this texture is compressed. */
+  public boolean isCompressed() {
+    return (isPixelFormatFlagSet(DDPF_FOURCC));
+  }
+
+  /** If this surface is compressed, returns the kind of compression
+      used (DXT1..DXT5). */
+  public int getCompressionFormat() {
+    return header.pfFourCC;
+  }
+
+  /** Width of the texture (or the top-most mipmap if mipmaps are
+      present) */
+  public int getWidth() {
+    return header.width;
+  }
+
+  /** Height of the texture (or the top-most mipmap if mipmaps are
+      present) */
+  public int getHeight() {
+    return header.height;
+  }
+
+  /** Total number of bits per pixel. Only valid if DDPF_RGB is
+      present. For A8R8G8B8, would be 32. */
+  public int getDepth() {
+    return header.pfRGBBitCount;
+  }
+
+  /** Number of mip maps in the texture */
+  public int getNumMipMaps() {
+    if (!isSurfaceDescFlagSet(DDSD_MIPMAPCOUNT)) {
+      return 0;
+    }
+    return header.mipMapCountOrAux;
+  }
+
+  /** Gets the <i>i</i>th mipmap data (0..getNumMipMaps() - 1) */
+  public ImageInfo getMipMap(int map) {
+    if (getNumMipMaps() > 0 &&
+        ((map < 0) || (map >= getNumMipMaps()))) {
+      throw new RuntimeException("Illegal mipmap number " + map + " (0.." + (getNumMipMaps() - 1) + ")");
+    }
+
+    // Figure out how far to seek
+    int seek = Header.writtenSize();
+    for (int i = 0; i < map; i++) {
+      seek += mipMapSizeInBytes(i);
+    }
+    buf.limit(seek + mipMapSizeInBytes(map));
+    buf.position(seek);
+    ByteBuffer next = buf.slice();
+    buf.position(0);
+    buf.limit(buf.capacity());
+    return new ImageInfo(next, mipMapWidth(map), mipMapHeight(map), isCompressed(), getCompressionFormat());
+  }
+
+  /** Returns an array of ImageInfos corresponding to all mipmap
+      levels of this DDS file. */
+  public ImageInfo[] getAllMipMaps() {
+    int numLevels = getNumMipMaps();
+    if (numLevels == 0) {
+      numLevels = 1;
+    }
+    ImageInfo[] result = new ImageInfo[numLevels];
+    for (int i = 0; i < numLevels; i++) {
+      result[i] = getMipMap(i);
+    }
+    return result;
+  }
+
+  /** Converts e.g. DXT1 compression format constant (see {@link
+      #getCompressionFormat}) into "DXT1". */
+  public static String getCompressionFormatName(int compressionFormat) {
+    StringBuffer buf = new StringBuffer();
+    for (int i = 0; i < 4; i++) {
+      char c = (char) (compressionFormat & 0xFF);
+      buf.append(c);
+      compressionFormat = compressionFormat >> 8;
+    }
+    return buf.toString();
+  }
+
+  public void debugPrint() {
+    PrintStream tty = System.err;
+    tty.println("Compressed texture: " + isCompressed());
+    if (isCompressed()) {
+      int fmt = getCompressionFormat();
+      String name = getCompressionFormatName(fmt);
+      tty.println("Compression format: 0x" + Integer.toHexString(fmt) + " (" + name + ")");
+    }
+    tty.println("Width: " + header.width + " Height: " + header.height);
+    tty.println("header.pitchOrLinearSize: " + header.pitchOrLinearSize);
+    tty.println("header.pfRBitMask: 0x" + Integer.toHexString(header.pfRBitMask));
+    tty.println("header.pfGBitMask: 0x" + Integer.toHexString(header.pfGBitMask));
+    tty.println("header.pfBBitMask: 0x" + Integer.toHexString(header.pfBBitMask));
+    tty.println("SurfaceDesc flags:");
+    boolean recognizedAny = false;
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_CAPS, "DDSD_CAPS");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_HEIGHT, "DDSD_HEIGHT");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_WIDTH, "DDSD_WIDTH");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_PITCH, "DDSD_PITCH");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_BACKBUFFERCOUNT, "DDSD_BACKBUFFERCOUNT");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_ZBUFFERBITDEPTH, "DDSD_ZBUFFERBITDEPTH");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_ALPHABITDEPTH, "DDSD_ALPHABITDEPTH");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_LPSURFACE, "DDSD_LPSURFACE");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_PIXELFORMAT, "DDSD_PIXELFORMAT");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_MIPMAPCOUNT, "DDSD_MIPMAPCOUNT");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_LINEARSIZE, "DDSD_LINEARSIZE");
+    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_DEPTH, "DDSD_DEPTH");
+    if (!recognizedAny) {
+      tty.println("(none)");
+    }
+    tty.println("Raw SurfaceDesc flags: 0x" + Integer.toHexString(header.flags));
+    tty.println("Pixel format flags:");
+    recognizedAny = false;
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ALPHAPIXELS, "DDPF_ALPHAPIXELS");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ALPHA, "DDPF_ALPHA");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_FOURCC, "DDPF_FOURCC");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED4, "DDPF_PALETTEINDEXED4");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXEDTO8, "DDPF_PALETTEINDEXEDTO8");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED8, "DDPF_PALETTEINDEXED8");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_RGB, "DDPF_RGB");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_COMPRESSED, "DDPF_COMPRESSED");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_RGBTOYUV, "DDPF_RGBTOYUV");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_YUV, "DDPF_YUV");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ZBUFFER, "DDPF_ZBUFFER");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED1, "DDPF_PALETTEINDEXED1");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED2, "DDPF_PALETTEINDEXED2");
+    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ZPIXELS, "DDPF_ZPIXELS");
+    if (!recognizedAny) {
+      tty.println("(none)");
+    }
+    tty.println("Raw pixel format flags: 0x" + Integer.toHexString(header.pfFlags));
+    tty.println("Depth: " + getDepth());
+    tty.println("Number of mip maps: " + getNumMipMaps());
+    int fmt = getPixelFormat();
+    tty.print("Pixel format: ");
+    switch (fmt) {
+    case D3DFMT_R8G8B8:   tty.println("D3DFMT_R8G8B8"); break;
+    case D3DFMT_A8R8G8B8: tty.println("D3DFMT_A8R8G8B8"); break;
+    case D3DFMT_X8R8G8B8: tty.println("D3DFMT_X8R8G8B8"); break;
+    case D3DFMT_DXT1:     tty.println("D3DFMT_DXT1"); break;
+    case D3DFMT_DXT2:     tty.println("D3DFMT_DXT2"); break;
+    case D3DFMT_DXT3:     tty.println("D3DFMT_DXT3"); break;
+    case D3DFMT_DXT4:     tty.println("D3DFMT_DXT4"); break;
+    case D3DFMT_DXT5:     tty.println("D3DFMT_DXT5"); break;
+    case D3DFMT_UNKNOWN:  tty.println("D3DFMT_UNKNOWN"); break;
+    default:              tty.println("(unknown pixel format " + fmt + ")"); break;
+    }
+  }
+
+  //----------------------------------------------------------------------
+  // Internals only below this point
+  //
+
+  private static final int MAGIC = 0x20534444;
+
+  static class Header {
+    int size;                 // size of the DDSURFACEDESC structure
+    int flags;                // determines what fields are valid
+    int height;               // height of surface to be created
+    int width;                // width of input surface
+    int pitchOrLinearSize;
+    int backBufferCountOrDepth;
+    int mipMapCountOrAux;     // number of mip-map levels requested (in this context)
+    int alphaBitDepth;        // depth of alpha buffer requested
+    int reserved1;            // reserved
+    int surface;              // pointer to the associated surface memory
+    // NOTE: following two entries are from DDCOLORKEY data structure
+    // Are overlaid with color for empty cubemap faces (unused in this reader)
+    int colorSpaceLowValue;
+    int colorSpaceHighValue;
+    int destBltColorSpaceLowValue;
+    int destBltColorSpaceHighValue;
+    int srcOverlayColorSpaceLowValue;
+    int srcOverlayColorSpaceHighValue;
+    int srcBltColorSpaceLowValue;
+    int srcBltColorSpaceHighValue;
+    // NOTE: following entries are from DDPIXELFORMAT data structure
+    // Are overlaid with flexible vertex format description of vertex
+    // buffers (unused in this reader)
+    int pfSize;                 // size of DDPIXELFORMAT structure
+    int pfFlags;                // pixel format flags
+    int pfFourCC;               // (FOURCC code)
+    // Following five entries have multiple interpretations, not just
+    // RGBA (but that's all we support right now)
+    int pfRGBBitCount;          // how many bits per pixel
+    int pfRBitMask;             // mask for red bits
+    int pfGBitMask;             // mask for green bits
+    int pfBBitMask;             // mask for blue bits
+    int pfABitMask;             // mask for alpha channel
+    int ddsCaps1;               // Texture and mip-map flags
+    int ddsCaps2;               // Advanced capabilities, not yet used 
+    int ddsCapsReserved1;
+    int ddsCapsReserved2;
+    int textureStage;           // stage in multitexture cascade
+
+    void read(ByteBuffer buf) throws IOException {
+      int magic                     = buf.getInt();
+      if (magic != MAGIC) {
+        throw new IOException("Incorrect magic number 0x" +
+                              Integer.toHexString(magic) +
+                              " (expected " + MAGIC + ")");
+      }
+
+      size                          = buf.getInt();
+      flags                         = buf.getInt();
+      height                        = buf.getInt();
+      width                         = buf.getInt();
+      pitchOrLinearSize             = buf.getInt();
+      backBufferCountOrDepth        = buf.getInt();
+      mipMapCountOrAux              = buf.getInt();
+      alphaBitDepth                 = buf.getInt();
+      reserved1                     = buf.getInt();
+      surface                       = buf.getInt();
+      colorSpaceLowValue            = buf.getInt();
+      colorSpaceHighValue           = buf.getInt();
+      destBltColorSpaceLowValue     = buf.getInt();
+      destBltColorSpaceHighValue    = buf.getInt();
+      srcOverlayColorSpaceLowValue  = buf.getInt();
+      srcOverlayColorSpaceHighValue = buf.getInt();
+      srcBltColorSpaceLowValue      = buf.getInt();
+      srcBltColorSpaceHighValue     = buf.getInt();
+      pfSize                        = buf.getInt();
+      pfFlags                       = buf.getInt();
+      pfFourCC                      = buf.getInt();
+      pfRGBBitCount                 = buf.getInt();
+      pfRBitMask                    = buf.getInt();
+      pfGBitMask                    = buf.getInt();
+      pfBBitMask                    = buf.getInt();
+      pfABitMask                    = buf.getInt();
+      ddsCaps1                      = buf.getInt();
+      ddsCaps2                      = buf.getInt();
+      ddsCapsReserved1              = buf.getInt();
+      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
+  // simplest conversion using the DxTex tool to e.g. a DXT3 texture
+  // results in an illegal .dds file without either DDSD_PITCH or
+  // DDSD_LINEARSIZE set in the header's flags. This code, adapted
+  // from the DevIL library, fixes up the header in these situations.
+  private void fixupHeader() {
+    if (isCompressed() && !isSurfaceDescFlagSet(DDSD_LINEARSIZE)) {
+      // Figure out how big the linear size should be
+      int depth = header.backBufferCountOrDepth;
+      if (depth == 0) {
+        depth = 1;
+      }
+
+      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++) {
+      width >>= 1;
+    }
+    return width;
+  }
+
+  private int mipMapHeight(int map) {
+    int height = getHeight();
+    for (int i = 0; i < map; i++) {
+      height >>= 1;
+    }
+    return height;
+  }
+
+  private int mipMapSizeInBytes(int map) {
+    if (isCompressed()) {
+      if (!isSurfaceDescFlagSet(DDSD_LINEARSIZE)) {
+        throw new RuntimeException("Illegal compressed texture: DDSD_LINEARSIZE not specified in texture header");
+      }
+      int bytes = header.pitchOrLinearSize;
+      for (int i = 0; i < map; i++) {
+        bytes >>= 2;
+      }
+      return bytes;
+    } else {
+      int width  = mipMapWidth(map);
+      int height = mipMapHeight(map);
+      return width * height * (getDepth() / 8);
+    }
+  }
+
+  private boolean printIfRecognized(PrintStream tty, int flags, int flag, String what) {
+    if ((flags & flag) != 0) {
+      tty.println(what);
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/src/classes/com/sun/opengl/utils/DDSReader.java b/src/classes/com/sun/opengl/utils/DDSReader.java
deleted file mode 100755
index dbbf8e65b..000000000
--- a/src/classes/com/sun/opengl/utils/DDSReader.java
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * 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.*;
-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. */
-
-public class DDSReader {
-
-  /** Simple class describing images and data; does not encapsulate
-      image format information. User is responsible for transmitting
-      that information in another way. */
-
-  public static class ImageInfo {
-    private ByteBuffer data;
-    private int width;
-    private int height;
-    private boolean isCompressed;
-    private int compressionFormat;
-
-    public ImageInfo(ByteBuffer data, int width, int height, boolean compressed, int compressionFormat) {
-      this.data = data; this.width = width; this.height = height;
-      this.isCompressed = compressed; this.compressionFormat = compressionFormat;
-    }
-    public int        getWidth()  { return width;  }
-    public int        getHeight() { return height; }
-    public ByteBuffer getData()   { return data;   }
-    public boolean    isCompressed() { return isCompressed; }
-    public int        getCompressionFormat() {
-      if (!isCompressed())
-        throw new RuntimeException("Should not call unless compressed");
-      return compressionFormat;
-    }
-  }
-
-  private FileInputStream fis;
-  private FileChannel     chan;
-  private ByteBuffer buf;
-  private Header header;
-
-  //
-  // Selected bits in header flags
-  //
-
-  public static final int DDSD_CAPS            = 0x00000001; // Capacities are valid
-  public static final int DDSD_HEIGHT          = 0x00000002; // Height is valid
-  public static final int DDSD_WIDTH           = 0x00000004; // Width is valid
-  public static final int DDSD_PITCH           = 0x00000008; // Pitch is valid
-  public static final int DDSD_BACKBUFFERCOUNT = 0x00000020; // Back buffer count is valid
-  public static final int DDSD_ZBUFFERBITDEPTH = 0x00000040; // Z-buffer bit depth is valid (shouldn't be used in DDSURFACEDESC2)
-  public static final int DDSD_ALPHABITDEPTH   = 0x00000080; // Alpha bit depth is valid
-  public static final int DDSD_LPSURFACE       = 0x00000800; // lpSurface is valid
-  public static final int DDSD_PIXELFORMAT     = 0x00001000; // ddpfPixelFormat is valid
-  public static final int DDSD_MIPMAPCOUNT     = 0x00020000; // Mip map count is valid
-  public static final int DDSD_LINEARSIZE      = 0x00080000; // dwLinearSize is valid
-  public static final int DDSD_DEPTH           = 0x00800000; // dwDepth is valid
-
-  public static final int DDPF_ALPHAPIXELS     = 0x00000001; // Alpha channel is present
-  public static final int DDPF_ALPHA           = 0x00000002; // Only contains alpha information
-  public static final int DDPF_FOURCC          = 0x00000004; // FourCC code is valid
-  public static final int DDPF_PALETTEINDEXED4 = 0x00000008; // Surface is 4-bit color indexed
-  public static final int DDPF_PALETTEINDEXEDTO8 = 0x00000010; // Surface is indexed into a palette which stores indices
-                                                               // into the destination surface's 8-bit palette
-  public static final int DDPF_PALETTEINDEXED8 = 0x00000020; // Surface is 8-bit color indexed
-  public static final int DDPF_RGB             = 0x00000040; // RGB data is present
-  public static final int DDPF_COMPRESSED      = 0x00000080; // Surface will accept pixel data in the format specified
-                                                             // and compress it during the write
-  public static final int DDPF_RGBTOYUV        = 0x00000100; // Surface will accept RGB data and translate it during
-                                                             // the write to YUV data. The format of the data to be written
-                                                             // will be contained in the pixel format structure. The DDPF_RGB
-                                                             // flag will be set.
-  public static final int DDPF_YUV             = 0x00000200; // Pixel format is YUV - YUV data in pixel format struct is valid
-  public static final int DDPF_ZBUFFER         = 0x00000400; // Pixel format is a z buffer only surface
-  public static final int DDPF_PALETTEINDEXED1 = 0x00000800; // Surface is 1-bit color indexed
-  public static final int DDPF_PALETTEINDEXED2 = 0x00001000; // Surface is 2-bit color indexed
-  public static final int DDPF_ZPIXELS         = 0x00002000; // Surface contains Z information in the pixels
-
-  // Selected bits in DDS capabilities flags
-  public static final int DDSCAPS_TEXTURE      = 0x00001000; // Can be used as a texture
-  public static final int DDSCAPS_MIPMAP       = 0x00400000; // Is one level of a mip-map
-
-  // Known pixel formats
-  public static final int D3DFMT_UNKNOWN   =  0;
-  public static final int D3DFMT_R8G8B8    =  20;
-  public static final int D3DFMT_A8R8G8B8  =  21;
-  public static final int D3DFMT_X8R8G8B8  =  22;
-  // The following are also valid FourCC codes
-  public static final int D3DFMT_DXT1      =  0x31545844;
-  public static final int D3DFMT_DXT2      =  0x32545844;
-  public static final int D3DFMT_DXT3      =  0x33545844;
-  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));
-  }
-
-  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();
-  }
-
-  public void close() {
-    try {
-      chan.close();
-      fis.close();
-    } catch (IOException e) {
-      e.printStackTrace();
-    }
-  }
-
-  /** Test for presence/absence of surface description flags (DDSD_*) */
-  public boolean isSurfaceDescFlagSet(int flag) {
-    return ((header.flags & flag) != 0);
-  }
-
-  /** Test for presence/absence of pixel format flags (DDPF_*) */
-  public boolean isPixelFormatFlagSet(int flag) {
-    return ((header.pfFlags & flag) != 0);
-  }
-
-  /** Gets the pixel format of this texture (D3DFMT_*) based on some
-      heuristics. Returns D3DFMT_UNKNOWN if could not recognize the
-      pixel format. */
-  public int getPixelFormat() {
-    if (isCompressed()) {
-      return getCompressionFormat();
-    } else if (isPixelFormatFlagSet(DDPF_RGB)) {
-      if (isPixelFormatFlagSet(DDPF_ALPHAPIXELS)) {
-        if (getDepth() == 32 &&
-            header.pfRBitMask == 0x00FF0000 &&
-            header.pfGBitMask == 0x0000FF00 &&
-            header.pfBBitMask == 0x000000FF &&
-            header.pfABitMask == 0xFF000000) {
-          return D3DFMT_A8R8G8B8;
-        }
-      } else {
-        if (getDepth() == 24 &&
-            header.pfRBitMask == 0x00FF0000 &&
-            header.pfGBitMask == 0x0000FF00 &&
-            header.pfBBitMask == 0x000000FF) {
-          return D3DFMT_R8G8B8;
-        } else if (getDepth() == 32 &&
-                   header.pfRBitMask == 0x00FF0000 &&
-                   header.pfGBitMask == 0x0000FF00 &&
-                   header.pfBBitMask == 0x000000FF) {
-          return D3DFMT_X8R8G8B8;
-        }
-      }
-    }
-
-    return D3DFMT_UNKNOWN;
-  }
-
-  /** Indicates whether this texture is compressed. */
-  public boolean isCompressed() {
-    return (isPixelFormatFlagSet(DDPF_FOURCC));
-  }
-
-  /** If this surface is compressed, returns the kind of compression
-      used (DXT1..DXT5). */
-  public int getCompressionFormat() {
-    return header.pfFourCC;
-  }
-
-  /** Width of the texture (or the top-most mipmap if mipmaps are
-      present) */
-  public int getWidth() {
-    return header.width;
-  }
-
-  /** Height of the texture (or the top-most mipmap if mipmaps are
-      present) */
-  public int getHeight() {
-    return header.height;
-  }
-
-  /** Total number of bits per pixel. Only valid if DDPF_RGB is
-      present. For A8R8G8B8, would be 32. */
-  public int getDepth() {
-    return header.pfRGBBitCount;
-  }
-
-  /** Number of mip maps in the texture */
-  public int getNumMipMaps() {
-    if (!isSurfaceDescFlagSet(DDSD_MIPMAPCOUNT)) {
-      return 0;
-    }
-    return header.mipMapCountOrAux;
-  }
-
-  /** Gets the <i>i</i>th mipmap data (0..getNumMipMaps() - 1) */
-  public ImageInfo getMipMap(int map) {
-    if (getNumMipMaps() > 0 &&
-        ((map < 0) || (map >= getNumMipMaps()))) {
-      throw new RuntimeException("Illegal mipmap number " + map + " (0.." + (getNumMipMaps() - 1) + ")");
-    }
-
-    // Figure out how far to seek
-    int seek = 4 + header.size;
-    for (int i = 0; i < map; i++) {
-      seek += mipMapSizeInBytes(i);
-    }
-    buf.limit(seek + mipMapSizeInBytes(map));
-    buf.position(seek);
-    ByteBuffer next = buf.slice();
-    buf.position(0);
-    buf.limit(buf.capacity());
-    return new ImageInfo(next, mipMapWidth(map), mipMapHeight(map), isCompressed(), getCompressionFormat());
-  }
-
-  /** Returns an array of ImageInfos corresponding to all mipmap
-      levels of this DDS file. */
-  public ImageInfo[] getAllMipMaps() {
-    int numLevels = getNumMipMaps();
-    if (numLevels == 0) {
-      numLevels = 1;
-    }
-    ImageInfo[] result = new ImageInfo[numLevels];
-    for (int i = 0; i < numLevels; i++) {
-      result[i] = getMipMap(i);
-    }
-    return result;
-  }
-
-  /** Converts e.g. DXT1 compression format constant (see {@link
-      #getCompressionFormat}) into "DXT1". */
-  public static String getCompressionFormatName(int compressionFormat) {
-    StringBuffer buf = new StringBuffer();
-    for (int i = 0; i < 4; i++) {
-      char c = (char) (compressionFormat & 0xFF);
-      buf.append(c);
-      compressionFormat = compressionFormat >> 8;
-    }
-    return buf.toString();
-  }
-
-  public void debugPrint() {
-    PrintStream tty = System.err;
-    tty.println("Compressed texture: " + isCompressed());
-    if (isCompressed()) {
-      int fmt = getCompressionFormat();
-      String name = getCompressionFormatName(fmt);
-      tty.println("Compression format: 0x" + Integer.toHexString(fmt) + " (" + name + ")");
-    }
-    tty.println("Width: " + header.width + " Height: " + header.height);
-    tty.println("header.pitchOrLinearSize: " + header.pitchOrLinearSize);
-    tty.println("header.pfRBitMask: 0x" + Integer.toHexString(header.pfRBitMask));
-    tty.println("header.pfGBitMask: 0x" + Integer.toHexString(header.pfGBitMask));
-    tty.println("header.pfBBitMask: 0x" + Integer.toHexString(header.pfBBitMask));
-    tty.println("SurfaceDesc flags:");
-    boolean recognizedAny = false;
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_CAPS, "DDSD_CAPS");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_HEIGHT, "DDSD_HEIGHT");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_WIDTH, "DDSD_WIDTH");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_PITCH, "DDSD_PITCH");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_BACKBUFFERCOUNT, "DDSD_BACKBUFFERCOUNT");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_ZBUFFERBITDEPTH, "DDSD_ZBUFFERBITDEPTH");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_ALPHABITDEPTH, "DDSD_ALPHABITDEPTH");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_LPSURFACE, "DDSD_LPSURFACE");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_PIXELFORMAT, "DDSD_PIXELFORMAT");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_MIPMAPCOUNT, "DDSD_MIPMAPCOUNT");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_LINEARSIZE, "DDSD_LINEARSIZE");
-    recognizedAny |= printIfRecognized(tty, header.flags, DDSD_DEPTH, "DDSD_DEPTH");
-    if (!recognizedAny) {
-      tty.println("(none)");
-    }
-    tty.println("Raw SurfaceDesc flags: 0x" + Integer.toHexString(header.flags));
-    tty.println("Pixel format flags:");
-    recognizedAny = false;
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ALPHAPIXELS, "DDPF_ALPHAPIXELS");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ALPHA, "DDPF_ALPHA");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_FOURCC, "DDPF_FOURCC");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED4, "DDPF_PALETTEINDEXED4");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXEDTO8, "DDPF_PALETTEINDEXEDTO8");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED8, "DDPF_PALETTEINDEXED8");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_RGB, "DDPF_RGB");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_COMPRESSED, "DDPF_COMPRESSED");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_RGBTOYUV, "DDPF_RGBTOYUV");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_YUV, "DDPF_YUV");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ZBUFFER, "DDPF_ZBUFFER");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED1, "DDPF_PALETTEINDEXED1");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED2, "DDPF_PALETTEINDEXED2");
-    recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ZPIXELS, "DDPF_ZPIXELS");
-    if (!recognizedAny) {
-      tty.println("(none)");
-    }
-    tty.println("Raw pixel format flags: 0x" + Integer.toHexString(header.pfFlags));
-    tty.println("Depth: " + getDepth());
-    tty.println("Number of mip maps: " + getNumMipMaps());
-    int fmt = getPixelFormat();
-    tty.print("Pixel format: ");
-    switch (fmt) {
-    case D3DFMT_R8G8B8:   tty.println("D3DFMT_R8G8B8"); break;
-    case D3DFMT_A8R8G8B8: tty.println("D3DFMT_A8R8G8B8"); break;
-    case D3DFMT_X8R8G8B8: tty.println("D3DFMT_X8R8G8B8"); break;
-    case D3DFMT_DXT1:     tty.println("D3DFMT_DXT1"); break;
-    case D3DFMT_DXT2:     tty.println("D3DFMT_DXT2"); break;
-    case D3DFMT_DXT3:     tty.println("D3DFMT_DXT3"); break;
-    case D3DFMT_DXT4:     tty.println("D3DFMT_DXT4"); break;
-    case D3DFMT_DXT5:     tty.println("D3DFMT_DXT5"); break;
-    case D3DFMT_UNKNOWN:  tty.println("D3DFMT_UNKNOWN"); break;
-    default:              tty.println("(unknown pixel format " + fmt + ")"); break;
-    }
-  }
-
-  //----------------------------------------------------------------------
-  // Internals only below this point
-  //
-
-  private static final int MAGIC = 0x20534444;
-
-  static class Header {
-    int size;                 // size of the DDSURFACEDESC structure
-    int flags;                // determines what fields are valid
-    int height;               // height of surface to be created
-    int width;                // width of input surface
-    int pitchOrLinearSize;
-    int backBufferCountOrDepth;
-    int mipMapCountOrAux;     // number of mip-map levels requested (in this context)
-    int alphaBitDepth;        // depth of alpha buffer requested
-    int reserved1;            // reserved
-    int surface;              // pointer to the associated surface memory
-    // NOTE: following two entries are from DDCOLORKEY data structure
-    // Are overlaid with color for empty cubemap faces (unused in this reader)
-    int colorSpaceLowValue;
-    int colorSpaceHighValue;
-    int destBltColorSpaceLowValue;
-    int destBltColorSpaceHighValue;
-    int srcOverlayColorSpaceLowValue;
-    int srcOverlayColorSpaceHighValue;
-    int srcBltColorSpaceLowValue;
-    int srcBltColorSpaceHighValue;
-    // NOTE: following entries are from DDPIXELFORMAT data structure
-    // Are overlaid with flexible vertex format description of vertex
-    // buffers (unused in this reader)
-    int pfSize;                 // size of DDPIXELFORMAT structure
-    int pfFlags;                // pixel format flags
-    int pfFourCC;               // (FOURCC code)
-    // Following five entries have multiple interpretations, not just
-    // RGBA (but that's all we support right now)
-    int pfRGBBitCount;          // how many bits per pixel
-    int pfRBitMask;             // mask for red bits
-    int pfGBitMask;             // mask for green bits
-    int pfBBitMask;             // mask for blue bits
-    int pfABitMask;             // mask for alpha channel
-    int ddsCaps1;               // Texture and mip-map flags
-    int ddsCaps2;               // Advanced capabilities, not yet used 
-    int ddsCapsReserved1;
-    int ddsCapsReserved2;
-    int textureStage;           // stage in multitexture cascade
-
-    void read(ByteBuffer buf) throws IOException {
-      int magic                     = buf.getInt();
-      if (magic != MAGIC) {
-        throw new IOException("Incorrect magic number 0x" +
-                              Integer.toHexString(magic) +
-                              " (expected " + MAGIC + ")");
-      }
-
-      size                          = buf.getInt();
-      flags                         = buf.getInt();
-      height                        = buf.getInt();
-      width                         = buf.getInt();
-      pitchOrLinearSize             = buf.getInt();
-      backBufferCountOrDepth        = buf.getInt();
-      mipMapCountOrAux              = buf.getInt();
-      alphaBitDepth                 = buf.getInt();
-      reserved1                     = buf.getInt();
-      surface                       = buf.getInt();
-      colorSpaceLowValue            = buf.getInt();
-      colorSpaceHighValue           = buf.getInt();
-      destBltColorSpaceLowValue     = buf.getInt();
-      destBltColorSpaceHighValue    = buf.getInt();
-      srcOverlayColorSpaceLowValue  = buf.getInt();
-      srcOverlayColorSpaceHighValue = buf.getInt();
-      srcBltColorSpaceLowValue      = buf.getInt();
-      srcBltColorSpaceHighValue     = buf.getInt();
-      pfSize                        = buf.getInt();
-      pfFlags                       = buf.getInt();
-      pfFourCC                      = buf.getInt();
-      pfRGBBitCount                 = buf.getInt();
-      pfRBitMask                    = buf.getInt();
-      pfGBitMask                    = buf.getInt();
-      pfBBitMask                    = buf.getInt();
-      pfABitMask                    = buf.getInt();
-      ddsCaps1                      = buf.getInt();
-      ddsCaps2                      = buf.getInt();
-      ddsCapsReserved1              = buf.getInt();
-      ddsCapsReserved2              = buf.getInt();
-      textureStage                  = buf.getInt();
-    }
-  }
-
-  // Microsoft doesn't follow their own specifications and the
-  // simplest conversion using the DxTex tool to e.g. a DXT3 texture
-  // results in an illegal .dds file without either DDSD_PITCH or
-  // DDSD_LINEARSIZE set in the header's flags. This code, adapted
-  // from the DevIL library, fixes up the header in these situations.
-  private void fixupHeader() {
-    if (isCompressed() && !isSurfaceDescFlagSet(DDSD_LINEARSIZE)) {
-      // Figure out how big the linear size should be
-      int depth = header.backBufferCountOrDepth;
-      if (depth == 0) {
-        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;
-      }
-
-      header.pitchOrLinearSize = blockSize;
-      header.flags |= DDSD_LINEARSIZE;
-    }
-  }
-
-  private int mipMapWidth(int map) {
-    int width = getWidth();
-    for (int i = 0; i < map; i++) {
-      width >>= 1;
-    }
-    return width;
-  }
-
-  private int mipMapHeight(int map) {
-    int height = getHeight();
-    for (int i = 0; i < map; i++) {
-      height >>= 1;
-    }
-    return height;
-  }
-
-  private int mipMapSizeInBytes(int map) {
-    if (isCompressed()) {
-      if (!isSurfaceDescFlagSet(DDSD_LINEARSIZE)) {
-        throw new RuntimeException("Illegal compressed texture: DDSD_LINEARSIZE not specified in texture header");
-      }
-      int bytes = header.pitchOrLinearSize;
-      for (int i = 0; i < map; i++) {
-        bytes >>= 2;
-      }
-      return bytes;
-    } else {
-      int width  = mipMapWidth(map);
-      int height = mipMapHeight(map);
-      return width * height * (getDepth() / 8);
-    }
-  }
-
-  private boolean printIfRecognized(PrintStream tty, int flags, int flag, String what) {
-    if ((flags & flag) != 0) {
-      tty.println(what);
-      return true;
-    }
-    return false;
-  }
-}
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;
   }
 
@@ -241,6 +241,17 @@ public class Texture {
     updateImage(data, 0);
   }
 
+  /**
+   * 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
@@ -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);
   }
 
@@ -412,18 +423,6 @@ public class Texture {
   // Internals only below this point
   //
 
-  /**
-   * 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.
    *
@@ -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);
       }
 
@@ -917,10 +1083,230 @@ 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;
+}
-- 
cgit v1.2.3