#include "PngScreenshotWriter.h" #include #include #include #include #include namespace { std::string HResultToString(HRESULT hr) { std::ostringstream stream; stream << "HRESULT 0x" << std::hex << static_cast(hr); return stream.str(); } bool WritePngFile( const std::filesystem::path& outputPath, unsigned width, unsigned height, const std::vector& bgraPixels, std::string& error) { if (width == 0 || height == 0 || bgraPixels.size() < static_cast(width) * height * 4) { error = "Invalid screenshot dimensions or pixel buffer."; return false; } HRESULT initializeResult = CoInitializeEx(nullptr, COINIT_MULTITHREADED); const bool shouldUninitialize = SUCCEEDED(initializeResult); if (FAILED(initializeResult) && initializeResult != RPC_E_CHANGED_MODE) { error = "CoInitializeEx failed: " + HResultToString(initializeResult); return false; } CComPtr factory; HRESULT result = CoCreateInstance( CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)); if (FAILED(result)) { error = "Could not create WIC imaging factory: " + HResultToString(result); if (shouldUninitialize) CoUninitialize(); return false; } CComPtr stream; result = factory->CreateStream(&stream); if (SUCCEEDED(result)) result = stream->InitializeFromFilename(outputPath.wstring().c_str(), GENERIC_WRITE); if (FAILED(result)) { error = "Could not open screenshot output file: " + HResultToString(result); if (shouldUninitialize) CoUninitialize(); return false; } CComPtr encoder; result = factory->CreateEncoder(GUID_ContainerFormatPng, nullptr, &encoder); if (SUCCEEDED(result)) result = encoder->Initialize(stream, WICBitmapEncoderNoCache); if (FAILED(result)) { error = "Could not initialize PNG encoder: " + HResultToString(result); if (shouldUninitialize) CoUninitialize(); return false; } CComPtr frame; CComPtr propertyBag; result = encoder->CreateNewFrame(&frame, &propertyBag); if (SUCCEEDED(result)) result = frame->Initialize(propertyBag); if (SUCCEEDED(result)) result = frame->SetSize(width, height); WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA; if (SUCCEEDED(result)) result = frame->SetPixelFormat(&pixelFormat); if (SUCCEEDED(result) && pixelFormat != GUID_WICPixelFormat32bppBGRA) { error = "PNG encoder did not accept BGRA pixel format."; result = E_FAIL; } const UINT stride = width * 4; const UINT imageSize = stride * height; if (SUCCEEDED(result)) result = frame->WritePixels(height, stride, imageSize, const_cast(bgraPixels.data())); if (SUCCEEDED(result)) result = frame->Commit(); if (SUCCEEDED(result)) result = encoder->Commit(); if (shouldUninitialize) CoUninitialize(); if (FAILED(result)) { error = "Could not write screenshot PNG: " + HResultToString(result); std::error_code ignored; std::filesystem::remove(outputPath, ignored); return false; } return true; } } void WritePngFileAsync( const std::filesystem::path& outputPath, unsigned width, unsigned height, std::vector rgbaPixels) { std::thread( [outputPath, width, height, pixels = std::move(rgbaPixels)]() mutable { for (std::size_t index = 0; index + 3 < pixels.size(); index += 4) std::swap(pixels[index], pixels[index + 2]); std::string error; if (!WritePngFile(outputPath, width, height, pixels, error)) OutputDebugStringA(("Screenshot write failed: " + error + "\n").c_str()); else OutputDebugStringA(("Screenshot written: " + outputPath.string() + "\n").c_str()); }).detach(); }