Jack Palevich | a6276fd | 2009-12-28 19:31:43 +0800 | [diff] [blame] | 1 | /* |
Dan Bornstein | c086ca1 | 2010-12-07 15:35:20 -0800 | [diff] [blame] | 2 | * Copyright (C) 2009 The Android Open Source Project |
Jack Palevich | a6276fd | 2009-12-28 19:31:43 +0800 | [diff] [blame] | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
Dan Bornstein | c086ca1 | 2010-12-07 15:35:20 -0800 | [diff] [blame] | 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
Jack Palevich | a6276fd | 2009-12-28 19:31:43 +0800 | [diff] [blame] | 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package android.opengl; |
| 18 | |
| 19 | import java.io.IOException; |
| 20 | import java.io.InputStream; |
| 21 | import java.io.OutputStream; |
| 22 | import java.nio.Buffer; |
| 23 | import java.nio.ByteBuffer; |
| 24 | import java.nio.ByteOrder; |
| 25 | |
| 26 | /** |
| 27 | * Utility methods for using ETC1 compressed textures. |
| 28 | * |
| 29 | */ |
| 30 | public class ETC1Util { |
| 31 | /** |
| 32 | * Convenience method to load an ETC1 texture whether or not the active OpenGL context |
| 33 | * supports the ETC1 texture compression format. |
| 34 | * @param target the texture target. |
| 35 | * @param level the texture level |
| 36 | * @param border the border size. Typically 0. |
| 37 | * @param fallbackFormat the format to use if ETC1 texture compression is not supported. |
| 38 | * Must be GL_RGB. |
| 39 | * @param fallbackType the type to use if ETC1 texture compression is not supported. |
| 40 | * Can be either GL_UNSIGNED_SHORT_5_6_5, which implies 16-bits-per-pixel, |
| 41 | * or GL_UNSIGNED_BYTE, which implies 24-bits-per-pixel. |
| 42 | * @param input the input stream containing an ETC1 texture in PKM format. |
| 43 | * @throws IOException |
| 44 | */ |
| 45 | public static void loadTexture(int target, int level, int border, |
| 46 | int fallbackFormat, int fallbackType, InputStream input) |
| 47 | throws IOException { |
| 48 | loadTexture(target, level, border, fallbackFormat, fallbackType, createTexture(input)); |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * Convenience method to load an ETC1 texture whether or not the active OpenGL context |
| 53 | * supports the ETC1 texture compression format. |
| 54 | * @param target the texture target. |
| 55 | * @param level the texture level |
| 56 | * @param border the border size. Typically 0. |
| 57 | * @param fallbackFormat the format to use if ETC1 texture compression is not supported. |
| 58 | * Must be GL_RGB. |
| 59 | * @param fallbackType the type to use if ETC1 texture compression is not supported. |
| 60 | * Can be either GL_UNSIGNED_SHORT_5_6_5, which implies 16-bits-per-pixel, |
| 61 | * or GL_UNSIGNED_BYTE, which implies 24-bits-per-pixel. |
| 62 | * @param texture the ETC1 to load. |
| 63 | */ |
| 64 | public static void loadTexture(int target, int level, int border, |
| 65 | int fallbackFormat, int fallbackType, ETC1Texture texture) { |
| 66 | if (fallbackFormat != GLES10.GL_RGB) { |
| 67 | throw new IllegalArgumentException("fallbackFormat must be GL_RGB"); |
| 68 | } |
| 69 | if (! (fallbackType == GLES10.GL_UNSIGNED_SHORT_5_6_5 |
| 70 | || fallbackType == GLES10.GL_UNSIGNED_BYTE)) { |
| 71 | throw new IllegalArgumentException("Unsupported fallbackType"); |
| 72 | } |
| 73 | |
| 74 | int width = texture.getWidth(); |
| 75 | int height = texture.getHeight(); |
| 76 | Buffer data = texture.getData(); |
| 77 | if (isETC1Supported()) { |
| 78 | int imageSize = data.remaining(); |
| 79 | GLES10.glCompressedTexImage2D(target, level, ETC1.ETC1_RGB8_OES, width, height, |
| 80 | border, imageSize, data); |
| 81 | } else { |
| 82 | boolean useShorts = fallbackType != GLES10.GL_UNSIGNED_BYTE; |
| 83 | int pixelSize = useShorts ? 2 : 3; |
| 84 | int stride = pixelSize * width; |
| 85 | ByteBuffer decodedData = ByteBuffer.allocateDirect(stride*height) |
| 86 | .order(ByteOrder.nativeOrder()); |
| 87 | ETC1.decodeImage(data, decodedData, width, height, pixelSize, stride); |
| 88 | GLES10.glTexImage2D(target, level, fallbackFormat, width, height, border, |
| 89 | fallbackFormat, fallbackType, decodedData); |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | /** |
| 94 | * Check if ETC1 texture compression is supported by the active OpenGL ES context. |
Jack Palevich | 8af9649 | 2009-12-31 15:29:28 +0800 | [diff] [blame] | 95 | * @return true if the active OpenGL ES context supports ETC1 texture compression. |
Jack Palevich | a6276fd | 2009-12-28 19:31:43 +0800 | [diff] [blame] | 96 | */ |
| 97 | public static boolean isETC1Supported() { |
| 98 | int[] results = new int[20]; |
| 99 | GLES10.glGetIntegerv(GLES10.GL_NUM_COMPRESSED_TEXTURE_FORMATS, results, 0); |
| 100 | int numFormats = results[0]; |
| 101 | if (numFormats > results.length) { |
| 102 | results = new int[numFormats]; |
| 103 | } |
| 104 | GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, results, 0); |
| 105 | for (int i = 0; i < numFormats; i++) { |
| 106 | if (results[i] == ETC1.ETC1_RGB8_OES) { |
| 107 | return true; |
| 108 | } |
| 109 | } |
| 110 | return false; |
| 111 | } |
| 112 | |
| 113 | /** |
| 114 | * A utility class encapsulating a compressed ETC1 texture. |
| 115 | */ |
| 116 | public static class ETC1Texture { |
| 117 | public ETC1Texture(int width, int height, ByteBuffer data) { |
| 118 | mWidth = width; |
| 119 | mHeight = height; |
| 120 | mData = data; |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Get the width of the texture in pixels. |
| 125 | * @return the width of the texture in pixels. |
| 126 | */ |
| 127 | public int getWidth() { return mWidth; } |
| 128 | |
| 129 | /** |
| 130 | * Get the height of the texture in pixels. |
| 131 | * @return the width of the texture in pixels. |
| 132 | */ |
| 133 | public int getHeight() { return mHeight; } |
| 134 | |
| 135 | /** |
| 136 | * Get the compressed data of the texture. |
| 137 | * @return the texture data. |
| 138 | */ |
| 139 | public ByteBuffer getData() { return mData; } |
| 140 | |
| 141 | private int mWidth; |
| 142 | private int mHeight; |
| 143 | private ByteBuffer mData; |
| 144 | } |
| 145 | |
| 146 | /** |
| 147 | * Create a new ETC1Texture from an input stream containing a PKM formatted compressed texture. |
| 148 | * @param input an input stream containing a PKM formatted compressed texture. |
| 149 | * @return an ETC1Texture read from the input stream. |
| 150 | * @throws IOException |
| 151 | */ |
| 152 | public static ETC1Texture createTexture(InputStream input) throws IOException { |
| 153 | int width = 0; |
| 154 | int height = 0; |
| 155 | byte[] ioBuffer = new byte[4096]; |
| 156 | { |
| 157 | if (input.read(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE) != ETC1.ETC_PKM_HEADER_SIZE) { |
| 158 | throw new IOException("Unable to read PKM file header."); |
| 159 | } |
| 160 | ByteBuffer headerBuffer = ByteBuffer.allocateDirect(ETC1.ETC_PKM_HEADER_SIZE) |
| 161 | .order(ByteOrder.nativeOrder()); |
| 162 | headerBuffer.put(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE).position(0); |
| 163 | if (!ETC1.isValid(headerBuffer)) { |
| 164 | throw new IOException("Not a PKM file."); |
| 165 | } |
| 166 | width = ETC1.getWidth(headerBuffer); |
| 167 | height = ETC1.getHeight(headerBuffer); |
| 168 | } |
| 169 | int encodedSize = ETC1.getEncodedDataSize(width, height); |
| 170 | ByteBuffer dataBuffer = ByteBuffer.allocateDirect(encodedSize).order(ByteOrder.nativeOrder()); |
| 171 | for (int i = 0; i < encodedSize; ) { |
| 172 | int chunkSize = Math.min(ioBuffer.length, encodedSize - i); |
| 173 | if (input.read(ioBuffer, 0, chunkSize) != chunkSize) { |
| 174 | throw new IOException("Unable to read PKM file data."); |
| 175 | } |
| 176 | dataBuffer.put(ioBuffer, 0, chunkSize); |
| 177 | i += chunkSize; |
| 178 | } |
| 179 | dataBuffer.position(0); |
| 180 | return new ETC1Texture(width, height, dataBuffer); |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Helper function that compresses an image into an ETC1Texture. |
| 185 | * @param input a native order direct buffer containing the image data |
| 186 | * @param width the width of the image in pixels |
| 187 | * @param height the height of the image in pixels |
| 188 | * @param pixelSize the size of a pixel in bytes (2 or 3) |
| 189 | * @param stride the width of a line of the image in bytes |
| 190 | * @return the ETC1 texture. |
| 191 | */ |
| 192 | public static ETC1Texture compressTexture(Buffer input, int width, int height, int pixelSize, int stride){ |
| 193 | int encodedImageSize = ETC1.getEncodedDataSize(width, height); |
| 194 | ByteBuffer compressedImage = ByteBuffer.allocateDirect(encodedImageSize). |
| 195 | order(ByteOrder.nativeOrder()); |
Alex Sakhartchouk | fc5e224 | 2011-11-03 17:28:33 -0700 | [diff] [blame] | 196 | ETC1.encodeImage(input, width, height, pixelSize, stride, compressedImage); |
Jack Palevich | a6276fd | 2009-12-28 19:31:43 +0800 | [diff] [blame] | 197 | return new ETC1Texture(width, height, compressedImage); |
| 198 | } |
| 199 | |
| 200 | /** |
| 201 | * Helper function that writes an ETC1Texture to an output stream formatted as a PKM file. |
| 202 | * @param texture the input texture. |
| 203 | * @param output the stream to write the formatted texture data to. |
| 204 | * @throws IOException |
| 205 | */ |
| 206 | public static void writeTexture(ETC1Texture texture, OutputStream output) throws IOException { |
| 207 | ByteBuffer dataBuffer = texture.getData(); |
| 208 | int originalPosition = dataBuffer.position(); |
| 209 | try { |
| 210 | int width = texture.getWidth(); |
| 211 | int height = texture.getHeight(); |
| 212 | ByteBuffer header = ByteBuffer.allocateDirect(ETC1.ETC_PKM_HEADER_SIZE).order(ByteOrder.nativeOrder()); |
| 213 | ETC1.formatHeader(header, width, height); |
| 214 | byte[] ioBuffer = new byte[4096]; |
| 215 | header.get(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE); |
| 216 | output.write(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE); |
| 217 | int encodedSize = ETC1.getEncodedDataSize(width, height); |
| 218 | for (int i = 0; i < encodedSize; ) { |
| 219 | int chunkSize = Math.min(ioBuffer.length, encodedSize - i); |
| 220 | dataBuffer.get(ioBuffer, 0, chunkSize); |
| 221 | output.write(ioBuffer, 0, chunkSize); |
| 222 | i += chunkSize; |
| 223 | } |
| 224 | } finally { |
| 225 | dataBuffer.position(originalPosition); |
| 226 | } |
| 227 | } |
| 228 | } |