1 /* 2 * Copyright (C) 2009 The Android Open Source Project 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 * 8 * http://www.apache.org/licenses/LICENSE-2.0 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. 95 * @return true if the active OpenGL ES context supports ETC1 texture compression. 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()); 196 ETC1.encodeImage(input, width, height, pixelSize, stride, compressedImage); 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 } 229