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:
Daniel Gibson 2017-03-12 03:28:06 +01:00
parent 516c417d91
commit 9462063869
5 changed files with 1596 additions and 133 deletions

File diff suppressed because it is too large Load diff

View file

@ -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;

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
}