package loon.canvas; import java.io.IOException; import loon.BaseIO; import loon.LRelease; import loon.LSystem; import loon.utils.ArrayByte; public class TGA { private static final int TGA_HEADER_SIZE = 18; private static final int TGA_HEADER_INVALID = 0; private static final int TGA_HEADER_UNCOMPRESSED = 1; private static final int TGA_HEADER_COMPRESSED = 2; public static class State implements LRelease { public int type; public int pixelDepth; public int width; public int height; public int[] pixels; public void close() { if (pixels != null) { pixels = null; } } } public static State inJustDecode(String res) throws IOException { return inJustDecode(BaseIO.loadArrayByte(res)); } public static State inJustDecode(ArrayByte in) throws IOException { return loadHeader(in, new State()); } private static State loadHeader(ArrayByte in, State info) throws IOException { in.readByte(); in.readByte(); info.type = in.readByte(); in.readByte(); in.readByte(); in.readByte(); in.readByte(); in.readByte(); in.readByte(); in.readByte(); in.readByte(); in.readByte(); info.width = (in.readByte() & 0xff) | ((in.readByte() & 0xff) << 8); info.height = (in.readByte() & 0xff) | ((in.readByte() & 0xff) << 8); info.pixelDepth = in.readByte() & 0xff; return info; } private static final short getUnsignedByte(byte[] bytes, int byteIndex) { return (short) (bytes[byteIndex] & 0xFF); } private static final int getUnsignedShort(byte[] bytes, int byteIndex) { return (getUnsignedByte(bytes, byteIndex + 1) << 8) + getUnsignedByte(bytes, byteIndex + 0); } private static void readBuffer(ArrayByte in, byte[] buffer) throws IOException { int bytesRead = 0; int bytesToRead = buffer.length; for (; bytesToRead > 0;) { int read = in.read(buffer, bytesRead, bytesToRead); bytesRead += read; bytesToRead -= read; } } private static final void skipBytes(ArrayByte in, long toSkip) throws IOException { for (; toSkip > 0L;) { long skipped = in.skip(toSkip); if (skipped > 0) { toSkip -= skipped; } else if (skipped < 0) { toSkip = 0; } } } private static final int compareFormatHeader(ArrayByte in, byte[] header) throws IOException { readBuffer(in, header); boolean hasPalette = false; int result = TGA_HEADER_INVALID; int imgIDSize = getUnsignedByte(header, 0); if ((header[1] != (byte) 0) && (header[1] != (byte) 1)) { return TGA_HEADER_INVALID; } switch (getUnsignedByte(header, 2)) { case 0: result = TGA_HEADER_UNCOMPRESSED; break; case 1: hasPalette = true; result = TGA_HEADER_UNCOMPRESSED; throw LSystem.runThrow("Indexed State is not yet supported !"); case 2: result = TGA_HEADER_UNCOMPRESSED; break; case 3: result = TGA_HEADER_UNCOMPRESSED; break; case 9: hasPalette = true; result = TGA_HEADER_COMPRESSED; throw LSystem.runThrow("Indexed State is not yet supported !"); case 10: result = TGA_HEADER_COMPRESSED; break; case 11: result = TGA_HEADER_COMPRESSED; break; default: return TGA_HEADER_INVALID; } if (!hasPalette) { if (getUnsignedShort(header, 3) != 0) { return TGA_HEADER_INVALID; } } if (!hasPalette) { if (getUnsignedShort(header, 5) != 0) { return TGA_HEADER_INVALID; } } short paletteEntrySize = getUnsignedByte(header, 7); if (!hasPalette) { if (paletteEntrySize != 0) { return TGA_HEADER_INVALID; } } else { if ((paletteEntrySize != 15) && (paletteEntrySize != 16) && (paletteEntrySize != 24) && (paletteEntrySize != 32)) { return TGA_HEADER_INVALID; } } if (getUnsignedShort(header, 8) != 0) { return TGA_HEADER_INVALID; } if (getUnsignedShort(header, 10) != 0) { return TGA_HEADER_INVALID; } switch (getUnsignedByte(header, 16)) { case 1: case 8: case 15: case 16: throw LSystem.runThrow( "this State with non RGB or RGBA pixels are not yet supported."); case 24: case 32: break; default: return TGA_HEADER_INVALID; } if (imgIDSize != 0) { skipBytes(in, imgIDSize); } return result; } private static final void writePixel(int[] pixels, final byte red, final byte green, final byte blue, final byte alpha, final boolean hasAlpha, final int offset) { int pixel; if (hasAlpha) { pixel = (red & 0xff); pixel |= ((green & 0xff) << 8); pixel |= ((blue & 0xff) << 16); pixel |= ((alpha & 0xff) << 24); pixels[offset / 4] = pixel; } else { pixel = (red & 0xff); pixel |= ((green & 0xff) << 8); pixel |= ((blue & 0xff) << 16); pixels[offset / 4] = pixel; } } private static int[] readBuffer(ArrayByte in, int width, int height, int srcBytesPerPixel, boolean acceptAlpha, boolean flipVertically) throws IOException { int[] pixels = new int[width * height]; byte[] buffer = new byte[srcBytesPerPixel]; final boolean copyAlpha = (srcBytesPerPixel == 4) && acceptAlpha; final int dstBytesPerPixel = acceptAlpha ? srcBytesPerPixel : 3; final int trgLineSize = width * dstBytesPerPixel; int dstByteOffset = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int read = in.read(buffer, 0, srcBytesPerPixel); if (read < srcBytesPerPixel) { return pixels; } int actualByteOffset = dstByteOffset; if (!flipVertically) { actualByteOffset = ((height - y - 1) * trgLineSize) + (x * dstBytesPerPixel); } if (copyAlpha) { writePixel(pixels, buffer[2], buffer[1], buffer[0], buffer[3], true, actualByteOffset); } else { writePixel(pixels, buffer[2], buffer[1], buffer[0], (byte) 0, false, actualByteOffset); } dstByteOffset += dstBytesPerPixel; } } return pixels; } private static void loadUncompressed(byte[] header, State tga, ArrayByte in, boolean acceptAlpha, boolean flipVertically) throws IOException { // 图像宽 int orgWidth = getUnsignedShort(header, 12); // 图像高 int orgHeight = getUnsignedShort(header, 14); // 图像位图(24&32) int pixelDepth = getUnsignedByte(header, 16); tga.width = orgWidth; tga.height = orgHeight; tga.pixelDepth = pixelDepth; boolean isOriginBottom = (header[17] & 0x20) == 0; if (!isOriginBottom) { flipVertically = !flipVertically; } // 不支持的格式 if ((orgWidth <= 0) || (orgHeight <= 0) || ((pixelDepth != 24) && (pixelDepth != 32))) { throw new IOException("Invalid texture information !"); } int bytesPerPixel = (pixelDepth / 8); // 获取图像数据并转为int[] tga.pixels = readBuffer(in, orgWidth, orgHeight, bytesPerPixel, acceptAlpha, flipVertically); // 图像色彩模式 tga.type = (acceptAlpha && (bytesPerPixel == 4) ? 4 : 3); } private static void loadCompressed(byte[] header, State tga, ArrayByte in, boolean acceptAlpha, boolean flipVertically) throws IOException { int orgWidth = getUnsignedShort(header, 12); int orgHeight = getUnsignedShort(header, 14); int pixelDepth = getUnsignedByte(header, 16); tga.width = orgWidth; tga.height = orgHeight; tga.pixelDepth = pixelDepth; boolean isOriginBottom = (header[17] & 0x20) == 0; if (!isOriginBottom) { flipVertically = !flipVertically; } if ((orgWidth <= 0) || (orgHeight <= 0) || ((pixelDepth != 24) && (pixelDepth != 32))) { throw new IOException("Invalid texture information !"); } int bytesPerPixel = (pixelDepth / 8); int pixelCount = orgHeight * orgWidth; int currentPixel = 0; byte[] colorBuffer = new byte[bytesPerPixel]; int width = orgWidth; int height = orgHeight; final int dstBytesPerPixel = (acceptAlpha && (bytesPerPixel == 4) ? 4 : 3); final int trgLineSize = orgWidth * dstBytesPerPixel; int[] pixels = new int[width * height]; int dstByteOffset = 0; do { int chunkHeader = (in.readByte() & 0xFF); boolean repeatColor; if (chunkHeader < 128) { chunkHeader++; repeatColor = false; } else { chunkHeader -= 127; readBuffer(in, colorBuffer); repeatColor = true; } for (int counter = 0; counter < chunkHeader; counter++) { if (!repeatColor) { readBuffer(in, colorBuffer); } int x = currentPixel % orgWidth; int y = currentPixel / orgWidth; int actualByteOffset = dstByteOffset; if (!flipVertically) { actualByteOffset = ((height - y - 1) * trgLineSize) + (x * dstBytesPerPixel); } if (dstBytesPerPixel == 4) { writePixel(pixels, colorBuffer[2], colorBuffer[1], colorBuffer[0], colorBuffer[3], true, actualByteOffset); } else { writePixel(pixels, colorBuffer[2], colorBuffer[1], colorBuffer[0], (byte) 0, false, actualByteOffset); } dstByteOffset += dstBytesPerPixel; currentPixel++; if (currentPixel > pixelCount) { throw new IOException("Too many pixels read !"); } } } while (currentPixel < pixelCount); tga.pixels = pixels; tga.type = dstBytesPerPixel; } public static State load(String res) throws IOException { return load(res, new State()); } public static State load(String res, State tag) throws IOException { ArrayByte in = BaseIO.loadArrayByte(res); State tga = load(in, tag, true, false); if (in != null) { try { in.close(); in = null; } catch (Exception e) { } } return tga; } public static State load(ArrayByte in, State tga, boolean acceptAlpha, boolean flipVertically) throws IOException { if (in.available() < TGA_HEADER_SIZE) { return (null); } byte[] header = new byte[TGA_HEADER_SIZE]; final int headerType = compareFormatHeader(in, header); if (headerType == TGA_HEADER_INVALID) { return (null); } if (headerType == TGA_HEADER_UNCOMPRESSED) { loadUncompressed(header, tga, in, acceptAlpha, flipVertically); } else if (headerType == TGA_HEADER_COMPRESSED) { loadCompressed(header, tga, in, acceptAlpha, flipVertically); } else { throw new IOException("State file be type 2 or type 10 !"); } return tga; } }