diff --git a/UI/screenshot-obj.hpp b/UI/screenshot-obj.hpp index df7c148c5..ccb68c8e3 100644 --- a/UI/screenshot-obj.hpp +++ b/UI/screenshot-obj.hpp @@ -36,6 +36,7 @@ public: OBSWeakSource weakSource; std::string path; QImage image; + std::vector half_bytes; uint32_t cx; uint32_t cy; std::thread th; diff --git a/UI/window-basic-main-screenshot.cpp b/UI/window-basic-main-screenshot.cpp index 7695491e0..306d41ca0 100644 --- a/UI/window-basic-main-screenshot.cpp +++ b/UI/window-basic-main-screenshot.cpp @@ -19,6 +19,13 @@ #include "screenshot-obj.hpp" #include "qt-wrappers.hpp" +#ifdef _WIN32 +#include +#include +#include +#pragma comment(lib, "windowscodecs.lib") +#endif + static void ScreenshotTick(void *param, float); /* ========================================================================= */ @@ -71,11 +78,23 @@ void ScreenshotObj::Screenshot() return; } - texrender = gs_texrender_create(GS_RGBA, GS_ZS_NONE); - stagesurf = gs_stagesurface_create(cx, cy, GS_RGBA); +#ifdef _WIN32 + enum gs_color_space space = + obs_source_get_color_space(source, 0, nullptr); + if (space == GS_CS_709_EXTENDED) { + /* Convert for JXR */ + space = GS_CS_709_SCRGB; + } +#else + /* Tonemap to SDR if HDR */ + const enum gs_color_space space = GS_CS_SRGB; +#endif + const enum gs_color_format format = gs_get_format_from_space(space); - gs_texrender_reset(texrender); - if (gs_texrender_begin(texrender, cx, cy)) { + texrender = gs_texrender_create(format, GS_ZS_NONE); + stagesurf = gs_stagesurface_create(cx, cy, format); + + if (gs_texrender_begin_with_color_space(texrender, cx, cy, space)) { vec4 zero; vec4_zero(&zero); @@ -108,13 +127,26 @@ void ScreenshotObj::Copy() uint8_t *videoData = nullptr; uint32_t videoLinesize = 0; - image = QImage(cx, cy, QImage::Format::Format_RGBX8888); - if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) { - int linesize = image.bytesPerLine(); - for (int y = 0; y < (int)cy; y++) - memcpy(image.scanLine(y), - videoData + (y * videoLinesize), linesize); + if (gs_stagesurface_get_color_format(stagesurf) == GS_RGBA16F) { + const uint32_t linesize = cx * 8; + half_bytes.reserve(cx * cy * 8); + + for (uint32_t y = 0; y < cy; y++) { + const uint8_t *const line = + videoData + (y * videoLinesize); + half_bytes.insert(half_bytes.end(), line, + line + linesize); + } + } else { + image = QImage(cx, cy, QImage::Format::Format_RGBX8888); + + int linesize = image.bytesPerLine(); + for (int y = 0; y < (int)cy; y++) + memcpy(image.scanLine(y), + videoData + (y * videoLinesize), + linesize); + } gs_stagesurface_unmap(stagesurf); } @@ -143,17 +175,122 @@ void ScreenshotObj::Save() bool overwriteIfExists = config_get_bool(config, "Output", "OverwriteIfExists"); + const char *ext = half_bytes.empty() ? "png" : "jxr"; path = GetOutputFilename( - rec_path, "png", noSpace, overwriteIfExists, + rec_path, ext, noSpace, overwriteIfExists, GetFormatString(filenameFormat, "Screenshot", nullptr).c_str()); th = std::thread([this] { MuxAndFinish(); }); } +#ifdef _WIN32 +static HRESULT SaveJxrImage(LPCWSTR path, uint8_t *pixels, uint32_t cx, + uint32_t cy, IWICBitmapFrameEncode *frameEncode, + IPropertyBag2 *options) +{ + wchar_t lossless[] = L"Lossless"; + PROPBAG2 bag = {}; + bag.pstrName = lossless; + VARIANT value = {}; + value.vt = VT_BOOL; + value.bVal = TRUE; + HRESULT hr = options->Write(1, &bag, &value); + if (FAILED(hr)) + return hr; + + hr = frameEncode->Initialize(options); + if (FAILED(hr)) + return hr; + + hr = frameEncode->SetSize(cx, cy); + if (FAILED(hr)) + return hr; + + hr = frameEncode->SetResolution(72, 72); + if (FAILED(hr)) + return hr; + + WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat64bppRGBAHalf; + hr = frameEncode->SetPixelFormat(&pixelFormat); + if (FAILED(hr)) + return hr; + + if (memcmp(&pixelFormat, &GUID_WICPixelFormat64bppRGBAHalf, + sizeof(WICPixelFormatGUID)) != 0) + return E_FAIL; + + hr = frameEncode->WritePixels(cy, cx * 8, cx * cy * 8, pixels); + if (FAILED(hr)) + return hr; + + hr = frameEncode->Commit(); + if (FAILED(hr)) + return hr; + + return S_OK; +} + +static HRESULT SaveJxr(LPCWSTR path, uint8_t *pixels, uint32_t cx, uint32_t cy) +{ + Microsoft::WRL::ComPtr factory; + HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(factory.GetAddressOf())); + if (FAILED(hr)) + return hr; + + Microsoft::WRL::ComPtr stream; + hr = factory->CreateStream(stream.GetAddressOf()); + if (FAILED(hr)) + return hr; + + hr = stream->InitializeFromFilename(path, GENERIC_WRITE); + if (FAILED(hr)) + return hr; + + Microsoft::WRL::ComPtr encoder = NULL; + hr = factory->CreateEncoder(GUID_ContainerFormatWmp, NULL, + encoder.GetAddressOf()); + if (FAILED(hr)) + return hr; + + hr = encoder->Initialize(stream.Get(), WICBitmapEncoderNoCache); + if (FAILED(hr)) + return hr; + + Microsoft::WRL::ComPtr frameEncode; + Microsoft::WRL::ComPtr options; + hr = encoder->CreateNewFrame(frameEncode.GetAddressOf(), + options.GetAddressOf()); + if (FAILED(hr)) + return hr; + + hr = SaveJxrImage(path, pixels, cx, cy, frameEncode.Get(), + options.Get()); + if (FAILED(hr)) + return hr; + + encoder->Commit(); + return S_OK; +} +#endif // #ifdef _WIN32 + void ScreenshotObj::MuxAndFinish() { - image.save(QT_UTF8(path.c_str())); - blog(LOG_INFO, "Saved screenshot to '%s'", path.c_str()); + if (half_bytes.empty()) { + image.save(QT_UTF8(path.c_str())); + blog(LOG_INFO, "Saved screenshot to '%s'", path.c_str()); + } else { +#ifdef _WIN32 + wchar_t *path_w = nullptr; + os_utf8_to_wcs_ptr(path.c_str(), 0, &path_w); + if (path_w) { + SaveJxr(path_w, half_bytes.data(), cx, cy); + bfree(path_w); + } +#endif // #ifdef _WIN32 + } + deleteLater(); }