mirror of
https://github.com/yquake2/yquake2remaster.git
synced 2025-02-21 11:21:52 +00:00
Screenshots can be tga, bmp, png, jpg now, using stb_image_write.h
the screenshot command now supports the filetype as optional argument (just "screenshot" will use tga like before): "screenshot png" will save the screenshot as PNG, same with jpg, png and tga. For jpg, you can even specify the quality, like "screenshot jpg 90" (the Quality is between 1 and 100, like with libjpeg). To reduce duplicated code, I addeed Vid_WriteScreenshot() to refimport_t and implement most of it in the client (vid.c). The renderer still fetches the raw image data from OpenGL or whatever and then calls re.VidWriteScreenshot() which will write it to disk in the format requested by the user.
This commit is contained in:
parent
516c417d91
commit
9462063869
5 changed files with 1596 additions and 133 deletions
1454
src/backends/generic/header/stb_image_write.h
Normal file
1454
src/backends/generic/header/stb_image_write.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -41,6 +41,9 @@
|
|||
#include "../../client/header/client.h"
|
||||
#include "../../client/header/keyboard.h"
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "header/stb_image_write.h"
|
||||
|
||||
qboolean VID_LoadRefresh(void);
|
||||
|
||||
typedef struct vidmode_s
|
||||
|
@ -173,6 +176,98 @@ VID_Init(void)
|
|||
VID_CheckChanges();
|
||||
}
|
||||
|
||||
// called with image data of width*height pixel which comp bytes per pixel (must be 3 or 4 for RGB or RGBA)
|
||||
// expects the pixels data to be row-wise, starting at top left
|
||||
void VID_WriteScreenshot( int width, int height, int comp, const void* data )
|
||||
{
|
||||
char picname[80];
|
||||
char checkname[MAX_OSPATH];
|
||||
int i, success=0;
|
||||
static const char* supportedFormats[] = { "tga", "bmp", "png", "jpg" };
|
||||
static const int numFormats = sizeof(supportedFormats)/sizeof(supportedFormats[0]);
|
||||
int format = 0; // 0=tga 1=bmp 2=png 3=jpg
|
||||
int quality = 85;
|
||||
int argc = Cmd_Argc();
|
||||
const char* gameDir = FS_Gamedir();
|
||||
|
||||
/* FS_InitFilesystem() made sure the screenshots dir exists */
|
||||
|
||||
if(argc > 1)
|
||||
{
|
||||
const char* maybeFormat = Cmd_Argv(1);
|
||||
|
||||
for(i=0; i<numFormats; ++i)
|
||||
{
|
||||
if(Q_stricmp(maybeFormat, supportedFormats[i]) == 0)
|
||||
{
|
||||
format = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i==numFormats)
|
||||
{
|
||||
Com_Printf("the (optional) second argument to 'screenshot' is the format, one of \"tga\", \"bmp\", \"png\", \"jpg\"\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if(argc > 2)
|
||||
{
|
||||
const char* q = Cmd_Argv(2);
|
||||
int qualityStrLen = strlen(q);
|
||||
for(i=0; i<qualityStrLen; ++i)
|
||||
{
|
||||
if(q[i] < '0' || q[i] > '9')
|
||||
{
|
||||
Com_Printf("the (optional!) third argument to 'screenshot' is jpg quality, a number between 1 and 100!\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
quality = atoi(q);
|
||||
if(quality < 1) quality = 1;
|
||||
else if(quality > 100) quality = 100;
|
||||
}
|
||||
}
|
||||
|
||||
/* find a file name to save it to */
|
||||
for (i = 0; i <= 9999; i++)
|
||||
{
|
||||
FILE *f;
|
||||
Com_sprintf(checkname, sizeof(checkname), "%s/scrnshot/q2_%04d.%s", gameDir, i, supportedFormats[format]);
|
||||
f = fopen(checkname, "rb");
|
||||
|
||||
if (!f)
|
||||
{
|
||||
Com_sprintf(picname, sizeof(picname), "q2_%04d.%s", i, supportedFormats[format]);
|
||||
break; /* file doesn't exist */
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
if (i == 10000)
|
||||
{
|
||||
Com_Printf("SCR_ScreenShot_f: Couldn't create a file\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch(format) // 0=tga 1=bmp 2=png 3=jpg
|
||||
{
|
||||
case 0: success = stbi_write_tga(checkname, width, height, comp, data); break;
|
||||
case 1: success = stbi_write_bmp(checkname, width, height, comp, data); break;
|
||||
case 2: success = stbi_write_png(checkname, width, height, comp, data, 0); break;
|
||||
case 3: success = stbi_write_jpg(checkname, width, height, comp, data, quality); break;
|
||||
}
|
||||
|
||||
if(success)
|
||||
{
|
||||
Com_Printf("Wrote %s\n", picname);
|
||||
}
|
||||
else
|
||||
{
|
||||
Com_Printf("SCR_ScreenShot_f: Couldn't write %s\n", picname);
|
||||
}
|
||||
}
|
||||
|
||||
// Structure containing functions exported from refresh DLL
|
||||
refexport_t re;
|
||||
void *reflib_handle = NULL; // Handle to refresh DLL
|
||||
|
@ -231,6 +326,7 @@ VID_LoadRefresh(void)
|
|||
ri.Vid_GetModeInfo = VID_GetModeInfo;
|
||||
ri.Vid_MenuInit = VID_MenuInit;
|
||||
ri.Vid_NewWindow = VID_NewWindow;
|
||||
ri.Vid_WriteScreenshot = VID_WriteScreenshot;
|
||||
|
||||
ri.Vid_ShutdownWindow = VID_ShutdownWindow;
|
||||
ri.GLimp_Init = GLimp_Init;
|
||||
|
|
|
@ -229,6 +229,9 @@ typedef struct
|
|||
qboolean (IMPORT *Vid_GetModeInfo)(int *width, int *height, int mode);
|
||||
void (IMPORT *Vid_MenuInit)( void );
|
||||
void (IMPORT *Vid_NewWindow)( int width, int height );
|
||||
// called with image data of width*height pixel which comp bytes per pixel (must be 3 or 4 for RGB or RGBA)
|
||||
// expects the pixels data to be row-wise, starting at top left
|
||||
void (IMPORT *Vid_WriteScreenshot)( int width, int height, int comp, const void* data );
|
||||
|
||||
void (IMPORT *Vid_ShutdownWindow)(void);
|
||||
int (IMPORT *GLimp_Init)(void);
|
||||
|
|
|
@ -102,87 +102,38 @@ R_InitParticleTexture(void)
|
|||
void
|
||||
R_ScreenShot(void)
|
||||
{
|
||||
byte *buffer, temp;
|
||||
char picname[80];
|
||||
char checkname[MAX_OSPATH];
|
||||
int i, c;
|
||||
FILE *f;
|
||||
int w=vid.width, h=vid.height;
|
||||
byte *buffer = malloc(w*h*3);
|
||||
|
||||
/* FS_InitFilesystem() made sure the screenshots dir exists */
|
||||
|
||||
/* find a file name to save it to */
|
||||
strcpy(picname, "quake00.tga");
|
||||
|
||||
for (i = 0; i <= 99; i++)
|
||||
{
|
||||
picname[5] = i / 10 + '0';
|
||||
picname[6] = i % 10 + '0';
|
||||
Com_sprintf(checkname, sizeof(checkname), "%s/scrnshot/%s",
|
||||
ri.FS_Gamedir(), picname);
|
||||
f = fopen(checkname, "rb");
|
||||
|
||||
if (!f)
|
||||
{
|
||||
break; /* file doesn't exist */
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
if (i == 100)
|
||||
{
|
||||
R_Printf(PRINT_ALL, "SCR_ScreenShot_f: Couldn't create a file\n");
|
||||
return;
|
||||
}
|
||||
|
||||
static const int headerLength = 18+4;
|
||||
|
||||
c = headerLength + vid.width * vid.height * 3;
|
||||
|
||||
buffer = malloc(c);
|
||||
if (!buffer)
|
||||
{
|
||||
R_Printf(PRINT_ALL, "SCR_ScreenShot_f: Couldn't malloc %d bytes\n", c);
|
||||
R_Printf(PRINT_ALL, "R_ScreenShot: Couldn't malloc %d bytes\n", w*h*3);
|
||||
return;
|
||||
}
|
||||
|
||||
memset(buffer, 0, headerLength);
|
||||
buffer[0] = 4; // image ID: "yq2\0"
|
||||
buffer[2] = 2; /* uncompressed type */
|
||||
buffer[12] = vid.width & 255;
|
||||
buffer[13] = vid.width >> 8;
|
||||
buffer[14] = vid.height & 255;
|
||||
buffer[15] = vid.height >> 8;
|
||||
buffer[16] = 24; /* pixel size */
|
||||
buffer[17] = 0; // image descriptor
|
||||
buffer[18] = 'y'; // following: the 4 image ID fields
|
||||
buffer[19] = 'q';
|
||||
buffer[20] = '2';
|
||||
buffer[21] = '\0';
|
||||
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(0, 0, vid.width, vid.height, GL_RGB,
|
||||
GL_UNSIGNED_BYTE, buffer + headerLength);
|
||||
glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, buffer);
|
||||
|
||||
/* swap rgb to bgr */
|
||||
for (i = headerLength; i < c; i += 3)
|
||||
// the pixels are now row-wise left to right, bottom to top,
|
||||
// but we need them row-wise left to right, top to bottom.
|
||||
// so swap bottom rows with top rows
|
||||
{
|
||||
temp = buffer[i];
|
||||
buffer[i] = buffer[i + 2];
|
||||
buffer[i + 2] = temp;
|
||||
size_t bytesPerRow = 3*w;
|
||||
byte rowBuffer[bytesPerRow];
|
||||
byte *curRowL = buffer; // first byte of first row
|
||||
byte *curRowH = buffer + bytesPerRow*(h-1); // first byte of last row
|
||||
while(curRowL < curRowH)
|
||||
{
|
||||
memcpy(rowBuffer, curRowL, bytesPerRow);
|
||||
memcpy(curRowL, curRowH, bytesPerRow);
|
||||
memcpy(curRowH, rowBuffer, bytesPerRow);
|
||||
|
||||
curRowL += bytesPerRow;
|
||||
curRowH -= bytesPerRow;
|
||||
}
|
||||
}
|
||||
|
||||
f = fopen(checkname, "wb");
|
||||
if (f)
|
||||
{
|
||||
fwrite(buffer, 1, c, f);
|
||||
fclose(f);
|
||||
R_Printf(PRINT_ALL, "Wrote %s\n", picname);
|
||||
}
|
||||
else
|
||||
{
|
||||
R_Printf(PRINT_ALL, "SCR_ScreenShot_f: Couldn't write %s\n", picname);
|
||||
}
|
||||
ri.Vid_WriteScreenshot(w, h, 3, buffer);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
|
|
@ -126,79 +126,38 @@ GL3_InitParticleTexture(void)
|
|||
void
|
||||
GL3_ScreenShot(void)
|
||||
{
|
||||
byte *buffer;
|
||||
char picname[80];
|
||||
char checkname[MAX_OSPATH];
|
||||
int i, c;
|
||||
FILE *f;
|
||||
int w=vid.width, h=vid.height;
|
||||
byte *buffer = malloc(w*h*3);
|
||||
|
||||
/* FS_InitFilesystem() made sure the screenshots dir exists */
|
||||
|
||||
/* find a file name to save it to */
|
||||
strcpy(picname, "quake00.tga");
|
||||
|
||||
for (i = 0; i <= 99; i++)
|
||||
{
|
||||
picname[5] = i / 10 + '0';
|
||||
picname[6] = i % 10 + '0';
|
||||
Com_sprintf(checkname, sizeof(checkname), "%s/scrnshot/%s",
|
||||
ri.FS_Gamedir(), picname);
|
||||
f = fopen(checkname, "rb");
|
||||
|
||||
if (!f)
|
||||
{
|
||||
break; /* file doesn't exist */
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
if (i == 100)
|
||||
{
|
||||
R_Printf(PRINT_ALL, "SCR_ScreenShot_f: Couldn't create a file\n");
|
||||
return;
|
||||
}
|
||||
|
||||
static const int headerLength = 18+4;
|
||||
|
||||
c = headerLength + vid.width * vid.height * 3;
|
||||
|
||||
buffer = malloc(c);
|
||||
if (!buffer)
|
||||
{
|
||||
R_Printf(PRINT_ALL, "SCR_ScreenShot_f: Couldn't malloc %d bytes\n", c);
|
||||
R_Printf(PRINT_ALL, "GL3_ScreenShot: Couldn't malloc %d bytes\n", w*h*3);
|
||||
return;
|
||||
}
|
||||
|
||||
memset(buffer, 0, headerLength);
|
||||
buffer[0] = 4; // image ID: "yq2\0"
|
||||
buffer[2] = 2; /* uncompressed type */
|
||||
buffer[12] = vid.width & 255;
|
||||
buffer[13] = vid.width >> 8;
|
||||
buffer[14] = vid.height & 255;
|
||||
buffer[15] = vid.height >> 8;
|
||||
buffer[16] = 24; /* pixel size */
|
||||
buffer[17] = 0; // image descriptor
|
||||
buffer[18] = 'y'; // following: the 4 image ID fields
|
||||
buffer[19] = 'q';
|
||||
buffer[20] = '2';
|
||||
buffer[21] = '\0';
|
||||
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(0, 0, vid.width, vid.height, GL_BGR,
|
||||
GL_UNSIGNED_BYTE, buffer + headerLength);
|
||||
glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, buffer);
|
||||
|
||||
f = fopen(checkname, "wb");
|
||||
if (f)
|
||||
// the pixels are now row-wise left to right, bottom to top,
|
||||
// but we need them row-wise left to right, top to bottom.
|
||||
// so swap bottom rows with top rows
|
||||
{
|
||||
fwrite(buffer, 1, c, f);
|
||||
fclose(f);
|
||||
R_Printf(PRINT_ALL, "Wrote %s\n", picname);
|
||||
}
|
||||
else
|
||||
{
|
||||
R_Printf(PRINT_ALL, "SCR_ScreenShot_f: Couldn't write %s\n", picname);
|
||||
size_t bytesPerRow = 3*w;
|
||||
byte rowBuffer[bytesPerRow];
|
||||
byte *curRowL = buffer; // first byte of first row
|
||||
byte *curRowH = buffer + bytesPerRow*(h-1); // first byte of last row
|
||||
while(curRowL < curRowH)
|
||||
{
|
||||
memcpy(rowBuffer, curRowL, bytesPerRow);
|
||||
memcpy(curRowL, curRowH, bytesPerRow);
|
||||
memcpy(curRowH, rowBuffer, bytesPerRow);
|
||||
|
||||
curRowL += bytesPerRow;
|
||||
curRowH -= bytesPerRow;
|
||||
}
|
||||
}
|
||||
|
||||
ri.Vid_WriteScreenshot(w, h, 3, buffer);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue