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.
204 lines
4.6 KiB
204 lines
4.6 KiB
/** |
|
* @file capture.cpp |
|
* |
|
* Implementation of the screenshot function. |
|
*/ |
|
#include <cstdint> |
|
#include <cstdio> |
|
#include <ctime> |
|
|
|
#include <fmt/format.h> |
|
|
|
#include "DiabloUI/diabloui.h" |
|
#include "engine/backbuffer_state.hpp" |
|
#include "engine/dx.h" |
|
#include "engine/palette.h" |
|
#include "utils/file_util.h" |
|
#include "utils/log.hpp" |
|
#include "utils/paths.h" |
|
#include "utils/pcx.hpp" |
|
#include "utils/str_cat.hpp" |
|
#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 |
|
*/ |
|
bool CaptureHdr(int16_t width, int16_t height, FILE *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); |
|
|
|
return std::fwrite(&buffer, sizeof(buffer), 1, out) == 1; |
|
} |
|
|
|
/** |
|
* @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 |
|
*/ |
|
bool CapturePal(SDL_Color *palette, FILE *out) |
|
{ |
|
uint8_t 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; |
|
} |
|
|
|
return std::fwrite(pcxPalette, sizeof(pcxPalette), 1, out) == 1; |
|
} |
|
|
|
/** |
|
* @brief RLE compress the pixel data |
|
* @param src Raw pixel buffer |
|
* @param dst Output buffer |
|
* @param width Width of pixel buffer |
|
|
|
* @return Output buffer |
|
*/ |
|
uint8_t *CaptureEnc(uint8_t *src, uint8_t *dst, int width) |
|
{ |
|
int rleLength; |
|
|
|
do { |
|
uint8_t 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 Pixel data |
|
* @param out File stream for the PCX file. |
|
* @return True if successful, else false |
|
*/ |
|
bool CapturePix(const Surface &buf, FILE *out) |
|
{ |
|
int width = buf.w(); |
|
std::unique_ptr<uint8_t[]> pBuffer { new uint8_t[2 * width] }; |
|
uint8_t *pixels = buf.begin(); |
|
for (int height = buf.h(); height > 0; height--) { |
|
const uint8_t *pBufferEnd = CaptureEnc(pixels, pBuffer.get(), width); |
|
pixels += buf.pitch(); |
|
if (std::fwrite(pBuffer.get(), pBufferEnd - pBuffer.get(), 1, out) != 1) |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
FILE *CaptureFile(std::string *dstPath) |
|
{ |
|
const std::time_t tt = std::time(nullptr); |
|
const std::tm *tm = std::localtime(&tt); |
|
const std::string filename = tm != nullptr |
|
? fmt::format("Screenshot from {:04}-{:02}-{:02} {:02}-{:02}-{:02}", |
|
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec) |
|
: "Screenshot"; |
|
*dstPath = StrCat(paths::PrefPath(), filename, ".pcx"); |
|
int i = 0; |
|
while (FileExists(dstPath->c_str())) { |
|
i++; |
|
*dstPath = StrCat(paths::PrefPath(), filename, "-", i, ".pcx"); |
|
} |
|
return OpenFile(dstPath->c_str(), "wb"); |
|
} |
|
|
|
/** |
|
* @brief Make a red version of the given palette and apply it to the screen. |
|
*/ |
|
void RedPalette() |
|
{ |
|
for (int i = 0; i < 256; i++) { |
|
system_palette[i].g = 0; |
|
system_palette[i].b = 0; |
|
} |
|
palette_update(); |
|
BltFast(nullptr, nullptr); |
|
RenderPresent(); |
|
} |
|
} // namespace |
|
|
|
void CaptureScreen() |
|
{ |
|
SDL_Color palette[256]; |
|
std::string fileName; |
|
bool success; |
|
|
|
FILE *outStream = CaptureFile(&fileName); |
|
if (outStream == nullptr) |
|
return; |
|
DrawAndBlit(); |
|
PaletteGetEntries(256, palette); |
|
RedPalette(); |
|
|
|
const Surface &buf = GlobalBackBuffer(); |
|
success = CaptureHdr(buf.w(), buf.h(), outStream); |
|
if (success) { |
|
success = CapturePix(buf, outStream); |
|
} |
|
if (success) { |
|
success = CapturePal(palette, outStream); |
|
} |
|
std::fclose(outStream); |
|
|
|
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(); |
|
RedrawEverything(); |
|
} |
|
|
|
} // namespace devilution
|
|
|