content: /*
   * Copyright (C) 2009 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.
   */
  
  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.
       * @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] == 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, pixelSize, 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);
          }
      }
  }
  