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