/* =========================================================================== Copyright (C) 1997-2001 Id Software, Inc. This file is part of Quake 2 source code. Quake 2 source code 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. Quake 2 source code 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 Quake 2 source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // r_misc.c - particle image loading, and screenshots #include "r_local.h" #include "../include/jpeg/jpeglib.h" // Heffo - JPEG Screenshots #ifdef PNG_SUPPORT #if defined (_MSC_VER) && (_MSC_VER <= 1200) // use older version of libpng for MSVC6 #include "../include/zlibpng_vc6/png.h" #else #include "../include/zlibpng/png.h" #endif #endif // PNG_SUPPORT /* ================== R_CreateNullTexture ================== */ #define NULLTEX_SIZE 16 image_t * R_CreateNullTexture (void) { byte nulltex[NULLTEX_SIZE][NULLTEX_SIZE][4]; int x; memset (nulltex, 32, sizeof(nulltex)); for (x=0; xtexnum); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); qglHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST); qglTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); return image; } /* ================== R_CreateCelShadeTexture ================== */ #define CEL_SHADE_SIZE 32 const byte cel_tex_colors[CEL_SHADE_SIZE][2] = { // + 3 = 3 0, 255, 0, 255, 0, 255, // + 5 = 8 0, 170, 0, 170, 0, 170, 0, 170, 0, 170, // + 8 = 16 0, 85, 0, 85, 0, 85, 0, 85, 0, 85, 0, 85, 0, 85, 0, 85, // + 8 = 24 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 8 = 32 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, }; image_t *R_CreateCelShadeTexture (void) { byte cel_tex[CEL_SHADE_SIZE][CEL_SHADE_SIZE][4]; int x, y; image_t *image; for (x=0; xtexnum); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); return image; } /* ================== LoadPartImg ================== */ image_t *LoadPartImg (char *name, imagetype_t type) { image_t *image = R_FindImage(name, type); if (!image) image = glMedia.notexture; return image; } /* ================== R_SetParticlePicture ================== */ void R_SetParticlePicture (int num, char *name) { glMedia.particletextures[num] = LoadPartImg (name, it_part); } /* ================== R_CreateDisplayLists ================== */ void R_CreateDisplayLists (void) { int i; for (i=0; iinteger) { qglReadPixels (grab_x, 0, grab_width, vid.height, GL_RGB, GL_UNSIGNED_BYTE, buffer); return; } g = 1.0f - (vid_gamma->value - 1.0f); qglPushAttrib(GL_PIXEL_MODE_BIT); qglPixelTransferf(GL_RED_SCALE, g); qglPixelTransferf(GL_GREEN_SCALE, g); qglPixelTransferf(GL_BLUE_SCALE, g); qglReadPixels (grab_x, 0, grab_width, vid.height, GL_RGB, GL_UNSIGNED_BYTE, buffer); qglPopAttrib(); } /* ================ R_ResampleShotLerpLine from DarkPlaces ================ */ void R_ResampleShotLerpLine (byte *in, byte *out, int inwidth, int outwidth) { int j, xi, oldx = 0, f, fstep, l1, l2, endx; fstep = (int) (inwidth*65536.0f/outwidth); endx = (inwidth-1); for (j = 0,f = 0; j < outwidth; j++, f += fstep) { xi = (int) f >> 16; if (xi != oldx) { in += (xi - oldx) * 3; oldx = xi; } if (xi < endx) { l2 = f & 0xFFFF; l1 = 0x10000 - l2; *out++ = (byte) ((in[0] * l1 + in[3] * l2) >> 16); *out++ = (byte) ((in[1] * l1 + in[4] * l2) >> 16); *out++ = (byte) ((in[2] * l1 + in[5] * l2) >> 16); } else // last pixel of the line has no pixel to lerp to { *out++ = in[0]; *out++ = in[1]; *out++ = in[2]; } } } /* ================ R_ResampleShot ================ */ void R_ResampleShot (void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight) { int i, j, yi, oldy, f, fstep, l1, l2, endy = (inheight-1); byte *inrow, *out, *row1, *row2; out = outdata; fstep = (int) (inheight*65536.0f/outheight); row1 = malloc(outwidth*3); row2 = malloc(outwidth*3); inrow = indata; oldy = 0; R_ResampleShotLerpLine (inrow, row1, inwidth, outwidth); R_ResampleShotLerpLine (inrow + inwidth*3, row2, inwidth, outwidth); for (i = 0, f = 0; i < outheight; i++,f += fstep) { yi = f >> 16; if (yi != oldy) { inrow = (byte *)indata + inwidth*3*yi; if (yi == oldy+1) memcpy(row1, row2, outwidth*3); else R_ResampleShotLerpLine (inrow, row1, inwidth, outwidth); if (yi < endy) R_ResampleShotLerpLine (inrow + inwidth*3, row2, inwidth, outwidth); else memcpy(row2, row1, outwidth*3); oldy = yi; } if (yi < endy) { l2 = f & 0xFFFF; l1 = 0x10000 - l2; for (j = 0;j < outwidth;j++) { *out++ = (byte) ((*row1++ * l1 + *row2++ * l2) >> 16); *out++ = (byte) ((*row1++ * l1 + *row2++ * l2) >> 16); *out++ = (byte) ((*row1++ * l1 + *row2++ * l2) >> 16); } row1 -= outwidth*3; row2 -= outwidth*3; } else // last line has no pixels to lerp to { for (j = 0;j < outwidth;j++) { *out++ = *row1++; *out++ = *row1++; *out++ = *row1++; } row1 -= outwidth*3; } } free(row1); free(row2); } /* ================== R_ScaledScreenshot by Knightmare ================== */ byte *saveshotdata; void R_ScaledScreenshot (char *name) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; JSAMPROW s[1]; FILE *file; char shotname[MAX_OSPATH]; int saveshotWidth, saveshotHeight, offset, grab_width; byte *jpgdata; if (!saveshotdata) return; // Round down width to nearest multiple of 4 grab_width = vid.width & ~3; // Optional hi-res saveshots saveshotWidth = saveshotHeight = 256; if (r_saveshotsize->integer) { if (grab_width >= 1024) saveshotWidth = 1024; else if (grab_width >= 512) saveshotWidth = 512; if (vid.height >= 1024) saveshotHeight = 1024; else if (vid.height >= 512) saveshotHeight = 512; } /* if (r_saveshotsize->integer && (grab_width >= 1024) && (vid.height >= 1024)) saveshotsize = 1024; else if (r_saveshotsize->integer && (grab_width >= 512) && (vid.height >= 512)) saveshotsize = 512; else saveshotsize = 256;*/ // Allocate room for reduced screenshot jpgdata = malloc(saveshotWidth * saveshotHeight * 3); if (!jpgdata) return; // Resize grabbed screen R_ResampleShot(saveshotdata, grab_width, vid.height, jpgdata, saveshotWidth, saveshotHeight); // Open the file for Binary Output Com_sprintf (shotname, sizeof(shotname), "%s", name); file = fopen(shotname, "wb"); if (!file) { VID_Printf (PRINT_ALL, "Menu_ScreenShot: Couldn't create %s\n", name); return; } // Initialise the JPEG compression object cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, file); // Setup JPEG Parameters cinfo.image_width = saveshotWidth; //256; cinfo.image_height = saveshotHeight; //256; cinfo.in_color_space = JCS_RGB; cinfo.input_components = 3; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, 85, TRUE); // was 100 // Start Compression jpeg_start_compress(&cinfo, true); // Feed Scanline data offset = (cinfo.image_width * cinfo.image_height * 3) - (cinfo.image_width * 3); while (cinfo.next_scanline < cinfo.image_height) { s[0] = &jpgdata[offset - (cinfo.next_scanline * (cinfo.image_width * 3))]; jpeg_write_scanlines(&cinfo, s, 1); } // Finish Compression jpeg_finish_compress(&cinfo); // Destroy JPEG object jpeg_destroy_compress(&cinfo); // Close File fclose(file); // Free Reduced screenshot free(jpgdata); } /* ================== R_GrabScreen by Knightmare ================== */ void R_GrabScreen (void) { int grab_width, grab_x; // Free saveshot buffer first if (saveshotdata) free(saveshotdata); // Round down width to nearest multiple of 4 grab_width = vid.width & ~3; grab_x = (vid.width - grab_width) / 2; // Allocate room for a copy of the framebuffer saveshotdata = malloc(grab_width * vid.height * 3); if (!saveshotdata) return; // Read the framebuffer into our storage qglReadPixels(grab_x, 0, grab_width, vid.height, GL_RGB, GL_UNSIGNED_BYTE, saveshotdata); } /* ================== R_ScreenShot_JPG By Robert 'Heffo' Heffernan ================== */ void R_ScreenShot_JPG (qboolean silent) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; byte *rgbdata; JSAMPROW s[1]; FILE *file; char picname[80], checkname[MAX_OSPATH]; int i, offset, grab_width, grab_x; // Create the screenshots directory if it doesn't exist Com_sprintf (checkname, sizeof(checkname), "%s/screenshots", FS_Savegamedir()); // was FS_Gamedir() Sys_Mkdir (checkname); // Knightmare- changed screenshot filenames, up to 10000 screenies // Find a file name to save it to for (i=0; i<=9999; i++) { int one, ten, hundred, thousand; thousand = i*0.001; hundred = (i - thousand*1000)*0.01; ten = (i - thousand*1000 - hundred*100)*0.1; one = i - thousand*1000 - hundred*100 - ten*10; Com_sprintf (picname, sizeof(picname), "kmquake2_%i%i%i%i.jpg", thousand, hundred, ten, one); Com_sprintf (checkname, sizeof(checkname), "%s/screenshots/%s", FS_Savegamedir(), picname); // was FS_Gamedir() file = fopen (checkname, "rb"); if (!file) break; // file doesn't exist fclose (file); } if (i == 10000) { VID_Printf (PRINT_ALL, "R_ScreenShot_JPG: Screenshots directory is full!\n"); return; } // Open the file for Binary Output file = fopen(checkname, "wb"); if (!file) { VID_Printf (PRINT_ALL, "R_ScreenShot_JPG: Couldn't create a file\n"); return; } // Round down width to nearest multiple of 4 grab_width = vid.width & ~3; grab_x = (vid.width - grab_width) / 2; // Allocate room for a copy of the framebuffer rgbdata = malloc(grab_width * vid.height * 3); if (!rgbdata) { fclose(file); return; } // Read the framebuffer into our storage // qglReadPixels(grab_x, 0, grab_width, vid.height, GL_RGB, GL_UNSIGNED_BYTE, rgbdata); R_ScreenShot_Read_Buffer(grab_x, grab_width, rgbdata); // Initialise the JPEG compression object cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, file); // Setup JPEG Parameters cinfo.image_width = grab_width; cinfo.image_height = vid.height; cinfo.in_color_space = JCS_RGB; cinfo.input_components = 3; jpeg_set_defaults(&cinfo); if ((r_screenshot_jpeg_quality->integer >= 101) || (r_screenshot_jpeg_quality->integer <= 0)) Cvar_Set("r_screenshot_jpeg_quality", "85"); jpeg_set_quality(&cinfo, r_screenshot_jpeg_quality->integer, TRUE); // Start Compression jpeg_start_compress(&cinfo, true); // Feed Scanline data offset = (cinfo.image_width * cinfo.image_height * 3) - (cinfo.image_width * 3); while(cinfo.next_scanline < cinfo.image_height) { s[0] = &rgbdata[offset - (cinfo.next_scanline * (cinfo.image_width * 3))]; jpeg_write_scanlines(&cinfo, s, 1); } // Finish Compression jpeg_finish_compress(&cinfo); // Destroy JPEG object jpeg_destroy_compress(&cinfo); // Close File fclose(file); // Free Temp Framebuffer free(rgbdata); // Done! if (!silent) VID_Printf (PRINT_ALL, "Wrote %s\n", picname); } #ifdef PNG_SUPPORT // fix for old libpng on MSVC6 //#if defined (PNG_LIBPNG_VER) && (PNG_LIBPNG_VER < 10209) #ifndef png_jmpbuf #define png_jmpbuf(a) ((a)->jmpbuf) #endif /* ================== R_ScreenShot_PNG ================== */ void R_ScreenShot_PNG (qboolean silent) { char picname[80], checkname[MAX_OSPATH]; int i, grab_width, grab_x; png_structp png_sptr; png_infop png_infoptr; byte *rgbdata; void *lineptr; FILE *file; // create the screenshots directory if it doesn't exist Com_sprintf (checkname, sizeof(checkname), "%s/screenshots", FS_Savegamedir()); // was FS_Gamedir() Sys_Mkdir (checkname); // // find a file name to save it to // // Knightmare- changed screenshot filenames, up to 10000 screenies for (i=0; i<=9999; i++) { int one, ten, hundred, thousand; thousand = i*0.001; hundred = (i - thousand*1000)*0.01; ten = (i - thousand*1000 - hundred*100)*0.1; one = i - thousand*1000 - hundred*100 - ten*10; Com_sprintf (picname, sizeof(picname), "kmquake2_%i%i%i%i.png", thousand, hundred, ten, one); Com_sprintf (checkname, sizeof(checkname), "%s/screenshots/%s", FS_Savegamedir(), picname); // was FS_Gamedir() file = fopen (checkname, "rb"); if (!file) break; // file doesn't exist fclose (file); } if (i == 10000) { VID_Printf (PRINT_ALL, "R_ScreenShot_PNG: Screenshots directory is full!\n"); return; } // Round down width to nearest multiple of 4 grab_width = vid.width & ~3; grab_x = (vid.width - grab_width) / 2; // Allocate room for a copy of the framebuffer rgbdata = malloc(grab_width * vid.height * 3); if (!rgbdata) { return; } // Read the framebuffer into our storage // qglReadPixels(grab_x, 0, grab_width, vid.height, GL_RGB, GL_UNSIGNED_BYTE, rgbdata); R_ScreenShot_Read_Buffer(grab_x, grab_width, rgbdata); png_sptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_sptr) { free(rgbdata); VID_Printf (PRINT_ALL, "R_ScreenShot_PNG: Couldn't create PNG struct\n"); return; } png_infoptr = png_create_info_struct(png_sptr); if (!png_infoptr) { png_destroy_write_struct(&png_sptr, 0); free(rgbdata); VID_Printf (PRINT_ALL, "R_ScreenShot_PNG: Couldn't create info struct\n"); return; } // if ( setjmp(png_sptr->jmpbuf) ) if ( setjmp(png_jmpbuf(png_sptr)) ) { png_destroy_info_struct(png_sptr, &png_infoptr); png_destroy_write_struct(&png_sptr, 0); free(rgbdata); VID_Printf (PRINT_ALL, "R_ScreenShot_PNG: bad data\n"); return; } // open png file file = fopen(checkname, "wb"); if (!file) { png_destroy_info_struct(png_sptr, &png_infoptr); png_destroy_write_struct (&png_sptr, 0); free(rgbdata); VID_Printf (PRINT_ALL, "R_ScreenShot_PNG: Couldn't create a file\n"); return; } // encode and output png_init_io(png_sptr, file); png_set_IHDR(png_sptr, png_infoptr, grab_width, vid.height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png_sptr, png_infoptr); for (i=vid.height-1; i>=0; i--) { lineptr = rgbdata + i*grab_width*3; png_write_row(png_sptr, lineptr); } png_write_end(png_sptr, png_infoptr); // clean up fclose (file); png_destroy_info_struct(png_sptr, &png_infoptr); png_destroy_write_struct(&png_sptr, 0); free (rgbdata); if (!silent) VID_Printf (PRINT_ALL, "Wrote %s\n", picname); } #endif // PNG_SUPPORT /* ================== R_ScreenShot_TGA ================== */ void R_ScreenShot_TGA (qboolean silent) { byte *buffer; char picname[80]; char checkname[MAX_OSPATH]; int i, c, temp, grab_width, grab_x; FILE *f; /* // Heffo - JPEG Screenshots if (r_screenshot_jpeg->integer) { R_ScreenShot_JPG(); return; }*/ // create the screenshots directory if it doesn't exist Com_sprintf (checkname, sizeof(checkname), "%s/screenshots", FS_Savegamedir()); // was FS_Gamedir() Sys_Mkdir (checkname); // // find a file name to save it to // // Knightmare- changed screenshot filenames, up to 10000 screenies for (i=0; i<=9999; i++) { int one, ten, hundred, thousand; thousand = i*0.001; hundred = (i - thousand*1000)*0.01; ten = (i - thousand*1000 - hundred*100)*0.1; one = i - thousand*1000 - hundred*100 - ten*10; Com_sprintf (picname, sizeof(picname), "kmquake2_%i%i%i%i.tga", thousand, hundred, ten, one); Com_sprintf (checkname, sizeof(checkname), "%s/screenshots/%s", FS_Savegamedir(), picname); // was FS_Gamedir() f = fopen (checkname, "rb"); if (!f) break; // file doesn't exist fclose (f); } if (i == 10000) { VID_Printf (PRINT_ALL, "R_ScreenShot_TGA: Screenshots directory is full!\n"); return; } // Round down width to nearest multiple of 4 grab_width = vid.width & ~3; grab_x = (vid.width - grab_width) / 2; buffer = malloc(grab_width*vid.height*3 + 18); memset (buffer, 0, 18); buffer[2] = 2; // uncompressed type buffer[12] = grab_width&255; buffer[13] = grab_width>>8; buffer[14] = vid.height&255; buffer[15] = vid.height>>8; buffer[16] = 24; // pixel size // qglReadPixels (grab_x, 0, grab_width, vid.height, GL_RGB, GL_UNSIGNED_BYTE, buffer+18 ); R_ScreenShot_Read_Buffer(grab_x, grab_width, buffer+18); // swap rgb to bgr c = 18+grab_width*vid.height*3; for (i = 18; i < c; i += 3) { temp = buffer[i]; buffer[i] = buffer[i+2]; buffer[i+2] = temp; } f = fopen (checkname, "wb"); if (!f) { free (buffer); VID_Printf (PRINT_ALL, "R_ScreenShot_TGA: Couldn't create a file\n"); return; } fwrite (buffer, 1, c, f); fclose (f); free (buffer); if (!silent) VID_Printf (PRINT_ALL, "Wrote %s\n", picname); } /* ================== R_ScreenShot_f ================== */ void R_ScreenShot_f (void) { if ( !Q_strcasecmp(r_screenshot_format->string, "jpg") ) R_ScreenShot_JPG (false); #ifdef PNG_SUPPORT else if ( !Q_strcasecmp(r_screenshot_format->string, "png") ) R_ScreenShot_PNG (false); #endif // PNG_SUPPORT else R_ScreenShot_TGA (false); } /* ================== R_ScreenShot_Silent_f ================== */ void R_ScreenShot_Silent_f (void) { if ( !Q_strcasecmp(r_screenshot_format->string, "jpg") ) R_ScreenShot_JPG (true); #ifdef PNG_SUPPORT else if ( !Q_strcasecmp(r_screenshot_format->string, "png") ) R_ScreenShot_PNG (true); #endif // PNG_SUPPORT else R_ScreenShot_TGA (true); } /* ================== R_ScreenShot_TGA_f ================== */ void R_ScreenShot_TGA_f (void) { R_ScreenShot_TGA (false); } /* ================== R_ScreenShot_TGA_f ================== */ void R_ScreenShot_JPG_f (void) { R_ScreenShot_JPG (false); } /* ================== R_ScreenShot_PNG_f ================== */ void R_ScreenShot_PNG_f (void) { R_ScreenShot_PNG (false); } //============================================================================== /* ================= GL_UpdateSwapInterval ================= */ void GL_UpdateSwapInterval (void) { static qboolean registering; // don't swap interval if loading a map if (registering != registration_active) r_swapinterval->modified = true; if ( r_swapinterval->modified ) { r_swapinterval->modified = false; registering = registration_active; if ( !glState.stereo_enabled ) { #ifdef _WIN32 if ( qwglSwapIntervalEXT ) qwglSwapIntervalEXT( (registration_active) ? 0 : r_swapinterval->integer ); #endif } } }