Sign in to follow this  
  • entries
    557
  • comments
    1237
  • views
    422069

Untitled

Sign in to follow this  

84 views

Hnng. I just spent 3 hours writing a utility program to load a TGA file split into a 16x16 grid, and count the maximum width of each image in the bounding area for each of the 256 cells is.

As you can probably guess, this is for fonts. At work, our fonts are just arranged on a texture on a 16x16 pixel grid for each letter. But to work out the letter spacing properly, we need to know the width of each letter. Given a 512x512 texture, a single character has a maximum size of 32x32, but the letter 'I' may only be 4px across.

Now, we had a tool to generate a font file and definition file (Letter width for each character) from a windows font, which was relatively straightforward, and the artists were using. However, we didn't take into account artists desire to tinker with things; the font was generated fine, but then they added a 2 or 3 pixel border around each letter, and put some better shading on the letter. That means that:
1. The letters are no longer in their bounding boxes.
2. The font definition file is now gibberish.

Point 1 can be fixed my moving all the letters, which is pretty painless, but point 2 requires counting the pixel widths of each letter, which I'm responsible for (If a font doesn't work in game, I get told to fix it, and the artists always forget to update the font definition file (Which is a script file)). And since I can't afford to spend an hour or two per font, for 4 fonts on a build day, I've written a utility to load the TGA font file and find the width of each letter.

Actually, I could probably have done it in a little over an hour, but I decided to make the app more artist-friendly and make it support RLE TGA files, and the Nintendo Wii extended TGA format (Which isn't particularly difficult).

Oh the excitement. And, for your excitement, have the source code (Anyone spotting any bugs gets a rate++ and a cookie [smile]). It only grabs the alpha channel of the image, but could easily be modified to read the whole 32-bit pixel values (And I'd do that myself if I could be bothered and it wasn't 11pm).

//==================================================================================================
// Main.cpp - Program entry point
//==================================================================================================
#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#include
#include

//==================================================================================================

typedef unsigned char u8;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned int u32;

#pragma pack(push, 1)
struct TGAHeader
{
u8 nIDSize; // size of ID field that follows 18 byte header (0 usually)
u8 nColourMapType; // type of colour map 0=none, 1=has palette
u8 nImageType; // type of image 0=none,1=indexed,2=rgb,3=grey,+8=rle packed

s16 nColourMapStart; // first colour map entry in palette
s16 nColourMapLen; // number of colours in palette
u8 nColourMapBits; // number of bits per palette entry 15,16,24,32

s16 nStartX; // image x origin
s16 nStartY; // image y origin
s16 nWidth; // image width in pixels
s16 nHeight; // image height in pixels
u8 nBPP; // image bits per pixel 8,16,24,32
u8 nDescriptor; // image descriptor bits: 00vhaaaa (h=hflip, v=vflip, a=alpha bits)
};
#pragma pack(pop)

//==================================================================================================

u8* LoadTGA(const char* _szFilename, TGAHeader& _header)
{
// Open file
FILE* pFile = fopen(_szFilename, "rb");
if(!pFile)
{
printf("Could not open '%s'\n", _szFilename);
return NULL;
}

// Read header
if(fread(&_header, 1, sizeof(_header), pFile) != sizeof(_header))
{
fclose(pFile);
printf("Could not read TGA header from '%s'\n", _szFilename);
return NULL;
}

// Validate header
if(_header.nColourMapType != 0)
{
fclose(pFile);
printf("Palettised TGA files not supported.\n");
return NULL;
}
if(_header.nImageType != 2 && _header.nImageType != 10)
{
fclose(pFile);
printf("Only RGB TGA files supported (ImageType = %d).\n",
_header.nImageType);
return NULL;
}
if(_header.nBPP != 32)
{
fclose(pFile);
printf("Only 32-bit TGA files supported (BPP = %d).\n", _header.nBPP);
return NULL;
}
if(_header.nDescriptor & 0x20)
{
fclose(pFile);
printf("Only non-horizontally flipped TGA files supported.\n");
return NULL;
}
if(_header.nWidth < 0 || _header.nHeight < 0)
{
fclose(pFile);
printf("Image has invalid dimensions (%d x %d).\n", _header.nWidth, _header.nHeight);
return NULL;
}

// Get file size, then seek to just past image descriptor
fseek(pFile, 0, SEEK_END);
long nFileLength = ftell(pFile) - (sizeof(_header)+_header.nIDSize);
fseek(pFile, sizeof(_header)+_header.nIDSize, SEEK_SET);
if(nFileLength < 0)
{
fclose(pFile);
printf("Could not determine file size.\n");
return NULL;
}

// Allocate buffer to load file into and read it
size_t nBytes = (size_t)nFileLength;
u8* pBuffer = new u8[nBytes];
if(!pBuffer)
{
fclose(pFile);
printf("Out of memory allocating %d bytes\n", nBytes);
return NULL;
}
if(fread(pBuffer, 1, nBytes, pFile) != nBytes)
{
delete[] pBuffer;
fclose(pFile);
printf("Failed to read %d bytes from file. Corrupt image header?\n", nBytes);
return NULL;
}
fclose(pFile);

// Convert to 8-bit
size_t nImageBytes = _header.nWidth * _header.nHeight;
u8* pImage = new u8[nImageBytes];
if(!pImage)
{
delete[] pBuffer;
printf("Out of memory allocating %d bytes\n", nImageBytes);
return NULL;
}

// Copy / decode image
u8* pBuffOut = pImage;
if(_header.nImageType & 0x08)
{
// RLE encoded
size_t nPixelsRemain = _header.nWidth * _header.nHeight;
u8* pBuffIn = pBuffer;
u8* pInMax = pBuffer + nFileLength;
while(nPixelsRemain > 0)
{
u8 nRepetionCount = *pBuffIn++;
if(nRepetionCount & 0x80)
{
nRepetionCount = (nRepetionCount & 0x7f) + 1;
if(nRepetionCount > nPixelsRemain || pBuffIn+4 > pInMax)
{
delete[] pImage;
delete[] pBuffer;
printf("Corrupt image, apparently invalid RLE data.\n");
return NULL;
}
u8 nPixel = pBuffIn[3];
pBuffIn += 4;
for(size_t i=0; i *pBuffOut++ = nPixel;
}
else
{
nRepetionCount = (nRepetionCount & 0x7f) + 1;
if(nRepetionCount > nPixelsRemain || pBuffIn+nRepetionCount*4 > pInMax)
{
delete[] pImage;
delete[] pBuffer;
printf("Corrupt image, apparently invalid RLE data.\n");
return NULL;
}
for(size_t i=0; i {
u8 nPixel = pBuffIn[3];
pBuffIn += 4;
*pBuffOut++ = nPixel;
}
}
nPixelsRemain -= nRepetionCount;
}
}
else
{
// Not compressed
for(size_t i=0; i4)
*pBuffOut++ = pBuffer[i+3];
}
delete[] pBuffer;

// Vertically flip image
u8* pFlipped = new u8[nImageBytes];
if(!pFlipped)
{
delete[] pImage;
printf("Out of memory allocating %d bytes\n", nImageBytes);
return NULL;
}
for(int y=0; y<_header.nHeight; ++y)
{
memcpy(pFlipped + y*_header.nWidth, pImage + (_header.nHeight - y - 1)*_header.nWidth,
_header.nWidth);
}
delete[] pImage;
pImage = pFlipped;

/*
// Debug spew alpha mask
pFile = fopen("out.txt", "w");
for(int y=0; y<_header.nHeight; ++y)
{
for(int x=0; x<_header.nWidth; ++x)
{
if(pImage[y*_header.nWidth + x])
fprintf(pFile, "X");
else
fprintf(pFile, " ");
}
fprintf(pFile, "\n");
}
fclose(pFile);
*/

return pImage;
}

//==================================================================================================

int main(int argc, char** argv)
{
// Check params
if(argc != 2)
{
printf("FontCalc.exe - calculate ui.lua entries for a given font image\n"
"\n"
"Usage:\n"
" FontCalc.exe FontFile.tga\n");
return -1;
}

// Load image file
TGAHeader header;
u8* pImage = LoadTGA(argv[1], header);
if(!pImage)
return -2;

// Open output file
FILE* pFile = fopen("Font.txt", "w");
if(!pFile)
{
delete[] pImage;
printf("Could not open \"Font.txt\" for writing.\n");
return -3;
}

// Make sure it's square and a multiple of 16px
if(header.nWidth != header.nHeight)
{
delete[] pImage;
printf("Image is not square (Is %d x %d)\n", header.nWidth, header.nHeight);
return -4;
}
if(header.nWidth&0x0f)
{
delete[] pImage;
printf("Image is not a multiple of 16px in size (Is %d x %d)\n",
header.nWidth, header.nHeight);
return -5;
}
int nCharSize = header.nWidth / 16;

// Print status
printf("Processing '%s' (%d x %d), char size = (%d x %d), %s TGA\n",
argv[1], header.nWidth, header.nHeight, nCharSize, nCharSize,
(header.nImageType&0x08)?"RLE compressed":"uncompressed");

// Parse it
for(int nChar=0; nChar<256; ++nChar)
{
int nRowPix = (nChar / 16) * nCharSize;
int nColPix = (nChar % 16) * nCharSize ;
u8* pStart = pImage + (nRowPix * header.nWidth) + nColPix;

int xMax=-1;
for(int y=0; y {
u8* pRow = pStart + header.nWidth * y;
for(int x=0; x {
if(*pRow++)
{
if(x > xMax)
xMax = x;
}
}
}

if(xMax > -1)
{
fprintf(pFile, "\t\t{\"%c\", %d},\n", nChar, xMax+1);
if(xMax+1 == nCharSize)
{
printf("Warning: Character '%c' is possibly overlapping the previous character\n",
nChar+1);
}
}
}

fclose(pFile);
delete[] pImage;
printf("Font information written to Font.txt.\n");
return 0;
}

//==================================================================================================



In other news, Paint.NET is awesome. The only reason I still have Photoshop installed at work is that we need it for saving Nintendo Wii format textures. So I'm going to write a plugin for it as soon as I can. Because Photoshop annoys me in many, many ways.

Anyway. All done. Bed.
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now