535 lines
16 KiB
C
535 lines
16 KiB
C
/*
|
|
TGA 2 SPR SOURCECODE
|
|
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2016-2019 Marco "eukara" Hladik <marco at icculus.org>
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
This tool converts 24-bit, uncompressed Targa files into Quake sprites.
|
|
You just have to give it a few more infos.
|
|
|
|
Syntax:
|
|
.\tga2spr sprite.qc
|
|
|
|
In which "sprite.qc" is a plaintext file containing a number of commands.
|
|
Notice that you can also pop in a palette.lmp from whatever mod you're
|
|
targetting into the same directory. By default it uses Quake's palette.
|
|
It will not add any dithering.
|
|
If you want any dithering during palettization, use an external tool.
|
|
The GNU Image Manipulation Program provides that feature.
|
|
Just remember to export it as a 24-bit image again.
|
|
|
|
================
|
|
LIST OF COMMANDS
|
|
================
|
|
output [STRING]
|
|
Specifies the output sprite. E.g. flame.spr
|
|
identifer [CHARS]
|
|
Specifies the magic number. In case you use some modded engine that allows
|
|
that. Default: IDSP
|
|
version [INT]
|
|
Specifies the sprite version. Default 1.
|
|
type [INT]
|
|
Specifies the sprite type. Used for orientation. Default is 0.
|
|
0: parallel upright
|
|
1: facing upright
|
|
2: parallel
|
|
3: oriented
|
|
4: parallel oriented
|
|
radius [FLOAT]
|
|
Default is 1.0. Not sure if even used.
|
|
synctype [INT]
|
|
Default is 0. If not 0, animation starts at random offsets.
|
|
maxwidth [INT]
|
|
Used for the visible bounding box. Use the width value of the largest frame.
|
|
maxheight [INT]
|
|
Used for the visible bounding box. Use the height value of the largest
|
|
frame.
|
|
reserved [INT]
|
|
Unused in default Quake. Kurok uses this I believe. Default is 0.
|
|
frame [TGANAME] [OFFSET X] [OFFSET Y]
|
|
Single frame. Loads for [TGANAME] (e.g. flame1.tga) and specifies an offset
|
|
in INT form (X and Y)
|
|
anim [TGATITLE] [NUMFRAMES] [OFFSET X] [OFFSET Y] [...FPS*FRAME]
|
|
An entire animationgroup. TGATITLE is the Targa name without extension.
|
|
E.g. if you specify "flame" it will look for flame_1.tga, flame_2.tga and so
|
|
on.
|
|
You then specify the number of frames and offset of that group and a
|
|
frame-delay for each frame in the animation. Play with it.
|
|
Keep in mind that some engines ignore variable framerates in sprites.
|
|
|
|
You don't have to use ALL available parameters.
|
|
For example you have a sprite that's 64x64 in size.
|
|
3 Targa images, One is static (wow.tga) and the two others are meant to be an
|
|
animation sequence (face_1.tga, face_2.tga).
|
|
|
|
Then you'd have this:
|
|
|
|
output test.spr
|
|
maxwidth 64
|
|
maxheight 64
|
|
frame wow 0 0
|
|
anim face 2 0 0 8 5
|
|
|
|
For every frame inside the anim parameter, you should append a FPS number.
|
|
Otherwise a value of 1 is assumed for each frame.
|
|
In the above exmaple, face_1 will skip to the next at 8 fps, while the
|
|
other will do so at 5 fps.
|
|
|
|
Despite the somewhat in-complete implementation of the SPR spec in most engines.
|
|
I hope this tool will be of use to somebody.
|
|
|
|
Use the program as-is. If you notice any glaring problems feel free
|
|
to mail me (marco at icculus dot org) and we can talk about them.
|
|
|
|
Due to the way it's written, it doesn't allocate much memory, it streams it from
|
|
the input right to the output. This is by design. This way you can export large
|
|
sprites (gigabytes worth) even on DOS. Use at your own risk.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <malloc.h>
|
|
#include <string.h>
|
|
|
|
#define TGAHEADER 18
|
|
#define QPALSIZE 768
|
|
|
|
typedef struct {
|
|
char identifer[4];
|
|
long version;
|
|
long sprtype;
|
|
float radius;
|
|
long max_width;
|
|
long max_height;
|
|
long total_frames;
|
|
long unused;
|
|
long synctype;
|
|
} spr_header_t;
|
|
|
|
typedef unsigned char byte;
|
|
|
|
typedef union {
|
|
int rgba:32;
|
|
struct {
|
|
byte b:8;
|
|
byte g:8;
|
|
byte r:8;
|
|
byte a:8;
|
|
} u;
|
|
} pixel_t;
|
|
|
|
byte cust_pal[QPALSIZE]; /* custom 256 color palette */
|
|
|
|
/* fallback palette from Quake, put into the Public Domain by John Carmack */
|
|
pixel_t pal_fallb[256] = {
|
|
0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b,
|
|
0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbb,0xcbcbcb,0xdbdbdb,0xebebeb,
|
|
0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b,
|
|
0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23,
|
|
0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767,
|
|
0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383b,0x8b8bcb,
|
|
0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07,
|
|
0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f,
|
|
0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000,
|
|
0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000,
|
|
0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307,
|
|
0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723,
|
|
0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b,
|
|
0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b,
|
|
0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733,
|
|
0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397,
|
|
0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b,
|
|
0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707,
|
|
0xb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b,
|
|
0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707,
|
|
0xdbc3b,0xcb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353,
|
|
0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07,
|
|
0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f,
|
|
0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07,
|
|
0xfff31b,0xefdf17,0xdbcb13,0xcb70f,0xba70f,0xab970b,0x9b8307,0x8b7307,
|
|
0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700,
|
|
0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f,
|
|
0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f,
|
|
0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b,
|
|
0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b,
|
|
0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000,
|
|
0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53
|
|
};
|
|
|
|
byte c_last; /* last palette index we chose */
|
|
pixel_t last_px; /* last RGB value we chose */
|
|
|
|
/* quickly translate 24 bit RGB value to our palette */
|
|
byte pal24to8(byte r, byte g, byte b)
|
|
{
|
|
pixel_t px;
|
|
byte c_red, c_green, c_blue, c_best, l;
|
|
int dist, best;
|
|
|
|
/* compare the last with the current pixel color for speed */
|
|
if ((last_px.u.r == r) && (last_px.u.g == g) && (last_px.u.b == b)) {
|
|
return c_last;
|
|
}
|
|
|
|
px.u.r = last_px.u.r = r;
|
|
px.u.g = last_px.u.g = g;
|
|
px.u.b = last_px.u.b = b;
|
|
|
|
best = 255 + 255 + 255;
|
|
c_last = c_best = c_red = c_green = c_blue = 255;
|
|
l = 0;
|
|
|
|
while (1) {
|
|
if ((cust_pal[l * 3 + 0] == r) && (cust_pal[l * 3 + 0] == g)
|
|
&& (cust_pal[l * 3 + 0] == b))
|
|
{
|
|
last_px.u.r = cust_pal[l * 3 + 0];
|
|
last_px.u.g = cust_pal[l * 3 + 1];
|
|
last_px.u.b = cust_pal[l * 3 + 2];
|
|
c_last = l;
|
|
return l;
|
|
}
|
|
|
|
c_red = abs(cust_pal[l * 3 + 0] - px.u.r);
|
|
c_green = abs(cust_pal[l * 3 + 1] - px.u.g);
|
|
c_blue = abs(cust_pal[l * 3 + 2] - px.u.b);
|
|
dist = (c_red + c_green + c_blue);
|
|
|
|
/* is it better than the last? */
|
|
if (dist < best) {
|
|
best = dist;
|
|
c_best = l;
|
|
}
|
|
|
|
if (l != 255) {
|
|
l++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
c_last = c_best;
|
|
return c_best;
|
|
}
|
|
|
|
void parse_targa(FILE *fdesc, char *tga_filename, int ofs_x, int ofs_y)
|
|
{
|
|
FILE *fTGA; /* File Ident of the TARGA */
|
|
byte tga_header[TGAHEADER]; /* TARGA Header (18 bytes usually) */
|
|
byte *tga_buff; /* 24bit BGR input buffer + TGA header */
|
|
int col, row, done; /* used for the sorting loop */
|
|
int img_w, img_h; /* Dimensions */
|
|
byte bTemp;
|
|
|
|
fTGA = fopen(tga_filename, "rb");
|
|
|
|
/* Check whether the file exists or not */
|
|
if (!fTGA) {
|
|
fprintf(stderr, "couldn't find %s\n", tga_filename);
|
|
exit(0);
|
|
}
|
|
|
|
/* Put the TARGA header into the buffer for validation */
|
|
fread(tga_header, 1, TGAHEADER, fTGA);
|
|
|
|
/* only allow uncompressed, 24bit TARGAs */
|
|
if (tga_header[2] != 2) {
|
|
fprintf(stderr, "%s should be an uncompressed image\n", tga_filename);
|
|
return;
|
|
}
|
|
if (tga_header[16] != 24) {
|
|
fprintf(stderr, "%s is not 24 bit in depth\n", tga_filename);
|
|
return;
|
|
}
|
|
|
|
/* Read the resolution into an int (TGA uses shorts for the dimensions) */
|
|
img_w = (tga_header[12]) | (tga_header[13] << 8);
|
|
img_h = (tga_header[14]) | (tga_header[15] << 8);
|
|
|
|
printf("\tframe image size: %d x %d\n", img_w, img_h);
|
|
tga_buff = malloc(img_w * img_h * 3);
|
|
|
|
if (tga_buff == NULL) {
|
|
fprintf(stderr, "error: Memory allocation failed. Exiting.\n");
|
|
exit(0);
|
|
}
|
|
|
|
/* Skip to after the TARGA HEADER... and then read the buffer */
|
|
fseek(fTGA, TGAHEADER, SEEK_SET);
|
|
fread(tga_buff, 1, img_w * img_h * 3, fTGA);
|
|
fclose(fTGA);
|
|
|
|
/* Let's write the sprite */
|
|
fwrite(&ofs_x, 1, 4, fdesc);
|
|
fwrite(&ofs_y, 1, 4, fdesc);
|
|
fwrite(&img_w, 1, 4, fdesc);
|
|
fwrite(&img_h, 1, 4, fdesc);
|
|
|
|
/* Translate the BGR values into indexed entries and flip */
|
|
done = 0;
|
|
for (row = img_h - 1; row >= 0; row--) {
|
|
for (col = 0; col < img_w; col++) {
|
|
bTemp = pal24to8(tga_buff[((row * (img_w * 3)) + (col * 3 + 2))],
|
|
tga_buff[((row * (img_w * 3)) + (col * 3 + 1))],
|
|
tga_buff[((row * (img_w * 3)) + (col * 3 + 0))]);
|
|
fwrite(&bTemp, 1, 1, fdesc);
|
|
done++;
|
|
}
|
|
}
|
|
free(tga_buff);
|
|
}
|
|
|
|
void process_qc(char *filename)
|
|
{
|
|
FILE *spr_file;
|
|
FILE *qc_file;
|
|
spr_header_t spr_header;
|
|
char qcline[255];
|
|
char out_filename[32];
|
|
char tga_filename[64];
|
|
char *tok;
|
|
int ofs_x;
|
|
int ofs_y;
|
|
long single;
|
|
float fps;
|
|
int num_frames;
|
|
int i;
|
|
|
|
/* Default values */
|
|
spr_header.identifer[0] = 'I';
|
|
spr_header.identifer[1] = 'D';
|
|
spr_header.identifer[2] = 'S';
|
|
spr_header.identifer[3] = 'P';
|
|
spr_header.version = 1;
|
|
spr_header.sprtype = 0;
|
|
spr_header.radius = 1.0f;
|
|
spr_header.max_width = 0;
|
|
spr_header.max_height = 0;
|
|
spr_header.total_frames = 0;
|
|
spr_header.unused = 0;
|
|
spr_header.synctype = 0;
|
|
|
|
if ((qc_file = fopen(filename, "r")) == NULL) {
|
|
fprintf(stderr, "cannot find %s\n", filename);
|
|
return;
|
|
}
|
|
|
|
fprintf(stdout, "processing control file %s\n", filename);
|
|
|
|
/* Read it the first time to cache the header, get the amount of frames */
|
|
while (fgets(qcline, sizeof(qcline), qc_file) != NULL) {
|
|
const char* headercmd = strtok(qcline, " ");
|
|
|
|
if (strcmp(headercmd , "output") == 0) {
|
|
strcpy(out_filename, strtok(NULL, " \n"));
|
|
} else if (strcmp(headercmd , "identifer") == 0) {
|
|
const char* cIdent = strtok(NULL, " \n");
|
|
spr_header.identifer[0] = cIdent[0];
|
|
spr_header.identifer[1] = cIdent[1];
|
|
spr_header.identifer[2] = cIdent[2];
|
|
spr_header.identifer[3] = cIdent[3];
|
|
} else if (strcmp(headercmd , "version") == 0) {
|
|
spr_header.version = atoi(strtok(NULL, " \n"));
|
|
} else if (strcmp(headercmd , "type") == 0) {
|
|
spr_header.sprtype = atoi(strtok(NULL, " \n"));
|
|
} else if (strcmp(headercmd , "radius") == 0) {
|
|
spr_header.radius = (float)atof(strtok(NULL, " \n"));
|
|
} else if (strcmp(headercmd , "synctype") == 0) {
|
|
spr_header.synctype = atoi(strtok(NULL, " \n"));
|
|
} else if (strcmp(headercmd , "maxwidth") == 0) {
|
|
spr_header.max_width = atoi(strtok(NULL, " \n"));
|
|
} else if (strcmp(headercmd , "maxheight") == 0) {
|
|
spr_header.max_height = atoi(strtok(NULL, " \n"));
|
|
} else if (strcmp(headercmd , "reserved") == 0) {
|
|
spr_header.unused = atoi(strtok(NULL, " \n"));
|
|
} else if (strcmp(headercmd , "frame") == 0) {
|
|
spr_header.total_frames++;
|
|
} else if (strcmp(headercmd , "anim") == 0) {
|
|
spr_header.total_frames++;
|
|
}
|
|
}
|
|
|
|
/* Go back to the start to start parsing the frames properly */
|
|
fseek(qc_file, 0, SEEK_SET);
|
|
|
|
/* Too lazy */
|
|
if (out_filename != NULL) {
|
|
spr_file = fopen(out_filename, "w+b");
|
|
} else {
|
|
spr_file = fopen("output.spr", "w+b");
|
|
}
|
|
|
|
/* Write the header first */
|
|
fwrite(&spr_header.identifer, 1, 4, spr_file);
|
|
fwrite(&spr_header.version, 1, 4, spr_file);
|
|
fwrite(&spr_header.sprtype, 1, 4, spr_file);
|
|
fwrite(&spr_header.radius, 1, 4, spr_file);
|
|
fwrite(&spr_header.max_width, 1, 4, spr_file);
|
|
fwrite(&spr_header.max_height, 1, 4, spr_file);
|
|
fwrite(&spr_header.total_frames, 1, 4, spr_file);
|
|
fwrite(&spr_header.unused, 1, 4, spr_file);
|
|
fwrite(&spr_header.synctype, 1, 4, spr_file);
|
|
|
|
printf("header info:\n");
|
|
printf("\tidentifer: %s\n", spr_header.identifer);
|
|
printf("\tversion: %i\n", spr_header.version);
|
|
printf("\ttype: %i\n", spr_header.sprtype);
|
|
printf("\tradius: %f\n", spr_header.radius);
|
|
printf("\tmax Width: %i\n", spr_header.max_width);
|
|
printf("\tmax Height: %i\n", spr_header.max_height);
|
|
printf("\tframes: %i\n", spr_header.total_frames);
|
|
printf("\treserved: %i\n", spr_header.unused);
|
|
printf("\tsync type: %i\n\n", spr_header.synctype);
|
|
|
|
/* Read the TGAs and write them directly to the file */
|
|
while (fgets(qcline, sizeof(qcline), qc_file) != NULL) {
|
|
const char* framecmd = strtok(qcline, " ");
|
|
|
|
/* Single frames */
|
|
if (strcmp(framecmd , "frame") == 0) {
|
|
tok = strtok(NULL, " \n");
|
|
if (tok == NULL) {
|
|
fprintf(stderr, "\terror: nspecified filename for frame!\n");
|
|
continue;
|
|
}
|
|
sprintf(tga_filename, "%s.tga", tok);
|
|
|
|
tok = strtok(NULL, " \n");
|
|
if (tok == NULL) {
|
|
fprintf(stderr, "\terror: unspecified offset (x) for frame!\n");
|
|
continue;
|
|
}
|
|
ofs_x = atoi(tok);
|
|
|
|
tok = strtok(NULL, " \n");
|
|
if (tok == NULL) {
|
|
fprintf(stderr, "\terror: unspecified offset (y) for frame!\n");
|
|
continue;
|
|
}
|
|
ofs_y = atoi(tok);
|
|
|
|
printf("saving frame %s (%i, %i)\n", tga_filename, ofs_x, ofs_y);
|
|
|
|
/* Group */
|
|
single = 0;
|
|
fwrite(&single, 1, 4, spr_file);
|
|
|
|
/* Frame */
|
|
parse_targa(spr_file, tga_filename, ofs_x, ofs_y);
|
|
printf("\n");
|
|
}
|
|
|
|
/* Animated groups */
|
|
if (strcmp(framecmd , "anim") == 0) {
|
|
const char* basename = strtok(NULL, " \n");
|
|
if (basename == NULL) {
|
|
fprintf(stderr, "\terror: unknown filename for anim!\n");
|
|
continue;
|
|
}
|
|
|
|
tok = strtok(NULL, " \n");
|
|
if (tok == NULL) {
|
|
fprintf(stderr, "\terror: unknown number of frames in anim!\n");
|
|
continue;
|
|
}
|
|
num_frames = atoi(tok);
|
|
|
|
tok = strtok(NULL, " \n");
|
|
if (tok == NULL) {
|
|
fprintf(stderr, "\terror: unknown offset (x) in anim!\n");
|
|
continue;
|
|
}
|
|
ofs_x = atoi(tok);
|
|
|
|
tok = strtok(NULL, " \n");
|
|
if (tok == NULL) {
|
|
fprintf(stderr, "\terror: unknown offset (y) in anim!\n");
|
|
continue;
|
|
}
|
|
ofs_y = atoi(tok);
|
|
|
|
printf("saving group %s (%i, %i)\n", basename, ofs_x, ofs_y);
|
|
|
|
/* Group */
|
|
single = 0x1;
|
|
fwrite(&single, 1, 4, spr_file);
|
|
fwrite(&num_frames, 1, 4, spr_file);
|
|
|
|
for (i = 0; i < num_frames; i++) {
|
|
tok = strtok(NULL, " \n");
|
|
if (tok == NULL) {
|
|
fprintf(stderr, "\terror: no fps for frame %i in anim!\n", i);
|
|
fps = 1.0f;
|
|
} else {
|
|
fps = (float)atof(tok);
|
|
}
|
|
|
|
fwrite(&fps, 1, 4, spr_file);
|
|
printf("\tframe %i animated at %fFPS\n", i+1, fps);
|
|
}
|
|
|
|
for (i = 0; i < num_frames; i++) {
|
|
sprintf(tga_filename, "%s_%i.tga", basename, i);
|
|
parse_targa(spr_file, tga_filename, ofs_x, ofs_y);
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
fclose(spr_file);
|
|
fclose(qc_file);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int c;
|
|
short p;
|
|
FILE *fPAL;
|
|
|
|
if (argc <= 1) {
|
|
fprintf(stderr, "usage: tga2spr [file.qc ...]\n");
|
|
return 1;
|
|
}
|
|
|
|
fPAL = fopen("palette.lmp", "rb");
|
|
|
|
if (!fPAL) {
|
|
fprintf(stdout, "no palette.lmp found, using builtin palette.\n");
|
|
for (p = 0; p < 256; p++) {
|
|
cust_pal[p * 3 + 0] = pal_fallb[p].u.r;
|
|
cust_pal[p * 3 + 1] = pal_fallb[p].u.g;
|
|
cust_pal[p * 3 + 2] = pal_fallb[p].u.b;
|
|
}
|
|
} else {
|
|
fprintf(stdout, "custom palette.lmp found\n");
|
|
fread(cust_pal, 1, QPALSIZE, fPAL);
|
|
fclose(fPAL);
|
|
}
|
|
|
|
for (c = 1; c < argc; c++)
|
|
process_qc(argv[c]);
|
|
|
|
return 0;
|
|
}
|