/**
* TGAWriter.java
*
* Copyright (c) 2015 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;
public class TGAWriter {
public enum EncodeType {
NONE, // No RLE encoding
RLE, // RLE encoding
AUTO, // auto
}
public static byte [] write(int [] pixels, int width, int height, TGAReader.Order order) {
return write(pixels, width, height, order, EncodeType.AUTO);
}
public static byte [] write(int [] pixels, int width, int height, TGAReader.Order order, EncodeType encodeType) {
int elementCount = hasAlpha(pixels, order) ? 4 : 3;
int rawSize = elementCount * pixels.length;
int rleSize = getEncodeSize(pixels, width, elementCount);
boolean encoding;
int dataSize;
switch(encodeType) {
case RLE:
encoding = true;
dataSize = rleSize;
break;
case AUTO:
encoding = rleSize < rawSize;
dataSize = encoding ? rleSize : rawSize;
break;
default:
// raw
encoding = false;
dataSize = rawSize;
break;
}
int length = 18 + FOOTER.length + dataSize;
byte [] buffer = new byte[length];
int index = 0;
// Header
buffer[index++] = 0; // idFieldLength
buffer[index++] = 0; // colormapType
buffer[index++] = (byte)(encoding ? 10 : 2); // RGB or RGB_RLE
buffer[index++] = 0; buffer[index++] = 0; // colormapOrigin
buffer[index++] = 0; buffer[index++] = 0; // colormapLength
buffer[index++] = 0; // colormapDepth
buffer[index++] = 0; buffer[index++] = 0; // originX
buffer[index++] = 0; buffer[index++] = 0; // originY
buffer[index++] = (byte)((width >> 0) & 0xFF); // width
buffer[index++] = (byte)((width >> 8) & 0xFF); // width
buffer[index++] = (byte)((height >> 0) & 0xFF); // height
buffer[index++] = (byte)((height >> 8) & 0xFF); // height
buffer[index++] = (byte)(8*elementCount); // depth
buffer[index++] = 0x20; // descriptor TODO alpha channel depth
if(encoding) {
index = encodeRLE(pixels, width, elementCount, order, buffer, index);
}
else {
index = writeRaw(pixels, buffer, index, elementCount, order);
}
// Copy Footer
for(int i=0; i<FOOTER.length; i++) {
buffer[index++] = FOOTER[i];
}
return buffer;
}
private static int writeRaw(int [] pixels, byte [] buffer, int index, int elementCount, TGAReader.Order order) {
if(elementCount == 3) {
// BGR
for(int i=0; i<pixels.length; i++) {
buffer[index++] = (byte)((pixels[i] >> order.blueShift) & 0xFF);
buffer[index++] = (byte)((pixels[i] >> order.greenShift) & 0xFF);
buffer[index++] = (byte)((pixels[i] >> order.redShift) & 0xFF);
}
}
else {
// BGRA
for(int i=0; i<pixels.length; i++) {
buffer[index++] = (byte)((pixels[i] >> order.blueShift) & 0xFF);
buffer[index++] = (byte)((pixels[i] >> order.greenShift) & 0xFF);
buffer[index++] = (byte)((pixels[i] >> order.redShift) & 0xFF);
buffer[index++] = (byte)((pixels[i] >> order.alphaShift) & 0xFF);
}
}
return index;
}
private static final int MODE_RESET = 0;
private static final int MODE_SELECT = 1;
private static final int MODE_SAME_COLOR = 2;
private static final int MODE_DIFFERENT_COLOR = 3;
private static int getEncodeSize(int [] pixels, int width, int elementCount) {
int size = 0;
int color = 0;
int mode = MODE_RESET;
int start = 0;
for(int i=0; i<pixels.length; i++) {
if(mode == MODE_RESET) {
color = pixels[i];
mode = MODE_SELECT;
start = i;
}
else if(mode == MODE_SELECT) {
mode = (color == pixels[i]) ? MODE_SAME_COLOR : MODE_DIFFERENT_COLOR;
color = pixels[i];
}
else if(mode == MODE_SAME_COLOR) {
if(color != pixels[i]) {
// packet + rleData
size += 1 + elementCount;
mode = MODE_SELECT;
color = pixels[i];
start = i;
}
else if((i-start) >= 127) {
size += 1 + elementCount;
mode = MODE_RESET;
}
}
else if(mode == MODE_DIFFERENT_COLOR) {
if(color == pixels[i]) {
// packet + rawData * count
size += 1 + elementCount*(i-1-start);
mode = MODE_SAME_COLOR;
color = pixels[i];
start = i-1;
}
else if((i-start) >= 127) {
size += 1 + elementCount*128;
mode = MODE_RESET;
}
}
if((i+1)%width == 0 && mode != MODE_RESET) {
if(mode == MODE_SAME_COLOR) {
size += 1 + elementCount;
}
else {
// MODE_SELECT or MODE_DIFFERENT_COLOR
size += 1 + elementCount*(i-start+1);
}
mode = MODE_RESET;
}
// update color
color = pixels[i];
}
if(mode != MODE_RESET) {
System.out.println("Error!");
}
return size;
}
private static int encodeRLE(int [] pixels, int width, int elementCount, TGAReader.Order order, byte [] buffer, int index) {
int color = 0;
int mode = MODE_RESET;
int start = 0;
for(int i=0; i<pixels.length; i++) {
if(mode == MODE_RESET) {
color = pixels[i];
mode = MODE_SELECT;
start = i;
}
else if(mode == MODE_SELECT) {
mode = (color == pixels[i]) ? MODE_SAME_COLOR : MODE_DIFFERENT_COLOR;
color = pixels[i];
}
else if(mode == MODE_SAME_COLOR) {
if(color != pixels[i]) {
// packet + rleData
index = encodeRLE(buffer, index, color, i-start, elementCount, order);
mode = MODE_SELECT;
color = pixels[i];
start = i;
}
else if((i-start) >= 127) {
index = encodeRLE(buffer, index, color, 128, elementCount, order);
mode = MODE_RESET;
}
}
else if(mode == MODE_DIFFERENT_COLOR) {
if(color == pixels[i]) {
// packet + rawData * count
index = encodeRLE(buffer, index, pixels, start, i-1-start, elementCount, order);
mode = MODE_SAME_COLOR;
color = pixels[i];
start = i-1;
}
else if((i-start) >= 127) {
index = encodeRLE(buffer, index, pixels, start, 128, elementCount, order);
mode = MODE_RESET;
}
}
if((i+1)%width == 0 && mode != MODE_RESET) {
if(mode == MODE_SAME_COLOR) {
index = encodeRLE(buffer, index, color, i-start+1, elementCount, order);
}
else {
// MODE_SELECT or MODE_DIFFERENT_COLOR
index = encodeRLE(buffer, index, pixels, start, i-start+1, elementCount, order);
}
mode = MODE_RESET;
}
// update color
color = pixels[i];
}
if(mode != MODE_RESET) {
System.out.println("Error!");
}
return index;
}
private static int encodeRLE(byte [] buffer, int index, int color, int count, int elementCount, TGAReader.Order order) {
buffer[index++] = (byte)(0x80 | (count-1));
buffer[index++] = (byte)((color >> order.blueShift) & 0xFF);
buffer[index++] = (byte)((color >> order.greenShift) & 0xFF);
buffer[index++] = (byte)((color >> order.redShift) & 0xFF);
if(elementCount == 4) {
buffer[index++] = (byte)((color >> order.alphaShift) & 0xFF);
}
return index;
}
private static int encodeRLE(byte [] buffer, int index, int [] pixels, int start, int count, int elementCount, TGAReader.Order order) {
buffer[index++] = (byte)(count-1);
for(int i=0; i<count; i++) {
int color = pixels[start+i];
buffer[index++] = (byte)((color >> order.blueShift) & 0xFF);
buffer[index++] = (byte)((color >> order.greenShift) & 0xFF);
buffer[index++] = (byte)((color >> order.redShift) & 0xFF);
if(elementCount == 4) {
buffer[index++] = (byte)((color >> order.alphaShift) & 0xFF);
}
}
return index;
}
private static boolean hasAlpha(int [] pixels, TGAReader.Order order) {
int alphaShift = order.alphaShift;
for(int i=0; i<pixels.length; i++) {
int alpha = (pixels[i] >> alphaShift) & 0xFF;
if(alpha != 0xFF) return true;
}
return false;
}
private static final byte [] FOOTER = {0,0,0,0,0,0,0,0,84,82,85,69,86,73,83,73,79,78,45,88,70,73,76,69,46,0}; // TRUEVISION-XFILE
}