Skip to content

Commit

Permalink
Add screenshotPNG command
Browse files Browse the repository at this point in the history
Add screenshotPNG command.
Fix "\screenshotJPEG levelshot" to save JPEG image instead of TGA.
Allow specifying the size of levelshot using "\screenshot levelshot 256".
  • Loading branch information
zturtleman committed Feb 28, 2017
1 parent 313064b commit 299ba16
Show file tree
Hide file tree
Showing 14 changed files with 4,240 additions and 249 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2033,10 +2033,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 @@ -3184,6 +3184,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 @@ -3260,6 +3282,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 @@ -3280,6 +3303,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));
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

0 comments on commit 299ba16

Please sign in to comment.