You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
4.7 KiB
206 lines
4.7 KiB
/** |
|
* @file capture.cpp |
|
* |
|
* Implementation of the screenshot function. |
|
*/ |
|
#include <cstdint> |
|
#include <fstream> |
|
|
|
#include "DiabloUI/diabloui.h" |
|
#include "dx.h" |
|
#include "palette.h" |
|
#include "storm/storm.h" |
|
#include "utils/file_util.h" |
|
#include "utils/log.hpp" |
|
#include "utils/paths.h" |
|
#include "utils/ui_fwd.h" |
|
|
|
namespace devilution { |
|
namespace { |
|
|
|
/** |
|
* @brief Write the PCX-file header |
|
* @param width Image width |
|
* @param height Image height |
|
* @param out File stream to write to |
|
* @return True on success |
|
*/ |
|
static bool CaptureHdr(int16_t width, int16_t height, std::ofstream *out) |
|
{ |
|
PCXHeader buffer; |
|
|
|
memset(&buffer, 0, sizeof(buffer)); |
|
buffer.Manufacturer = 10; |
|
buffer.Version = 5; |
|
buffer.Encoding = 1; |
|
buffer.BitsPerPixel = 8; |
|
buffer.Xmax = SDL_SwapLE16(width - 1); |
|
buffer.Ymax = SDL_SwapLE16(height - 1); |
|
buffer.HDpi = SDL_SwapLE16(width); |
|
buffer.VDpi = SDL_SwapLE16(height); |
|
buffer.NPlanes = 1; |
|
buffer.BytesPerLine = SDL_SwapLE16(width); |
|
|
|
out->write(reinterpret_cast<const char *>(&buffer), sizeof(buffer)); |
|
return !out->fail(); |
|
} |
|
|
|
/** |
|
* @brief Write the current in-game palette to the PCX file |
|
* @param palette Current palette |
|
* @param out File stream for the PCX file. |
|
* @return True if successful, else false |
|
*/ |
|
static bool CapturePal(SDL_Color *palette, std::ofstream *out) |
|
{ |
|
BYTE pcxPalette[1 + 256 * 3]; |
|
|
|
pcxPalette[0] = 12; |
|
for (int i = 0; i < 256; i++) { |
|
pcxPalette[1 + 3 * i + 0] = palette[i].r; |
|
pcxPalette[1 + 3 * i + 1] = palette[i].g; |
|
pcxPalette[1 + 3 * i + 2] = palette[i].b; |
|
} |
|
|
|
out->write(reinterpret_cast<const char *>(pcxPalette), sizeof(pcxPalette)); |
|
return !out->fail(); |
|
} |
|
|
|
/** |
|
* @brief RLE compress the pixel data |
|
* @param src Raw pixel buffer |
|
* @param dst Output buffer |
|
* @param width Width of pixel buffer |
|
|
|
* @return Output buffer |
|
*/ |
|
static BYTE *CaptureEnc(BYTE *src, BYTE *dst, int width) |
|
{ |
|
int rleLength; |
|
|
|
do { |
|
BYTE rlePixel = *src; |
|
src++; |
|
rleLength = 1; |
|
|
|
width--; |
|
|
|
while (rlePixel == *src) { |
|
if (rleLength >= 63) |
|
break; |
|
if (width == 0) |
|
break; |
|
rleLength++; |
|
|
|
width--; |
|
src++; |
|
} |
|
|
|
if (rleLength > 1 || rlePixel > 0xBF) { |
|
*dst = rleLength | 0xC0; |
|
dst++; |
|
} |
|
|
|
*dst = rlePixel; |
|
dst++; |
|
} while (width > 0); |
|
|
|
return dst; |
|
} |
|
|
|
/** |
|
* @brief Write the pixel data to the PCX file |
|
* @param buf Buffer |
|
* @return True if successful, else false |
|
*/ |
|
static bool CapturePix(const Surface &buf, std::ofstream *out) |
|
{ |
|
int width = buf.w(); |
|
std::unique_ptr<BYTE[]> pBuffer { new BYTE[2 * width] }; |
|
BYTE *pixels = buf.begin(); |
|
for (int height = buf.h(); height > 0; height--) { |
|
const BYTE *pBufferEnd = CaptureEnc(pixels, pBuffer.get(), width); |
|
pixels += buf.pitch(); |
|
out->write(reinterpret_cast<const char *>(pBuffer.get()), pBufferEnd - pBuffer.get()); |
|
if (out->fail()) |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
/** |
|
* Returns a pointer because in GCC < 5 ofstream itself is not moveable due to a bug. |
|
*/ |
|
static std::ofstream *CaptureFile(std::string *dstPath) |
|
{ |
|
char filename[sizeof("screen00.PCX") / sizeof(char)]; |
|
for (int i = 0; i <= 99; ++i) { |
|
snprintf(filename, sizeof(filename) / sizeof(char), "screen%02d.PCX", i); |
|
*dstPath = paths::PrefPath() + filename; |
|
if (!FileExists(dstPath->c_str())) { |
|
return new std::ofstream(*dstPath, std::ios::binary | std::ios::trunc); |
|
} |
|
} |
|
return nullptr; |
|
} |
|
|
|
/** |
|
* @brief Make a red version of the given palette and apply it to the screen. |
|
*/ |
|
static void RedPalette() |
|
{ |
|
for (int i = 0; i < 255; i++) { |
|
system_palette[i].g = 0; |
|
system_palette[i].b = 0; |
|
} |
|
palette_update(); |
|
BltFast(nullptr, nullptr); |
|
RenderPresent(); |
|
} |
|
} |
|
|
|
/** |
|
* @brief Save the current screen to a screen??.PCX (00-99) in file if available, then make the screen red for 200ms. |
|
|
|
*/ |
|
void CaptureScreen() |
|
{ |
|
SDL_Color palette[256]; |
|
std::string fileName; |
|
bool success; |
|
|
|
std::ofstream *outStream = CaptureFile(&fileName); |
|
if (outStream == nullptr) |
|
return; |
|
DrawAndBlit(); |
|
PaletteGetEntries(256, palette); |
|
RedPalette(); |
|
|
|
lock_buf(2); |
|
const Surface &buf = GlobalBackBuffer(); |
|
success = CaptureHdr(buf.w(), buf.h(), outStream); |
|
if (success) { |
|
success = CapturePix(buf, outStream); |
|
} |
|
if (success) { |
|
success = CapturePal(palette, outStream); |
|
} |
|
unlock_buf(2); |
|
outStream->close(); |
|
|
|
if (!success) { |
|
Log("Failed to save screenshot at {}", fileName); |
|
RemoveFile(fileName.c_str()); |
|
} else { |
|
Log("Screenshot saved at {}", fileName); |
|
} |
|
SDL_Delay(300); |
|
for (int i = 0; i < 256; i++) { |
|
system_palette[i] = palette[i]; |
|
} |
|
palette_update(); |
|
force_redraw = 255; |
|
delete outStream; |
|
} |
|
|
|
} // namespace devilution
|
|
|