com.badlogic.gdx.graphics.glutils.ETC1.java Source code

Java tutorial

Introduction

Here is the source code for com.badlogic.gdx.graphics.glutils.ETC1.java

Source

/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * 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 com.badlogic.gdx.graphics.glutils;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.nio.ByteBuffer;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.BufferUtils;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.StreamUtils;

/** Class for encoding and decoding ETC1 compressed images. Also provides methods to add a PKM header.
 * @author mzechner */
public class ETC1 {
    /** The PKM header size in bytes **/
    public static int PKM_HEADER_SIZE = 16;
    public static int ETC1_RGB8_OES = 0x00008d64;

    /** Class for storing ETC1 compressed image data.
     * @author mzechner */
    public final static class ETC1Data implements Disposable {
        /** the width in pixels **/
        public final int width;
        /** the height in pixels **/
        public final int height;
        /** the optional PKM header and compressed image data **/
        public final ByteBuffer compressedData;
        /** the offset in bytes to the actual compressed data. Might be 16 if this contains a PKM header, 0 otherwise **/
        public final int dataOffset;

        public ETC1Data(int width, int height, ByteBuffer compressedData, int dataOffset) {
            this.width = width;
            this.height = height;
            this.compressedData = compressedData;
            this.dataOffset = dataOffset;
            checkNPOT();
        }

        public ETC1Data(FileHandle pkmFile) {
            byte[] buffer = new byte[1024 * 10];
            DataInputStream in = null;
            try {
                in = new DataInputStream(new BufferedInputStream(new GZIPInputStream(pkmFile.read())));
                int fileSize = in.readInt();
                compressedData = BufferUtils.newUnsafeByteBuffer(fileSize);
                int readBytes = 0;
                while ((readBytes = in.read(buffer)) != -1) {
                    compressedData.put(buffer, 0, readBytes);
                }
                compressedData.position(0);
                compressedData.limit(compressedData.capacity());
            } catch (Exception e) {
                throw new GdxRuntimeException("Couldn't load pkm file '" + pkmFile + "'", e);
            } finally {
                StreamUtils.closeQuietly(in);
            }

            width = getWidthPKM(compressedData, 0);
            height = getHeightPKM(compressedData, 0);
            dataOffset = PKM_HEADER_SIZE;
            compressedData.position(dataOffset);
            checkNPOT();
        }

        private void checkNPOT() {
            if (!MathUtils.isPowerOfTwo(width) || !MathUtils.isPowerOfTwo(height)) {
                Gdx.app.debug("ETC1Data",
                        "warning: non-power-of-two ETC1 textures may crash the driver of PowerVR GPUs");
            }
        }

        /** @return whether this ETC1Data has a PKM header */
        public boolean hasPKMHeader() {
            return dataOffset == 16;
        }

        /** Writes the ETC1Data with a PKM header to the given file.
         * @param file the file. */
        public void write(FileHandle file) {
            DataOutputStream write = null;
            byte[] buffer = new byte[10 * 1024];
            int writtenBytes = 0;
            compressedData.position(0);
            compressedData.limit(compressedData.capacity());
            try {
                write = new DataOutputStream(new GZIPOutputStream(file.write(false)));
                write.writeInt(compressedData.capacity());
                while (writtenBytes != compressedData.capacity()) {
                    int bytesToWrite = Math.min(compressedData.remaining(), buffer.length);
                    compressedData.get(buffer, 0, bytesToWrite);
                    write.write(buffer, 0, bytesToWrite);
                    writtenBytes += bytesToWrite;
                }
            } catch (Exception e) {
                throw new GdxRuntimeException("Couldn't write PKM file to '" + file + "'", e);
            } finally {
                StreamUtils.closeQuietly(write);
            }
            compressedData.position(dataOffset);
            compressedData.limit(compressedData.capacity());
        }

        /** Releases the native resources of the ETC1Data instance. */
        public void dispose() {
            BufferUtils.disposeUnsafeByteBuffer(compressedData);
        }

        public String toString() {
            if (hasPKMHeader()) {
                return (ETC1.isValidPKM(compressedData, 0) ? "valid" : "invalid") + " pkm ["
                        + ETC1.getWidthPKM(compressedData, 0) + "x" + ETC1.getHeightPKM(compressedData, 0)
                        + "], compressed: " + (compressedData.capacity() - ETC1.PKM_HEADER_SIZE);
            } else {
                return "raw [" + width + "x" + height + "], compressed: "
                        + (compressedData.capacity() - ETC1.PKM_HEADER_SIZE);
            }
        }
    }

    private static int getPixelSize(Format format) {
        if (format == Format.RGB565)
            return 2;
        if (format == Format.RGB888)
            return 3;
        throw new GdxRuntimeException("Can only handle RGB565 or RGB888 images");
    }

    /** Encodes the image via the ETC1 compression scheme. Only {@link Format#RGB565} and {@link Format#RGB888} are supported.
     * @param pixmap the {@link Pixmap}
     * @return the {@link ETC1Data} */
    public static ETC1Data encodeImage(Pixmap pixmap) {
        int pixelSize = getPixelSize(pixmap.getFormat());
        ByteBuffer compressedData = encodeImage(pixmap.getPixels(), 0, pixmap.getWidth(), pixmap.getHeight(),
                pixelSize);
        BufferUtils.newUnsafeByteBuffer(compressedData);
        return new ETC1Data(pixmap.getWidth(), pixmap.getHeight(), compressedData, 0);
    }

    /** Encodes the image via the ETC1 compression scheme. Only {@link Format#RGB565} and {@link Format#RGB888} are supported. Adds
     * a PKM header in front of the compressed image data.
     * @param pixmap the {@link Pixmap}
     * @return the {@link ETC1Data} */
    public static ETC1Data encodeImagePKM(Pixmap pixmap) {
        int pixelSize = getPixelSize(pixmap.getFormat());
        ByteBuffer compressedData = encodeImagePKM(pixmap.getPixels(), 0, pixmap.getWidth(), pixmap.getHeight(),
                pixelSize);
        BufferUtils.newUnsafeByteBuffer(compressedData);
        return new ETC1Data(pixmap.getWidth(), pixmap.getHeight(), compressedData, 16);
    }

    /** Takes ETC1 compressed image data and converts it to a {@link Format#RGB565} or {@link Format#RGB888} {@link Pixmap}. Does
     * not modify the ByteBuffer's position or limit.
     * @param etc1Data the {@link ETC1Data} instance
     * @param format either {@link Format#RGB565} or {@link Format#RGB888}
     * @return the Pixmap */
    public static Pixmap decodeImage(ETC1Data etc1Data, Format format) {
        int dataOffset = 0;
        int width = 0;
        int height = 0;

        if (etc1Data.hasPKMHeader()) {
            dataOffset = 16;
            width = ETC1.getWidthPKM(etc1Data.compressedData, 0);
            height = ETC1.getHeightPKM(etc1Data.compressedData, 0);
        } else {
            dataOffset = 0;
            width = etc1Data.width;
            height = etc1Data.height;
        }

        int pixelSize = getPixelSize(format);
        Pixmap pixmap = new Pixmap(width, height, format);
        decodeImage(etc1Data.compressedData, dataOffset, pixmap.getPixels(), 0, width, height, pixelSize);
        return pixmap;
    }

    // @off
    /*JNI
    #include <etc1/etc1_utils.h>
    #include <stdlib.h>
     */

    /** @param width the width in pixels
     * @param height the height in pixels
     * @return the number of bytes needed to store the compressed data */
    public static native int getCompressedDataSize(int width, int height); /*
                                                                           return etc1_get_encoded_data_size(width, height);
                                                                           */

    /** Writes a PKM header to the {@link ByteBuffer}. Does not modify the position or limit of the ByteBuffer.
     * @param header the direct native order {@link ByteBuffer}
     * @param offset the offset to the header in bytes
     * @param width the width in pixels
     * @param height the height in pixels */
    public static native void formatHeader(ByteBuffer header, int offset, int width, int height); /*
                                                                                                  etc1_pkm_format_header((etc1_byte*)header + offset, width, height);
                                                                                                  */

    /** @param header direct native order {@link ByteBuffer} holding the PKM header
     * @param offset the offset in bytes to the PKM header from the ByteBuffer's start
     * @return the width stored in the PKM header */
    static native int getWidthPKM(ByteBuffer header, int offset); /*
                                                                  return etc1_pkm_get_width((etc1_byte*)header + offset);
                                                                  */

    /** @param header direct native order {@link ByteBuffer} holding the PKM header
     * @param offset the offset in bytes to the PKM header from the ByteBuffer's start
     * @return the height stored in the PKM header */
    static native int getHeightPKM(ByteBuffer header, int offset); /*
                                                                   return etc1_pkm_get_height((etc1_byte*)header + offset);
                                                                   */

    /** @param header direct native order {@link ByteBuffer} holding the PKM header
     * @param offset the offset in bytes to the PKM header from the ByteBuffer's start
     * @return the width stored in the PKM header */
    static native boolean isValidPKM(ByteBuffer header, int offset); /*
                                                                     return etc1_pkm_is_valid((etc1_byte*)header + offset) != 0?true:false;
                                                                     */

    /** Decodes the compressed image data to RGB565 or RGB888 pixel data. Does not modify the position or limit of the
     * {@link ByteBuffer} instances.
     * @param compressedData the compressed image data in a direct native order {@link ByteBuffer}
     * @param offset the offset in bytes to the image data from the start of the buffer
     * @param decodedData the decoded data in a direct native order ByteBuffer, must hold width * height * pixelSize bytes.
     * @param offsetDec the offset in bytes to the decoded image data.
     * @param width the width in pixels
     * @param height the height in pixels
     * @param pixelSize the pixel size, either 2 (RBG565) or 3 (RGB888) */
    private static native void decodeImage(ByteBuffer compressedData, int offset, ByteBuffer decodedData,
            int offsetDec, int width, int height, int pixelSize); /*
                                                                  etc1_decode_image((etc1_byte*)compressedData + offset, (etc1_byte*)decodedData + offsetDec, width, height, pixelSize, width * pixelSize);
                                                                  */

    /** Encodes the image data given as RGB565 or RGB888. Does not modify the position or limit of the {@link ByteBuffer}.
     * @param imageData the image data in a direct native order {@link ByteBuffer}
     * @param offset the offset in bytes to the image data from the start of the buffer
     * @param width the width in pixels
     * @param height the height in pixels
     * @param pixelSize the pixel size, either 2 (RGB565) or 3 (RGB888)
     * @return a new direct native order ByteBuffer containing the compressed image data */
    private static native ByteBuffer encodeImage(ByteBuffer imageData, int offset, int width, int height,
            int pixelSize); /*
                            int compressedSize = etc1_get_encoded_data_size(width, height);
                            etc1_byte* compressedData = (etc1_byte*)malloc(compressedSize);
                            etc1_encode_image((etc1_byte*)imageData + offset, width, height, pixelSize, width * pixelSize, compressedData);
                            return env->NewDirectByteBuffer(compressedData, compressedSize);
                            */

    /** Encodes the image data given as RGB565 or RGB888. Does not modify the position or limit of the {@link ByteBuffer}.
     * @param imageData the image data in a direct native order {@link ByteBuffer}
     * @param offset the offset in bytes to the image data from the start of the buffer
     * @param width the width in pixels
     * @param height the height in pixels
     * @param pixelSize the pixel size, either 2 (RGB565) or 3 (RGB888)
     * @return a new direct native order ByteBuffer containing the compressed image data */
    private static native ByteBuffer encodeImagePKM(ByteBuffer imageData, int offset, int width, int height,
            int pixelSize); /*
                            int compressedSize = etc1_get_encoded_data_size(width, height);
                            etc1_byte* compressed = (etc1_byte*)malloc(compressedSize + ETC_PKM_HEADER_SIZE);
                            etc1_pkm_format_header(compressed, width, height);
                            etc1_encode_image((etc1_byte*)imageData + offset, width, height, pixelSize, width * pixelSize, compressed + ETC_PKM_HEADER_SIZE);
                            return env->NewDirectByteBuffer(compressed, compressedSize + ETC_PKM_HEADER_SIZE);
                            */
}