blob: e343c979de6cca04726ef2946b812564bfced221 [file] [log] [blame]
Jack Palevicha6276fd2009-12-28 19:31:43 +08001/*
Dan Bornsteinc086ca12010-12-07 15:35:20 -08002 * Copyright (C) 2009 The Android Open Source Project
Jack Palevicha6276fd2009-12-28 19:31:43 +08003 *
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 Bornsteinc086ca12010-12-07 15:35:20 -08008 * http://www.apache.org/licenses/LICENSE-2.0
Jack Palevicha6276fd2009-12-28 19:31:43 +08009 *
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
17package android.opengl;
18
19import java.io.IOException;
20import java.io.InputStream;
21import java.io.OutputStream;
22import java.nio.Buffer;
23import java.nio.ByteBuffer;
24import java.nio.ByteOrder;
25
26/**
27 * Utility methods for using ETC1 compressed textures.
28 *
29 */
30public 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 Palevich8af96492009-12-31 15:29:28 +080095 * @return true if the active OpenGL ES context supports ETC1 texture compression.
Jack Palevicha6276fd2009-12-28 19:31:43 +080096 */
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 Sakhartchoukfc5e2242011-11-03 17:28:33 -0700196 ETC1.encodeImage(input, width, height, pixelSize, stride, compressedImage);
Jack Palevicha6276fd2009-12-28 19:31:43 +0800197 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}