Questions about TGA Files

Ok, it’s MUCH better now but not the 4-8x you promised. :wink:

Whereas before, it would take about 4-5 seconds to load it all in, it’s more around 1.5 seconds to load in 16 24-bit or 32-bit TGA files all of the 1024x864 variant with file sizes of 2593 KB. That all said, PNG’s still loaded in marginally faster at about 1 I’d estimate. I’ll post up the code in case I goofed up somewhere, but I did about what you said. I actually tried a variant where it read the whole file in, and it came out slower than the solution I’m posting where I read in the header as normal, then bulk read the image data and then use arrays from there.

All in all, it’s at least within the same order of magnitude now and more than acceptable for me, especially since the game won’t ever load this many images of this size at once, usually 1 per level and occasionally several if they are animated or if there’s a background and foreground image.


public static BufferedImage loadIntoImage(InputStream fis, boolean flipped) throws IOException {
		byte red = 0;
		byte green = 0;
		byte blue = 0;
		byte alpha = 0;
		
		BufferedInputStream bis = new BufferedInputStream(fis, 100000);
		DataInputStream dis = new DataInputStream(bis);
		
		// Read in the Header
		short idLength = (short) dis.read();
		short colorMapType = (short) dis.read();
		short imageType = (short) dis.read();
		short cMapStart = flipEndian(dis.readShort());
		short cMapLength = flipEndian(dis.readShort());
		short cMapDepth = (short) dis.read();
		short xOffset = flipEndian(dis.readShort());
		short yOffset = flipEndian(dis.readShort());
		
		width = flipEndian(dis.readShort());
		height = flipEndian(dis.readShort());
		pixelDepth = (short) dis.read();
		
		texWidth = width;
		texHeight = height;
		
		short imageDescriptor = (short) dis.read();
		
		// Skip image ID
		if(idLength > 0) 
		{
			bis.skip(idLength);
		}
		
		//Now we stop the normal process, read in bulk and then process.
		byte[] allData = null;
		byte[] rawData = null;
		
		if (pixelDepth == 32)
		{
			allData = new byte[texWidth * texHeight * 4];
			rawData = new byte[texWidth * texHeight * 4];
		}
		else
		{
			allData = new byte[texWidth * texHeight * 3];
			rawData = new byte[texWidth * texHeight * 3];
		}
		
		dis.read(allData);
		
		int pos = 0;
		
		if (pixelDepth == 24) {
			for (int i = height-1; i >= 0; i--) {
				for (int j = 0; j < width; j++) {
					blue = allData[pos];
					pos++;
					green = allData[pos];
					pos++;
					red = allData[pos];
					pos++;
					
					int ofs = ((j + (i * texWidth)) * 3);
					rawData[ofs] = (byte) red;
					rawData[ofs + 1] = (byte) green;
					rawData[ofs + 2] = (byte) blue;
				}
			}
		} else if (pixelDepth == 32) {
			if (flipped) {
				for (int i = height-1; i >= 0; i--) {
					for (int j = 0; j < width; j++) {
						blue = allData[pos];
						pos++;
						green = allData[pos];
						pos++;
						red = allData[pos];
						pos++;
						alpha = allData[pos];
						pos++;
						
						int ofs = ((j + (i * texWidth)) * 4);
						
						rawData[ofs] = (byte) red;
						rawData[ofs + 1] = (byte) green;
						rawData[ofs + 2] = (byte) blue;
						rawData[ofs + 3] = (byte) alpha;
						
						if (alpha == 0) {
							rawData[ofs + 2] = (byte) 0;
							rawData[ofs + 1] = (byte) 0;
							rawData[ofs] = (byte) 0;
						}
					}
				}
			} else {
				for (int i = 0; i < height; i++) {
					for (int j = 0; j < width; j++) {
						blue = allData[pos];
						pos++;
						green = allData[pos];
						pos++;
						red = allData[pos];
						pos++;
						alpha = allData[pos];
						pos++;
						
						int ofs = ((j + (i * texWidth)) * 4);
						
						rawData[ofs + 2] = (byte) red;
						rawData[ofs + 1] = (byte) green;
						rawData[ofs] = (byte) blue;
						rawData[ofs + 3] = (byte) alpha;
						
						if (alpha == 0) {
							rawData[ofs + 2] = (byte) 0;
							rawData[ofs + 1] = (byte) 0;
							rawData[ofs] = (byte) 0;
						}
					}
				}
			}
		}
		fis.close();
		
		//End Kev's Code
		
		DataBufferByte dataBuffer = new DataBufferByte
		(
			rawData, 
			rawData.length
		);
		
		int[] offsets = null;
		
		if(pixelDepth == 24)
		{
			int[] offsets24 = {0,1,2};
			offsets = offsets24;
		}
		
		else
		{
			int[] offsets32 = {0,1,2,3};
			offsets = offsets32;
		}
		
		PixelInterleavedSampleModel sampleModel = null;
		
		if(pixelDepth == 24)
		{
			sampleModel = new PixelInterleavedSampleModel
			(
				DataBuffer.TYPE_BYTE,
				texWidth, 
				texHeight,
				3,
				3 * texWidth,
				offsets
			);
		}
		
		else
		{
			sampleModel = new PixelInterleavedSampleModel
			(
				DataBuffer.TYPE_BYTE,
				texWidth, 
				texHeight,
				4,
				4 * texWidth,
				offsets
			);
		}
		
		WritableRaster raster = Raster.createWritableRaster
		(
			sampleModel,
			dataBuffer,
			new Point(0,0)
		);
		
		ColorModel cm = null;
		
		if(pixelDepth == 24)
		{
			cm = new ComponentColorModel
			(
				ColorSpace.getInstance
				(ColorSpace.CS_sRGB),
	            new int[] {8,8,8},
	            false,
	            false,
	            ComponentColorModel.OPAQUE,
	            DataBuffer.TYPE_BYTE
	        );
		}
		
		else
		{
			cm = new ComponentColorModel
			(
				ColorSpace.getInstance(ColorSpace.CS_sRGB),
                new int[] {8,8,8,8},
                true,
                false,
                ComponentColorModel.TRANSLUCENT,
                DataBuffer.TYPE_BYTE
            );
		}
		
		BufferedImage img = new BufferedImage(cm, raster, false, null);

		return img;
	}

This code:


						blue = allData[pos];
						pos++;
						green = allData[pos];
						pos++;
						red = allData[pos];
						pos++;
						alpha = allData[pos];
						pos++;
						
						int ofs = ((j + (i * texWidth)) * 4);
						
						rawData[ofs] = (byte) red;
						rawData[ofs + 1] = (byte) green;
						rawData[ofs + 2] = (byte) blue;
						rawData[ofs + 3] = (byte) alpha;
						
						if (alpha == 0) {
							rawData[ofs + 2] = (byte) 0;
							rawData[ofs + 1] = (byte) 0;
							rawData[ofs] = (byte) 0;
						}

Is not very fast. First of all, why do you have that (alpha==0), it won’t chnage anything, and when you were to mipmap your image, you’d get ugly dark edges in your scaled images. So I’d suggest you take that out.

For the real deal, why not change it in:


						int ofs = ((j + (i * texWidth))  << 2);
						
						rawData[ofs       ] = allData[pos + 2];
						rawData[ofs + 1] = allData[pos + 1];
						rawData[ofs + 2] = allData[pos       ];
						rawData[ofs + 3] = allData[pos + 3];

						pos += 4;

The alpha == 0 check is bespoke for icon loading where only the rgb value is used but where black is used as transparent on some systems for bitmask only transparency. - a hack to apply some form of alpha in these cass as well.

The getByte() replacement sounds useful (I’ll add soon) but PNG is still faster to just load data wise. As i mentioned at the top of the thread (I think) the percieved gain with TGA for most peope is the lack of data -> buffered image -> gl texture process when you’ve written a custom loader that can give you the data in the right fomat straight away.i.e. data -> texture.

The bottle neck with usng ImageIO is the conversion from buffered image to texture, not the data loading?

Kev

Which is why… after all of this, I think I’ll go with storing the image both as a PNG and a TGA. PNG for the editor and non-game related tasks and TGA for the game itself. Since I already stick everything in a JAR, the TGA’s took up practically no space anyways (<< less than the space the PNG’s took in the JAR).

I don’t notice much of a performance gain from this. It might be 0.1 seconds faster. Kinda hard to tell unless I try some formal benchmarking or operate on a larger dataset.

Simply put, it works wonders. My loading times are down to practically nothing now. Thanks guys!

wait a second, what works? Can you write a small conclusion?
I was quickly going through this thread, not fully reading everything and I noticed you guys talk about TGA as fastest for loading. Now in test PNG is better. I guess I could read everything (and I will) but still you could write a conclusion in few sentences, I like that :slight_smile:

Now in test PNG is better.

Java2D. The TGAs are 32bit and the PNGs are 8bit (indexed).

In lwjgl there is no awt fiddling… therefore its a lot faster there.

TGA as fastest for loading

DXTn is the fastest. Its directly loaded (unless a software decoding fallback for ancient cards is used), its 1/4th or 1/6th of the size (compared to raw stuff like tga) and its also quite compressible on top (zip/lzma/whatever).

edit: But DXTn isn’t suitable for all kinds of images. There has to be some catch, right? :wink:

DXTn’s that lossy compression format, isn’t it? I’m done with this image loading stuff for the moment, but I might look into that down the line if I’m greedy about performance. :slight_smile:

Yes, its lossy (therefore its not suitable for all kinds of images). But generally it works very well, I would say.