1
0
forked from 0ad/0ad
0ad/source/lib/res/graphics/tex_png.cpp
janwas 71f7fccd12 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.
2005-09-02 02:56:54 +00:00

304 lines
7.6 KiB
C++

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