aboutsummaryrefslogtreecommitdiffstats
path: root/src/classes/com/sun/opengl/util/texture
diff options
context:
space:
mode:
authorKenneth Russel <[email protected]>2006-01-13 07:29:23 +0000
committerKenneth Russel <[email protected]>2006-01-13 07:29:23 +0000
commit16e23f079b1e5fdc15b64bf1593cbda8e8dcffc0 (patch)
tree8cf430f395f2a2691d152c9a61ca212d7a6c1f55 /src/classes/com/sun/opengl/util/texture
parent688ffdc8cc190c51c3e7355a4893e3e475efa29f (diff)
Renamed com.sun.opengl.utils to com.sun.opengl.util. Moved
TextureIO-related classes to com.sun.opengl.util.texture and TextureProvider, TextureWriter and format-specific readers to com.sun.opengl.util.texture.spi. Renamed BufferUtils to BufferUtil. Added ImageUtil and FileUtil. Cleaned up javadoc. Updated demos. Cleaned up some imports. git-svn-id: file:///usr/local/projects/SUN/JOGL/git-svn/svn-server-sync/jogl/trunk@538 232f8b59-042b-4e1e-8c03-345bb8c30851
Diffstat (limited to 'src/classes/com/sun/opengl/util/texture')
-rwxr-xr-xsrc/classes/com/sun/opengl/util/texture/Texture.java536
-rwxr-xr-xsrc/classes/com/sun/opengl/util/texture/TextureCoords.java79
-rwxr-xr-xsrc/classes/com/sun/opengl/util/texture/TextureData.java512
-rwxr-xr-xsrc/classes/com/sun/opengl/util/texture/TextureIO.java1272
-rwxr-xr-xsrc/classes/com/sun/opengl/util/texture/spi/DDSImage.java722
-rwxr-xr-xsrc/classes/com/sun/opengl/util/texture/spi/LEDataInputStream.java223
-rwxr-xr-xsrc/classes/com/sun/opengl/util/texture/spi/SGIImage.java671
-rwxr-xr-xsrc/classes/com/sun/opengl/util/texture/spi/TGAImage.java386
-rwxr-xr-xsrc/classes/com/sun/opengl/util/texture/spi/TextureProvider.java165
-rwxr-xr-xsrc/classes/com/sun/opengl/util/texture/spi/TextureWriter.java57
10 files changed, 4623 insertions, 0 deletions
diff --git a/src/classes/com/sun/opengl/util/texture/Texture.java b/src/classes/com/sun/opengl/util/texture/Texture.java
new file mode 100755
index 000000000..9987551e2
--- /dev/null
+++ b/src/classes/com/sun/opengl/util/texture/Texture.java
@@ -0,0 +1,536 @@
+/*
+ * 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.
+ */
+
+package com.sun.opengl.util.texture;
+
+import java.nio.*;
+
+import javax.media.opengl.*;
+import javax.media.opengl.glu.*;
+import com.sun.opengl.impl.*;
+
+/**
+ * Represents an OpenGL texture object. Contains convenience routines
+ * for enabling/disabling OpenGL texture state, binding this texture,
+ * and computing texture coordinates for both the entire image as well
+ * as a sub-image.
+ *
+ * <br> REMIND: document GL_TEXTURE_2D/GL_TEXTURE_RECTANGLE_ARB issues...
+ * <br> REMIND: translucent images will have premultiplied comps by default...
+ *
+ * @author Chris Campbell
+ * @author Kenneth Russell
+ */
+public class Texture {
+ /** The GL target type. */
+ private int target;
+ /** The GL texture ID. */
+ private int texID;
+ /** The width of the texture. */
+ private int texWidth;
+ /** The height of the texture. */
+ private int texHeight;
+ /** The width of the image. */
+ private int imgWidth;
+ /** The height of the image. */
+ private int imgHeight;
+ /** Indicates whether the TextureData requires a vertical flip of
+ the texture coords. */
+ private boolean mustFlipVertically;
+
+ /** The texture coordinates corresponding to the entire image. */
+ private TextureCoords coords;
+
+ private static final boolean DEBUG = Debug.debug("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 = GLU.getCurrentGL();
+ texID = createTextureID(gl);
+
+ updateImage(data);
+ }
+
+ // Constructor for use when creating e.g. cube maps, where there is
+ // no initial texture data
+ Texture(int target) throws GLException {
+ GL gl = GLU.getCurrentGL();
+ texID = createTextureID(gl);
+ this.target = target;
+ }
+
+ /**
+ * Enables this texture's target (e.g., GL_TEXTURE_2D) in the
+ * current GL context's state.
+ *
+ * @throws GLException if no OpenGL context was current or if any
+ * OpenGL-related errors occurred
+ */
+ public void enable() throws GLException {
+ GLU.getCurrentGL().glEnable(target);
+ }
+
+ /**
+ * Disables this texture's target (e.g., GL_TEXTURE_2D) in the
+ * current GL context's state.
+ *
+ * @throws GLException if no OpenGL context was current or if any
+ * OpenGL-related errors occurred
+ */
+ public void disable() throws GLException {
+ GLU.getCurrentGL().glDisable(target);
+ }
+
+ /**
+ * Binds this texture to the current GL context.
+ *
+ * @throws GLException if no OpenGL context was current or if any
+ * OpenGL-related errors occurred
+ */
+ public void bind() throws GLException {
+ GLU.getCurrentGL().glBindTexture(target, texID);
+ }
+
+ /**
+ * Disposes the native resources used by this texture object.
+ *
+ * @throws GLException if no OpenGL context was current or if any
+ * OpenGL-related errors occurred
+ */
+ public void dispose() throws GLException {
+ GLU.getCurrentGL().glDeleteTextures(1, new int[] {texID}, 0);
+ texID = 0;
+ }
+
+ /**
+ * Returns the OpenGL "target" of this texture.
+ *
+ * @return the OpenGL target of this texture
+ * @see javax.media.opengl.GL#GL_TEXTURE_2D
+ * @see javax.media.opengl.GL#GL_TEXTURE_RECTANGLE_ARB
+ */
+ public int getTarget() {
+ return target;
+ }
+
+ /**
+ * Returns the width of the texture. Note that the texture width will
+ * be greater than or equal to the width of the image contained within.
+ *
+ * @return the width of the texture
+ */
+ public int getWidth() {
+ return texWidth;
+ }
+
+ /**
+ * Returns the height of the texture. Note that the texture height will
+ * be greater than or equal to the height of the image contained within.
+ *
+ * @return the height of the texture
+ */
+ public int getHeight() {
+ return texHeight;
+ }
+
+ /**
+ * Returns the width of the image contained within this texture.
+ *
+ * @return the width of the image
+ */
+ public int getImageWidth() {
+ return imgWidth;
+ }
+
+ /**
+ * Returns the height of the image contained within this texture.
+ *
+ * @return the height of the image
+ */
+ public int getImageHeight() {
+ return imgHeight;
+ }
+
+ /**
+ * Returns the set of texture coordinates corresponding to the
+ * entire image. If the TextureData indicated that the texture
+ * coordinates must be flipped vertically, the returned
+ * TextureCoords will take that into account.
+ *
+ * @return the texture coordinates corresponding to the entire image
+ */
+ public TextureCoords getImageTexCoords() {
+ return coords;
+ }
+
+ /**
+ * Returns the set of texture coordinates corresponding to the
+ * specified sub-image. The (x1, y1) and (x2, y2) points are
+ * specified in terms of pixels starting from the lower-left of the
+ * image. (x1, y1) should specify the lower-left corner of the
+ * sub-image and (x2, y2) the upper-right corner of the sub-image.
+ * If the TextureData indicated that the texture coordinates must be
+ * flipped vertically, the returned TextureCoords will take that
+ * into account; this should not be handled by the end user in the
+ * specification of the y1 and y2 coordinates.
+ *
+ * @return the texture coordinates corresponding to the specified sub-image
+ */
+ public TextureCoords getSubImageTexCoords(int x1, int y1, int x2, int y2) {
+ if (target == GL.GL_TEXTURE_RECTANGLE_ARB) {
+ if (mustFlipVertically) {
+ return new TextureCoords(x1, texHeight - y1, x2, texHeight - y2);
+ } else {
+ return new TextureCoords(x1, y1, x2, y2);
+ }
+ } else {
+ float tx1 = (float)x1 / (float)texWidth;
+ float ty1 = (float)y1 / (float)texHeight;
+ float tx2 = (float)x2 / (float)texWidth;
+ float ty2 = (float)y2 / (float)texHeight;
+ if (mustFlipVertically) {
+ return new TextureCoords(tx1, 1.0f - ty1, tx2, 1.0f - ty2);
+ } else {
+ return new TextureCoords(tx1, ty1, tx2, ty2);
+ }
+ }
+ }
+
+ /**
+ * Updates the entire content area of this texture using the data in
+ * the given image.
+ *
+ * @throws GLException if no OpenGL context was current or if any
+ * OpenGL-related errors occurred
+ */
+ public void updateImage(TextureData data) throws GLException {
+ 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
+ * for construction of cube maps.
+ *
+ * @throws GLException if no OpenGL context was current or if any
+ * OpenGL-related errors occurred
+ */
+ public void updateImage(TextureData data, int target) throws GLException {
+ GL gl = GLU.getCurrentGL();
+
+ imgWidth = data.getWidth();
+ imgHeight = data.getHeight();
+ mustFlipVertically = data.getMustFlipVertically();
+
+ int newTarget = 0;
+
+ if (data.getMipmap()) {
+ // GLU always scales the texture's dimensions to be powers of
+ // two. It also doesn't really matter exactly what the texture
+ // width and height are because the texture coords are always
+ // between 0.0 and 1.0.
+ imgWidth = nextPowerOfTwo(imgWidth);
+ imgHeight = nextPowerOfTwo(imgHeight);
+ texWidth = imgWidth;
+ texHeight = imgHeight;
+ newTarget = GL.GL_TEXTURE_2D;
+ } else if ((isPowerOfTwo(imgWidth) && isPowerOfTwo(imgHeight)) ||
+ gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two")) {
+ if (DEBUG) {
+ if (isPowerOfTwo(imgWidth) && isPowerOfTwo(imgHeight)) {
+ System.err.println("Power-of-two texture");
+ } else {
+ System.err.println("Using GL_ARB_texture_non_power_of_two");
+ }
+ }
+
+ texWidth = imgWidth;
+ texHeight = imgHeight;
+ newTarget = GL.GL_TEXTURE_2D;
+ } else if (gl.isExtensionAvailable("GL_ARB_texture_rectangle")) {
+ if (DEBUG) {
+ System.err.println("Using GL_ARB_texture_rectangle");
+ }
+
+ texWidth = imgWidth;
+ texHeight = imgHeight;
+ newTarget = GL.GL_TEXTURE_RECTANGLE_ARB;
+ } else {
+ if (DEBUG) {
+ System.err.println("Expanding texture to power-of-two dimensions");
+ }
+
+ if (data.getBorder() != 0) {
+ throw new RuntimeException("Scaling up a non-power-of-two texture which has a border won't work");
+ }
+ texWidth = nextPowerOfTwo(imgWidth);
+ texHeight = nextPowerOfTwo(imgHeight);
+ newTarget = GL.GL_TEXTURE_2D;
+ }
+
+ setImageSize(imgWidth, imgHeight);
+
+ if (target != 0) {
+ // Allow user to override auto detection and skip bind step (for
+ // cubemap construction)
+ newTarget = target;
+ if (this.target == 0) {
+ throw new GLException("Override of target failed; no target specified yet");
+ }
+ gl.glBindTexture(this.target, texID);
+ } else {
+ gl.glBindTexture(newTarget, texID);
+ }
+
+ // REMIND: let the user specify these, optionally
+ int minFilter = (data.getMipmap() ? GL.GL_LINEAR_MIPMAP_LINEAR : GL.GL_LINEAR);
+ int magFilter = GL.GL_LINEAR;
+ int wrapMode = GL.GL_CLAMP_TO_EDGE;
+
+ // REMIND: figure out what to do for GL_TEXTURE_RECTANGLE_ARB
+ if (newTarget != GL.GL_TEXTURE_RECTANGLE_ARB) {
+ gl.glTexParameteri(newTarget, GL.GL_TEXTURE_MIN_FILTER, minFilter);
+ gl.glTexParameteri(newTarget, GL.GL_TEXTURE_MAG_FILTER, magFilter);
+ gl.glTexParameteri(newTarget, GL.GL_TEXTURE_WRAP_S, wrapMode);
+ gl.glTexParameteri(newTarget, GL.GL_TEXTURE_WRAP_T, wrapMode);
+ if (newTarget == GL.GL_TEXTURE_CUBE_MAP) {
+ gl.glTexParameteri(newTarget, GL.GL_TEXTURE_WRAP_R, wrapMode);
+ }
+ }
+
+ if (data.getMipmap()) {
+ GLU glu = new GLU();
+ glu.gluBuild2DMipmaps(newTarget, data.getInternalFormat(),
+ data.getWidth(), data.getHeight(),
+ data.getPixelFormat(), data.getPixelType(), data.getBuffer());
+ } else {
+ gl.glTexImage2D(newTarget, 0, data.getInternalFormat(),
+ texWidth, texHeight, data.getBorder(),
+ data.getPixelFormat(), data.getPixelType(), null);
+ Buffer[] mipmapData = data.getMipmapData();
+ if (mipmapData != null) {
+ for (int i = 0; i < mipmapData.length; i++) {
+ updateSubImageImpl(data, newTarget, i, 0, 0);
+ }
+ } else {
+ updateSubImageImpl(data, newTarget, 0, 0, 0);
+ }
+ }
+
+ // Don't overwrite target if we're loading e.g. faces of a cube
+ // map
+ if ((this.target == 0) ||
+ (this.target == GL.GL_TEXTURE_2D) ||
+ (this.target == GL.GL_TEXTURE_RECTANGLE_ARB)) {
+ this.target = newTarget;
+ }
+ }
+
+ /**
+ * Updates a subregion of the content area of this texture using the
+ * data in the given image. Only updates the specified mipmap level
+ * and does not re-generate mipmaps if they were originally produced
+ * or loaded.
+ *
+ * @param data the image data to be uploaded to this texture
+ * @param mipmapLevel the mipmap level of the texture to set. If
+ * this is non-zero and the TextureData contains mipmap data, the
+ * appropriate mipmap level will be selected.
+ * @param x the x offset (in pixels) relative to the lower-left corner
+ * of this texture
+ * @param y the y offset (in pixels) relative to the lower-left corner
+ * of this texture
+ *
+ * @throws GLException if no OpenGL context was current or if any
+ * OpenGL-related errors occurred
+ */
+ public void updateSubImage(TextureData data, int mipmapLevel, int x, int y) throws GLException {
+ updateSubImageImpl(data, target, mipmapLevel, x, y);
+ }
+
+ /**
+ * Sets the OpenGL integer texture parameter for the texture's
+ * target. This gives control over parameters such as
+ * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T, which by default are set
+ * to GL_CLAMP_TO_EDGE. Causes this texture to be bound to the
+ * current texture state.
+ *
+ * @throws GLException if no OpenGL context was current or if any
+ * OpenGL-related errors occurred
+ */
+ public void setTexParameteri(int parameterName,
+ int value) {
+ bind();
+ GL gl = GLU.getCurrentGL();
+ gl.glTexParameteri(target, parameterName, value);
+ }
+
+ /**
+ * Returns the underlying OpenGL texture object for this texture.
+ * Most applications will not need to access this, since it is
+ * handled automatically by the bind() and dispose() APIs.
+ */
+ public int getTextureObject() {
+ return texID;
+ }
+
+ //----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ /**
+ * Returns true if the given value is a power of two.
+ *
+ * @return true if the given value is a power of two, false otherwise
+ */
+ private static boolean isPowerOfTwo(int val) {
+ return ((val & (val - 1)) == 0);
+ }
+
+ /**
+ * Returns the nearest power of two that is larger than the given value.
+ * If the given value is already a power of two, this method will simply
+ * return that value.
+ *
+ * @param val the value
+ * @return the next power of two
+ */
+ private static int nextPowerOfTwo(int val) {
+ int ret = 1;
+ while (ret < val) {
+ ret <<= 1;
+ }
+ return ret;
+ }
+
+ /**
+ * Updates the actual image dimensions; usually only called from
+ * <code>updateImage</code>.
+ */
+ private void setImageSize(int width, int height) {
+ imgWidth = width;
+ imgHeight = height;
+ if (target == GL.GL_TEXTURE_RECTANGLE_ARB) {
+ if (mustFlipVertically) {
+ coords = new TextureCoords(0, imgHeight, imgWidth, 0);
+ } else {
+ coords = new TextureCoords(0, 0, imgWidth, imgHeight);
+ }
+ } else {
+ if (mustFlipVertically) {
+ coords = new TextureCoords(0, (float) imgHeight / (float) texHeight,
+ (float) imgWidth / (float) texWidth, 0);
+ } else {
+ coords = new TextureCoords(0, 0,
+ (float) imgWidth / (float) texWidth,
+ (float) imgHeight / (float) texHeight);
+ }
+ }
+ }
+
+ private void updateSubImageImpl(TextureData data, int newTarget, int mipmapLevel, int x, int y) throws GLException {
+ GL gl = GLU.getCurrentGL();
+ gl.glBindTexture(newTarget, texID);
+ int width = data.getWidth();
+ int height = data.getHeight();
+ Buffer buffer = data.getBuffer();
+ if (data.getMipmapData() != null) {
+ // Compute the width and height at the specified mipmap level
+ for (int i = 0; i < mipmapLevel; i++) {
+ width /= 2;
+ height /= 2;
+ }
+ buffer = data.getMipmapData()[mipmapLevel];
+ }
+
+ if (data.isDataCompressed()) {
+ switch (data.getInternalFormat()) {
+ case GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ case GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ case GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ case GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+ if (!gl.isExtensionAvailable("GL_EXT_texture_compression_s3tc") &&
+ !gl.isExtensionAvailable("GL_NV_texture_compression_vtc")) {
+ throw new GLException("DXTn compressed textures not supported by this graphics card");
+ }
+ break;
+ default:
+ // FIXME: should test availability of more texture
+ // compression extensions here
+ break;
+ }
+
+ gl.glCompressedTexSubImage2D(newTarget, mipmapLevel,
+ x, y, width, height,
+ data.getInternalFormat(),
+ buffer.remaining(), buffer);
+ } else {
+ int[] align = new int[1];
+ gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, align, 0); // save alignment
+ gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, data.getAlignment());
+
+ gl.glTexSubImage2D(newTarget, mipmapLevel,
+ x, y, width, height,
+ data.getPixelFormat(), data.getPixelType(),
+ buffer);
+ gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore align
+ }
+ }
+
+ /**
+ * Creates a new texture ID.
+ *
+ * @param gl the GL object associated with the current OpenGL context
+ * @return a new texture ID
+ */
+ private static int createTextureID(GL gl) {
+ int[] tmp = new int[1];
+ gl.glGenTextures(1, tmp, 0);
+ return tmp[0];
+ }
+}
diff --git a/src/classes/com/sun/opengl/util/texture/TextureCoords.java b/src/classes/com/sun/opengl/util/texture/TextureCoords.java
new file mode 100755
index 000000000..dca1d1e01
--- /dev/null
+++ b/src/classes/com/sun/opengl/util/texture/TextureCoords.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package com.sun.opengl.util.texture;
+
+/** Specifies texture coordinates for a rectangular area of a
+ texture. Note that some textures are inherently flipped vertically
+ from OpenGL's standard coordinate system. This class takes care of
+ this vertical flip so that the "bottom" and "top" coordinates may
+ sometimes be reversed. From the point of view of code rendering
+ textured polygons, it can always map the bottom and left texture
+ coordinates from the TextureCoords to the lower left point of the
+ textured polygon and achieve correct results. */
+
+public class TextureCoords {
+ // These represent the lower-left point
+ private float left;
+ private float bottom;
+ // These represent the upper-right point
+ private float right;
+ private float top;
+
+ public TextureCoords(float left, float bottom,
+ float right, float top) {
+ this.left = left;
+ this.bottom = bottom;
+ this.right = right;
+ this.top = top;
+ }
+
+ /** Returns the leftmost (x) texture coordinate of this
+ rectangle. */
+ public float left() { return left; }
+
+ /** Returns the rightmost (x) texture coordinate of this
+ rectangle. */
+ public float right() { return right; }
+
+ /** Returns the bottommost (y) texture coordinate of this
+ rectangle. */
+ public float bottom() { return bottom; }
+
+ /** Returns the topmost (y) texture coordinate of this
+ rectangle. */
+ public float top() { return top; }
+}
diff --git a/src/classes/com/sun/opengl/util/texture/TextureData.java b/src/classes/com/sun/opengl/util/texture/TextureData.java
new file mode 100755
index 000000000..83a1c983e
--- /dev/null
+++ b/src/classes/com/sun/opengl/util/texture/TextureData.java
@@ -0,0 +1,512 @@
+/*
+ * 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.
+ */
+
+package com.sun.opengl.util.texture;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Transparency;
+import java.awt.color.*;
+import java.awt.image.*;
+import java.nio.*;
+
+import javax.media.opengl.*;
+import com.sun.opengl.util.*;
+
+/**
+ * Represents the data for an OpenGL texture. This is separated from
+ * the notion of a Texture to support things like streaming in of
+ * textures in a background thread without requiring an OpenGL context
+ * to be current on that thread.
+ *
+ * @author Chris Campbell
+ * @author Kenneth Russell
+ */
+
+public class TextureData {
+ private int width;
+ private int height;
+ private int border;
+ private int pixelFormat;
+ private int pixelType;
+ private int internalFormat; // perhaps inferred from pixelFormat?
+ private boolean mipmap; // indicates whether mipmaps should be generated
+ // (ignored if mipmaps are supplied from the file)
+ private boolean dataIsCompressed;
+ private boolean mustFlipVertically; // Must flip texture coordinates
+ // vertically to get OpenGL output
+ // to look correct
+ private Buffer buffer; // the actual data...
+ private Buffer[] mipmapData; // ...or a series of mipmaps
+ private Flusher flusher;
+ private int alignment; // 1, 2, or 4 bytes
+
+ private static final ColorModel rgbaColorModel =
+ new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ new int[] {8, 8, 8, 8}, true, true,
+ Transparency.TRANSLUCENT,
+ DataBuffer.TYPE_BYTE);
+ private static final ColorModel rgbColorModel =
+ new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ new int[] {8, 8, 8, 0}, false, false,
+ Transparency.OPAQUE,
+ DataBuffer.TYPE_BYTE);
+
+ /**
+ * Constructs a new TextureData object with the specified parameters
+ * and data contained in the given Buffer. The optional Flusher can
+ * be used to clean up native resources associated with this
+ * TextureData when processing is complete; for example, closing of
+ * memory-mapped files that might otherwise require a garbage
+ * collection to reclaim and close.
+ *
+ * @param internalFormat the OpenGL internal format for the
+ * resulting texture; must be specified, may
+ * not be 0
+ * @param width the width in pixels of the texture
+ * @param height the height in pixels of the texture
+ * @param border the number of pixels of border this texture
+ * data has (0 or 1)
+ * @param pixelFormat the OpenGL pixel format for the
+ * resulting texture; must be specified, may
+ * not be 0
+ * @param pixelType the OpenGL type of the pixels of the texture
+ * @param mipmap indicates whether mipmaps should be
+ * autogenerated (using GLU) for the resulting
+ * texture. Currently if mipmap is true then
+ * dataIsCompressed may not be true.
+ * @param dataIsCompressed indicates whether the texture data is in
+ * compressed form
+ * (e.g. GL_COMPRESSED_RGB_S3TC_DXT1_EXT)
+ * @param mustFlipVertically indicates whether the texture
+ * coordinates must be flipped vertically
+ * in order to properly display the
+ * texture
+ * @param buffer the buffer containing the texture data
+ * @param flusher optional flusher to perform cleanup tasks
+ * upon call to flush()
+ *
+ * @throws IllegalArgumentException if any parameters of the texture
+ * data were invalid, such as requesting mipmap generation for a
+ * compressed texture
+ */
+ public TextureData(int internalFormat,
+ int width,
+ int height,
+ int border,
+ int pixelFormat,
+ int pixelType,
+ boolean mipmap,
+ boolean dataIsCompressed,
+ boolean mustFlipVertically,
+ Buffer buffer,
+ Flusher flusher) throws IllegalArgumentException {
+ if (mipmap && dataIsCompressed) {
+ throw new IllegalArgumentException("Can not generate mipmaps for compressed textures");
+ }
+
+ this.width = width;
+ this.height = height;
+ this.border = border;
+ this.pixelFormat = pixelFormat;
+ this.pixelType = pixelType;
+ this.internalFormat = internalFormat;
+ this.mipmap = mipmap;
+ this.dataIsCompressed = dataIsCompressed;
+ this.mustFlipVertically = mustFlipVertically;
+ this.buffer = buffer;
+ this.flusher = flusher;
+ alignment = 1; // FIXME: is this correct enough in all situations?
+ }
+
+ /**
+ * Constructs a new TextureData object with the specified parameters
+ * and data for multiple mipmap levels contained in the given array
+ * of Buffers. The optional Flusher can be used to clean up native
+ * resources associated with this TextureData when processing is
+ * complete; for example, closing of memory-mapped files that might
+ * otherwise require a garbage collection to reclaim and close.
+ *
+ * @param internalFormat the OpenGL internal format for the
+ * resulting texture; must be specified, may
+ * not be 0
+ * @param width the width in pixels of the topmost mipmap
+ * level of the texture
+ * @param height the height in pixels of the topmost mipmap
+ * level of the texture
+ * @param border the number of pixels of border this texture
+ * data has (0 or 1)
+ * @param pixelFormat the OpenGL pixel format for the
+ * resulting texture; must be specified, may
+ * not be 0
+ * @param pixelType the OpenGL type of the pixels of the texture
+ * @param dataIsCompressed indicates whether the texture data is in
+ * compressed form
+ * (e.g. GL_COMPRESSED_RGB_S3TC_DXT1_EXT)
+ * @param mustFlipVertically indicates whether the texture
+ * coordinates must be flipped vertically
+ * in order to properly display the
+ * texture
+ * @param mipmapData the buffers containing all mipmap levels
+ * of the texture's data
+ * @param flusher optional flusher to perform cleanup tasks
+ * upon call to flush()
+ *
+ * @throws IllegalArgumentException if any parameters of the texture
+ * data were invalid, such as requesting mipmap generation for a
+ * compressed texture
+ */
+ public TextureData(int internalFormat,
+ int width,
+ int height,
+ int border,
+ int pixelFormat,
+ int pixelType,
+ boolean dataIsCompressed,
+ boolean mustFlipVertically,
+ Buffer[] mipmapData,
+ Flusher flusher) throws IllegalArgumentException {
+ this.width = width;
+ this.height = height;
+ this.border = border;
+ this.pixelFormat = pixelFormat;
+ this.pixelType = pixelType;
+ this.internalFormat = internalFormat;
+ this.dataIsCompressed = dataIsCompressed;
+ this.mustFlipVertically = mustFlipVertically;
+ this.mipmapData = (Buffer[]) mipmapData.clone();
+ this.flusher = flusher;
+ alignment = 1; // FIXME: is this correct enough in all situations?
+ }
+
+ /**
+ * Constructs a new TextureData object with the specified parameters
+ * and data contained in the given BufferedImage.
+ *
+ * @param internalFormat the OpenGL internal format for the
+ * resulting texture; may be 0, in which case
+ * it is inferred from the image's type
+ * @param pixelFormat the OpenGL internal format for the
+ * resulting texture; may be 0, in which case
+ * it is inferred from the image's type (note:
+ * this argument is currently always ignored)
+ * @param mipmap indicates whether mipmaps should be
+ * autogenerated (using GLU) for the resulting
+ * texture
+ * @param image the image containing the texture data
+ */
+ public TextureData(int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ BufferedImage image) {
+ if (internalFormat == 0) {
+ this.internalFormat = image.getColorModel().hasAlpha() ? GL.GL_RGBA : GL.GL_RGB;
+ } else {
+ this.internalFormat = internalFormat;
+ }
+ createFromImage(image);
+ this.mipmap = mipmap;
+ }
+
+ /** Returns the width in pixels of the texture data. */
+ public int getWidth() { return width; }
+ /** Returns the height in pixels of the texture data. */
+ public int getHeight() { return height; }
+ /** Returns the border in pixels of the texture data. */
+ public int getBorder() { return border; }
+ /** Returns the intended OpenGL pixel format of the texture data. */
+ public int getPixelFormat() { return pixelFormat; }
+ /** Returns the intended OpenGL pixel type of the texture data. */
+ public int getPixelType() { return pixelType; }
+ /** Returns the intended OpenGL internal format of the texture data. */
+ public int getInternalFormat() { return internalFormat; }
+ /** Returns whether mipmaps should be generated for the texture data. */
+ public boolean getMipmap() { return mipmap; }
+ /** Indicates whether the texture data is in compressed form. */
+ public boolean isDataCompressed() { return dataIsCompressed; }
+ /** Indicates whether the texture coordinates must be flipped
+ vertically for proper display. */
+ public boolean getMustFlipVertically() { return mustFlipVertically; }
+ /** Returns the texture data, or null if it is specified as a set of mipmaps. */
+ public Buffer getBuffer() { return buffer; }
+ /** Returns all mipmap levels for the texture data, or null if it is
+ specified as a single image. */
+ public Buffer[] getMipmapData() { return mipmapData; }
+ /** Returns the required byte alignment for the texture data. */
+ public int getAlignment() { return alignment; }
+
+ /** Sets the width in pixels of the texture data. */
+ public void setWidth(int width) { this.width = width; }
+ /** Sets the height in pixels of the texture data. */
+ public void setHeight(int height) { this.height = height; }
+ /** Sets the border in pixels of the texture data. */
+ public void setBorder(int border) { this.border = border; }
+ /** Sets the intended OpenGL pixel format of the texture data. */
+ public void setPixelFormat(int pixelFormat) { this.pixelFormat = pixelFormat; }
+ /** Sets the intended OpenGL pixel type of the texture data. */
+ public void setPixelType(int pixelType) { this.pixelType = pixelType; }
+ /** Sets the intended OpenGL internal format of the texture data. */
+ public void setInternalFormat(int internalFormat) { this.internalFormat = internalFormat; }
+ /** Sets whether mipmaps should be generated for the texture data. */
+ public void setMipmap(boolean mipmap) { this.mipmap = mipmap; }
+ /** Sets whether the texture data is in compressed form. */
+ public void setIsDataCompressed(boolean compressed) { this.dataIsCompressed = compressed; }
+ /** Sets whether the texture coordinates must be flipped vertically
+ for proper display. */
+ public void setMustFlipVertically(boolean mustFlipVertically) { this.mustFlipVertically = mustFlipVertically; }
+ /** Sets the texture data. */
+ public void setBuffer(Buffer buffer) { this.buffer = buffer; }
+ /** Sets the required byte alignment for the texture data. */
+ public void setAlignment(int alignment) { this.alignment = alignment; }
+
+ /** Flushes resources associated with this TextureData by calling
+ Flusher.flush(). */
+ public void flush() {
+ if (flusher != null) {
+ flusher.flush();
+ flusher = null;
+ }
+ }
+
+ /** Defines a callback mechanism to allow the user to explicitly
+ deallocate native resources (memory-mapped files, etc.)
+ associated with a particular TextureData. */
+ public static interface Flusher {
+ /** Flushes any native resources associated with this
+ TextureData. */
+ public void flush();
+ }
+
+ //----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ private void createNIOBufferFromImage(BufferedImage image, boolean flipVertically) {
+ if (flipVertically) {
+ ImageUtil.flipImageVertically(image);
+ }
+
+ //
+ // Note: Grabbing the DataBuffer will defeat Java2D's image
+ // management mechanism (as of JDK 5/6, at least). This shouldn't
+ // be a problem for most JOGL apps, but those that try to upload
+ // the image into an OpenGL texture and then use the same image in
+ // Java2D rendering might find the 2D rendering is not as fast as
+ // it could be.
+ //
+
+ // Allow previously-selected pixelType (if any) to override that
+ // we can infer from the DataBuffer
+ DataBuffer data = image.getRaster().getDataBuffer();
+ if (data instanceof DataBufferByte) {
+ if (pixelType == 0) pixelType = GL.GL_UNSIGNED_BYTE;
+ buffer = ByteBuffer.wrap(((DataBufferByte) data).getData());
+ } else if (data instanceof DataBufferDouble) {
+ throw new RuntimeException("DataBufferDouble rasters not supported by OpenGL");
+ } else if (data instanceof DataBufferFloat) {
+ if (pixelType == 0) pixelType = GL.GL_FLOAT;
+ buffer = FloatBuffer.wrap(((DataBufferFloat) data).getData());
+ } else if (data instanceof DataBufferInt) {
+ // FIXME: should we support signed ints?
+ if (pixelType == 0) pixelType = GL.GL_UNSIGNED_INT;
+ buffer = IntBuffer.wrap(((DataBufferInt) data).getData());
+ } else if (data instanceof DataBufferShort) {
+ if (pixelType == 0) pixelType = GL.GL_SHORT;
+ buffer = ShortBuffer.wrap(((DataBufferShort) data).getData());
+ } else if (data instanceof DataBufferUShort) {
+ if (pixelType == 0) pixelType = GL.GL_UNSIGNED_SHORT;
+ buffer = ShortBuffer.wrap(((DataBufferShort) data).getData());
+ } else {
+ throw new RuntimeException("Unexpected DataBuffer type?");
+ }
+ }
+
+ private void createFromImage(BufferedImage image) {
+ pixelType = 0; // Determine from image
+
+ width = image.getWidth();
+ height = image.getHeight();
+
+ switch (image.getType()) {
+ case BufferedImage.TYPE_INT_RGB:
+ pixelFormat = GL.GL_BGRA;
+ pixelType = GL.GL_UNSIGNED_INT_8_8_8_8_REV;
+ alignment = 4;
+ break;
+ case BufferedImage.TYPE_INT_ARGB_PRE:
+ pixelFormat = GL.GL_BGRA;
+ pixelType = GL.GL_UNSIGNED_INT_8_8_8_8_REV;
+ alignment = 4;
+ break;
+ case BufferedImage.TYPE_INT_BGR:
+ pixelFormat = GL.GL_RGBA;
+ pixelType = GL.GL_UNSIGNED_INT_8_8_8_8_REV;
+ alignment = 4;
+ break;
+ case BufferedImage.TYPE_3BYTE_BGR:
+ {
+ Raster raster = image.getRaster();
+ ComponentSampleModel csm =
+ (ComponentSampleModel)raster.getSampleModel();
+ // we can pass the image data directly to OpenGL only if
+ // the raster is tightly packed (i.e. there is no extra
+ // space at the end of each scanline)
+ if ((csm.getScanlineStride() / 3) == csm.getWidth()) {
+ pixelFormat = GL.GL_BGR;
+ pixelType = GL.GL_UNSIGNED_BYTE;
+ alignment = 1;
+ } else {
+ createFromCustom(image);
+ return;
+ }
+ }
+ break;
+ case BufferedImage.TYPE_4BYTE_ABGR_PRE:
+ {
+ Raster raster = image.getRaster();
+ ComponentSampleModel csm =
+ (ComponentSampleModel)raster.getSampleModel();
+ // we can pass the image data directly to OpenGL only if
+ // the raster is tightly packed (i.e. there is no extra
+ // space at the end of each scanline) and only if the
+ // GL_EXT_abgr extension is present
+
+ // FIXME: with the way this is currently organized we can't
+ // probe for the existence of the GL_EXT_abgr extension
+ // here; disable this code path for now
+ if (((csm.getScanlineStride() / 4) == csm.getWidth()) &&
+ /* gl.isExtensionAvailable("GL_EXT_abgr") */ false)
+ {
+ pixelFormat = GL.GL_ABGR_EXT;
+ pixelType = GL.GL_UNSIGNED_BYTE;
+ alignment = 4;
+ } else {
+ createFromCustom(image);
+ return;
+ }
+ }
+ break;
+ case BufferedImage.TYPE_USHORT_565_RGB:
+ pixelFormat = GL.GL_RGB;
+ pixelType = GL.GL_UNSIGNED_SHORT_5_6_5;
+ alignment = 2;
+ break;
+ case BufferedImage.TYPE_USHORT_555_RGB:
+ pixelFormat = GL.GL_BGRA;
+ pixelType = GL.GL_UNSIGNED_SHORT_1_5_5_5_REV;
+ alignment = 2;
+ break;
+ case BufferedImage.TYPE_BYTE_GRAY:
+ pixelFormat = GL.GL_LUMINANCE;
+ pixelType = GL.GL_UNSIGNED_BYTE;
+ alignment = 1;
+ break;
+ case BufferedImage.TYPE_USHORT_GRAY:
+ pixelFormat = GL.GL_LUMINANCE;
+ pixelType = GL.GL_UNSIGNED_SHORT;
+ alignment = 2;
+ break;
+ case BufferedImage.TYPE_INT_ARGB:
+ case BufferedImage.TYPE_4BYTE_ABGR:
+ case BufferedImage.TYPE_BYTE_BINARY:
+ case BufferedImage.TYPE_BYTE_INDEXED:
+ case BufferedImage.TYPE_CUSTOM:
+ default:
+ ColorModel cm = image.getColorModel();
+ if (cm.equals(rgbColorModel)) {
+ pixelFormat = GL.GL_RGB;
+ pixelType = GL.GL_UNSIGNED_BYTE;
+ alignment = 1;
+ } else if (cm.equals(rgbaColorModel)) {
+ pixelFormat = GL.GL_RGBA;
+ pixelType = GL.GL_UNSIGNED_BYTE;
+ alignment = 4;
+ } else {
+ createFromCustom(image);
+ return;
+ }
+ break;
+ }
+
+ createNIOBufferFromImage(image, true);
+ }
+
+ private void createFromCustom(BufferedImage image) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ // create a temporary image that is compatible with OpenGL
+ boolean hasAlpha = image.getColorModel().hasAlpha();
+ ColorModel cm = null;
+ int dataBufferType = image.getRaster().getDataBuffer().getDataType();
+ if (dataBufferType == DataBuffer.TYPE_BYTE) {
+ cm = hasAlpha ? rgbaColorModel : rgbColorModel;
+ } else {
+ if (hasAlpha) {
+ cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ null, true, true,
+ Transparency.TRANSLUCENT,
+ dataBufferType);
+ } else {
+ cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ null, false, false,
+ Transparency.OPAQUE,
+ dataBufferType);
+ }
+ }
+
+ boolean premult = cm.isAlphaPremultiplied();
+ WritableRaster raster =
+ cm.createCompatibleWritableRaster(width, height);
+ BufferedImage texImage = new BufferedImage(cm, raster, premult, null);
+
+ // copy the source image into the temporary image
+ Graphics2D g = texImage.createGraphics();
+ g.setComposite(AlphaComposite.Src);
+ // Flip image vertically as long as we're at it
+ g.drawImage(image,
+ 0, height, width, 0,
+ 0, 0, width, height,
+ null);
+ g.dispose();
+
+ // Wrap the buffer from the temporary image
+ createNIOBufferFromImage(texImage, false);
+ pixelFormat = hasAlpha ? GL.GL_RGBA : GL.GL_RGB;
+ alignment = 1; // FIXME: do we need better?
+ }
+}
diff --git a/src/classes/com/sun/opengl/util/texture/TextureIO.java b/src/classes/com/sun/opengl/util/texture/TextureIO.java
new file mode 100755
index 000000000..c1e5bcf8b
--- /dev/null
+++ b/src/classes/com/sun/opengl/util/texture/TextureIO.java
@@ -0,0 +1,1272 @@
+/*
+ * 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.util.texture;
+
+import java.awt.Graphics;
+import java.awt.image.*;
+import java.io.*;
+import java.net.*;
+import java.nio.*;
+import java.util.*;
+import javax.imageio.*;
+
+import javax.media.opengl.*;
+import javax.media.opengl.glu.*;
+import com.sun.opengl.util.*;
+import com.sun.opengl.util.texture.spi.*;
+
+/** <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
+ 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 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")
+ images from both files and streams. One handles DirectDraw Surface
+ (".dds") images read from files, though can not read these images
+ from streams. One handles Targa (".tga") images read from both
+ files and streams. These providers are executed in an arbitrary
+ order. Some of these providers require the file's suffix to either
+ be specified via the newTextureData methods or for the file to be
+ named with the appropriate suffix. In general a file suffix should
+ be provided to the newTexture and newTextureData methods if at all
+ possible. </P>
+
+ <P> Note that additional TextureProviders, if reading images from
+ InputStreams, must use the mark()/reset() methods on InputStream
+ 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 {
+ /** Constant which can be used as a file suffix to indicate a
+ DirectDraw Surface file. */
+ public static final String DDS = "dds";
+
+ /** Constant which can be used as a file suffix to indicate an SGI
+ RGB file. */
+ public static final String SGI = "sgi";
+
+ /** Constant which can be used as a file suffix to indicate an SGI
+ RGB file. */
+ public static final String SGI_RGB = "rgb";
+
+ /** Constant which can be used as a file suffix to indicate a GIF
+ file. */
+ public static final String GIF = "gif";
+
+ /** Constant which can be used as a file suffix to indicate a JPEG
+ file. */
+ public static final String JPG = "jpg";
+
+ /** Constant which can be used as a file suffix to indicate a PNG
+ file. */
+ public static final String PNG = "png";
+
+ /** Constant which can be used as a file suffix to indicate a Targa
+ file. */
+ public static final String TGA = "tga";
+
+ /** Constant which can be used as a file suffix to indicate a TIFF
+ file. */
+ public static final String TIFF = "tiff";
+
+ //----------------------------------------------------------------------
+ // methods that *do not* require a current context
+ // These methods assume RGB or RGBA textures.
+ // Some texture providers may not recognize the file format unless
+ // the fileSuffix is specified, so it is strongly recommended to
+ // specify it wherever it is known.
+ // Some texture providers may also only support one kind of input,
+ // i.e., reading from a file as opposed to a stream.
+
+ /**
+ * Creates a TextureData from the given file. Does no OpenGL work.
+ *
+ * @param file the file from which to read the texture data
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ * @param fileSuffix the suffix of the file name to be used as a
+ * hint of the file format to the underlying
+ * texture provider, or null if none and should be
+ * auto-detected (some texture providers do not
+ * support this)
+ * @return the texture data from the file, or null if none of the
+ * registered texture providers could read the file
+ * @throws IOException if an error occurred while reading the file
+ */
+ public static TextureData newTextureData(File file,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ return newTextureDataImpl(file, 0, 0, mipmap, fileSuffix);
+ }
+
+ /**
+ * Creates a TextureData from the given stream. Does no OpenGL work.
+ *
+ * @param stream the stream from which to read the texture data
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ * @param fileSuffix the suffix of the file name to be used as a
+ * hint of the file format to the underlying
+ * texture provider, or null if none and should be
+ * auto-detected (some texture providers do not
+ * support this)
+ * @return the texture data from the stream, or null if none of the
+ * registered texture providers could read the stream
+ * @throws IOException if an error occurred while reading the stream
+ */
+ public static TextureData newTextureData(InputStream stream,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ return newTextureDataImpl(stream, 0, 0, mipmap, fileSuffix);
+ }
+
+ /**
+ * Creates a TextureData from the given URL. Does no OpenGL work.
+ *
+ * @param url the URL from which to read the texture data
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ * @param fileSuffix the suffix of the file name to be used as a
+ * hint of the file format to the underlying
+ * texture provider, or null if none and should be
+ * auto-detected (some texture providers do not
+ * support this)
+ * @return the texture data from the URL, or null if none of the
+ * registered texture providers could read the URL
+ * @throws IOException if an error occurred while reading the URL
+ */
+ public static TextureData newTextureData(URL url,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ return newTextureDataImpl(url, 0, 0, mipmap, fileSuffix);
+ }
+
+ /**
+ * Creates a TextureData from the given BufferedImage. Does no
+ * OpenGL work.
+ *
+ * @param image the BufferedImage containing the texture data
+ * @param mipmap whether mipmaps should be produced for this
+ * texture by autogenerating them
+ * @return the texture data from the image
+ */
+ public static TextureData newTextureData(BufferedImage image,
+ boolean mipmap) {
+ return newTextureDataImpl(image, 0, 0, mipmap);
+ }
+
+ //----------------------------------------------------------------------
+ // These methods make no assumption about the OpenGL internal format
+ // or pixel format of the texture; they must be specified by the
+ // user. It is not allowed to supply 0 (indicating no preference)
+ // for either the internalFormat or the pixelFormat;
+ // IllegalArgumentException will be thrown in this case.
+
+ /**
+ * Creates a TextureData from the given file, using the specified
+ * OpenGL internal format and pixel format for the texture which
+ * will eventually result. The internalFormat and pixelFormat must
+ * be specified and may not be zero; to use default values, use the
+ * variant of this method which does not take these arguments. Does
+ * no OpenGL work.
+ *
+ * @param file the file from which to read the texture data
+ * @param internalFormat the OpenGL internal format of the texture
+ * which will eventually result from the TextureData
+ * @param pixelFormat the OpenGL pixel format of the texture
+ * which will eventually result from the TextureData
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ * @param fileSuffix the suffix of the file name to be used as a
+ * hint of the file format to the underlying
+ * texture provider, or null if none and should be
+ * auto-detected (some texture providers do not
+ * support this)
+ * @return the texture data from the file, or null if none of the
+ * registered texture providers could read the file
+ * @throws IllegalArgumentException if either internalFormat or
+ * pixelFormat was 0
+ * @throws IOException if an error occurred while reading the file
+ */
+ public static TextureData newTextureData(File file,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException, IllegalArgumentException {
+ if ((internalFormat == 0) || (pixelFormat == 0)) {
+ throw new IllegalArgumentException("internalFormat and pixelFormat must be non-zero");
+ }
+
+ return newTextureDataImpl(file, internalFormat, pixelFormat, mipmap, fileSuffix);
+ }
+
+ /**
+ * Creates a TextureData from the given stream, using the specified
+ * OpenGL internal format and pixel format for the texture which
+ * will eventually result. The internalFormat and pixelFormat must
+ * be specified and may not be zero; to use default values, use the
+ * variant of this method which does not take these arguments. Does
+ * no OpenGL work.
+ *
+ * @param stream the stream from which to read the texture data
+ * @param internalFormat the OpenGL internal format of the texture
+ * which will eventually result from the TextureData
+ * @param pixelFormat the OpenGL pixel format of the texture
+ * which will eventually result from the TextureData
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ * @param fileSuffix the suffix of the file name to be used as a
+ * hint of the file format to the underlying
+ * texture provider, or null if none and should be
+ * auto-detected (some texture providers do not
+ * support this)
+ * @return the texture data from the stream, or null if none of the
+ * registered texture providers could read the stream
+ * @throws IllegalArgumentException if either internalFormat or
+ * pixelFormat was 0
+ * @throws IOException if an error occurred while reading the stream
+ */
+ public static TextureData newTextureData(InputStream stream,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException, IllegalArgumentException {
+ if ((internalFormat == 0) || (pixelFormat == 0)) {
+ throw new IllegalArgumentException("internalFormat and pixelFormat must be non-zero");
+ }
+
+ return newTextureDataImpl(stream, internalFormat, pixelFormat, mipmap, fileSuffix);
+ }
+
+ /**
+ * Creates a TextureData from the given URL, using the specified
+ * OpenGL internal format and pixel format for the texture which
+ * will eventually result. The internalFormat and pixelFormat must
+ * be specified and may not be zero; to use default values, use the
+ * variant of this method which does not take these arguments. Does
+ * no OpenGL work.
+ *
+ * @param url the URL from which to read the texture data
+ * @param internalFormat the OpenGL internal format of the texture
+ * which will eventually result from the TextureData
+ * @param pixelFormat the OpenGL pixel format of the texture
+ * which will eventually result from the TextureData
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ * @param fileSuffix the suffix of the file name to be used as a
+ * hint of the file format to the underlying
+ * texture provider, or null if none and should be
+ * auto-detected (some texture providers do not
+ * support this)
+ * @return the texture data from the URL, or null if none of the
+ * registered texture providers could read the URL
+ * @throws IllegalArgumentException if either internalFormat or
+ * pixelFormat was 0
+ * @throws IOException if an error occurred while reading the URL
+ */
+ public static TextureData newTextureData(URL url,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException, IllegalArgumentException {
+ if ((internalFormat == 0) || (pixelFormat == 0)) {
+ throw new IllegalArgumentException("internalFormat and pixelFormat must be non-zero");
+ }
+
+ return newTextureDataImpl(url, internalFormat, pixelFormat, mipmap, fileSuffix);
+ }
+
+ /**
+ * Creates a TextureData from the given BufferedImage, using the
+ * specified OpenGL internal format and pixel format for the texture
+ * which will eventually result. The internalFormat and pixelFormat
+ * must be specified and may not be zero; to use default values, use
+ * the variant of this method which does not take these
+ * arguments. Does no OpenGL work.
+ *
+ * @param image the BufferedImage containing the texture data
+ * @param internalFormat the OpenGL internal format of the texture
+ * which will eventually result from the TextureData
+ * @param pixelFormat the OpenGL pixel format of the texture
+ * which will eventually result from the TextureData
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ * @return the texture data from the image
+ * @throws IllegalArgumentException if either internalFormat or
+ * pixelFormat was 0
+ */
+ public static TextureData newTextureData(BufferedImage image,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap) throws IllegalArgumentException {
+ if ((internalFormat == 0) || (pixelFormat == 0)) {
+ throw new IllegalArgumentException("internalFormat and pixelFormat must be non-zero");
+ }
+
+ return newTextureDataImpl(image, internalFormat, pixelFormat, mipmap);
+ }
+
+ //----------------------------------------------------------------------
+ // methods that *do* require a current context
+ //
+
+ /**
+ * Creates an OpenGL texture object from the specified TextureData
+ * using the current OpenGL context. Does not automatically generate
+ * mipmaps for the resulting texture.
+ *
+ * @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, IllegalArgumentException {
+ if (data == null) {
+ throw new IllegalArgumentException("Null TextureData");
+ }
+ return new Texture(data);
+ }
+
+ /**
+ * Creates an OpenGL texture object from the specified file using
+ * the current OpenGL context. Does not automatically generate
+ * mipmaps for the resulting texture.
+ *
+ * @param file the file from which to read the texture data
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ * @throws IOException if an error occurred while reading the file
+ * @throws GLException if no OpenGL context is current or if an
+ * OpenGL error occurred
+ */
+ public static Texture newTexture(File file, boolean mipmap) throws IOException, GLException {
+ TextureData data = newTextureData(file, mipmap, FileUtil.getFileSuffix(file));
+ Texture texture = newTexture(data);
+ data.flush();
+ return texture;
+ }
+
+ /**
+ * Creates an OpenGL texture object from the specified stream using
+ * the current OpenGL context. Does not automatically generate
+ * mipmaps for the resulting texture.
+ *
+ * @param stream the stream from which to read the texture data
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ * @param fileSuffix the suffix of the file name to be used as a
+ * hint of the file format to the underlying
+ * texture provider, or null if none and should be
+ * auto-detected (some texture providers do not
+ * support this)
+ * @throws IOException if an error occurred while reading the stream
+ * @throws GLException if no OpenGL context is current or if an
+ * OpenGL error occurred
+ */
+ public static Texture newTexture(InputStream stream, boolean mipmap, String fileSuffix) throws IOException, GLException {
+ TextureData data = newTextureData(stream, mipmap, fileSuffix);
+ Texture texture = newTexture(data);
+ data.flush();
+ return texture;
+ }
+
+ /**
+ * Creates an OpenGL texture object from the specified URL using the
+ * current OpenGL context. Does not automatically generate mipmaps
+ * for the resulting texture.
+ *
+ * @param url the URL from which to read the texture data
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ * @param fileSuffix the suffix of the file name to be used as a
+ * hint of the file format to the underlying
+ * texture provider, or null if none and should be
+ * auto-detected (some texture providers do not
+ * support this)
+ * @throws IOException if an error occurred while reading the URL
+ * @throws GLException if no OpenGL context is current or if an
+ * OpenGL error occurred
+ */
+ public static Texture newTexture(URL url, boolean mipmap, String fileSuffix) throws IOException, GLException {
+ TextureData data = newTextureData(url, mipmap, fileSuffix);
+ Texture texture = newTexture(data);
+ data.flush();
+ return texture;
+ }
+
+ /**
+ * Creates an OpenGL texture object from the specified BufferedImage
+ * using the current OpenGL context.
+ *
+ * @param image the BufferedImage from which to read the texture data
+ * @param mipmap whether mipmaps should be produced for this
+ * texture by autogenerating them
+ * @throws GLException if no OpenGL context is current or if an
+ * OpenGL error occurred
+ */
+ public static Texture newTexture(BufferedImage image, boolean mipmap) throws GLException {
+ TextureData data = newTextureData(image, mipmap);
+ Texture texture = newTexture(data);
+ data.flush();
+ return texture;
+ }
+
+ /**
+ * Creates an OpenGL texture object associated with the given OpenGL
+ * texture target using the current OpenGL context. The texture has
+ * no initial data. This is used, for example, to construct cube
+ * maps out of multiple TextureData objects.
+ *
+ * @throws GLException if no OpenGL context is current or if an
+ * OpenGL error occurred
+ */
+ public static Texture newTexture(int target) throws GLException {
+ 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");
+ }
+
+ //----------------------------------------------------------------------
+ // SPI support
+ //
+
+ /** Adds a TextureProvider to support reading of a new file
+ format. */
+ public static void addTextureProvider(TextureProvider provider) {
+ // Must always add at the front so the ImageIO provider is last,
+ // so we don't accidentally use it instead of a user's possibly
+ // more optimal provider
+ 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
+ addTextureProvider(new IIOTextureProvider());
+
+ // Other special-case providers
+ 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
+ private static TextureData newTextureDataImpl(File file,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ if (file == null) {
+ throw new IOException("File was null");
+ }
+
+ fileSuffix = toLowerCase(fileSuffix);
+
+ for (Iterator iter = textureProviders.iterator(); iter.hasNext(); ) {
+ TextureProvider provider = (TextureProvider) iter.next();
+ TextureData data = provider.newTextureData(file,
+ internalFormat,
+ pixelFormat,
+ mipmap,
+ fileSuffix);
+ if (data != null) {
+ return data;
+ }
+ }
+
+ throw new IOException("No suitable reader for given file");
+ }
+
+ private static TextureData newTextureDataImpl(InputStream stream,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ if (stream == null) {
+ throw new IOException("Stream was null");
+ }
+
+ fileSuffix = toLowerCase(fileSuffix);
+
+ // Note: use of BufferedInputStream works around 4764639/4892246
+ if (!(stream instanceof BufferedInputStream)) {
+ stream = new BufferedInputStream(stream);
+ }
+
+ for (Iterator iter = textureProviders.iterator(); iter.hasNext(); ) {
+ TextureProvider provider = (TextureProvider) iter.next();
+ TextureData data = provider.newTextureData(stream,
+ internalFormat,
+ pixelFormat,
+ mipmap,
+ fileSuffix);
+ if (data != null) {
+ return data;
+ }
+ }
+
+ throw new IOException("No suitable reader for given stream");
+ }
+
+ private static TextureData newTextureDataImpl(URL url,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ if (url == null) {
+ throw new IOException("URL was null");
+ }
+
+ fileSuffix = toLowerCase(fileSuffix);
+
+ for (Iterator iter = textureProviders.iterator(); iter.hasNext(); ) {
+ TextureProvider provider = (TextureProvider) iter.next();
+ TextureData data = provider.newTextureData(url,
+ internalFormat,
+ pixelFormat,
+ mipmap,
+ fileSuffix);
+ if (data != null) {
+ return data;
+ }
+ }
+
+ throw new IOException("No suitable reader for given URL");
+ }
+
+ private static TextureData newTextureDataImpl(BufferedImage image,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap) {
+ return new TextureData(internalFormat, pixelFormat, mipmap, image);
+ }
+
+ //----------------------------------------------------------------------
+ // Base provider - used last
+ static class IIOTextureProvider implements TextureProvider {
+ public TextureData newTextureData(File file,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ BufferedImage img = ImageIO.read(file);
+ if (img == null) {
+ return null;
+ }
+ return new TextureData(internalFormat, pixelFormat, mipmap, img);
+ }
+
+ public TextureData newTextureData(InputStream stream,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ BufferedImage img = ImageIO.read(stream);
+ if (img == null) {
+ return null;
+ }
+ return new TextureData(internalFormat, pixelFormat, mipmap, img);
+ }
+
+ public TextureData newTextureData(URL url,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ InputStream stream = url.openStream();
+ try {
+ return newTextureData(stream, internalFormat, pixelFormat, mipmap, fileSuffix);
+ } finally {
+ stream.close();
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // DDS provider -- supports files only for now
+ static class DDSTextureProvider implements TextureProvider {
+ public TextureData newTextureData(File file,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ if (DDS.equals(fileSuffix) ||
+ DDS.equals(FileUtil.getFileSuffix(file))) {
+ final DDSImage image = DDSImage.read(file);
+ DDSImage.ImageInfo info = image.getMipMap(0);
+ if (pixelFormat == 0) {
+ switch (image.getPixelFormat()) {
+ case DDSImage.D3DFMT_R8G8B8:
+ pixelFormat = GL.GL_RGB;
+ break;
+ default:
+ pixelFormat = GL.GL_RGBA;
+ break;
+ }
+ }
+ if (info.isCompressed()) {
+ switch (info.getCompressionFormat()) {
+ case DDSImage.D3DFMT_DXT1:
+ internalFormat = GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+ break;
+ case DDSImage.D3DFMT_DXT3:
+ internalFormat = GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ break;
+ case DDSImage.D3DFMT_DXT5:
+ internalFormat = GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ break;
+ default:
+ throw new RuntimeException("Unsupported DDS compression format \"" +
+ DDSImage.getCompressionFormatName(info.getCompressionFormat()) + "\"");
+ }
+ }
+ if (internalFormat == 0) {
+ switch (image.getPixelFormat()) {
+ case DDSImage.D3DFMT_R8G8B8:
+ pixelFormat = GL.GL_RGB;
+ break;
+ default:
+ pixelFormat = GL.GL_RGBA;
+ break;
+ }
+ }
+ TextureData.Flusher flusher = new TextureData.Flusher() {
+ public void flush() {
+ image.close();
+ }
+ };
+ TextureData data;
+ 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(),
+ info.getHeight(),
+ 0,
+ pixelFormat,
+ GL.GL_UNSIGNED_BYTE,
+ info.isCompressed(),
+ true,
+ mipmapData,
+ flusher);
+ } else {
+ // Fix this up for the end user because we can't generate
+ // mipmaps for compressed textures
+ mipmap = false;
+ data = new TextureData(internalFormat,
+ info.getWidth(),
+ info.getHeight(),
+ 0,
+ pixelFormat,
+ GL.GL_UNSIGNED_BYTE,
+ mipmap,
+ info.isCompressed(),
+ true,
+ info.getData(),
+ flusher);
+ }
+ return data;
+ }
+
+ return null;
+ }
+
+ public TextureData newTextureData(InputStream stream,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ return null;
+ }
+
+ public TextureData newTextureData(URL url,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ return null;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // Base class for SGI RGB and TGA image providers
+ static abstract class StreamBasedTextureProvider implements TextureProvider {
+ public TextureData newTextureData(File file,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ InputStream inStream = new BufferedInputStream(new FileInputStream(file));
+ try {
+ // The SGIImage and TGAImage implementations use InputStreams
+ // anyway so there isn't much point in having a separate code
+ // path for files
+ return newTextureData(inStream,
+ internalFormat,
+ pixelFormat,
+ mipmap,
+ ((fileSuffix != null) ? fileSuffix : FileUtil.getFileSuffix(file)));
+ } finally {
+ inStream.close();
+ }
+ }
+
+ public TextureData newTextureData(URL url,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ InputStream stream = new BufferedInputStream(url.openStream());
+ try {
+ return newTextureData(stream, internalFormat, pixelFormat, mipmap, fileSuffix);
+ } finally {
+ stream.close();
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // SGI RGB image provider
+ static class SGITextureProvider extends StreamBasedTextureProvider {
+ public TextureData newTextureData(InputStream stream,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ if (SGI.equals(fileSuffix) ||
+ SGI_RGB.equals(fileSuffix) ||
+ SGIImage.isSGIImage(stream)) {
+ SGIImage image = SGIImage.read(stream);
+ if (pixelFormat == 0) {
+ pixelFormat = image.getFormat();
+ }
+ if (internalFormat == 0) {
+ internalFormat = image.getFormat();
+ }
+ return new TextureData(internalFormat,
+ image.getWidth(),
+ image.getHeight(),
+ 0,
+ pixelFormat,
+ GL.GL_UNSIGNED_BYTE,
+ mipmap,
+ false,
+ false,
+ ByteBuffer.wrap(image.getData()),
+ null);
+ }
+
+ return null;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // TGA (Targa) image provider
+ static class TGATextureProvider extends StreamBasedTextureProvider {
+ public TextureData newTextureData(InputStream stream,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ if (TGA.equals(fileSuffix)) {
+ TGAImage image = TGAImage.read(stream);
+ if (pixelFormat == 0) {
+ pixelFormat = image.getGLFormat();
+ }
+ if (internalFormat == 0) {
+ internalFormat = GL.GL_RGBA8;
+ }
+ return new TextureData(internalFormat,
+ image.getWidth(),
+ image.getHeight(),
+ 0,
+ pixelFormat,
+ GL.GL_UNSIGNED_BYTE,
+ mipmap,
+ false,
+ false,
+ image.getData(),
+ null);
+ }
+
+ return null;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // 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
+ ImageUtil.flipImageVertically(image);
+
+ // Happened to notice that writing RGBA images to JPEGS is broken
+ if (JPG.equals(FileUtil.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, FileUtil.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(FileUtil.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 = FileUtil.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(FileUtil.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;
+ }
+
+ return arg.toLowerCase();
+ }
+}
diff --git a/src/classes/com/sun/opengl/util/texture/spi/DDSImage.java b/src/classes/com/sun/opengl/util/texture/spi/DDSImage.java
new file mode 100755
index 000000000..486515628
--- /dev/null
+++ b/src/classes/com/sun/opengl/util/texture/spi/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.util.texture.spi;
+
+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/util/texture/spi/LEDataInputStream.java b/src/classes/com/sun/opengl/util/texture/spi/LEDataInputStream.java
new file mode 100755
index 000000000..edbb6e35e
--- /dev/null
+++ b/src/classes/com/sun/opengl/util/texture/spi/LEDataInputStream.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2003 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.util.texture.spi;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.FilterInputStream;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * Little Endian Data Input Stream.
+ *
+ * This class implements an input stream filter to allow reading
+ * of java native datatypes from an input stream which has those
+ * native datatypes stored in a little endian byte order.<p>
+ *
+ * This is the sister class of the DataInputStream which allows
+ * for reading of java native datatypes from an input stream with
+ * the datatypes stored in big endian byte order.<p>
+ *
+ * This class implements the minimum required and calls DataInputStream
+ * for some of the required methods for DataInput.<p>
+ *
+ * Not all methods are implemented due to lack of immediatte requirement
+ * for that functionality. It is not clear if it is ever going to be
+ * functionally required to be able to read UTF data in a LittleEndianManner<p>
+ *
+ * @author Robin Luiten
+ * @version 1.1 15/Dec/1997
+ */
+class LEDataInputStream extends FilterInputStream implements DataInput
+{
+ /**
+ * To reuse some of the non endian dependent methods from
+ * DataInputStreams methods.
+ */
+ DataInputStream dataIn;
+
+ public LEDataInputStream(InputStream in)
+ {
+ super(in);
+ dataIn = new DataInputStream(in);
+ }
+
+ public void close() throws IOException
+ {
+ dataIn.close(); // better close as we create it.
+ // this will close underlying as well.
+ }
+
+ public synchronized final int read(byte b[]) throws IOException
+ {
+ return dataIn.read(b, 0, b.length);
+ }
+
+ public synchronized final int read(byte b[], int off, int len) throws IOException
+ {
+ int rl = dataIn.read(b, off, len);
+ return rl;
+ }
+
+ public final void readFully(byte b[]) throws IOException
+ {
+ dataIn.readFully(b, 0, b.length);
+ }
+
+ public final void readFully(byte b[], int off, int len) throws IOException
+ {
+ dataIn.readFully(b, off, len);
+ }
+
+ public final int skipBytes(int n) throws IOException
+ {
+ return dataIn.skipBytes(n);
+ }
+
+ public final boolean readBoolean() throws IOException
+ {
+ int ch = dataIn.read();
+ if (ch < 0)
+ throw new EOFException();
+ return (ch != 0);
+ }
+
+ public final byte readByte() throws IOException
+ {
+ int ch = dataIn.read();
+ if (ch < 0)
+ throw new EOFException();
+ return (byte)(ch);
+ }
+
+ public final int readUnsignedByte() throws IOException
+ {
+ int ch = dataIn.read();
+ if (ch < 0)
+ throw new EOFException();
+ return ch;
+ }
+
+ public final short readShort() throws IOException
+ {
+ int ch1 = dataIn.read();
+ int ch2 = dataIn.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (short)((ch1 << 0) + (ch2 << 8));
+ }
+
+ public final int readUnsignedShort() throws IOException
+ {
+ int ch1 = dataIn.read();
+ int ch2 = dataIn.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (ch1 << 0) + (ch2 << 8);
+ }
+
+ public final char readChar() throws IOException
+ {
+ int ch1 = dataIn.read();
+ int ch2 = dataIn.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (char)((ch1 << 0) + (ch2 << 8));
+ }
+
+ public final int readInt() throws IOException
+ {
+ int ch1 = dataIn.read();
+ int ch2 = dataIn.read();
+ int ch3 = dataIn.read();
+ int ch4 = dataIn.read();
+ if ((ch1 | ch2 | ch3 | ch4) < 0)
+ throw new EOFException();
+ return ((ch1 << 0) + (ch2 << 8) + (ch3 << 16) + (ch4 << 24));
+ }
+
+ public final long readLong() throws IOException
+ {
+ int i1 = readInt();
+ int i2 = readInt();
+ return ((long)(i1) & 0xFFFFFFFFL) + (i2 << 32);
+ }
+
+ public final float readFloat() throws IOException
+ {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ public final double readDouble() throws IOException
+ {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ /**
+ * dont call this it is not implemented.
+ * @return empty new string
+ **/
+ public final String readLine() throws IOException
+ {
+ return new String();
+ }
+
+ /**
+ * dont call this it is not implemented
+ * @return empty new string
+ **/
+ public final String readUTF() throws IOException
+ {
+ return new String();
+ }
+
+ /**
+ * dont call this it is not implemented
+ * @return empty new string
+ **/
+ public final static String readUTF(DataInput in) throws IOException
+ {
+ return new String();
+ }
+}
+
diff --git a/src/classes/com/sun/opengl/util/texture/spi/SGIImage.java b/src/classes/com/sun/opengl/util/texture/spi/SGIImage.java
new file mode 100755
index 000000000..12523eb18
--- /dev/null
+++ b/src/classes/com/sun/opengl/util/texture/spi/SGIImage.java
@@ -0,0 +1,671 @@
+/*
+ * Portions 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.util.texture.spi;
+
+import java.io.*;
+import javax.media.opengl.*;
+import com.sun.opengl.util.*;
+
+// Test harness
+import java.awt.image.*;
+import javax.swing.*;
+
+/** <p> Reads and writes SGI RGB/RGBA images. </p>
+
+ <p> Written from <a href =
+ "http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/">Paul
+ Bourke's adaptation</a> of the <a href =
+ "http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/sgiversion.html">SGI
+ specification</a>. </p>
+*/
+
+public class SGIImage {
+ private Header header;
+ private int format;
+ private byte[] data;
+ // Used for decoding RLE-compressed images
+ private int[] rowStart;
+ private int[] rowSize;
+ private int rleEnd;
+ private byte[] tmpData;
+ private byte[] tmpRead;
+
+ private static final int MAGIC = 474;
+
+ static class Header {
+ short magic; // IRIS image file magic number
+ // This should be decimal 474
+ byte storage; // Storage format
+ // 0 for uncompressed
+ // 1 for RLE compression
+ byte bpc; // Number of bytes per pixel channel
+ // Legally 1 or 2
+ short dimension; // Number of dimensions
+ // Legally 1, 2, or 3
+ // 1 means a single row, XSIZE long
+ // 2 means a single 2D image
+ // 3 means multiple 2D images
+ short xsize; // X size in pixels
+ short ysize; // Y size in pixels
+ short zsize; // Number of channels
+ // 1 indicates greyscale
+ // 3 indicates RGB
+ // 4 indicates RGB and Alpha
+ int pixmin; // Minimum pixel value
+ // This is the lowest pixel value in the image
+ int pixmax; // Maximum pixel value
+ // This is the highest pixel value in the image
+ int dummy; // Ignored
+ // Normally set to 0
+ String imagename; // Image name; 80 bytes long
+ // Must be null terminated, therefore at most 79 bytes
+ int colormap; // Colormap ID
+ // 0 - normal mode
+ // 1 - dithered, 3 mits for red and green, 2 for blue, obsolete
+ // 2 - index colour, obsolete
+ // 3 - not an image but a colourmap
+ // 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();
+ bpc = in.readByte();
+ dimension = in.readShort();
+ xsize = in.readShort();
+ ysize = in.readShort();
+ zsize = in.readShort();
+ pixmin = in.readInt();
+ pixmax = in.readInt();
+ dummy = in.readInt();
+ byte[] tmpname = new byte[80];
+ in.read(tmpname);
+ int numChars = 0;
+ while (tmpname[numChars++] != 0);
+ imagename = new String(tmpname, 0, numChars);
+ colormap = in.readInt();
+ byte[] tmp = new byte[404];
+ in.read(tmp);
+ }
+
+ public String toString() {
+ return ("magic: " + magic +
+ " storage: " + (int) storage +
+ " bpc: " + (int) bpc +
+ " dimension: " + dimension +
+ " xsize: " + xsize +
+ " ysize: " + ysize +
+ " zsize: " + zsize +
+ " pixmin: " + pixmin +
+ " pixmax: " + pixmax +
+ " imagename: " + imagename +
+ " colormap: " + colormap);
+ }
+ }
+
+ private SGIImage(Header header) {
+ this.header = header;
+ }
+
+ /** Reads an SGI image from the specified file. */
+ public static SGIImage read(String filename) throws IOException {
+ return read(new FileInputStream(filename));
+ }
+
+ /** Reads an SGI image from the specified InputStream. */
+ public static SGIImage read(InputStream in) throws IOException {
+ DataInputStream dIn = new DataInputStream(new BufferedInputStream(in));
+
+ Header header = new Header(dIn);
+ SGIImage res = new SGIImage(header);
+ res.decodeImage(dIn);
+ 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
+ of read-ahead. */
+ public static boolean isSGIImage(InputStream in) throws IOException {
+ if (!(in instanceof BufferedInputStream)) {
+ in = new BufferedInputStream(in);
+ }
+ if (!in.markSupported()) {
+ throw new IOException("Can not test non-destructively whether given InputStream is an SGI RGB image");
+ }
+ DataInputStream dIn = new DataInputStream(in);
+ dIn.mark(4);
+ short magic = dIn.readShort();
+ dIn.reset();
+ return (magic == MAGIC);
+ }
+
+ /** Returns the width of the image. */
+ public int getWidth() {
+ return header.xsize;
+ }
+
+ /** Returns the height of the image. */
+ public int getHeight() {
+ return header.ysize;
+ }
+
+ /** Returns the OpenGL format for this texture; e.g. GL.GL_RGB or GL.GL_RGBA. */
+ public int getFormat() {
+ return format;
+ }
+
+ /** Returns the raw data for this texture in the correct
+ (bottom-to-top) order for calls to glTexImage2D. */
+ public byte[] getData() { return data; }
+
+ public String toString() {
+ return header.toString();
+ }
+
+ //----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ private void decodeImage(DataInputStream in) throws IOException {
+ if (header.storage == 1) {
+ // Read RLE compression data; row starts and sizes
+ int x = header.ysize * header.zsize;
+ rowStart = new int[x];
+ rowSize = new int[x];
+ rleEnd = 4 * 2 * x + 512;
+ for (int i = 0; i < x; i++) {
+ rowStart[i] = in.readInt();
+ }
+ for (int i = 0; i < x; i++) {
+ rowSize[i] = in.readInt();
+ }
+ tmpRead = new byte[header.xsize * 256];
+ }
+ tmpData = readAll(in);
+
+ int xsize = header.xsize;
+ int ysize = header.ysize;
+ int zsize = header.zsize;
+ int lptr = 0;
+
+ data = new byte[xsize * ysize * 4];
+ byte[] rbuf = new byte[xsize];
+ byte[] gbuf = new byte[xsize];
+ byte[] bbuf = new byte[xsize];
+ byte[] abuf = new byte[xsize];
+ for (int y = 0; y < ysize; y++) {
+ if (zsize >= 4) {
+ getRow(rbuf, y, 0);
+ getRow(gbuf, y, 1);
+ getRow(bbuf, y, 2);
+ getRow(abuf, y, 3);
+ rgbatorgba(rbuf, gbuf, bbuf, abuf, data, lptr);
+ } else if (zsize == 3) {
+ getRow(rbuf, y, 0);
+ getRow(gbuf, y, 1);
+ getRow(bbuf, y, 2);
+ rgbtorgba(rbuf, gbuf, bbuf, data, lptr);
+ } else if (zsize == 2) {
+ getRow(rbuf, y, 0);
+ getRow(abuf, y, 1);
+ latorgba(rbuf, abuf, data, lptr);
+ } else {
+ getRow(rbuf, y, 0);
+ bwtorgba(rbuf, data, lptr);
+ }
+ lptr += 4 * xsize;
+ }
+ rowStart = null;
+ rowSize = null;
+ tmpData = null;
+ tmpRead = null;
+ format = GL.GL_RGBA;
+ header.zsize = 4;
+ }
+
+ private void getRow(byte[] buf, int y, int z) {
+ if (header.storage == 1) {
+ int offs = rowStart[y + z * header.ysize] - rleEnd;
+ System.arraycopy(tmpData, offs, tmpRead, 0, rowSize[y + z * header.ysize]);
+ int iPtr = 0;
+ int oPtr = 0;
+ for (;;) {
+ byte pixel = tmpRead[iPtr++];
+ int count = (int) (pixel & 0x7F);
+ if (count == 0) {
+ return;
+ }
+ if ((pixel & 0x80) != 0) {
+ while ((count--) > 0) {
+ buf[oPtr++] = tmpRead[iPtr++];
+ }
+ } else {
+ pixel = tmpRead[iPtr++];
+ while ((count--) > 0) {
+ buf[oPtr++] = pixel;
+ }
+ }
+ }
+ } else {
+ int offs = (y * header.xsize) + (z * header.xsize * header.ysize);
+ System.arraycopy(tmpData, offs, buf, 0, header.xsize);
+ }
+ }
+
+ private void bwtorgba(byte[] b, byte[] dest, int lptr) {
+ for (int i = 0; i < b.length; i++) {
+ dest[4 * i + lptr + 0] = b[i];
+ dest[4 * i + lptr + 1] = b[i];
+ dest[4 * i + lptr + 2] = b[i];
+ dest[4 * i + lptr + 3] = (byte) 0xFF;
+ }
+ }
+
+ private void latorgba(byte[] b, byte[] a, byte[] dest, int lptr) {
+ for (int i = 0; i < b.length; i++) {
+ dest[4 * i + lptr + 0] = b[i];
+ dest[4 * i + lptr + 1] = b[i];
+ dest[4 * i + lptr + 2] = b[i];
+ dest[4 * i + lptr + 3] = a[i];
+ }
+ }
+
+ private void rgbtorgba(byte[] r, byte[] g, byte[] b, byte[] dest, int lptr) {
+ for (int i = 0; i < b.length; i++) {
+ dest[4 * i + lptr + 0] = r[i];
+ dest[4 * i + lptr + 1] = g[i];
+ dest[4 * i + lptr + 2] = b[i];
+ dest[4 * i + lptr + 3] = (byte) 0xFF;
+ }
+ }
+
+ private void rgbatorgba(byte[] r, byte[] g, byte[] b, byte[] a, byte[] dest, int lptr) {
+ for (int i = 0; i < b.length; i++) {
+ dest[4 * i + lptr + 0] = r[i];
+ dest[4 * i + lptr + 1] = g[i];
+ dest[4 * i + lptr + 2] = b[i];
+ dest[4 * i + lptr + 3] = a[i];
+ }
+ }
+
+ 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;
+ int numRead = 0;
+
+ boolean done = false;
+
+ do {
+ numRead = in.read(dest, pos, dest.length - pos);
+ if (pos == dest.length) {
+ // Resize destination buffer
+ byte[] newDest = new byte[2 * dest.length];
+ System.arraycopy(dest, 0, newDest, 0, pos);
+ dest = newDest;
+ }
+ if (numRead > 0) {
+ pos += numRead;
+ }
+
+ done = ((numRead == -1) || (in.available() == 0));
+ } while (!done);
+
+ // Trim destination buffer
+ if (pos != dest.length) {
+ byte[] finalDest = new byte[pos];
+ System.arraycopy(dest, 0, finalDest, 0, pos);
+ dest = finalDest;
+ }
+
+ return dest;
+ }
+
+ // Test case
+ /*
+ public static void main(String[] args) {
+ for (int i = 0; i < args.length; i++) {
+ try {
+ System.out.println(args[i] + ":");
+ SGIImage image = SGIImage.read(args[i]);
+ System.out.println(image);
+ BufferedImage img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
+ WritableRaster raster = img.getRaster();
+ DataBufferByte db = (DataBufferByte) raster.getDataBuffer();
+ byte[] src = image.getData();
+ byte[] dest = db.getData();
+ for (int j = 0; j < src.length; j += 4) {
+ dest[j + 0] = src[j + 3];
+ dest[j + 1] = src[j + 2];
+ dest[j + 2] = src[j + 1];
+ dest[j + 3] = src[j + 0];
+ }
+ // System.arraycopy(src, 0, dest, 0, src.length);
+ ImageIcon icon = new ImageIcon(img);
+ JLabel label = new JLabel();
+ label.setIcon(icon);
+ JFrame frame = new JFrame(args[i]);
+ frame.getContentPane().add(label);
+ frame.pack();
+ frame.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ */
+}
diff --git a/src/classes/com/sun/opengl/util/texture/spi/TGAImage.java b/src/classes/com/sun/opengl/util/texture/spi/TGAImage.java
new file mode 100755
index 000000000..8ab095643
--- /dev/null
+++ b/src/classes/com/sun/opengl/util/texture/spi/TGAImage.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2003-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.util.texture.spi;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import javax.media.opengl.*;
+import com.sun.opengl.util.*;
+
+/**
+ * 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>
+ *
+ * Image decoder for image data stored in TGA file format.
+ * Currently only the original TGA file format is supported. This is
+ * because the new TGA format has data at the end of the file, getting
+ * to the end of a file in an InputStream orient environment presents
+ * several difficulties which are avoided at the moment.
+ *
+ * <P>
+ *
+ * This is a simple decoder and is only setup to load a single image
+ * from the input stream
+ *
+ * <P>
+ *
+ * @author Robin Luiten
+ * @author Kenneth Russell
+ * @version $Revision$
+ */
+
+public class TGAImage {
+ private Header header;
+ private int format;
+ private ByteBuffer data;
+
+ private TGAImage(Header header) {
+ this.header = header;
+ }
+
+ /**
+ * This class reads in all of the TGA image header in addition it also
+ * reads in the imageID field as it is convenient to handle that here.
+ *
+ * @author Robin Luiten
+ * @version 1.1
+ */
+ public static class Header {
+ /** Set of possible file format TGA types */
+ public final static int TYPE_NEW = 0;
+ public final static int TYPE_OLD = 1;
+ public final static int TYPE_UNK = 2; // cant rewind stream so unknown for now.
+
+ /** Set of possible image types in TGA file */
+ public final static int NO_IMAGE = 0; // no image data
+ public final static int UCOLORMAPPED = 1; // uncompressed color mapped image
+ public final static int UTRUECOLOR = 2; // uncompressed true color image
+ public final static int UBLACKWHITE = 3; // uncompressed black and white image
+ public final static int COLORMAPPED = 9; // compressed color mapped image
+ public final static int TRUECOLOR = 10; // compressed true color image
+ public final static int BLACKWHITE = 11; // compressed black and white image
+
+ /** Field image descriptor bitfield values definitions */
+ public final static int ID_ATTRIBPERPIXEL = 0xF;
+ public final static int ID_RIGHTTOLEFT = 0x10;
+ public final static int ID_TOPTOBOTTOM = 0x20;
+ public final static int ID_INTERLEAVE = 0xC0;
+
+ /** Field image descriptor / interleave values */
+ public final static int I_NOTINTERLEAVED = 0;
+ public final static int I_TWOWAY = 1;
+ public final static int I_FOURWAY = 2;
+
+ /** Type of this TGA file format */
+ private int tgaType;
+
+ /** initial TGA image data fields */
+ private int idLength; // byte value
+ private int colorMapType; // byte value
+ private int imageType; // byte value
+
+ /** TGA image colour map fields */
+ private int firstEntryIndex;
+ private int colorMapLength;
+ private byte colorMapEntrySize;
+
+ /** TGA image specification fields */
+ private int xOrigin;
+ private int yOrigin;
+ private int width;
+ private int height;
+ private byte pixelDepth;
+ private byte imageDescriptor;
+
+ 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;
+
+ tgaType = TYPE_OLD; // dont try and get footer.
+
+ // initial header fields
+ idLength = in.readUnsignedByte();
+ colorMapType = in.readUnsignedByte();
+ imageType = in.readUnsignedByte();
+
+ // color map header fields
+ firstEntryIndex = in.readUnsignedShort();
+ colorMapLength = in.readUnsignedShort();
+ colorMapEntrySize = in.readByte();
+
+ // TGA image specification fields
+ xOrigin = in.readUnsignedShort();
+ yOrigin = in.readUnsignedShort();
+ width = in.readUnsignedShort();
+ height = in.readUnsignedShort();
+ pixelDepth = in.readByte();
+ imageDescriptor = in.readByte();
+
+ if (idLength > 0) {
+ imageIDbuf = new byte[idLength];
+ in.read(imageIDbuf, 0, idLength);
+ imageID = new String(imageIDbuf, "US-ASCII");
+ }
+ }
+
+ public int tgaType() { return tgaType; }
+
+ /** initial TGA image data fields */
+ public int idLength() { return idLength; }
+ public int colorMapType() { return colorMapType; }
+ public int imageType() { return imageType; }
+
+ /** TGA image colour map fields */
+ public int firstEntryIndex() { return firstEntryIndex; }
+ public int colorMapLength() { return colorMapLength; }
+ public byte colorMapEntrySize() { return colorMapEntrySize; }
+
+ /** TGA image specification fields */
+ public int xOrigin() { return xOrigin; }
+ public int yOrigin() { return yOrigin; }
+ public int width() { return width; }
+ public int height() { return height; }
+ public byte pixelDepth() { return pixelDepth; }
+ public byte imageDescriptor() { return imageDescriptor; }
+
+ /** bitfields in imageDescriptor */
+ 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; }
+
+ public String toString() {
+ return "TGA Header " +
+ " id length: " + idLength +
+ " color map type: "+ colorMapType +
+ " image type: "+ imageType +
+ " first entry index: " + firstEntryIndex +
+ " color map length: " + colorMapLength +
+ " color map entry size: " + colorMapEntrySize +
+ " x Origin: " + xOrigin +
+ " y Origin: " + yOrigin +
+ " width: "+ width +
+ " height: "+ height +
+ " pixel depth: "+ pixelDepth +
+ " 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);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Identifies the image type of the tga image data and loads
+ * it into the JimiImage structure. This was taken from the
+ * prototype and modified for the new Jimi structure
+ */
+ private void decodeImage(LEDataInputStream dIn) throws IOException {
+ switch (header.imageType()) {
+ case Header.UCOLORMAPPED:
+ throw new IOException("TGADecoder Uncompressed Colormapped images not supported");
+
+ case Header.UTRUECOLOR: // pixelDepth 15, 16, 24 and 32
+ switch (header.pixelDepth) {
+ case 16:
+ throw new IOException("TGADecoder Compressed 16-bit True Color images not supported");
+
+ case 24:
+ case 32:
+ decodeRGBImageU24_32(dIn);
+ break;
+ }
+ break;
+
+ case Header.UBLACKWHITE:
+ throw new IOException("TGADecoder Uncompressed Grayscale images not supported");
+
+ case Header.COLORMAPPED:
+ throw new IOException("TGADecoder Compressed Colormapped images not supported");
+
+ case Header.TRUECOLOR:
+ throw new IOException("TGADecoder Compressed True Color images not supported");
+
+ case Header.BLACKWHITE:
+ throw new IOException("TGADecoder Compressed Grayscale images not supported");
+ }
+ }
+
+ /**
+ * This assumes that the body is for a 24 bit or 32 bit for a
+ * RGB or ARGB image respectively.
+ */
+ private void decodeRGBImageU24_32(LEDataInputStream dIn) throws IOException {
+ int i; // row index
+ int j; // column index
+ int y; // output row index
+ int raw; // index through the raw input buffer
+ int rawWidth = header.width() * (header.pixelDepth() / 8);
+ byte[] rawBuf = new byte[rawWidth];
+ byte[] tmpData = new byte[rawWidth * header.height()];
+
+ if (header.pixelDepth() == 24) {
+ format = GL.GL_BGR;
+ } else {
+ assert header.pixelDepth() == 32;
+ format = GL.GL_BGRA;
+ }
+
+ for (i = 0; i < header.height(); ++i) {
+ dIn.readFully(rawBuf, 0, rawWidth);
+
+ if (header.topToBottom())
+ y = header.height - i - 1; // range 0 to (header.height - 1)
+ else
+ y = i;
+
+ System.arraycopy(rawBuf, 0, tmpData, y * rawWidth, rawBuf.length);
+ }
+
+ data = ByteBuffer.wrap(tmpData);
+ }
+
+ /** Returns the width of the image. */
+ public int getWidth() { return header.width(); }
+
+ /** Returns the height of the image. */
+ public int getHeight() { return header.height(); }
+
+ /** Returns the OpenGL format for this texture; e.g. GL.GL_BGR or GL.GL_BGRA. */
+ public int getGLFormat() { return format; }
+
+ /** Returns the raw data for this texture in the correct
+ (bottom-to-top) order for calls to glTexImage2D. */
+ public ByteBuffer getData() { return data; }
+
+ /** Reads a Targa image from the specified file. */
+ public static TGAImage read(String filename) throws IOException {
+ return read(new FileInputStream(filename));
+ }
+
+ /** Reads a Targa image from the specified InputStream. */
+ public static TGAImage read(InputStream in) throws IOException {
+ LEDataInputStream dIn = new LEDataInputStream(new BufferedInputStream(in));
+
+ Header header = new Header(dIn);
+ TGAImage res = new TGAImage(header);
+ 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/util/texture/spi/TextureProvider.java b/src/classes/com/sun/opengl/util/texture/spi/TextureProvider.java
new file mode 100755
index 000000000..1c822ef02
--- /dev/null
+++ b/src/classes/com/sun/opengl/util/texture/spi/TextureProvider.java
@@ -0,0 +1,165 @@
+/*
+ * 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.util.texture.spi;
+
+import java.io.*;
+import java.net.*;
+
+import com.sun.opengl.util.texture.*;
+
+/** Plug-in interface to TextureIO to support reading OpenGL textures
+ from new file formats. For all methods, either internalFormat or
+ pixelFormat may be 0 in which case they must be inferred as
+ e.g. RGB or RGBA depending on the file contents.
+*/
+
+public interface TextureProvider {
+
+ /**
+ * Produces a TextureData object from a file, or returns null if the
+ * file format was not supported by this TextureProvider. Does not
+ * do any OpenGL-related work. The resulting TextureData can be
+ * converted into an OpenGL texture in a later step.
+ *
+ * @param file the file from which to read the texture data
+ *
+ * @param internalFormat the OpenGL internal format to be used for
+ * the texture, or 0 if it should be inferred
+ * from the file's contents
+ *
+ * @param pixelFormat the OpenGL pixel format to be used for
+ * the texture, or 0 if it should be inferred
+ * from the file's contents
+ *
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ *
+ * @param fileSuffix the file suffix to be used as a hint to the
+ * provider to more quickly decide whether it
+ * can handle the file, or null if the
+ * provider should infer the type from the
+ * file's contents
+ *
+ * @throws IOException if an error occurred while reading the file
+ */
+ public TextureData newTextureData(File file,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException;
+
+ /**
+ * Produces a TextureData object from a stream, or returns null if
+ * the file format was not supported by this TextureProvider. Does
+ * not do any OpenGL-related work. The resulting TextureData can be
+ * converted into an OpenGL texture in a later step.
+ *
+ * @param stream the stream from which to read the texture data
+ *
+ * @param internalFormat the OpenGL internal format to be used for
+ * the texture, or 0 if it should be inferred
+ * from the file's contents
+ *
+ * @param pixelFormat the OpenGL pixel format to be used for
+ * the texture, or 0 if it should be inferred
+ * from the file's contents
+ *
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ *
+ * @param fileSuffix the file suffix to be used as a hint to the
+ * provider to more quickly decide whether it
+ * can handle the file, or null if the
+ * provider should infer the type from the
+ * file's contents
+ *
+ * @throws IOException if an error occurred while reading the stream
+ */
+ public TextureData newTextureData(InputStream stream,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException;
+
+ /**
+ * Produces a TextureData object from a URL, or returns null if the
+ * file format was not supported by this TextureProvider. Does not
+ * do any OpenGL-related work. The resulting TextureData can be
+ * converted into an OpenGL texture in a later step.
+ *
+ * @param url the URL from which to read the texture data
+ *
+ * @param internalFormat the OpenGL internal format to be used for
+ * the texture, or 0 if it should be inferred
+ * from the file's contents
+ *
+ * @param pixelFormat the OpenGL pixel format to be used for
+ * the texture, or 0 if it should be inferred
+ * from the file's contents
+ *
+ * @param mipmap whether mipmaps should be produced for this
+ * texture either by autogenerating them or
+ * reading them from the file. Some file formats
+ * support multiple mipmaps in a single file in
+ * which case those mipmaps will be used rather
+ * than generating them.
+ *
+ * @param fileSuffix the file suffix to be used as a hint to the
+ * provider to more quickly decide whether it
+ * can handle the file, or null if the
+ * provider should infer the type from the
+ * file's contents
+ *
+ * @throws IOException if an error occurred while reading the URL
+ */
+ public TextureData newTextureData(URL url,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException;
+}
diff --git a/src/classes/com/sun/opengl/util/texture/spi/TextureWriter.java b/src/classes/com/sun/opengl/util/texture/spi/TextureWriter.java
new file mode 100755
index 000000000..423a3b3e5
--- /dev/null
+++ b/src/classes/com/sun/opengl/util/texture/spi/TextureWriter.java
@@ -0,0 +1,57 @@
+/*
+ * 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.util.texture.spi;
+
+import java.io.*;
+
+import com.sun.opengl.util.texture.*;
+
+/** 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;
+}