forked from 0ad/0ad
lib.cpp: forgot to use TEST (for self-test)
self_test: add dox (also to wiki) tex_codecs: further work; renamed+reorg for clarity. separating codecs from IO implementation This was SVN commit r2654.
This commit is contained in:
parent
93313f61bf
commit
f4c535a326
@ -12,6 +12,9 @@
|
||||
#include "UnitManager.h"
|
||||
#include "Unit.h"
|
||||
|
||||
#include "ps/XML/Xeromyces.h"
|
||||
#include "ps/XML/XMLWriter.h"
|
||||
|
||||
#include "lib/res/file/vfs.h"
|
||||
|
||||
#include <sstream>
|
||||
|
@ -488,12 +488,12 @@ namespace test {
|
||||
|
||||
static void test_log2()
|
||||
{
|
||||
debug_assert(ilog2(0) == -1);
|
||||
debug_assert(ilog2(3) == -1);
|
||||
debug_assert(ilog2(0xffffffff) == -1);
|
||||
debug_assert(ilog2(1) == 0);
|
||||
debug_assert(ilog2(256) == 8);
|
||||
debug_assert(ilog2(0x80000000) == 31);
|
||||
TEST(ilog2(0) == -1);
|
||||
TEST(ilog2(3) == -1);
|
||||
TEST(ilog2(0xffffffff) == -1);
|
||||
TEST(ilog2(1) == 0);
|
||||
TEST(ilog2(256) == 8);
|
||||
TEST(ilog2(0x80000000) == 31);
|
||||
}
|
||||
|
||||
static void self_test()
|
||||
|
@ -33,6 +33,39 @@
|
||||
static int global_orientation = TEX_TOP_DOWN;
|
||||
|
||||
|
||||
|
||||
// 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.
|
||||
static int validate_format(uint bpp, uint flags)
|
||||
{
|
||||
const bool alpha = (flags & TEX_ALPHA ) != 0;
|
||||
const bool grey = (flags & TEX_GREY ) != 0;
|
||||
const bool dxt = (flags & TEX_DXT ) != 0;
|
||||
const bool mipmaps = (flags & TEX_MIPMAPS) != 0;
|
||||
|
||||
if(dxt || mipmaps)
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
|
||||
// grey must be 8bpp without alpha, or it's invalid.
|
||||
if(grey)
|
||||
{
|
||||
if(bpp == 8 && !alpha)
|
||||
return 0;
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
}
|
||||
|
||||
if(bpp == 24 && !alpha)
|
||||
return 0;
|
||||
if(bpp == 32 && alpha)
|
||||
return 0;
|
||||
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
}
|
||||
|
||||
|
||||
// handles BGR and row flipping in "plain" format (see below).
|
||||
//
|
||||
// called by codecs after they get their format-specific transforms out of
|
||||
@ -52,7 +85,7 @@ static int plain_transform(Tex* t, uint new_format)
|
||||
if(pending_transforms & ~(TEX_BGR|TEX_ORIENTATION))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
// .. img is not in "plain" format
|
||||
if(tex_codec_validate_format(bpp, flags) != 0)
|
||||
if(validate_format(bpp, flags) != 0)
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
// .. nothing to do
|
||||
if(!pending_transforms)
|
||||
@ -167,7 +200,11 @@ int tex_codec_register(const TexCodecVTbl* c)
|
||||
// <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.
|
||||
//
|
||||
// note: we don't allocate the data param ourselves because this function is
|
||||
// needed for encoding, too (where data is already present).
|
||||
int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
|
||||
int file_orientation, RowArray& rows)
|
||||
{
|
||||
@ -200,38 +237,6 @@ 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.
|
||||
int tex_codec_validate_format(uint bpp, uint flags)
|
||||
{
|
||||
const bool alpha = (flags & TEX_ALPHA ) != 0;
|
||||
const bool grey = (flags & TEX_GREY ) != 0;
|
||||
const bool dxt = (flags & TEX_DXT ) != 0;
|
||||
const bool mipmaps = (flags & TEX_MIPMAPS) != 0;
|
||||
|
||||
if(dxt || mipmaps)
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
|
||||
// grey must be 8bpp without alpha, or it's invalid.
|
||||
if(grey)
|
||||
{
|
||||
if(bpp == 8 && !alpha)
|
||||
return 0;
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
}
|
||||
|
||||
if(bpp == 24 && !alpha)
|
||||
return 0;
|
||||
if(bpp == 32 && alpha)
|
||||
return 0;
|
||||
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// API
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -339,6 +344,9 @@ int tex_transform(Tex* t, uint new_flags)
|
||||
|
||||
int tex_write(const char* fn, uint w, uint h, uint bpp, uint flags, void* in_img)
|
||||
{
|
||||
if(validate_format(bpp, flags) != 0)
|
||||
return ERR_TEX_FMT_INVALID;
|
||||
|
||||
const size_t in_img_size = w * h * bpp / 8;
|
||||
|
||||
const char* ext = strrchr(fn, '.');
|
||||
@ -353,6 +361,7 @@ int tex_write(const char* fn, uint w, uint h, uint bpp, uint flags, void* in_img
|
||||
0, // image data offset
|
||||
w, h, bpp, flags
|
||||
};
|
||||
|
||||
u8* out; size_t out_size;
|
||||
for(int i = 0; i < MAX_CODECS; i++)
|
||||
{
|
||||
|
@ -7,21 +7,14 @@
|
||||
|
||||
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;
|
||||
@ -29,7 +22,6 @@ struct BmpHeader
|
||||
u16 biBitCount;
|
||||
u32 biCompression;
|
||||
u32 biSizeImage;
|
||||
|
||||
// the following are unused and zeroed when writing:
|
||||
long biXPelsPerMeter;
|
||||
long biYPelsPerMeter;
|
||||
@ -113,17 +105,17 @@ static int bmp_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const
|
||||
{
|
||||
if(stricmp(ext, "bmp"))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
/*
|
||||
CHECK_ERR(fmt_8_or_24_or_32(t->bpp, t->flags));
|
||||
|
||||
size_t img_size = t->w * t->h * t->bpp/8;
|
||||
|
||||
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;
|
||||
const long h = (t->flags & TEX_TOP_DOWN)? -(long)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);
|
||||
tex_transform(t, transforms);
|
||||
|
||||
const BmpHeader hdr =
|
||||
{
|
||||
@ -144,8 +136,6 @@ static int bmp_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const
|
||||
0, 0, 0, 0 // unused (bi?PelsPerMeter, biClr*)
|
||||
};
|
||||
|
||||
return write_img(fn, &hdr, sizeof(hdr), img, img_size);
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,11 @@ extern int tex_codec_register(const TexCodecVTbl* c);
|
||||
// <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.
|
||||
//
|
||||
// note: we don't allocate the data param ourselves because this function is
|
||||
// needed for encoding, too (where data is already present).
|
||||
typedef const u8* RowPtr;
|
||||
typedef RowPtr* RowArray;
|
||||
extern int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
|
||||
@ -52,11 +56,12 @@ extern int tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch,
|
||||
|
||||
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);
|
||||
|
||||
struct MemSource
|
||||
{
|
||||
u8* p;
|
||||
size_t size;
|
||||
size_t pos;
|
||||
};
|
||||
|
||||
#endif // #ifndef TEX_CODEC_H__
|
||||
|
@ -37,9 +37,9 @@ typedef struct
|
||||
size_t size; /* total size (bytes) */
|
||||
size_t pos; /* offset (bytes) to new data */
|
||||
}
|
||||
MemSrcMgr;
|
||||
SrcMgr;
|
||||
|
||||
typedef MemSrcMgr* SrcPtr;
|
||||
typedef SrcMgr* SrcPtr;
|
||||
|
||||
|
||||
/*
|
||||
@ -47,7 +47,7 @@ typedef MemSrcMgr* SrcPtr;
|
||||
* before any data is actually read.
|
||||
*/
|
||||
|
||||
METHODDEF(void) init_source(j_decompress_ptr UNUSED(cinfo))
|
||||
METHODDEF(void) src_init(j_decompress_ptr UNUSED(cinfo))
|
||||
{
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ METHODDEF(void) init_source(j_decompress_ptr UNUSED(cinfo))
|
||||
* input file, so we handle that case specially.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean) fill_input_buffer(j_decompress_ptr cinfo)
|
||||
METHODDEF(boolean) src_fill_buffer(j_decompress_ptr cinfo)
|
||||
{
|
||||
SrcPtr src = (SrcPtr)cinfo->src;
|
||||
static const JOCTET eoi[2] = { 0xFF, JPEG_EOI };
|
||||
@ -95,7 +95,7 @@ METHODDEF(boolean) fill_input_buffer(j_decompress_ptr cinfo)
|
||||
* uninteresting data (such as an APPn marker).
|
||||
*/
|
||||
|
||||
METHODDEF(void) skip_input_data(j_decompress_ptr cinfo, long num_bytes)
|
||||
METHODDEF(void) src_skip_data(j_decompress_ptr cinfo, long num_bytes)
|
||||
{
|
||||
SrcPtr src = (SrcPtr)cinfo->src;
|
||||
size_t skip_count = (size_t)num_bytes;
|
||||
@ -136,7 +136,7 @@ METHODDEF(void) skip_input_data(j_decompress_ptr cinfo, long num_bytes)
|
||||
* for error exit.
|
||||
*/
|
||||
|
||||
METHODDEF(void) term_source(j_decompress_ptr UNUSED(cinfo))
|
||||
METHODDEF(void) src_term(j_decompress_ptr UNUSED(cinfo))
|
||||
{
|
||||
/*
|
||||
* no-op (we don't own the buffer and shouldn't,
|
||||
@ -150,7 +150,7 @@ METHODDEF(void) term_source(j_decompress_ptr UNUSED(cinfo))
|
||||
* The caller is responsible for freeing it after finishing decompression.
|
||||
*/
|
||||
|
||||
GLOBAL(void) jpeg_mem_src(j_decompress_ptr cinfo, void* p, size_t size)
|
||||
GLOBAL(void) src_prepare(j_decompress_ptr cinfo, void* p, size_t size)
|
||||
{
|
||||
SrcPtr src;
|
||||
|
||||
@ -170,15 +170,15 @@ GLOBAL(void) jpeg_mem_src(j_decompress_ptr cinfo, void* p, size_t size)
|
||||
if(!cinfo->src)
|
||||
cinfo->src = (struct jpeg_source_mgr*)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_PERMANENT,
|
||||
sizeof(MemSrcMgr));
|
||||
sizeof(SrcMgr));
|
||||
/* (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.init_source = src_init;
|
||||
src->pub.fill_input_buffer = src_fill_buffer;
|
||||
src->pub.skip_input_data = src_skip_data;
|
||||
src->pub.resync_to_restart = jpeg_resync_to_restart; /* default */
|
||||
src->pub.term_source = term_source;
|
||||
src->pub.term_source = src_term;
|
||||
|
||||
/*
|
||||
* fill buffer with everything we have.
|
||||
@ -193,16 +193,15 @@ GLOBAL(void) jpeg_mem_src(j_decompress_ptr cinfo, void* p, size_t size)
|
||||
// mem destination manager
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/* Expanded data destination object for VFS output */
|
||||
/* Expanded data destination object for memory output */
|
||||
typedef struct {
|
||||
struct jpeg_destination_mgr pub; /* public fields */
|
||||
|
||||
Handle hf;
|
||||
JOCTET* buf;
|
||||
// jpeg-6b interface needs a memory buffer
|
||||
} VfsDstMgr;
|
||||
} DstMgr;
|
||||
|
||||
typedef VfsDstMgr* DstPtr;
|
||||
typedef DstMgr* DstPtr;
|
||||
|
||||
#define OUTPUT_BUF_SIZE 16*KiB /* choose an efficiently writeable size */
|
||||
|
||||
@ -212,7 +211,7 @@ typedef VfsDstMgr* DstPtr;
|
||||
* before any data is actually written.
|
||||
*/
|
||||
|
||||
METHODDEF(void) init_destination(j_compress_ptr cinfo)
|
||||
METHODDEF(void) dst_init(j_compress_ptr cinfo)
|
||||
{
|
||||
DstPtr dst = (DstPtr)cinfo->dest;
|
||||
|
||||
@ -248,14 +247,14 @@ METHODDEF(void) init_destination(j_compress_ptr cinfo)
|
||||
* write it out when emptying the buffer externally.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean) empty_output_buffer(j_compress_ptr cinfo)
|
||||
METHODDEF(boolean) dst_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);
|
||||
// sounds reasonable, but makes 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;
|
||||
@ -273,14 +272,14 @@ METHODDEF(boolean) empty_output_buffer(j_compress_ptr cinfo)
|
||||
* for error exit.
|
||||
*/
|
||||
|
||||
METHODDEF(void) term_destination(j_compress_ptr cinfo)
|
||||
METHODDEF(void) dst_term(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);
|
||||
// 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.
|
||||
}
|
||||
@ -292,7 +291,7 @@ METHODDEF(void) term_destination(j_compress_ptr cinfo)
|
||||
* for closing it after finishing compression.
|
||||
*/
|
||||
|
||||
GLOBAL(void) jpeg_vfs_dst(j_compress_ptr cinfo, Handle hf)
|
||||
GLOBAL(void) dst_prepare(j_compress_ptr cinfo)
|
||||
{
|
||||
/* The destination object is made permanent so that multiple JPEG images
|
||||
* can be written to the same file without re-executing jpeg_stdio_dest.
|
||||
@ -302,14 +301,13 @@ GLOBAL(void) jpeg_vfs_dst(j_compress_ptr cinfo, Handle hf)
|
||||
*/
|
||||
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));
|
||||
((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(DstMgr));
|
||||
}
|
||||
|
||||
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;
|
||||
dst->pub.init_destination = dst_init;
|
||||
dst->pub.empty_output_buffer = dst_empty_output_buffer;
|
||||
dst->pub.term_destination = dst_term;
|
||||
}
|
||||
|
||||
|
||||
@ -329,22 +327,27 @@ GLOBAL(void) jpeg_vfs_dst(j_compress_ptr cinfo, Handle hf)
|
||||
// setjmp return point. it needs access to the jmp_buf,
|
||||
// so we store it in a "subclass" of jpeg_error_mgr.
|
||||
|
||||
struct JpgErrMgr
|
||||
struct JpgErrorMgr
|
||||
{
|
||||
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.
|
||||
// jump here (back to JPEG lib caller) on error
|
||||
jmp_buf call_site;
|
||||
|
||||
// description of first error encountered; must store in JPEG context
|
||||
// for thread safety. initialized in setup_err_mgr.
|
||||
char msg[JMSG_LENGTH_MAX];
|
||||
|
||||
JpgErrorMgr(j_common_ptr cinfo);
|
||||
};
|
||||
|
||||
METHODDEF(void) jpg_error_exit(j_common_ptr cinfo)
|
||||
|
||||
METHODDEF(void) err_error_exit(j_common_ptr cinfo)
|
||||
{
|
||||
// get subclass
|
||||
JpgErrMgr* err_mgr = (JpgErrMgr*)cinfo->err;
|
||||
JpgErrorMgr* err_mgr = (JpgErrorMgr*)cinfo->err;
|
||||
|
||||
// "output" error message (i.e. store in JpgErrMgr;
|
||||
// "output" error message (i.e. store in JpgErrorMgr;
|
||||
// call_site is responsible for displaying it via debug_printf)
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
|
||||
@ -353,17 +356,17 @@ METHODDEF(void) jpg_error_exit(j_common_ptr cinfo)
|
||||
}
|
||||
|
||||
|
||||
// stores message in JpgErrMgr for later output by jpg_(de|en)code.
|
||||
// stores message in JpgErrorMgr 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)
|
||||
METHODDEF(void) err_output_message(j_common_ptr cinfo)
|
||||
{
|
||||
// get subclass
|
||||
JpgErrMgr* err_mgr = (JpgErrMgr*)cinfo->err;
|
||||
JpgErrorMgr* err_mgr = (JpgErrorMgr*)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.
|
||||
// note: was set to '\0' by ctor.
|
||||
if(err_mgr->msg[0] != '\0')
|
||||
return;
|
||||
|
||||
@ -371,6 +374,24 @@ METHODDEF(void) jpg_output_message(j_common_ptr cinfo)
|
||||
(*cinfo->err->format_message)(cinfo, err_mgr->msg);
|
||||
}
|
||||
|
||||
|
||||
JpgErrorMgr::JpgErrorMgr(j_common_ptr cinfo)
|
||||
{
|
||||
// fill in pub fields
|
||||
jpeg_std_error(&pub);
|
||||
// .. and override some methods:
|
||||
pub.error_exit = err_error_exit;
|
||||
pub.output_message = err_output_message;
|
||||
|
||||
// required for "already have message" check in err_output_message
|
||||
msg[0] = '\0';
|
||||
|
||||
// hack: register this error manager with cinfo.
|
||||
// must be done before jpeg_create_* in case that fails
|
||||
// (unlikely, but possible if out of memory).
|
||||
cinfo->err = &pub;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@ -381,6 +402,148 @@ static int jpg_transform(Tex* t, int new_flags)
|
||||
}
|
||||
|
||||
|
||||
// note: jpg_encode and jpg_decode cannot be combined due to
|
||||
// libjpg interface differences.
|
||||
// we do split them up into interface and impl to simplify
|
||||
// resource cleanup and avoid "dtor / setjmp interaction" warnings.
|
||||
//
|
||||
// rationale for row array: jpeg won't output more than a few
|
||||
// scanlines at a time, so we need an output loop anyway. however,
|
||||
// passing at least 2..4 rows is more efficient in low-quality modes
|
||||
// due to less copying.
|
||||
|
||||
|
||||
static int jpg_decode_impl(Tex* t, u8* file, size_t file_size,
|
||||
jpeg_decompress_struct* cinfo,
|
||||
Handle& img_hm, RowArray& rows, const char** perr_msg)
|
||||
{
|
||||
src_prepare(cinfo, file, file_size);
|
||||
|
||||
// ignore return value since:
|
||||
// - suspension is not possible with the mem data source
|
||||
// - we passed TRUE to raise an error if table-only JPEG file
|
||||
(void)jpeg_read_header(cinfo, TRUE);
|
||||
|
||||
// set libjpg output format. we cannot go with the default because
|
||||
// Photoshop writes non-standard CMYK files that must be converted to RGB.
|
||||
int flags = 0;
|
||||
cinfo->out_color_space = JCS_RGB;
|
||||
if(cinfo->num_components == 1)
|
||||
{
|
||||
flags |= TEX_GREY;
|
||||
cinfo->out_color_space = JCS_GRAYSCALE;
|
||||
}
|
||||
|
||||
// lower quality, but faster
|
||||
cinfo->dct_method = JDCT_IFAST;
|
||||
cinfo->do_fancy_upsampling = FALSE;
|
||||
|
||||
// ignore return value since suspension is not possible with the
|
||||
// mem data source.
|
||||
// note: since we've set out_color_space, JPEG will always
|
||||
// return an acceptable image format; no need to check.
|
||||
(void)jpeg_start_decompress(cinfo);
|
||||
|
||||
// scaled output image dimensions and final bpp are now available.
|
||||
int w = cinfo->output_width;
|
||||
int h = cinfo->output_height;
|
||||
int bpp = cinfo->output_components * 8;
|
||||
|
||||
// alloc destination buffer
|
||||
const size_t pitch = w * bpp / 8;
|
||||
const size_t img_size = pitch * h; // for allow_rows
|
||||
u8* img = (u8*)mem_alloc(img_size, 64*KiB, 0, &img_hm);
|
||||
if(!img)
|
||||
return ERR_NO_MEM;
|
||||
|
||||
// read rows
|
||||
RETURN_ERR(tex_codec_alloc_rows(img, h, pitch, TEX_TOP_DOWN, rows));
|
||||
// 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
|
||||
}
|
||||
|
||||
// ignore return value since suspension is not possible with the
|
||||
// mem data source.
|
||||
(void)jpeg_finish_decompress(cinfo);
|
||||
|
||||
if(cinfo->err->num_warnings != 0)
|
||||
*perr_msg = "warning: corrupt image data";
|
||||
|
||||
// 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); // must come after jpeg_finish_decompress
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int jpg_encode_impl(Tex* t,
|
||||
jpeg_compress_struct* cinfo,
|
||||
RowArray& rows, const char** perr_msg)
|
||||
{
|
||||
dst_prepare(cinfo);
|
||||
|
||||
// describe image format
|
||||
// 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);
|
||||
// (add optional settings, e.g. quality, here)
|
||||
|
||||
// TRUE ensures that we will write a complete interchange-JPEG file.
|
||||
// don't change unless you are very sure of what you're doing.
|
||||
jpeg_start_compress(cinfo, TRUE);
|
||||
|
||||
// if BGR, convert to 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);
|
||||
RETURN_ERR(tex_codec_alloc_rows(data, t->h, pitch, TEX_TOP_DOWN, rows));
|
||||
|
||||
|
||||
// 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(cinfo->err->num_warnings != 0)
|
||||
*perr_msg = "warning: corrupt image data";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int jpg_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
{
|
||||
// JFIF requires SOI marker at start of stream.
|
||||
@ -388,193 +551,55 @@ static int jpg_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
if(file[0] != 0xff || file[1] == 0xd8)
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
int err = -1;
|
||||
|
||||
// freed when ret is reached:
|
||||
// .. contains the JPEG decompression parameters and pointers to
|
||||
// working space (allocated as needed by the JPEG library).
|
||||
struct jpeg_decompress_struct cinfo;
|
||||
// contains the JPEG decompression parameters and pointers to
|
||||
// working space (allocated as needed by the JPEG library).
|
||||
// .. array of pointers to scanlines (see rationale above)
|
||||
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
|
||||
JpgErrorMgr jerr((j_common_ptr)&cinfo);
|
||||
if(setjmp(jerr.call_site))
|
||||
{
|
||||
fail:
|
||||
// either JPEG has raised an error, or code below failed.
|
||||
// libjpg longjmp-ed here after an error, or code below failed.
|
||||
mem_free_h(img_hm);
|
||||
goto ret;
|
||||
}
|
||||
|
||||
// goto scoping
|
||||
{
|
||||
jpeg_create_decompress(&cinfo);
|
||||
jpeg_create_decompress(&cinfo);
|
||||
|
||||
jpeg_mem_src(&cinfo, file, file_size);
|
||||
int err = jpg_decode_impl(t, file, file_size, &cinfo, img_hm, rows, perr_msg);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
|
||||
//
|
||||
// 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
|
||||
|
||||
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:
|
||||
// .. contains the JPEG compression parameters and pointers to
|
||||
// working space (allocated as needed by the JPEG library).
|
||||
struct jpeg_compress_struct cinfo;
|
||||
// contains the JPEG compression parameters and pointers to
|
||||
// working space (allocated as needed by the JPEG library).
|
||||
// .. array of pointers to scanlines (see rationale above)
|
||||
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;
|
||||
// freed when fail is reached:
|
||||
// (mem buffer)
|
||||
|
||||
// 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
|
||||
JpgErrorMgr jerr((j_common_ptr)&cinfo);
|
||||
if(setjmp(jerr.call_site))
|
||||
{
|
||||
fail:
|
||||
@ -582,85 +607,16 @@ fail:
|
||||
goto ret;
|
||||
}
|
||||
|
||||
// goto scoping
|
||||
{
|
||||
jpeg_create_compress(&cinfo);
|
||||
|
||||
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);
|
||||
*/
|
||||
int err = jpg_encode_impl(t, &cinfo, rows, perr_msg);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
//
|
||||
// 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
|
||||
|
||||
jpeg_destroy_compress(&cinfo); // releases a "good deal" of memory
|
||||
free(rows);
|
||||
vfs_close(hf);
|
||||
|
||||
// (free mem buffer)
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -39,24 +39,16 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
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)
|
||||
static void io_read(png_struct* png_ptr, u8* data, png_size_t length)
|
||||
{
|
||||
PngMemFile* f = (PngMemFile*)png_ptr->io_ptr;
|
||||
MemSource* ms = (MemSource*)png_ptr->io_ptr;
|
||||
|
||||
void* src = (u8*)(f->p + f->pos);
|
||||
void* src = (u8*)(ms->p + ms->pos);
|
||||
|
||||
// make sure there's enough new data remaining in the buffer
|
||||
f->pos += length;
|
||||
if(f->pos > f->size)
|
||||
ms->pos += length;
|
||||
if(ms->pos > ms->size)
|
||||
png_error(png_ptr, "png_read: not enough data to satisfy request!");
|
||||
|
||||
memcpy(data, src, length);
|
||||
@ -64,7 +56,7 @@ static void png_io_read(png_struct* png_ptr, u8* data, png_size_t length)
|
||||
|
||||
|
||||
// write libpng output to PNG file
|
||||
static void png_io_write(png_struct* png_ptr, u8* data, png_size_t length)
|
||||
static void io_write(png_struct* png_ptr, u8* data, png_size_t length)
|
||||
{
|
||||
void* p = (void*)data;
|
||||
Handle hf = *(Handle*)png_ptr->io_ptr;
|
||||
@ -73,7 +65,7 @@ static void png_io_write(png_struct* png_ptr, u8* data, png_size_t length)
|
||||
}
|
||||
|
||||
|
||||
static void png_io_flush(png_structp)
|
||||
static void io_flush(png_structp)
|
||||
{
|
||||
}
|
||||
|
||||
@ -94,10 +86,10 @@ static int png_transform(Tex* t, int new_flags)
|
||||
// "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)
|
||||
Handle& img_hm, RowArray& rows, const char** perr_msg)
|
||||
{
|
||||
PngMemFile f = { file, file_size };
|
||||
png_set_read_fn(png_ptr, &f, png_io_read);
|
||||
MemSource ms = { file, file_size, 0 };
|
||||
png_set_read_fn(png_ptr, &ms, io_read);
|
||||
|
||||
// read header and determine format
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
@ -126,10 +118,7 @@ static int png_decode_impl(Tex* t, u8* file, size_t file_size,
|
||||
}
|
||||
|
||||
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);
|
||||
u8* img = (u8*)mem_alloc(img_size, 64*KiB, 0, &img_hm);
|
||||
if(!img)
|
||||
return ERR_NO_MEM;
|
||||
|
||||
@ -139,12 +128,12 @@ static int png_decode_impl(Tex* t, u8* file, size_t file_size,
|
||||
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);
|
||||
debug_assert(ms.p == file && ms.size == file_size && ms.pos == ms.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);
|
||||
mem_free_h(t->hm); // must come after png_read_end
|
||||
t->hm = img_hm;
|
||||
t->ofs = 0; // libpng returns decoded image data; no header
|
||||
t->w = w;
|
||||
@ -160,7 +149,7 @@ static int png_decode_impl(Tex* t, u8* file, size_t file_size,
|
||||
// "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)
|
||||
RowArray& rows, const char** perr_msg)
|
||||
{
|
||||
UNUSED2(perr_msg); // we don't produce any error messages ATM.
|
||||
|
||||
@ -189,7 +178,7 @@ static int png_encode_impl(Tex* t,
|
||||
/*
|
||||
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_write_fn(png_ptr, &hf, io_write, 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);
|
||||
@ -213,15 +202,13 @@ static int png_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
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
|
||||
Handle img_hm = 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);
|
||||
@ -234,23 +221,20 @@ static int png_decode(u8* file, size_t file_size, Tex* t, const char** perr_msg)
|
||||
// setup error handling
|
||||
if(setjmp(png_jmpbuf(png_ptr)))
|
||||
{
|
||||
// reached if PNG triggered a longjmp
|
||||
// libpng longjmp-ed here after an error, or code below failed.
|
||||
fail:
|
||||
mem_free(img);
|
||||
mem_free_h(img_hm);
|
||||
goto ret;
|
||||
}
|
||||
|
||||
err = png_decode_impl(t, file, file_size, png_ptr, info_ptr, img, rows, perr_msg);
|
||||
int err = png_decode_impl(t, file, file_size, png_ptr, info_ptr, img_hm, 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);
|
||||
|
||||
free(rows);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -261,13 +245,13 @@ static int png_encode(const char* ext, Tex* t, u8** out, size_t* UNUSED(out_size
|
||||
if(stricmp(ext, "png"))
|
||||
return TEX_CODEC_CANNOT_HANDLE;
|
||||
|
||||
int err = -1;
|
||||
|
||||
// freed when ret is reached.
|
||||
// freed when ret is reached:
|
||||
png_structp png_ptr = 0;
|
||||
png_infop info_ptr = 0;
|
||||
RowArray rows = 0;
|
||||
Handle hf = 0;
|
||||
RowArray rows = 0;
|
||||
|
||||
// free when fail is reached:
|
||||
// (mem buffer)
|
||||
|
||||
// allocate PNG structures; use default stderr and longjmp error handlers
|
||||
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
|
||||
@ -280,13 +264,12 @@ static int png_encode(const char* ext, Tex* t, u8** out, size_t* UNUSED(out_size
|
||||
// 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
|
||||
// libjpg longjmp-ed here after an error, or code below failed.
|
||||
goto ret;
|
||||
}
|
||||
|
||||
err = png_encode_impl(t, png_ptr, info_ptr, rows, hf, perr_msg);
|
||||
int err = png_encode_impl(t, png_ptr, info_ptr, rows, perr_msg);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
@ -295,7 +278,6 @@ ret:
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
|
||||
free(rows);
|
||||
vfs_close(hf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ enum TgaImgDesc
|
||||
{
|
||||
TGA_RIGHT_TO_LEFT = BIT(4),
|
||||
TGA_TOP_DOWN = BIT(5),
|
||||
TGA_BOTTOM_UP = 0 // opposite of TGA_TOP_DOWN
|
||||
};
|
||||
|
||||
typedef struct
|
||||
@ -123,10 +122,10 @@ static int tga_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const
|
||||
{
|
||||
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;
|
||||
u8 img_desc = 0;
|
||||
if(t->flags & TEX_TOP_DOWN)
|
||||
img_desc |= TGA_TOP_DOWN;
|
||||
if(t->bpp == 32)
|
||||
img_desc |= 8; // size of alpha channel
|
||||
TgaImgType img_type = (t->flags & TEX_GREY)? TGA_GREY : TGA_TRUE_COLOUR;
|
||||
@ -135,9 +134,9 @@ static int tga_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const
|
||||
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);
|
||||
tex_transform(t, transforms);
|
||||
|
||||
TgaHeader hdr =
|
||||
const TgaHeader hdr =
|
||||
{
|
||||
0, // no image identifier present
|
||||
0, // no colour map present
|
||||
@ -149,8 +148,7 @@ static int tga_encode(const char* ext, Tex* t, u8** out, size_t* out_size, const
|
||||
t->bpp,
|
||||
img_desc
|
||||
};
|
||||
return write_img(fn, &hdr, sizeof(hdr), img, img_size);
|
||||
*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,27 @@
|
||||
// helpers for built-in self tests
|
||||
//
|
||||
// Copyright (c) 2005 Jan Wassenberg
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// Contact info:
|
||||
// Jan.Wassenberg@stud.uni-karlsruhe.de
|
||||
// http://www.stud.uni-karlsruhe.de/~urkt/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "self_test.h"
|
||||
|
||||
// if true, debug_assert does nothing.
|
||||
// checked by debug_assert_failed; disables asserts if true (see above).
|
||||
// set/cleared by run_self_test.
|
||||
bool self_test_active = false;
|
||||
|
||||
// trampoline that sets self_test_active and returns a dummy value;
|
||||
@ -13,4 +32,4 @@ int run_self_test(void(*test_func)())
|
||||
test_func();
|
||||
self_test_active = false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,111 @@
|
||||
// helpers for built-in self tests
|
||||
//
|
||||
// Copyright (c) 2005 Jan Wassenberg
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// Contact info:
|
||||
// Jan.Wassenberg@stud.uni-karlsruhe.de
|
||||
// http://www.stud.uni-karlsruhe.de/~urkt/
|
||||
|
||||
/*
|
||||
|
||||
[KEEP IN SYNC WITH WIKI!]
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Self-tests as advocated by eXtreme Programming have proven to be useful.
|
||||
By embedding test code into modules, we can be confident that boundary
|
||||
cases are handled correctly and everything still works correctly after edits.
|
||||
We give guidelines for their use and explain several helper mechanisms below.
|
||||
|
||||
|
||||
Guidelines
|
||||
----------
|
||||
|
||||
What makes a good self-test?
|
||||
- They belong in the module being tested to ensure they are kept in
|
||||
sync with it.
|
||||
- It is easiest to attach them to low-level functions, e.g. ilog2, rather
|
||||
than verifying the module's final result (e.g. checking renderer output by
|
||||
comparing pictures).
|
||||
- You should cover all cases: expected failures ("does it fail as expected?"),
|
||||
bad inputs ("does it reject those?"), and successes ("did it have the
|
||||
expected result?").
|
||||
- Tests should be non-intrusive (only bother user if something fails) and
|
||||
very quick. This is because we run them automatically at startup,
|
||||
which solves the common problem of making sure they actually run.
|
||||
|
||||
|
||||
Example Usage
|
||||
-------------
|
||||
|
||||
The following is a working example of a built-in self test using
|
||||
our facilities. Further notes below are referenced with (1) etc.
|
||||
|
||||
>>>
|
||||
|
||||
#if SELF_TEST_ENABLED // (1)
|
||||
namespace test { // (2)
|
||||
|
||||
static void test_log2()
|
||||
{
|
||||
TEST(ilog2(0) == -1);
|
||||
// further test cases..
|
||||
}
|
||||
|
||||
static void self_test()
|
||||
{
|
||||
test_log2();
|
||||
// further test groups..
|
||||
}
|
||||
|
||||
RUN_SELF_TEST; // (3)
|
||||
|
||||
} // namespace test
|
||||
#endif // #if SELF_TEST_ENABLED
|
||||
|
||||
<<<
|
||||
|
||||
(1) when not enabled, self-tests are completely removed so as
|
||||
not to bloat the executable.
|
||||
|
||||
(2) wrapping in a namespace is optional and must be removed for C programs.
|
||||
it avoids possible name collisions with the module being tested.
|
||||
|
||||
(3) automatically calls your self_test function at non-local static object
|
||||
init time (i.e. before main is entered).
|
||||
|
||||
For further details, see below.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef SELF_TEST_H__
|
||||
#define SELF_TEST_H__
|
||||
|
||||
// this is the global default for enabling/disabling all tests.
|
||||
// you can override it in individual files by defining to 0 or 1 before
|
||||
// including this header.
|
||||
#ifndef SELF_TEST_ENABLED
|
||||
#define SELF_TEST_ENABLED 1
|
||||
#endif
|
||||
|
||||
// each test case should use this to verify conditions.
|
||||
// each test case should use this (instead of assert et al.) to verify
|
||||
// conditions.
|
||||
// rationale: some code checks boundary conditions via assert. these are
|
||||
// often triggered deliberately in self-tests to verify error behavior.
|
||||
// we therefore squelch asserts while tests are active (see mechanism below),
|
||||
// and this is the only error reporter guaranteed to work.
|
||||
//
|
||||
// note: could also stringize condition and display that, but it'd require
|
||||
// macro magic (stringize+prepend L) and we already get file+line.
|
||||
#define TEST(condition) STMT(\
|
||||
@ -11,7 +113,27 @@
|
||||
DISPLAY_ERROR(L"Self-test failed");\
|
||||
)
|
||||
|
||||
|
||||
// your source file should contain a void function "self_test" that
|
||||
// performs all tests or calls out to individual test functions.
|
||||
// this macro calls it at static init time and takes care of setting
|
||||
// self_test_active (see above).
|
||||
//
|
||||
// rationale: since compiler optimizations may mess with the dummy variable,
|
||||
// best to put this in a macro so we won't have to change each occurrence.
|
||||
#define RUN_SELF_TEST static int dummy = run_self_test(self_test)
|
||||
|
||||
|
||||
//
|
||||
// internal use only:
|
||||
//
|
||||
|
||||
// trampoline that sets self_test_active and returns a dummy value;
|
||||
// used by RUN_SELF_TEST.
|
||||
extern int run_self_test(void(*test_func)());
|
||||
|
||||
// checked by debug_assert_failed; disables asserts if true (see above).
|
||||
// set/cleared by run_self_test.
|
||||
extern bool self_test_active;
|
||||
|
||||
extern int run_self_test(void(*test_func)());
|
||||
#define RUN_SELF_TEST static int dummy = run_self_test(self_test)
|
||||
#endif // #ifndef SELF_TEST_H__
|
||||
|
Loading…
Reference in New Issue
Block a user