In many implementations of OpenGL, a two-dimensional texture must be a power of two in both width and height. The obvious way to rescale the image is to use gluScaleImage() appropriately, but in the current version of JOGL (JSR-231 1.1.1) there appears to be a bug that causes a crash.
So here is a code snippet that uses AWT’s AffineTransform and Graphics2D classes to rescale the image. It’s not particularly challenging, but I thought I could save some others a few minutes of coding. Warning: this code is only lightly tested. In particular, I haven’t loaded any images that preserve alpha, nor tried loading a variety of formats. Let me know if you find problems so we can post improved versions.
`
static class Texture2D {
public static Texture2D create(String resourceName) {
return Texture2D.create(resourceName, false, false);
}
public static Texture2D create(String resourceName, boolean powerOfTwo) {
return Texture2D.create(resourceName, powerOfTwo, false);
}
/*
* Create a texture object from an image file, suitable for use for
* calls to glBindTexture and related functions in OpenGL. If powerOfTwo
* is true, the width and height of the underlying pixel buffer will be
* adusted to be a power of two. If perserveAlpha is true, the pixel
* buffer will include the alpha channel from the original image.
*/
public static Texture2D create(String resourceName,
boolean powerOfTwo,
boolean preserveAlpha) {
Texture2D texture = null;
try {
InputStream resource = ResourceRetriever.getResourceAsStream(resourceName);
BufferedImage bufferedImage = ImageIO.read(resource);
if (powerOfTwo) {
bufferedImage = resampleToPowerOfTwo(bufferedImage);
}
ByteBuffer byteBuffer = unpackImage(bufferedImage,
preserveAlpha);
texture = new Texture2D(byteBuffer,
bufferedImage.getWidth(),
bufferedImage.getHeight());
} catch (IOException e) {}
return texture;
}
/*
* Create a scaled image which has width and height that are powers
* of two, equal to or smaller than the width and height of srcImage. If
* srcImage already has width and height that are exact powers of two,
* it is returned unchanged.
*/
private static BufferedImage resampleToPowerOfTwo(BufferedImage srcImage) {
BufferedImage po2Image = srcImage;
int w = srcImage.getWidth();
int h = srcImage.getHeight();
int w2 = powerOfTwoFloor(w);
int h2 = powerOfTwoFloor(h);
if ((w != w2) || (h != h2)) {
// Need to scale srcImage down to nearest power of two. Use an
// AWT affine transform and Graphics2D rendering for the job.
double scaleW = (double) w2 / (double) w;
double scaleH = (double) h2 / (double) h;
po2Image = new BufferedImage(w2, h2, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = po2Image.createGraphics();
AffineTransform at = AffineTransform.getScaleInstance(scaleW, scaleH);
g2.drawRenderedImage(srcImage, at);
g2.dispose();
}
return po2Image;
}
/*
* Return the largest power of two that is less than or equal to i.
*/
private static int powerOfTwoFloor(int i) {
double log2i = Math.log(i) / Math.log(2.0);
return 1 << (int) (Math.floor(log2i));
}
/*
* Unpack an INT_RGB or RGBA BufferedImage into a GL-compatible
* ByteBuffer. If preserveAlpha is true, then the alpha channel is
* extracted, otherwise it is omitted.
*/
private static ByteBuffer unpackImage(BufferedImage image,
boolean preserveAlpha) {
int[] packedPixels = new int[image.getWidth() * image.getHeight()];
PixelGrabber pixelgrabber = new PixelGrabber(image,
0,
0,
image.getWidth(),
image.getHeight(),
packedPixels,
0,
image.getWidth());
try {
pixelgrabber.grabPixels();
} catch (InterruptedException e) {
throw new RuntimeException();
}
int bytesPerPixel = preserveAlpha ? 4 : 3;
ByteBuffer unpackedPixels = BufferUtil.newByteBuffer(packedPixels.length
* bytesPerPixel);
for (int row = image.getHeight() - 1; row >= 0; row--) {
for (int col = 0; col < image.getWidth(); col++) {
int packedPixel = packedPixels[row * image.getWidth() + col];
unpackedPixels.put((byte) ((packedPixel >> 16) & 0xFF));
unpackedPixels.put((byte) ((packedPixel >> 8) & 0xFF));
unpackedPixels.put((byte) ((packedPixel >> 0) & 0xFF));
if (preserveAlpha) {
unpackedPixels.put((byte) ((packedPixel >> 24) & 0xFF));
}
}
}
unpackedPixels.flip();
return unpackedPixels;
}
private ByteBuffer _pixels;
private int _width;
private int _height;
public Texture2D(ByteBuffer pixels, int width, int height) {
super();
_pixels = pixels;
_width = width;
_height = height;
}
public Buffer getPixels() {
return _pixels;
}
public int getHeight() {
return _height;
}
public int getWidth() {
return _width;
}
}
`