From 8022854493e5595b924ed2d573255bff8faf6d4f Mon Sep 17 00:00:00 2001 From: divverent Date: Thu, 31 Dec 2009 14:44:18 +0000 Subject: [PATCH] new cvar: scr_screenshot_png (and obvious PNG screenshotting code to support it) git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@9760 d7cf8633-e32d-0410-b094-e92efae38249 --- cl_screen.c | 41 +++++++++++++--- gl_backend.h | 2 +- image_png.c | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++- image_png.h | 1 + 4 files changed, 164 insertions(+), 9 deletions(-) diff --git a/cl_screen.c b/cl_screen.c index 8b34edac..9ccbfd67 100644 --- a/cl_screen.c +++ b/cl_screen.c @@ -3,6 +3,7 @@ #include "cl_video.h" #include "image.h" #include "jpeg.h" +#include "image_png.h" #include "cl_collision.h" #include "libcurl.h" #include "csprogs.h" @@ -30,6 +31,7 @@ cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480", "virtual height of 2D cvar_t vid_pixelheight = {CVAR_SAVE, "vid_pixelheight", "1", "adjusts vertical field of vision to account for non-square pixels (1280x1024 on a CRT monitor for example)"}; cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","1", "save jpeg instead of targa"}; cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9", "image quality of saved jpeg"}; +cvar_t scr_screenshot_png = {CVAR_SAVE, "scr_screenshot_png","0", "save png instead of targa"}; cvar_t scr_screenshot_gammaboost = {CVAR_SAVE, "scr_screenshot_gammaboost","1", "gamma correction on saved screenshots and videos, 1.0 saves unmodified images"}; cvar_t scr_screenshot_hwgamma = {CVAR_SAVE, "scr_screenshot_hwgamma","1", "apply the video gamma ramp to saved screenshots and videos"}; // scr_screenshot_name is defined in fs.c @@ -860,6 +862,7 @@ void CL_Screen_Init(void) Cvar_RegisterVariable (&vid_pixelheight); Cvar_RegisterVariable (&scr_screenshot_jpeg); Cvar_RegisterVariable (&scr_screenshot_jpeg_quality); + Cvar_RegisterVariable (&scr_screenshot_png); Cvar_RegisterVariable (&scr_screenshot_gammaboost); Cvar_RegisterVariable (&scr_screenshot_hwgamma); Cvar_RegisterVariable (&scr_screenshot_name_in_mapdir); @@ -917,6 +920,7 @@ void SCR_ScreenShot_f (void) unsigned char *buffer2; unsigned char *buffer3; qboolean jpeg = (scr_screenshot_jpeg.integer != 0); + qboolean png = (scr_screenshot_png.integer != 0) && !jpeg; if (Cmd_Argc() == 2) { @@ -924,12 +928,23 @@ void SCR_ScreenShot_f (void) strlcpy(filename, Cmd_Argv(1), sizeof(filename)); ext = FS_FileExtension(filename); if (!strcasecmp(ext, "jpg")) + { jpeg = true; + png = false; + } else if (!strcasecmp(ext, "tga")) + { jpeg = false; + png = false; + } + else if (!strcasecmp(ext, "png")) + { + jpeg = false; + png = true; + } else { - Con_Printf("screenshot: supplied filename must end in .jpg or .tga\n"); + Con_Printf("screenshot: supplied filename must end in .jpg or .tga or .png\n"); return; } } @@ -954,7 +969,7 @@ void SCR_ScreenShot_f (void) // find a file name to save it to for (;shotnumber < 1000000;shotnumber++) - if (!FS_SysFileExists(va("%s/screenshots/%s%06d.tga", fs_gamedir, prefix_name, shotnumber)) && !FS_SysFileExists(va("%s/screenshots/%s%06d.jpg", fs_gamedir, prefix_name, shotnumber))) + if (!FS_SysFileExists(va("%s/screenshots/%s%06d.tga", fs_gamedir, prefix_name, shotnumber)) && !FS_SysFileExists(va("%s/screenshots/%s%06d.jpg", fs_gamedir, prefix_name, shotnumber)) && !FS_SysFileExists(va("%s/screenshots/%s%06d.png", fs_gamedir, prefix_name, shotnumber))) break; if (shotnumber >= 1000000) { @@ -962,17 +977,27 @@ void SCR_ScreenShot_f (void) return; } - dpsnprintf(filename, sizeof(filename), "screenshots/%s%06d.%s", prefix_name, shotnumber, jpeg ? "jpg" : "tga"); + dpsnprintf(filename, sizeof(filename), "screenshots/%s%06d.%s", prefix_name, shotnumber, jpeg ? "jpg" : png ? "png" : "tga"); } buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3); buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3); buffer3 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3 + 18); - if (SCR_ScreenShot (filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, jpeg, true)) + if (SCR_ScreenShot (filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, jpeg, png, true)) Con_Printf("Wrote %s\n", filename); else + { Con_Printf("Unable to write %s\n", filename); + if(jpeg || png) + { + if(SCR_ScreenShot (filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, false, false, true)) + { + strlcpy(filename + strlen(filename) - 3, "tga", 4); + Con_Printf("Wrote %s\n", filename); + } + } + } Mem_Free (buffer1); Mem_Free (buffer2); @@ -1344,7 +1369,7 @@ static void R_Envmap_f (void) R_Mesh_Start(); R_RenderView(); R_Mesh_Finish(); - SCR_ScreenShot(filename, buffer1, buffer2, buffer3, 0, vid.height - (r_refdef.view.y + r_refdef.view.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false, false); + SCR_ScreenShot(filename, buffer1, buffer2, buffer3, 0, vid.height - (r_refdef.view.y + r_refdef.view.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false, false, false); } Mem_Free (buffer1); @@ -1427,7 +1452,7 @@ void SHOWLMP_drawall(void) ============================================================================== */ -qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, unsigned char *buffer3, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean gammacorrect) +qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, unsigned char *buffer3, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean png, qboolean gammacorrect) { int indices[3] = {0,1,2}; qboolean ret; @@ -1468,6 +1493,8 @@ qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *b if (jpeg) ret = JPEG_SaveImage_preflipped (filename, width, height, buffer2); + else if (png) + ret = PNG_SaveImage_preflipped (filename, width, height, buffer2); else ret = Image_WriteTGABGR_preflipped (filename, width, height, buffer2, buffer3); @@ -1638,7 +1665,7 @@ void SCR_DrawScreen (void) buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3); buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3); buffer3 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3 + 18); - SCR_ScreenShot(filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, false, true); + SCR_ScreenShot(filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, false, false, true); Mem_Free(buffer1); Mem_Free(buffer2); Mem_Free(buffer3); diff --git a/gl_backend.h b/gl_backend.h index 50b76d44..72f2bc46 100644 --- a/gl_backend.h +++ b/gl_backend.h @@ -101,7 +101,7 @@ void R_Mesh_ResetTextureState(void); void R_Mesh_Draw(int firstvertex, int numvertices, int firsttriangle, int numtriangles, const int *element3i, const unsigned short *element3s, int bufferobject3i, int bufferobject3s); // saves a section of the rendered frame to a .tga or .jpg file -qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, unsigned char *buffer3, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean gammacorrect); +qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, unsigned char *buffer3, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean png, qboolean gammacorrect); // used by R_Envmap_f and internally in backend, clears the frame void R_ClearScreen(qboolean fogcolor); diff --git a/image_png.c b/image_png.c index d57c1893..4dc0fd93 100644 --- a/image_png.c +++ b/image_png.c @@ -33,50 +33,72 @@ static void (*qpng_set_sig_bytes) (void*, int); static int (*qpng_sig_cmp) (const unsigned char*, size_t, size_t); static void* (*qpng_create_read_struct) (const char*, void*, void(*)(void *png, const char *message), void(*)(void *png, const char *message)); +static void* (*qpng_create_write_struct) (const char*, void*, void(*)(void *png, const char *message), void(*)(void *png, const char *message)); static void* (*qpng_create_info_struct) (void*); static void (*qpng_read_info) (void*, void*); +static void (*qpng_set_compression_level) (void*, int); static void (*qpng_set_expand) (void*); static void (*qpng_set_gray_1_2_4_to_8) (void*); static void (*qpng_set_palette_to_rgb) (void*); static void (*qpng_set_tRNS_to_alpha) (void*); static void (*qpng_set_gray_to_rgb) (void*); static void (*qpng_set_filler) (void*, unsigned int, int); +static void (*qpng_set_IHDR) (void*, void*, unsigned long, unsigned long, int, int, int, int, int); +static void (*qpng_set_packing) (void*); +static void (*qpng_set_bgr) (void*); +static int (*qpng_set_interlace_handling) (void*); static void (*qpng_read_update_info) (void*, void*); static void (*qpng_read_image) (void*, unsigned char**); static void (*qpng_read_end) (void*, void*); static void (*qpng_destroy_read_struct) (void**, void**, void**); +static void (*qpng_destroy_write_struct) (void**, void**); static void (*qpng_set_read_fn) (void*, void*, void(*)(void *png, unsigned char *data, size_t length)); +static void (*qpng_set_write_fn) (void*, void*, void(*)(void *png, unsigned char *data, size_t length), void(*)(void *png)); static unsigned int (*qpng_get_valid) (void*, void*, unsigned int); static unsigned int (*qpng_get_rowbytes) (void*, void*); static unsigned char (*qpng_get_channels) (void*, void*); static unsigned char (*qpng_get_bit_depth) (void*, void*); static unsigned int (*qpng_get_IHDR) (void*, void*, unsigned long*, unsigned long*, int *, int *, int *, int *, int *); static char* (*qpng_get_libpng_ver) (void*); +static void (*qpng_write_info) (void*, void*); +static void (*qpng_write_row) (void*, unsigned char*); +static void (*qpng_write_end) (void*, void*); static dllfunction_t pngfuncs[] = { {"png_set_sig_bytes", (void **) &qpng_set_sig_bytes}, {"png_sig_cmp", (void **) &qpng_sig_cmp}, {"png_create_read_struct", (void **) &qpng_create_read_struct}, + {"png_create_write_struct", (void **) &qpng_create_write_struct}, {"png_create_info_struct", (void **) &qpng_create_info_struct}, {"png_read_info", (void **) &qpng_read_info}, + {"png_set_compression_level", (void **) &qpng_set_compression_level}, {"png_set_expand", (void **) &qpng_set_expand}, {"png_set_gray_1_2_4_to_8", (void **) &qpng_set_gray_1_2_4_to_8}, {"png_set_palette_to_rgb", (void **) &qpng_set_palette_to_rgb}, {"png_set_tRNS_to_alpha", (void **) &qpng_set_tRNS_to_alpha}, {"png_set_gray_to_rgb", (void **) &qpng_set_gray_to_rgb}, {"png_set_filler", (void **) &qpng_set_filler}, + {"png_set_IHDR", (void **) &qpng_set_IHDR}, + {"png_set_packing", (void **) &qpng_set_packing}, + {"png_set_bgr", (void **) &qpng_set_bgr}, + {"png_set_interlace_handling", (void **) &qpng_set_interlace_handling}, {"png_read_update_info", (void **) &qpng_read_update_info}, {"png_read_image", (void **) &qpng_read_image}, {"png_read_end", (void **) &qpng_read_end}, {"png_destroy_read_struct", (void **) &qpng_destroy_read_struct}, + {"png_destroy_write_struct", (void **) &qpng_destroy_write_struct}, {"png_set_read_fn", (void **) &qpng_set_read_fn}, + {"png_set_write_fn", (void **) &qpng_set_write_fn}, {"png_get_valid", (void **) &qpng_get_valid}, {"png_get_rowbytes", (void **) &qpng_get_rowbytes}, {"png_get_channels", (void **) &qpng_get_channels}, {"png_get_bit_depth", (void **) &qpng_get_bit_depth}, {"png_get_IHDR", (void **) &qpng_get_IHDR}, {"png_get_libpng_ver", (void **) &qpng_get_libpng_ver}, + {"png_write_info", (void **) &qpng_write_info}, + {"png_write_row", (void **) &qpng_write_row}, + {"png_write_end", (void **) &qpng_write_end}, {NULL, NULL} }; @@ -135,7 +157,6 @@ void PNG_CloseLibrary (void) Sys_UnloadLibrary (&png_dll); } - /* ================================================================= @@ -187,6 +208,7 @@ static struct int Filter; //double LastModified; //int Transparent; + qfile_t *outfile; } my_png; //LordHavoc: removed __cdecl prefix, added overrun protection, and rewrote this to be more efficient @@ -207,6 +229,15 @@ void PNG_fReadData(void *png, unsigned char *data, size_t length) //Com_HexDumpToConsole(data, (int)length); } +void PNG_fWriteData(void *png, unsigned char *data, size_t length) +{ + FS_Write(my_png.outfile, data, length); +} + +void PNG_fFlushData(void *png) +{ +} + void PNG_error_fn(void *png, const char *message) { Con_Printf("PNG_LoadImage: error: %s\n", message); @@ -378,3 +409,99 @@ unsigned char *PNG_LoadImage_BGRA (const unsigned char *raw, int filesize) return imagedata; } +/* +================================================================= + + PNG compression + +================================================================= +*/ + +#define Z_BEST_COMPRESSION 9 +#define PNG_INTERLACE_ADAM7 1 +#define PNG_FILTER_TYPE_BASE 0 +#define PNG_FILTER_TYPE_DEFAULT PNG_FILTER_TYPE_BASE +#define PNG_COMPRESSION_TYPE_BASE 0 +#define PNG_COMPRESSION_TYPE_DEFAULT PNG_COMPRESSION_TYPE_BASE + + +/* +==================== +PNG_SaveImage_preflipped + +Save a preflipped PNG image to a file +==================== +*/ +qboolean PNG_SaveImage_preflipped (const char *filename, int width, int height, unsigned char *data) +{ + unsigned int offset, linesize; + qfile_t* file = NULL; + void *png, *pnginfo; + unsigned char ioBuffer[8192]; + int passes, i, j; + + // No DLL = no JPEGs + if (!png_dll) + { + Con_Print("You need the libpng library to save PNG images\n"); + return false; + } + + png = (void *)qpng_create_write_struct(PNG_LIBPNG_VER_STRING, 0, PNG_error_fn, PNG_warning_fn); + if(!png) + return false; + pnginfo = (void *)qpng_create_info_struct(png); + if(!pnginfo) + { + qpng_destroy_write_struct(&png, NULL); + return false; + } + + // this must be memset before the setjmp error handler, because it relies + // on the fields in this struct for cleanup + memset(&my_png, 0, sizeof(my_png)); + + // NOTE: this relies on jmp_buf being the first thing in the png structure + // created by libpng! (this is correct for libpng 1.2.x) +#ifdef __cplusplus +#if defined(MACOSX) || defined(WIN32) + if (setjmp((int *)png)) +#else + if (setjmp((__jmp_buf_tag *)png)) +#endif +#else + if (setjmp(png)) +#endif + { + qpng_destroy_write_struct(&png, &pnginfo); + return false; + } + + // Open the file + file = FS_OpenRealFile(filename, "wb", true); + if (!file) + return false; + my_png.outfile = file; + qpng_set_write_fn(png, ioBuffer, PNG_fWriteData, PNG_fFlushData); + + qpng_set_compression_level(png, Z_BEST_COMPRESSION); + + qpng_set_IHDR(png, pnginfo, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_ADAM7, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + qpng_write_info(png, pnginfo); + qpng_set_packing(png); + qpng_set_bgr(png); + + passes = qpng_set_interlace_handling(png); + + linesize = width * 3; + offset = linesize * (height - 1); + for(i = 0; i < passes; ++i) + for(j = 0; j < height; ++j) + qpng_write_row(png, &data[offset - j * linesize]); + + qpng_write_end(png, NULL); + qpng_destroy_write_struct(&png, &pnginfo); + + FS_Close (file); + return true; +} diff --git a/image_png.h b/image_png.h index 045b1cb6..c521afdc 100644 --- a/image_png.h +++ b/image_png.h @@ -27,6 +27,7 @@ qboolean PNG_OpenLibrary (void); void PNG_CloseLibrary (void); unsigned char* PNG_LoadImage_BGRA (const unsigned char *f, int filesize); +qboolean PNG_SaveImage_preflipped (const char *filename, int width, int height, unsigned char *data); #endif -- 2.39.2