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:
janwas 2005-09-02 02:56:54 +00:00
parent 9ec828f6fd
commit 71f7fccd12
8 changed files with 1860 additions and 1591 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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__

View 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);

View 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__

View 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);

View 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);

View 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);

View 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);