diff --git a/src/common/config.cpp b/src/common/config.cpp index 2059da0b3..da967e83a 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -95,6 +95,7 @@ std::vector m_pkg_viewer; std::vector m_elf_viewer; std::vector m_recent_files; std::string emulator_language = "en"; +static int backgroundImageOpacity = 50; // Language u32 m_language = 1; // english @@ -611,6 +612,14 @@ u32 GetLanguage() { return m_language; } +int getBackgroundImageOpacity() { + return backgroundImageOpacity; +} + +void setBackgroundImageOpacity(int opacity) { + backgroundImageOpacity = std::clamp(opacity, 0, 100); +} + void load(const std::filesystem::path& path) { // If the configuration file does not exist, create it and return std::error_code error; @@ -655,6 +664,7 @@ void load(const std::filesystem::path& path) { checkCompatibilityOnStartup = toml::find_or(general, "checkCompatibilityOnStartup", false); chooseHomeTab = toml::find_or(general, "chooseHomeTab", "Release"); + backgroundImageOpacity = toml::find_or(general, "backgroundImageOpacity", 50); } if (data.contains("Input")) { @@ -783,6 +793,7 @@ void save(const std::filesystem::path& path) { data["General"]["separateUpdateEnabled"] = separateupdatefolder; data["General"]["compatibilityEnabled"] = compatibilityData; data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; + data["General"]["backgroundImageOpacity"] = backgroundImageOpacity; data["Input"]["cursorState"] = cursorState; data["Input"]["cursorHideTimeout"] = cursorHideTimeout; data["Input"]["backButtonBehavior"] = backButtonBehavior; @@ -914,6 +925,7 @@ void setDefaultValues() { separateupdatefolder = false; compatibilityData = false; checkCompatibilityOnStartup = false; + backgroundImageOpacity = 50; } constexpr std::string_view GetDefaultKeyboardConfig() { diff --git a/src/common/config.h b/src/common/config.h index 77ed69ece..57df2ab9f 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -30,6 +30,7 @@ bool getEnableDiscordRPC(); bool getSeparateUpdateEnabled(); bool getCompatibilityEnabled(); bool getCheckCompatibilityOnStartup(); +int getBackgroundImageOpacity(); std::string getLogFilter(); std::string getLogType(); @@ -88,6 +89,7 @@ void setGameInstallDirs(const std::vector& settings_insta void setSaveDataPath(const std::filesystem::path& path); void setCompatibilityEnabled(bool use); void setCheckCompatibilityOnStartup(bool use); +void setBackgroundImageOpacity(int opacity); void setCursorState(s16 cursorState); void setCursorHideTimeout(int newcursorHideTimeout); diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index f6ce59069..2b74b5fea 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include "common/path_util.h" #include "game_grid_frame.h" #include "qt_gui/compatibility_info.h" @@ -153,32 +155,43 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from } void GameGridFrame::SetGridBackgroundImage(int row, int column) { - int itemID = (row * this->columnCount()) + column; QWidget* item = this->cellWidget(row, column); - if (item) { - QString pic1Path; - Common::FS::PathToQString(pic1Path, (*m_games_shared)[itemID].pic_path); - const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - (*m_games_shared)[itemID].serial / "pic1.png"; - QString blurredPic1PathQt; - Common::FS::PathToQString(blurredPic1PathQt, blurredPic1Path); - - backgroundImage = QImage(blurredPic1PathQt); - if (backgroundImage.isNull()) { - QImage image(pic1Path); - backgroundImage = m_game_list_utils.ChangeImageOpacity(image, image.rect(), 0.5); - - std::filesystem::path img_path = - Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - (*m_games_shared)[itemID].serial; - std::filesystem::create_directories(img_path); - if (!backgroundImage.save(blurredPic1PathQt, "PNG")) { - // qDebug() << "Error: Unable to save image."; - } - } - RefreshGridBackgroundImage(); + if (!item) { + // handle case where no item was clicked + return; } + + const auto& game = (*m_games_shared)[itemID]; + const int opacity = Config::getBackgroundImageOpacity(); + const auto cache_path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / + game.serial / fmt::format("pic1_{}.png", opacity); + + // Fast path - try to load cached version first + if (std::filesystem::exists(cache_path)) { + backgroundImage = QImage(QString::fromStdString(cache_path.string())); + if (!backgroundImage.isNull()) { + RefreshGridBackgroundImage(); + return; + } + } + + // Cache miss - generate and store + m_game_list_utils.CleanupOldOpacityImages(cache_path.parent_path()); + QImage original_image(QString::fromStdString(game.pic_path.string())); + if (!original_image.isNull()) { + std::filesystem::create_directories(cache_path.parent_path()); + backgroundImage = m_game_list_utils.ChangeImageOpacity( + original_image, original_image.rect(), opacity / 100.0f); + if (!backgroundImage.isNull()) { + // Save the image to the cache asynchronously + QFuture future = QtConcurrent::run([this, cache_path]() { + backgroundImage.save(QString::fromStdString(cache_path.string()), "PNG"); + }); + } + } + + RefreshGridBackgroundImage(); } void GameGridFrame::RefreshGridBackgroundImage() { diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index b871f47b5..b834b5a9c 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -167,26 +167,35 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { return; } - QString pic1Path; - Common::FS::PathToQString(pic1Path, m_game_info->m_games[item->row()].pic_path); - const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - m_game_info->m_games[item->row()].serial / "pic1.png"; - QString blurredPic1PathQt; - Common::FS::PathToQString(blurredPic1PathQt, blurredPic1Path); + const auto& game = m_game_info->m_games[item->row()]; + const int opacity = Config::getBackgroundImageOpacity(); + const auto cache_path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / + game.serial / fmt::format("pic1_{}.png", opacity); - backgroundImage = QImage(blurredPic1PathQt); - if (backgroundImage.isNull()) { - QImage image(pic1Path); - backgroundImage = m_game_list_utils.ChangeImageOpacity(image, image.rect(), 0.5); - - std::filesystem::path img_path = - Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - m_game_info->m_games[item->row()].serial; - std::filesystem::create_directories(img_path); - if (!backgroundImage.save(blurredPic1PathQt, "PNG")) { - // qDebug() << "Error: Unable to save image."; + // Fast path - try to load cached version first + if (std::filesystem::exists(cache_path)) { + backgroundImage = QImage(QString::fromStdString(cache_path.string())); + if (!backgroundImage.isNull()) { + RefreshListBackgroundImage(); + return; } } + + // Cache miss - generate and store + m_game_list_utils.CleanupOldOpacityImages(cache_path.parent_path()); + QImage original_image(QString::fromStdString(game.pic_path.string())); + if (!original_image.isNull()) { + std::filesystem::create_directories(cache_path.parent_path()); + backgroundImage = m_game_list_utils.ChangeImageOpacity( + original_image, original_image.rect(), opacity / 100.0f); + if (!backgroundImage.isNull()) { + // Save the image to the cache asynchronously + QFuture future = QtConcurrent::run([this, cache_path]() { + backgroundImage.save(QString::fromStdString(cache_path.string()), "PNG"); + }); + } + } + RefreshListBackgroundImage(); } diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index 1a8860c1e..ac7702f70 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -202,16 +202,17 @@ public: return result; } - QImage ChangeImageOpacity(const QImage& image, const QRect& rect, float opacity) { + // Opacity is a float between 0 and 1 + static QImage ChangeImageOpacity(const QImage& image, const QRect& rect, float opacity) { // Convert to ARGB32 format to ensure alpha channel support QImage result = image.convertToFormat(QImage::Format_ARGB32); - + // Ensure opacity is between 0 and 1 opacity = std::clamp(opacity, 0.0f, 1.0f); - + // Convert opacity to integer alpha value (0-255) int alpha = static_cast(opacity * 255); - + // Process only the specified rectangle area for (int y = rect.top(); y <= rect.bottom(); ++y) { QRgb* line = reinterpret_cast(result.scanLine(y)); @@ -223,7 +224,20 @@ public: line[x] = qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), newAlpha); } } - + return result; } + + void CleanupOldOpacityImages(const std::filesystem::path& dir) { + if (!std::filesystem::exists(dir)) { + return; + } + + for (const auto& entry : std::filesystem::directory_iterator(dir)) { + const auto& path = entry.path(); + if (path.filename().string().starts_with("pic1") && path.extension() == ".png") { + std::filesystem::remove(path); + } + } + } }; diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 802325126..706bdfd72 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -173,6 +173,10 @@ SettingsDialog::SettingsDialog(std::span physical_devices, { connect(ui->chooseHomeTabComboBox, &QComboBox::currentTextChanged, this, [](const QString& hometab) { Config::setChooseHomeTab(hometab.toStdString()); }); + + // Add background image opacity slider connection + connect(ui->backgroundImageOpacitySlider, &QSlider::valueChanged, this, + [](int value) { Config::setBackgroundImageOpacity(value); }); } // Input TAB { @@ -251,6 +255,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, #ifdef ENABLE_UPDATER ui->updaterGroupBox->installEventFilter(this); #endif + ui->GUIBackgroundImageGroupBox->installEventFilter(this); ui->GUIMusicGroupBox->installEventFilter(this); ui->disableTrophycheckBox->installEventFilter(this); ui->enableCompatibilityCheckBox->installEventFilter(this); @@ -410,6 +415,7 @@ void SettingsDialog::LoadValuesFromConfig() { ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty()); ResetInstallFolders(); + ui->backgroundImageOpacitySlider->setValue(Config::getBackgroundImageOpacity()); } void SettingsDialog::InitializeEmulatorLanguages() { @@ -504,6 +510,8 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { } else if (elementName == "updaterGroupBox") { text = tr("updaterGroupBox"); #endif + } else if (elementName == "GUIBackgroundImageGroupBox") { + text = tr("GUIBackgroundImageGroupBox"); } else if (elementName == "GUIMusicGroupBox") { text = tr("GUIMusicGroupBox"); } else if (elementName == "disableTrophycheckBox") {