imageBuffer to DDS according to the default attributes.
+ * The bytes in imageBuffer must be readable by {@link BitmapFactory#decodeStream
+ * BitmapFactory.decodeStream}. Once the image data is read, this is equivalent to calling {@link #compressImage(Bitmap)} with the Bitmap created by
+ * BitmapFactory. This returns null if the bytes
+ * inimageBuffer are not in a format understood by BitmapFactory.
+ *
+ * @param imageBuffer
+ * image file data to convert to the DDS file format.
+ * @return little endian ordered ByteBuffer containing the DDS file bytes, or null if the imageBuffer is not in a format understood by
+ * BitmapFactory.
+ * @throws IllegalArgumentException
+ * if imageBuffer is null.
+ */
+ public static java.nio.ByteBuffer compressImageBuffer(java.nio.ByteBuffer imageBuffer) {
+ if (imageBuffer == null) {
+ String message = Logging.getMessage("nullValue.Image");
+ Logging.error(message);
+ throw new IllegalArgumentException(message);
+ }
+
+ return compressImageBuffer(imageBuffer, ETC1DDSCompressor.getDefaultCompressionAttributes());
+ }
+
+ /**
+ * Returns the default compression attributes. The default DXT compression attributes are defined as follows:
+ * | Attribute | + *Value | + *
|---|---|
| Build Mipmaps | + *true | + *
| Premultiply Alpha | + *true | + *
| DXT Format | + *Let DDSCompressor choose optimal format. | + *
| Enable DXT1 Alpha | + *false | + *
| DXT1 Alpha Threshold | + *128 | + *
| Compression Algorithm | + *Euclidean Distance | + *
imageBuffer to DDS according to the specified
+ * compression attributes. The bytes in imageBuffer must be readable by {@link BitmapFactory#decodeStream
+ * BitmapFactory.decodeStream}. Once the image data is read, this is equivalent to
+ * calling {@link #compressImage(Bitmap, DXTCompressionAttributes)} with the Bitmap created with the specified attributes. This returns null if
+ * the bytes in imageBuffer are not in a format
+ * understood by BitmapFactory.
+ *
+ * @param imageBuffer
+ * image file data to convert to the DDS file format.
+ * @param attributes
+ * attributes that control the compression.
+ * @return little endian ordered ByteBuffer containing the DDS file bytes, or null if the imageBuffer is not in a format understood by
+ * BitmapFactory.
+ * @throws IllegalArgumentException
+ * if either imageBuffer or attributes are null.
+ */
+ public static java.nio.ByteBuffer compressImageBuffer(java.nio.ByteBuffer imageBuffer, DXTCompressionAttributes attributes) {
+ if (imageBuffer == null) {
+ String message = Logging.getMessage("nullValue.Image");
+ Logging.error(message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (attributes == null) {
+ String message = Logging.getMessage("nullValue.AttributesIsNull");
+ Logging.error(message);
+ throw new IllegalArgumentException(message);
+ }
+
+ java.io.InputStream inputStream = WWIO.getInputStreamFromByteBuffer(imageBuffer);
+ return compressImageStream(inputStream, attributes);
+ }
+
+ /**
+ * Convenience method to convert the specified image stream to DDS according to the specified
+ * compression attributes. The stream must be readable by {@link BitmapFactory#decodeStream BitmapFactory.decodeStream}. Once the
+ * stream is read, this is equivalent
+ * to calling {@link #compressImage(Bitmap, DXTCompressionAttributes)} with the Bitmap created with the specified attributes. This returns null
+ * if the stream is not in a format understood by
+ * BitmapFactory.
+ *
+ * @param inputStream
+ * image stream to convert to the DDS file format.
+ * @param attributes
+ * attributes that control the compression.
+ * @return little endian ordered ByteBuffer containing the DDS file bytes, or null if the stream is not
+ * in a format understood by BitmapFactory.
+ * @throws IllegalArgumentException
+ * if either the stream or the attributes are null.
+ */
+ public static java.nio.ByteBuffer compressImageStream(java.io.InputStream inputStream, DXTCompressionAttributes attributes) {
+ if (inputStream == null) {
+ String message = Logging.getMessage("nullValue.InputStreamIsNull");
+ Logging.error(message);
+ throw new IllegalArgumentException(message);
+ }
+
+ if (attributes == null) {
+ String message = Logging.getMessage("nullValue.AttributesIsNull");
+ Logging.error(message);
+ throw new IllegalArgumentException(message);
+ }
+
+ Options opts = new BitmapFactory.Options();
+ //opts.inPreferQualityOverSpeed = false;
+ opts.inPreferredConfig = Config.ARGB_8888;
+ Bitmap image = BitmapFactory.decodeStream(inputStream, null, opts);
+
+ if (image == null) {
+ return null;
+ }
+
+ ETC1DDSCompressor compressor = new ETC1DDSCompressor();
+ return compressor.compressImage(image, attributes);
+ }
+
+ /**
+ * Converts the specified image to DDS according to the attributes. If the caller
+ * specified a DXT format in the attributes, then we return a compressor matching that format. Otherwise, we choose
+ * one automatically from the image type. If no choice can be made from the image type, we default to using a DXT3
+ * compressor.
+ *
+ * @param image
+ * image to convert to the DDS file format.
+ * @param attributes
+ * attributes that control the compression.
+ * @return buffer little endian ordered ByteBuffer containing the dds file bytes.
+ * @throws IllegalArgumentException
+ * if either image or attributes are null, or if image has non power of two dimensions.
+ */
+ public java.nio.ByteBuffer compressImage(Bitmap image, DXTCompressionAttributes attributes) {
+ if (image == null) {
+ String message = Logging.getMessage("nullValue.ImageIsNull");
+ Logging.error(message);
+ throw new IllegalArgumentException(message);
+ }
+ if (!WWMath.isPowerOfTwo(image.getWidth()) || !WWMath.isPowerOfTwo(image.getHeight())) {
+ String message = Logging.getMessage("generic.InvalidImageSize", image.getWidth(), image.getHeight());
+ Logging.error(message);
+ throw new IllegalArgumentException(message);
+ }
+ if (attributes == null) {
+ String message = Logging.getMessage("nullValue.AttributesIsNull");
+ Logging.error(message);
+ throw new IllegalArgumentException(message);
+ }
+
+ DXTCompressor compressor = this.getDXTCompressor(image, attributes);
+ return this.doCompressImage(compressor, image, attributes);
+ }
+
+ protected DDSHeader createDDSHeader(DXTCompressor compressor, Bitmap image, DXTCompressionAttributes attributes) {
+ DDSPixelFormat pixelFormat = new DDSPixelFormat();
+ pixelFormat.setFlags(pixelFormat.getFlags() | DDSConstants.DDPF_FOURCC);
+ pixelFormat.setFourCC(compressor.getDXTFormat());
+
+ DDSHeader header = new DDSHeader();
+ header.setFlags(header.getFlags() | DDSConstants.DDSD_WIDTH | DDSConstants.DDSD_HEIGHT | DDSConstants.DDSD_LINEARSIZE | DDSConstants.DDSD_PIXELFORMAT
+ | DDSConstants.DDSD_CAPS);
+ header.setWidth(image.getWidth());
+ header.setHeight(image.getHeight());
+ header.setLinearSize(compressor.getCompressedSize(image, attributes));
+ header.setPixelFormat(pixelFormat);
+ header.setCaps(header.getCaps() | DDSConstants.DDSCAPS_TEXTURE);
+
+ return header;
+ }
+
+ /**
+ * Documentation on the DDS header format is available at http://msdn.microsoft.com/en-us/library/bb943982(VS.85).aspx.
+ *
+ * @param header
+ * header structure to write.
+ * @param buffer
+ * buffer that receives the header structure bytes.
+ */
+ protected void writeDDSHeader(DDSHeader header, java.nio.ByteBuffer buffer) {
+ int pos = buffer.position();
+
+ buffer.putInt(header.getSize()); // dwSize
+ buffer.putInt(header.getFlags()); // dwFlags
+ buffer.putInt(header.getHeight()); // dwHeight
+ buffer.putInt(header.getWidth()); // dwWidth
+ buffer.putInt(header.getLinearSize()); // dwLinearSize
+ buffer.putInt(header.getDepth()); // dwDepth
+ buffer.putInt(header.getMipMapCount()); // dwMipMapCount
+ buffer.position(buffer.position() + 44); // dwReserved1[11] (unused)
+ this.writeDDSPixelFormat(header.getPixelFormat(), buffer); // ddpf
+ buffer.putInt(header.getCaps()); // dwCaps
+ buffer.putInt(header.getCaps2()); // dwCaps2
+ buffer.putInt(header.getCaps3()); // dwCaps3
+ buffer.putInt(header.getCaps4()); // dwCaps4
+ buffer.position(buffer.position() + 4); // dwReserved2 (unused)
+
+ buffer.position(pos + header.getSize());
+ }
+
+ /**
+ * Documentation on the DDS pixel format is available at http://msdn.microsoft.com/en-us/library/bb943984(VS.85).aspx.
+ *
+ * @param pixelFormat
+ * pixel format structure to write.
+ * @param buffer
+ * buffer that receives the pixel format structure bytes.
+ */
+ protected void writeDDSPixelFormat(DDSPixelFormat pixelFormat, java.nio.ByteBuffer buffer) {
+ int pos = buffer.position();
+
+ buffer.putInt(pixelFormat.getSize()); // dwSize
+ buffer.putInt(pixelFormat.getFlags()); // dwFlags
+ buffer.putInt(pixelFormat.getFourCC()); // dwFourCC
+ buffer.putInt(pixelFormat.getRGBBitCount()); // dwRGBBitCount
+ buffer.putInt(pixelFormat.getRBitMask()); // dwRBitMask
+ buffer.putInt(pixelFormat.getGBitMask()); // dwGBitMask
+ buffer.putInt(pixelFormat.getBBitMask()); // dwBBitMask
+ buffer.putInt(pixelFormat.getABitMask()); // dwABitMask
+
+ buffer.position(pos + pixelFormat.getSize());
+ }
+
+ protected java.nio.ByteBuffer createBuffer(int size) {
+ return java.nio.ByteBuffer.allocateDirect(size);
+ }
+
+ protected ByteBuffer doCompressImage(DXTCompressor compressor, Bitmap image, DXTCompressionAttributes attributes) {
+ // Create the DDS header structure that describes the specified image, compressor, and compression attributes.
+ DDSHeader header = this.createDDSHeader(compressor, image, attributes);
+
+ // Compute the DDS file size and mip map levels. If the attributes specify to build mip maps, then we compute
+ // the total file size including mip maps, create a chain of mip map images, and update the DDS header to
+ // describe the number of mip map levels. Otherwise, we compute the file size for a single image and do nothing
+ // to the DDS header.
+ Bitmap[] mipMapLevels = null;
+ int fileSize = 4 + header.getSize();
+
+ if (attributes.isBuildMipmaps()) {
+ mipMapLevels = this.buildMipMaps(image, attributes);
+
+ int maxLevel = ImageUtil.getMaxMipmapLevel(image.getWidth(), image.getHeight());
+
+ if(attributes.getDXTFormat() == ETCConstants.D3DFMT_ETC1) {
+ // mipmaps are computed with renderscript API
+ int width = image.getWidth();
+ int height = image.getHeight();
+ for (int i = 1; i < maxLevel; i++) {
+ fileSize += RsETC1.getEncodedDataSize(width, height);
+ width /= 2;
+ height /= 2;
+ }
+ //System.out.println("fileSize : "+fileSize);
+ } else {
+ for (Bitmap mipMapImage : mipMapLevels) {
+ fileSize += compressor.getCompressedSize(mipMapImage, attributes);
+ }
+ }
+
+ header.setFlags(header.getFlags() | DDSConstants.DDSD_MIPMAPCOUNT);
+ header.setMipMapCount(1+maxLevel);
+ } else {
+ fileSize += compressor.getCompressedSize(image, attributes);
+
+ header.setFlags(header.getFlags() | DDSConstants.DDSD_MIPMAPCOUNT);
+ header.setMipMapCount(1);
+ }
+
+ // Create a little endian buffer that holds the bytes of the DDS file.
+ java.nio.ByteBuffer buffer = this.createBuffer(fileSize);
+ buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
+
+ // Write the DDS magic number and DDS header to the file.
+ buffer.putInt(DDSConstants.MAGIC);
+ this.writeDDSHeader(header, buffer);
+
+ // Write the compressed DXT blocks to the DDS file. If the attributes specify to build mip maps, then we write
+ // each mip map level to the DDS file, starting with level 0 and ending with level N. Otherwise, we write a
+ // single image to the DDS file.
+ if (mipMapLevels == null) {
+ compressor.compressImage(image, attributes, buffer);
+ } else {
+ for (Bitmap mipMapImage : mipMapLevels) {
+ compressor.compressImage(mipMapImage, attributes, buffer);
+ }
+ }
+
+ buffer.rewind();
+ return buffer;
+ }
+
+}
diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETCConstants.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETCConstants.java
new file mode 100644
index 0000000..8b28fcb
--- /dev/null
+++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/dds/ETCConstants.java
@@ -0,0 +1,7 @@
+package nicastel.renderscripttexturecompressor.dds;
+
+import gov.nasa.worldwind.util.dds.DDSConstants;
+
+public class ETCConstants {
+ public static final int D3DFMT_ETC1 = DDSConstants.makeFourCC('E', 'T', 'C', '1');
+}
diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java
new file mode 100644
index 0000000..de301a9
--- /dev/null
+++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1.java
@@ -0,0 +1,113 @@
+package nicastel.renderscripttexturecompressor.etc1.rs;
+
+import java.nio.ByteBuffer;
+
+import android.graphics.Bitmap;
+import android.support.v8.renderscript.Allocation;
+import android.support.v8.renderscript.Allocation.MipmapControl;
+import android.support.v8.renderscript.Element;
+import android.support.v8.renderscript.RenderScript;
+
+public class RsETC1 {
+ // Copyright 2009 Google Inc.
+ // Copyright 2011 Nicolas CASTEL
+ //
+ // 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.
+
+ /**
+ * Size in bytes of an encoded block.
+ */
+ public static final int ENCODED_BLOCK_SIZE = 8;
+
+ /**
+ * Size in pixel of a decoded block.
+ */
+ public static final int DECODED_BLOCK_SIZE = 48;
+
+ /**
+ * Accepted by the internalformat parameter of glCompressedTexImage2D.
+ */
+ public static final int ETC1_RGB8_OES = 0x8D64;
+
+ /**
+ * Return the size of the encoded image data (does not include size of PKM
+ * header).
+ */
+ public static int getEncodedDataSize(int width, int 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.
+ * @param script
+ * @param containMipmaps
+ */
+ public static int encodeImage(RenderScript rs, ScriptC_etc1compressor script, Allocation aIn, int width, int height,
+ int pixelSize, int stride, ByteBuffer compressedImage, boolean containMipmaps) {
+
+ long tInitArray = java.lang.System.currentTimeMillis();
+
+ script.set_height(height);
+ script.set_width(width);
+ script.set_containMipmaps(containMipmaps);
+ script.set_pixelSize(pixelSize);
+
+ if (pixelSize < 2 || pixelSize > 4) {
+ return -1;
+ }
+
+ // int iOut = 0;
+
+ int size = Math.max(aIn.getBytesSize() / ((DECODED_BLOCK_SIZE/3)*pixelSize), 1);
+ Allocation aout = Allocation.createSized(rs, Element.U16_4(rs), size);
+
+ tInitArray = java.lang.System.currentTimeMillis() - tInitArray;
+ //System.out.println("tInitArray : "+tInitArray+" ms");
+
+ long tFillAlloc = java.lang.System.currentTimeMillis();
+ script.bind_pInA(aIn);
+ tFillAlloc = java.lang.System.currentTimeMillis() - tFillAlloc;
+ //System.out.println("tFillAlloc : "+tFillAlloc+" ms");
+
+ long tExec = java.lang.System.currentTimeMillis();
+ script.forEach_root(aout);
+ tExec = java.lang.System.currentTimeMillis() - tExec;
+ //System.out.println("tExec : "+tExec+" ms");
+
+ long tFillOut = java.lang.System.currentTimeMillis();
+
+ short[] arrayOut3Temp = new short[4*size];
+ aout.copyTo(arrayOut3Temp);
+ aout.destroy();
+
+ Allocation aout2 = Allocation.createSized(rs, Element.U8(rs), 8*size);
+ aout2.copyFromUnchecked(arrayOut3Temp);
+
+ aout2.copyTo(compressedImage.array());
+ aout2.destroy();
+
+ tFillOut = java.lang.System.currentTimeMillis() - tFillOut;
+
+ compressedImage.rewind();
+
+ //System.out.println("tFillOut : "+tFillOut+" ms");
+
+ return 0;
+ }
+
+
+
+}
diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java
new file mode 100644
index 0000000..0d2bf21
--- /dev/null
+++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/RsETC1Util.java
@@ -0,0 +1,229 @@
+package nicastel.renderscripttexturecompressor.etc1.rs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nicastel.renderscripttexturecompressor.etc1.rs.ScriptC_etc1compressor;
+import android.opengl.ETC1;
+import android.opengl.GLES10;
+import android.support.v8.renderscript.Allocation;
+import android.support.v8.renderscript.Element;
+import android.support.v8.renderscript.RenderScript;
+
+/**
+ * Utility methods for using ETC1 compressed textures.
+ *
+ */
+public class RsETC1Util {
+ /**
+ * 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, RsETC1.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((ByteBuffer) 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.
+ * @return 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] == RsETC1.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(RenderScript rs, ScriptC_etc1compressor script, Buffer input, int width, int height, int pixelSize, int stride){
+ int encodedImageSize = RsETC1.getEncodedDataSize(width, height);
+ System.out.println("encodedImageSize : "+encodedImageSize);
+ ByteBuffer compressedImage = ByteBuffer.allocateDirect(encodedImageSize).
+ order(ByteOrder.nativeOrder());
+ Allocation p00 = Allocation.createSized(rs, Element.U8(rs), width * height * pixelSize);
+ p00.copyFrom(((ByteBuffer)input).array());
+
+ // TODO : there is a bug in the android sdk :
+ // ETC1.encodeImage((ByteBuffer) input, width, height, 3, stride, compressedImage); should be
+ RsETC1.encodeImage(rs, script, p00, width, height, pixelSize, stride, compressedImage, false);
+ p00.destroy();
+
+ compressedImage.rewind();
+ 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();
+ dataBuffer.rewind();
+ System.out.println(dataBuffer.remaining());
+ 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);
+ header.position(0);
+ byte[] ioBuffer = new byte[4096];
+ header.get(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE);
+ output.write(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE);
+ while (dataBuffer.remaining()>0) {
+ int chunkSize = Math.min(ioBuffer.length, dataBuffer.remaining());
+ dataBuffer.get(ioBuffer, 0, chunkSize);
+ output.write(ioBuffer, 0, chunkSize);
+ }
+ } finally {
+ dataBuffer.position(originalPosition);
+ }
+ }
+}
diff --git a/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs
new file mode 100644
index 0000000..f9bd686
--- /dev/null
+++ b/WorldWindAndroid/src/nicastel/renderscripttexturecompressor/etc1/rs/etc1compressor.rs
@@ -0,0 +1,643 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs_fp_imprecise
+#pragma rs java_package_name(nicastel.renderscripttexturecompressor.etc1.rs)
+
+
+// DXT compressor copied here since I found no way to import properly
+static inline uint8_t AV_RL16(const uint8_t * x) {
+ return ((((const uint8_t*)(x))[1] << 8) |
+ ((const uint8_t*)(x))[0]);
+}
+
+static inline uint8_t AV_RL32(const uint8_t * x) {
+ return ((((const uint8_t*)(x))[3] << 24) |
+ (((const uint8_t*)(x))[2] << 16) |
+ (((const uint8_t*)(x))[1] << 8) |
+ ((const uint8_t*)(x))[0]);
+}
+
+static inline uint8_t AV_RL64(const uint8_t * x) {
+ return (((uint64_t)((const uint8_t*)(x))[7] << 56) |
+ ((uint64_t)((const uint8_t*)(x))[6] << 48) |
+ ((uint64_t)((const uint8_t*)(x))[5] << 40) |
+ ((uint64_t)((const uint8_t*)(x))[4] << 32) |
+ ((uint64_t)((const uint8_t*)(x))[3] << 24) |
+ ((uint64_t)((const uint8_t*)(x))[2] << 16) |
+ ((uint64_t)((const uint8_t*)(x))[1] << 8) |
+ (uint64_t)((const uint8_t*)(x))[0]);
+}
+
+static inline void dxt1_decode_pixels(const uint8_t *s, uint32_t *d,
+ unsigned int qstride, unsigned int flag,uint64_t alpha) {
+ unsigned int x, y, c0, c1, a = (!flag * 255) << 24;
+ unsigned int rb0, rb1, rb2, rb3, g0, g1, g2, g3;
+ uint32_t colors[4], pixels;
+
+ c0 = AV_RL16(s);
+ c1 = AV_RL16(s+2);
+
+ rb0 = (c0<<3 | c0<<8) & 0xf800f8;
+ rb1 = (c1<<3 | c1<<8) & 0xf800f8;
+ rb0 += (rb0>>5) & 0x070007;
+ rb1 += (rb1>>5) & 0x070007;
+ g0 = (c0 <<5) & 0x00fc00;
+ g1 = (c1 <<5) & 0x00fc00;
+ g0 += (g0 >>6) & 0x000300;
+ g1 += (g1 >>6) & 0x000300;
+
+ colors[0] = rb0 + g0 + a;
+ colors[1] = rb1 + g1 + a;
+
+ if (c0 > c1 || flag) {
+ rb2 = (((2*rb0+rb1) * 21) >> 6) & 0xff00ff;
+ rb3 = (((2*rb1+rb0) * 21) >> 6) & 0xff00ff;
+ g2 = (((2*g0 +g1 ) * 21) >> 6) & 0x00ff00;
+ g3 = (((2*g1 +g0 ) * 21) >> 6) & 0x00ff00;
+ colors[3] = rb3 + g3 + a;
+ } else {
+ rb2 = ((rb0+rb1) >> 1) & 0xff00ff;
+ g2 = ((g0 +g1 ) >> 1) & 0x00ff00;
+ colors[3] = 0;
+ }
+
+ colors[2] = rb2 + g2 + a;
+
+ pixels = AV_RL32(s+4);
+ for (y=0; y<4; y++) {
+ for (x=0; x<4; x++) {
+ a = (alpha & 0x0f) << 28;
+ a += a >> 4;
+ d[x] = a + colors[pixels&3];
+ pixels >>= 2;
+ alpha >>= 4;
+ }
+ d += qstride;
+ }
+ }
+
+typedef unsigned char etc1_byte;
+typedef int etc1_bool;
+typedef unsigned int etc1_uint32;
+
+typedef struct EtcCompressed {
+ etc1_uint32 high;
+ etc1_uint32 low;
+ etc1_uint32 score; // Lower is more accurate
+} etc_compressed;
+
+/* 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
+