/* 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 #include "quakedef.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_WRITE_STATIC #include "stb_image_write.h" #define LODEPNG_NO_COMPILE_DECODER #define LODEPNG_NO_COMPILE_CPP #define LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS #define LODEPNG_NO_COMPILE_ERROR_TEXT #include "lodepng.h" #include "lodepng.c" static char loadfilename[MAX_OSPATH]; //file scope so that error messages can use it typedef struct stdio_buffer_s { FILE *f; unsigned char buffer[1024]; int size; int pos; } 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); } static inline int Buf_GetC(stdio_buffer_t *buf) { if (buf->pos >= buf->size) { buf->size = fread(buf->buffer, 1, sizeof(buf->buffer), buf->f); buf->pos = 0; if (buf->size == 0) 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 ============ */ byte *Image_LoadImage (const char *name, int *width, int *height) { FILE *f; q_snprintf (loadfilename, sizeof(loadfilename), "%s.tga", name); COM_FOpenFile (loadfilename, &f, NULL); if (f) return Image_LoadTGA (f, width, height); q_snprintf (loadfilename, sizeof(loadfilename), "%s.pcx", name); COM_FOpenFile (loadfilename, &f, NULL); if (f) return Image_LoadPCX (f, width, height); return NULL; } //============================================================================== // // TGA // //============================================================================== typedef struct targaheader_s { unsigned char id_length, colormap_type, image_type; unsigned short colormap_index, colormap_length; unsigned char colormap_size; unsigned short x_origin, y_origin, width, height; unsigned char pixel_size, attributes; } targaheader_t; #define TARGAHEADERSIZE 18 //size on disk targaheader_t targa_header; int fgetLittleShort (FILE *f) { byte b1, b2; b1 = fgetc(f); b2 = fgetc(f); return (short)(b1 + b2*256); } int fgetLittleLong (FILE *f) { byte b1, b2, b3, b4; b1 = fgetc(f); b2 = fgetc(f); b3 = fgetc(f); b4 = fgetc(f); return b1 + (b2<<8) + (b3<<16) + (b4<<24); } /* ============ 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) ============ */ qboolean Image_WriteTGA (const char *name, byte *data, int width, int height, int bpp, qboolean upsidedown) { int 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) return false; Q_memset (header, 0, TARGAHEADERSIZE); header[2] = 2; // uncompressed type header[12] = width&255; header[13] = width>>8; header[14] = height&255; header[15] = height>>8; header[16] = bpp; // pixel size if (upsidedown) header[17] = 0x20; //upside-down attribute // swap red and blue bytes bytes = bpp/8; size = width*height*bytes; for (i=0; i=0; row--) { //johnfitz -- fix for upside-down targas realrow = upside_down ? row : rows - 1 - row; pixbuf = targa_rgba + realrow*columns*4; //johnfitz for(column=0; column=0; row--) { //johnfitz -- fix for upside-down targas realrow = upside_down ? row : rows - 1 - row; pixbuf = targa_rgba + realrow*columns*4; //johnfitz for(column=0; column0) row--; else goto breakOut; //johnfitz -- fix for upside-down targas realrow = upside_down ? row : rows - 1 - row; pixbuf = targa_rgba + realrow*columns*4; //johnfitz } } } else // non run-length packet { for(j=0;j0) row--; else goto breakOut; //johnfitz -- fix for upside-down targas realrow = upside_down ? row : rows - 1 - row; pixbuf = targa_rgba + realrow*columns*4; //johnfitz } } } } breakOut:; } } Buf_Free(buf); fclose(fin); *width = (int)(targa_header.width); *height = (int)(targa_header.height); return targa_rgba; } //============================================================================== // // PCX // //============================================================================== typedef struct { char signature; char version; char encoding; char bits_per_pixel; unsigned short xmin,ymin,xmax,ymax; unsigned short hdpi,vdpi; byte colortable[48]; char reserved; char color_planes; unsigned short bytes_per_line; unsigned short palette_type; char filler[58]; } pcxheader_t; /* ============ Image_LoadPCX ============ */ byte *Image_LoadPCX (FILE *f, int *width, int *height) { pcxheader_t pcx; int x, y, w, h, readbyte, runlength, start; byte *p, *data; byte palette[768]; stdio_buffer_t *buf; 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) fread(&pcx, sizeof(pcx), 1, f); pcx.xmin = (unsigned short)LittleShort (pcx.xmin); pcx.ymin = (unsigned short)LittleShort (pcx.ymin); pcx.xmax = (unsigned short)LittleShort (pcx.xmax); pcx.ymax = (unsigned short)LittleShort (pcx.ymax); pcx.bytes_per_line = (unsigned short)LittleShort (pcx.bytes_per_line); if (pcx.signature != 0x0A) Sys_Error ("'%s' is not a valid PCX file", loadfilename); if (pcx.version != 5) Sys_Error ("'%s' is version %i, should be 5", loadfilename, pcx.version); if (pcx.encoding != 1 || pcx.bits_per_pixel != 8 || pcx.color_planes != 1) Sys_Error ("'%s' has wrong encoding or bit depth", loadfilename); w = pcx.xmax - pcx.xmin + 1; h = pcx.ymax - pcx.ymin + 1; data = (byte *) Hunk_Alloc((w*h+1)*4); //+1 to allow reading padding byte on last line //load palette fseek (f, start + com_filesize - 768, SEEK_SET); fread (palette, 1, 768, f); //back to start of image data fseek (f, start + sizeof(pcx), SEEK_SET); buf = Buf_Alloc(f); for (y=0; y= 0xC0) { runlength = readbyte & 0x3F; readbyte = Buf_GetC(buf); } else runlength = 1; while(runlength--) { p[0] = palette[readbyte*3]; p[1] = palette[readbyte*3+1]; p[2] = palette[readbyte*3+2]; p[3] = 255; p += 4; x++; } } } Buf_Free(buf); fclose(f); *width = w; *height = h; return data; } //============================================================================== // // STB_IMAGE_WRITE // //============================================================================== static byte *CopyFlipped(const byte *data, int width, int height, int bpp) { int y, rowsize; byte *flipped; rowsize = width * (bpp / 8); flipped = (byte *) malloc(height * rowsize); if (!flipped) return NULL; for (y=0; y