/* Copyright (C) 2008-2009 Crow_bar. 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. */ #include extern "C" { #include #include "../../quakedef.h" } #include cvar_t jpeg_compression_level = {"jpeg_compression_level", "75"}; int image_width; int image_height; static int image_palette_type = 0; static byte image_palette[1024]; #define IMAGE_MAX_DIMENSIONS 4096 /* ============= LoadJPG ============= */ byte *LoadJPG (FILE *fin, int matchwidth, int matchheight) { int i, row_stride; byte *data, *scanline, *p; struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error (&jerr); jpeg_create_decompress (&cinfo); jpeg_stdio_src (&cinfo, fin); jpeg_read_header (&cinfo, true); jpeg_start_decompress (&cinfo); if (cinfo.image_width > IMAGE_MAX_DIMENSIONS || cinfo.image_height > IMAGE_MAX_DIMENSIONS) { return NULL; } if ((matchwidth && cinfo.image_width != matchwidth) || (matchheight && cinfo.image_height != matchheight)) { jpeg_finish_decompress (&cinfo); jpeg_destroy_decompress (&cinfo); return NULL; } Con_Printf("JPG: Allocating data\n"); data = static_cast(Hunk_Alloc(cinfo.image_width * cinfo.image_height * 4)); row_stride = cinfo.output_width * cinfo.output_components; scanline = static_cast(Hunk_Alloc(row_stride)); Con_Printf("JPG: done allocating data\n"); p = data; while (cinfo.output_scanline < cinfo.output_height) { jpeg_read_scanlines (&cinfo, &scanline, 1); // convert the image to RGBA switch (cinfo.output_components) { // RGB images case 3: for (i=0 ; imanufacturer != 0x0a || pcx->version != 5 || pcx->encoding != 1 || pcx->bits_per_pixel != 8 || pcx->xmax >= 320 || pcx->ymax >= 256) { Con_Printf("Bad pcx file\n"); return nullptr; } if (matchwidth && (pcx->xmax + 1) != matchwidth) { fclose(f); return nullptr; } if (matchheight && (pcx->ymax + 1) != matchheight) { fclose(f); return nullptr; } fseek(f, -768, SEEK_END); byte palette[768]; fread(palette, 1, 768, f); fseek(f, sizeof(pcxbuf) - 4, SEEK_SET); int count = (pcx->xmax + 1) * (pcx->ymax + 1); byte* image_rgba = static_cast(Q_malloc(4 * count)); byte* pix = image_rgba; for (int y = 0; y <= pcx->ymax; y++) { for (int x = 0; x <= pcx->xmax;) { int dataByte = fgetc(f); int runLength = 1; if ((dataByte & 0xC0) == 0xC0) { runLength = dataByte & 0x3F; dataByte = fgetc(f); } while (runLength-- > 0) { byte* color = palette + dataByte * 3; pix[0] = color[0]; pix[1] = color[1]; pix[2] = color[2]; pix[3] = 0xFF; // set alpha to 255 pix += 4; x++; } } } image_width = pcx->xmax + 1; image_height = pcx->ymax + 1; //memcpy_vfpu(image_palette, palette, sizeof(palette)); //image_palette_type = PAL_RGB; fclose(f); return image_rgba; } /* ================ LoadWal ================ */ typedef struct miptexq2_s { char name[32]; unsigned width, height; unsigned offsets[4]; // four mip maps stored char animname[32]; // next frame in animation chain int flags; int contents; int value; } miptexq2_t; byte *LoadWAL (char *name) { miptexq2_t *mt; int width, height, ofs, size; byte *data; mt = (miptexq2_t*)COM_LoadFile (name, 0); if (!mt) { return NULL; } width = LittleLong (mt->width); height = LittleLong (mt->height); ofs = LittleLong (mt->offsets[0]); size = width * height; data = static_cast(Q_malloc(size)); memcpy_vfpu(data, (byte *)mt + ofs, size); image_palette_type = PAL_Q2; Z_Free(mt); return data; } /* ========================================================= Targa ========================================================= */ #define TGA_MAXCOLORS 16384 /* Definitions for image types. */ #define TGA_Null 0 /* no image data */ #define TGA_Map 1 /* Uncompressed, color-mapped images. */ #define TGA_RGB 2 /* Uncompressed, RGB images. */ #define TGA_Mono 3 /* Uncompressed, black and white images. */ #define TGA_RLEMap 9 /* Runlength encoded color-mapped images. */ #define TGA_RLERGB 10 /* Runlength encoded RGB images. */ #define TGA_RLEMono 11 /* Compressed, black and white images. */ #define TGA_CompMap 32 /* Compressed color-mapped data, using Huffman, Delta, and runlength encoding. */ #define TGA_CompMap4 33 /* Compressed color-mapped data, using Huffman, Delta, and runlength encoding. 4-pass quadtree-type process. */ /* Definitions for interleave flag. */ #define TGA_IL_None 0 /* non-interleaved. */ #define TGA_IL_Two 1 /* two-way (even/odd) interleaving */ #define TGA_IL_Four 2 /* four way interleaving */ #define TGA_IL_Reserved 3 /* reserved */ /* Definitions for origin flag */ #define TGA_O_UPPER 0 /* Origin in lower left-hand corner. */ #define TGA_O_LOWER 1 /* Origin in upper left-hand corner. */ typedef struct _TargaHeader { 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; 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); } /* ============= LoadTGA ============= */ byte *LoadTGA (FILE *fin, int matchwidth, int matchheight) { int w, h, x, y, realrow, truerow, baserow, i, temp1, temp2, pixel_size, map_idx; int RLE_count, RLE_flag, size, interleave, origin; bool mapped, rlencoded; byte *data, *dst, r, g, b, a, j, k, l, *ColorMap; TargaHeader header; header.id_length = fgetc (fin); header.colormap_type = fgetc (fin); header.image_type = fgetc (fin); header.colormap_index = fgetLittleShort (fin); header.colormap_length = fgetLittleShort (fin); header.colormap_size = fgetc (fin); header.x_origin = fgetLittleShort (fin); header.y_origin = fgetLittleShort (fin); header.width = fgetLittleShort (fin); header.height = fgetLittleShort (fin); header.pixel_size = fgetc (fin); header.attributes = fgetc (fin); if (header.width > IMAGE_MAX_DIMENSIONS || header.height > IMAGE_MAX_DIMENSIONS) { Con_DPrintf ("TGA image %s exceeds maximum supported dimensions\n"); fclose (fin); return NULL; } if ((matchwidth && header.width != matchwidth) || (matchheight && header.height != matchheight)) { fclose (fin); return NULL; } if (header.id_length != 0) fseek (fin, header.id_length, SEEK_CUR); /* validate TGA type */ switch (header.image_type) { case TGA_Map: case TGA_RGB: case TGA_Mono: case TGA_RLEMap: case TGA_RLERGB: case TGA_RLEMono: break; default: Con_DPrintf ("Unsupported TGA image %s: Only type 1 (map), 2 (RGB), 3 (mono), 9 (RLEmap), 10 (RLERGB), 11 (RLEmono) TGA images supported\n"); fclose (fin); return NULL; } /* validate color depth */ switch (header.pixel_size) { case 8: case 15: case 16: case 24: case 32: break; default: Con_DPrintf ("Unsupported TGA image %s: Only 8, 15, 16, 24 or 32 bit images (with colormaps) supported\n"); fclose (fin); return NULL; } r = g = b = a = l = 0; /* if required, read the color map information. */ ColorMap = NULL; mapped = (header.image_type == TGA_Map || header.image_type == TGA_RLEMap) && header.colormap_type == 1; if (mapped) { /* validate colormap size */ switch (header.colormap_size) { case 8: case 15: case 16: case 32: case 24: break; default: Con_DPrintf ("Unsupported TGA image %s: Only 8, 15, 16, 24 or 32 bit colormaps supported\n"); fclose (fin); return NULL; } temp1 = header.colormap_index; temp2 = header.colormap_length; if ((temp1 + temp2 + 1) >= TGA_MAXCOLORS) { fclose (fin); return NULL; } ColorMap = static_cast(Q_malloc (TGA_MAXCOLORS * 4)); map_idx = 0; for (i = temp1 ; i < temp1 + temp2 ; ++i, map_idx += 4) { /* read appropriate number of bytes, break into rgb & put in map. */ switch (header.colormap_size) { case 8: /* grey scale, read and triplicate. */ r = g = b = getc (fin); a = 255; break; case 15: /* 5 bits each of red green and blue. */ /* watch byte order. */ j = getc (fin); k = getc (fin); l = ((unsigned int)k << 8) + j; r = (byte)(((k & 0x7C) >> 2) << 3); g = (byte)((((k & 0x03) << 3) + ((j & 0xE0) >> 5)) << 3); b = (byte)((j & 0x1F) << 3); a = 255; break; case 16: /* 5 bits each of red green and blue, 1 alpha bit. */ /* watch byte order. */ j = getc (fin); k = getc (fin); l = ((unsigned int)k << 8) + j; r = (byte)(((k & 0x7C) >> 2) << 3); g = (byte)((((k & 0x03) << 3) + ((j & 0xE0) >> 5)) << 3); b = (byte)((j & 0x1F) << 3); a = (k & 0x80) ? 255 : 0; break; case 24: /* 8 bits each of blue, green and red. */ b = getc (fin); g = getc (fin); r = getc (fin); a = 255; l = 0; break; case 32: /* 8 bits each of blue, green, red and alpha. */ b = getc (fin); g = getc (fin); r = getc (fin); a = getc (fin); l = 0; break; } ColorMap[map_idx+0] = r; ColorMap[map_idx+1] = g; ColorMap[map_idx+2] = b; ColorMap[map_idx+3] = a; } } /* check run-length encoding. */ rlencoded = (header.image_type == TGA_RLEMap || header.image_type == TGA_RLERGB || header.image_type == TGA_RLEMono); RLE_count = RLE_flag = 0; image_width = w = header.width; image_height = h = header.height; size = w * h * 4; data = static_cast(calloc (size, 1)); /* read the Targa file body and convert to portable format. */ pixel_size = header.pixel_size; origin = (header.attributes & 0x20) >> 5; interleave = (header.attributes & 0xC0) >> 6; truerow = baserow = 0; for (y=0 ; y> 2) << 3); g = (byte)((((k & 0x03) << 3) + ((j & 0xE0) >> 5)) << 3); b = (byte)((j & 0x1F) << 3); a = 255; break; case 16: /* 5 bits each of red green and blue, 1 alpha bit. */ /* watch byte order. */ j = getc (fin); k = getc (fin); l = ((unsigned int)k << 8) + j; r = (byte)(((k & 0x7C) >> 2) << 3); g = (byte)((((k & 0x03) << 3) + ((j & 0xE0) >> 5)) << 3); b = (byte)((j & 0x1F) << 3); a = (k & 0x80) ? 255 : 0; break; case 24: /* 8 bits each of blue, green and red. */ b = getc (fin); g = getc (fin); r = getc (fin); a = 255; l = 0; break; case 32: /* 8 bits each of blue, green, red and alpha. */ b = getc (fin); g = getc (fin); r = getc (fin); a = getc (fin); l = 0; break; default: Con_DPrintf ("Malformed TGA image: Illegal pixel_size '%d'\n", pixel_size); fclose (fin); free (data); if (mapped) free (ColorMap); return NULL; } PixEncode: if (mapped) { map_idx = l * 4; *dst++ = ColorMap[map_idx+0]; *dst++ = ColorMap[map_idx+1]; *dst++ = ColorMap[map_idx+2]; *dst++ = ColorMap[map_idx+3]; } else { *dst++ = r; *dst++ = g; *dst++ = b; *dst++ = a; } } if (interleave == TGA_IL_Four) truerow += 4; else if (interleave == TGA_IL_Two) truerow += 2; else truerow++; if (truerow >= h) truerow = ++baserow; } if (mapped) free (ColorMap); fclose (fin); return data; } /* ========================================================= PNG ========================================================= */ static void PNG_IO_user_read_data (png_structp png_ptr, png_bytep data, png_size_t length) { FILE *f = (FILE *)png_get_io_ptr(png_ptr); fread (data, 1, length, f); } /* static void PNG_IO_user_write_data (png_structp png_ptr, png_bytep data, png_size_t length) { FILE *f = (FILE *)png_get_io_ptr(png_ptr); fwrite (data, 1, length, f); } static void PNG_IO_user_flush_data (png_structp png_ptr) { FILE *f = (FILE *)png_get_io_ptr(png_ptr); fflush (f); } */ /* ============= LoadPNG ============= */ void* Hunk_AllocPNG(png_struct* tmp, unsigned int num_bytes) { void* ptr = Hunk_Alloc(num_bytes); return ptr; } // Libpng requires a function for freeing memory, hunk gets free'd later so keep this empty. void Hunk_FreePNG(png_struct* tmp, void* ptr) { return; } byte *LoadPNG (FILE *fin, int matchwidth, int matchheight) { int y, width, height, bitdepth, colortype; int interlace, compression, filter, bytesperpixel; byte header[8], **rowpointers, *data; png_structp png_ptr; png_infop pnginfo; unsigned long rowbytes; fread (header, 1, 8, fin); if (png_sig_cmp(header, 0, 8)) { Con_DPrintf ("Invalid PNG image\n"); fclose (fin); return NULL; } if (!(png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, NULL, Hunk_AllocPNG, Hunk_FreePNG))) { fclose (fin); return NULL; } if (!(pnginfo = png_create_info_struct(png_ptr))) { png_destroy_read_struct (&png_ptr, &pnginfo, NULL); fclose (fin); return NULL; } /* naievil -- invalid use of incomplete type if (setjmp(png_ptr->jmpbuf)) { png_destroy_read_struct (&png_ptr, &pnginfo, NULL); fclose (fin); return NULL; } */ png_set_read_fn (png_ptr, fin, PNG_IO_user_read_data); png_set_sig_bytes (png_ptr, 8); png_read_info (png_ptr, pnginfo); png_get_IHDR (png_ptr, pnginfo, (png_uint_32 *)&width, (png_uint_32 *)&height, &bitdepth, &colortype, &interlace, &compression, &filter); if (width > IMAGE_MAX_DIMENSIONS || height > IMAGE_MAX_DIMENSIONS) { Con_DPrintf ("PNG image exceeds maximum supported dimensions\n"); png_destroy_read_struct (&png_ptr, &pnginfo, NULL); fclose (fin); return NULL; } if ((matchwidth && width != matchwidth) || (matchheight && height != matchheight)) { png_destroy_read_struct (&png_ptr, &pnginfo, NULL); fclose (fin); return NULL; } if (colortype == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb (png_ptr); png_set_filler (png_ptr, 255, PNG_FILLER_AFTER); } if (colortype == PNG_COLOR_TYPE_GRAY && bitdepth < 8) png_set_expand_gray_1_2_4_to_8 (png_ptr); if (png_get_valid(png_ptr, pnginfo, PNG_INFO_tRNS)) png_set_tRNS_to_alpha (png_ptr); if (colortype == PNG_COLOR_TYPE_GRAY || colortype == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb (png_ptr); if (colortype != PNG_COLOR_TYPE_RGBA) png_set_filler (png_ptr, 255, PNG_FILLER_AFTER); if (bitdepth < 8) png_set_expand (png_ptr); else if (bitdepth == 16) png_set_strip_16 (png_ptr); png_read_update_info (png_ptr, pnginfo); rowbytes = png_get_rowbytes (png_ptr, pnginfo); bytesperpixel = png_get_channels (png_ptr, pnginfo); bitdepth = png_get_bit_depth (png_ptr, pnginfo); if (bitdepth != 8 || bytesperpixel != 4) { Con_DPrintf ("Unsupported PNG image: Bad color depth and/or bpp\n"); png_destroy_read_struct (&png_ptr, &pnginfo, NULL); fclose (fin); return NULL; } data = static_cast(Hunk_Alloc(height * rowbytes)); rowpointers = static_cast(Hunk_Alloc(height * sizeof(*rowpointers))); for (y=0 ; y(Q_malloc(numPixels * 4)); for ( row = rows-1; row >= 0; row-- ) { pixbuf = bmpRGBA + row*columns*4; for ( column = 0; column < columns; column++ ) { unsigned char red, green, blue, alpha; int palIndex; unsigned short shortPixel; switch ( bmpHeader.bitsPerPixel ) { case 8: palIndex = getc(fin); *pixbuf++ = bmpHeader.palette[palIndex][2]; *pixbuf++ = bmpHeader.palette[palIndex][1]; *pixbuf++ = bmpHeader.palette[palIndex][0]; *pixbuf++ = 0xff; break; case 16: shortPixel = * ( unsigned short * ) pixbuf; pixbuf += 2; *pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7; *pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2; *pixbuf++ = ( shortPixel & ( 31 ) ) << 3; *pixbuf++ = 0xff; break; case 24: blue = getc (fin); green = getc (fin); red = getc (fin); *pixbuf++ = red; *pixbuf++ = green; *pixbuf++ = blue; *pixbuf++ = 255; break; case 32: blue = getc (fin); green = getc (fin); red = getc (fin); alpha = getc (fin); *pixbuf++ = red; *pixbuf++ = green; *pixbuf++ = blue; *pixbuf++ = alpha; break; default: Sys_Error("LoadBMP: illegal pixel_size '%d' in file\n", bmpHeader.bitsPerPixel); break; } } } fclose(fin); return bmpRGBA; } /* ============= loadimagepixels ============= */ // HACK HACK HACK byte* loadimagepixels (char* filename, qboolean complain, int matchwidth, int matchheight) { FILE *f; char basename[128], name[128]; byte *c; if (complain == qfalse) COM_StripExtension(filename, basename); // strip the extension to allow TGA and PCX else strcpy(basename, filename); c = (byte*)(basename); while (*c) { if (*c == '*') *c = '+'; c++; } com_netpath[0] = 0; /* sprintf (name, "%s.wal", basename); if (data = LoadWAL(name)) return data; */ sprintf (name, "%s.tga", basename); FS_FOpenFile (name, &f); if (f) return LoadTGA (f, matchwidth, matchheight); sprintf (name, "%s.pcx", basename); FS_FOpenFile (name, &f); if (f) return LoadPCX (f, matchwidth, matchheight); sprintf (name, "%s.jpg", basename); FS_FOpenFile (name, &f); if (f) { return LoadJPG (f, matchwidth, matchheight); } sprintf (name, "%s.png", basename); FS_FOpenFile (name, &f); if (f) return LoadPNG (f, matchwidth, matchheight); sprintf (name, "%s.bmp", basename); FS_FOpenFile (name, &f); if (f) return LoadBMP (f, matchwidth, matchheight); //if (complain) // Con_Printf ("Couldn't load %s .tga .jpg .bmp .png \n", filename); return NULL; } /* ============= loadtextureimage ============= */ int loadtextureimage (char* filename, int matchwidth, int matchheight, qboolean complain, int filter) { int texture_index; byte *data; int hunk_start = Hunk_LowMark(); data = loadimagepixels (filename, complain, matchwidth, matchheight); int hunk_stop = Hunk_LowMark(); if(!data) { return 0; } if(image_palette_type) { texture_index = GL_LoadPalTex (filename, image_width, image_height, data, qtrue, filter, 0, (byte *)image_palette, image_palette_type); memset(image_palette, 0, sizeof(image_palette)); // clear temp pal image_palette_type = 0; } else { texture_index = GL_LoadImages (filename, image_width, image_height, data, qtrue, filter, 0, 4); } // Only free the hunk if it was used. if (hunk_start != hunk_stop) Hunk_FreeToLowMark(hunk_start); else free(data); return texture_index; } // Hacky thing to only load a top half of an image for skybox sides, hard to imagine other use for this int loadskyboxsideimage (char* filename, int matchwidth, int matchheight, qboolean complain, int filter) { int texture_index; byte *data; int hunk_start = Hunk_LowMark(); data = loadimagepixels (filename, complain, matchwidth, matchheight); int hunk_stop = Hunk_LowMark(); if(!data) { return 0; } int newheight = image_height * 0.5; texture_index = GL_LoadImages (filename, image_width, newheight, data, qtrue, filter, 0, 4); // Only free the hunk if it was used. if (hunk_start != hunk_stop) Hunk_FreeToLowMark(hunk_start); else free(data); return texture_index; } /* ============= loadrgbafrompal ============= */ int loadrgbafrompal (char* name, int width, int height, byte* data) { int pixels = width * height; byte* rgbadata = (byte*)Q_malloc(pixels * 4); for(int i = 0; i < pixels; i++) { byte palette_index = data[i]; rgbadata[i * 4] = host_basepal[palette_index * 3 + 0]; rgbadata[i * 4 + 1] = host_basepal[palette_index * 3 + 1]; rgbadata[i * 4 + 2] = host_basepal[palette_index * 3 + 2]; rgbadata[i * 4 + 3] = 255; // Set alpha to opaque } int ret = GL_LoadImages(name, width, height, rgbadata, qtrue, GU_LINEAR, 0, 4); free(rgbadata); return ret; }