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:
janwas 2005-09-02 18:38:25 +00:00
parent 93313f61bf
commit f4c535a326
10 changed files with 472 additions and 388 deletions

View File

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

View File

@ -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()

View File

@ -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++)
{

View File

@ -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;
}

View File

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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

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