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 | |
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.
-rw-r--r-- | api/current.xml | 395 | ||||
-rw-r--r-- | core/jni/Android.mk | 1 | ||||
-rw-r--r-- | core/jni/android/opengl/util.cpp | 309 | ||||
-rw-r--r-- | opengl/include/ETC1/etc1.h | 106 | ||||
-rw-r--r-- | opengl/java/android/opengl/ETC1.java | 137 | ||||
-rw-r--r-- | opengl/java/android/opengl/ETC1Util.java | 228 | ||||
-rw-r--r-- | opengl/libs/Android.mk | 30 | ||||
-rw-r--r-- | opengl/libs/ETC1/etc1.cpp | 670 |
8 files changed, 1876 insertions, 0 deletions
diff --git a/api/current.xml b/api/current.xml index 55289e5..ad5a1a7 100644 --- a/api/current.xml +++ b/api/current.xml @@ -88168,6 +88168,401 @@ </package> <package name="android.opengl" > +<class name="ETC1" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="ETC1" + type="android.opengl.ETC1" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<method name="decodeBlock" + return="void" + abstract="false" + native="true" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="in" type="java.nio.Buffer"> +</parameter> +<parameter name="out" type="java.nio.Buffer"> +</parameter> +</method> +<method name="decodeImage" + return="void" + abstract="false" + native="true" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="in" type="java.nio.Buffer"> +</parameter> +<parameter name="out" type="java.nio.Buffer"> +</parameter> +<parameter name="width" type="int"> +</parameter> +<parameter name="height" type="int"> +</parameter> +<parameter name="pixelSize" type="int"> +</parameter> +<parameter name="stride" type="int"> +</parameter> +</method> +<method name="encodeBlock" + return="void" + abstract="false" + native="true" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="in" type="java.nio.Buffer"> +</parameter> +<parameter name="validPixelMask" type="int"> +</parameter> +<parameter name="out" type="java.nio.Buffer"> +</parameter> +</method> +<method name="encodeImage" + return="void" + abstract="false" + native="true" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="in" type="java.nio.Buffer"> +</parameter> +<parameter name="width" type="int"> +</parameter> +<parameter name="height" type="int"> +</parameter> +<parameter name="pixelSize" type="int"> +</parameter> +<parameter name="stride" type="int"> +</parameter> +<parameter name="out" type="java.nio.Buffer"> +</parameter> +</method> +<method name="formatHeader" + return="void" + abstract="false" + native="true" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="header" type="java.nio.Buffer"> +</parameter> +<parameter name="width" type="int"> +</parameter> +<parameter name="height" type="int"> +</parameter> +</method> +<method name="getEncodedDataSize" + return="int" + abstract="false" + native="true" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="width" type="int"> +</parameter> +<parameter name="height" type="int"> +</parameter> +</method> +<method name="getHeight" + return="int" + abstract="false" + native="true" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="header" type="java.nio.Buffer"> +</parameter> +</method> +<method name="getWidth" + return="int" + abstract="false" + native="true" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="header" type="java.nio.Buffer"> +</parameter> +</method> +<method name="isValid" + return="boolean" + abstract="false" + native="true" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="header" type="java.nio.Buffer"> +</parameter> +</method> +<field name="DECODED_BLOCK_SIZE" + type="int" + transient="false" + volatile="false" + value="48" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ENCODED_BLOCK_SIZE" + type="int" + transient="false" + volatile="false" + value="8" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ETC1_RGB8_OES" + type="int" + transient="false" + volatile="false" + value="36196" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ETC_PKM_HEADER_SIZE" + type="int" + transient="false" + volatile="false" + value="16" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="ETC1Util" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="ETC1Util" + type="android.opengl.ETC1Util" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<method name="compressTexture" + return="android.opengl.ETC1Util.ETC1Texture" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="input" type="java.nio.Buffer"> +</parameter> +<parameter name="width" type="int"> +</parameter> +<parameter name="height" type="int"> +</parameter> +<parameter name="pixelSize" type="int"> +</parameter> +<parameter name="stride" type="int"> +</parameter> +</method> +<method name="createTexture" + return="android.opengl.ETC1Util.ETC1Texture" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="input" type="java.io.InputStream"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="isETC1Supported" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="loadTexture" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="target" type="int"> +</parameter> +<parameter name="level" type="int"> +</parameter> +<parameter name="border" type="int"> +</parameter> +<parameter name="fallbackFormat" type="int"> +</parameter> +<parameter name="fallbackType" type="int"> +</parameter> +<parameter name="input" type="java.io.InputStream"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="loadTexture" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="target" type="int"> +</parameter> +<parameter name="level" type="int"> +</parameter> +<parameter name="border" type="int"> +</parameter> +<parameter name="fallbackFormat" type="int"> +</parameter> +<parameter name="fallbackType" type="int"> +</parameter> +<parameter name="texture" type="android.opengl.ETC1Util.ETC1Texture"> +</parameter> +</method> +<method name="writeTexture" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="texture" type="android.opengl.ETC1Util.ETC1Texture"> +</parameter> +<parameter name="output" type="java.io.OutputStream"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +</class> +<class name="ETC1Util.ETC1Texture" + extends="java.lang.Object" + abstract="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="ETC1Util.ETC1Texture" + type="android.opengl.ETC1Util.ETC1Texture" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="width" type="int"> +</parameter> +<parameter name="height" type="int"> +</parameter> +<parameter name="data" type="java.nio.ByteBuffer"> +</parameter> +</constructor> +<method name="getData" + return="java.nio.ByteBuffer" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getHeight" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getWidth" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +</class> <class name="GLDebugHelper" extends="java.lang.Object" abstract="false" diff --git a/core/jni/Android.mk b/core/jni/Android.mk index c92a86c..67a0bda 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -165,6 +165,7 @@ LOCAL_SHARED_LIBRARIES := \ libEGL \ libGLESv1_CM \ libGLESv2 \ + libETC1 \ libhardware \ libhardware_legacy \ libsonivox \ diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp index 4041346..589b255 100644 --- a/core/jni/android/opengl/util.cpp +++ b/core/jni/android/opengl/util.cpp @@ -23,6 +23,7 @@ #include <dlfcn.h> #include <GLES/gl.h> +#include <ETC1/etc1.h> #include <core/SkBitmap.h> @@ -39,6 +40,7 @@ namespace android { static jclass gIAEClass; static jclass gUOEClass; +static jclass gAIOOBEClass; static inline void mx4transform(float x, float y, float z, float w, const float* pM, float* pDest) { @@ -712,6 +714,297 @@ static jint util_texSubImage2D(JNIEnv *env, jclass clazz, } /* + * ETC1 methods. + */ + +static jclass nioAccessClass; +static jclass bufferClass; +static jmethodID getBasePointerID; +static jmethodID getBaseArrayID; +static jmethodID getBaseArrayOffsetID; +static jfieldID positionID; +static jfieldID limitID; +static jfieldID elementSizeShiftID; + +/* Cache method IDs each time the class is loaded. */ + +static void +nativeClassInitBuffer(JNIEnv *_env) +{ + jclass nioAccessClassLocal = _env->FindClass("java/nio/NIOAccess"); + nioAccessClass = (jclass) _env->NewGlobalRef(nioAccessClassLocal); + + jclass bufferClassLocal = _env->FindClass("java/nio/Buffer"); + bufferClass = (jclass) _env->NewGlobalRef(bufferClassLocal); + + getBasePointerID = _env->GetStaticMethodID(nioAccessClass, + "getBasePointer", "(Ljava/nio/Buffer;)J"); + getBaseArrayID = _env->GetStaticMethodID(nioAccessClass, + "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;"); + getBaseArrayOffsetID = _env->GetStaticMethodID(nioAccessClass, + "getBaseArrayOffset", "(Ljava/nio/Buffer;)I"); + positionID = _env->GetFieldID(bufferClass, "position", "I"); + limitID = _env->GetFieldID(bufferClass, "limit", "I"); + elementSizeShiftID = + _env->GetFieldID(bufferClass, "_elementSizeShift", "I"); +} + +static void * +getPointer(JNIEnv *_env, jobject buffer, jint *remaining) +{ + jint position; + jint limit; + jint elementSizeShift; + jlong pointer; + jint offset; + void *data; + + position = _env->GetIntField(buffer, positionID); + limit = _env->GetIntField(buffer, limitID); + elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID); + *remaining = (limit - position) << elementSizeShift; + pointer = _env->CallStaticLongMethod(nioAccessClass, + getBasePointerID, buffer); + if (pointer != 0L) { + return (void *) (jint) pointer; + } + return NULL; +} + +class BufferHelper { +public: + BufferHelper(JNIEnv *env, jobject buffer) { + mEnv = env; + mBuffer = buffer; + mData = NULL; + mRemaining = 0; + } + + bool checkPointer(const char* errorMessage) { + if (mBuffer) { + mData = getPointer(mEnv, mBuffer, &mRemaining); + if (mData == NULL) { + mEnv->ThrowNew(gIAEClass, errorMessage); + } + return mData != NULL; + } else { + mEnv->ThrowNew(gIAEClass, errorMessage); + return false; + } + } + + inline void* getData() { + return mData; + } + + inline jint remaining() { + return mRemaining; + } + +private: + JNIEnv* mEnv; + jobject mBuffer; + void* mData; + jint mRemaining; +}; + +/** + * Encode a block of pixels. + * + * @param in a pointer to a ETC1_DECODED_BLOCK_SIZE array of bytes 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 an ETC1 compressed version of the data. + * + */ +static void etc1_encodeBlock(JNIEnv *env, jclass clazz, + jobject in, jint validPixelMask, jobject out) { + if (validPixelMask < 0 || validPixelMask > 15) { + env->ThrowNew(gIAEClass, "validPixelMask"); + return; + } + BufferHelper inB(env, in); + BufferHelper outB(env, out); + if (inB.checkPointer("in") && outB.checkPointer("out")) { + if (inB.remaining() < ETC1_DECODED_BLOCK_SIZE) { + env->ThrowNew(gIAEClass, "in's remaining data < DECODED_BLOCK_SIZE"); + } else if (outB.remaining() < ETC1_ENCODED_BLOCK_SIZE) { + env->ThrowNew(gIAEClass, "out's remaining data < ENCODED_BLOCK_SIZE"); + } else { + etc1_encode_block((etc1_byte*) inB.getData(), validPixelMask, + (etc1_byte*) outB.getData()); + } + } +} + +/** + * Decode a block of pixels. + * + * @param in an ETC1 compressed version of the data. + * + * @param out a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes 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). + */ +static void etc1_decodeBlock(JNIEnv *env, jclass clazz, + jobject in, jobject out){ + BufferHelper inB(env, in); + BufferHelper outB(env, out); + if (inB.checkPointer("in") && outB.checkPointer("out")) { + if (inB.remaining() < ETC1_ENCODED_BLOCK_SIZE) { + env->ThrowNew(gIAEClass, "in's remaining data < ENCODED_BLOCK_SIZE"); + } else if (outB.remaining() < ETC1_DECODED_BLOCK_SIZE) { + env->ThrowNew(gIAEClass, "out's remaining data < DECODED_BLOCK_SIZE"); + } else { + etc1_decode_block((etc1_byte*) inB.getData(), + (etc1_byte*) outB.getData()); + } + } +} + +/** + * Return the size of the encoded image data (does not include size of PKM header). + */ +static jint etc1_getEncodedDataSize(JNIEnv *env, jclass clazz, + jint width, jint height) { + return etc1_get_encoded_data_size(width, height); +} + +/** + * Encode an entire image. + * @param in pointer to the image data. Formatted such that + * pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset; + * @param out pointer to encoded data. Must be large enough to store entire encoded image. + */ +static void etc1_encodeImage(JNIEnv *env, jclass clazz, + jobject in, jint width, jint height, + jint pixelSize, jint stride, jobject out) { + if (pixelSize < 2 || pixelSize > 3) { + env->ThrowNew(gIAEClass, "pixelSize must be 2 or 3"); + return; + } + BufferHelper inB(env, in); + BufferHelper outB(env, out); + if (inB.checkPointer("in") && outB.checkPointer("out")) { + jint imageSize = stride * height; + jint encodedImageSize = etc1_get_encoded_data_size(width, height); + if (inB.remaining() < imageSize) { + env->ThrowNew(gIAEClass, "in's remaining data < image size"); + } else if (outB.remaining() < encodedImageSize) { + env->ThrowNew(gIAEClass, "out's remaining data < encoded image size"); + } else { + int result = etc1_encode_image((etc1_byte*) inB.getData(), + width, height, pixelSize, + stride, + (etc1_byte*) outB.getData()); + } + } +} + +/** + * Decode an entire image. + * @param in the encoded data. + * @param out pointer to 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. + */ +static void etc1_decodeImage(JNIEnv *env, jclass clazz, + jobject in, jobject out, + jint width, jint height, + jint pixelSize, jint stride) { + if (pixelSize < 2 || pixelSize > 3) { + env->ThrowNew(gIAEClass, "pixelSize must be 2 or 3"); + return; + } + BufferHelper inB(env, in); + BufferHelper outB(env, out); + if (inB.checkPointer("in") && outB.checkPointer("out")) { + jint imageSize = stride * height; + jint encodedImageSize = etc1_get_encoded_data_size(width, height); + if (inB.remaining() < encodedImageSize) { + env->ThrowNew(gIAEClass, "in's remaining data < encoded image size"); + } else if (outB.remaining() < imageSize) { + env->ThrowNew(gIAEClass, "out's remaining data < image size"); + } else { + int result = etc1_decode_image((etc1_byte*) inB.getData(), + (etc1_byte*) outB.getData(), + width, height, pixelSize, + stride); + } + } +} + +/** + * Format a PKM header + */ +static void etc1_formatHeader(JNIEnv *env, jclass clazz, + jobject header, jint width, jint height) { + BufferHelper headerB(env, header); + if (headerB.checkPointer("header") ){ + if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { + env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE"); + } else { + etc1_pkm_format_header((etc1_byte*) headerB.getData(), width, height); + } + } +} + +/** + * Check if a PKM header is correctly formatted. + */ +static jboolean etc1_isValid(JNIEnv *env, jclass clazz, + jobject header) { + jboolean result = false; + BufferHelper headerB(env, header); + if (headerB.checkPointer("header") ){ + if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { + env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE"); + } else { + result = etc1_pkm_is_valid((etc1_byte*) headerB.getData()); + } + } + return result; +} + +/** + * Read the image width from a PKM header + */ +static jint etc1_getWidth(JNIEnv *env, jclass clazz, + jobject header) { + jint result = 0; + BufferHelper headerB(env, header); + if (headerB.checkPointer("header") ){ + if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { + env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE"); + } else { + result = etc1_pkm_get_width((etc1_byte*) headerB.getData()); + } + } + return result; +} + +/** + * Read the image height from a PKM header + */ +static int etc1_getHeight(JNIEnv *env, jclass clazz, + jobject header) { + jint result = 0; + BufferHelper headerB(env, header); + if (headerB.checkPointer("header") ){ + if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { + env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE"); + } else { + result = etc1_pkm_get_height((etc1_byte*) headerB.getData()); + } + } + return result; +} + +/* * JNI registration */ @@ -721,6 +1014,8 @@ lookupClasses(JNIEnv* env) { env->FindClass("java/lang/IllegalArgumentException")); gUOEClass = (jclass) env->NewGlobalRef( env->FindClass("java/lang/UnsupportedOperationException")); + gAIOOBEClass = (jclass) env->NewGlobalRef( + env->FindClass("java/lang/ArrayIndexOutOfBoundsException")); } static JNINativeMethod gMatrixMethods[] = { @@ -742,6 +1037,18 @@ static JNINativeMethod gUtilsMethods[] = { { "native_texSubImage2D", "(IIIILandroid/graphics/Bitmap;II)I", (void*)util_texSubImage2D }, }; +static JNINativeMethod gEtc1Methods[] = { + { "encodeBlock", "(Ljava/nio/Buffer;ILjava/nio/Buffer;)V", (void*) etc1_encodeBlock }, + { "decodeBlock", "(Ljava/nio/Buffer;Ljava/nio/Buffer;)V", (void*) etc1_decodeBlock }, + { "getEncodedDataSize", "(II)I", (void*) etc1_getEncodedDataSize }, + { "encodeImage", "(Ljava/nio/Buffer;IIIILjava/nio/Buffer;)V", (void*) etc1_encodeImage }, + { "decodeImage", "(Ljava/nio/Buffer;Ljava/nio/Buffer;IIII)V", (void*) etc1_decodeImage }, + { "formatHeader", "(Ljava/nio/Buffer;II)V", (void*) etc1_formatHeader }, + { "isValid", "(Ljava/nio/Buffer;)Z", (void*) etc1_isValid }, + { "getWidth", "(Ljava/nio/Buffer;)I", (void*) etc1_getWidth }, + { "getHeight", "(Ljava/nio/Buffer;)I", (void*) etc1_getHeight }, +}; + typedef struct _ClassRegistrationInfo { const char* classPath; JNINativeMethod* methods; @@ -752,11 +1059,13 @@ static ClassRegistrationInfo gClasses[] = { {"android/opengl/Matrix", gMatrixMethods, NELEM(gMatrixMethods)}, {"android/opengl/Visibility", gVisiblityMethods, NELEM(gVisiblityMethods)}, {"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)}, + {"android/opengl/ETC1", gEtc1Methods, NELEM(gEtc1Methods)}, }; int register_android_opengl_classes(JNIEnv* env) { lookupClasses(env); + nativeClassInitBuffer(env); int result = 0; for (int i = 0; i < NELEM(gClasses); i++) { ClassRegistrationInfo* cri = &gClasses[i]; diff --git a/opengl/include/ETC1/etc1.h b/opengl/include/ETC1/etc1.h new file mode 100644 index 0000000..0d38905 --- /dev/null +++ b/opengl/include/ETC1/etc1.h @@ -0,0 +1,106 @@ +// 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. + +#ifndef __etc1_h__ +#define __etc1_h__ + +#define ETC1_ENCODED_BLOCK_SIZE 8 +#define ETC1_DECODED_BLOCK_SIZE 48 + +#ifndef ETC1_RGB8_OES +#define ETC1_RGB8_OES 0x8D64 +#endif + +typedef unsigned char etc1_byte; +typedef int etc1_bool; +typedef unsigned int etc1_uint32; + +#ifdef __cplusplus +extern "C" { +#endif + +// Encode a block of pixels. +// +// pIn is a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes 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). +// +// 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. +// +// pOut is an ETC1 compressed version of the data. + +void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 validPixelMask, etc1_byte* pOut); + +// Decode a block of pixels. +// +// pIn is an ETC1 compressed version of the data. +// +// pOut is a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes 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). + +void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut); + +// Return the size of the encoded image data (does not include size of PKM header). + +etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height); + +// Encode an entire image. +// pIn - pointer to the image data. Formatted such that +// pixel (x,y) is at pIn + pixelSize * x + stride * y; +// pOut - pointer to encoded data. Must be large enough to store entire encoded image. +// pixelSize can be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, 3 is a GL_BYTE RGB image. +// returns non-zero if there is an error. + +int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut); + +// Decode an entire image. +// pIn - pointer to encoded data. +// pOut - pointer to 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. +// pixelSize can be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, 3 is a GL_BYTE RGB image. +// returns non-zero if there is an error. + +int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut, + etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride); + +// Size of a PKM header, in bytes. + +#define ETC_PKM_HEADER_SIZE 16 + +// Format a PKM header + +void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height); + +// Check if a PKM header is correctly formatted. + +etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader); + +// Read the image width from a PKM header + +etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader); + +// Read the image height from a PKM header + +etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader); + +#ifdef __cplusplus +} +#endif + +#endif 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); + } + } +} diff --git a/opengl/libs/Android.mk b/opengl/libs/Android.mk index 6d20e80..7353385 100644 --- a/opengl/libs/Android.mk +++ b/opengl/libs/Android.mk @@ -120,3 +120,33 @@ ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true) endif include $(BUILD_SHARED_LIBRARY) + +############################################################################### +# Build the ETC1 host static library +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + ETC1/etc1.cpp \ +# + +LOCAL_LDLIBS := -lpthread -ldl +LOCAL_MODULE:= libETC1 + +include $(BUILD_HOST_STATIC_LIBRARY) + +############################################################################### +# Build the ETC1 device library +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + ETC1/etc1.cpp \ +# + +LOCAL_LDLIBS := -lpthread -ldl +LOCAL_MODULE:= libETC1 + +include $(BUILD_SHARED_LIBRARY) diff --git a/opengl/libs/ETC1/etc1.cpp b/opengl/libs/ETC1/etc1.cpp new file mode 100644 index 0000000..5ed2c3c --- /dev/null +++ b/opengl/libs/ETC1/etc1.cpp @@ -0,0 +1,670 @@ +// 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. + +#include <ETC1/etc1.h> + +#include <string.h> + +/* From http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt + + The number of bits that represent a 4x4 texel block is 64 bits if + <internalformat> is given by ETC1_RGB8_OES. + + The data for a block is a number of bytes, + + {q0, q1, q2, q3, q4, q5, q6, q7} + + where byte q0 is located at the lowest memory address and q7 at + the highest. The 64 bits specifying the block is then represented + by the following 64 bit integer: + + int64bit = 256*(256*(256*(256*(256*(256*(256*q0+q1)+q2)+q3)+q4)+q5)+q6)+q7; + + ETC1_RGB8_OES: + + a) bit layout in bits 63 through 32 if diffbit = 0 + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 + ----------------------------------------------- + | base col1 | base col2 | base col1 | base col2 | + | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| + ----------------------------------------------- + + 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + --------------------------------------------------- + | base col1 | base col2 | table | table |diff|flip| + | B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + --------------------------------------------------- + + + b) bit layout in bits 63 through 32 if diffbit = 1 + + 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 + ----------------------------------------------- + | base col1 | dcol 2 | base col1 | dcol 2 | + | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | + ----------------------------------------------- + + 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + --------------------------------------------------- + | base col 1 | dcol 2 | table | table |diff|flip| + | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + --------------------------------------------------- + + + c) bit layout in bits 31 through 0 (in both cases) + + 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 + ----------------------------------------------- + | most significant pixel index bits | + | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| + ----------------------------------------------- + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + -------------------------------------------------- + | least significant pixel index bits | + | p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + -------------------------------------------------- + + + Add table 3.17.2: Intensity modifier sets for ETC1 compressed textures: + + table codeword modifier table + ------------------ ---------------------- + 0 -8 -2 2 8 + 1 -17 -5 5 17 + 2 -29 -9 9 29 + 3 -42 -13 13 42 + 4 -60 -18 18 60 + 5 -80 -24 24 80 + 6 -106 -33 33 106 + 7 -183 -47 47 183 + + + Add table 3.17.3 Mapping from pixel index values to modifier values for + ETC1 compressed textures: + + pixel index value + --------------- + msb lsb resulting modifier value + ----- ----- ------------------------- + 1 1 -b (large negative value) + 1 0 -a (small negative value) + 0 0 a (small positive value) + 0 1 b (large positive value) + + + */ + +static const int kModifierTable[] = { +/* 0 */2, 8, -2, -8, +/* 1 */5, 17, -5, -17, +/* 2 */9, 29, -9, -29, +/* 3 */13, 42, -13, -42, +/* 4 */18, 60, -18, -60, +/* 5 */24, 80, -24, -80, +/* 6 */33, 106, -33, -106, +/* 7 */47, 183, -47, -183 }; + +static const int kLookup[8] = { 0, 1, 2, 3, -4, -3, -2, -1 }; + +static inline etc1_byte clamp(int x) { + return (etc1_byte) (x >= 0 ? (x < 255 ? x : 255) : 0); +} + +static +inline int convert4To8(int b) { + int c = b & 0xf; + return (c << 4) | c; +} + +static +inline int convert5To8(int b) { + int c = b & 0x1f; + return (c << 3) | (c >> 2); +} + +static +inline int convert6To8(int b) { + int c = b & 0x3f; + return (c << 2) | (c >> 4); +} + +static +inline int divideBy255(int d) { + return (d + 128 + (d >> 8)) >> 8; +} + +static +inline int convert8To4(int b) { + int c = b & 0xff; + return divideBy255(b * 15); +} + +static +inline int convert8To5(int b) { + int c = b & 0xff; + return divideBy255(b * 31); +} + +static +inline int convertDiff(int base, int diff) { + return convert5To8((0x1f & base) + kLookup[0x7 & diff]); +} + +static +void decode_subblock(etc1_byte* pOut, int r, int g, int b, const int* table, + etc1_uint32 low, bool second, bool flipped) { + int baseX = 0; + int baseY = 0; + if (second) { + if (flipped) { + baseY = 2; + } else { + baseX = 2; + } + } + for (int i = 0; i < 8; i++) { + int x, y; + if (flipped) { + x = baseX + (i >> 1); + y = baseY + (i & 1); + } else { + x = baseX + (i >> 2); + y = baseY + (i & 3); + } + int k = y + (x * 4); + int offset = ((low >> k) & 1) | ((low >> (k + 15)) & 2); + int delta = table[offset]; + etc1_byte* q = pOut + 3 * (x + 4 * y); + *q++ = clamp(r + delta); + *q++ = clamp(g + delta); + *q++ = clamp(b + delta); + } +} + +// Input is an ETC1 compressed version of the data. +// Output is a 4 x 4 square of 3-byte pixels in form R, G, B + +void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut) { + etc1_uint32 high = (pIn[0] << 24) | (pIn[1] << 16) | (pIn[2] << 8) | pIn[3]; + etc1_uint32 low = (pIn[4] << 24) | (pIn[5] << 16) | (pIn[6] << 8) | pIn[7]; + int r1, r2, g1, g2, b1, b2; + if (high & 2) { + // differential + int rBase = high >> 27; + int gBase = high >> 19; + int bBase = high >> 11; + r1 = convert5To8(rBase); + r2 = convertDiff(rBase, high >> 24); + g1 = convert5To8(gBase); + g2 = convertDiff(gBase, high >> 16); + b1 = convert5To8(bBase); + b2 = convertDiff(bBase, high >> 8); + } else { + // not differential + r1 = convert4To8(high >> 28); + r2 = convert4To8(high >> 24); + g1 = convert4To8(high >> 20); + g2 = convert4To8(high >> 16); + b1 = convert4To8(high >> 12); + b2 = convert4To8(high >> 8); + } + int tableIndexA = 7 & (high >> 5); + int tableIndexB = 7 & (high >> 2); + const int* tableA = kModifierTable + tableIndexA * 4; + const int* tableB = kModifierTable + tableIndexB * 4; + bool flipped = (high & 1) != 0; + decode_subblock(pOut, r1, g1, b1, tableA, low, false, flipped); + decode_subblock(pOut, r2, g2, b2, tableB, low, true, flipped); +} + +typedef struct { + etc1_uint32 high; + etc1_uint32 low; + etc1_uint32 score; // Lower is more accurate +} etc_compressed; + +static +inline void take_best(etc_compressed* a, const etc_compressed* b) { + if (a->score > b->score) { + *a = *b; + } +} + +static +void etc_average_colors_subblock(const etc1_byte* pIn, etc1_uint32 inMask, + etc1_byte* pColors, bool flipped, bool second) { + int r = 0; + int g = 0; + int b = 0; + + if (flipped) { + int by = 0; + if (second) { + by = 2; + } + for (int y = 0; y < 2; y++) { + int yy = by + y; + for (int x = 0; x < 4; x++) { + int i = x + 4 * yy; + if (inMask & (1 << i)) { + const etc1_byte* p = pIn + i * 3; + r += *(p++); + g += *(p++); + b += *(p++); + } + } + } + } else { + int bx = 0; + if (second) { + bx = 2; + } + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 2; x++) { + int xx = bx + x; + int i = xx + 4 * y; + if (inMask & (1 << i)) { + const etc1_byte* p = pIn + i * 3; + r += *(p++); + g += *(p++); + b += *(p++); + } + } + } + } + pColors[0] = (etc1_byte)((r + 4) >> 3); + pColors[1] = (etc1_byte)((g + 4) >> 3); + pColors[2] = (etc1_byte)((b + 4) >> 3); +} + +static +inline int square(int x) { + return x * x; +} + +static etc1_uint32 chooseModifier(const etc1_byte* pBaseColors, + const etc1_byte* pIn, etc1_uint32 *pLow, int bitIndex, + const int* pModifierTable) { + etc1_uint32 bestScore = ~0; + int bestIndex = 0; + int pixelR = pIn[0]; + int pixelG = pIn[1]; + int pixelB = pIn[2]; + int r = pBaseColors[0]; + int g = pBaseColors[1]; + int b = pBaseColors[2]; + for (int i = 0; i < 4; i++) { + int modifier = pModifierTable[i]; + int decodedG = clamp(g + modifier); + etc1_uint32 score = (etc1_uint32) (6 * square(decodedG - pixelG)); + if (score >= bestScore) { + continue; + } + int decodedR = clamp(r + modifier); + score += (etc1_uint32) (3 * square(decodedR - pixelR)); + if (score >= bestScore) { + continue; + } + int decodedB = clamp(b + modifier); + score += (etc1_uint32) square(decodedB - pixelB); + if (score < bestScore) { + bestScore = score; + bestIndex = i; + } + } + etc1_uint32 lowMask = (((bestIndex >> 1) << 16) | (bestIndex & 1)) + << bitIndex; + *pLow |= lowMask; + return bestScore; +} + +static +void etc_encode_subblock_helper(const etc1_byte* pIn, etc1_uint32 inMask, + etc_compressed* pCompressed, bool flipped, bool second, + const etc1_byte* pBaseColors, const int* pModifierTable) { + int score = pCompressed->score; + if (flipped) { + int by = 0; + if (second) { + by = 2; + } + for (int y = 0; y < 2; y++) { + int yy = by + y; + for (int x = 0; x < 4; x++) { + int i = x + 4 * yy; + if (inMask & (1 << i)) { + score += chooseModifier(pBaseColors, pIn + i * 3, + &pCompressed->low, yy + x * 4, pModifierTable); + } + } + } + } else { + int bx = 0; + if (second) { + bx = 2; + } + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 2; x++) { + int xx = bx + x; + int i = xx + 4 * y; + if (inMask & (1 << i)) { + score += chooseModifier(pBaseColors, pIn + i * 3, + &pCompressed->low, y + xx * 4, pModifierTable); + } + } + } + } + pCompressed->score = score; +} + +static bool inRange4bitSigned(int color) { + return color >= -4 && color <= 3; +} + +static void etc_encodeBaseColors(etc1_byte* pBaseColors, + const etc1_byte* pColors, etc_compressed* pCompressed) { + int r1, g1, b1, r2, g2, b2; // 8 bit base colors for sub-blocks + bool differential; + { + int r51 = convert8To5(pColors[0]); + int g51 = convert8To5(pColors[1]); + int b51 = convert8To5(pColors[2]); + int r52 = convert8To5(pColors[3]); + int g52 = convert8To5(pColors[4]); + int b52 = convert8To5(pColors[5]); + + r1 = convert5To8(r51); + g1 = convert5To8(g51); + b1 = convert5To8(b51); + + int dr = r52 - r51; + int dg = g52 - g51; + int db = b52 - b51; + + differential = inRange4bitSigned(dr) && inRange4bitSigned(dg) + && inRange4bitSigned(db); + if (differential) { + r2 = convert5To8(r51 + dr); + g2 = convert5To8(g51 + dg); + b2 = convert5To8(b51 + db); + pCompressed->high |= (r51 << 27) | ((7 & dr) << 24) | (g51 << 19) + | ((7 & dg) << 16) | (b51 << 11) | ((7 & db) << 8) | 2; + } + } + + if (!differential) { + int r41 = convert8To4(pColors[0]); + int g41 = convert8To4(pColors[1]); + int b41 = convert8To4(pColors[2]); + int r42 = convert8To4(pColors[3]); + int g42 = convert8To4(pColors[4]); + int b42 = convert8To4(pColors[5]); + r1 = convert4To8(r41); + g1 = convert4To8(g41); + b1 = convert4To8(b41); + r2 = convert4To8(r42); + g2 = convert4To8(g42); + b2 = convert4To8(b42); + pCompressed->high |= (r41 << 28) | (r42 << 24) | (g41 << 20) | (g42 + << 16) | (b41 << 12) | (b42 << 8); + } + pBaseColors[0] = r1; + pBaseColors[1] = g1; + pBaseColors[2] = b1; + pBaseColors[3] = r2; + pBaseColors[4] = g2; + pBaseColors[5] = b2; +} + +static +void etc_encode_block_helper(const etc1_byte* pIn, etc1_uint32 inMask, + const etc1_byte* pColors, etc_compressed* pCompressed, bool flipped) { + pCompressed->score = ~0; + pCompressed->high = (flipped ? 1 : 0); + pCompressed->low = 0; + + etc1_byte pBaseColors[6]; + + etc_encodeBaseColors(pBaseColors, pColors, pCompressed); + + int originalHigh = pCompressed->high; + + const int* pModifierTable = kModifierTable; + for (int i = 0; i < 8; i++, pModifierTable += 4) { + etc_compressed temp; + temp.score = 0; + temp.high = originalHigh | (i << 5); + temp.low = 0; + etc_encode_subblock_helper(pIn, inMask, &temp, flipped, false, + pBaseColors, pModifierTable); + take_best(pCompressed, &temp); + } + pModifierTable = kModifierTable; + etc_compressed firstHalf = *pCompressed; + for (int i = 0; i < 8; i++, pModifierTable += 4) { + etc_compressed temp; + temp.score = firstHalf.score; + temp.high = firstHalf.high | (i << 2); + temp.low = firstHalf.low; + etc_encode_subblock_helper(pIn, inMask, &temp, flipped, true, + pBaseColors + 3, pModifierTable); + if (i == 0) { + *pCompressed = temp; + } else { + take_best(pCompressed, &temp); + } + } +} + +static void writeBigEndian(etc1_byte* pOut, etc1_uint32 d) { + pOut[0] = (etc1_byte)(d >> 24); + pOut[1] = (etc1_byte)(d >> 16); + pOut[2] = (etc1_byte)(d >> 8); + pOut[3] = (etc1_byte) d; +} + +// Input is a 4 x 4 square of 3-byte pixels in form R, G, B +// inmask is a 16-bit mask where bit (1 << (x + y * 4)) tells whether the corresponding (x,y) +// pixel is valid or not. Invalid pixel color values are ignored when compressing. +// Output is an ETC1 compressed version of the data. + +void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 inMask, + etc1_byte* pOut) { + etc1_byte colors[6]; + etc1_byte flippedColors[6]; + etc_average_colors_subblock(pIn, inMask, colors, false, false); + etc_average_colors_subblock(pIn, inMask, colors + 3, false, true); + etc_average_colors_subblock(pIn, inMask, flippedColors, true, false); + etc_average_colors_subblock(pIn, inMask, flippedColors + 3, true, true); + + etc_compressed a, b; + etc_encode_block_helper(pIn, inMask, colors, &a, false); + etc_encode_block_helper(pIn, inMask, flippedColors, &b, true); + take_best(&a, &b); + writeBigEndian(pOut, a.high); + writeBigEndian(pOut + 4, a.low); +} + +// Return the size of the encoded image data (does not include size of PKM header). + +etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height) { + return (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1; +} + +// Encode an entire image. +// pIn - pointer to the image data. Formatted such that the Red component of +// pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset; +// pOut - pointer to encoded data. Must be large enough to store entire encoded image. + +int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut) { + if (pixelSize < 2 || pixelSize > 3) { + return -1; + } + static const unsigned short kYMask[] = { 0x0, 0xf, 0xff, 0xfff, 0xffff }; + static const unsigned short kXMask[] = { 0x0, 0x1111, 0x3333, 0x7777, + 0xffff }; + etc1_byte block[ETC1_DECODED_BLOCK_SIZE]; + etc1_byte encoded[ETC1_ENCODED_BLOCK_SIZE]; + + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + + for (etc1_uint32 y = 0; y < encodedHeight; y += 4) { + etc1_uint32 yEnd = height - y; + if (yEnd > 4) { + yEnd = 4; + } + int ymask = kYMask[yEnd]; + for (etc1_uint32 x = 0; x < encodedWidth; x += 4) { + etc1_uint32 xEnd = width - x; + if (xEnd > 4) { + xEnd = 4; + } + int mask = ymask & kXMask[xEnd]; + for (etc1_uint32 cy = 0; cy < yEnd; cy++) { + etc1_byte* q = block + (cy * 4) * 3; + const etc1_byte* p = pIn + pixelSize * x + stride * (y + cy); + if (pixelSize == 3) { + memcpy(q, p, xEnd * 3); + } else { + for (etc1_uint32 cx = 0; cx < xEnd; cx++) { + int pixel = (p[1] << 8) | p[0]; + *q++ = convert5To8(pixel >> 11); + *q++ = convert6To8(pixel >> 5); + *q++ = convert5To8(pixel); + p += pixelSize; + } + } + } + etc1_encode_block(block, mask, encoded); + memcpy(pOut, encoded, sizeof(encoded)); + pOut += sizeof(encoded); + } + } + return 0; +} + +// Decode an entire image. +// pIn - pointer to encoded data. +// pOut - pointer to the image data. Will be written such that the Red component of +// pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset. Must be +// large enough to store entire image. + + +int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut, + etc1_uint32 width, etc1_uint32 height, + etc1_uint32 pixelSize, etc1_uint32 stride) { + if (pixelSize < 2 || pixelSize > 3) { + return -1; + } + etc1_byte block[ETC1_DECODED_BLOCK_SIZE]; + + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + + for (etc1_uint32 y = 0; y < encodedHeight; y += 4) { + etc1_uint32 yEnd = height - y; + if (yEnd > 4) { + yEnd = 4; + } + for (etc1_uint32 x = 0; x < encodedWidth; x += 4) { + etc1_uint32 xEnd = width - x; + if (xEnd > 4) { + xEnd = 4; + } + etc1_decode_block(pIn, block); + pIn += ETC1_ENCODED_BLOCK_SIZE; + for (etc1_uint32 cy = 0; cy < yEnd; cy++) { + const etc1_byte* q = block + (cy * 4) * 3; + etc1_byte* p = pOut + pixelSize * x + stride * (y + cy); + if (pixelSize == 3) { + memcpy(p, q, xEnd * 3); + } else { + for (etc1_uint32 cx = 0; cx < xEnd; cx++) { + etc1_byte r = *q++; + etc1_byte g = *q++; + etc1_byte b = *q++; + etc1_uint32 pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + *p++ = (etc1_byte) pixel; + *p++ = (etc1_byte) (pixel >> 8); + } + } + } + } + } + return 0; +} + +static const char kMagic[] = { 'P', 'K', 'M', ' ', '1', '0' }; + +static const etc1_uint32 ETC1_PKM_FORMAT_OFFSET = 6; +static const etc1_uint32 ETC1_PKM_ENCODED_WIDTH_OFFSET = 8; +static const etc1_uint32 ETC1_PKM_ENCODED_HEIGHT_OFFSET = 10; +static const etc1_uint32 ETC1_PKM_WIDTH_OFFSET = 12; +static const etc1_uint32 ETC1_PKM_HEIGHT_OFFSET = 14; + +static const etc1_uint32 ETC1_RGB_NO_MIPMAPS = 0; + +static void writeBEUint16(etc1_byte* pOut, etc1_uint32 data) { + pOut[0] = (etc1_byte) (data >> 8); + pOut[1] = (etc1_byte) data; +} + +static etc1_uint32 readBEUint16(const etc1_byte* pIn) { + return (pIn[0] << 8) | pIn[1]; +} + +// Format a PKM header + +void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height) { + memcpy(pHeader, kMagic, sizeof(kMagic)); + etc1_uint32 encodedWidth = (width + 3) & ~3; + etc1_uint32 encodedHeight = (height + 3) & ~3; + writeBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET, ETC1_RGB_NO_MIPMAPS); + writeBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET, encodedWidth); + writeBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET, encodedHeight); + writeBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET, width); + writeBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET, height); +} + +// Check if a PKM header is correctly formatted. + +etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader) { + if (memcmp(pHeader, kMagic, sizeof(kMagic))) { + return false; + } + etc1_uint32 format = readBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET); + etc1_uint32 encodedWidth = readBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET); + etc1_uint32 encodedHeight = readBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET); + etc1_uint32 width = readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET); + etc1_uint32 height = readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET); + return format == ETC1_RGB_NO_MIPMAPS && + encodedWidth >= width && encodedWidth - width < 4 && + encodedHeight >= height && encodedHeight - height < 4; +} + +// Read the image width from a PKM header + +etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader) { + return readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET); +} + +// Read the image height from a PKM header + +etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader){ + return readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET); +} |