spingle/source/image.c

594 lines
15 KiB
C
Raw Normal View History

2019-11-24 20:45:15 -08:00
/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2010-2014 QuakeSpasm developers
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
//image.c -- image loading
2019-12-02 07:07:37 -08:00
#include "q_defs.h"
2019-11-24 20:45:15 -08:00
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
2019-12-07 09:10:06 -08:00
#include "lib/stb_image_write.h"
2019-11-24 20:45:15 -08:00
#define LODEPNG_NO_COMPILE_DECODER
#define LODEPNG_NO_COMPILE_CPP
#define LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS
#define LODEPNG_NO_COMPILE_ERROR_TEXT
2019-12-07 09:10:06 -08:00
#include "lib/lodepng.h"
#include "lib/lodepng.c"
2019-11-24 20:45:15 -08:00
static char loadfilename[MAX_OSPATH]; //file scope so that error messages can use it
2019-11-25 17:40:18 -08:00
typedef struct stdio_buffer_s
{
2019-11-24 20:45:15 -08:00
FILE *f;
uint8_t buffer[1024];
2019-11-25 16:49:58 -08:00
int32_t size;
int32_t pos;
2019-11-24 20:45:15 -08:00
} stdio_buffer_t;
static stdio_buffer_t *Buf_Alloc(FILE *f)
{
stdio_buffer_t *buf = (stdio_buffer_t *) calloc(1, sizeof(stdio_buffer_t));
buf->f = f;
return buf;
}
static void Buf_Free(stdio_buffer_t *buf)
{
free(buf);
}
2019-11-25 16:49:58 -08:00
static inline int32_t Buf_GetC(stdio_buffer_t *buf)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
if(buf->pos >= buf->size)
2019-11-24 20:45:15 -08:00
{
buf->size = fread(buf->buffer, 1, sizeof(buf->buffer), buf->f);
buf->pos = 0;
2019-11-25 17:40:18 -08:00
if(buf->size == 0)
2019-11-24 20:45:15 -08:00
return EOF;
}
return buf->buffer[buf->pos++];
}
/*
============
Image_LoadImage
returns a pointer to hunk allocated RGBA data
TODO: search order: tga png jpg pcx lmp
============
*/
2019-11-25 17:40:18 -08:00
byte *Image_LoadImage(const char *name, int32_t *width, int32_t *height)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
FILE *f;
2019-11-24 20:45:15 -08:00
2019-11-25 17:40:18 -08:00
q_snprintf(loadfilename, sizeof(loadfilename), "%s.tga", name);
COM_FOpenFile(loadfilename, &f, NULL);
if(f)
return Image_LoadTGA(f, width, height);
2019-11-24 20:45:15 -08:00
2019-11-25 17:40:18 -08:00
q_snprintf(loadfilename, sizeof(loadfilename), "%s.pcx", name);
COM_FOpenFile(loadfilename, &f, NULL);
if(f)
return Image_LoadPCX(f, width, height);
2019-11-24 20:45:15 -08:00
return NULL;
}
//==============================================================================
//
// TGA
//
//==============================================================================
2019-11-25 17:40:18 -08:00
typedef struct targaheader_s
{
uint8_t id_length, colormap_type, image_type;
uint16_t colormap_index, colormap_length;
uint8_t colormap_size;
uint16_t x_origin, y_origin, width, height;
uint8_t pixel_size, attributes;
2019-11-24 20:45:15 -08:00
} targaheader_t;
#define TARGAHEADERSIZE 18 //size on disk
2019-12-03 11:44:52 -08:00
static targaheader_t targa_header;
2019-11-24 20:45:15 -08:00
2019-12-03 11:44:52 -08:00
static int32_t fgetLittleShort(FILE *f)
2019-11-24 20:45:15 -08:00
{
2019-12-03 11:44:52 -08:00
size_t i;
byte b[2];
2019-11-24 20:45:15 -08:00
2019-12-03 11:44:52 -08:00
for(i = 0; i < sizeof(b); i++)
b[i] = fgetc(f);
2019-11-24 20:45:15 -08:00
2019-12-03 11:44:52 -08:00
return LittleShort(BytesShort(b));
2019-11-24 20:45:15 -08:00
}
/*
============
Image_WriteTGA -- writes RGB or RGBA data to a TGA file
returns true if successful
TODO: support BGRA and BGR formats (since opengl can return them, and we don't have to swap)
============
*/
2019-11-25 17:40:18 -08:00
bool Image_WriteTGA(const char *name, byte *data, int32_t width, int32_t height, int32_t bpp, bool upsidedown)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
int32_t handle, i, size, temp, bytes;
char pathname[MAX_OSPATH];
byte header[TARGAHEADERSIZE];
Sys_mkdir(com_gamedir); //if we've switched to a nonexistant gamedir, create it now so we don't crash
q_snprintf(pathname, sizeof(pathname), "%s/%s", com_gamedir, name);
handle = Sys_FileOpenWrite(pathname);
if(handle == -1)
2019-11-24 20:45:15 -08:00
return false;
2019-12-07 09:27:26 -08:00
memset(header, 0, TARGAHEADERSIZE);
2019-11-24 20:45:15 -08:00
header[2] = 2; // uncompressed type
2019-11-25 17:40:18 -08:00
header[12] = width & 255;
header[13] = width >> 8;
header[14] = height & 255;
header[15] = height >> 8;
2019-11-24 20:45:15 -08:00
header[16] = bpp; // pixel size
2019-11-25 17:40:18 -08:00
if(upsidedown)
2019-11-24 20:45:15 -08:00
header[17] = 0x20; //upside-down attribute
// swap red and blue bytes
2019-11-25 17:40:18 -08:00
bytes = bpp / 8;
size = width * height * bytes;
for(i = 0; i < size; i += bytes)
2019-11-24 20:45:15 -08:00
{
temp = data[i];
2019-11-25 17:40:18 -08:00
data[i] = data[i + 2];
data[i + 2] = temp;
2019-11-24 20:45:15 -08:00
}
2019-11-25 17:40:18 -08:00
Sys_FileWrite(handle, header, TARGAHEADERSIZE);
Sys_FileWrite(handle, data, size);
Sys_FileClose(handle);
2019-11-24 20:45:15 -08:00
return true;
}
/*
=============
Image_LoadTGA
=============
*/
2019-11-25 17:40:18 -08:00
byte *Image_LoadTGA(FILE *fin, int32_t *width, int32_t *height)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
int32_t columns, rows, numPixels;
byte *pixbuf;
int32_t row, column;
byte *targa_rgba;
int32_t realrow; //johnfitz -- fix for upside-down targas
bool upside_down; //johnfitz -- fix for upside-down targas
stdio_buffer_t *buf;
2019-11-24 20:45:15 -08:00
targa_header.id_length = fgetc(fin);
targa_header.colormap_type = fgetc(fin);
targa_header.image_type = fgetc(fin);
targa_header.colormap_index = fgetLittleShort(fin);
targa_header.colormap_length = fgetLittleShort(fin);
targa_header.colormap_size = fgetc(fin);
targa_header.x_origin = fgetLittleShort(fin);
targa_header.y_origin = fgetLittleShort(fin);
targa_header.width = fgetLittleShort(fin);
targa_header.height = fgetLittleShort(fin);
targa_header.pixel_size = fgetc(fin);
targa_header.attributes = fgetc(fin);
2019-11-25 17:40:18 -08:00
if(targa_header.image_type != 2 && targa_header.image_type != 10)
Sys_Error("Image_LoadTGA: %s is not a type 2 or type 10 targa\n", loadfilename);
2019-11-24 20:45:15 -08:00
2019-11-25 17:40:18 -08:00
if(targa_header.colormap_type != 0 || (targa_header.pixel_size != 32 && targa_header.pixel_size != 24))
Sys_Error("Image_LoadTGA: %s is not a 24bit or 32bit targa\n", loadfilename);
2019-11-24 20:45:15 -08:00
columns = targa_header.width;
rows = targa_header.height;
numPixels = columns * rows;
upside_down = !(targa_header.attributes & 0x20); //johnfitz -- fix for upside-down targas
targa_rgba = Hunk_AllocName(numPixels * 4, __func__);
2019-11-24 20:45:15 -08:00
2019-11-25 17:40:18 -08:00
if(targa_header.id_length != 0)
2019-11-24 20:45:15 -08:00
fseek(fin, targa_header.id_length, SEEK_CUR); // skip TARGA image comment
buf = Buf_Alloc(fin);
2019-11-25 17:40:18 -08:00
if(targa_header.image_type == 2) // Uncompressed, RGB images
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
for(row = rows - 1; row >= 0; row--)
2019-11-24 20:45:15 -08:00
{
//johnfitz -- fix for upside-down targas
realrow = upside_down ? row : rows - 1 - row;
2019-11-25 17:40:18 -08:00
pixbuf = targa_rgba + realrow * columns * 4;
2019-11-24 20:45:15 -08:00
//johnfitz
2019-11-25 17:40:18 -08:00
for(column = 0; column < columns; column++)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
uint8_t red, green, blue, alphabyte;
switch(targa_header.pixel_size)
2019-11-24 20:45:15 -08:00
{
case 24:
blue = Buf_GetC(buf);
green = Buf_GetC(buf);
red = Buf_GetC(buf);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 255;
break;
case 32:
blue = Buf_GetC(buf);
green = Buf_GetC(buf);
red = Buf_GetC(buf);
alphabyte = Buf_GetC(buf);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
break;
}
}
}
}
2019-11-25 17:40:18 -08:00
else if(targa_header.image_type == 10) // Runlength encoded RGB images
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
uint8_t red, green, blue, alphabyte, packetHeader, packetSize, j;
for(row = rows - 1; row >= 0; row--)
2019-11-24 20:45:15 -08:00
{
//johnfitz -- fix for upside-down targas
realrow = upside_down ? row : rows - 1 - row;
2019-11-25 17:40:18 -08:00
pixbuf = targa_rgba + realrow * columns * 4;
2019-11-24 20:45:15 -08:00
//johnfitz
2019-11-25 17:40:18 -08:00
for(column = 0; column < columns;)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
packetHeader = Buf_GetC(buf);
2019-11-24 20:45:15 -08:00
packetSize = 1 + (packetHeader & 0x7f);
2019-11-25 17:40:18 -08:00
if(packetHeader & 0x80) // run-length packet
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
switch(targa_header.pixel_size)
2019-11-24 20:45:15 -08:00
{
case 24:
blue = Buf_GetC(buf);
green = Buf_GetC(buf);
red = Buf_GetC(buf);
alphabyte = 255;
break;
case 32:
blue = Buf_GetC(buf);
green = Buf_GetC(buf);
red = Buf_GetC(buf);
alphabyte = Buf_GetC(buf);
break;
default: /* avoid compiler warnings */
blue = red = green = alphabyte = 0;
}
2019-11-25 17:40:18 -08:00
for(j = 0; j < packetSize; j++)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
2019-11-24 20:45:15 -08:00
column++;
2019-11-25 17:40:18 -08:00
if(column == columns) // run spans across rows
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
column = 0;
if(row > 0)
2019-11-24 20:45:15 -08:00
row--;
else
2019-12-07 08:26:29 -08:00
goto breakout;
2019-11-24 20:45:15 -08:00
//johnfitz -- fix for upside-down targas
realrow = upside_down ? row : rows - 1 - row;
2019-11-25 17:40:18 -08:00
pixbuf = targa_rgba + realrow * columns * 4;
2019-11-24 20:45:15 -08:00
//johnfitz
}
}
}
else // non run-length packet
{
2019-11-25 17:40:18 -08:00
for(j = 0; j < packetSize; j++)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
switch(targa_header.pixel_size)
2019-11-24 20:45:15 -08:00
{
case 24:
blue = Buf_GetC(buf);
green = Buf_GetC(buf);
red = Buf_GetC(buf);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 255;
break;
case 32:
blue = Buf_GetC(buf);
green = Buf_GetC(buf);
red = Buf_GetC(buf);
alphabyte = Buf_GetC(buf);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
break;
default: /* avoid compiler warnings */
blue = red = green = alphabyte = 0;
}
column++;
2019-11-25 17:40:18 -08:00
if(column == columns) // pixel packet run spans across rows
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
column = 0;
if(row > 0)
2019-11-24 20:45:15 -08:00
row--;
else
2019-12-07 08:26:29 -08:00
goto breakout;
2019-11-24 20:45:15 -08:00
//johnfitz -- fix for upside-down targas
realrow = upside_down ? row : rows - 1 - row;
2019-11-25 17:40:18 -08:00
pixbuf = targa_rgba + realrow * columns * 4;
2019-11-24 20:45:15 -08:00
//johnfitz
}
}
}
}
2019-12-07 08:26:29 -08:00
breakout:
2019-11-25 17:40:18 -08:00
;
2019-11-24 20:45:15 -08:00
}
}
Buf_Free(buf);
fclose(fin);
2019-11-25 16:49:58 -08:00
*width = (int32_t)(targa_header.width);
*height = (int32_t)(targa_header.height);
2019-11-24 20:45:15 -08:00
return targa_rgba;
}
//==============================================================================
//
// PCX
//
//==============================================================================
typedef struct
{
2019-11-25 17:40:18 -08:00
int8_t signature;
int8_t version;
int8_t encoding;
int8_t bits_per_pixel;
uint16_t xmin, ymin, xmax, ymax;
uint16_t hdpi, vdpi;
uint8_t colortable[48];
int8_t reserved;
int8_t color_planes;
uint16_t bytes_per_line;
uint16_t palette_type;
uint8_t filler[58];
2019-11-24 20:45:15 -08:00
} pcxheader_t;
/*
============
Image_LoadPCX
============
*/
2019-11-25 17:40:18 -08:00
byte *Image_LoadPCX(FILE *f, int32_t *width, int32_t *height)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
pcxheader_t pcx;
int32_t x, y, w, h, readbyte, runlength, start;
byte *p, *data;
byte palette[768];
2019-11-24 20:45:15 -08:00
stdio_buffer_t *buf;
2019-11-25 17:40:18 -08:00
start = ftell(f); //save start of file (since we might be inside a pak file, SEEK_SET might not be the start of the pcx)
2019-11-24 20:45:15 -08:00
fread(&pcx, sizeof(pcx), 1, f);
2019-11-25 17:40:18 -08:00
pcx.xmin = (uint16_t)LittleShort(pcx.xmin);
pcx.ymin = (uint16_t)LittleShort(pcx.ymin);
pcx.xmax = (uint16_t)LittleShort(pcx.xmax);
pcx.ymax = (uint16_t)LittleShort(pcx.ymax);
pcx.bytes_per_line = (uint16_t)LittleShort(pcx.bytes_per_line);
2019-11-24 20:45:15 -08:00
2019-11-25 17:40:18 -08:00
if(pcx.signature != 0x0A)
Sys_Error("'%s' is not a valid PCX file", loadfilename);
2019-11-24 20:45:15 -08:00
2019-11-25 17:40:18 -08:00
if(pcx.version != 5)
Sys_Error("'%s' is version %" PRIi32 ", should be 5", loadfilename, pcx.version);
2019-11-24 20:45:15 -08:00
2019-11-25 17:40:18 -08:00
if(pcx.encoding != 1 || pcx.bits_per_pixel != 8 || pcx.color_planes != 1)
Sys_Error("'%s' has wrong encoding or bit depth", loadfilename);
2019-11-24 20:45:15 -08:00
w = pcx.xmax - pcx.xmin + 1;
h = pcx.ymax - pcx.ymin + 1;
data = Hunk_AllocName((w * h + 1) * 4, __func__); //+1 to allow reading padding byte on last line
2019-11-24 20:45:15 -08:00
//load palette
2019-11-25 17:40:18 -08:00
fseek(f, start + com_filesize - 768, SEEK_SET);
fread(palette, 1, 768, f);
2019-11-24 20:45:15 -08:00
//back to start of image data
2019-11-25 17:40:18 -08:00
fseek(f, start + sizeof(pcx), SEEK_SET);
2019-11-24 20:45:15 -08:00
buf = Buf_Alloc(f);
2019-11-25 17:40:18 -08:00
for(y = 0; y < h; y++)
2019-11-24 20:45:15 -08:00
{
p = data + y * w * 4;
2019-11-25 17:40:18 -08:00
for(x = 0; x < (pcx.bytes_per_line);) //read the extra padding byte if necessary
2019-11-24 20:45:15 -08:00
{
readbyte = Buf_GetC(buf);
if(readbyte >= 0xC0)
{
runlength = readbyte & 0x3F;
readbyte = Buf_GetC(buf);
}
else
runlength = 1;
while(runlength--)
{
2019-11-25 17:40:18 -08:00
p[0] = palette[readbyte * 3];
p[1] = palette[readbyte * 3 + 1];
p[2] = palette[readbyte * 3 + 2];
2019-11-24 20:45:15 -08:00
p[3] = 255;
p += 4;
x++;
}
}
}
Buf_Free(buf);
fclose(f);
*width = w;
*height = h;
return data;
}
//==============================================================================
//
// STB_IMAGE_WRITE
//
//==============================================================================
2019-11-25 16:49:58 -08:00
static byte *CopyFlipped(const byte *data, int32_t width, int32_t height, int32_t bpp)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
int32_t y, rowsize;
byte *flipped;
2019-11-24 20:45:15 -08:00
rowsize = width * (bpp / 8);
flipped = (byte *) malloc(height * rowsize);
2019-11-25 17:40:18 -08:00
if(!flipped)
2019-11-24 20:45:15 -08:00
return NULL;
2019-11-25 17:40:18 -08:00
for(y = 0; y < height; y++)
2019-11-24 20:45:15 -08:00
{
memcpy(&flipped[y * rowsize], &data[(height - 1 - y) * rowsize], rowsize);
}
return flipped;
}
/*
============
Image_WriteJPG -- writes using stb_image_write
returns true if successful
============
*/
2019-11-25 17:40:18 -08:00
bool Image_WriteJPG(const char *name, byte *data, int32_t width, int32_t height, int32_t bpp, int32_t quality, bool upsidedown)
2019-11-24 20:45:15 -08:00
{
unsigned error;
2019-11-25 17:40:18 -08:00
char pathname[MAX_OSPATH];
byte *flipped;
int32_t bytes_per_pixel;
2019-11-24 20:45:15 -08:00
2019-11-25 17:40:18 -08:00
if(!(bpp == 32 || bpp == 24))
Sys_Error("bpp not 24 or 32");
2019-11-24 20:45:15 -08:00
bytes_per_pixel = bpp / 8;
2019-11-25 17:40:18 -08:00
Sys_mkdir(com_gamedir); //if we've switched to a nonexistant gamedir, create it now so we don't crash
q_snprintf(pathname, sizeof(pathname), "%s/%s", com_gamedir, name);
2019-11-24 20:45:15 -08:00
2019-11-25 17:40:18 -08:00
if(!upsidedown)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
flipped = CopyFlipped(data, width, height, bpp);
if(!flipped)
2019-11-24 20:45:15 -08:00
return false;
}
else
flipped = data;
2019-11-25 17:40:18 -08:00
error = stbi_write_jpg(pathname, width, height, bytes_per_pixel, flipped, quality);
if(!upsidedown)
free(flipped);
2019-11-24 20:45:15 -08:00
return (error != 0);
}
2019-11-25 17:40:18 -08:00
bool Image_WritePNG(const char *name, byte *data, int32_t width, int32_t height, int32_t bpp, bool upsidedown)
2019-11-24 20:45:15 -08:00
{
unsigned error;
2019-11-25 17:40:18 -08:00
char pathname[MAX_OSPATH];
byte *flipped;
uint8_t *filters;
uint8_t *png;
size_t pngsize;
LodePNGState state;
if(!(bpp == 32 || bpp == 24))
2019-11-24 20:45:15 -08:00
Sys_Error("bpp not 24 or 32");
2019-11-25 17:40:18 -08:00
Sys_mkdir(com_gamedir); //if we've switched to a nonexistant gamedir, create it now so we don't crash
q_snprintf(pathname, sizeof(pathname), "%s/%s", com_gamedir, name);
2019-11-24 20:45:15 -08:00
2019-11-25 17:40:18 -08:00
flipped = (!upsidedown) ? CopyFlipped(data, width, height, bpp) : data;
filters = (uint8_t *) malloc(height);
if(!filters || !flipped)
2019-11-24 20:45:15 -08:00
{
2019-11-25 17:40:18 -08:00
if(!upsidedown)
free(flipped);
free(filters);
2019-11-24 20:45:15 -08:00
return false;
}
// set some options for faster compression
lodepng_state_init(&state);
state.encoder.zlibsettings.use_lz77 = 0;
state.encoder.auto_convert = 0;
state.encoder.filter_strategy = LFS_PREDEFINED;
memset(filters, 1, height); //use filter 1; see https://www.w3.org/TR/PNG-Filters.html
state.encoder.predefined_filters = filters;
2019-11-25 17:40:18 -08:00
if(bpp == 24)
2019-11-24 20:45:15 -08:00
{
state.info_raw.colortype = LCT_RGB;
state.info_png.color.colortype = LCT_RGB;
}
else
{
state.info_raw.colortype = LCT_RGBA;
state.info_png.color.colortype = LCT_RGBA;
}
2019-11-25 17:40:18 -08:00
error = lodepng_encode(&png, &pngsize, flipped, width, height, &state);
if(error == 0) lodepng_save_file(png, pngsize, pathname);
2019-12-02 04:24:20 -08:00
#if defined(LODEPNG_COMPILE_ERROR_TEXT)
2019-11-24 20:45:15 -08:00
else Con_Printf("WritePNG: %s\n", lodepng_error_text());
#endif
2019-11-25 17:40:18 -08:00
lodepng_state_cleanup(&state);
free(png);
free(filters);
if(!upsidedown)
free(flipped);
2019-11-24 20:45:15 -08:00
return (error == 0);
}