diff --git a/src/client/client.cpp b/src/client/client.cpp index 7f255a9fa..a5a3d088a 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -36,6 +36,7 @@ #include "translation.h" #include "util/auth.h" #include "util/pointedthing.h" +#include "util/screenshot.h" #include "util/serialize.h" #include "util/srp.h" #include "util/string.h" @@ -1915,69 +1916,12 @@ float Client::getCurRate() void Client::makeScreenshot() { video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); - video::IImage* const raw_image = driver->createScreenShot(); - - if (!raw_image) { - errorstream << "Could not take screenshot" << std::endl; - return; - } - - const struct tm tm = mt_localtime(); - - char timestamp_c[64]; - strftime(timestamp_c, sizeof(timestamp_c), "%Y%m%d_%H%M%S", &tm); - - std::string screenshot_dir = g_settings->get("screenshot_path"); - if (!fs::IsPathAbsolute(screenshot_dir)) - screenshot_dir = porting::path_user + DIR_DELIM + screenshot_dir; - - std::string filename_base = screenshot_dir - + DIR_DELIM - + std::string("screenshot_") - + timestamp_c; - std::string filename_ext = "." + g_settings->get("screenshot_format"); - - // Create the directory if it doesn't already exist. - // Otherwise, saving the screenshot would fail. - fs::CreateAllDirs(screenshot_dir); - - u32 quality = (u32)g_settings->getS32("screenshot_quality"); - quality = rangelim(quality, 0, 100) / 100.0f * 255; - - // Try to find a unique filename std::string filename; - unsigned serial = 0; - - while (serial < SCREENSHOT_MAX_SERIAL_TRIES) { - filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext; - if (!fs::PathExists(filename)) - break; // File did not apparently exist, we'll go with it - serial++; + if (takeScreenshot(driver, filename)) { + std::string msg = fmtgettext("Saved screenshot to \"%s\"", filename.c_str()); + pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM, + utf8_to_wide(msg))); } - - if (serial == SCREENSHOT_MAX_SERIAL_TRIES) { - errorstream << "Could not find suitable filename for screenshot" << std::endl; - } else { - video::IImage* const image = - driver->createImage(video::ECF_R8G8B8, raw_image->getDimension()); - - if (image) { - raw_image->copyTo(image); - - std::string msg; - if (driver->writeImageToFile(image, filename.c_str(), quality)) { - msg = fmtgettext("Saved screenshot to \"%s\"", filename.c_str()); - } else { - msg = fmtgettext("Failed to save screenshot to \"%s\"", filename.c_str()); - } - pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM, - utf8_to_wide(msg))); - infostream << msg << std::endl; - image->drop(); - } - } - - raw_image->drop(); } void Client::pushToEventQueue(ClientEvent *event) diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index e1517065b..b0ee6fd2a 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -22,6 +22,7 @@ #include #include #include "client/imagefilters.h" +#include "util/screenshot.h" #include "util/tracy_wrapper.h" #include "script/common/c_types.h" // LuaError @@ -39,6 +40,13 @@ void TextDestGuiEngine::gotText(const StringMap &fields) m_engine->getScriptIface()->handleMainMenuButtons(fields); } +void TextDestGuiEngine::requestScreenshot() +{ + if (m_engine) { + m_engine->requestScreenshot(); + } +} + /******************************************************************************/ MenuTextureSource::~MenuTextureSource() { @@ -367,6 +375,14 @@ void GUIEngine::run() // the menu. drawHeader(driver); + // Take screenshot if requested + // Must be before endScene() to capture the rendered frame + if (m_take_screenshot) { + m_take_screenshot = false; + std::string filename; + takeScreenshot(driver, filename); + } + driver->endScene(); } diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index da0700319..b83c640f0 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -62,6 +62,11 @@ public: */ void gotText(const StringMap &fields); + /** + * Request a screenshot from the main menu + */ + void requestScreenshot(); + private: /** target to transmit data to */ GUIEngine *m_engine = nullptr; @@ -146,6 +151,14 @@ public: return m_scriptdir; } + /** + * Request taking a screenshot on the next frame + */ + void requestScreenshot() + { + m_take_screenshot = true; + } + /** * Get translations for content * @@ -199,6 +212,9 @@ private: /** variable used to abort menu and return back to main game handling */ bool m_startgame = false; + /** flag to take a screenshot on next frame */ + bool m_take_screenshot = false; + /** scripting interface */ std::unique_ptr m_script; diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 66c228fd0..6194fefc9 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -33,6 +33,7 @@ #include "client/fontengine.h" #include "client/sound.h" #include "util/numeric.h" +#include "util/screenshot.h" #include "util/string.h" // for parseColorString() #include "irrlicht_changes/static_text.h" #include "client/guiscalingfilter.h" @@ -4082,9 +4083,13 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) return true; } - if (m_client != NULL && event.KeyInput.PressedDown && + if (event.KeyInput.PressedDown && (kp == getKeySetting("keymap_screenshot"))) { - m_client->makeScreenshot(); + if (m_client) { + m_client->makeScreenshot(); + } else if (m_text_dst) { // in main menu + m_text_dst->requestScreenshot(); + } } if (event.KeyInput.PressedDown && kp == getKeySetting("keymap_toggle_debug")) { diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 02849262a..5cdf2f667 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -68,6 +68,7 @@ struct TextDest virtual ~TextDest() = default; virtual void gotText(const StringMap &fields) = 0; + virtual void requestScreenshot() {} std::string m_formname; }; diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 0cbf0eaa1..170bcabd9 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -17,6 +17,7 @@ set(util_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/pointabilities.cpp ${CMAKE_CURRENT_SOURCE_DIR}/quicktune.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/screenshot.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sha1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/srp.cpp diff --git a/src/util/screenshot.cpp b/src/util/screenshot.cpp new file mode 100644 index 000000000..f9d230612 --- /dev/null +++ b/src/util/screenshot.cpp @@ -0,0 +1,96 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2013 celeron55, Perttu Ahola + +#include "screenshot.h" +#include "filesys.h" +#include "gettime.h" +#include "porting.h" +#include "settings.h" +#include "util/string.h" +#include "util/numeric.h" +#include "gettext.h" +#include "log.h" +#include "debug.h" +#include +#include + +#define SCREENSHOT_MAX_SERIAL_TRIES 1000 + +bool takeScreenshot(video::IVideoDriver *driver, std::string &filename_out) +{ + sanity_check(driver); + + video::IImage* const raw_image = driver->createScreenShot(); + + if (!raw_image) { + errorstream << "Could not take screenshot" << std::endl; + return false; + } + + const struct tm tm = mt_localtime(); + + char timestamp_c[64]; + strftime(timestamp_c, sizeof(timestamp_c), "%Y%m%d_%H%M%S", &tm); + + std::string screenshot_dir = g_settings->get("screenshot_path"); + if (!fs::IsPathAbsolute(screenshot_dir)) + screenshot_dir = porting::path_user + DIR_DELIM + screenshot_dir; + + std::string filename_base = screenshot_dir + + DIR_DELIM + + std::string("screenshot_") + + timestamp_c; + std::string filename_ext = "." + g_settings->get("screenshot_format"); + + // Create the directory if it doesn't already exist. + // Otherwise, saving the screenshot would fail. + fs::CreateAllDirs(screenshot_dir); + + u32 quality = (u32)g_settings->getS32("screenshot_quality"); + quality = rangelim(quality, 0, 100) / 100.0f * 255; + + // Try to find a unique filename + std::string filename; + unsigned serial = 0; + + while (serial < SCREENSHOT_MAX_SERIAL_TRIES) { + filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "").append(filename_ext); + if (!fs::PathExists(filename)) + break; // File did not apparently exist, we'll go with it + serial++; + } + + if (serial == SCREENSHOT_MAX_SERIAL_TRIES) { + errorstream << "Could not find suitable filename for screenshot" << std::endl; + raw_image->drop(); + return false; + } + + video::IImage* const image = + driver->createImage(video::ECF_R8G8B8, raw_image->getDimension()); + + if (!image) { + errorstream << "Could not create image for screenshot" << std::endl; + raw_image->drop(); + return false; + } + + raw_image->copyTo(image); + + bool success = driver->writeImageToFile(image, filename.c_str(), quality); + + if (success) { + filename_out = filename; + std::string msg = fmtgettext("Saved screenshot to \"%s\"", filename.c_str()); + infostream << msg << std::endl; + } else { + std::string msg = fmtgettext("Failed to save screenshot to \"%s\"", filename.c_str()); + errorstream << msg << std::endl; + } + + image->drop(); + raw_image->drop(); + return success; +} + diff --git a/src/util/screenshot.h b/src/util/screenshot.h new file mode 100644 index 000000000..0b7a97080 --- /dev/null +++ b/src/util/screenshot.h @@ -0,0 +1,20 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2013 celeron55, Perttu Ahola + +#pragma once + +#include + +namespace video { + class IVideoDriver; +} + +/** + * Take a screenshot and save it to disk + * @param driver Video driver to use for the screenshot + * @param filename_out Output parameter that receives the path to the saved screenshot + * @return true if the screenshot was saved successfully, false otherwise + */ +bool takeScreenshot(video::IVideoDriver *driver, std::string &filename_out); +