commit
967b43b549
7 changed files with 1326 additions and 0 deletions
@ -0,0 +1,38 @@
|
||||
Quake Tools |
||||
|
||||
Each one of them is a single C file that should compile OOTB on any platform. |
||||
E.g. gcc -o pal2tga pal2tga.c |
||||
|
||||
Their usage is pretty consistent - you supply the input files. |
||||
PAL2TGA wants a .lmp palette file, TGA2PAL wants a .tga image file - and so on. |
||||
|
||||
They are all released under the MIT License. Binaries for Win32 are outdated but usable. |
||||
|
||||
TrueVision Targa seems to be one of the saner image formats. The GIMP handles those fine. |
||||
Most advanced editors do... |
||||
|
||||
PAL2TGA |
||||
Converts a Quake palette file into a 24bit TrueVision Targa file for direct editing. |
||||
pal2tga.c |
||||
|
||||
TGA2PAL |
||||
Imports a 16x16 24bit Targa (like one generated by PAL2TGA) and converts it into a Quake palette file. |
||||
tga2pal.c |
||||
|
||||
LMP2TGA |
||||
Convert Quake graphic lump files (.lmp) into a TrueVision Targa for editing. |
||||
Allows use of a custom palette.lmp file for non-standard modifications/games. |
||||
Note: colormap.lmp and palette.lmp are no typical graphic lumps - opening them |
||||
will most likely result in an error when validating the header. |
||||
lmp2tga.c |
||||
|
||||
TGA2LMP |
||||
Converts a 24bit Targa into a Quake graphic lump file. |
||||
Allows use of a custom palette.lmp file for non-standard modifications/games. |
||||
tga2lmp.c |
||||
|
||||
TGA2SPR |
||||
Converts 24bit Targas into a Quake sprite lump file. |
||||
Allows use of a custom palette.lmp file for non-standard modifications/games, like the others... |
||||
Arguably the most complex one, README instructions required! |
||||
tga2spr.c |
@ -0,0 +1,193 @@
|
||||
/*
|
||||
LMP 2 TGA 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. |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <malloc.h> |
||||
#include <string.h> |
||||
#include <sys/stat.h> |
||||
|
||||
#define TGAHEADER 18 |
||||
#define QPALSIZE 768 |
||||
|
||||
typedef union |
||||
{ |
||||
int rgba:32;
|
||||
struct { |
||||
unsigned char b:8; |
||||
unsigned char g:8; |
||||
unsigned char r:8; |
||||
unsigned char a:8; |
||||
} u; |
||||
} palette_t; |
||||
|
||||
/* Fallback palette from Quake, put into the Public Domain by John Carmack */ |
||||
palette_t pal_fallb[256] = { |
||||
0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, |
||||
0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,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,0x8383bb,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, |
||||
0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, |
||||
0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, |
||||
0xdbc3bb,0xcbb3a7,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,0xcbb70f,0xbba70f,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 |
||||
}; |
||||
|
||||
unsigned char cust_pal[QPALSIZE]; /* Custom 256 color palette */ |
||||
|
||||
void process_lmp2tga(char *filename) |
||||
{ |
||||
FILE *fLMP; |
||||
int lmp_header[2]; /* Resolution of loaded LUMP file */ |
||||
unsigned char *lmp_buff; /* Buffer of the LUMP */ |
||||
struct stat lmp_st; |
||||
FILE *fTGA; |
||||
unsigned char *tga_buff; /* 24bit BGA output buffer + TGA header */ |
||||
int col, row, done; /* used for the sorting loop */ |
||||
|
||||
fLMP = fopen(filename, "rb"); |
||||
|
||||
if (!fLMP) { |
||||
fprintf(stderr, "couldn't find %s\n", filename); |
||||
return; |
||||
} |
||||
|
||||
stat(filename, &lmp_st); |
||||
|
||||
if (lmp_st.st_size <= 8) { |
||||
fprintf(stderr, "couldn't read %s\n", filename); |
||||
return; |
||||
} |
||||
|
||||
fread(lmp_header, sizeof(int) * 2, 1, fLMP); |
||||
|
||||
/* Other types of lumps (e.g. palette.lmp) don't have a header, skip them */ |
||||
if (lmp_st.st_size != (lmp_header[0] * lmp_header[1] + 8)) { |
||||
fprintf(stderr, "incomplete lump %s, skipping\n", filename); |
||||
return; |
||||
} |
||||
|
||||
lmp_buff = malloc(lmp_header[0] * lmp_header[1]); |
||||
fseek(fLMP, sizeof(int) * 2, SEEK_SET); |
||||
fread(lmp_buff, 1, lmp_header[0] * lmp_header[1], fLMP); |
||||
fclose(fLMP); |
||||
|
||||
/* Allocate enough memory for the header + buffer of the TARGA */ |
||||
tga_buff = malloc((lmp_header[0] * lmp_header[1] * 3) + TGAHEADER); |
||||
|
||||
memset (tga_buff, 0, 18); |
||||
tga_buff[2] = 2; /* Uncompressed TARGA */ |
||||
tga_buff[12] = lmp_header[0] & 0xFF; /* Width */ |
||||
tga_buff[13] = lmp_header[0] >> 8; |
||||
tga_buff[14] = lmp_header[1] & 0xFF; /* Height */ |
||||
tga_buff[15] = lmp_header[1] >> 8; |
||||
tga_buff[16] = 24; /* Color depth */ |
||||
|
||||
/* TARGAs are flipped in a messy way,
|
||||
* so we gotta do some sorting magic (vertical flip) */ |
||||
done = 0; /* readability */ |
||||
|
||||
for (row = lmp_header[1] - 1; row >= 0; row--) { |
||||
for (col = 0; col < lmp_header[0]; col++) { |
||||
tga_buff[18 + ((row * (lmp_header[0] * 3)) + (col * 3 + 0))] =
|
||||
cust_pal[lmp_buff[done] * 3 + 2];
|
||||
|
||||
tga_buff[18 + ((row * (lmp_header[0] * 3)) + (col * 3 + 1))] =
|
||||
cust_pal[lmp_buff[done] * 3 + 1]; |
||||
|
||||
tga_buff[18 + ((row * (lmp_header[0] * 3)) + (col * 3 + 2))] =
|
||||
cust_pal[lmp_buff[done] * 3 + 0]; |
||||
done++; |
||||
} |
||||
} |
||||
|
||||
/* FIXME: We assume too much!
|
||||
* Save the output to FILENAME.tga |
||||
* This is ugly when the input name has no .lmp extension. |
||||
* But who's going to try that. ...right? */ |
||||
filename[strlen(filename)-3] = 't'; |
||||
filename[strlen(filename)-2] = 'g'; |
||||
filename[strlen(filename)-1] = 'a'; |
||||
fprintf(stdout, "writing %s\n", filename); |
||||
fTGA = fopen(filename, "w+b"); |
||||
fwrite(tga_buff, 1, (lmp_header[0] * lmp_header[1] * 3) + TGAHEADER, fTGA); |
||||
fclose(fTGA); |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
int c; |
||||
short p; |
||||
FILE *fPAL; |
||||
|
||||
if (argc <= 1) { |
||||
fprintf(stderr, "usage: lmp2tga [file ...]\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_lmp2tga(argv[c]); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,106 @@
|
||||
/*
|
||||
PAL 2 TGA 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. |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <sys/stat.h> |
||||
|
||||
#define TGAHEADER 18 |
||||
#define QPALSIZE 768 |
||||
#define T2PVERSION "1.1" |
||||
|
||||
void process_pal2tga(char *filename) |
||||
{ |
||||
FILE *fLMP; |
||||
unsigned char pal_buff[QPALSIZE]; |
||||
FILE *fTGA; |
||||
unsigned char tga_buff[QPALSIZE + TGAHEADER]; |
||||
short pal_loop, tga_loop; |
||||
struct stat pal_st; |
||||
|
||||
fLMP = fopen(filename, "rb"); |
||||
|
||||
if (!fLMP) { |
||||
fprintf(stderr, "couldn't find %s\n", filename); |
||||
return; |
||||
} |
||||
|
||||
/* There are no other types of palette lumps. Sorry */ |
||||
stat(filename, &pal_st); |
||||
if (pal_st.st_size != QPALSIZE) { |
||||
fprintf(stderr, "invalid palette lump %s, skipping\n", filename); |
||||
return; |
||||
} |
||||
|
||||
fread(pal_buff, 1, QPALSIZE, fLMP); |
||||
fclose(fLMP); |
||||
|
||||
memset(tga_buff, 0, 18); |
||||
tga_buff[2] = 2; /* Uncompressed TARGA */ |
||||
tga_buff[12] = 16; /* Width */ |
||||
tga_buff[14] = 16; /* Height */ |
||||
tga_buff[16] = 24; /* Color depth */ |
||||
|
||||
/* TARGAs are flipped in an odd way,
|
||||
* so we gotta do some sorting magic (vertical flip) */ |
||||
for (tga_loop = 15; tga_loop >= 0; tga_loop--) { |
||||
for (pal_loop = 0; pal_loop < 16; pal_loop++) { |
||||
tga_buff[18 + (tga_loop * 48) + (pal_loop * 3) + 0] =
|
||||
pal_buff[((15 - tga_loop) * 48) + (pal_loop * 3) + 2]; |
||||
tga_buff[18 + (tga_loop * 48) + (pal_loop * 3) + 1] =
|
||||
pal_buff[((15 - tga_loop) * 48) + (pal_loop * 3) + 1]; |
||||
tga_buff[18 + (tga_loop * 48) + (pal_loop * 3) + 2] =
|
||||
pal_buff[((15 - tga_loop) * 48) + (pal_loop * 3) + 0]; |
||||
} |
||||
} |
||||
|
||||
/* FIXME: We assume too much!
|
||||
* Save the output to FILENAME.tga |
||||
* This is ugly when the input name has no .lmp extension. |
||||
* But who's going to try that. ...right? */ |
||||
filename[strlen(filename)-3] = 't'; |
||||
filename[strlen(filename)-2] = 'g'; |
||||
filename[strlen(filename)-1] = 'a'; |
||||
fprintf(stdout, "writing %s\n", filename); |
||||
fTGA = fopen(filename, "w+b"); |
||||
fwrite(tga_buff, 1, QPALSIZE + TGAHEADER, fTGA); |
||||
fclose(fTGA); |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
int c; |
||||
|
||||
if (argc <= 1) { |
||||
fprintf(stderr, "usage: pal2tga [file.lmp ...]\n"); |
||||
return 1; |
||||
} |
||||
|
||||
for (c = 1; c < argc; c++) |
||||
process_pal2tga(argv[c]); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,262 @@
|
||||
/*
|
||||
TGA 2 LMP 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. |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <malloc.h> |
||||
#include <string.h> |
||||
|
||||
#define TGAHEADER 18 |
||||
#define QPALSIZE 768 |
||||
#define VERSION "1.1" |
||||
|
||||
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 process_tga2lmp(char *filename) |
||||
{ |
||||
FILE *fLMP; /* file Ident of the LUMP */ |
||||
byte *lmp_buff; /* buffer of the LUMP */ |
||||
FILE *fTGA; /* file Ident of the TARGA */ |
||||
byte tga_header[TGAHEADER]; /* TARGA Header (18 bytes usually) */ |
||||
byte *tga_buff; /* 24bit gR input buffer + TGA header */ |
||||
int col, row, done; /* used for the sorting loop */ |
||||
int img_w, img_h; /* dimensions */ |
||||
|
||||
/* load the TARGA */ |
||||
fTGA = fopen(filename, "rb"); |
||||
|
||||
/* check whether the file exists or not */ |
||||
if (!fTGA) { |
||||
fprintf(stderr, "couldn't find %s\n", filename); |
||||
return; |
||||
} |
||||
|
||||
/* 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, RGB image\n", filename); |
||||
return; |
||||
} |
||||
if (tga_header[16] != 24) { |
||||
fprintf(stderr, "%s is not 24 bit in depth\n", 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); |
||||
tga_buff = malloc(img_w * img_h * 3); |
||||
|
||||
if (tga_buff == NULL) { |
||||
fprintf(stderr, "mem alloc failed at %d bytes\n", (img_w * img_h * 3)); |
||||
return; |
||||
} |
||||
|
||||
/* 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); |
||||
|
||||
/* start generating the lump data */ |
||||
lmp_buff = malloc(img_w * img_h + 8); |
||||
|
||||
if (lmp_buff == NULL) { |
||||
fprintf(stderr, "mem alloc failed at %d bytes\n", (img_w * img_h + 8)); |
||||
return; |
||||
} |
||||
|
||||
/* split the integer dimensions into 4 bytes */ |
||||
lmp_buff[3] = (img_w >> 24) & 0xFF; |
||||
lmp_buff[2] = (img_w >> 16) & 0xFF; |
||||
lmp_buff[1] = (img_w >> 8) & 0xFF; |
||||
lmp_buff[0] = img_w & 0xFF; |
||||
lmp_buff[7] = (img_h >> 24) & 0xFF; |
||||
lmp_buff[6] = (img_h >> 16) & 0xFF; |
||||
lmp_buff[5] = (img_h >> 8) & 0xFF; |
||||
lmp_buff[4] = img_h & 0xFF; |
||||
|
||||
/* translate the rgb values into indexed entries and flip */ |
||||
done = 0; |
||||
for (row = img_h - 1; row >= 0; row--) { |
||||
for (col = 0; col < img_w; col++) { |
||||
lmp_buff[8 + done] = |
||||
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))]); |
||||
done++; |
||||
} |
||||
} |
||||
|
||||
/* FIXME: We assume too much!
|
||||
* Save the output to FILENAME.tga |
||||
* This is ugly when the input name has no .lmp extension. |
||||
* But who's going to try that. ...right? */ |
||||
filename[strlen(filename)-3] = 'l'; |
||||
filename[strlen(filename)-2] = 'm'; |
||||
filename[strlen(filename)-1] = 'p'; |
||||
fprintf(stdout, "writing %s\n", filename); |
||||
fLMP = fopen(filename, "w+b"); |
||||
fwrite(lmp_buff, 1, (img_w * img_h) + 8, fLMP); |
||||
fclose(fLMP); |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
int c; |
||||
short p; |
||||
FILE *fPAL; |
||||
|
||||
if (argc <= 1) { |
||||
fprintf(stderr, "usage: tga2lmp [file.tga ...]\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_tga2lmp(argv[c]); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,109 @@
|
||||
/*
|
||||
TGA 2 PAL 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. |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
|
||||
#define TGAHEADER 18 |
||||
#define QPALSIZE 768 |
||||
|
||||
void process_tga2pal(char *filename) |
||||
{ |
||||
FILE *fTGA; |
||||
unsigned char tga_header[TGAHEADER]; |
||||
unsigned char tga_buff[QPALSIZE]; |
||||
FILE *fLMP; |
||||
unsigned char pal_buff[QPALSIZE]; |
||||
short loop_pal, loop_tga; |
||||
|
||||
fTGA = fopen(filename, "rb"); |
||||
if (!fTGA) { |
||||
fprintf(stderr, "couldn't find %s\n", filename); |
||||
return; |
||||
} |
||||
|
||||
/* 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, RGB image\n", filename); |
||||
return; |
||||
} |
||||
if (tga_header[16] != 24) { |
||||
fprintf(stderr, "%s is not 24 bit in depth\n", filename); |
||||
return; |
||||
} |
||||
if (tga_header[12] != 16 || tga_header[14] != 16) { |
||||
fprintf(stderr, "%s is not a 16x16 image\n", filename); |
||||
return; |
||||
} |
||||
|
||||
/* Skip to after the TARGA HEADER... and then read the buffer */ |
||||
fseek(fTGA, 18, SEEK_SET); |
||||
fread(tga_buff, 1, QPALSIZE, fTGA); |
||||
fclose(fTGA); |
||||
|
||||
/* TARGAs are flipped in an odd way,
|
||||
* so we gotta do the sorting dance */ |
||||
for(loop_tga = 15; loop_tga >= 0; loop_tga--) { |
||||
for(loop_pal = 0; loop_pal < 16; loop_pal++) { |
||||
pal_buff[((15 - loop_tga) * 48) + (loop_pal * 3) + 0] = |
||||
tga_buff[(loop_tga * 48) + (loop_pal * 3) + 2]; |
||||
pal_buff[((15 - loop_tga) * 48) + (loop_pal * 3) + 1] = |
||||
tga_buff[(loop_tga * 48) + (loop_pal * 3) + 1]; |
||||
pal_buff[((15 - loop_tga) * 48) + (loop_pal * 3) + 2] = |
||||
tga_buff[(loop_tga * 48) + (loop_pal * 3) + 0]; |
||||
} |
||||
} |
||||
|
||||
/* FIXME: We assume too much!
|
||||
* Save the output to FILENAME.tga |
||||
* This is ugly when the input name has no .lmp extension. |
||||
* But who's going to try that. ...right? */ |
||||
filename[strlen(filename)-3] = 'l'; |
||||
filename[strlen(filename)-2] = 'm'; |
||||
filename[strlen(filename)-1] = 'p'; |
||||
fprintf(stdout, "writing %s\n", filename); |
||||
fLMP = fopen(filename, "w+b"); |
||||
fwrite(pal_buff, 1, QPALSIZE, fLMP); |
||||
fclose(fLMP); |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
int c; |
||||
|
||||
if (argc <= 1) { |
||||
fprintf(stderr, "usage: tga2pal [file.tga ...]\n"); |
||||
return 1; |
||||
} |
||||
|
||||
for (c = 1; c < argc; c++) |
||||
process_tga2pal(argv[c]); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,535 @@
|
||||
/*
|
||||
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; |
||||
} |
@ -0,0 +1,83 @@
|
||||
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. |
||||
|
||||
Copyright (c) 2016-2019 Marco "eukara" Hladik <marco at icculus.org> |
||||
Released under the MIT License. |
Loading…
Reference in new issue