fteqw/imgtool.c
Spoike 11e6214daf Add explicit skyroom fog.
Changed how cubemaps are held in memory, making all images basically just 3d textures.
Don't start up at all if no game data is found.


git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5570 fc73d0e0-1445-4013-8a0c-d673dee63da5
2019-10-18 08:37:38 +00:00

1503 lines
45 KiB
C

#include "quakedef.h"
#include "shader.h"
#undef stderr
#define stderr stdout
#include <limits.h>
#ifdef _WIN32
#include <windows.h>
#endif
void VARGS Sys_Error (const char *fmt, ...)
{
va_list argptr;
va_start (argptr,fmt);
vfprintf (stderr,fmt,argptr);
va_end (argptr);
fflush(stderr);
exit(1);
}
void VARGS Con_Printf (const char *fmt, ...)
{
va_list argptr;
va_start (argptr,fmt);
vfprintf (stderr,fmt,argptr);
va_end (argptr);
fflush(stderr);
}
void VARGS Con_DPrintf (const char *fmt, ...)
{
va_list argptr;
va_start (argptr,fmt);
vfprintf (stderr,fmt,argptr);
va_end (argptr);
fflush(stderr);
}
void VARGS Con_ThrottlePrintf (float *timer, int developerlevel, const char *fmt, ...)
{
va_list argptr;
va_start (argptr,fmt);
vfprintf (stderr,fmt,argptr);
va_end (argptr);
fflush(stderr);
}
void *ZF_Malloc(size_t size)
{
#if defined(__linux__)
void *ret = NULL;
if (!posix_memalign(&ret, max(sizeof(float)*4, sizeof(void*)), size))
memset(ret, 0, size);
return ret;
#else
return calloc(size, 1);
#endif
}
void *Z_Malloc(size_t size)
{
void *r = ZF_Malloc(size);
if (!r)
exit(1);
return r;
}
void *BZ_Malloc(size_t size)
{
return Z_Malloc(size);
}
void *BZF_Malloc(size_t size)
{
return Z_Malloc(size);
}
void BZ_Free(void *p)
{
free(p);
}
void Z_Free(void *p)
{
free(p);
}
#include <sys/stat.h>
void FS_CreatePath(const char *pname, enum fs_relative relativeto)
{
char *t = strdup(pname), *sl = t;
while ((sl=strchr(sl, '/')))
{
*sl=0;
#ifdef _WIN32
CreateDirectoryA(t, NULL);
#else
mkdir(t, 0777);
#endif
*sl++='/';
}
free(t);
}
qboolean FS_Remove (const char *path, enum fs_relative relativeto)
{
//remove is part of c89.
if (remove(path) == -1)
return false;
return true;
}
qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out, int outlen)
{
Q_strncpyz(out, fname, outlen);
return true;
}
#ifdef __unix__
#include <sys/stat.h>
#endif
qbyte *FS_LoadMallocFile (const char *path, size_t *fsize)
{
qbyte *data = NULL;
FILE *f;
#ifdef __unix__
struct stat sb;
if (stat(path, &sb) < 0)
return NULL;
if ((sb.st_mode&S_IFMT) != S_IFREG)
return NULL;
#endif
f = fopen(path, "rb");
if (f)
{
long int sz;
if (fseek(f, 0, SEEK_END) >= 0)
{
sz = ftell(f);
if (sz >= 0)
{
*fsize = sz;
fseek(f, 0, SEEK_SET);
data = ZF_Malloc(*fsize+1);
if (data)
{
data[*fsize] = 0;
fread(data, 1, *fsize, f);
}
else
Con_Printf("Unable to allocate memory for %s\n", path);
}
}
fclose(f);
}
return data;
}
#ifdef _WIN32
dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs)
{
return NULL;
}
#else
#include <dlfcn.h>
void Sys_CloseLibrary(dllhandle_t *lib)
{
dlclose((void*)lib);
}
dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs)
{
int i;
dllhandle_t *lib;
lib = NULL;
if (!lib)
lib = dlopen (name, RTLD_LOCAL|RTLD_LAZY);
// if (!lib && !strstr(name, ".so"))
// lib = dlopen (va("%s.so", name), RTLD_LOCAL|RTLD_LAZY);
if (!lib)
{
// Con_DPrintf("%s\n", dlerror());
return NULL;
}
if (funcs)
{
for (i = 0; funcs[i].name; i++)
{
*funcs[i].funcptr = dlsym(lib, funcs[i].name);
if (!*funcs[i].funcptr)
break;
}
if (funcs[i].name)
{
Con_DPrintf("Unable to find symbol \"%s\" in \"%s\"\n", funcs[i].name, name);
Sys_CloseLibrary((dllhandle_t*)lib);
lib = NULL;
}
}
return (dllhandle_t*)lib;
}
#endif
struct imgfile_s
{
vfsfile_t pub;
FILE *f;
};
static qboolean QDECL ImgFile_Close(struct vfsfile_s *file)
{
struct imgfile_s *f = (struct imgfile_s*)file;
fclose(f->f);
free(f);
return true;
}
static int QDECL ImgFile_WriteBytes(struct vfsfile_s *file, const void *buffer, int bytestowrite)
{
struct imgfile_s *f = (struct imgfile_s*)file;
return fwrite(buffer, 1, bytestowrite, f->f);
}
static qboolean QDECL ImgFile_Seek(struct vfsfile_s *file, qofs_t newofs)
{
struct imgfile_s *f = (struct imgfile_s*)file;
if (fseek(f->f, newofs, SEEK_SET)==0)
return true; //success
return false;
}
static qofs_t QDECL ImgFile_Tell(struct vfsfile_s *file)
{
struct imgfile_s *f = (struct imgfile_s*)file;
return ftell(f->f);
}
vfsfile_t *QDECL FS_OpenVFS(const char *filename, const char *mode, enum fs_relative relativeto)
{
if (!strcmp(mode, "wb"))
{
struct imgfile_s *r = malloc(sizeof(*r));
r->f = fopen(filename, mode);
r->pub.seekstyle = SS_UNSEEKABLE;
r->pub.Close = ImgFile_Close;
r->pub.WriteBytes = ImgFile_WriteBytes;
r->pub.Seek = ImgFile_Seek;
r->pub.Tell = ImgFile_Tell;
if (r->f)
return &r->pub;
free(r);
}
return NULL;
}
qboolean COM_WriteFile (const char *filename, enum fs_relative fsroot, const void *data, int len)
{
return false;
}
void QDECL Q_strncpyz(char *d, const char *s, int n)
{
int i;
n--;
if (n < 0)
return; //this could be an error
for (i=0; *s; i++)
{
if (i == n)
break;
*d++ = *s++;
}
*d='\0';
}
void VARGS Q_vsnprintfz (char *dest, size_t size, const char *fmt, va_list argptr)
{
#ifdef _WIN32
_vsnprintf (dest, size, fmt, argptr);
#else
#ifdef _DEBUG
if ((size_t)vsnprintf (dest, size, fmt, argptr) > size-1)
Sys_Error("Q_vsnprintfz: truncation");
#else
vsnprintf (dest, size, fmt, argptr);
#endif
#endif
dest[size-1] = 0;
}
void VARGS Q_snprintfz (char *dest, size_t size, const char *fmt, ...)
{
va_list argptr;
va_start (argptr, fmt);
Q_vsnprintfz(dest, size, fmt, argptr);
va_end (argptr);
}
//palette data is used in lmps, as well as written into pcxes or wads, probably some other things.
qbyte *host_basepal;
unsigned int d_8to24rgbtable[256];
unsigned int d_8to24bgrtable[256];
static qbyte default_quakepal[768] =
{ //the quake palette was released into the public domain (or at least gpl) to ease development of tools writing quake-format data.
0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171,171,171,187,187,187,203,203,203,219,219,219,235,235,235,15,11,7,23,15,11,31,23,11,39,27,15,47,35,19,55,43,23,63,47,23,75,55,27,83,59,27,91,67,31,99,75,31,107,83,31,115,87,31,123,95,35,131,103,35,143,111,35,11,11,15,19,19,27,27,27,39,39,39,51,47,47,63,55,55,75,63,63,87,71,71,103,79,79,115,91,91,127,99,99,
139,107,107,151,115,115,163,123,123,175,131,131,187,139,139,203,0,0,0,7,7,0,11,11,0,19,19,0,27,27,0,35,35,0,43,43,7,47,47,7,55,55,7,63,63,7,71,71,7,75,75,11,83,83,11,91,91,11,99,99,11,107,107,15,7,0,0,15,0,0,23,0,0,31,0,0,39,0,0,47,0,0,55,0,0,63,0,0,71,0,0,79,0,0,87,0,0,95,0,0,103,0,0,111,0,0,119,0,0,127,0,0,19,19,0,27,27,0,35,35,0,47,43,0,55,47,0,67,
55,0,75,59,7,87,67,7,95,71,7,107,75,11,119,83,15,131,87,19,139,91,19,151,95,27,163,99,31,175,103,35,35,19,7,47,23,11,59,31,15,75,35,19,87,43,23,99,47,31,115,55,35,127,59,43,143,67,51,159,79,51,175,99,47,191,119,47,207,143,43,223,171,39,239,203,31,255,243,27,11,7,0,27,19,0,43,35,15,55,43,19,71,51,27,83,55,35,99,63,43,111,71,51,127,83,63,139,95,71,155,107,83,167,123,95,183,135,107,195,147,123,211,163,139,227,179,151,
171,139,163,159,127,151,147,115,135,139,103,123,127,91,111,119,83,99,107,75,87,95,63,75,87,55,67,75,47,55,67,39,47,55,31,35,43,23,27,35,19,19,23,11,11,15,7,7,187,115,159,175,107,143,163,95,131,151,87,119,139,79,107,127,75,95,115,67,83,107,59,75,95,51,63,83,43,55,71,35,43,59,31,35,47,23,27,35,19,19,23,11,11,15,7,7,219,195,187,203,179,167,191,163,155,175,151,139,163,135,123,151,123,111,135,111,95,123,99,83,107,87,71,95,75,59,83,63,
51,67,51,39,55,43,31,39,31,23,27,19,15,15,11,7,111,131,123,103,123,111,95,115,103,87,107,95,79,99,87,71,91,79,63,83,71,55,75,63,47,67,55,43,59,47,35,51,39,31,43,31,23,35,23,15,27,19,11,19,11,7,11,7,255,243,27,239,223,23,219,203,19,203,183,15,187,167,15,171,151,11,155,131,7,139,115,7,123,99,7,107,83,0,91,71,0,75,55,0,59,43,0,43,31,0,27,15,0,11,7,0,0,0,255,11,11,239,19,19,223,27,27,207,35,35,191,43,
43,175,47,47,159,47,47,143,47,47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23,7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123,59,183,155,55,199,195,55,231,227,87,127,191,255,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255,243,147,255,247,199,255,255,255,159,91,83
};
qbyte GetPaletteIndexNoFB(int red, int green, int blue)
{
int i;
int best=0;
int bestdist=INT_MAX;
int dist;
for (i = 0; i < 256-32; i++)
{
dist =
abs(host_basepal[i*3+0]-red)+
abs(host_basepal[i*3+1]-green)+
abs(host_basepal[i*3+2]-blue);
if (dist < bestdist)
{
bestdist = dist;
best = i;
}
}
return best;
}
static void ImgTool_SetupPalette(void)
{
int i;
//we ought to try to read gfx/palette.lmp, but its probably in a pak
host_basepal = default_quakepal;
for (i = 0; i < 256; i++)
{
d_8to24rgbtable[i] = (host_basepal[i*3+0]<<0)|(host_basepal[i*3+1]<<8)|(host_basepal[i*3+2]<<16);
d_8to24bgrtable[i] = (host_basepal[i*3+0]<<16)|(host_basepal[i*3+1]<<8)|(host_basepal[i*3+2]<<0);
}
}
static void ImgTool_FreeMips(struct pendingtextureinfo *mips)
{
size_t i;
if (mips)
{
for (i = 0; i < mips->mipcount; i++)
if (mips->mip[i].needfree)
BZ_Free(mips->mip[i].data);
if (mips->extrafree)
BZ_Free(mips->extrafree);
BZ_Free(mips);
}
}
sh_config_t sh_config;
viddef_t vid;
static const char *imagetypename[] = {"2D", "3D", "Cube", "2DArray", "CubemapArray", "INVALID", "INVALID", "INVALID", "INVALID", "INVALID"};
struct opts_s
{
int textype;
unsigned int flags; //image flags to use (affects how textures get interpreted a little)
unsigned int mipnum; //when exporting to a mipless format, this is the mip level that is actually written. default 0.
uploadfmt_t newpixelformat; //try to convert to this pixel format on export.
};
void Image_GenerateMips(struct pendingtextureinfo *mips, unsigned int flags);
int Image_WritePNG (const char *filename, enum fs_relative fsroot, int compression, void **buffers, int numbuffers, qintptr_t bufferstride, int width, int height, enum uploadfmt fmt, qboolean writemetadata);
qboolean WriteTGA(const char *filename, enum fs_relative fsroot, const qbyte *fte_restrict rgb_buffer, qintptr_t bytestride, int width, int height, enum uploadfmt fmt);
static enum uploadfmt ImgTool_ASTCToLDR(uploadfmt_t fmt)
{
if (fmt >= PTI_ASTC_FIRST && fmt <= PTI_ASTC_LAST)
{
if (fmt >= PTI_ASTC_4X4_HDR)
return (fmt-PTI_ASTC_4X4_HDR)+PTI_ASTC_4X4_LDR;
if (fmt >= PTI_ASTC_4X4_SRGB)
return (fmt-PTI_ASTC_4X4_SRGB)+PTI_ASTC_4X4_LDR;
}
if (fmt == PTI_BC1_RGB)
return PTI_BC1_RGBA;
return fmt;
}
#ifdef _WIN32
#include <fcntl.h>
static void FS_MakeTempName(char *out, size_t outsize, char *prefix, char *suffix)
{
int fd;
unsigned int n;
unsigned int s = rand();
for (n = 0; n < 0xffffff; n++)
{
Q_snprintfz(out, outsize, "/tmp/%s%06x%s", prefix, (n+s)&0xffffff, suffix);
fd = _open(out, _O_CREAT | _O_EXCL, _S_IREAD | _S_IWRITE);
if (fd == -1)
continue;
close(fd);
return;
}
Sys_Error("FS_MakeTempName failed\n");
}
#else
#include <unistd.h>
static void FS_MakeTempName(char *out, size_t outsize, char *prefix, char *suffix)
{
snprintf(out, outsize, "/tmp/%sXXXXXX%s", prefix, suffix);
close(mkstemps(out, strlen(suffix))); //bsd4.3/posix1-2001
}
#endif
static qboolean ImgTool_ConvertPixelFormat(struct opts_s *args, const char *inname, struct pendingtextureinfo *mips)
{
struct pendingtextureinfo tmp, *ret;
size_t m;
char raw[MAX_OSPATH];
char comp[MAX_OSPATH];
char command[MAX_OSPATH*3];
qbyte *fdata;
size_t fsize;
int bb,bw,bh;
qboolean canktx = false;
uploadfmt_t targfmt = args->newpixelformat;
int d,l, layers;
//force it to bc1 if bc2 or bc3 with no alpha channel.
if ((targfmt == PTI_BC2_RGBA || targfmt == PTI_BC3_RGBA) && !Image_FormatHasAlpha(mips->encoding))
targfmt = PTI_BC1_RGB;
if (targfmt >= PTI_ASTC_FIRST && targfmt <= PTI_ASTC_LAST)
{
Q_snprintfz(command, sizeof(command), "astcenc -c");
canktx = true;
}
else if (targfmt == PTI_BC1_RGB)
Q_snprintfz(command, sizeof(command), "nvcompress -bc1%s", (args->flags&IF_TRYBUMP)?"n":"");
else if (targfmt == PTI_BC1_RGBA)
Q_snprintfz(command, sizeof(command), "nvcompress -bc1a");
else if (targfmt == PTI_BC2_RGBA)
Q_snprintfz(command, sizeof(command), "nvcompress -bc2");
else if (targfmt == PTI_BC3_RGBA)
Q_snprintfz(command, sizeof(command), "nvcompress -bc3%s", (args->flags&IF_TRYBUMP)?"n":"");
else if (targfmt == PTI_BC4_R8)
Q_snprintfz(command, sizeof(command), "nvcompress -bc4");
else if (targfmt == PTI_BC5_RG8)
Q_snprintfz(command, sizeof(command), "nvcompress -bc5");
else if (targfmt == PTI_BC6_RGB_SFLOAT || targfmt == PTI_BC6_RGB_UFLOAT)
Q_snprintfz(command, sizeof(command), "nvcompress -bc6");
else if (targfmt == PTI_BC7_RGBA)
Q_snprintfz(command, sizeof(command), "nvcompress -bc7");
else
{
if (mips->encoding != targfmt)
{
for (m = 0; m < PTI_MAX; m++)
sh_config.texfmt[m] = (m == targfmt);
Image_ChangeFormat(mips, args->flags, PTI_INVALID, inname);
if (mips->encoding == targfmt)
return true;
//switch to common formats...
for (m = 0; m < PTI_MAX; m++)
sh_config.texfmt[m] = (m == targfmt) || (m==PTI_RGBA8);
Image_ChangeFormat(mips, args->flags, PTI_INVALID, inname);
//and try again...
for (m = 0; m < PTI_MAX; m++)
sh_config.texfmt[m] = (m == targfmt);
Image_ChangeFormat(mips, args->flags, PTI_INVALID, inname);
return (mips->encoding == targfmt);
}
return false;
}
if (canktx)
FS_MakeTempName(raw, sizeof(raw), "itr", ".ktx");
else
FS_MakeTempName(raw, sizeof(raw), "itr", ".png");
FS_MakeTempName(comp, sizeof(comp), "itc", ".ktx");
tmp.type = mips->type;
tmp.encoding = mips->encoding;
tmp.extrafree = NULL;
tmp.mipcount = 1;
Image_BlockSizeForEncoding(targfmt, &bb, &bw, &bh);
Q_snprintfz(command+strlen(command), sizeof(command)-strlen(command), " \"%s\" \"%s\"", raw, comp);
if (targfmt >= PTI_ASTC_FIRST && targfmt <= PTI_ASTC_LAST)
Q_snprintfz(command+strlen(command), sizeof(command)-strlen(command), " %ix%i -exhaustive", bw, bh);
if (targfmt >= PTI_ASTC_4X4_SRGB && targfmt <= PTI_ASTC_12X12_SRGB)
Q_strncatz(command, " -srgb", sizeof(command));
if (targfmt >= PTI_ASTC_4X4_HDR && targfmt <= PTI_ASTC_12X12_HDR)
Q_strncatz(command, " -hdr", sizeof(command));
if (targfmt >= PTI_BC1_RGB && targfmt <= PTI_BC7_RGBA_SRGB && (strstr(inname, "_n.")||strstr(inname, "_norm.")))
Q_strncatz(command, " -normal", sizeof(command)); //looks like a normalmap... tweak metrics to favour normalised results.
Q_strncatz(command, ">> /dev/null", sizeof(command));
if (!canktx)
{
//make sure the source pixel format is acceptable if we're forced to write a png
for (m = 0; m < PTI_MAX; m++)
sh_config.texfmt[m] =
(m == PTI_RGBA8) || (m == PTI_RGBX8) ||
(m == PTI_BGRA8) || (m == PTI_BGRX8) ||
(m == PTI_LLLA8) || (m == PTI_LLLX8) ||
(m == PTI_RGBA16) ||
(m == PTI_L8) || (m == PTI_L8A8) ||
/*(m == PTI_L16) ||*/
(m == PTI_BGR8) || (m == PTI_BGR8) ||
0;
Image_ChangeFormat(mips, args->flags&~IF_PREMULTIPLYALPHA, PTI_INVALID, inname);
//make sure we properly read whatever is given back to us
for (m = 1; m < countof(sh_config.texfmt); m++)
sh_config.texfmt[m] = true;
}
Image_BlockSizeForEncoding(mips->encoding, &bb, &bw, &bh);
for (m = 0; m < mips->mipcount; m++)
{
qbyte *srcdata = mips->mip[m].data;
size_t srcsize = mips->mip[m].datasize;
if (mips->type == PTI_3D)
{
layers = 1;
d = mips->mip[m].depth;
tmp.type = PTI_2D;
}
else
{
layers = mips->mip[m].depth;
d = 1;
tmp.type = PTI_2D;
}
for (l = 0; l < layers; l++)
{
Con_DPrintf("Compressing %s mip %u, layer %u\n", inname, (unsigned)m, l);
tmp.mip[0] = mips->mip[m];
tmp.mip[0].needfree = false;
tmp.mip[0].depth = d;
tmp.mip[0].datasize = srcsize/layers;
tmp.mip[0].data = srcdata + l * tmp.mip[0].datasize;
(void)tmp;
if (canktx)
{
#ifdef IMAGEFMT_KTX
if (!Image_WriteKTXFile(raw, FS_SYSTEM, &tmp))
#endif
break;
}
else
{
#ifdef AVAIL_PNGLIB
if (!Image_WritePNG(raw, FS_SYSTEM, 0, &tmp.mip[0].data, 1, tmp.mip[0].width*bb, tmp.mip[0].width, tmp.mip[0].height, tmp.encoding, false))
#endif
break;
}
system(command);
fdata = FS_LoadMallocFile(comp, &fsize);
ret = Image_LoadMipsFromMemory(IF_NOMIPMAP, comp, comp, fdata, fsize);
if (ret && ret->mip[0].width == mips->mip[m].width &&
ret->mip[0].height == mips->mip[m].height &&
ret->mip[0].depth == d &&
ImgTool_ASTCToLDR(ret->encoding) == ImgTool_ASTCToLDR(targfmt))
{
if (layers == 1) //just copy it over. FIXME: memory leak
mips->mip[m] = ret->mip[0];
else
{
if (!l)
{
mips->mip[m].datasize = ret->mip[0].datasize * layers;
mips->mip[m].data = BZ_Malloc(mips->mip[m].datasize);
mips->mip[m].needfree = true;
}
else if (ret->mip[0].datasize != mips->mip[m].datasize/layers)
break; //erk..?
memcpy(mips->mip[m].data + l * ret->mip[0].datasize, ret->mip[0].data, ret->mip[0].datasize);
}
continue;
}
break;
}
if (l != layers)
break;
}
mips->encoding = targfmt;
mips->mipcount = m;
if (mips->mipcount && targfmt >= PTI_BC1_RGB && targfmt <= PTI_BC7_RGBA_SRGB)
{ //d3d has some annoying limitations.
//do not warn for astc files, their block sizes are too weird.
Image_BlockSizeForEncoding(targfmt, &bb, &bw, &bh);
if (mips->mip[0].width%bw || mips->mip[0].height%bh)
Con_Printf("%s: mip0 of %i*%i is not a multiple of %i*%i (d3d warning)\n", inname, mips->mip[0].width, mips->mip[0].height, bw, bh);
}
FS_Remove(raw, FS_SYSTEM);
FS_Remove(comp, FS_SYSTEM);
return true;
}
const char *COM_GetFileExtension (const char *in, const char *term)
{
const char *dot;
if (!term)
term = in + strlen(in);
for (dot = term-1; dot >= in && *dot != '/' && *dot != '\\'; dot--)
{
if (*dot == '.')
return dot;
}
return "";
}
static struct pendingtextureinfo *ImgTool_Read(struct opts_s *args, const char *inname)
{
qbyte *indata;
size_t fsize;
struct pendingtextureinfo *in;
indata = FS_LoadMallocFile(inname, &fsize);
if (!indata)
printf("%s: unable to read\n", inname);
else
{
in = Image_LoadMipsFromMemory(args->flags, inname, inname, indata, fsize);
if (!in)
{
printf("%s: unsupported format\n", inname);
BZ_Free(indata);
}
else
{
printf("%s: %s %s, %i*%i, %i mips\n", inname, imagetypename[in->type], Image_FormatName(in->encoding), in->mip[0].width, in->mip[0].height, in->mipcount);
return in;
}
}
return NULL;
}
static struct pendingtextureinfo *ImgTool_Combine(struct opts_s *args, const char **namelist, unsigned int filecount)
{
struct pendingtextureinfo *r, *t;
unsigned int i;
unsigned int layers = 0;
unsigned int j = 0;
struct
{
const char *fname;
struct pendingtextureinfo *in;
} *srcs, *tmpsrcs;
if (args->textype == PTI_3D)
args->flags |= IF_NOMIPMAP; //generate any mipmaps after...
srcs = alloca(sizeof(*srcs)*filecount);
for (i = 0, j = 0; i < filecount; i++)
{
srcs[j].in = t = ImgTool_Read(args, namelist[i]);
if (srcs[j].in)
{
if (!j)
{
//get the image loader to massage pixel formats...
memset(sh_config.texfmt, 0, sizeof(sh_config.texfmt));
sh_config.texfmt[srcs[j].in->encoding] = true;
}
else if (!t->mipcount || !t->mip[0].data)
{
Con_Printf("%s: no valid image data\n", namelist[i]);
ImgTool_FreeMips(srcs[j].in);
continue;
}
else if (t->encoding != srcs[0].in->encoding)
{
Con_Printf("%s: mismatched pixel format, (%s not %s) cannot combine\n", namelist[i], Image_FormatName(t->encoding), Image_FormatName(srcs[0].in->encoding));
ImgTool_FreeMips(srcs[j].in);
continue;
}
else if (t->type == PTI_CUBE && t->mip[0].depth != 6)
{
Con_Printf("%s: incorrect cubemap data\n", namelist[i]);
ImgTool_FreeMips(srcs[j].in);
continue;
}
else if (srcs[0].in->mip[0].width != t->mip[0].width || srcs[0].in->mip[0].height != t->mip[0].height)
{
Con_Printf("%s: incorrect image size\n", namelist[i]);
ImgTool_FreeMips(srcs[j].in);
continue;
}
else if (t->mip[0].depth == 0)
{
Con_Printf("%s: no layers\n", namelist[i]);
ImgTool_FreeMips(srcs[j].in);
continue;
}
layers += t->mip[0].depth;
srcs[j++].fname = namelist[i];
}
}
filecount = j;
//FIXME: reorder input images to handle ft/bk/lt/rt/up/dn, and flip+rotate+etc to match quake
if (args->textype == PTI_CUBE)
{
int facetype[6];
static const struct {qboolean flipx, flipy, flipd;} skyboxflips[] ={
{true, false, true},
{false, true, true},
{true, true, false},
{false, false, false},
{true, false, true},
{true, false, true}
};
for (i = 0; i < 6; i++)
facetype[i] = 0;
for (i = 0; i < filecount; i++)
{
const char *ex = COM_GetFileExtension(srcs[i].fname, NULL);
if (ex && ex-srcs[i].fname > 2 && srcs[i].in->mip[0].depth == 1)
{
if (!strncasecmp(ex-2, "rt", 2))
facetype[0] = -i-1;
else if (!strncasecmp(ex-2, "lf", 2))
facetype[1] = -i-1;
else if (!strncasecmp(ex-2, "ft", 2))
facetype[2] = -i-1;
else if (!strncasecmp(ex-2, "bk", 2))
facetype[3] = -i-1;
else if (!strncasecmp(ex-2, "up", 2))
facetype[4] = -i-1;
else if (!strncasecmp(ex-2, "dn", 2))
facetype[5] = -i-1;
else if (!strncasecmp(ex-2, "px", 2))
facetype[0] = i+1;
else if (!strncasecmp(ex-2, "nx", 2))
facetype[1] = i+1;
else if (!strncasecmp(ex-2, "py", 2))
facetype[2] = i+1;
else if (!strncasecmp(ex-2, "ny", 2))
facetype[3] = i+1;
else if (!strncasecmp(ex-2, "pz", 2))
facetype[4] = i+1;
else if (!strncasecmp(ex-2, "nz", 2))
facetype[5] = i+1;
}
}
if (facetype[0] && facetype[1] && facetype[2] && facetype[3] && facetype[4] && facetype[5])
{
Con_Printf("Reordering images to match cubemap\n");
tmpsrcs = alloca(sizeof(*tmpsrcs)*filecount);
memcpy(tmpsrcs, srcs, sizeof(*tmpsrcs)*filecount);
for (i = 0; i < 6; i++)
{
if (facetype[i] < 0)
{ //flip to match legacy skyboxes
unsigned bb,bw,bh;
srcs[i] = tmpsrcs[-facetype[i]-1];
t = srcs[i].in;
Image_BlockSizeForEncoding(t->encoding, &bb,&bw,&bh);
if (bw == 1 && bh == 1)
{
for (j = 0; j < t->mipcount; j++)
{
void *data = Image_FlipImage(t->mip[j].data, BZ_Malloc(t->mip[j].datasize), &t->mip[j].width, &t->mip[j].height, bb, skyboxflips[i].flipx, skyboxflips[i].flipy, skyboxflips[i].flipd);
if (t->mip[j].needfree)
Z_Free(t->mip[j].data);
t->mip[j].data = data;
t->mip[j].needfree = true;
}
}
}
else
srcs[i] = tmpsrcs[facetype[i]-1];
}
}
else
Con_Printf("WARNING: Cubemap ordering unknown!\n");
}
if (!filecount)
Con_Printf("no valid input files\n");
else if (!layers)
Con_Printf("Images must have at least one layer\n");
else if (args->textype == PTI_2D && layers != 1)
Con_Printf("2D images must have one layer exactly, sorry\n");
else if (args->textype == PTI_CUBE && layers != 6)
Con_Printf("Cubemaps must have 6 layers exactly\n");
else if (args->textype == PTI_CUBE_ARRAY && layers % 6)
Con_Printf("Cubemap arrays must have a multiple of 6 layers exactly\n");
else
{
t = srcs[0].in;
r = Z_Malloc(sizeof(*t));
r->type = args->textype;
r->extrafree = NULL;
r->encoding = t->encoding;
if (args->textype == PTI_3D)
r->mipcount = 1;
else
r->mipcount = t->mipcount;
for (j = 0; j < t->mipcount; j++)
{
r->mip[j].datasize = t->mip[j].datasize*layers;
r->mip[j].width = t->mip[j].width;
r->mip[j].height = t->mip[j].height;
r->mip[j].depth = 0;
r->mip[j].needfree = true;
r->mip[j].data = BZ_Malloc(r->mip[j].datasize);
}
for (i = 0, j = 0; i < filecount; i++)
{
t = srcs[i].in;
if (!t)
{
ImgTool_FreeMips(r);
return NULL;
}
for (j = 0; j < r->mipcount; j++)
{
if (r->mip[j].width != t->mip[j].width || r->mip[j].height != t->mip[j].height || t->mip[j].depth != 1)
{
Con_Printf("%s: mismatched mipmap sizes\n", namelist[i]);
continue;
}
memcpy(r->mip[j].data + t->mip[j].datasize*r->mip[j].depth, t->mip[j].data, t->mip[j].datasize);
r->mip[j].depth++;
}
}
for (i = 0; i < filecount; i++)
ImgTool_FreeMips(srcs[i].in);
printf("%s: %s %s, %i*%i, %i mips\n", "combined", imagetypename[r->type], Image_FormatName(r->encoding), r->mip[0].width, r->mip[0].height, r->mipcount);
return r;
}
for (i = 0; i < filecount; i++)
ImgTool_FreeMips(srcs[i].in);
return NULL;
}
static void ImgTool_Convert(struct opts_s *args, struct pendingtextureinfo *in, const char *inname, const char *outname)
{
size_t k;
const char *outext = COM_GetFileExtension(outname, NULL);
qboolean allowcompressed = false;
if (!strcmp(outext, ".dds") || !strcmp(outext, ".ktx"))
allowcompressed = true;
if (in)
{
if (!(args->flags & IF_NOMIPMAP) && in->mipcount == 1)
Image_GenerateMips(in, args->flags);
if (args->mipnum >= in->mipcount)
{
ImgTool_FreeMips(in);
Con_Printf("%s: Requested output mip number was out of bounds %i >= %i\n", outname, args->mipnum, in->mipcount);
return;
}
for (k = 0; k < args->mipnum; k++)
{
if (in->mip[k].needfree)
BZ_Free(in->mip[k].data);
}
in->mipcount -= k;
memmove(in->mip, &in->mip[k], sizeof(in->mip[0])*in->mipcount);
if (args->newpixelformat != PTI_INVALID && (args->newpixelformat < PTI_BC1_RGB || allowcompressed) && ImgTool_ConvertPixelFormat(args, inname, in))
printf("\t(Converted to %s)\n", Image_FormatName(in->encoding));
if (!in->mipcount)
{
ImgTool_FreeMips(in);
printf("%s: unable to convert any mips\n", inname);
return;
}
if (0)
;
#ifdef IMAGEFMT_KTX
else if (!strcmp(outext, ".ktx"))
{
if (!Image_WriteKTXFile(outname, FS_SYSTEM, in))
Con_Printf("%s(%s): Write failed\n", outname, Image_FormatName(in->encoding));
}
#endif
#ifdef IMAGEFMT_DDS
else if (!strcmp(outext, ".dds"))
{
if (!Image_WriteDDSFile(outname, FS_SYSTEM, in))
Con_Printf("%s(%s): Write failed\n", outname, Image_FormatName(in->encoding));
}
#endif
else
{
int bb,bw,bh;
if (in->type != PTI_2D)
Con_Printf("%s: Unable to write %s file to 2d image format\n", outname, imagetypename[in->type]);
#ifdef IMAGEFMT_PNG
else if (!strcmp(outext, ".png"))
{
#ifdef AVAIL_PNGLIB
//force the format, because we can.
for (k = 0; k < PTI_MAX; k++)
sh_config.texfmt[k] =
(k == PTI_RGBA8) || (k == PTI_RGBX8) ||
(k == PTI_BGRA8) || (k == PTI_BGRX8) ||
(k == PTI_LLLA8) || (k == PTI_LLLX8) ||
(k == PTI_RGBA16) ||
(k == PTI_L8) || (k == PTI_L8A8) ||
/*(k == PTI_L16) ||*/
(k == PTI_BGR8) || (k == PTI_BGR8) ||
0;
if (!sh_config.texfmt[in->encoding])
{
Image_ChangeFormat(in, args->flags&~IF_PREMULTIPLYALPHA, PTI_INVALID, outname);
printf("\t(Exporting as %s)\n", Image_FormatName(in->encoding));
}
Image_BlockSizeForEncoding(in->encoding, &bb, &bw,&bh);
if (!Image_WritePNG(outname, FS_SYSTEM, 0, &in->mip[0].data, 1, in->mip[0].width*bb, in->mip[0].width, in->mip[0].height, in->encoding, false))
#endif
Con_Printf("%s(%s): Write failed\n", outname, Image_FormatName(in->encoding));
}
#endif
#ifdef IMAGEFMT_TGA
else if (!strcmp(outext, ".tga"))
{
for (k = 0; k < PTI_MAX; k++)
sh_config.texfmt[k] =
(k == PTI_RGBA8) || (k == PTI_RGBX8) ||
(k == PTI_BGRA8) || (k == PTI_BGRX8) ||
(k == PTI_LLLA8) || (k == PTI_LLLX8) ||
(k == PTI_RGBA16F) || (k == PTI_R16F) || //half-float tgas is a format extension, but allow it.
(k == PTI_L8) || (k == PTI_L8A8) ||
/*(k == PTI_L16) ||*/
(k == PTI_BGR8) || (k == PTI_BGR8) ||
0;
if (!sh_config.texfmt[in->encoding])
{
Image_ChangeFormat(in, args->flags&~IF_PREMULTIPLYALPHA, PTI_INVALID, outname);
printf("\t(Exporting as %s)\n", Image_FormatName(in->encoding));
}
Image_BlockSizeForEncoding(in->encoding, &bb, &bw,&bh);
if (!WriteTGA(outname, FS_SYSTEM, in->mip[0].data, in->mip[0].width*bb, in->mip[0].width, in->mip[0].height, in->encoding))
Con_Printf("%s(%s): Write failed\n", outname, Image_FormatName(in->encoding));
}
#endif
else
Con_Printf("%s: Unknown output file format\n", outname);
}
printf("%s: %s %s, %i*%i, %i mips\n", outname, imagetypename[in->type], Image_FormatName(in->encoding), in->mip[0].width, in->mip[0].height, in->mipcount);
for (k = 0; k < in->mipcount; k++)
printf("\t%u: %i*%i*%i, %u\n", (unsigned)k, in->mip[k].width, in->mip[k].height, in->mip[k].depth, (unsigned)in->mip[k].datasize);
ImgTool_FreeMips(in);
}
fflush(stdout);
}
static void ImgTool_Info(struct opts_s *args, const char *inname)
{
qbyte *indata;
size_t fsize;
size_t m;
struct pendingtextureinfo *in;
indata = FS_LoadMallocFile(inname, &fsize);
if (!indata)
printf("%s: unable to read\n", inname);
else
{
in = Image_LoadMipsFromMemory(args->flags, inname, inname, indata, fsize);
if (!in)
printf("%s: unsupported format\n", inname);
else
{
printf("%s: %s %s, %i*%i, %i mips\n", inname, imagetypename[in->type], Image_FormatName(in->encoding), in->mip[0].width, in->mip[0].height, in->mipcount);
for (m = 0; m < in->mipcount; m++)
printf("\t%u: %i*%i*%i, %u\n", (unsigned)m, in->mip[m].width, in->mip[m].height, in->mip[m].depth, (unsigned)in->mip[m].datasize);
ImgTool_FreeMips(in);
}
}
fflush(stdout);
}
struct filelist_s
{
const char **exts;
size_t numfiles;
struct {
char *name;
size_t baselen; //length up to but not including the filename extension.
} *file;
size_t maxfiles; //to avoid reallocs
};
static void FileList_Release(struct filelist_s *list)
{
size_t i;
for (i = 0; i < list->numfiles; i++)
free(list->file[i].name);
free(list->file);
list->numfiles = 0;
list->maxfiles = 0;
}
static void FileList_Add(struct filelist_s *list, char *fname)
{
size_t i;
size_t baselen;
const char *ext = COM_GetFileExtension(fname, NULL);
for (i = 0; ; i++)
{
if (!list->exts[i])
return; //extension wasn't in the list.
if (!strcmp(list->exts[i], ext))
break; //one of the accepted extensions
}
baselen = ext?ext-fname:strlen(fname);
for (i = 0; i < list->numfiles; i++)
{
if (list->file[i].baselen == baselen && !strncasecmp(list->file[i].name, fname, baselen))
{
Con_Printf("Ignoring dupe file %s (using %s)\n", fname, list->file[i].name);
return; //file already listed, but maybe with a different extension
}
}
if (i == list->maxfiles)
{
list->maxfiles += 64;
list->file = realloc(list->file, sizeof(*list->file)*list->maxfiles);
}
list->file[i].name = strdup(fname);
list->file[i].baselen = baselen;
list->numfiles++;
}
#ifdef _WIN32
static void ImgTool_TreeScan(struct filelist_s *list, const char *basepath, const char *subpath)
{
Con_Printf("ImgTool_TreeScan not implemented on windows.\n");
}
#else
#include <dirent.h>
#include <fnmatch.h>
static void ImgTool_TreeScan(struct filelist_s *list, const char *basepath, const char *subpath)
{
DIR *dir;
char file[MAX_OSPATH];
struct dirent *ent;
if (subpath && *subpath)
Q_snprintfz(file, sizeof(file), "%s/%s", basepath, subpath);
else
Q_snprintfz(file, sizeof(file), "%s", basepath);
dir = opendir(file);
if (!dir)
{
Con_Printf("Failed to open dir %s\n", file);
return;
}
for (;;)
{
ent = readdir(dir);
if (!ent)
break;
if (*ent->d_name == '.')
continue;
else if (ent->d_type == DT_DIR)
{
if (!subpath)
continue;
if (*subpath)
Q_snprintfz(file, sizeof(file), "%s/%s", subpath, ent->d_name);
else
Q_snprintfz(file, sizeof(file), "%s", ent->d_name);
ImgTool_TreeScan(list, basepath, file);
}
else if (ent->d_type == DT_REG)
{
if (subpath && *subpath)
Q_snprintfz(file, sizeof(file), "%s/%s", subpath, ent->d_name);
else
Q_snprintfz(file, sizeof(file), "%s", ent->d_name);
FileList_Add(list, file);
}
}
closedir(dir);
}
#endif
static void ImgTool_TreeConvert(struct opts_s *args, const char *srcpath, const char *destpath)
{
size_t newfiles=0, skippedfiles=0, processedfiles=0;
char file[MAX_OSPATH];
char dest[MAX_OSPATH];
const char *exts[] = {".png", ".bmp", ".tga", ".jpg", ".exr", ".hdr", NULL};
struct filelist_s list = {exts};
size_t i, destlen = strlen(destpath)+1;
ImgTool_TreeScan(&list, srcpath, "");
if (!list.numfiles)
Con_Printf("No suitable files found in directory: %s\n", srcpath);
for (i = 0; i < list.numfiles; i++)
{
struct stat statsrc, statdst;
Q_snprintfz(file, sizeof(file), "%s/%s", srcpath, list.file[i].name);
Q_snprintfz(dest, sizeof(dest), "%s/%s", destpath, list.file[i].name);
Q_snprintfz(dest+destlen+list.file[i].baselen, sizeof(dest)-destlen-list.file[i].baselen, ".dds");
if (stat(file, &statsrc) < 0)
{
Con_Printf("stat(\"%s\") failed...\n", file);
continue;
}
if (stat(dest, &statdst) < 0)
{
statdst.st_mtim.tv_sec = INT_MIN; //make it look old
newfiles++;
}
if (statdst.st_mtim.tv_sec <= statsrc.st_mtim.tv_sec)
{
processedfiles++;
// Con_Printf("Image file %s -> %s\n", file, dest);
FS_CreatePath(dest, FS_SYSTEM);
ImgTool_Convert(args, ImgTool_Read(args, file), file, dest);
}
else
{
skippedfiles++;
// Con_Printf("Unmodified image file %s -> %s\n", file, dest);
}
}
Con_Printf("found: %u, processed: %u, skipped: %u, new: %u\n", (unsigned int)list.numfiles, (unsigned int)processedfiles, (unsigned int)skippedfiles, (unsigned int)newfiles);
FileList_Release(&list);
return;
}
typedef struct
{
unsigned int offset; // Position of the entry in WAD
unsigned int dsize; // Size of the entry in WAD file
unsigned int size; // Size of the entry in memory
char type; // type of entry
char cmprs; // Compression. 0 if none.
short dummy; // Not used
char name[16]; // we use only first 8
} wad2entry_t;
typedef struct
{
char magic[4]; //should be WAD2
unsigned int num; //number of entries
unsigned int offset; //location of directory
} wad2_t;
static void ImgTool_WadConvert(struct opts_s *args, const char *srcpath, const char *destpath, int wadtype/*x,2,3*/)
{
char file[MAX_OSPATH];
const char *exts[] = {".png", ".bmp", ".tga", ".exr", ".hdr", ".dds", ".ktx", ".xcf", ".pcx", ".jpg", NULL};
struct filelist_s list = {exts};
size_t i, u;
vfsfile_t *f;
char *inname;
qbyte *indata;
size_t fsize;
wad2_t wad2;
wad2entry_t *wadentries = NULL, *entry;
size_t maxentries = 0;
miptex_t mip;
ImgTool_TreeScan(&list, srcpath, NULL);
f = FS_OpenVFS(destpath, "wb", FS_SYSTEM);
wad2.magic[0] = 'W';
wad2.magic[1] = 'A';
wad2.magic[2] = 'D';
wad2.magic[3] = (wadtype==2)?'3':'2'; //wad3 instead of 2, so we can include a palette for tools to validate against
wad2.num = 0;
wad2.offset = 0;
VFS_WRITE(f, &wad2, 12);
//try to decompress everything to a nice friendly palletizable range.
for (u = 1; u < countof(sh_config.texfmt); u++)
sh_config.texfmt[u] = (u==PTI_RGBA8)||(u==PTI_RGBX8)||(u==PTI_P8);
for (i = 0; i < list.numfiles; i++)
{
Q_snprintfz(file, sizeof(file), "%s/%s", srcpath, list.file[i].name);
inname = list.file[i].name;
if (list.file[i].baselen > 15)
{
Con_Printf("Path too long for wad - %s\n", inname);
continue;
}
indata = FS_LoadMallocFile(file, &fsize);
if (indata)
{
struct pendingtextureinfo *in = Image_LoadMipsFromMemory(args->flags, inname, file, indata, fsize);
Image_GenerateMips(in, args->flags);
if (in)
{
if (in->mipcount == 1)
Image_GenerateMips(in, args->flags);
if (!in->mipcount)
{
ImgTool_FreeMips(in);
Con_Printf("%s: unable to load any mips\n", inname);
continue;
}
}
if (args->mipnum >= in->mipcount)
{
ImgTool_FreeMips(in);
Con_Printf("%s: not enough mips\n", inname);
continue;
}
//strip out all but the 4 mip levels we care about.
for (u = 0; u < in->mipcount; u++)
{
if (u >= args->mipnum && u < args->mipnum+4)
{
if (!wadtype)
{ //if we're stripping out the wad data (so that the engine ends up requiring external textures) then do it now before palettizing, for efficiency.
if (in->mip[u].needfree)
BZ_Free(in->mip[u].data);
in->mip[u].data = NULL;
in->mip[u].datasize = 0;
}
}
else
{
if (in->mip[u].needfree)
BZ_Free(in->mip[u].data);
memset(&in->mip[u], 0, sizeof(in->mip[u]));
}
}
in->mipcount -= args->mipnum;
if (in->mipcount > 4)
in->mipcount = 4;
memmove(&in->mip[0], &in->mip[args->mipnum], sizeof(in->mip[0])*in->mipcount);
memset(&in->mip[in->mipcount], 0, sizeof(in->mip[0])*((args->mipnum+4)-in->mipcount)); //null it out, just in case.
if (!in->mip[0].width || (in->mip[0].width & 15))
Con_Printf("%s(%i): WARNING: miptex width is not a multiple of 16 - %i*%i\n", inname, args->mipnum, in->mip[0].width, in->mip[0].height);
if (!in->mip[0].height || (in->mip[0].height & 15))
Con_Printf("%s(%i): WARNING: miptex height is not a not multiple of 16 - %i*%i\n", inname, args->mipnum, in->mip[0].width, in->mip[0].height);
if (in->encoding != PTI_P8)
Image_ChangeFormat(in, IF_PALETTIZE, (*inname=='{')?TF_TRANS8:PTI_INVALID, inname);
if (in->encoding != PTI_P8)
{ //erk! we failed to palettize...
ImgTool_FreeMips(in);
continue;
}
if (wad2.num == maxentries)
{
maxentries += 64;
wadentries = realloc(wadentries, sizeof(*wadentries)*maxentries);
}
entry = &wadentries[wad2.num++];
Q_strncpyz(entry->name, inname, 16);
entry->name[list.file[i].baselen] = 0; //kill any .tga
if (*entry->name == '#')
*entry->name = '*'; //* is not valid in a filename, yet needed for turbs, so by convention # is used instead. this is only relevant for the first char.
entry->type = TYP_MIPTEX;
entry->cmprs = 0;
entry->dummy = 0;
entry->offset = VFS_TELL(f);
memcpy(mip.name, entry->name, sizeof(mip.name));
mip.width = in->mip[0].width;
mip.height = in->mip[0].height;
mip.offsets[0] = in->mip[0].datasize?sizeof(mip):0;
mip.offsets[1] = in->mip[1].datasize?mip.offsets[0]+in->mip[0].datasize:0;
mip.offsets[2] = in->mip[2].datasize?mip.offsets[1]+in->mip[1].datasize:0;
mip.offsets[3] = in->mip[3].datasize?mip.offsets[2]+in->mip[2].datasize:0;
Con_Printf("%s: %ix%i\n", mip.name, mip.width, mip.height);
VFS_WRITE(f, &mip, sizeof(mip));
VFS_WRITE(f, in->mip[0].data, in->mip[0].datasize);
VFS_WRITE(f, in->mip[1].data, in->mip[1].datasize);
VFS_WRITE(f, in->mip[2].data, in->mip[2].datasize);
VFS_WRITE(f, in->mip[3].data, in->mip[3].datasize);
if (wad2.magic[3] == '3')
{
VFS_WRITE(f, "\x00\x01", 2);
VFS_WRITE(f, host_basepal, 256*3);
}
entry->size = entry->dsize = VFS_TELL(f)-entry->offset;
ImgTool_FreeMips(in);
}
}
wad2.offset = VFS_TELL(f);
VFS_WRITE(f, wadentries, sizeof(*wadentries)*wad2.num);
VFS_SEEK(f, 0);
VFS_WRITE(f, &wad2, sizeof(wad2));
VFS_CLOSE(f);
FileList_Release(&list);
}
int main(int argc, const char **argv)
{
enum
{
mode_info,
mode_convert,
mode_autotree,
mode_genwadx,
mode_genwad2,
mode_genwad3,
} mode = mode_info;
size_t u, f;
qboolean nomoreopts = false;
struct opts_s args;
size_t files = 0;
for (u = 1; u < countof(sh_config.texfmt); u++)
sh_config.texfmt[u] = true;
args.flags = 0;
args.newpixelformat = PTI_INVALID;
args.mipnum = 0;
args.textype = -1;
sh_config.texture2d_maxsize = 1u<<31;
sh_config.texture3d_maxsize = 1u<<31;
sh_config.texture2darray_maxlayers = 1u<<31;
sh_config.texturecube_maxsize = 8192;
sh_config.texture_non_power_of_two = true;
sh_config.texture_non_power_of_two_pic = true;
sh_config.texture_allow_block_padding = true;
sh_config.npot_rounddown = true; //shouldn't be relevant
sh_config.havecubemaps = true; //I don't think this matters.
ImgTool_SetupPalette();
Image_Init();
if (argc==1)
goto showhelp;
for (u = 1; u < argc; u++)
{
if (*argv[u] == '-' && !nomoreopts)
{
if (!strcmp(argv[u], "--"))
nomoreopts = true;
else if (!strcmp(argv[u], "-?") || !strcmp(argv[u], "--help"))
{
showhelp:
Con_Printf("show info : %s -i *.ktx\n", argv[0]);
Con_Printf("compress : %s --astc_6x6_ldr [--nomips] in.png out.ktx [in2.png out2.ktx]\n", argv[0]);
Con_Printf("compress : %s --bc3_rgba [--premul] [--nomips] in.png out.dds\n\tConvert pixel format (to bc3 aka dxt5) before writing to output file.\n", argv[0]);
Con_Printf("convert : %s --convert in.exr out.dds\n\tConvert to different file format, while trying to preserve pixel formats.\n", argv[0]);
Con_Printf("recursive : %s --astc_6x6_ldr -r srcdir destdir\n", argv[0]);
Con_Printf("decompress: %s --decompress [--exportmip 0] [--nomips] in.ktx out.png\n\tDecompresses any block-compressed pixel data.\n", argv[0]);
Con_Printf("gen wad : %s --genwad3 [--exportmip 2] srcdir out.wad\n", argv[0]);
Image_PrintInputFormatVersions();
Con_Printf("Supported compressed/interesting pixelformats are:\n");
for (f = 0; f < PTI_MAX; f++)
{
int bb,bw,bh;
Image_BlockSizeForEncoding(f, &bb,&bw,&bh);
if (f >= PTI_ASTC_FIRST && f <= PTI_ASTC_LAST)
Con_Printf(" --%-16s %5.3g-bpp (requires astcenc)\n", Image_FormatName(f), 8*(float)bb/(bw*bh));
else if (f==PTI_BC1_RGB||f==PTI_BC1_RGBA||f==PTI_BC2_RGBA||f==PTI_BC3_RGBA||f==PTI_BC4_R8||f==PTI_BC5_RG8)
Con_Printf(" --%-16s %5.3g-bpp (requires nvcompress)\n", Image_FormatName(f), 8*(float)bb/(bw*bh));
else if (f==PTI_BC6_RGB_UFLOAT || f==PTI_BC6_RGB_SFLOAT || f==PTI_BC7_RGBA)
Con_Printf(" --%-16s %5.3g-bpp (requires nvcompress 2.1+)\n", Image_FormatName(f), 8*(float)bb/(bw*bh));
else if ( f==PTI_RGBA16F ||
f==PTI_RGBA32F ||
f==PTI_E5BGR9 ||
f==PTI_B10G11R11F ||
f==PTI_RGB565 ||
f==PTI_RGBA4444 ||
f==PTI_ARGB4444 ||
f==PTI_RGBA5551 ||
f==PTI_ARGB1555 ||
f==PTI_A2BGR10 ||
// f==PTI_R8 ||
// f==PTI_R16 ||
// f==PTI_R16F ||
// f==PTI_R32F ||
f==PTI_RG8 ||
f==PTI_L8 ||
f==PTI_L8A8 ||
0)
Con_Printf(" --%-16s %5.3g-bpp\n", Image_FormatName(f), 8*(float)bb/(bw*bh));
// else
// Con_DPrintf(" --%-16s %5.3g-bpp (unsupported)\n", Image_FormatName(f), 8*(float)bb/(bw*bh));
}
return EXIT_SUCCESS;
}
else if (!files && (!strcmp(argv[u], "-c") || !strcmp(argv[u], "--convert")))
mode = mode_convert;
else if (!files && (!strcmp(argv[u], "-d") || !strcmp(argv[u], "--decompress")))
{ //remove any (weird) gpu formats
for (f = PTI_BC1_RGB; f < PTI_ASTC_LAST; f++)
sh_config.texfmt[f] = false;
mode = mode_convert;
}
else if (!files && (!strcmp(argv[u], "-r") || !strcmp(argv[u], "--auto")))
mode = mode_autotree;
else if (!files && (!strcmp(argv[u], "-i") || !strcmp(argv[u], "--info")))
mode = mode_info;
else if (!files && (!strcmp(argv[u], "-w") || !strcmp(argv[u], "--genwad3")))
mode = mode_genwad3;
else if (!files && (!strcmp(argv[u], "-w") || !strcmp(argv[u], "--genwad2")))
mode = mode_genwad2;
else if (!files && (!strcmp(argv[u], "-w") || !strcmp(argv[u], "--genwadx")))
mode = mode_genwadx;
else if (!strcmp(argv[u], "--2d"))
args.textype = PTI_2D;
else if (!strcmp(argv[u], "--3d"))
args.textype = PTI_3D;
else if (!strcmp(argv[u], "--cube"))
args.textype = PTI_CUBE;
else if (!strcmp(argv[u], "--2darray"))
args.textype = PTI_2D_ARRAY;
else if (!strcmp(argv[u], "--cubearray"))
args.textype = PTI_CUBE_ARRAY;
else if (!strcmp(argv[u], "--nomips") )
args.flags |= IF_NOMIPMAP;
else if (!strcmp(argv[u], "--mips"))
args.flags &= ~IF_NOMIPMAP;
else if (!strcmp(argv[u], "--premul") )
args.flags |= IF_PREMULTIPLYALPHA;
else if (!strcmp(argv[u], "--nopremul"))
args.flags &= ~IF_PREMULTIPLYALPHA;
else if (!strcmp(argv[u], "--exportmip"))
{
char *e = "erk";
if (u+1 < argc)
args.mipnum = strtoul(argv[++u], &e, 10);
if (*e)
{
Con_Printf("--exportmip requires trailing numeric argument\n");
return 1;
}
}
else
{
if (argv[u][1] == '-')
{
for (f = 0; f < PTI_MAX; f++)
{
if (!strcasecmp(argv[u]+2, Image_FormatName(f)))
{
args.newpixelformat = f;
mode = mode_convert;
break;
}
}
if (f < PTI_MAX)
continue;
}
Con_Printf("Unknown arg %s\n", argv[u]);
goto showhelp;
}
argv[u] = NULL;
}
else
argv[files++] = argv[u];
}
if (mode == mode_info)
{ //just print info about each listed file.
for (u = 0; u < files; u++)
ImgTool_Info(&args, argv[u]);
}
else if (mode == mode_convert && files > 1 && args.textype>=0) //overwrite input
{
files--;
ImgTool_Convert(&args, ImgTool_Combine(&args, argv, files), "combined", argv[files]);
}
else if (mode == mode_convert && files == 1 && args.textype<0) //overwrite input
ImgTool_Convert(&args, ImgTool_Read(&args, argv[u]), argv[u], argv[u]);
else if (mode == mode_convert && !(files&1) && args.textype<0) //list of pairs
{
//-c src1 dst1 src2 dst2
for (u = 0; u+1 < files; u+=2)
ImgTool_Convert(&args, ImgTool_Read(&args, argv[u]), argv[u], argv[u+1]);
}
else if (mode == mode_autotree && files == 2)
ImgTool_TreeConvert(&args, argv[0], argv[1]);
else if ((mode == mode_genwad2 || mode == mode_genwad3 || mode == mode_genwadx) && files == 2)
ImgTool_WadConvert(&args, argv[0], argv[1], mode-mode_genwadx);
else
return EXIT_FAILURE;
return EXIT_SUCCESS;
}