rodecomp/Source/Render/ScreenshotDX7.cpp
2026-10-24 00:35:19 +00:00

280 lines
10 KiB
C++

#include "Render/DeviceOwnerDX7.h"
#include "Render/RendererBackBuffer.h"
#include "Render/RendererDX7Operations.h"
#include "Resource/BitmapImage.h"
#include "Resource/BitmapResource.h"
#include "Resource/IJL15Runtime.h"
#include "Resource/ResourceMgr.h"
#include "Resource/TextureNames.h"
#include <array>
#include <cstdio>
#include <cstdlib>
#include <cstring>
namespace {
std::uint16_t PackedColor(const DeviceOwnerDX7& owner, unsigned color) {
const auto channel = [](unsigned value, const DeviceOwnerDX7::Channel& field) {
return static_cast<std::uint16_t>((value >> (field.expansion & 31)) << (field.shift & 31));
};
return static_cast<std::uint16_t>(
channel((color >> 16) & 255, owner.red) + channel((color >> 8) & 255, owner.green) +
channel(color & 255, owner.blue)
);
}
unsigned char ExpandedChannel(unsigned value, DWORD mask, const DeviceOwnerDX7::Channel& field) {
const auto component = static_cast<unsigned char>((value & mask) >> (field.shift & 31));
return static_cast<unsigned char>(static_cast<unsigned>(component) << (field.expansion & 31));
}
}
// OFFSET: 0x0055D860
// FUNCTION: fn_02109
bool RendererDX7Operations::SaveBitmap(
const char* filename,
const BitmapImage& overlay,
int width,
int height,
RendererBackBuffer& backBuffer
) {
if (!backBuffer.Lock(this->owner)) {
return false;
}
std::FILE* file = std::fopen(filename, "wb");
if (!file) {
// The original leaves the render target locked when fopen fails.
return false;
}
BITMAPFILEHEADER header{};
header.bfType = 0x4D42;
header.bfSize = 3u * (static_cast<unsigned>(width) * static_cast<unsigned>(height) + 18u);
header.bfOffBits = 54;
std::fwrite(&header, sizeof(header), 1, file);
BITMAPINFOHEADER information{};
information.biSize = sizeof(information);
information.biWidth = width;
information.biHeight = height;
information.biPlanes = 1;
information.biBitCount = 24;
std::fwrite(&information, sizeof(information), 1, file);
const auto red = this->owner.red;
const auto green = this->owner.green;
const auto blue = this->owner.blue;
const auto redMask = this->owner.redMask;
const auto greenMask = this->owner.greenMask;
const auto blueMask = this->owner.blueMask;
const auto transparent = PackedColor(this->owner, 0xFF00FF);
std::array<unsigned char, 10800> row;
for (int y = height - 1; y >= 0; --y) {
const auto* source = static_cast<const unsigned char*>(backBuffer.pixels) + y * backBuffer.pitch;
for (int x = 0; x < width; ++x) {
unsigned color = 0;
const int overlayX = x + overlay.width - width;
const int overlayY = y + overlay.height - height;
bool useOverlay = overlayX >= 0 && overlayX < overlay.width && overlayY >= 0 && overlayY < overlay.height;
if (useOverlay) {
color = overlay.pixels[overlayX + overlayY * overlay.width];
useOverlay = backBuffer.bitsPerPixel == 16 ? PackedColor(this->owner, color) != transparent
: (color & 0xFFFFFF) != 0xFF00FF;
}
if (!useOverlay) {
if (backBuffer.bitsPerPixel == 16) {
std::uint16_t pixel;
std::memcpy(&pixel, source + 2 * x, sizeof(pixel));
color = (static_cast<unsigned>(ExpandedChannel(pixel, redMask, red)) << 16) |
(static_cast<unsigned>(ExpandedChannel(pixel, greenMask, green)) << 8) |
ExpandedChannel(pixel, blueMask, blue);
} else {
std::memcpy(&color, source + 4 * x, sizeof(color));
}
}
row[3 * x] = static_cast<unsigned char>(color);
row[3 * x + 1] = static_cast<unsigned char>(color >> 8);
row[3 * x + 2] = static_cast<unsigned char>(color >> 16);
}
// Original writes tightly packed BGR rows without BMP alignment padding.
std::fwrite(row.data(), 3u * static_cast<unsigned>(width), 1, file);
}
backBuffer.Unlock(this->owner);
std::fclose(file);
return true;
}
// OFFSET: 0x0055DDF0
// FUNCTION: fn_02110
bool RendererDX7Operations::SaveJpeg(
const char* filename,
const BitmapImage& overlay,
int width,
int height,
RendererBackBuffer& backBuffer,
IJL15Runtime& runtime
) {
if (!backBuffer.Lock(this->owner)) {
return false;
}
const unsigned size = 3u * static_cast<unsigned>(width) * static_cast<unsigned>(height);
auto* pixels = static_cast<unsigned char*>(std::malloc(size));
const auto red = this->owner.red;
const auto green = this->owner.green;
const auto blue = this->owner.blue;
const auto redMask = this->owner.redMask;
const auto greenMask = this->owner.greenMask;
const auto blueMask = this->owner.blueMask;
const auto transparent = PackedColor(this->owner, 0xFF00FF);
for (int y = height - 1; y >= 0; --y) {
const auto* source = static_cast<const unsigned char*>(backBuffer.pixels) + y * backBuffer.pitch;
auto* row = pixels + 3u * static_cast<unsigned>(y) * static_cast<unsigned>(width);
for (int x = 0; x < width; ++x) {
unsigned color = 0;
const int overlayX = x + overlay.width - width;
const int overlayY = y + overlay.height - height;
bool useOverlay = overlayX >= 0 && overlayX < overlay.width && overlayY >= 0 && overlayY < overlay.height;
if (useOverlay) {
color = overlay.pixels[overlayX + overlayY * overlay.width];
useOverlay = backBuffer.bitsPerPixel == 16 ? PackedColor(this->owner, color) != transparent
: (color & 0xFFFFFF) != 0xFF00FF;
}
if (!useOverlay) {
if (backBuffer.bitsPerPixel == 16) {
std::uint16_t pixel;
std::memcpy(&pixel, source + 2 * x, sizeof(pixel));
color = (static_cast<unsigned>(ExpandedChannel(pixel, redMask, red)) << 16) |
(static_cast<unsigned>(ExpandedChannel(pixel, greenMask, green)) << 8) |
ExpandedChannel(pixel, blueMask, blue);
} else {
std::memcpy(&color, source + 4 * x, sizeof(color));
}
}
row[3 * x] = static_cast<unsigned char>(color);
row[3 * x + 1] = static_cast<unsigned char>(color >> 8);
row[3 * x + 2] = static_cast<unsigned char>(color >> 16);
}
}
backBuffer.Unlock(this->owner);
IJL15Properties properties{};
if (runtime.initialize(&properties) != 0) {
// Original does not free the temporary pixels on initialization failure.
return false;
}
properties.dibWidth = width;
properties.dibHeight = height;
properties.dibBytes = pixels;
properties.dibPadding = ((3u * static_cast<unsigned>(width) + 3u) & ~3u) - 3u * static_cast<unsigned>(width);
properties.jpegFile = filename;
properties.dibChannels = 3;
properties.dibColor = 2;
properties.jpegWidth = width;
properties.jpegHeight = height;
const int status = runtime.write(&properties, 8);
std::free(pixels);
// The original omits ijlFree on both encoder success and write failure.
return status == 0;
}
// OFFSET: 0x0055E340
// FUNCTION: fn_02111
bool RendererDX7Operations::SaveLogo(
std::string filename,
ResourceMgr& resources,
TextureNames& names,
IJL15Runtime& runtime
) {
auto* logo = static_cast<CBitmapRes*>(resources.Load(names.Resolve("scr_logo.bmp")));
if (!logo) {
return false;
}
DDSURFACEDESC2 locked{};
locked.dwSize = sizeof(locked);
if (FAILED(this->owner.renderTarget->Lock(nullptr, &locked, DDLOCK_WAIT, nullptr))) {
return false;
}
const int pixelBytes = locked.lPitch / static_cast<int>(locked.dwWidth);
const int paddedWidth = locked.lPitch / pixelBytes;
unsigned remaining = 3u * static_cast<unsigned>(paddedWidth) * locked.dwHeight;
auto* pixels = static_cast<unsigned char*>(std::malloc(remaining));
auto* destination = pixels;
const auto& image = logo->image;
for (unsigned y = 0; y < locked.dwHeight; ++y) {
const auto* source = static_cast<const unsigned char*>(locked.lpSurface) + y * locked.lPitch;
for (unsigned x = 0; x < locked.dwWidth && static_cast<int>(remaining) > 0; ++x) {
const unsigned overlayX = x + static_cast<unsigned>(image.width) - locked.dwWidth;
const unsigned overlayY = y + static_cast<unsigned>(image.height) - locked.dwHeight;
const bool inOverlay = x >= locked.dwWidth - static_cast<unsigned>(image.width) &&
y >= locked.dwHeight - static_cast<unsigned>(image.height);
unsigned color = inOverlay ? image.pixels[overlayX + overlayY * image.width] : 0xFF00FF;
if (!inOverlay || (color & 0xFFFFFF) == 0xFF00FF) {
if (locked.ddpfPixelFormat.dwRGBBitCount == 16) {
std::uint16_t value;
std::memcpy(&value, source + x * pixelBytes, 2);
color = (static_cast<unsigned>(ExpandedChannel(value, this->owner.redMask, this->owner.red)) << 16) |
(static_cast<unsigned>(ExpandedChannel(value, this->owner.greenMask, this->owner.green)) << 8) |
ExpandedChannel(value, this->owner.blueMask, this->owner.blue);
} else {
std::memcpy(&color, source + x * pixelBytes, 4);
}
}
destination[0] = static_cast<unsigned char>(color);
destination[1] = static_cast<unsigned char>(color >> 8);
destination[2] = static_cast<unsigned char>(color >> 16);
destination += 3;
remaining -= 3;
}
destination = pixels + 3u * static_cast<unsigned>(paddedWidth) * (y + 1);
remaining = 3u * static_cast<unsigned>(paddedWidth) * (locked.dwHeight - y - 1);
}
this->owner.renderTarget->Unlock(nullptr);
IJL15Properties properties{};
if (runtime.initialize(&properties) != 0) {
std::free(pixels);
return false;
}
properties.dibBytes = pixels;
properties.dibWidth = paddedWidth;
properties.dibHeight = locked.dwHeight;
properties.dibPadding =
((3u * static_cast<unsigned>(paddedWidth) + 3u) & ~3u) - 3u * static_cast<unsigned>(paddedWidth);
properties.jpegFile = filename.c_str();
properties.dibChannels = 3;
properties.dibColor = 2;
properties.jpegWidth = locked.dwWidth;
properties.jpegHeight = locked.dwHeight;
const int status = runtime.write(&properties, 8);
std::free(pixels);
// The bitmap stays cache-owned; the original does not remove/release it or
// invoke ijlFree after screenshot encoding.
return status == 0;
}