1 /**
2  * @file capture.cpp
3  *
4  * Implementation of the screenshot function.
5  */
6 #include <fstream>
7 
8 #include "all.h"
9 #include "../3rdParty/Storm/Source/storm.h"
10 #include "paths.h"
11 #include "file_util.h"
12 
13 DEVILUTION_BEGIN_NAMESPACE
14 
15 /**
16  * @brief Write the PCX-file header
17  * @param width Image width
18  * @param height Image height
19  * @param out File stream to write to
20  * @return True on success
21  */
CaptureHdr(short width,short height,std::ofstream * out)22 static BOOL CaptureHdr(short width, short height, std::ofstream *out)
23 {
24 	PCXHEADER Buffer;
25 
26 	memset(&Buffer, 0, sizeof(Buffer));
27 	Buffer.Manufacturer = 10;
28 	Buffer.Version = 5;
29 	Buffer.Encoding = 1;
30 	Buffer.BitsPerPixel = 8;
31 	Buffer.Xmax = SDL_SwapLE16(width - 1);
32 	Buffer.Ymax = SDL_SwapLE16(height - 1);
33 	Buffer.HDpi = SDL_SwapLE16(width);
34 	Buffer.VDpi = SDL_SwapLE16(height);
35 	Buffer.NPlanes = 1;
36 	Buffer.BytesPerLine = SDL_SwapLE16(width);
37 
38 	out->write(reinterpret_cast<const char *>(&Buffer), sizeof(Buffer));
39 	return !out->fail();
40 }
41 
42 /**
43  * @brief Write the current ingame palette to the PCX file
44  * @param palette Current palette
45  * @param out File stream for the PCX file.
46  * @return True if successful, else false
47  */
CapturePal(SDL_Color * palette,std::ofstream * out)48 static BOOL CapturePal(SDL_Color *palette, std::ofstream *out)
49 {
50 	BYTE pcx_palette[1 + 256 * 3];
51 	int i;
52 
53 	pcx_palette[0] = 12;
54 	for (i = 0; i < 256; i++) {
55 		pcx_palette[1 + 3 * i + 0] = palette[i].r;
56 		pcx_palette[1 + 3 * i + 1] = palette[i].g;
57 		pcx_palette[1 + 3 * i + 2] = palette[i].b;
58 	}
59 
60 	out->write(reinterpret_cast<const char *>(pcx_palette), sizeof(pcx_palette));
61 	return !out->fail();
62 }
63 
64 /**
65  * @brief RLE compress the pixel data
66  * @param src Raw pixel buffer
67  * @param dst Output buffer
68  * @param width Width of pixel buffer
69 
70  * @return Output buffer
71  */
CaptureEnc(BYTE * src,BYTE * dst,int width)72 static BYTE *CaptureEnc(BYTE *src, BYTE *dst, int width)
73 {
74 	int rleLength;
75 
76 	do {
77 		BYTE rlePixel = *src;
78 		src++;
79 		rleLength = 1;
80 
81 		width--;
82 
83 		while (rlePixel == *src) {
84 			if (rleLength >= 63)
85 				break;
86 			if (!width)
87 				break;
88 			rleLength++;
89 
90 			width--;
91 			src++;
92 		}
93 
94 		if (rleLength > 1 || rlePixel > 0xBF) {
95 			*dst = rleLength | 0xC0;
96 			dst++;
97 		}
98 
99 		*dst = rlePixel;
100 		dst++;
101 	} while (width);
102 
103 	return dst;
104 }
105 
106 /**
107  * @brief Write the pixel data to the PCX file
108  * @param buf Buffer
109  * @return True if successful, else false
110  */
CapturePix(CelOutputBuffer buf,std::ofstream * out)111 static bool CapturePix(CelOutputBuffer buf, std::ofstream *out)
112 {
113 	int width = buf.w();
114 	int height = buf.h();
115 	BYTE *pBuffer = (BYTE *)DiabloAllocPtr(2 * width);
116 	BYTE *pixels = buf.begin();
117 	while (height--) {
118 		const BYTE *pBufferEnd = CaptureEnc(pixels, pBuffer, width);
119 		pixels += buf.pitch();
120 		out->write(reinterpret_cast<const char *>(pBuffer), pBufferEnd - pBuffer);
121 		if (out->fail())
122 			return false;
123 	}
124 	mem_free_dbg(pBuffer);
125 	return true;
126 }
127 
128 /**
129  * Returns a pointer because in GCC < 5 ofstream itself is not moveable due to a bug.
130  */
CaptureFile(std::string * dst_path)131 static std::ofstream *CaptureFile(std::string *dst_path)
132 {
133 	char filename[sizeof("screen00.PCX") / sizeof(char)];
134 	for (int i = 0; i <= 99; ++i) {
135 		snprintf(filename, sizeof(filename) / sizeof(char), "screen%02d.PCX", i);
136 		*dst_path = GetPrefPath() + filename;
137 		if (!FileExists(dst_path->c_str())) {
138 			return new std::ofstream(*dst_path, std::ios::binary | std::ios::trunc);
139 		}
140 	}
141 	return NULL;
142 }
143 
144 /**
145  * @brief Make a red version of the given palette and apply it to the screen.
146  */
RedPalette()147 static void RedPalette()
148 {
149 	for (int i = 0; i < 255; i++) {
150 		system_palette[i].g = 0;
151 		system_palette[i].b = 0;
152 	}
153 	palette_update();
154 	SDL_Rect SrcRect = {
155 		BUFFER_BORDER_LEFT,
156 		BUFFER_BORDER_TOP,
157 		gnScreenWidth,
158 		gnScreenHeight,
159 	};
160 	BltFast(&SrcRect, NULL);
161 	RenderPresent();
162 }
163 
164 /**
165  * @brief Save the current screen to a screen??.PCX (00-99) in file if available, then make the screen red for 200ms.
166 
167  */
CaptureScreen()168 void CaptureScreen()
169 {
170 	SDL_Color palette[256];
171 	std::string FileName;
172 	BOOL success;
173 
174 	std::ofstream *out_stream = CaptureFile(&FileName);
175 	if (out_stream == NULL)
176 		return;
177 	DrawAndBlit();
178 	PaletteGetEntries(256, palette);
179 	RedPalette();
180 
181 	lock_buf(2);
182 	CelOutputBuffer buf = GlobalBackBuffer();
183 	success = CaptureHdr(buf.w(), buf.h(), out_stream);
184 	if (success) {
185 		success = CapturePix(buf, out_stream);
186 	}
187 	if (success) {
188 		success = CapturePal(palette, out_stream);
189 	}
190 	unlock_buf(2);
191 	out_stream->close();
192 
193 	if (!success) {
194 		SDL_Log("Failed to save screenshot at %s", FileName.c_str());
195 		RemoveFile(FileName.c_str());
196 	} else {
197 		SDL_Log("Screenshot saved at %s", FileName.c_str());
198 	}
199 	SDL_Delay(300);
200 	for (int i = 0; i < 256; i++) {
201 		system_palette[i] = palette[i];
202 	}
203 	palette_update();
204 	force_redraw = 255;
205 	delete out_stream;
206 }
207 
208 DEVILUTION_END_NAMESPACE
209