/****************************************************************************** Copyright (C) 2020 by Hugh Bailey This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ #include "window-basic-main.hpp" #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); /* ========================================================================= */ ScreenshotObj::ScreenshotObj(obs_source_t *source) : weakSource(OBSGetWeakRef(source)) { obs_add_tick_callback(ScreenshotTick, this); } ScreenshotObj::~ScreenshotObj() { obs_enter_graphics(); gs_stagesurface_destroy(stagesurf); gs_texrender_destroy(texrender); obs_leave_graphics(); obs_remove_tick_callback(ScreenshotTick, this); if (th.joinable()) { th.join(); if (cx && cy) { OBSBasic *main = OBSBasic::Get(); main->ShowStatusBarMessage( QTStr("Basic.StatusBar.ScreenshotSavedTo") .arg(QT_UTF8(path.c_str()))); } } } void ScreenshotObj::Screenshot() { OBSSource source = OBSGetStrongRef(weakSource); if (source) { cx = obs_source_get_base_width(source); cy = obs_source_get_base_height(source); } else { obs_video_info ovi; obs_get_video_info(&ovi); cx = ovi.base_width; cy = ovi.base_height; } if (!cx || !cy) { blog(LOG_WARNING, "Cannot screenshot, invalid target size"); obs_remove_tick_callback(ScreenshotTick, this); deleteLater(); return; } #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); 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); gs_clear(GS_CLEAR_COLOR, &zero, 0.0f, 0); gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f); gs_blend_state_push(); gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); if (source) { obs_source_inc_showing(source); obs_source_video_render(source); obs_source_dec_showing(source); } else { obs_render_main_texture(); } gs_blend_state_pop(); gs_texrender_end(texrender); } } void ScreenshotObj::Download() { gs_stage_texture(stagesurf, gs_texrender_get_texture(texrender)); } void ScreenshotObj::Copy() { uint8_t *videoData = nullptr; uint32_t videoLinesize = 0; if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) { 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); } } void ScreenshotObj::Save() { OBSBasic *main = OBSBasic::Get(); config_t *config = main->Config(); const char *mode = config_get_string(config, "Output", "Mode"); const char *type = config_get_string(config, "AdvOut", "RecType"); const char *adv_path = strcmp(type, "Standard") ? config_get_string(config, "AdvOut", "FFFilePath") : config_get_string(config, "AdvOut", "RecFilePath"); const char *rec_path = strcmp(mode, "Advanced") ? config_get_string(config, "SimpleOutput", "FilePath") : adv_path; bool noSpace = config_get_bool(config, "SimpleOutput", "FileNameWithoutSpace"); const char *filenameFormat = config_get_string(config, "Output", "FilenameFormatting"); bool overwriteIfExists = config_get_bool(config, "Output", "OverwriteIfExists"); const char *ext = half_bytes.empty() ? "png" : "jxr"; path = GetOutputFilename( 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() { 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(); } /* ========================================================================= */ #define STAGE_SCREENSHOT 0 #define STAGE_DOWNLOAD 1 #define STAGE_COPY_AND_SAVE 2 #define STAGE_FINISH 3 static void ScreenshotTick(void *param, float) { ScreenshotObj *data = reinterpret_cast(param); if (data->stage == STAGE_FINISH) { return; } obs_enter_graphics(); switch (data->stage) { case STAGE_SCREENSHOT: data->Screenshot(); break; case STAGE_DOWNLOAD: data->Download(); break; case STAGE_COPY_AND_SAVE: data->Copy(); QMetaObject::invokeMethod(data, "Save"); obs_remove_tick_callback(ScreenshotTick, data); break; } obs_leave_graphics(); data->stage++; } void OBSBasic::Screenshot(OBSSource source) { if (!!screenshotData) { blog(LOG_WARNING, "Cannot take new screenshot, " "screenshot currently in progress"); return; } screenshotData = new ScreenshotObj(source); } void OBSBasic::ScreenshotSelectedSource() { OBSSceneItem item = GetCurrentSceneItem(); if (item) { Screenshot(obs_sceneitem_get_source(item)); } else { blog(LOG_INFO, "Could not take a source screenshot: " "no source selected"); } } void OBSBasic::ScreenshotProgram() { Screenshot(GetProgramSource()); } void OBSBasic::ScreenshotScene() { Screenshot(GetCurrentSceneSource()); }