diff options
| author | Jack Palevich <jackpal@google.com> | 2009-12-28 19:31:43 +0800 |
|---|---|---|
| committer | Jack Palevich <jackpal@google.com> | 2009-12-31 13:31:04 +0800 |
| commit | a6276fdd4253c3a7150ab675678c750473ab6c45 (patch) | |
| tree | d025885cd75cd415b62eb7757d25c05174c3db49 /opengl/java/android | |
| parent | 8eb3ea659761edc2cd5db3acf448059f19389e5e (diff) | |
| download | frameworks_base-a6276fdd4253c3a7150ab675678c750473ab6c45.zip frameworks_base-a6276fdd4253c3a7150ab675678c750473ab6c45.tar.gz frameworks_base-a6276fdd4253c3a7150ab675678c750473ab6c45.tar.bz2 | |
A library for encoding and decoding ETC1 textures.
The ETC1 compressed texture format is commonly
supported by OpenGL ES 2.0-capable devices.
Diffstat (limited to 'opengl/java/android')
| -rw-r--r-- | opengl/java/android/opengl/ETC1.java | 137 | ||||
| -rw-r--r-- | opengl/java/android/opengl/ETC1Util.java | 228 |
2 files changed, 365 insertions, 0 deletions
diff --git a/opengl/java/android/opengl/ETC1.java b/opengl/java/android/opengl/ETC1.java new file mode 100644 index 0000000..677bbd4 --- /dev/null +++ b/opengl/java/android/opengl/ETC1.java @@ -0,0 +1,137 @@ +/* + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.opengl; + +import java.nio.Buffer; + +/** + * Methods for encoding and decoding ETC1 textures. + * <p> + * The standard for the ETC1 texture format can be found at + * http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt + * <p> + * The PKM file format is of a 16-byte header that describes the image bounds + * followed by the encoded ETC1 texture data. + * <p> + * @see(ETC1Util) + */ +public class ETC1 { + + /** + * Size in bytes of an encoded block. + */ + public static final int ENCODED_BLOCK_SIZE = 8; + + /** + * Size in bytes of a decoded block. + */ + public static final int DECODED_BLOCK_SIZE = 48; + + /** + * Size of a PKM file header, in bytes. + */ + public static final int ETC_PKM_HEADER_SIZE = 16; + + /** + * Accepted by the internalformat parameter of glCompressedTexImage2D. + */ + public static final int ETC1_RGB8_OES = 0x8D64; + + /** + * Encode a block of pixels. + * + * @param in a native order direct buffer of size DECODED_BLOCK_SIZE that represent a + * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R + * value of pixel (x, y). + * + * @param validPixelMask is a 16-bit mask where bit (1 << (x + y * 4)) indicates whether + * the corresponding (x,y) pixel is valid. Invalid pixel color values are ignored when compressing. + * + * @param out a native order direct buffer of size ENCODED_BLOCK_SIZE that receives the + * ETC1 compressed version of the data. + * + */ + public static native void encodeBlock(Buffer in, int validPixelMask, Buffer out); + + /** + * Decode a block of pixels. + * + * @param in a native order direct buffer of size ENCODED_BLOCK_SIZE that contains the + * ETC1 compressed version of the data. + * + * @param out a native order direct buffer of size DECODED_BLOCK_SIZE that will receive + * the decoded data. The data represents a + * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R + * value of pixel (x, y). + */ + public static native void decodeBlock(Buffer in, Buffer out); + + /** + * Return the size of the encoded image data (does not include size of PKM header). + */ + public static native int getEncodedDataSize(int width, int height); + + /** + * Encode an entire image. + * @param in a native order direct buffer of the image data. Formatted such that + * pixel (x,y) is at pIn + pixelSize * x + stride * y; + * @param out a native order direct buffer of the encoded data. + * Must be large enough to store entire encoded image. + * @param pixelSize must be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, + * 3 is a GL_BYTE RGB image. + */ + public static native void encodeImage(Buffer in, int width, int height, + int pixelSize, int stride, Buffer out); + + /** + * Decode an entire image. + * @param in native order direct buffer of the encoded data. + * @param out native order direct buffer of the image data. Will be written such that + * pixel (x,y) is at pIn + pixelSize * x + stride * y. Must be + * large enough to store entire image. + * @param pixelSize must be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, + * 3 is a GL_BYTE RGB image. + */ + public static native void decodeImage(Buffer in, Buffer out, + int width, int height, int pixelSize, int stride); + + /** + * Format a PKM header + * @param header native order direct buffer of the header. + * @param width the width of the image in pixels. + * @param height the height of the image in pixels. + */ + public static native void formatHeader(Buffer header, int width, int height); + + /** + * Check if a PKM header is correctly formatted. + * @param header native order direct buffer of the header. + */ + public static native boolean isValid(Buffer header); + + /** + * Read the image width from a PKM header + * @param header native order direct buffer of the header. + */ + public static native int getWidth(Buffer header); + + /** + * Read the image height from a PKM header + * @param header native order direct buffer of the header. + */ + public static native int getHeight(Buffer header); +} diff --git a/opengl/java/android/opengl/ETC1Util.java b/opengl/java/android/opengl/ETC1Util.java new file mode 100644 index 0000000..56e2f23 --- /dev/null +++ b/opengl/java/android/opengl/ETC1Util.java @@ -0,0 +1,228 @@ +/* + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.opengl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Utility methods for using ETC1 compressed textures. + * + */ +public class ETC1Util { + /** + * Convenience method to load an ETC1 texture whether or not the active OpenGL context + * supports the ETC1 texture compression format. + * @param target the texture target. + * @param level the texture level + * @param border the border size. Typically 0. + * @param fallbackFormat the format to use if ETC1 texture compression is not supported. + * Must be GL_RGB. + * @param fallbackType the type to use if ETC1 texture compression is not supported. + * Can be either GL_UNSIGNED_SHORT_5_6_5, which implies 16-bits-per-pixel, + * or GL_UNSIGNED_BYTE, which implies 24-bits-per-pixel. + * @param input the input stream containing an ETC1 texture in PKM format. + * @throws IOException + */ + public static void loadTexture(int target, int level, int border, + int fallbackFormat, int fallbackType, InputStream input) + throws IOException { + loadTexture(target, level, border, fallbackFormat, fallbackType, createTexture(input)); + } + + /** + * Convenience method to load an ETC1 texture whether or not the active OpenGL context + * supports the ETC1 texture compression format. + * @param target the texture target. + * @param level the texture level + * @param border the border size. Typically 0. + * @param fallbackFormat the format to use if ETC1 texture compression is not supported. + * Must be GL_RGB. + * @param fallbackType the type to use if ETC1 texture compression is not supported. + * Can be either GL_UNSIGNED_SHORT_5_6_5, which implies 16-bits-per-pixel, + * or GL_UNSIGNED_BYTE, which implies 24-bits-per-pixel. + * @param texture the ETC1 to load. + */ + public static void loadTexture(int target, int level, int border, + int fallbackFormat, int fallbackType, ETC1Texture texture) { + if (fallbackFormat != GLES10.GL_RGB) { + throw new IllegalArgumentException("fallbackFormat must be GL_RGB"); + } + if (! (fallbackType == GLES10.GL_UNSIGNED_SHORT_5_6_5 + || fallbackType == GLES10.GL_UNSIGNED_BYTE)) { + throw new IllegalArgumentException("Unsupported fallbackType"); + } + + int width = texture.getWidth(); + int height = texture.getHeight(); + Buffer data = texture.getData(); + if (isETC1Supported()) { + int imageSize = data.remaining(); + GLES10.glCompressedTexImage2D(target, level, ETC1.ETC1_RGB8_OES, width, height, + border, imageSize, data); + } else { + boolean useShorts = fallbackType != GLES10.GL_UNSIGNED_BYTE; + int pixelSize = useShorts ? 2 : 3; + int stride = pixelSize * width; + ByteBuffer decodedData = ByteBuffer.allocateDirect(stride*height) + .order(ByteOrder.nativeOrder()); + ETC1.decodeImage(data, decodedData, width, height, pixelSize, stride); + GLES10.glTexImage2D(target, level, fallbackFormat, width, height, border, + fallbackFormat, fallbackType, decodedData); + } + } + + /** + * Check if ETC1 texture compression is supported by the active OpenGL ES context. + * @returns true if the active OpenGL ES context supports ETC1 texture compression. + */ + public static boolean isETC1Supported() { + int[] results = new int[20]; + GLES10.glGetIntegerv(GLES10.GL_NUM_COMPRESSED_TEXTURE_FORMATS, results, 0); + int numFormats = results[0]; + if (numFormats > results.length) { + results = new int[numFormats]; + } + GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, results, 0); + for (int i = 0; i < numFormats; i++) { + if (results[i] == ETC1.ETC1_RGB8_OES) { + return true; + } + } + return false; + } + + /** + * A utility class encapsulating a compressed ETC1 texture. + */ + public static class ETC1Texture { + public ETC1Texture(int width, int height, ByteBuffer data) { + mWidth = width; + mHeight = height; + mData = data; + } + + /** + * Get the width of the texture in pixels. + * @return the width of the texture in pixels. + */ + public int getWidth() { return mWidth; } + + /** + * Get the height of the texture in pixels. + * @return the width of the texture in pixels. + */ + public int getHeight() { return mHeight; } + + /** + * Get the compressed data of the texture. + * @return the texture data. + */ + public ByteBuffer getData() { return mData; } + + private int mWidth; + private int mHeight; + private ByteBuffer mData; + } + + /** + * Create a new ETC1Texture from an input stream containing a PKM formatted compressed texture. + * @param input an input stream containing a PKM formatted compressed texture. + * @return an ETC1Texture read from the input stream. + * @throws IOException + */ + public static ETC1Texture createTexture(InputStream input) throws IOException { + int width = 0; + int height = 0; + byte[] ioBuffer = new byte[4096]; + { + if (input.read(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE) != ETC1.ETC_PKM_HEADER_SIZE) { + throw new IOException("Unable to read PKM file header."); + } + ByteBuffer headerBuffer = ByteBuffer.allocateDirect(ETC1.ETC_PKM_HEADER_SIZE) + .order(ByteOrder.nativeOrder()); + headerBuffer.put(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE).position(0); + if (!ETC1.isValid(headerBuffer)) { + throw new IOException("Not a PKM file."); + } + width = ETC1.getWidth(headerBuffer); + height = ETC1.getHeight(headerBuffer); + } + int encodedSize = ETC1.getEncodedDataSize(width, height); + ByteBuffer dataBuffer = ByteBuffer.allocateDirect(encodedSize).order(ByteOrder.nativeOrder()); + for (int i = 0; i < encodedSize; ) { + int chunkSize = Math.min(ioBuffer.length, encodedSize - i); + if (input.read(ioBuffer, 0, chunkSize) != chunkSize) { + throw new IOException("Unable to read PKM file data."); + } + dataBuffer.put(ioBuffer, 0, chunkSize); + i += chunkSize; + } + dataBuffer.position(0); + return new ETC1Texture(width, height, dataBuffer); + } + + /** + * Helper function that compresses an image into an ETC1Texture. + * @param input a native order direct buffer containing the image data + * @param width the width of the image in pixels + * @param height the height of the image in pixels + * @param pixelSize the size of a pixel in bytes (2 or 3) + * @param stride the width of a line of the image in bytes + * @return the ETC1 texture. + */ + public static ETC1Texture compressTexture(Buffer input, int width, int height, int pixelSize, int stride){ + int encodedImageSize = ETC1.getEncodedDataSize(width, height); + ByteBuffer compressedImage = ByteBuffer.allocateDirect(encodedImageSize). + order(ByteOrder.nativeOrder()); + ETC1.encodeImage(input, width, height, 3, stride, compressedImage); + return new ETC1Texture(width, height, compressedImage); + } + + /** + * Helper function that writes an ETC1Texture to an output stream formatted as a PKM file. + * @param texture the input texture. + * @param output the stream to write the formatted texture data to. + * @throws IOException + */ + public static void writeTexture(ETC1Texture texture, OutputStream output) throws IOException { + ByteBuffer dataBuffer = texture.getData(); + int originalPosition = dataBuffer.position(); + try { + int width = texture.getWidth(); + int height = texture.getHeight(); + ByteBuffer header = ByteBuffer.allocateDirect(ETC1.ETC_PKM_HEADER_SIZE).order(ByteOrder.nativeOrder()); + ETC1.formatHeader(header, width, height); + byte[] ioBuffer = new byte[4096]; + header.get(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE); + output.write(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE); + int encodedSize = ETC1.getEncodedDataSize(width, height); + for (int i = 0; i < encodedSize; ) { + int chunkSize = Math.min(ioBuffer.length, encodedSize - i); + dataBuffer.get(ioBuffer, 0, chunkSize); + output.write(ioBuffer, 0, chunkSize); + i += chunkSize; + } + } finally { + dataBuffer.position(originalPosition); + } + } +} |
