/** * TGAReader.java * * Copyright (c) 2014 Kenji Sasaki * Released under the MIT license. * https://github.com/npedotnet/TGAReader/blob/master/LICENSE * * English document * https://github.com/npedotnet/TGAReader/blob/master/README.md * * Japanese document * http://3dtech.jp/wiki/index.php?TGAReader * */ package net.npe.tga; import java.io.IOException; public final class TGAReader { public static final Order ARGB = new Order(16, 8, 0, 24); public static final Order ABGR = new Order(0, 8, 16, 24); public static int getWidth(byte [] buffer) { return (buffer[12] & 0xFF) | (buffer[13] & 0xFF) << 8; } public static int getHeight(byte [] buffer) { return (buffer[14] & 0xFF) | (buffer[15] & 0xFF) << 8; } public static int [] read(byte [] buffer, Order order) throws IOException { // header // int idFieldLength = buffer[0] & 0xFF; // int colormapType = buffer[1] & 0xFF; int type = buffer[2] & 0xFF; int colormapOrigin = (buffer[3] & 0xFF) | (buffer[4] & 0xFF) << 8; int colormapLength = (buffer[5] & 0xFF) | (buffer[6] & 0xFF) << 8; int colormapDepth = buffer[7] & 0xFF; // int originX = (buffer[8] & 0xFF) | (buffer[9] & 0xFF) << 8; // unsupported // int originY = (buffer[10] & 0xFF) | (buffer[11] & 0xFF) << 8; // unsupported int width = getWidth(buffer); int height = getHeight(buffer); int depth = buffer[16] & 0xFF; int descriptor = buffer[17] & 0xFF; int [] pixels = null; // data switch(type) { case COLORMAP: { int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength; pixels = createPixelsFromColormap(width, height, colormapDepth, buffer, imageDataOffset, buffer, colormapOrigin, descriptor, order); } break; case RGB: pixels = createPixelsFromRGB(width, height, depth, buffer, 18, descriptor, order); break; case GRAYSCALE: pixels = createPixelsFromGrayscale(width, height, depth, buffer, 18, descriptor, order); break; case COLORMAP_RLE: { int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength; byte [] decodeBuffer = decodeRLE(width, height, depth, buffer, imageDataOffset); pixels = createPixelsFromColormap(width, height, colormapDepth, decodeBuffer, 0, buffer, colormapOrigin, descriptor, order); } break; case RGB_RLE: { byte [] decodeBuffer = decodeRLE(width, height, depth, buffer, 18); pixels = createPixelsFromRGB(width, height, depth, decodeBuffer, 0, descriptor, order); } break; case GRAYSCALE_RLE: { byte [] decodeBuffer = decodeRLE(width, height, depth, buffer, 18); pixels = createPixelsFromGrayscale(width, height, depth, decodeBuffer, 0, descriptor, order); } break; default: throw new IOException("Unsupported image type: "+type); } return pixels; } private static final int COLORMAP = 1; private static final int RGB = 2; private static final int GRAYSCALE = 3; private static final int COLORMAP_RLE = 9; private static final int RGB_RLE = 10; private static final int GRAYSCALE_RLE = 11; private static final int RIGHT_ORIGIN = 0x10; private static final int UPPER_ORIGIN = 0x20; private static byte [] decodeRLE(int width, int height, int depth, byte [] buffer, int offset) { int elementCount = depth/8; byte [] elements = new byte[elementCount]; int decodeBufferLength = elementCount * width * height; byte [] decodeBuffer = new byte[decodeBufferLength]; int decoded = 0; while(decoded < decodeBufferLength) { int packet = buffer[offset++] & 0xFF; if((packet & 0x80) != 0) { // RLE for(int i=0; i<elementCount; i++) { elements[i] = buffer[offset++]; } int count = (packet&0x7F)+1; for(int i=0; i<count; i++) { for(int j=0; j<elementCount; j++) { decodeBuffer[decoded++] = elements[j]; } } } else { // RAW int count = (packet+1) * elementCount; for(int i=0; i<count; i++) { decodeBuffer[decoded++] = buffer[offset++]; } } } return decodeBuffer; } private static int [] createPixelsFromColormap(int width, int height, int depth, byte [] bytes, int offset, byte [] palette, int colormapOrigin, int descriptor, Order order) throws IOException { int [] pixels = null; int rs = order.redShift; int gs = order.greenShift; int bs = order.blueShift; int as = order.alphaShift; switch(depth) { case 24: pixels = new int[width*height]; if((descriptor & RIGHT_ORIGIN) != 0) { if((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int colormapIndex = bytes[offset+width*i+j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if(colormapIndex >= 0) { int index = 3*colormapIndex+18; int b = palette[index+0] & 0xFF; int g = palette[index+1] & 0xFF; int r = palette[index+2] & 0xFF; int a = 0xFF; color = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } pixels[width*i+(width-j-1)] = color; } } } else { // LowerRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int colormapIndex = bytes[offset+width*i+j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if(colormapIndex >= 0) { int index = 3*colormapIndex+18; int b = palette[index+0] & 0xFF; int g = palette[index+1] & 0xFF; int r = palette[index+2] & 0xFF; int a = 0xFF; color = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } pixels[width*(height-i-1)+(width-j-1)] = color; } } } } else { if((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int colormapIndex = bytes[offset+width*i+j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if(colormapIndex >= 0) { int index = 3*colormapIndex+18; int b = palette[index+0] & 0xFF; int g = palette[index+1] & 0xFF; int r = palette[index+2] & 0xFF; int a = 0xFF; color = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } pixels[width*i+j] = color; } } } else { // LowerLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int colormapIndex = bytes[offset+width*i+j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if(colormapIndex >= 0) { int index = 3*colormapIndex+18; int b = palette[index+0] & 0xFF; int g = palette[index+1] & 0xFF; int r = palette[index+2] & 0xFF; int a = 0xFF; color = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } pixels[width*(height-i-1)+j] = color; } } } } break; case 32: pixels = new int[width*height]; if((descriptor & RIGHT_ORIGIN) != 0) { if((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int colormapIndex = bytes[offset+width*i+j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if(colormapIndex >= 0) { int index = 4*colormapIndex+18; int b = palette[index+0] & 0xFF; int g = palette[index+1] & 0xFF; int r = palette[index+2] & 0xFF; int a = palette[index+3] & 0xFF; color = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } pixels[width*i+(width-j-1)] = color; } } } else { // LowerRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int colormapIndex = bytes[offset+width*i+j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if(colormapIndex >= 0) { int index = 4*colormapIndex+18; int b = palette[index+0] & 0xFF; int g = palette[index+1] & 0xFF; int r = palette[index+2] & 0xFF; int a = palette[index+3] & 0xFF; color = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } pixels[width*(height-i-1)+(width-j-1)] = color; } } } } else { if((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int colormapIndex = bytes[offset+width*i+j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if(colormapIndex >= 0) { int index = 4*colormapIndex+18; int b = palette[index+0] & 0xFF; int g = palette[index+1] & 0xFF; int r = palette[index+2] & 0xFF; int a = palette[index+3] & 0xFF; color = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } pixels[width*i+j] = color; } } } else { // LowerLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int colormapIndex = bytes[offset+width*i+j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if(colormapIndex >= 0) { int index = 4*colormapIndex+18; int b = palette[index+0] & 0xFF; int g = palette[index+1] & 0xFF; int r = palette[index+2] & 0xFF; int a = palette[index+3] & 0xFF; color = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } pixels[width*(height-i-1)+j] = color; } } } } break; default: throw new IOException("Unsupported depth:"+depth); } return pixels; } private static int [] createPixelsFromRGB(int width, int height, int depth, byte [] bytes, int offset, int descriptor, Order order) throws IOException { int [] pixels = null; int rs = order.redShift; int gs = order.greenShift; int bs = order.blueShift; int as = order.alphaShift; switch(depth) { case 24: pixels = new int[width*height]; if((descriptor & RIGHT_ORIGIN) != 0) { if((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int index = offset+3*width*i+3*j; int b = bytes[index+0] & 0xFF; int g = bytes[index+1] & 0xFF; int r = bytes[index+2] & 0xFF; int a = 0xFF; pixels[width*i+(width-j-1)] = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } } } else { // LowerRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int index = offset+3*width*i+3*j; int b = bytes[index+0] & 0xFF; int g = bytes[index+1] & 0xFF; int r = bytes[index+2] & 0xFF; int a = 0xFF; pixels[width*(height-i-1)+(width-j-1)] = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } } } } else { if((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int index = offset+3*width*i+3*j; int b = bytes[index+0] & 0xFF; int g = bytes[index+1] & 0xFF; int r = bytes[index+2] & 0xFF; int a = 0xFF; pixels[width*i+j] = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } } } else { // LowerLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int index = offset+3*width*i+3*j; int b = bytes[index+0] & 0xFF; int g = bytes[index+1] & 0xFF; int r = bytes[index+2] & 0xFF; int a = 0xFF; pixels[width*(height-i-1)+j] = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } } } } break; case 32: pixels = new int[width*height]; if((descriptor & RIGHT_ORIGIN) != 0) { if((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int index = offset+4*width*i+4*j; int b = bytes[index+0] & 0xFF; int g = bytes[index+1] & 0xFF; int r = bytes[index+2] & 0xFF; int a = bytes[index+3] & 0xFF; pixels[width*i+(width-j-1)] = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } } } else { // LowerRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int index = offset+4*width*i+4*j; int b = bytes[index+0] & 0xFF; int g = bytes[index+1] & 0xFF; int r = bytes[index+2] & 0xFF; int a = bytes[index+3] & 0xFF; pixels[width*(height-i-1)+(width-j-1)] = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } } } } else { if((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int index = offset+4*width*i+4*j; int b = bytes[index+0] & 0xFF; int g = bytes[index+1] & 0xFF; int r = bytes[index+2] & 0xFF; int a = bytes[index+3] & 0xFF; pixels[width*i+j] = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } } } else { // LowerLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int index = offset+4*width*i+4*j; int b = bytes[index+0] & 0xFF; int g = bytes[index+1] & 0xFF; int r = bytes[index+2] & 0xFF; int a = bytes[index+3] & 0xFF; pixels[width*(height-i-1)+j] = (r<<rs) | (g<<gs) | (b<<bs) | (a<<as); } } } } break; default: throw new IOException("Unsupported depth:"+depth); } return pixels; } private static int [] createPixelsFromGrayscale(int width, int height, int depth, byte [] bytes, int offset, int descriptor, Order order) throws IOException { int [] pixels = null; int rs = order.redShift; int gs = order.greenShift; int bs = order.blueShift; int as = order.alphaShift; switch(depth) { case 8: pixels = new int[width*height]; if((descriptor & RIGHT_ORIGIN) != 0) { if((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int e = bytes[offset+width*i+j] & 0xFF; int a = 0xFF; pixels[width*i+(width-j-1)] = (e<<rs) | (e<<gs) | (e<<bs) | (a<<as); } } } else { // LowerRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int e = bytes[offset+width*i+j] & 0xFF; int a = 0xFF; pixels[width*(height-i-1)+(width-j-1)] = (e<<rs) | (e<<gs) | (e<<bs) | (a<<as); } } } } else { if((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int e = bytes[offset+width*i+j] & 0xFF; int a = 0xFF; pixels[width*i+j] = (e<<rs) | (e<<gs) | (e<<bs) | (a<<as); } } } else { // LowerLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int e = bytes[offset+width*i+j] & 0xFF; int a = 0xFF; pixels[width*(height-i-1)+j] = (e<<rs) | (e<<gs) | (e<<bs) | (a<<as); } } } } break; case 16: pixels = new int[width*height]; if((descriptor & RIGHT_ORIGIN) != 0) { if((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int e = bytes[offset+2*width*i+2*j+0] & 0xFF; int a = bytes[offset+2*width*i+2*j+1] & 0xFF; pixels[width*i+(width-j-1)] = (e<<rs) | (e<<gs) | (e<<bs) | (a<<as); } } } else { // LowerRight for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int e = bytes[offset+2*width*i+2*j+0] & 0xFF; int a = bytes[offset+2*width*i+2*j+1] & 0xFF; pixels[width*(height-i-1)+(width-j-1)] = (e<<rs) | (e<<gs) | (e<<bs) | (a<<as); } } } } else { if((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int e = bytes[offset+2*width*i+2*j+0] & 0xFF; int a = bytes[offset+2*width*i+2*j+1] & 0xFF; pixels[width*i+j] = (e<<rs) | (e<<gs) | (e<<bs) | (a<<as); } } } else { // LowerLeft for(int i=0; i<height; i++) { for(int j=0; j<width; j++) { int e = bytes[offset+2*width*i+2*j+0] & 0xFF; int a = bytes[offset+2*width*i+2*j+1] & 0xFF; pixels[width*(height-i-1)+j] = (e<<rs) | (e<<gs) | (e<<bs) | (a<<as); } } } } break; default: throw new IOException("Unsupported depth:"+depth); } return pixels; } private TGAReader() {} private static final class Order { Order(int redShift, int greenShift, int blueShift, int alphaShift) { this.redShift = redShift; this.greenShift = greenShift; this.blueShift = blueShift; this.alphaShift = alphaShift; } public int redShift; public int greenShift; public int blueShift; public int alphaShift; } }