Page 2 of 4

Posted: Fri Jan 22, 2010 2:37 am
by Sajt
A nearest-neighbour reduction using luminosity weighting (wow... a lot of big words makes that sound more than what it is: extremely simple) produces a much more palatable image:

Image

But I'm sure that that fancier algorithm must be good for something. I wonder what sort of image it would do well on.

Posted: Fri Jan 22, 2010 2:59 am
by Baker
Works well enough for my purposes. Most textures that would be used in a Quake map aren't really "millions of colors" type textures.

I used what was available in FreeImage.

Quake palette conversion of Nexuiz:

Image

Maybe fun to work on the fixed palette conversions for future versions :D

/Btw ... I can't tell if you are being sarcastic, but if so I'd like to point out that your palette indexes in your sample are wrong.

Image

Posted: Fri Jan 22, 2010 6:10 am
by Sajt
I'm not really sure what I could have been sarcastic about. I would like to see an example of what sort of situations this special algorithm is designed for.

As for the palette, my program exported a PCX and I used PSP to convert it to a PNG so I could upload it. Paint Shop Pro rearranges the palette (or "optimizes" it?) when it saves a PNG. They still are the same Quake colours, though, just in a different order. (It's not like anyone in their right mind would use PNG for game textures, anyway.)

Looks like PSP has an option to disable that palette "optimization". I'll use that next time...

Posted: Fri Jan 22, 2010 1:38 pm
by Baker
Sajt wrote:I'm not really sure what I could have been sarcastic about. I would like to see an example of what sort of situations this special algorithm is designed for.
Could be a speed thing ...
My general research activities are in visual/multimedia computing and communications. I have published numerous algorithms for computer graphics and image processing (image coding in particular), some of which are being used by practitioners, such as a fast optimal color quantizer, and a Context-based Adaptive Lossless Image Codec (CALIC) which was developed jointly with Nasir Memon as a candidate algorithm for the new JPEG lossless standard.

Posted: Fri Jan 22, 2010 1:52 pm
by Teiman
Sajt wrote:... (It's not like anyone in their right mind would use PNG for game textures, anyway.)...
Oooops... hehehe.

Posted: Fri Jan 22, 2010 1:59 pm
by Baker
Teiman wrote:
Sajt wrote:... (It's not like anyone in their right mind would use PNG for game textures, anyway.)...
Oooops... hehehe.
It is a shame that TGA can't be used on the internet like PNG can. It is also a shame that TGA cannot easily be browsed with the thumbnail view in Windows.

Back when I was a fan of PNG textures in the past, that was what I liked about them: make them once, display them anywhere.

Posted: Fri Jan 22, 2010 2:30 pm
by c0burn
I use FastStone MaxView as my image viewer, and when installed the shell extension shows TGA thumbnails, which is nice.

Posted: Fri Jan 22, 2010 4:21 pm
by Teiman
I was trying to avoid it, but here goes:

Why? Why is a bad idea to use PNG?

Is not like JPG, it don't destroy the quality of the image. and it supports a alpha channel, two styles (1 bit alfa and 8 bits alfa )...

Why is not that good? do it randomize the palette in 8 bits mode or something?

Posted: Fri Jan 22, 2010 4:58 pm
by Spike
Tei:
png and zip use the same compression algorithm, but applied differently.
In png, its applied on a per-plane basis. In zip+tga its applied over the entire byte stream.
Compressing a tga in a zip means that you compress each pixel individually while png compresses each pixel 3/4 times. Repeating patterns and windows and stuff mean that tga has a smaller chance for repetition, but when there is some, there are a third less references in the window.

Basically the outcome is that you can block-decompress+decode a tga faster than you can decode+combine the multiple planes of a png.
And its a bit smaller too.

.rar+.tga or .tar.gz+.tga gives solid archives, which allows the reuse of compression windows over multiple similar files.

If the engine supports zips/pk3s, use tgas.
If the engine doesn't support zips/pk3s, you get even better compression from tgas inside a pak anyway, and the extra size weighed against the lack of compression doesn't hinder load times, but it does mean faster download speeds. However it does use more disk space. Disk space is rarely a concern nowadays.

pngs are nice and all... but... mneh.

Posted: Fri Jan 22, 2010 6:03 pm
by Baker
Teiman wrote:I was trying to avoid it, but here goes:

Why? Why is a bad idea to use PNG?
Another reason: TGA loading in the engine can just use the engine code. If you have some non-traditional platform, supporting TGA loading is as easy as any other platform. You don't need .dll files to load a TGA.

Case in point, FitzQuake and Enhanced GL/WinQuake support external TGA textures but need no external dlls.

Code: Select all

/*
=========================================================

TARGA LOADING

=========================================================
*/

#pragma pack(1)
typedef struct _TargaHeader {
	unsigned char 	id_length, colormap_type, image_type;
	unsigned short	colormap_index, colormap_length;
	unsigned char	colormap_size;
	unsigned short	x_origin, y_origin, width, height;
	unsigned char	pixel_size, attributes;
} TargaHeader;
#pragma pack()

/*
=============
LoadTGA
=============
*/
static byte *LoadTGA (char *name, int *width, int *height, qboolean alphablend)
{
	TargaHeader *targa_header;
	FILE	    *f;
	int	    columns, rows, numPixels;
	byte	    *pixbuf, *buf;
	byte	    *targa_rgba;
	int	    row, realrow, column;
	qboolean    upside_down, alpha, blend;
	char	    filename[MAX_OSPATH];

	sprintf (filename, "%s.tga", name);
	COM_FOpenFile (filename, &f);

	if (!f)
		return NULL;

	if (com_filesize < sizeof(TargaHeader))
		Sys_Error ("LoadTGA: can't read header in %s", filename);

	targa_header = malloc (com_filesize);

	if (!targa_header)
		Sys_Error ("LoadTGA1: error allocating %d bytes for %s", com_filesize, filename);

	if (fread (targa_header, 1, com_filesize, f) != com_filesize)
		Sys_Error ("LoadTGA: error reading %s", filename);

	fclose (f);

	targa_header->width = SwapShort (&targa_header->width);
	targa_header->height = SwapShort (&targa_header->height);

	if (targa_header->image_type!=2
		&& targa_header->image_type!=10)
		Sys_Error ("LoadTGA: %s: only type 2 and 10 targa RGB images supported, not %d", filename, targa_header->image_type);

	if (targa_header->colormap_type !=0
		|| (targa_header->pixel_size!=32 && targa_header->pixel_size!=24))
		Sys_Error ("LoadTGA: %s: only 24 and 32 bit images supported (no colormaps, type=%d, bpp=%d)", filename, targa_header->colormap_type, targa_header->pixel_size);

	columns = targa_header->width;
	rows = targa_header->height;
	numPixels = columns * rows;
	upside_down = !(targa_header->attributes & 0x20); // true => picture is stored bottom to top

	targa_rgba = malloc (numPixels*4);

	if (!targa_rgba)
		Sys_Error ("LoadTGA2: error allocating %d bytes for %s", numPixels*4, filename);

	blend = alphablend && gl_alphablend.value;
	alpha = targa_header->pixel_size == 32;

	buf = (byte *)(targa_header + 1);

	if (targa_header->id_length != 0)
		buf += targa_header->id_length; // skip TARGA image comment

	if (targa_header->image_type==2) {  // Uncompressed, RGB images
		for(row=rows-1; row>=0; row--) {
			realrow = upside_down ? row : rows - 1 - row;
			pixbuf = targa_rgba + realrow*columns*4;

			ChkBounds ("LoadTGA1", buf - (byte *)targa_header + columns * targa_header->pixel_size / 8, com_filesize, filename);
			ChkBounds ("LoadTGA2", pixbuf - targa_rgba + columns * 4, numPixels * 4, filename);

			for(column=0; column<columns; column++) {
				unsigned char red,green,blue,alphabyte;
				blue = *buf++;
				green = *buf++;
				red = *buf++;
				*pixbuf++ = red;
				*pixbuf++ = green;
				*pixbuf++ = blue;
				*pixbuf++ = alpha ? *buf++ : (blend ? (red + green + blue) / 3 : 255); // Better way?;
			}
		}
	}
	else if (targa_header->image_type==10) {   // Runlength encoded RGB images
		unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j;
		for(row=rows-1; row>=0; row--) {
			realrow = upside_down ? row : rows - 1 - row;
			pixbuf = targa_rgba + realrow*columns*4;
			for(column=0; column<columns; ) {
				packetHeader = *buf++;
				packetSize = 1 + (packetHeader & 0x7f);

				ChkBounds ("LoadTGA3", pixbuf - targa_rgba + packetSize * 4, numPixels * 4, filename);

				if (packetHeader & 0x80) {        // run-length packet
					ChkBounds ("LoadTGA4", buf - (byte *)targa_header + targa_header->pixel_size / 8, com_filesize, filename);
					blue = *buf++;
					green = *buf++;
					red = *buf++;
					alphabyte = alpha ? *buf++ : (blend ? (red + green + blue) / 3 : 255); // Better way?;

					for(j=0;j<packetSize;j++) {
						*pixbuf++ = red;
						*pixbuf++ = green;
						*pixbuf++ = blue;
						*pixbuf++ = alphabyte;
						column++;
						if (column==columns) { // run spans across rows
							column=0;
							if (row>0)
								row--;
							else
								goto breakOut;
							realrow = upside_down ? row : rows - 1 - row;
							pixbuf = targa_rgba + realrow*columns*4;
						}
					}
				}
				else {                            // non run-length packet
					ChkBounds ("LoadTGA5", buf - (byte *)targa_header + packetSize * targa_header->pixel_size / 8, com_filesize, filename);
					for(j=0;j<packetSize;j++) {
						blue = *buf++;
						green = *buf++;
						red = *buf++;
						*pixbuf++ = red;
						*pixbuf++ = green;
						*pixbuf++ = blue;
						*pixbuf++ = alpha ? *buf++ : (blend ? (red + green + blue) / 3 : 255); // Better way?;
						column++;
						if (column==columns) { // pixel packet run spans across rows
							column=0;
							if (row>0)
								row--;
							else
								goto breakOut;
							realrow = upside_down ? row : rows - 1 - row;
							pixbuf = targa_rgba + realrow*columns*4;
						}
					}
				}
			}
			breakOut:;
		}
	}

	*width = targa_header->width;
	*height = targa_header->height;

	free (targa_header);

	return targa_rgba;
}

Posted: Fri Jan 22, 2010 7:55 pm
by Sajt
Also, the LibPNG loader is notoriously slow.

Of course, much better than either PNG or zipped TGA is the mysterious "dmip" format... but that's another story.

Posted: Fri Jan 22, 2010 9:59 pm
by Teiman
Uh.. ok, thanks guys ( Sajt, Baker, Spike).

Now.. I remember the libpng library as a memory-hungry and unstable one.

The tga in a zip thing is very interesting.

Posted: Sat Jan 23, 2010 7:01 am
by ceriux
made a news post about it on my moddb group

http://www.moddb.com/groups/quakedb/new ... rsion-tool

Posted: Sun Jan 24, 2010 2:21 pm
by negke
Good job.
Especially the 50% reduction will make batch conversion much more convenient in many cases.

Posted: Wed Feb 17, 2010 6:11 pm
by Baker
Some things that this could do better ...

Take the #water #lava textures and for the Quake portion of the process, turn them into *water and *lava, etc.

Apparently a 16 pixels is the minimum Quake texture dimension, not 8. Reference: http://www.celephais.net/stuff/texturefaq.htm

And apparently liquids and clip should be 64x64 for software renderers.

And sky textures 256x128. Although since Quake 3 and no other game uses Quake sky textures, such a conversion enforcement is a little "unneeded".