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 */ loadTexture(int target, int level, int border, int fallbackFormat, int fallbackType, InputStream input)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 */ loadTexture(int target, int level, int border, int fallbackFormat, int fallbackType, ETC1Texture texture)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 */ isETC1Supported()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 { ETC1Texture(int width, int height, ByteBuffer data)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 */ getWidth()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 */ getHeight()133 public int getHeight() { return mHeight; } 134 135 /** 136 * Get the compressed data of the texture. 137 * @return the texture data. 138 */ getData()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 */ createTexture(InputStream input)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 */ compressTexture(Buffer input, int width, int height, int pixelSize, int stride)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 */ writeTexture(ETC1Texture texture, OutputStream output)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