split texture loader into codecs (greatly eases navigation)
tex.cpp now has loader API and codec support routines. provide for codecs "transforming" the image; this allows e.g. decompressing DDS, which is currently done in ogl code (but belongs here) This was SVN commit r2648.
This commit is contained in:
parent
9ec828f6fd
commit
71f7fccd12
File diff suppressed because it is too large
Load Diff
@ -23,14 +23,16 @@
|
||||
|
||||
|
||||
|
||||
enum TexInfoFlags
|
||||
enum TexFlags
|
||||
{
|
||||
TEX_DXT = 0x07, // mask; value = {1,3,5}
|
||||
TEX_BGR = 0x08,
|
||||
TEX_DXT = 0x7, // mask; value = {1,3,5}
|
||||
|
||||
TEX_ALPHA = 0x10,
|
||||
TEX_GREY = 0x20,
|
||||
|
||||
// orientation - never returned by tex_load, since it automatically
|
||||
TEX_BGR = 0x08,
|
||||
|
||||
// orientation - never returned by ogl_tex_load, since it automatically
|
||||
// flips to match global orientation. these are passed to tex_write
|
||||
// to indicate the image orientation, or to tex_set_global_orientation.
|
||||
TEX_BOTTOM_UP = 0x40,
|
||||
@ -43,25 +45,41 @@ enum TexInfoFlags
|
||||
};
|
||||
|
||||
// minimize size - stored in ogl tex resource control block
|
||||
struct TexInfo
|
||||
struct Tex
|
||||
{
|
||||
Handle hm; // H_Mem handle to loaded file
|
||||
size_t ofs; // offset to image data in file
|
||||
uint w : 16;
|
||||
uint h : 16;
|
||||
uint bpp : 16; // average bits per pixel
|
||||
uint bpp : 16;
|
||||
|
||||
// describes the image format; indicates deviations from the
|
||||
// plain image format (e.g. BGR layout, upside-down).
|
||||
//
|
||||
// we don't want to just load as-is and set flags, thereby dumping the
|
||||
// burden of conversion on apps. instead, we convert as much as is
|
||||
// convenient and necessary at load-time. however, we do not go
|
||||
// overboard and support all<->all conversions (too complex) or
|
||||
// encourage transforms at runtime.
|
||||
// in release builds, prefer formats optimized for their intended use
|
||||
// that don't require any preprocessing (minimize load time!)
|
||||
uint flags : 16;
|
||||
};
|
||||
|
||||
extern int tex_load(const char* fn, TexInfo* ti);
|
||||
extern int tex_load_mem(Handle hm, const char* fn, TexInfo* t);
|
||||
extern int tex_free(TexInfo* ti);
|
||||
|
||||
extern int tex_load(const char* fn, Tex* t);
|
||||
extern int tex_load_mem(Handle hm, const char* fn, Tex* t);
|
||||
extern int tex_free(Tex* t);
|
||||
|
||||
extern u8* tex_get_data(const Tex* t);
|
||||
extern int tex_transform(Tex* t, uint new_flags);
|
||||
|
||||
extern int tex_write(const char* fn, uint w, uint h, uint bpp, uint flags, void* img);
|
||||
|
||||
extern int tex_is_known_fmt(void* p, size_t size_t);
|
||||
|
||||
// rationale: some libraries can flip images for free when loading, so set a
|
||||
// global orientation rather than only flipping at upload time.
|
||||
// param: either TEX_BOTTOM_UP or TEX_TOP_DOWN
|
||||
extern void tex_set_global_orientation(int orientation);
|
||||
|
||||
|
||||
#endif // __TEX_H__
|
||||
|
152
source/lib/res/graphics/tex_bmp.cpp
Normal file
152
source/lib/res/graphics/tex_bmp.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "lib/byte_order.h"
|
||||
#include "tex_codec.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct BmpHeader
|
||||
{
|
||||
//
|
||||
// BITMAPFILEHEADER
|
||||
//
|
||||
|
||||
u16 bfType; // "BM"
|
||||
u32 bfSize; // of file
|
||||
u16 bfReserved1;
|
||||
u16 bfReserved2;
|
||||
u32 bfOffBits; // offset to image data
|
||||
|
||||
|
||||
//
|
||||
// BITMAPINFOHEADER
|
||||
//
|
||||
|
||||
u32 biSize;
|
||||
long biWidth;
|
||||
long biHeight;
|
||||
u16 biPlanes;
|
||||
u16 biBitCount;
|
||||
u32 biCompression;
|
||||
u32 biSizeImage;
|
||||
|
||||
// the following are unused and zeroed when writing:
|
||||
long biXPelsPerMeter;
|
||||
long biYPelsPerMeter;
|
||||
u32 biClrUsed;
|
||||
u32 biClrImportant;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#define BI_RGB 0 // biCompression
|
||||
|
||||
|
||||
static int bmp_transform(Tex* t, int new_flags)
|
||||
{
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
}
|
||||
|
||||
|
||||
// requirements: uncompressed, direct colour, bottom up
|
||||
static int bmp_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
{
|
||||
// check header signature (bfType == "BM"?).
|
||||
// we compare single bytes to be endian-safe.
|
||||
if(file[0] != 'B' || file[1] != 'M')
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
const size_t hdr_size = sizeof(BmpHeader);
|
||||
|
||||
// make sure we can access all header fields
|
||||
if(file_size < hdr_size)
|
||||
{
|
||||
*perr_msg = "header not completely read";
|
||||
fail:
|
||||
return ERR_CORRUPTED;
|
||||
}
|
||||
|
||||
const BmpHeader* hdr = (const BmpHeader*)file;
|
||||
const long w = (long)read_le32(&hdr->biWidth);
|
||||
const long h_ = (long)read_le32(&hdr->biHeight);
|
||||
const u16 bpp = read_le16(&hdr->biBitCount);
|
||||
const u32 compress = read_le32(&hdr->biCompression);
|
||||
const u32 ofs = read_le32(&hdr->bfOffBits);
|
||||
|
||||
const int orientation = (h_ < 0)? TEX_TOP_DOWN : TEX_BOTTOM_UP;
|
||||
const long h = abs(h_);
|
||||
const size_t pitch = w * bpp/8;
|
||||
const size_t img_size = h * pitch;
|
||||
u8* const img = file + ofs;
|
||||
|
||||
int flags = TEX_BGR;
|
||||
if(bpp == 32)
|
||||
flags |= TEX_ALPHA;
|
||||
|
||||
// sanity checks
|
||||
const char* err = 0;
|
||||
if(compress != BI_RGB)
|
||||
err = "compressed";
|
||||
if(bpp != 24 && bpp != 32)
|
||||
err = "invalid bpp (not direct colour)";
|
||||
if(file_size < ofs+img_size)
|
||||
err = "image not completely read";
|
||||
if(err)
|
||||
{
|
||||
*perr_msg = err;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
t->ofs = ofs;
|
||||
t->w = w;
|
||||
t->h = h;
|
||||
t->bpp = bpp;
|
||||
t->flags = flags;
|
||||
|
||||
tex_codec_set_orientation(t, orientation);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int bmp_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const char** perr_msg)
|
||||
{
|
||||
if(stricmp(ext, "bmp"))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
/*
|
||||
CHECK_ERR(fmt_8_or_24_or_32(t->bpp, t->flags));
|
||||
|
||||
const size_t hdr_size = sizeof(BmpHeader); // needed for BITMAPFILEHEADER
|
||||
const size_t file_size = hdr_size + img_size;
|
||||
const long h = (t->flags & TEX_TOP_DOWN)? -(int)t->h : t->h;
|
||||
|
||||
int transforms = t->flags;
|
||||
transforms &= ~TEX_ORIENTATION; // no flip needed - we can set top-down bit.
|
||||
transforms ^= TEX_BGR; // BMP is native BGR.
|
||||
transform(t, img, transforms);
|
||||
|
||||
const BmpHeader hdr =
|
||||
{
|
||||
// BITMAPFILEHEADER
|
||||
0x4d42, // bfType = 'B','M'
|
||||
(u32)file_size, // bfSize
|
||||
0, 0, // bfReserved1,2
|
||||
hdr_size, // bfOffBits
|
||||
|
||||
// BITMAPINFOHEADER
|
||||
40, // biSize = sizeof(BITMAPINFOHEADER)
|
||||
t->w,
|
||||
h,
|
||||
1, // biPlanes
|
||||
t->bpp,
|
||||
BI_RGB, // biCompression
|
||||
(u32)img_size, // biSizeImage
|
||||
0, 0, 0, 0 // unused (bi?PelsPerMeter, biClr*)
|
||||
};
|
||||
|
||||
return write_img(fn, &hdr, sizeof(hdr), img, img_size);
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEX_CODEC_REGISTER(bmp);
|
62
source/lib/res/graphics/tex_codec.h
Normal file
62
source/lib/res/graphics/tex_codec.h
Normal file
@ -0,0 +1,62 @@
|
||||
#ifndef TEX_CODEC_H__
|
||||
#define TEX_CODEC_H__
|
||||
|
||||
#include "tex.h"
|
||||
|
||||
// rationale: no C++ to allow us to store const char* name in vtbl.
|
||||
|
||||
struct TexCodecVTbl
|
||||
{
|
||||
// pointers aren't const, because the textures
|
||||
// may have to be flipped in-place - see "texture orientation".
|
||||
// size is guaranteed to be >= 4.
|
||||
// (usually enough to compare the header's "magic" field;
|
||||
// anyway, no legitimate file will be smaller)
|
||||
int (*decode)(u8* data, size_t data_size, Tex* t, const char** perr_msg);
|
||||
|
||||
// rationale: some codecs cannot calculate the output size beforehand
|
||||
// (e.g. PNG output via libpng); we therefore require each one to
|
||||
// allocate memory itself and return the pointer.
|
||||
int (*encode)(const char* ext, Tex* t, u8** out, size_t* out_size, const char** perr_msg);
|
||||
|
||||
int (*transform)(Tex* t, int new_flags);
|
||||
|
||||
const char* name;
|
||||
};
|
||||
|
||||
|
||||
#define TEX_CODEC_REGISTER(name)\
|
||||
static const TexCodecVTbl vtbl = { name##_decode, name##_encode, name##_transform, #name};\
|
||||
static int dummy = tex_codec_register(&vtbl);
|
||||
|
||||
|
||||
// the given texture cannot be handled by this codec; pass the buck on to the next one
|
||||
const int TEX_CODEC_CANNOT_HANDLE = 1;
|
||||
|
||||
|
||||
// add this vtbl to the codec list. called at NLSO init time by the
|
||||
// TEX_CODEC_REGISTER in each codec file. note that call order and therefore
|
||||
// order in the list is undefined, but since each codec only steps up if it
|
||||
// can handle the given format, this is not a problem.
|
||||
extern int tex_codec_register(const TexCodecVTbl* c);
|
||||
|
||||
// allocate an array of row pointers that point into the given texture data.
|
||||
// <file_orientation> indicates whether the file format is top-down or
|
||||
// bottom-up; the row array is inverted if necessary to match global
|
||||
// orienatation. (this is more efficient than "transforming" later)
|
||||
// used by PNG and JPG codecs; caller must free() rows when done.
|
||||
typedef const u8* RowPtr;
|
||||
typedef RowPtr* RowArray;
|
||||
extern int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
|
||||
int file_orientation, RowArray& rows);
|
||||
|
||||
extern int tex_codec_set_orientation(Tex* t, int file_orientation);
|
||||
|
||||
// check if the given texture format is acceptable: 8bpp grey,
|
||||
// 24bpp color or 32bpp color+alpha (BGR / upside down are permitted).
|
||||
// basically, this is the "plain" format understood by all codecs and
|
||||
// tex_codec_plain_transform.
|
||||
// return 0 if ok or a negative error code.
|
||||
extern int tex_codec_validate_format(uint bpp, uint flags);
|
||||
|
||||
#endif // #ifndef TEX_CODEC_H__
|
309
source/lib/res/graphics/tex_dds.cpp
Normal file
309
source/lib/res/graphics/tex_dds.cpp
Normal file
@ -0,0 +1,309 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "lib/byte_order.h"
|
||||
#include "tex_codec.h"
|
||||
|
||||
// NOTE: the convention is bottom-up for DDS, but there's no way to tell.
|
||||
|
||||
// defs modified from ddraw header
|
||||
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// DDPIXELFORMAT.dwFlags
|
||||
#define DDPF_ALPHAPIXELS 0x00000001
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 dwSize; // size of structure (32)
|
||||
u32 dwFlags; // indicates which fields are valid
|
||||
u32 dwFourCC; // (DDPF_FOURCC) FOURCC code, "DXTn"
|
||||
u32 dwReserved1[5]; // reserved
|
||||
}
|
||||
DDPIXELFORMAT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 dwCaps[4];
|
||||
}
|
||||
DDSCAPS2;
|
||||
|
||||
// DDSURFACEDESC2.dwFlags
|
||||
#define DDSD_HEIGHT 0x00000002
|
||||
#define DDSD_WIDTH 0x00000004
|
||||
#define DDSD_PIXELFORMAT 0x00001000
|
||||
#define DDSD_MIPMAPCOUNT 0x00020000
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 dwSize; // size of structure (124)
|
||||
u32 dwFlags; // indicates which fields are valid
|
||||
u32 dwHeight; // height of main image (pixels)
|
||||
u32 dwWidth; // width of main image (pixels)
|
||||
u32 dwLinearSize; // (DDSD_LINEARSIZE): total image size
|
||||
u32 dwDepth; // (DDSD_DEPTH) vol. textures: vol. depth
|
||||
u32 dwMipMapCount; // (DDSD_MIPMAPCOUNT) total # levels
|
||||
u32 dwReserved1[11]; // reserved
|
||||
DDPIXELFORMAT ddpfPixelFormat; // pixel format description of the surface
|
||||
DDSCAPS2 ddsCaps; // direct draw surface capabilities
|
||||
u32 dwReserved2; // reserved
|
||||
}
|
||||
DDSURFACEDESC2;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
static int dds_decompress(Tex* t)
|
||||
{
|
||||
uint w = t->w, h = t->h;
|
||||
if(w==0 || h==0 || w%4 || h%4)
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
|
||||
int dxt = t->flags & TEX_DXT;
|
||||
debug_assert(dxt == 1 || dxt == 3 || dxt == 5);
|
||||
|
||||
u8* data = tex_get_data(t);
|
||||
/*
|
||||
GLsizei blocks_w = (GLsizei)(round_up(w, 4) / 4);
|
||||
GLsizei blocks_h = (GLsizei)(round_up(h, 4) / 4);
|
||||
GLsizei blocks = blocks_w * blocks_h;
|
||||
GLsizei size = blocks * 16 * (base_fmt == GL_RGB ? 3 : 4);
|
||||
GLsizei pitch = size / (blocks_h*4);
|
||||
UNUSED2(pitch);
|
||||
void* rgb_data = malloc(size);
|
||||
|
||||
debug_assert(imageSize == blocks * (dxt1? 8 : 16));
|
||||
|
||||
// This code is inefficient, but I don't care:
|
||||
|
||||
for(GLsizei block_y = 0; block_y < blocks_h; ++block_y)
|
||||
for(GLsizei block_x = 0; block_x < blocks_w; ++block_x)
|
||||
{
|
||||
int c0_a = 255, c1_a = 255, c2_a = 255, c3_a = 255;
|
||||
u8* alpha = NULL;
|
||||
u8 dxt5alpha[8];
|
||||
|
||||
if(!dxt1)
|
||||
{
|
||||
alpha = (u8*)data;
|
||||
data = (char*)data + 8;
|
||||
|
||||
if(dxt5)
|
||||
{
|
||||
dxt5alpha[0] = alpha[0];
|
||||
dxt5alpha[1] = alpha[1];
|
||||
if(alpha[0] > alpha[1])
|
||||
{
|
||||
dxt5alpha[2] = (6*alpha[0] + 1*alpha[1] + 3)/7;
|
||||
dxt5alpha[3] = (5*alpha[0] + 2*alpha[1] + 3)/7;
|
||||
dxt5alpha[4] = (4*alpha[0] + 3*alpha[1] + 3)/7;
|
||||
dxt5alpha[5] = (3*alpha[0] + 4*alpha[1] + 3)/7;
|
||||
dxt5alpha[6] = (2*alpha[0] + 5*alpha[1] + 3)/7;
|
||||
dxt5alpha[7] = (1*alpha[0] + 6*alpha[1] + 3)/7;
|
||||
}
|
||||
else
|
||||
{
|
||||
dxt5alpha[2] = (4*alpha[0] + 1*alpha[1] + 2)/5;
|
||||
dxt5alpha[3] = (3*alpha[0] + 2*alpha[1] + 2)/5;
|
||||
dxt5alpha[4] = (2*alpha[0] + 3*alpha[1] + 2)/5;
|
||||
dxt5alpha[5] = (1*alpha[0] + 4*alpha[1] + 2)/5;
|
||||
dxt5alpha[6] = 0;
|
||||
dxt5alpha[7] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u16 c0 = *(u16*)( (char*)data + 0 );
|
||||
u16 c1 = *(u16*)( (char*)data + 2 );
|
||||
u32 bits = *(u32*)( (char*)data + 4 );
|
||||
|
||||
data = (char*)data + 8;
|
||||
|
||||
// Unpack 565, and copy high bits to low bits
|
||||
int c0_r = ((c0>>8)&0xF8) | ((c0>>13)&7);
|
||||
int c1_r = ((c1>>8)&0xF8) | ((c1>>13)&7);
|
||||
int c0_g = ((c0>>3)&0xFC) | ((c0>>9 )&3);
|
||||
int c1_g = ((c1>>3)&0xFC) | ((c1>>9 )&3);
|
||||
int c0_b = ((c0<<3)&0xF8) | ((c0>>2 )&7);
|
||||
int c1_b = ((c1<<3)&0xF8) | ((c1>>2 )&7);
|
||||
int c2_r, c2_g, c2_b;
|
||||
int c3_r, c3_g, c3_b;
|
||||
if(!dxt1 || c0 > c1)
|
||||
{
|
||||
c2_r = (c0_r*2+c1_r+1)/3; c2_g = (c0_g*2+c1_g+1)/3; c2_b = (c0_b*2+c1_b+1)/3;
|
||||
c3_r = (c0_r+2*c1_r+1)/3; c3_g = (c0_g+2*c1_g+1)/3; c3_b = (c0_b+2*c1_b+1)/3;
|
||||
}
|
||||
else
|
||||
{
|
||||
c2_r = (c0_r+c1_r)/2; c2_g = (c0_g+c1_g)/2; c2_b = (c0_b+c1_b)/2;
|
||||
c3_r = c3_g = c3_b = c3_a = 0;
|
||||
}
|
||||
|
||||
if(base_fmt == GL_RGB)
|
||||
{
|
||||
int i = 0;
|
||||
for(int y = 0; y < 4; ++y)
|
||||
{
|
||||
u8* out = (u8*)rgb_data + ((block_y*4+y)*blocks_w*4 + block_x*4) * 3;
|
||||
for(int x = 0; x < 4; ++x, ++i)
|
||||
{
|
||||
switch((bits >> (2*i)) & 3) {
|
||||
case 0: *out++ = c0_r; *out++ = c0_g; *out++ = c0_b; break;
|
||||
case 1: *out++ = c1_r; *out++ = c1_g; *out++ = c1_b; break;
|
||||
case 2: *out++ = c2_r; *out++ = c2_g; *out++ = c2_b; break;
|
||||
case 3: *out++ = c3_r; *out++ = c3_g; *out++ = c3_b; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
for(int y = 0; y < 4; ++y)
|
||||
{
|
||||
u8* out = (u8*)rgb_data + ((block_y*4+y)*blocks_w*4 + block_x*4) * 4;
|
||||
for(int x = 0; x < 4; ++x, ++i)
|
||||
{
|
||||
int a = 0; // squelch bogus uninitialized warning
|
||||
switch((bits >> (2*i)) & 3) {
|
||||
case 0: *out++ = c0_r; *out++ = c0_g; *out++ = c0_b; a = c0_a; break;
|
||||
case 1: *out++ = c1_r; *out++ = c1_g; *out++ = c1_b; a = c1_a; break;
|
||||
case 2: *out++ = c2_r; *out++ = c2_g; *out++ = c2_b; a = c2_a; break;
|
||||
case 3: *out++ = c3_r; *out++ = c3_g; *out++ = c3_b; a = c3_a; break;
|
||||
}
|
||||
if(dxt3)
|
||||
{
|
||||
a = (int)((*(u64*)alpha >> (4*i)) & 0xF);
|
||||
a |= a<<4; // copy low bits to high bits
|
||||
}
|
||||
else if(dxt5)
|
||||
{
|
||||
a = dxt5alpha[(*(u64*)(alpha+2) >> (3*i)) & 0x7];
|
||||
}
|
||||
*out++ = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dds_transform(Tex* t, int new_flags)
|
||||
{
|
||||
const int dxt = t->flags & TEX_DXT, new_dxt = new_flags & TEX_DXT;
|
||||
// requesting decompression
|
||||
if(dxt && !new_dxt)
|
||||
return dds_decompress(t);
|
||||
// both are DXT (unsupported; there are no flags we can change while
|
||||
// compressed) or requesting compression (not implemented) or
|
||||
// both not DXT (nothing we can do) - bail.
|
||||
else
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
}
|
||||
|
||||
|
||||
static int dds_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
{
|
||||
if(*(u32*)file != FOURCC('D','D','S',' '))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
const DDSURFACEDESC2* surf = (const DDSURFACEDESC2*)(file+4);
|
||||
const size_t hdr_size = 4+sizeof(DDSURFACEDESC2);
|
||||
|
||||
// make sure we can access all header fields
|
||||
if(file_size < hdr_size)
|
||||
{
|
||||
*perr_msg = "header not completely read";
|
||||
fail:
|
||||
return ERR_CORRUPTED;
|
||||
}
|
||||
|
||||
const u32 sd_size = read_le32(&surf->dwSize);
|
||||
const u32 sd_flags = read_le32(&surf->dwFlags);
|
||||
const u32 h = read_le32(&surf->dwHeight);
|
||||
const u32 w = read_le32(&surf->dwWidth);
|
||||
const u32 img_size = read_le32(&surf->dwLinearSize);
|
||||
u32 mipmaps = read_le32(&surf->dwMipMapCount);
|
||||
const u32 pf_size = read_le32(&surf->ddpfPixelFormat.dwSize);
|
||||
const u32 pf_flags = read_le32(&surf->ddpfPixelFormat.dwFlags);
|
||||
const u32 fourcc = surf->ddpfPixelFormat.dwFourCC;
|
||||
// compared against FOURCC, which takes care of endian conversion.
|
||||
|
||||
// we'll use these fields; make sure they're present below.
|
||||
// note: we can't guess image dimensions if not specified -
|
||||
// the image isn't necessarily square.
|
||||
const u32 sd_req_flags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT;
|
||||
|
||||
// make sure fields that aren't indicated as valid are zeroed.
|
||||
if(!(sd_flags & DDSD_MIPMAPCOUNT))
|
||||
mipmaps = 0;
|
||||
|
||||
// MS DXTex tool doesn't set the required dwPitchOrLinearSize field -
|
||||
// they can't even write out their own file format correctly. *sigh*
|
||||
// we need to pass it to OpenGL; it's calculated from w, h, and bpp,
|
||||
// which we determine from the pixel format.
|
||||
int bpp = 0;
|
||||
int flags = 0;
|
||||
|
||||
if(pf_flags & DDPF_ALPHAPIXELS)
|
||||
flags |= TEX_ALPHA;
|
||||
|
||||
switch(fourcc)
|
||||
{
|
||||
case FOURCC('D','X','T','1'):
|
||||
bpp = 4;
|
||||
flags |= 1; // bits in TEX_DXT mask indicate format
|
||||
break;
|
||||
case FOURCC('D','X','T','3'):
|
||||
bpp = 8;
|
||||
flags |= 3; // "
|
||||
break;
|
||||
case FOURCC('D','X','T','5'):
|
||||
bpp = 8;
|
||||
flags |= 5; // "
|
||||
break;
|
||||
}
|
||||
|
||||
if(mipmaps)
|
||||
flags |= TEX_MIPMAPS;
|
||||
|
||||
// sanity checks
|
||||
const char* err = 0;
|
||||
if(file_size < hdr_size + img_size)
|
||||
err = "file size too small";
|
||||
if(w % 4 || h % 4)
|
||||
err = "image dimensions not padded to S3TC block size";
|
||||
if(!w || !h)
|
||||
err = "width or height = 0";
|
||||
if(bpp == 0)
|
||||
err = "invalid pixel format (not DXT{1,3,5})";
|
||||
if((sd_flags & sd_req_flags) != sd_req_flags)
|
||||
err = "missing one or more required fields (w, h, pixel format)";
|
||||
if(sizeof(DDPIXELFORMAT) != pf_size)
|
||||
err = "DDPIXELFORMAT size mismatch";
|
||||
if(sizeof(DDSURFACEDESC2) != sd_size)
|
||||
err = "DDSURFACEDESC2 size mismatch";
|
||||
if(err)
|
||||
{
|
||||
*perr_msg = err;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
t->ofs = hdr_size;
|
||||
t->w = w;
|
||||
t->h = h;
|
||||
t->bpp = bpp;
|
||||
t->flags = flags;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int dds_encode(const char* UNUSED(ext), Tex* UNUSED(t), u8** UNUSED(out), size_t* UNUSED(out_size), const char** perr_msg)
|
||||
{
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
TEX_CODEC_REGISTER(dds);
|
667
source/lib/res/graphics/tex_jpg.cpp
Normal file
667
source/lib/res/graphics/tex_jpg.cpp
Normal file
@ -0,0 +1,667 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
extern "C" {
|
||||
// this is not a core library module, so it doesn't define JPEG_INTERNALS
|
||||
#include "jpeglib.h"
|
||||
#include "jerror.h"
|
||||
}
|
||||
|
||||
#include "lib.h"
|
||||
#include "lib/res/res.h"
|
||||
#include "tex_codec.h"
|
||||
|
||||
#if MSC_VERSION
|
||||
# ifdef NDEBUG
|
||||
# pragma comment(lib, "jpeg-6b.lib")
|
||||
# else
|
||||
# pragma comment(lib, "jpeg-6bd.lib")
|
||||
# endif // #ifdef NDEBUG
|
||||
#endif // #ifdef MSC_VERSION
|
||||
|
||||
|
||||
|
||||
/* IMPORTANT: we assume that JOCTET is 8 bits. */
|
||||
cassert(sizeof(JOCTET) == 1 && CHAR_BIT == 8);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// mem source manager
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
/* Expanded data source object for memory input */
|
||||
typedef struct
|
||||
{
|
||||
struct jpeg_source_mgr pub; /* public fields */
|
||||
|
||||
JOCTET* buf;
|
||||
size_t size; /* total size (bytes) */
|
||||
size_t pos; /* offset (bytes) to new data */
|
||||
}
|
||||
MemSrcMgr;
|
||||
|
||||
typedef MemSrcMgr* SrcPtr;
|
||||
|
||||
|
||||
/*
|
||||
* Initialize source --- called by jpeg_read_header
|
||||
* before any data is actually read.
|
||||
*/
|
||||
|
||||
METHODDEF(void) init_source(j_decompress_ptr UNUSED(cinfo))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Fill the input buffer --- called whenever buffer is emptied.
|
||||
*
|
||||
* In typical applications, this should read fresh data into the buffer
|
||||
* (ignoring the current state of next_input_byte & bytes_in_buffer),
|
||||
* reset the pointer & count to the start of the buffer, and return TRUE
|
||||
* indicating that the buffer has been reloaded. It is not necessary to
|
||||
* fill the buffer entirely, only to obtain at least one more byte.
|
||||
*
|
||||
* There is no such thing as an EOF return. If the end of the file has been
|
||||
* reached, the routine has a choice of ERREXIT() or inserting fake data into
|
||||
* the buffer. In most cases, generating a warning message and inserting a
|
||||
* fake EOI marker is the best course of action --- this will allow the
|
||||
* decompressor to output however much of the image is there. However,
|
||||
* the resulting error message is misleading if the real problem is an empty
|
||||
* input file, so we handle that case specially.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean) fill_input_buffer(j_decompress_ptr cinfo)
|
||||
{
|
||||
SrcPtr src = (SrcPtr)cinfo->src;
|
||||
static const JOCTET eoi[2] = { 0xFF, JPEG_EOI };
|
||||
|
||||
/*
|
||||
* since jpeg_mem_src fills the buffer with everything we've got,
|
||||
* jpeg is trying to read beyond end of buffer. return a fake EOI marker.
|
||||
* note: don't modify input buffer: it might be read-only.
|
||||
*/
|
||||
|
||||
WARNMS(cinfo, JWRN_JPEG_EOF);
|
||||
|
||||
|
||||
src->pub.next_input_byte = eoi;
|
||||
src->pub.bytes_in_buffer = 2;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Skip data --- used to skip over a potentially large amount of
|
||||
* uninteresting data (such as an APPn marker).
|
||||
*/
|
||||
|
||||
METHODDEF(void) skip_input_data(j_decompress_ptr cinfo, long num_bytes)
|
||||
{
|
||||
SrcPtr src = (SrcPtr)cinfo->src;
|
||||
size_t skip_count = (size_t)num_bytes;
|
||||
|
||||
/* docs say non-positive num_byte skips should be ignored */
|
||||
if(num_bytes <= 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* just subtract bytes available in buffer,
|
||||
* making sure we don't underflow the size_t.
|
||||
* note: if we skip to or beyond end of buffer,
|
||||
* bytes_in_buffer = 0 => fill_input_buffer called => abort.
|
||||
*/
|
||||
if(skip_count > src->pub.bytes_in_buffer)
|
||||
skip_count = src->pub.bytes_in_buffer;
|
||||
|
||||
src->pub.bytes_in_buffer -= skip_count;
|
||||
src->pub.next_input_byte += skip_count;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* An additional method that can be provided by data source modules is the
|
||||
* resync_to_restart method for error recovery in the presence of RST markers.
|
||||
* For the moment, this source module just uses the default resync method
|
||||
* provided by the JPEG library. That method assumes that no backtracking
|
||||
* is possible.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Terminate source --- called by jpeg_finish_decompress
|
||||
* after all data has been read. Often a no-op.
|
||||
*
|
||||
* NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
|
||||
* application must deal with any cleanup that should happen even
|
||||
* for error exit.
|
||||
*/
|
||||
|
||||
METHODDEF(void) term_source(j_decompress_ptr UNUSED(cinfo))
|
||||
{
|
||||
/*
|
||||
* no-op (we don't own the buffer and shouldn't,
|
||||
* to make possible multiple images in a source).
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Prepare for input from a buffer.
|
||||
* The caller is responsible for freeing it after finishing decompression.
|
||||
*/
|
||||
|
||||
GLOBAL(void) jpeg_mem_src(j_decompress_ptr cinfo, void* p, size_t size)
|
||||
{
|
||||
SrcPtr src;
|
||||
|
||||
/* Treat 0-length buffer as fatal error */
|
||||
if(size == 0)
|
||||
ERREXIT(cinfo, JERR_INPUT_EMPTY);
|
||||
|
||||
/*
|
||||
* The source object is made permanent so that
|
||||
* a series of JPEG images can be read from the same file
|
||||
* by calling jpeg_mem_src only before the first one.
|
||||
* This makes it unsafe to use this manager and a different source
|
||||
* manager serially with the same JPEG object. Caveat programmer.
|
||||
*/
|
||||
|
||||
/* first time for this JPEG object? */
|
||||
if(!cinfo->src)
|
||||
cinfo->src = (struct jpeg_source_mgr*)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_PERMANENT,
|
||||
sizeof(MemSrcMgr));
|
||||
/* (takes care of raising error if out of memory) */
|
||||
|
||||
src = (SrcPtr)cinfo->src;
|
||||
src->pub.init_source = init_source;
|
||||
src->pub.fill_input_buffer = fill_input_buffer;
|
||||
src->pub.skip_input_data = skip_input_data;
|
||||
src->pub.resync_to_restart = jpeg_resync_to_restart; /* default */
|
||||
src->pub.term_source = term_source;
|
||||
|
||||
/*
|
||||
* fill buffer with everything we have.
|
||||
* if fill_input_buffer is called, the buffer was overrun.
|
||||
*/
|
||||
src->pub.bytes_in_buffer = size;
|
||||
src->pub.next_input_byte = (JOCTET*)p;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// mem destination manager
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/* Expanded data destination object for VFS output */
|
||||
typedef struct {
|
||||
struct jpeg_destination_mgr pub; /* public fields */
|
||||
|
||||
Handle hf;
|
||||
JOCTET* buf;
|
||||
// jpeg-6b interface needs a memory buffer
|
||||
} VfsDstMgr;
|
||||
|
||||
typedef VfsDstMgr* DstPtr;
|
||||
|
||||
#define OUTPUT_BUF_SIZE 16*KiB /* choose an efficiently writeable size */
|
||||
|
||||
|
||||
/*
|
||||
* Initialize destination --- called by jpeg_start_compress
|
||||
* before any data is actually written.
|
||||
*/
|
||||
|
||||
METHODDEF(void) init_destination(j_compress_ptr cinfo)
|
||||
{
|
||||
DstPtr dst = (DstPtr)cinfo->dest;
|
||||
|
||||
/* Allocate the output buffer --- it will be released when done with image */
|
||||
dst->buf = (JOCTET*)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
OUTPUT_BUF_SIZE * sizeof(JOCTET));
|
||||
|
||||
dst->pub.next_output_byte = dst->buf;
|
||||
dst->pub.free_in_buffer = OUTPUT_BUF_SIZE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Empty the output buffer --- called whenever buffer fills up.
|
||||
*
|
||||
* In typical applications, this should write the entire output buffer
|
||||
* (ignoring the current state of next_output_byte & free_in_buffer),
|
||||
* reset the pointer & count to the start of the buffer, and return TRUE
|
||||
* indicating that the buffer has been dumped.
|
||||
*
|
||||
* In applications that need to be able to suspend compression due to output
|
||||
* overrun, a FALSE return indicates that the buffer cannot be emptied now.
|
||||
* In this situation, the compressor will return to its caller (possibly with
|
||||
* an indication that it has not accepted all the supplied scanlines). The
|
||||
* application should resume compression after it has made more room in the
|
||||
* output buffer. Note that there are substantial restrictions on the use of
|
||||
* suspension --- see the documentation.
|
||||
*
|
||||
* When suspending, the compressor will back up to a convenient restart point
|
||||
* (typically the start of the current MCU). next_output_byte & free_in_buffer
|
||||
* indicate where the restart point will be if the current call returns FALSE.
|
||||
* Data beyond this point will be regenerated after resumption, so do not
|
||||
* write it out when emptying the buffer externally.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean) empty_output_buffer(j_compress_ptr cinfo)
|
||||
{
|
||||
DstPtr dst = (DstPtr)cinfo->dest;
|
||||
|
||||
// writing out OUTPUT_BUF_SIZE-dst->pub.free_in_buffer bytes
|
||||
// sounds reasonable, but make for broken output.
|
||||
if(vfs_io(dst->hf, OUTPUT_BUF_SIZE, (void**)&dst->buf) != OUTPUT_BUF_SIZE)
|
||||
ERREXIT(cinfo, JERR_FILE_WRITE);
|
||||
|
||||
dst->pub.next_output_byte = dst->buf;
|
||||
dst->pub.free_in_buffer = OUTPUT_BUF_SIZE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Terminate destination --- called by jpeg_finish_compress
|
||||
* after all data has been written. Usually needs to flush buffer.
|
||||
*
|
||||
* NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
|
||||
* application must deal with any cleanup that should happen even
|
||||
* for error exit.
|
||||
*/
|
||||
|
||||
METHODDEF(void) term_destination(j_compress_ptr cinfo)
|
||||
{
|
||||
DstPtr dst = (DstPtr)cinfo->dest;
|
||||
|
||||
// make sure any data left in the buffer is written out
|
||||
const size_t bytes_in_buf = OUTPUT_BUF_SIZE - dst->pub.free_in_buffer;
|
||||
if(vfs_io(dst->hf, bytes_in_buf, (void**)&dst->buf) != (ssize_t)bytes_in_buf)
|
||||
ERREXIT(cinfo, JERR_FILE_WRITE);
|
||||
|
||||
// flush file, if necessary.
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Prepare for output to a stdio stream.
|
||||
* The caller must have already opened the stream, and is responsible
|
||||
* for closing it after finishing compression.
|
||||
*/
|
||||
|
||||
GLOBAL(void) jpeg_vfs_dst(j_compress_ptr cinfo, Handle hf)
|
||||
{
|
||||
/* The destination object is made permanent so that multiple JPEG images
|
||||
* can be written to the same file without re-executing jpeg_stdio_dest.
|
||||
* This makes it dangerous to use this manager and a different destination
|
||||
* manager serially with the same JPEG object, because their private object
|
||||
* sizes may be different. Caveat programmer.
|
||||
*/
|
||||
if (cinfo->dest == NULL) { /* first time for this JPEG object? */
|
||||
cinfo->dest = (struct jpeg_destination_mgr*)(*cinfo->mem->alloc_small)
|
||||
((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(VfsDstMgr));
|
||||
}
|
||||
|
||||
DstPtr dst = (DstPtr)cinfo->dest;
|
||||
dst->pub.init_destination = init_destination;
|
||||
dst->pub.empty_output_buffer = empty_output_buffer;
|
||||
dst->pub.term_destination = term_destination;
|
||||
dst->hf = hf;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// error handler, shared by jpg_(en|de)code
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// the JPEG library's standard error handler (jerror.c) is divided into
|
||||
// several "methods" which we can override individually. This allows
|
||||
// adjusting the behavior without duplicating a lot of code, which may
|
||||
// have to be updated with each future release.
|
||||
//
|
||||
// we here override error_exit to return control to the library's caller
|
||||
// (i.e. jpg_(de|en)code) when a fatal error occurs, rather than calling exit.
|
||||
//
|
||||
// the replacement error_exit does a longjmp back to the caller's
|
||||
// setjmp return point. it needs access to the jmp_buf,
|
||||
// so we store it in a "subclass" of jpeg_error_mgr.
|
||||
|
||||
struct JpgErrMgr
|
||||
{
|
||||
struct jpeg_error_mgr pub; // "public" fields
|
||||
|
||||
jmp_buf call_site; // jump here (back to JPEG lib caller) on error
|
||||
char msg[JMSG_LENGTH_MAX]; // description of first error encountered
|
||||
// must store per JPEG context for thread safety.
|
||||
// initialized as part of JPEG context error setup.
|
||||
};
|
||||
|
||||
METHODDEF(void) jpg_error_exit(j_common_ptr cinfo)
|
||||
{
|
||||
// get subclass
|
||||
JpgErrMgr* err_mgr = (JpgErrMgr*)cinfo->err;
|
||||
|
||||
// "output" error message (i.e. store in JpgErrMgr;
|
||||
// call_site is responsible for displaying it via debug_printf)
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
|
||||
// jump back to call site, i.e. jpg_(de|en)code
|
||||
longjmp(err_mgr->call_site, 1);
|
||||
}
|
||||
|
||||
|
||||
// stores message in JpgErrMgr for later output by jpg_(de|en)code.
|
||||
// note: don't display message here, so the caller can
|
||||
// add some context (whether encoding or decoding, and filename).
|
||||
METHODDEF(void) jpg_output_message(j_common_ptr cinfo)
|
||||
{
|
||||
// get subclass
|
||||
JpgErrMgr* err_mgr = (JpgErrMgr*)cinfo->err;
|
||||
|
||||
// this context already had an error message; don't overwrite it.
|
||||
// (subsequent errors probably aren't related to the real problem).
|
||||
// note: was set to '\0' by JPEG context error setup.
|
||||
if(err_mgr->msg[0] != '\0')
|
||||
return;
|
||||
|
||||
// generate the message and store it
|
||||
(*cinfo->err->format_message)(cinfo, err_mgr->msg);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
static int jpg_transform(Tex* t, int new_flags)
|
||||
{
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
}
|
||||
|
||||
|
||||
static int jpg_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
{
|
||||
// JFIF requires SOI marker at start of stream.
|
||||
// we compare single bytes to be endian-safe.
|
||||
if(file[0] != 0xff || file[1] == 0xd8)
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
int err = -1;
|
||||
|
||||
// freed when ret is reached:
|
||||
struct jpeg_decompress_struct cinfo;
|
||||
// contains the JPEG decompression parameters and pointers to
|
||||
// working space (allocated as needed by the JPEG library).
|
||||
RowArray rows = 0;
|
||||
// array of pointers to scanlines in img, set by alloc_rows.
|
||||
// jpeg won't output more than a few scanlines at a time,
|
||||
// so we need an output loop anyway, but passing at least 2..4
|
||||
// rows is more efficient in low-quality modes (due to less copying).
|
||||
|
||||
// freed when fail is reached:
|
||||
Handle img_hm; // decompressed image memory
|
||||
|
||||
// set up our error handler, which overrides jpeg's default
|
||||
// write-to-stderr-and-exit behavior.
|
||||
// notes:
|
||||
// - must be done before jpeg_create_decompress, in case that fails
|
||||
// (unlikely, but possible if out of memory).
|
||||
// - valid over cinfo lifetime (avoids dangling pointer in cinfo)
|
||||
JpgErrMgr jerr;
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = jpg_error_exit;
|
||||
jerr.pub.output_message = jpg_output_message;
|
||||
jerr.msg[0] = '\0';
|
||||
// required for "already have message" check in output_message
|
||||
if(setjmp(jerr.call_site))
|
||||
{
|
||||
fail:
|
||||
// either JPEG has raised an error, or code below failed.
|
||||
mem_free_h(img_hm);
|
||||
goto ret;
|
||||
}
|
||||
|
||||
// goto scoping
|
||||
{
|
||||
jpeg_create_decompress(&cinfo);
|
||||
|
||||
jpeg_mem_src(&cinfo, file, file_size);
|
||||
|
||||
|
||||
//
|
||||
// read header, determine format
|
||||
//
|
||||
|
||||
(void) jpeg_read_header(&cinfo, TRUE);
|
||||
// we can ignore the return value since:
|
||||
// - suspension is not possible with the mem data source
|
||||
// - we passed TRUE to raise an error if table-only JPEG file
|
||||
|
||||
int bpp = cinfo.num_components * 8;
|
||||
// preliminary; set below to reflect output params
|
||||
|
||||
// make sure we get a colour format we know
|
||||
// (exception: if bpp = 8, go greyscale below)
|
||||
// necessary to support non-standard CMYK files written by Photoshop.
|
||||
cinfo.out_color_space = JCS_RGB;
|
||||
|
||||
int flags = 0;
|
||||
if(bpp == 8)
|
||||
{
|
||||
flags |= TEX_GREY;
|
||||
cinfo.out_color_space = JCS_GRAYSCALE;
|
||||
}
|
||||
|
||||
// lower quality, but faster
|
||||
cinfo.dct_method = JDCT_IFAST;
|
||||
cinfo.do_fancy_upsampling = FALSE;
|
||||
|
||||
|
||||
(void) jpeg_start_decompress(&cinfo);
|
||||
// we can ignore the return value since
|
||||
// suspension is not possible with the mem data source.
|
||||
|
||||
// scaled output image dimensions and final bpp are now available.
|
||||
int w = cinfo.output_width;
|
||||
int h = cinfo.output_height;
|
||||
bpp = cinfo.output_components * 8;
|
||||
|
||||
// note: since we've set out_color_space, JPEG will always
|
||||
// return an acceptable image format; no need to check.
|
||||
|
||||
|
||||
//
|
||||
// allocate memory for uncompressed image
|
||||
//
|
||||
|
||||
size_t pitch = w * bpp / 8;
|
||||
// needed by alloc_rows
|
||||
const size_t img_size = pitch * h;
|
||||
// cannot free old t->hm until after jpeg_finish_decompress,
|
||||
// but need to set to this handle afterwards => need tmp var.
|
||||
u8* img = (u8*)mem_alloc(img_size, 64*KiB, 0, &img_hm);
|
||||
if(!img)
|
||||
{
|
||||
err = ERR_NO_MEM;
|
||||
goto fail;
|
||||
}
|
||||
int ret = tex_codec_alloc_rows(img, h, pitch, TEX_TOP_DOWN, rows);
|
||||
if(ret < 0)
|
||||
{
|
||||
err = ret;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
// could use cinfo.output_scanline to keep track of progress,
|
||||
// but we need to count lines_left anyway (paranoia).
|
||||
JSAMPARRAY row = (JSAMPARRAY)rows;
|
||||
JDIMENSION lines_left = h;
|
||||
while(lines_left != 0)
|
||||
{
|
||||
JDIMENSION lines_read = jpeg_read_scanlines(&cinfo, row, lines_left);
|
||||
row += lines_read;
|
||||
lines_left -= lines_read;
|
||||
|
||||
// we've decoded in-place; no need to further process
|
||||
}
|
||||
|
||||
(void)jpeg_finish_decompress(&cinfo);
|
||||
// we can ignore the return value since suspension
|
||||
// is not possible with the mem data source.
|
||||
|
||||
if(jerr.pub.num_warnings != 0)
|
||||
debug_printf("jpg_decode: corrupt-data warning(s) occurred\n");
|
||||
|
||||
// store image info
|
||||
// .. transparently switch handles - free the old (compressed)
|
||||
// buffer and replace it with the decoded-image memory handle.
|
||||
mem_free_h(t->hm);
|
||||
t->hm = img_hm;
|
||||
t->ofs = 0; // jpeg returns decoded image data; no header
|
||||
t->w = w;
|
||||
t->h = h;
|
||||
t->bpp = bpp;
|
||||
t->flags = flags;
|
||||
|
||||
err = 0;
|
||||
|
||||
}
|
||||
|
||||
// shared cleanup
|
||||
ret:
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
// releases a "good deal" of memory
|
||||
|
||||
free(rows);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// limitation: palette images aren't supported
|
||||
static int jpg_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const char** perr_msg)
|
||||
{
|
||||
if(stricmp(ext, "jpg") && stricmp(ext, "jpeg"))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
const char* msg = 0;
|
||||
int err = -1;
|
||||
|
||||
// freed when ret is reached:
|
||||
struct jpeg_compress_struct cinfo;
|
||||
// contains the JPEG compression parameters and pointers to
|
||||
// working space (allocated as needed by the JPEG library).
|
||||
RowArray rows = 0;
|
||||
// array of pointers to scanlines in img, set by alloc_rows.
|
||||
// jpeg won't output more than a few scanlines at a time,
|
||||
// so we need an output loop anyway, but passing at least 2..4
|
||||
// rows is more efficient in low-quality modes (due to less copying).
|
||||
|
||||
Handle hf = 0;
|
||||
|
||||
// set up our error handler, which overrides jpeg's default
|
||||
// write-to-stderr-and-exit behavior.
|
||||
// notes:
|
||||
// - must be done before jpeg_create_compress, in case that fails
|
||||
// (unlikely, but possible if out of memory).
|
||||
// - valid over cinfo lifetime (avoids dangling pointer in cinfo)
|
||||
JpgErrMgr jerr;
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = jpg_error_exit;
|
||||
jerr.pub.output_message = jpg_output_message;
|
||||
jerr.msg[0] = '\0';
|
||||
// required for "already have message" check in output_message
|
||||
if(setjmp(jerr.call_site))
|
||||
{
|
||||
fail:
|
||||
// either JPEG has raised an error, or code below failed.
|
||||
goto ret;
|
||||
}
|
||||
|
||||
// goto scoping
|
||||
{
|
||||
|
||||
jpeg_create_compress(&cinfo);
|
||||
/*
|
||||
hf = vfs_open(fn, FILE_WRITE|FILE_NO_AIO);
|
||||
if(hf <= 0)
|
||||
{
|
||||
err = (int)hf;
|
||||
goto fail;
|
||||
}
|
||||
jpeg_vfs_dst(&cinfo, hf);
|
||||
*/
|
||||
|
||||
//
|
||||
// describe input image
|
||||
//
|
||||
|
||||
// required:
|
||||
cinfo.image_width = t->w;
|
||||
cinfo.image_height = t->h;
|
||||
cinfo.input_components = t->bpp / 8;
|
||||
cinfo.in_color_space = (t->bpp == 8)? JCS_GRAYSCALE : JCS_RGB;
|
||||
|
||||
// defaults depend on cinfo.in_color_space already having been set!
|
||||
jpeg_set_defaults(&cinfo);
|
||||
|
||||
// more settings (e.g. quality)
|
||||
|
||||
|
||||
jpeg_start_compress(&cinfo, TRUE);
|
||||
// TRUE ensures that we will write a complete interchange-JPEG file.
|
||||
// don't change unless you are very sure of what you're doing.
|
||||
|
||||
|
||||
// make sure we have RGB
|
||||
const int bgr_transform = t->flags & TEX_BGR; // JPG is native RGB.
|
||||
WARN_ERR(tex_transform(t, bgr_transform));
|
||||
|
||||
const size_t pitch = t->w * t->bpp / 8;
|
||||
u8* data = tex_get_data(t);
|
||||
int ret = tex_codec_alloc_rows(data, t->h, pitch, TEX_TOP_DOWN, rows);
|
||||
if(ret < 0)
|
||||
{
|
||||
err = ret;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
// could use cinfo.output_scanline to keep track of progress,
|
||||
// but we need to count lines_left anyway (paranoia).
|
||||
JSAMPARRAY row = (JSAMPARRAY)rows;
|
||||
JDIMENSION lines_left = t->h;
|
||||
while(lines_left != 0)
|
||||
{
|
||||
JDIMENSION lines_read = jpeg_write_scanlines(&cinfo, row, lines_left);
|
||||
row += lines_read;
|
||||
lines_left -= lines_read;
|
||||
|
||||
// we've decoded in-place; no need to further process
|
||||
}
|
||||
|
||||
jpeg_finish_compress(&cinfo);
|
||||
|
||||
if(jerr.pub.num_warnings != 0)
|
||||
debug_printf("jpg_encode: corrupt-data warning(s) occurred\n");
|
||||
|
||||
err = 0;
|
||||
|
||||
}
|
||||
|
||||
// shared cleanup
|
||||
ret:
|
||||
jpeg_destroy_compress(&cinfo);
|
||||
// releases a "good deal" of memory
|
||||
|
||||
free(rows);
|
||||
vfs_close(hf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
TEX_CODEC_REGISTER(jpg);
|
303
source/lib/res/graphics/tex_png.cpp
Normal file
303
source/lib/res/graphics/tex_png.cpp
Normal file
@ -0,0 +1,303 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
// include libpng header. we also prevent it from including windows.h, which
|
||||
// would conflict with other headers. instead, WINAPI is defined here.
|
||||
#if OS_WIN
|
||||
# define _WINDOWS_
|
||||
# define WINAPI __stdcall
|
||||
# define WINAPIV __cdecl
|
||||
#endif // OS_WIN
|
||||
#include "libpng13/png.h"
|
||||
|
||||
#include "lib/byte_order.h"
|
||||
#include "lib/res/res.h"
|
||||
#include "tex_codec.h"
|
||||
|
||||
|
||||
|
||||
#if MSC_VERSION
|
||||
|
||||
// squelch "dtor / setjmp interaction" warnings.
|
||||
// all attempts to resolve the underlying problem failed; apparently
|
||||
// the warning is generated if setjmp is used at all in C++ mode.
|
||||
// (png_decode has no code that would trigger ctors/dtors, nor are any
|
||||
// called in its prolog/epilog code).
|
||||
# pragma warning(disable: 4611)
|
||||
|
||||
// pull in the appropriate debug/release library
|
||||
# ifdef NDEBUG
|
||||
# pragma comment(lib, "libpng13.lib")
|
||||
# else
|
||||
# pragma comment(lib, "libpng13d.lib")
|
||||
# endif // NDEBUG
|
||||
|
||||
#endif // MSC_VERSION
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
struct PngMemFile
|
||||
{
|
||||
const u8* p;
|
||||
size_t size;
|
||||
|
||||
size_t pos; // 0-initialized if no initializer
|
||||
};
|
||||
|
||||
// pass data from PNG file in memory to libpng
|
||||
static void png_io_read(png_struct* png_ptr, u8* data, png_size_t length)
|
||||
{
|
||||
PngMemFile* f = (PngMemFile*)png_ptr->io_ptr;
|
||||
|
||||
void* src = (u8*)(f->p + f->pos);
|
||||
|
||||
// make sure there's enough new data remaining in the buffer
|
||||
f->pos += length;
|
||||
if(f->pos > f->size)
|
||||
png_error(png_ptr, "png_read: not enough data to satisfy request!");
|
||||
|
||||
memcpy(data, src, length);
|
||||
}
|
||||
|
||||
|
||||
// write libpng output to PNG file
|
||||
static void png_io_write(png_struct* png_ptr, u8* data, png_size_t length)
|
||||
{
|
||||
void* p = (void*)data;
|
||||
Handle hf = *(Handle*)png_ptr->io_ptr;
|
||||
if(vfs_io(hf, length, &p) != (ssize_t)length)
|
||||
png_error(png_ptr, "png_write: !");
|
||||
}
|
||||
|
||||
|
||||
static void png_io_flush(png_structp)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static int png_transform(Tex* t, int new_flags)
|
||||
{
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
}
|
||||
|
||||
|
||||
// note: it's not worth combining png_encode and png_decode, due to
|
||||
// libpng read/write interface differences (grr).
|
||||
|
||||
// split out of png_decode to simplify resource cleanup and avoid
|
||||
// "dtor / setjmp interaction" warning.
|
||||
static int png_decode_impl(Tex* t, u8* file, size_t file_size,
|
||||
png_structp png_ptr, png_infop info_ptr,
|
||||
u8*& img, RowArray& rows, const char** perr_msg)
|
||||
{
|
||||
PngMemFile f = { file, file_size };
|
||||
png_set_read_fn(png_ptr, &f, png_io_read);
|
||||
|
||||
// read header and determine format
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
png_uint_32 w, h;
|
||||
int bit_depth, colour_type;
|
||||
png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &colour_type, 0, 0, 0);
|
||||
const size_t pitch = png_get_rowbytes(png_ptr, info_ptr);
|
||||
const u32 bpp = (u32)(pitch / w * 8);
|
||||
|
||||
int flags = 0;
|
||||
if(bpp == 32)
|
||||
flags |= TEX_ALPHA;
|
||||
if(colour_type == PNG_COLOR_TYPE_GRAY)
|
||||
flags |= TEX_GREY;
|
||||
|
||||
// make sure format is acceptable
|
||||
const char* err = 0;
|
||||
if(bit_depth != 8)
|
||||
err = "channel precision != 8 bits";
|
||||
if(colour_type & PNG_COLOR_MASK_PALETTE)
|
||||
err = "colour type is invalid (must be direct colour)";
|
||||
if(err)
|
||||
{
|
||||
*perr_msg = err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const size_t img_size = pitch * h;
|
||||
Handle img_hm;
|
||||
// cannot free old t->hm until after png_read_end,
|
||||
// but need to set to this handle afterwards => need tmp var.
|
||||
img = (u8*)mem_alloc(img_size, 64*KiB, 0, &img_hm);
|
||||
if(!img)
|
||||
return ERR_NO_MEM;
|
||||
|
||||
CHECK_ERR(tex_codec_alloc_rows(img, h, pitch, TEX_TOP_DOWN, rows));
|
||||
|
||||
png_read_image(png_ptr, (png_bytepp)rows);
|
||||
png_read_end(png_ptr, info_ptr);
|
||||
|
||||
// success; make sure all data was consumed.
|
||||
debug_assert(f.p == file && f.size == file_size && f.pos == f.size);
|
||||
|
||||
// store image info
|
||||
// .. transparently switch handles - free the old (compressed)
|
||||
// buffer and replace it with the decoded-image memory handle.
|
||||
mem_free_h(t->hm);
|
||||
t->hm = img_hm;
|
||||
t->ofs = 0; // libpng returns decoded image data; no header
|
||||
t->w = w;
|
||||
t->h = h;
|
||||
t->bpp = bpp;
|
||||
t->flags = flags;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// split out of png_encode to simplify resource cleanup and avoid
|
||||
// "dtor / setjmp interaction" warning.
|
||||
static int png_encode_impl(Tex* t,
|
||||
png_structp png_ptr, png_infop info_ptr,
|
||||
RowArray& rows, Handle& hf, const char** perr_msg)
|
||||
{
|
||||
UNUSED2(perr_msg); // we don't produce any error messages ATM.
|
||||
|
||||
const int png_transforms = (t->flags & TEX_BGR)? PNG_TRANSFORM_BGR : PNG_TRANSFORM_IDENTITY;
|
||||
// PNG is native RGB.
|
||||
|
||||
const png_uint_32 w = t->w, h = t->h;
|
||||
const size_t pitch = w * t->bpp / 8;
|
||||
|
||||
int colour_type;
|
||||
switch(t->flags & (TEX_GREY|TEX_ALPHA))
|
||||
{
|
||||
case TEX_GREY|TEX_ALPHA:
|
||||
colour_type = PNG_COLOR_TYPE_GRAY_ALPHA;
|
||||
break;
|
||||
case TEX_GREY:
|
||||
colour_type = PNG_COLOR_TYPE_GRAY;
|
||||
break;
|
||||
case TEX_ALPHA:
|
||||
colour_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
break;
|
||||
default:
|
||||
colour_type = PNG_COLOR_TYPE_RGB;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
hf = vfs_open(fn, FILE_WRITE|FILE_NO_AIO);
|
||||
CHECK_ERR(hf);
|
||||
png_set_write_fn(png_ptr, &hf, png_io_write, png_io_flush);
|
||||
*/
|
||||
png_set_IHDR(png_ptr, info_ptr, w, h, 8, colour_type,
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
u8* data = tex_get_data(t);
|
||||
CHECK_ERR(tex_codec_alloc_rows(data, h, pitch, TEX_TOP_DOWN, rows));
|
||||
|
||||
png_set_rows(png_ptr, info_ptr, (png_bytepp)rows);
|
||||
png_write_png(png_ptr, info_ptr, png_transforms, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// limitation: palette images aren't supported
|
||||
static int png_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
{
|
||||
// don't use png_sig_cmp, so we don't pull in libpng for
|
||||
// this check alone (it might not actually be used).
|
||||
if(*(u32*)file != FOURCC('\x89','P','N','G'))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
int err = -1;
|
||||
|
||||
// freed when ret is reached:
|
||||
png_structp png_ptr = 0;
|
||||
png_infop info_ptr = 0;
|
||||
RowArray rows = 0;
|
||||
|
||||
// freed if fail is reached:
|
||||
u8* img = 0; // decompressed image memory
|
||||
|
||||
// allocate PNG structures; use default stderr and longjmp error handlers
|
||||
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
|
||||
if(!png_ptr)
|
||||
goto fail;
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if(!info_ptr)
|
||||
goto fail;
|
||||
|
||||
// setup error handling
|
||||
if(setjmp(png_jmpbuf(png_ptr)))
|
||||
{
|
||||
// reached if PNG triggered a longjmp
|
||||
fail:
|
||||
mem_free(img);
|
||||
goto ret;
|
||||
}
|
||||
|
||||
err = png_decode_impl(t, file, file_size, png_ptr, info_ptr, img, rows, perr_msg);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
// shared cleanup
|
||||
ret:
|
||||
free(rows);
|
||||
|
||||
if(png_ptr)
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
// limitation: palette images aren't supported
|
||||
static int png_encode(const char* ext, Tex* t, u8** out, size_t* UNUSED(out_size), const char** perr_msg)
|
||||
{
|
||||
if(stricmp(ext, "png"))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
int err = -1;
|
||||
|
||||
// freed when ret is reached.
|
||||
png_structp png_ptr = 0;
|
||||
png_infop info_ptr = 0;
|
||||
RowArray rows = 0;
|
||||
Handle hf = 0;
|
||||
|
||||
// allocate PNG structures; use default stderr and longjmp error handlers
|
||||
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
|
||||
if(!png_ptr)
|
||||
goto fail;
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if(!info_ptr)
|
||||
goto fail;
|
||||
|
||||
// setup error handling
|
||||
if(setjmp(png_jmpbuf(png_ptr)))
|
||||
{
|
||||
// reached if libpng triggered a longjmp
|
||||
fail:
|
||||
// currently no extra resources that need to be freed
|
||||
goto ret;
|
||||
}
|
||||
|
||||
err = png_encode_impl(t, png_ptr, info_ptr, rows, hf, perr_msg);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
// shared cleanup
|
||||
ret:
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
|
||||
free(rows);
|
||||
vfs_close(hf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
TEX_CODEC_REGISTER(png);
|
157
source/lib/res/graphics/tex_tga.cpp
Normal file
157
source/lib/res/graphics/tex_tga.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "lib/byte_order.h"
|
||||
#include "tex_codec.h"
|
||||
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
enum TgaImgType
|
||||
{
|
||||
TGA_TRUE_COLOUR = 2, // uncompressed 24 or 32 bit direct RGB
|
||||
TGA_GREY = 3 // uncompressed 8 bit direct greyscale
|
||||
};
|
||||
|
||||
enum TgaImgDesc
|
||||
{
|
||||
TGA_RIGHT_TO_LEFT = BIT(4),
|
||||
TGA_TOP_DOWN = BIT(5),
|
||||
TGA_BOTTOM_UP = 0 // opposite of TGA_TOP_DOWN
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 img_id_len; // 0 - no image identifier present
|
||||
u8 colour_map_type; // 0 - no colour map present
|
||||
u8 img_type; // see TgaImgType
|
||||
u8 colour_map[5]; // unused
|
||||
|
||||
u16 x_origin; // unused
|
||||
u16 y_origin; // unused
|
||||
|
||||
u16 w;
|
||||
u16 h;
|
||||
u8 bpp; // bits per pixel
|
||||
|
||||
u8 img_desc;
|
||||
}
|
||||
TgaHeader;
|
||||
|
||||
// TGA file: header [img id] [colour map] image data
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
static int tga_transform(Tex* t, int new_flags)
|
||||
{
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
}
|
||||
|
||||
|
||||
// requirements: uncompressed, direct colour, bottom up
|
||||
static int tga_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
{
|
||||
TgaHeader* hdr = (TgaHeader*)file;
|
||||
|
||||
// the first TGA header doesn't have a magic field;
|
||||
// we can only check if the first 4 bytes are valid
|
||||
// .. not direct colour
|
||||
if(hdr->colour_map_type != 0)
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
// .. wrong colour type (not uncompressed greyscale or RGB)
|
||||
if(hdr->img_type != TGA_TRUE_COLOUR && hdr->img_type != TGA_GREY)
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
// make sure we can access all header fields
|
||||
const size_t hdr_size = sizeof(TgaHeader) + hdr->img_id_len;
|
||||
if(file_size < hdr_size)
|
||||
{
|
||||
*perr_msg = "header not completely read";
|
||||
fail:
|
||||
return ERR_CORRUPTED;
|
||||
}
|
||||
|
||||
const u8 type = hdr->img_type;
|
||||
const uint w = read_le16(&hdr->w);
|
||||
const uint h = read_le16(&hdr->h);
|
||||
const uint bpp = hdr->bpp;
|
||||
const u8 desc = hdr->img_desc;
|
||||
|
||||
const u8 alpha_bits = desc & 0x0f;
|
||||
const int orientation = (desc & TGA_TOP_DOWN)? TEX_TOP_DOWN : TEX_BOTTOM_UP;
|
||||
u8* const img = file + hdr_size;
|
||||
const size_t img_size = w * h * bpp/8;
|
||||
|
||||
|
||||
int flags = 0;
|
||||
if(alpha_bits != 0)
|
||||
flags |= TEX_ALPHA;
|
||||
if(bpp == 8)
|
||||
flags |= TEX_GREY;
|
||||
if(type == TGA_TRUE_COLOUR)
|
||||
flags |= TEX_BGR;
|
||||
|
||||
// sanity checks
|
||||
const char* err = 0;
|
||||
// .. storing right-to-left is just stupid;
|
||||
// we're not going to bother converting it.
|
||||
if(desc & TGA_RIGHT_TO_LEFT)
|
||||
err = "image is stored right-to-left";
|
||||
if(bpp != 8 && bpp != 16 && bpp != 24 && bpp != 32)
|
||||
err = "invalid bpp";
|
||||
if(file_size < hdr_size + img_size)
|
||||
err = "size < image size";
|
||||
if(err)
|
||||
{
|
||||
*perr_msg = err;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
t->ofs = hdr_size;
|
||||
t->w = w;
|
||||
t->h = h;
|
||||
t->bpp = bpp;
|
||||
t->flags = flags;
|
||||
|
||||
tex_codec_set_orientation(t, orientation);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int tga_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const char** perr_msg)
|
||||
{
|
||||
if(stricmp(ext, "tga"))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
/*
|
||||
CHECK_ERR(tex_codec_validate_format(t->bpp, t->flags));
|
||||
|
||||
u8 img_desc = (t->flags & TEX_TOP_DOWN)? TGA_TOP_DOWN : TGA_BOTTOM_UP;
|
||||
if(t->bpp == 32)
|
||||
img_desc |= 8; // size of alpha channel
|
||||
TgaImgType img_type = (t->flags & TEX_GREY)? TGA_GREY : TGA_TRUE_COLOUR;
|
||||
|
||||
// transform
|
||||
int transforms = t->flags;
|
||||
transforms &= ~TEX_ORIENTATION; // no flip needed - we can set top-down bit.
|
||||
transforms ^= TEX_BGR; // TGA is native BGR.
|
||||
transform(t, transforms);
|
||||
|
||||
TgaHeader hdr =
|
||||
{
|
||||
0, // no image identifier present
|
||||
0, // no colour map present
|
||||
(u8)img_type,
|
||||
{0,0,0,0,0}, // unused (colour map)
|
||||
0, 0, // unused (origin)
|
||||
t->w,
|
||||
t->h,
|
||||
t->bpp,
|
||||
img_desc
|
||||
};
|
||||
return write_img(fn, &hdr, sizeof(hdr), img, img_size);
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEX_CODEC_REGISTER(tga);
|
Loading…
Reference in New Issue
Block a user