Files
video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/PngScreenshotWriter.cpp
Aiden 05d0bcbedd
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 1m35s
CI / Windows Release Package (push) Successful in 2m17s
PNG writer
2026-05-08 15:33:40 +10:00

134 lines
3.5 KiB
C++

#include "PngScreenshotWriter.h"
#include <windows.h>
#include <wincodec.h>
#include <atlbase.h>
#include <sstream>
#include <thread>
namespace
{
std::string HResultToString(HRESULT hr)
{
std::ostringstream stream;
stream << "HRESULT 0x" << std::hex << static_cast<unsigned long>(hr);
return stream.str();
}
bool WritePngFile(
const std::filesystem::path& outputPath,
unsigned width,
unsigned height,
const std::vector<unsigned char>& rgbaPixels,
std::string& error)
{
if (width == 0 || height == 0 || rgbaPixels.size() < static_cast<std::size_t>(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<IWICImagingFactory> 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<IWICStream> 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<IWICBitmapEncoder> 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<IWICBitmapFrameEncode> frame;
CComPtr<IPropertyBag2> propertyBag;
result = encoder->CreateNewFrame(&frame, &propertyBag);
if (SUCCEEDED(result))
result = frame->Initialize(propertyBag);
if (SUCCEEDED(result))
result = frame->SetSize(width, height);
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppRGBA;
if (SUCCEEDED(result))
result = frame->SetPixelFormat(&pixelFormat);
if (SUCCEEDED(result) && pixelFormat != GUID_WICPixelFormat32bppRGBA)
{
error = "PNG encoder did not accept RGBA 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<BYTE*>(rgbaPixels.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);
return false;
}
return true;
}
}
void WritePngFileAsync(
const std::filesystem::path& outputPath,
unsigned width,
unsigned height,
std::vector<unsigned char> rgbaPixels)
{
std::thread(
[outputPath, width, height, pixels = std::move(rgbaPixels)]() mutable
{
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();
}