md3view/png/png.cpp

847 lines
17 KiB
C++
Executable File

// Generic PNG file loading code
#include "../system.h"
#include "../oddbits.h"
int LongSwap (int l)
{
byte b1,b2,b3,b4;
b1 = l&255;
b2 = (l>>8)&255;
b3 = (l>>16)&255;
b4 = (l>>24)&255;
return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
}
#define BigLong(x) LongSwap(x)
// Generic PNG file loading code
//
//#include "../renderer/tr_local.h"
//
#include "png.h"
#include "../zlib/zlib.h"
// Error returns
#define PNG_ERROR_OK 0
#define PNG_ERROR_DECOMP 1
#define PNG_ERROR_COMP 2
#define PNG_ERROR_MEMORY 3
#define PNG_ERROR_NOSIG 4
#define PNG_ERROR_TOO_SMALL 5
#define PNG_ERROR_WNP2 6
#define PNG_ERROR_HNP2 7
#define PNG_ERROR_NOT_TC 8
#define PNG_ERROR_INV_FIL 9
#define PNG_ERROR_FAILED_CRC 10
#define PNG_ERROR_CREATE_FAIL 11
#define PNG_ERROR_WRITE 12
static int png_error = PNG_ERROR_OK;
static const byte png_signature[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
static const char png_copyright[] = "Copyright\0Raven Software Inc. 2000";
static const char *png_errors[] =
{
"OK.",
"Error decompressing image data.",
"Error compressing image data.",
"Error allocating memory.",
"PNG signature not found.",
"Image is too small to load.",
"Width is not a power of two.",
"Height is not a power of two.",
"Image is not 24 or 32 bit.",
"Invalid filter or compression type.",
"Failed CRC check.",
"Could not create file.",
"Error writing to file.",
};
// Gets the error string for a failed PNG operation
const char *PNG_GetError(void)
{
return(png_errors[png_error]);
}
// Create a header chunk
void PNG_CreateHeader(png_ihdr_t *header, png_image_t *image)
{
header->width = BigLong(image->width);
header->height = BigLong(image->height);
header->bitdepth = 8;
if(image->bytedepth == 3)
{
header->colortype = 2;
}
if(image->bytedepth == 4)
{
header->colortype = 6;
}
header->compression = 0;
header->filter = 0;
header->interlace = 0;
}
// Processes the header chunk and checks to see if all the data is valid
bool PNG_HandleIHDR(const byte *data, png_image_t *image)
{
png_ihdr_t *ihdr = (png_ihdr_t *)data;
image->width = BigLong(ihdr->width);
image->height = BigLong(ihdr->height);
// Make sure image is a reasonable size
if((image->width < 8) || (image->height < 8))
{
png_error = PNG_ERROR_TOO_SMALL;
return(false);
}
// Check for non power of two size
if(image->width & (image->width - 1))
{
png_error = PNG_ERROR_WNP2;
return(false);
}
if(image->height & (image->height - 1))
{
png_error = PNG_ERROR_HNP2;
return(false);
}
// Make sure we have a 24 or 32 bit image
if((ihdr->colortype != 2) && (ihdr->colortype != 6))
{
png_error = PNG_ERROR_NOT_TC;
return(false);
}
// Make sure we aren't using any wacky compression or filter algos
if(ihdr->compression || ihdr->filter)
{
png_error = PNG_ERROR_INV_FIL;
return(false);
}
// Extract the data we need
if(ihdr->colortype == 2)
{
image->bytedepth = 3;
}
if(ihdr->colortype == 6)
{
image->bytedepth = 4;
}
return(true);
}
// Filter a row of data
void PNG_Filter(byte *out, byte filter, const byte *in, const byte *lastline, ulong rowbytes, ulong bpp)
{
ulong i;
switch(filter)
{
case PNG_FILTER_VALUE_NONE:
memcpy(out, in, rowbytes);
break;
case PNG_FILTER_VALUE_SUB:
for(i = 0; i < bpp; i++)
{
*out++ = *in++;
}
for(i = bpp; i < rowbytes; i++)
{
*out++ = *in - *(in - bpp);
in++;
}
break;
case PNG_FILTER_VALUE_UP:
for(i = 0; i < rowbytes; i++)
{
if(lastline)
{
*out++ = *in++ - *lastline++;
}
else
{
*out++ = *in++;
}
}
break;
case PNG_FILTER_VALUE_AVG:
for(i = 0; i < bpp; i++)
{
if(lastline)
{
*out++ = *in++ - (*lastline++ >> 1);
}
else
{
*out++ = *in++;
}
}
for(i = bpp; i < rowbytes; i++)
{
if(lastline)
{
*out++ = *in - ((*lastline++ + *(in - bpp)) >> 1);
}
else
{
*out++ = *in - (*(in - bpp) >> 1);
}
in++;
}
break;
case PNG_FILTER_VALUE_PAETH:
int a, b, c;
int pa, pb, pc, p;
for(i = 0; i < bpp; i++)
{
if(lastline)
{
*out++ = *in++ - *lastline++;
}
else
{
*out++ = *in++;
}
}
for(i = bpp; i < rowbytes; i++)
{
a = *(in - bpp);
c = 0;
b = 0;
if(lastline)
{
c = *(lastline - bpp);
b = *lastline++;
}
p = b - c;
pc = a - c;
pa = p < 0 ? -p : p;
pb = pc < 0 ? -pc : pc;
pc = (p + pc) < 0 ? -(p + pc) : p + pc;
p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;
*out++ = *in++ - p;
}
break;
}
}
// Unfilters a row of data
void PNG_Unfilter(byte *out, byte filter, const byte *lastline, ulong rowbytes, ulong bpp)
{
ulong i;
switch(filter)
{
case PNG_FILTER_VALUE_NONE:
break;
case PNG_FILTER_VALUE_SUB:
out += bpp;
for(i = bpp; i < rowbytes; i++)
{
*out += *(out - bpp);
out++;
}
break;
case PNG_FILTER_VALUE_UP:
for(i = 0; i < rowbytes; i++)
{
if(lastline)
{
*out += *lastline++;
}
out++;
}
break;
case PNG_FILTER_VALUE_AVG:
for(i = 0; i < bpp; i++)
{
if(lastline)
{
*out += *lastline++ >> 1;
}
out++;
}
for(i = bpp; i < rowbytes; i++)
{
if(lastline)
{
*out += (*lastline++ + *(out - bpp)) >> 1;
}
else
{
*out += *(out - bpp) >> 1;
}
out++;
}
break;
case PNG_FILTER_VALUE_PAETH:
int a, b, c;
int pa, pb, pc, p;
for(i = 0; i < bpp; i++)
{
if(lastline)
{
*out += *lastline++;
}
out++;
}
for(i = bpp; i < rowbytes; i++)
{
a = *(out - bpp);
c = 0;
b = 0;
if(lastline)
{
c = *(lastline - bpp);
b = *lastline++;
}
p = b - c;
pc = a - c;
pa = p < 0 ? -p : p;
pb = pc < 0 ? -pc : pc;
pc = (p + pc) < 0 ? -(p + pc) : p + pc;
p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;
*out++ += p;
}
break;
default:
break;
}
}
// Pack up the image data line by line
bool PNG_Pack(byte *out, ulong *size, ulong maxsize, png_image_t *image)
{
z_stream zdata;
z_stream zfilter;
ulong min_out, min_index, rowbytes;
ulong y, i;
const byte *lastline, *source;
// Number of bytes per row
rowbytes = image->width * image->bytedepth;
memset(&zdata, 0, sizeof(z_stream));
if(deflateInit(&zdata, Z_DEFAULT_COMPRESSION) != Z_OK)
{
png_error = PNG_ERROR_COMP;
return(false);
}
zdata.next_out = out;
zdata.avail_out = maxsize;
// Storage for temp zipped data
byte *templine = new byte [(image->width * image->bytedepth) + 128];
// Storage for filter type and filtered row
byte *workline = new byte [(image->width * image->bytedepth) + 1];
lastline = NULL;
source = image->data;
for(y = 0; y < image->height; y++)
{
// Zip total_out field is cumulative
min_out = zdata.total_out + rowbytes + 128;
min_index = 0;
for(i = 0; i < PNG_FILTER_NUM; i++)
{
if(deflateCopy(&zfilter, &zdata) != Z_OK)
{
deflateEnd(&zdata);
delete [] templine;
delete [] workline;
png_error = PNG_ERROR_COMP;
return(false);
}
zfilter.next_out = templine;
zfilter.avail_out = rowbytes + 128;
// Create filtered row to compress
workline[0] = (byte)i;
PNG_Filter(workline + 1, (byte)i, source, lastline, rowbytes, image->bytedepth);
// Compress it all in one chunk
zfilter.next_in = workline;
zfilter.avail_in = rowbytes + 1;
if(deflate(&zfilter, Z_SYNC_FLUSH) != Z_OK)
{
deflateEnd(&zdata);
delete [] templine;
delete [] workline;
png_error = PNG_ERROR_COMP;
return(false);
}
if(zfilter.total_out < min_out)
{
min_out = zfilter.total_out;
min_index = i;
}
deflateEnd(&zfilter);
}
// Refilter using the most compressable filter algo
workline[0] = (byte)min_index;
PNG_Filter(workline + 1, (byte)min_index, source, lastline, rowbytes, image->bytedepth);
zdata.next_in = workline;
zdata.avail_in = rowbytes + 1;
if(deflate(&zdata, Z_SYNC_FLUSH) != Z_OK)
{
deflateEnd(&zdata);
delete [] templine;
delete [] workline;
png_error = PNG_ERROR_COMP;
return(false);
}
lastline = source;
source += rowbytes;
}
if(deflate(&zdata, Z_FINISH) != Z_STREAM_END)
{
delete [] templine;
delete [] workline;
png_error = PNG_ERROR_COMP;
return(false);
}
*size = zdata.total_out;
deflateEnd(&zdata);
delete [] templine;
delete [] workline;
return(true);
}
// Unpack the image data, line by line
bool PNG_Unpack(const byte *data, const ulong datasize, png_image_t *image)
{
ulong rowbytes, zerror, y;
byte filter;
z_stream zdata;
byte *lastline, *out;
memset(&zdata, 0, sizeof(z_stream));
if(inflateInit(&zdata) != Z_OK)
{
png_error = PNG_ERROR_DECOMP;
return(false);
}
zdata.next_in = (byte *)data;
zdata.avail_in = datasize;
rowbytes = image->width * image->bytedepth;
lastline = NULL;
out = image->data;
for(y = 0; y < image->height; y++)
{
// Inflate a row of data
zdata.next_out = &filter;
zdata.avail_out = 1;
if(inflate(&zdata, Z_SYNC_FLUSH) != Z_OK)
{
inflateEnd(&zdata);
png_error = PNG_ERROR_DECOMP;
return(false);
}
zdata.next_out = out;
zdata.avail_out = rowbytes;
zerror = inflate(&zdata, Z_SYNC_FLUSH);
if((zerror != Z_OK) && (zerror != Z_STREAM_END))
{
inflateEnd(&zdata);
png_error = PNG_ERROR_DECOMP;
return(false);
}
// Unfilter a row of data
PNG_Unfilter(out, filter, lastline, rowbytes, image->bytedepth);
lastline = out;
out += rowbytes;
}
inflateEnd(&zdata);
return(true);
}
// Scan through all chunks and process each one
bool PNG_Load(const byte *data, ulong datasize, png_image_t *image)
{
bool moredata;
const byte *next;
byte *workspace, *work;
ulong length, type, crc, totallength;
png_error = PNG_ERROR_OK;
if(memcmp(data, png_signature, sizeof(png_signature)))
{
png_error = PNG_ERROR_NOSIG;
return(false);
}
data += sizeof(png_signature);
workspace = (byte *)malloc(datasize);
work = workspace;
totallength = 0;
moredata = true;
while(moredata)
{
length = BigLong(*(ulong *)data);
data += sizeof(ulong);
type = BigLong(*(ulong *)data);
const byte *crcbase = data;
data += sizeof(ulong);
// CRC checksum location
next = data + length + sizeof(ulong);
// CRC checksum includes header field
crc = crc32(0, crcbase, length + sizeof(ulong));
if(crc != (unsigned)BigLong(*(ulong *)(next - 4)))
{
if(image->data)
{
free(image->data);
image->data = NULL;
}
free(workspace);
png_error = PNG_ERROR_FAILED_CRC;
return(false);
}
switch(type)
{
case PNG_IHDR:
if(!PNG_HandleIHDR(data, image))
{
free(workspace);
return(false);
}
image->data = (byte *)malloc(image->width * image->height * image->bytedepth);
if(!image->data)
{
free(workspace);
png_error = PNG_ERROR_MEMORY;
return(false);
}
break;
case PNG_IDAT:
// Need to copy all the various IDAT chunks into one big one
// Everything but 3dsmax has one IDAT chunk
memcpy(work, data, length);
work += length;
totallength += length;
break;
case PNG_IEND:
if(!PNG_Unpack(workspace, totallength, image))
{
free(workspace);
free(image->data);
image->data = NULL;
return(false);
}
moredata = false;
break;
default:
break;
}
data = next;
}
free(workspace);
return(true);
}
// Outputs a crc'd chunk of PNG data
bool PNG_OutputChunk(FILE *fp, ulong type, byte *data, ulong size)
{
ulong crc, little, outcount;
// Output a standard PNG chunk - length, type, data, crc
little = BigLong(size);
outcount = fwrite(&little, 1, sizeof(little), fp);
little = BigLong(type);
crc = crc32(0, (byte *)&little, sizeof(little));
outcount += fwrite(&little, 1, sizeof(little), fp);
if(size)
{
crc = crc32(crc, data, size);
outcount += fwrite(data, 1, size, fp);
}
little = BigLong(crc);
outcount += fwrite(&little, 1, sizeof(little), fp);
if(outcount != (size + 12))
{
png_error = PNG_ERROR_WRITE;
return(false);
}
return(true);
}
// Saves a PNG format compressed image
bool PNG_Save(const char *name, png_image_t *image)
{
byte *work;
FILE *fp;
int maxsize;
ulong size, outcount;
png_ihdr_t png_header;
png_error = PNG_ERROR_OK;
// Create the file
fp = fopen(name, "wb");
if(!fp)
{
png_error = PNG_ERROR_CREATE_FAIL;
return(false);
}
// Write out the PNG signature
outcount = fwrite(png_signature, 1, sizeof(png_signature), fp);
if(outcount != sizeof(png_signature))
{
fclose(fp);
png_error = PNG_ERROR_WRITE;
return(false);
}
// Create and output a valid header
PNG_CreateHeader(&png_header, image);
if(!PNG_OutputChunk(fp, PNG_IHDR, (byte *)&png_header, sizeof(png_header)))
{
fclose(fp);
return(false);
}
// Create and output the copyright info
if(!PNG_OutputChunk(fp, PNG_tEXt, (byte *)png_copyright, sizeof(png_copyright)))
{
fclose(fp);
return(false);
}
// Max size of compressed image (source size + 0.1% + 12)
maxsize = (image->width * image->height * image->bytedepth) + 4096;
work = (byte *)malloc(maxsize);
if(!work)
{
fclose(fp);
png_error = PNG_ERROR_MEMORY;
return(false);
}
// Pack up the image data
if(!PNG_Pack(work, &size, maxsize, image))
{
free(work);
fclose(fp);
return(false);
}
// Write out the compressed image data
if(!PNG_OutputChunk(fp, PNG_IDAT, (byte *)work, size))
{
free(work);
fclose(fp);
return(false);
}
free(work);
// Output terminating chunk
if(!PNG_OutputChunk(fp, PNG_IEND, NULL, 0))
{
fclose(fp);
return(false);
}
fclose(fp);
return(true);
}
// Prints out the relevant info regarding a PNG file
bool PNG_Info(const byte *data, ulong datasize, png_image_t *image)
{
bool moredata;
const byte *next;
ulong length, type, crc;
png_error = PNG_ERROR_OK;
printf("Checking signature.....");
if(memcmp(data, png_signature, sizeof(png_signature)))
{
printf("failed\n");
return(false);
}
data += sizeof(png_signature);
printf("OK\n\n");
moredata = true;
while(moredata)
{
printf("Chunk: ");
length = BigLong(*(ulong *)data) + sizeof(ulong);
data += sizeof(ulong);
type = BigLong(*(ulong *)data);
const byte *crcbase = data;
data += sizeof(ulong);
printf("%c%c%c%c ", type >> 24, type >> 16, type >> 8, type);
printf("Length: %8d ", length - sizeof(ulong));
// CRC checksum location
next = data + length;
// CRC checksum includes header field
crc = crc32(0, crcbase, length);
printf("CRC 0x%x ", crc);
if(crc != (unsigned)BigLong(*(ulong *)(next - 4)))
{
printf("failed\n");
png_error = PNG_ERROR_FAILED_CRC;
return(false);
}
printf("passed\n");
switch(type)
{
case PNG_IHDR:
if(!PNG_HandleIHDR(data, image))
{
return(false);
}
break;
case PNG_IDAT:
break;
case PNG_IEND:
moredata = false;
break;
default:
break;
}
data = next;
}
printf("\nDimensions: %d x %d x %d\n", image->width, image->height, image->bytedepth * 8);
printf("\nAll chunks processed OK.\n\n");
return(true);
}
/*
=============
PNG_ConvertTo32
=============
*/
void PNG_ConvertTo32(png_image_t *image)
{
byte *temp;
byte *old, *old2;
ulong i;
temp = (byte *)malloc(image->width * image->height * 4);
old = image->data;
old2 = old;
image->data = temp;
image->bytedepth = 4;
for(i = 0; i < image->width * image->height; i++)
{
*temp++ = *old++;
*temp++ = *old++;
*temp++ = *old++;
*temp++ = 0xff;
}
free(old2);
}
/*
=============
LoadPNG32
=============
*/
bool LoadPNG32 (const char *name, byte **pixels, int *width, int *height, int *bytedepth)
{
byte *buffer;
byte **bufferptr = &buffer;
int nLen;
png_image_t png_image;
if(!pixels)
{
bufferptr = NULL;
}
//nLen = ri.FS_ReadFile ( ( char * ) name, (void **)bufferptr);
nLen = LoadFile (name, (void **)bufferptr);
if (nLen == -1)
{
ErrorBox(va("Couldn't read %s\n", name));
if(pixels)
{
*pixels = NULL;
}
return(false);
}
if(!pixels)
{
return(true);
}
*pixels = NULL;
if(!PNG_Load(buffer, nLen, &png_image))
{
ErrorBox(va("Error parsing %s: %s\n", name, PNG_GetError()));
return(false);
}
if(png_image.bytedepth != 4)
{
PNG_ConvertTo32(&png_image);
}
*pixels = png_image.data;
if(width)
{
*width = png_image.width;
}
if(height)
{
*height = png_image.height;
}
if(bytedepth)
{
*bytedepth = png_image.bytedepth;
}
//ri.FS_FreeFile(buffer);
free(buffer);
return(true);
}
// end