Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add screenshotPNG command #262

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2246,10 +2246,13 @@ endif
ifeq ($(USE_INTERNAL_ZLIB),1)
Q3OBJ += \
$(B)/client/adler32.o \
$(B)/client/compress.o \
$(B)/client/crc32.o \
$(B)/client/deflate.o \
$(B)/client/inffast.o \
$(B)/client/inflate.o \
$(B)/client/inftrees.o \
$(B)/client/trees.o \
$(B)/client/zutil.o
endif

Expand Down
30 changes: 30 additions & 0 deletions code/client/cl_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -3193,6 +3193,28 @@ int CL_ScaledMilliseconds(void) {
return Sys_Milliseconds()*com_timescale->value;
}

void CL_GetModDescription( char *buf, int bufLength ) {
FS_GetModDescription( FS_GetCurrentGameDir(), buf, bufLength );
}

void CL_GetMapTitle( char *buf, int bufLength ) {
Q_strncpyz(buf, cl.gameState.stringData + cl.gameState.stringOffsets[CS_MESSAGE], bufLength);
}

void CL_GetPlayerLocation( char *buf, int bufLength ) {
playerState_t *ps;

if (clc.state != CA_ACTIVE || !cl.snap.valid) {
Q_strncpyz(buf, "Unknown", bufLength);
return;
}

ps = &cl.snap.ps;
Com_sprintf(buf, bufLength, "X:%d Y:%d Z:%d A:%d", (int)ps->origin[0],
(int)ps->origin[1], (int)ps->origin[2],
(int)(ps->viewangles[YAW]+360)%360);
}

/*
============
CL_InitRef
Expand Down Expand Up @@ -3269,6 +3291,7 @@ void CL_InitRef( void ) {
ri.Cvar_CheckRange = Cvar_CheckRange;
ri.Cvar_SetDescription = Cvar_SetDescription;
ri.Cvar_VariableIntegerValue = Cvar_VariableIntegerValue;
ri.Cvar_VariableStringBuffer = Cvar_VariableStringBuffer;

// cinematic stuff

Expand All @@ -3289,6 +3312,13 @@ void CL_InitRef( void ) {
ri.Sys_GLimpInit = Sys_GLimpInit;
ri.Sys_LowPhysicalMemory = Sys_LowPhysicalMemory;

ri.zlib_compress = compress;
ri.zlib_crc32 = crc32;

ri.CL_GetModDescription = CL_GetModDescription;
ri.CL_GetMapTitle = CL_GetMapTitle;
ri.CL_GetPlayerLocation = CL_GetPlayerLocation;

ret = GetRefAPI( REF_API_VERSION, &ri );

#if defined __USEA3D && defined __A3D_GEOM
Expand Down
297 changes: 296 additions & 1 deletion code/renderercommon/tr_image_png.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/*
===========================================================================
ioquake3 png decoder
ioquake3 png decoder and encoder
Copyright (C) 2007,2008 Joerg Dietrich
Copyright (C) 2011 Zack Middleton

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
Expand Down Expand Up @@ -78,6 +79,7 @@ typedef uint32_t PNG_ChunkCRC;
#define MAKE_CHUNKTYPE(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | ((d)))

#define PNG_ChunkType_IHDR MAKE_CHUNKTYPE('I', 'H', 'D', 'R')
#define PNG_ChunkType_tEXt MAKE_CHUNKTYPE('t', 'E', 'X', 't')
#define PNG_ChunkType_PLTE MAKE_CHUNKTYPE('P', 'L', 'T', 'E')
#define PNG_ChunkType_IDAT MAKE_CHUNKTYPE('I', 'D', 'A', 'T')
#define PNG_ChunkType_IEND MAKE_CHUNKTYPE('I', 'E', 'N', 'D')
Expand Down Expand Up @@ -2483,3 +2485,296 @@ void R_LoadPNG(const char *name, byte **pic, int *width, int *height)

CloseBufferedFile(ThePNG);
}

/*
* Encode a non-interlaced 8-bit true color image
*/

static qboolean EncodeImageNonInterlaced8True(uint32_t IHDR_Width, uint32_t IHDR_Height,
byte *InBuffer,
uint32_t InBytesPerPixel,
uint32_t Padding,
uint8_t *ImageData)
{
uint32_t BytesPerScanline, BytesPerPixel, PixelsPerByte;
uint32_t w, h;
byte *InPtr;
uint8_t *OutPtr;

/*
* input verification
*/

if(!InBuffer)
{
return(qfalse);
}

/*
* information for un-filtering
* PNG_ColourType_True, PNG_BitDepth_8
*/

BytesPerPixel = PNG_NumColourComponents_True;
PixelsPerByte = 1;

/*
* Calculate the size of one scanline
*/

BytesPerScanline = (IHDR_Width * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte;

/*
* Set the working pointers to the beginning of the buffers.
*/

InPtr = InBuffer;
OutPtr = ImageData;

/*
* Create the output image.
*/

for(h = IHDR_Height; h > 0; h--)
{
InPtr = InBuffer + (BytesPerScanline + Padding) * (h - 1);

/*
* set FilterType
*/

OutPtr[0] = PNG_FilterType_None;
OutPtr++;

for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++)
{
OutPtr[0] = InPtr[0];
OutPtr[1] = InPtr[1];
OutPtr[2] = InPtr[2];

InPtr += InBytesPerPixel;
OutPtr += BytesPerPixel;
}
}

return(qtrue);
}

/*
* Write data to buffer.
*/

void WriteToBuffer(void **buffer, const void *data, size_t length)
{
memcpy(*buffer, data, length);
*(byte**)buffer += length;
}

/*
* Write PNG chuck header to buffer.
*/

void WriteChunkHeader(void **buffer, void **crcPtr, PNG_ChunkCRC *CRC, int type, int length)
{
struct PNG_ChunkHeader CH;

/*
* Write chuck header
*/

CH.Type = BigLong(type);
CH.Length = BigLong(length);

WriteToBuffer(buffer, &CH, PNG_ChunkHeader_Size);

/*
* Init CRC
*/

*CRC = ri.zlib_crc32(0, Z_NULL, 0);
*CRC = ri.zlib_crc32(*CRC, *(byte**)buffer-4, 4);
*crcPtr = *buffer;
}

/*
* Write CRC to buffer.
*/

void WriteCRC(void **buffer, void **crcPtr, PNG_ChunkCRC CRC)
{
int size = (intptr_t)*buffer-(intptr_t)*crcPtr;

/*
* Update CRC
*/
if (size > 0)
CRC = ri.zlib_crc32(CRC, *crcPtr, size);

/*
* Write CRC
*/
CRC = BigLong(CRC);
WriteToBuffer(buffer, &CRC, PNG_ChunkCRC_Size);
}

/*
* The PNG saver
*/

void RE_SavePNG(const char *filename, int width, int height, byte *data, int padding) {
void *pngData;
size_t pngSize;
void *buffer;
struct PNG_Chunk_IHDR IHDR;
PNG_ChunkCRC CRC;
void *crcPtr;
int numtEXt = 0;
#define NUMTEXT 7
struct
{
char key[80]; // PNG limits to 79+'\0'.
char text[256]; // PNG allows any length.
} tEXt[NUMTEXT];
int i;
uint8_t *imageData;
uint32_t imageLength;
Bytef *compressedData = NULL;
uLongf compressedDataLength;

/*
* Create the png image data from buffer.
*/

imageLength = (width*PNG_NumColourComponents_True + 1) * height;
imageData = ri.Malloc(imageLength);

EncodeImageNonInterlaced8True(width, height, data, 3, padding, imageData);

/*
* Compress the png image data.
*/

compressedDataLength = imageLength * 1.01f + 12;
compressedData = ri.Malloc(compressedDataLength);

if (ri.zlib_compress(compressedData, &compressedDataLength, imageData, imageLength) != Z_OK) {
ri.Free(compressedData);
ri.Free(imageData);
ri.Printf(PRINT_WARNING, "RE_SavePNG: Failed to compress image data.\n");
return;
}

/*
* Setup text data.
*/

Q_strncpyz(tEXt[numtEXt].key, "Title", sizeof (tEXt[numtEXt].key));
ri.Cvar_VariableStringBuffer("version", tEXt[numtEXt].text, sizeof (tEXt[numtEXt].text));
numtEXt++;
Q_strncpyz(tEXt[numtEXt].key, "Author", sizeof (tEXt[numtEXt].key));
ri.Cvar_VariableStringBuffer("username", tEXt[numtEXt].text, sizeof (tEXt[numtEXt].text));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I don't mind much about my playername (e.g. ^1That^6Guy) to show up as metadata in screenshots, I'd rather not have my username (e.g. John Doe) recorded somewhere. Especially since the average user might not know about this somewhat hidden metadata or how to remove/edit it.

numtEXt++;
Q_strncpyz(tEXt[numtEXt].key, "Description", sizeof (tEXt[numtEXt].key));
ri.CL_GetModDescription(tEXt[numtEXt].text, sizeof (tEXt[numtEXt].text));
numtEXt++;
Q_strncpyz(tEXt[numtEXt].key, "Mapname", sizeof (tEXt[numtEXt].key));
ri.Cvar_VariableStringBuffer("mapname", tEXt[numtEXt].text, sizeof (tEXt[numtEXt].text));
numtEXt++;
Q_strncpyz(tEXt[numtEXt].key, "Maptitle", sizeof (tEXt[numtEXt].key));
ri.CL_GetMapTitle(tEXt[numtEXt].text, sizeof (tEXt[numtEXt].text));
numtEXt++;
Q_strncpyz(tEXt[numtEXt].key, "Playername", sizeof (tEXt[numtEXt].key));
ri.Cvar_VariableStringBuffer("name", tEXt[numtEXt].text, sizeof (tEXt[numtEXt].text));
numtEXt++;
Q_strncpyz(tEXt[numtEXt].key, "Location", sizeof (tEXt[numtEXt].key));
ri.CL_GetPlayerLocation(tEXt[numtEXt].text, sizeof (tEXt[numtEXt].text));
numtEXt++;


/*
* Calculate the size of the image.
*/

pngSize = PNG_Signature_Size + PNG_ChunkHeader_Size + PNG_Chunk_IHDR_Size + PNG_ChunkCRC_Size;

/*
* Calculate the length of the tEXt chunks.
*/

for (i = 0; i < numtEXt; i++) {
pngSize += PNG_ChunkHeader_Size + strlen(tEXt[i].key)+1 + strlen(tEXt[i].text) + PNG_ChunkCRC_Size;
}

pngSize += PNG_ChunkHeader_Size + compressedDataLength + PNG_ChunkCRC_Size
+ PNG_ChunkHeader_Size + PNG_ChunkCRC_Size;

/*
* Allocate memory to hold the full png image data.
*/

buffer = pngData = ri.Hunk_AllocateTempMemory(pngSize);

/*
* Setup CRC.
*/

CRC = ri.zlib_crc32(0, Z_NULL, 0);
crcPtr = (byte*)buffer + PNG_Signature_Size + 4;

/*
* Header
*/

WriteToBuffer(&buffer, PNG_Signature, PNG_Signature_Size);

WriteChunkHeader(&buffer, &crcPtr, &CRC, PNG_ChunkType_IHDR, PNG_Chunk_IHDR_Size);
IHDR.Width = BigLong(width);
IHDR.Height = BigLong(height);
IHDR.BitDepth = PNG_BitDepth_8;
IHDR.ColourType = PNG_ColourType_True;
IHDR.CompressionMethod = PNG_CompressionMethod_0;
IHDR.FilterMethod = PNG_FilterMethod_0;
IHDR.InterlaceMethod = PNG_InterlaceMethod_NonInterlaced;
WriteToBuffer(&buffer, &IHDR, PNG_Chunk_IHDR_Size);
WriteCRC(&buffer, &crcPtr, CRC);

/*
* tEXt, Textual data.
*/
for (i = 0; i < numtEXt; i++) {
WriteChunkHeader(&buffer, &crcPtr, &CRC, PNG_ChunkType_tEXt, strlen(tEXt[i].key)+1 + strlen(tEXt[i].text));

/*
* Write string data.
*/
WriteToBuffer(&buffer, tEXt[i].key, strlen(tEXt[i].key)+1);
WriteToBuffer(&buffer, tEXt[i].text, strlen(tEXt[i].text));

WriteCRC(&buffer, &crcPtr, CRC);
}

/*
* IDAT, Image Data.
*/
WriteChunkHeader(&buffer, &crcPtr, &CRC, PNG_ChunkType_IDAT, compressedDataLength);
WriteToBuffer(&buffer, compressedData, compressedDataLength);
WriteCRC(&buffer, &crcPtr, CRC);

/*
* IEND, Image End.
*/
WriteChunkHeader(&buffer, &crcPtr, &CRC, PNG_ChunkType_IEND, 0);
WriteCRC(&buffer, &crcPtr, CRC);

/*
* Write the image to file.
*/
ri.FS_WriteFile(filename, pngData, pngSize);

/*
* Free memory.
*/
ri.Hunk_FreeTempMemory(pngData);
ri.Free(compressedData);
ri.Free(imageData);
}
Loading