public static class

PVRTexture.PVRTextureHeader

extends Object
package org.andengine.opengl.texture.compressed.pvr;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.andengine.opengl.texture.PixelFormat;
import org.andengine.opengl.texture.Texture;
import org.andengine.opengl.texture.TextureManager;
import org.andengine.opengl.texture.TextureOptions;
import org.andengine.opengl.texture.compressed.pvr.pixelbufferstrategy.GreedyPVRTexturePixelBufferStrategy;
import org.andengine.opengl.texture.compressed.pvr.pixelbufferstrategy.IPVRTexturePixelBufferStrategy;
import org.andengine.opengl.texture.compressed.pvr.pixelbufferstrategy.IPVRTexturePixelBufferStrategy.IPVRTexturePixelBufferStrategyBufferManager;
import org.andengine.opengl.util.GLState;
import org.andengine.util.StreamUtils;
import org.andengine.util.adt.DataConstants;
import org.andengine.util.adt.array.ArrayUtils;
import org.andengine.util.adt.io.out.ByteBufferOutputStream;
import org.andengine.util.debug.Debug;
import org.andengine.util.math.MathUtils;

import android.opengl.GLES20;

/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 *
 * @author Nicolas Gramlich
 * @since 16:18:10 - 13.07.2011
 */
public abstract class PVRTexture extends Texture {
	// ===========================================================
	// Constants
	// ===========================================================

	public static final int FLAG_MIPMAP = (1 << 8); // has mip map levels
	public static final int FLAG_TWIDDLE = (1 << 9); // is twiddled
	public static final int FLAG_BUMPMAP = (1 << 10); // has normals encoded for a bump map
	public static final int FLAG_TILING = (1 << 11); // is bordered for tiled pvr
	public static final int FLAG_CUBEMAP = (1 << 12); // is a cubemap/skybox
	public static final int FLAG_FALSEMIPCOL = (1 << 13); // are there false colored MIP levels
	public static final int FLAG_VOLUME = (1 << 14); // is this a volume texture
	public static final int FLAG_ALPHA = (1 << 15); // v2.1 is there transparency info in the texture
	public static final int FLAG_VERTICALFLIP = (1 << 16); // v2.1 is the texture vertically flipped

	// ===========================================================
	// Fields
	// ===========================================================

	private final PVRTextureHeader mPVRTextureHeader;
	private final IPVRTexturePixelBufferStrategy mPVRTexturePixelBufferStrategy;

	// ===========================================================
	// Constructors
	// ===========================================================

	public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat) throws IllegalArgumentException, IOException {
		this(pTextureManager, pPVRTextureFormat, new GreedyPVRTexturePixelBufferStrategy(), TextureOptions.DEFAULT, null);
	}

	public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy) throws IllegalArgumentException, IOException {
		this(pTextureManager, pPVRTextureFormat, pPVRTexturePixelBufferStrategy, TextureOptions.DEFAULT, null);
	}

	public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException {
		this(pTextureManager, pPVRTextureFormat, new GreedyPVRTexturePixelBufferStrategy(), TextureOptions.DEFAULT, pTextureStateListener);
	}

	public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException {
		this(pTextureManager, pPVRTextureFormat, pPVRTexturePixelBufferStrategy, TextureOptions.DEFAULT, pTextureStateListener);
	}

	public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final TextureOptions pTextureOptions) throws IllegalArgumentException, IOException {
		this(pTextureManager, pPVRTextureFormat, new GreedyPVRTexturePixelBufferStrategy(), pTextureOptions, null);
	}

	public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy, final TextureOptions pTextureOptions) throws IllegalArgumentException, IOException {
		this(pTextureManager, pPVRTextureFormat, pPVRTexturePixelBufferStrategy, pTextureOptions, null);
	}

	public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final TextureOptions pTextureOptions, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException {
		this(pTextureManager, pPVRTextureFormat, new GreedyPVRTexturePixelBufferStrategy(), pTextureOptions, pTextureStateListener);
	}

	public PVRTexture(final TextureManager pTextureManager, final PVRTextureFormat pPVRTextureFormat, final IPVRTexturePixelBufferStrategy pPVRTexturePixelBufferStrategy, final TextureOptions pTextureOptions, final ITextureStateListener pTextureStateListener) throws IllegalArgumentException, IOException {
		super(pTextureManager, pPVRTextureFormat.getPixelFormat(), pTextureOptions, pTextureStateListener);
		this.mPVRTexturePixelBufferStrategy = pPVRTexturePixelBufferStrategy;

		InputStream inputStream = null;
		try {
			inputStream = this.getInputStream();
			this.mPVRTextureHeader = new PVRTextureHeader(StreamUtils.streamToBytes(inputStream, PVRTextureHeader.SIZE));
		} finally {
			StreamUtils.close(inputStream);
		}

		if(this.mPVRTextureHeader.getPVRTextureFormat().getPixelFormat() != pPVRTextureFormat.getPixelFormat()) {
			throw new IllegalArgumentException("Other PVRTextureFormat: '" + this.mPVRTextureHeader.getPVRTextureFormat().getPixelFormat() + "' found than expected: '" + pPVRTextureFormat.getPixelFormat() + "'.");
		}

		if(this.mPVRTextureHeader.getPVRTextureFormat().isCompressed()) { // TODO && ! GLHELPER_EXTENSION_PVRTC] ) {
			throw new IllegalArgumentException("Invalid PVRTextureFormat: '" + this.mPVRTextureHeader.getPVRTextureFormat() + "'.");
		}

		this.mUpdateOnHardwareNeeded = true;
	}

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	@Override
	public int getWidth() {
		return this.mPVRTextureHeader.getWidth();
	}

	@Override
	public int getHeight() {
		return this.mPVRTextureHeader.getHeight();
	}

	public PVRTextureHeader getPVRTextureHeader() {
		return this.mPVRTextureHeader;
	}

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	protected abstract InputStream onGetInputStream() throws IOException;

	public InputStream getInputStream() throws IOException {
		return this.onGetInputStream();
	}

	@Override
	protected void writeTextureToHardware(final GLState pGLState) throws IOException {
		final IPVRTexturePixelBufferStrategyBufferManager pvrTextureLoadStrategyManager = this.mPVRTexturePixelBufferStrategy.newPVRTexturePixelBufferStrategyManager(this);

		int width = this.getWidth();
		int height = this.getHeight();

		final int dataLength = this.mPVRTextureHeader.getDataLength();

		final int bytesPerPixel = this.mPVRTextureHeader.getBitsPerPixel() / DataConstants.BITS_PER_BYTE;

		final boolean useDefaultAlignment = MathUtils.isPowerOfTwo(width) && MathUtils.isPowerOfTwo(height) && this.mPVRTextureHeader.mPVRTextureFormat == PVRTextureFormat.RGBA_8888;
		if(!useDefaultAlignment) {
			/* Adjust unpack alignment. */
			GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
		}

		int currentLevel = 0;
		int currentPixelDataOffset = 0;
		while (currentPixelDataOffset < dataLength) {
			if (currentLevel > 0 && (width != height || MathUtils.nextPowerOfTwo(width) != width)) {
				Debug.w("Mipmap level '" + currentLevel + "' is not squared. Width: '" + width + "', height: '" + height + "'. Texture won't render correctly.");
			}

			final int currentPixelDataSize = height * width * bytesPerPixel;

			/* Load the current level. */
			this.mPVRTexturePixelBufferStrategy.loadPVRTextureData(pvrTextureLoadStrategyManager, width, height, bytesPerPixel, this.mPixelFormat, currentLevel, currentPixelDataOffset, currentPixelDataSize);

			currentPixelDataOffset += currentPixelDataSize;

			/* Prepare next mipmap level. */
			width = Math.max(width / 2, 1);
			height = Math.max(height / 2, 1);

			currentLevel++;
		}

		if(!useDefaultAlignment) {
			/* Restore default unpack alignment. */
			GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, GLState.GL_UNPACK_ALIGNMENT_DEFAULT);
		}
	}

	// ===========================================================
	// Methods
	// ===========================================================

	public ByteBuffer getPVRTextureBuffer() throws IOException {
		final InputStream inputStream = this.getInputStream();
		try {
			final ByteBufferOutputStream os = new ByteBufferOutputStream(DataConstants.BYTES_PER_KILOBYTE, DataConstants.BYTES_PER_MEGABYTE / 2);
			StreamUtils.copy(inputStream, os);
			return os.toByteBuffer();
		} finally {
			StreamUtils.close(inputStream);
		}
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================

	public static class PVRTextureHeader {
		// ===========================================================
		// Constants
		// ===========================================================

		static final byte[] MAGIC_IDENTIFIER = {
			(byte)'P',
			(byte)'V',
			(byte)'R',
			(byte)'!'
		};

		public static final int SIZE = 13 * DataConstants.BYTES_PER_INT;
		private static final int FORMAT_FLAG_MASK = 0x0FF;

		// ===========================================================
		// Fields
		// ===========================================================

		private final ByteBuffer mDataByteBuffer;
		private final PVRTextureFormat mPVRTextureFormat;

		// ===========================================================
		// Constructors
		// ===========================================================

		public PVRTextureHeader(final byte[] pData) {
			this.mDataByteBuffer = ByteBuffer.wrap(pData);
			this.mDataByteBuffer.rewind();
			this.mDataByteBuffer.order(ByteOrder.LITTLE_ENDIAN);

			/* Check magic bytes. */
			if(!ArrayUtils.equals(pData, 11 * DataConstants.BYTES_PER_INT, PVRTextureHeader.MAGIC_IDENTIFIER, 0, PVRTextureHeader.MAGIC_IDENTIFIER.length)) {
				throw new IllegalArgumentException("Invalid " + this.getClass().getSimpleName() + "!");
			}

			this.mPVRTextureFormat = PVRTextureFormat.fromID(this.getFlags() & PVRTextureHeader.FORMAT_FLAG_MASK);
		}

		// ===========================================================
		// Getter & Setter
		// ===========================================================

		public PVRTextureFormat getPVRTextureFormat() {
			return this.mPVRTextureFormat;
		}

		public int headerLength() {
			return this.mDataByteBuffer.getInt(0 * DataConstants.BYTES_PER_INT); // TODO Constants
		}

		public int getHeight() {
			return this.mDataByteBuffer.getInt(1 * DataConstants.BYTES_PER_INT);
		}

		public int getWidth() {
			return this.mDataByteBuffer.getInt(2 * DataConstants.BYTES_PER_INT);
		}

		public int getNumMipmaps() {
			return this.mDataByteBuffer.getInt(3 * DataConstants.BYTES_PER_INT);
		}

		public int getFlags() {
			return this.mDataByteBuffer.getInt(4 * DataConstants.BYTES_PER_INT);
		}

		public int getDataLength() {
			return this.mDataByteBuffer.getInt(5 * DataConstants.BYTES_PER_INT);
		}

		public int getBitsPerPixel() {
			return this.mDataByteBuffer.getInt(6 * DataConstants.BYTES_PER_INT);
		}

		public int getBitmaskRed() {
			return this.mDataByteBuffer.getInt(7 * DataConstants.BYTES_PER_INT);
		}

		public int getBitmaskGreen() {
			return this.mDataByteBuffer.getInt(8 * DataConstants.BYTES_PER_INT);
		}

		public int getBitmaskBlue() {
			return this.mDataByteBuffer.getInt(9 * DataConstants.BYTES_PER_INT);
		}

		public int getBitmaskAlpha() {
			return this.mDataByteBuffer.getInt(10 * DataConstants.BYTES_PER_INT);
		}

		public boolean hasAlpha() {
			return this.getBitmaskAlpha() != 0;
		}

		public int getPVRTag() {
			return this.mDataByteBuffer.getInt(11 * DataConstants.BYTES_PER_INT);
		}

		public int numSurfs() {
			return this.mDataByteBuffer.getInt(12 * DataConstants.BYTES_PER_INT);
		}

		// ===========================================================
		// Methods for/from SuperClass/Interfaces
		// ===========================================================

		// ===========================================================
		// Methods
		// ===========================================================

		// ===========================================================
		// Inner and Anonymous Classes
		// ===========================================================
	}

	public static enum PVRTextureFormat {
		// ===========================================================
		// Elements
		// ===========================================================

		RGBA_4444(0x10, false, PixelFormat.RGBA_4444),
		RGBA_5551(0x11, false, PixelFormat.RGBA_5551),
		RGBA_8888(0x12, false, PixelFormat.RGBA_8888),
		RGB_565(0x13, false, PixelFormat.RGB_565),
		//		RGB_555( 0x14, ...),
		//		RGB_888( 0x15, ...),
		I_8(0x16, false, PixelFormat.I_8),
		AI_88(0x17, false, PixelFormat.AI_88),
		//		PVRTC_2(0x18, GL10.GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, true, TextureFormat.???),
		//		PVRTC_4(0x19, GL10.GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, true, TextureFormat.???),
		//		BGRA_8888(0x1A, GL10.GL_RGBA, TextureFormat.???),
		A_8(0x1B, false, PixelFormat.A_8);

		// ===========================================================
		// Constants
		// ===========================================================

		// ===========================================================
		// Fields
		// ===========================================================

		private final int mID;
		private final boolean mCompressed;
		private final PixelFormat mPixelFormat;

		// ===========================================================
		// Constructors
		// ===========================================================

		private PVRTextureFormat(final int pID, final boolean pCompressed, final PixelFormat pPixelFormat) {
			this.mID = pID;
			this.mCompressed = pCompressed;
			this.mPixelFormat = pPixelFormat;
		}

		public static PVRTextureFormat fromID(final int pID) {
			final PVRTextureFormat[] pvrTextureFormats = PVRTextureFormat.values();
			final int pvrTextureFormatCount = pvrTextureFormats.length;
			for(int i = 0; i < pvrTextureFormatCount; i++) {
				final PVRTextureFormat pvrTextureFormat = pvrTextureFormats[i];
				if(pvrTextureFormat.mID == pID) {
					return pvrTextureFormat;
				}
			}
			throw new IllegalArgumentException("Unexpected " + PVRTextureFormat.class.getSimpleName() + "-ID: '" + pID + "'.");
		}

		public static PVRTextureFormat fromPixelFormat(final PixelFormat pPixelFormat) throws IllegalArgumentException {
			switch(pPixelFormat) {
				case RGBA_8888:
					return PVRTextureFormat.RGBA_8888;
				case RGBA_4444:
					return PVRTextureFormat.RGBA_4444;
				case RGB_565:
					return PVRTextureFormat.RGB_565;
				default:
					throw new IllegalArgumentException("Unsupported " + PixelFormat.class.getName() + ": '" + pPixelFormat + "'.");
			}
		}

		// ===========================================================
		// Getter & Setter
		// ===========================================================

		public int getID() {
			return this.mID;
		}

		public boolean isCompressed() {
			return this.mCompressed;
		}

		public PixelFormat getPixelFormat() {
			return this.mPixelFormat;
		}

		// ===========================================================
		// Methods from SuperClass/Interfaces
		// ===========================================================

		// ===========================================================
		// Methods
		// ===========================================================

		// ===========================================================
		// Inner and Anonymous Classes
		// ===========================================================
	}
}