Skip to content

Commit b6ad512

Browse files
authored
Change Background Image for games (#2334)
* Added opacity change instead of blur for background image * Fixed integer overflow when refreshing grid list * Added slider to control background image opacity * Added show background image button * Added UI code for checkbox and English and Spanish translations for new UI elements * Removed background image caching * Background image update on apply/save * Only recompute image if opacity or game changes * Fixed segfault when trying to change opacity after table refresh * Placed background image settings under GUI in settings file
1 parent 363604c commit b6ad512

13 files changed

+275
-52
lines changed

src/common/config.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ std::vector<std::string> m_pkg_viewer;
9595
std::vector<std::string> m_elf_viewer;
9696
std::vector<std::string> m_recent_files;
9797
std::string emulator_language = "en";
98+
static int backgroundImageOpacity = 50;
99+
static bool showBackgroundImage = true;
98100

99101
// Language
100102
u32 m_language = 1; // english
@@ -611,6 +613,22 @@ u32 GetLanguage() {
611613
return m_language;
612614
}
613615

616+
int getBackgroundImageOpacity() {
617+
return backgroundImageOpacity;
618+
}
619+
620+
void setBackgroundImageOpacity(int opacity) {
621+
backgroundImageOpacity = std::clamp(opacity, 0, 100);
622+
}
623+
624+
bool getShowBackgroundImage() {
625+
return showBackgroundImage;
626+
}
627+
628+
void setShowBackgroundImage(bool show) {
629+
showBackgroundImage = show;
630+
}
631+
614632
void load(const std::filesystem::path& path) {
615633
// If the configuration file does not exist, create it and return
616634
std::error_code error;
@@ -731,6 +749,8 @@ void load(const std::filesystem::path& path) {
731749
m_recent_files = toml::find_or<std::vector<std::string>>(gui, "recentFiles", {});
732750
m_table_mode = toml::find_or<int>(gui, "gameTableMode", 0);
733751
emulator_language = toml::find_or<std::string>(gui, "emulatorLanguage", "en");
752+
backgroundImageOpacity = toml::find_or<int>(gui, "backgroundImageOpacity", 50);
753+
showBackgroundImage = toml::find_or<bool>(gui, "showBackgroundImage", true);
734754
}
735755

736756
if (data.contains("Settings")) {
@@ -821,6 +841,8 @@ void save(const std::filesystem::path& path) {
821841
data["GUI"]["addonInstallDir"] =
822842
std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
823843
data["GUI"]["emulatorLanguage"] = emulator_language;
844+
data["GUI"]["backgroundImageOpacity"] = backgroundImageOpacity;
845+
data["GUI"]["showBackgroundImage"] = showBackgroundImage;
824846
data["Settings"]["consoleLanguage"] = m_language;
825847

826848
std::ofstream file(path, std::ios::binary);
@@ -914,6 +936,8 @@ void setDefaultValues() {
914936
separateupdatefolder = false;
915937
compatibilityData = false;
916938
checkCompatibilityOnStartup = false;
939+
backgroundImageOpacity = 50;
940+
showBackgroundImage = true;
917941
}
918942

919943
constexpr std::string_view GetDefaultKeyboardConfig() {

src/common/config.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ bool getEnableDiscordRPC();
3030
bool getSeparateUpdateEnabled();
3131
bool getCompatibilityEnabled();
3232
bool getCheckCompatibilityOnStartup();
33+
int getBackgroundImageOpacity();
34+
bool getShowBackgroundImage();
3335

3436
std::string getLogFilter();
3537
std::string getLogType();
@@ -88,6 +90,8 @@ void setGameInstallDirs(const std::vector<std::filesystem::path>& settings_insta
8890
void setSaveDataPath(const std::filesystem::path& path);
8991
void setCompatibilityEnabled(bool use);
9092
void setCheckCompatibilityOnStartup(bool use);
93+
void setBackgroundImageOpacity(int opacity);
94+
void setShowBackgroundImage(bool show);
9195

9296
void setCursorState(s16 cursorState);
9397
void setCursorHideTimeout(int newcursorHideTimeout);

src/qt_gui/game_grid_frame.cpp

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,34 @@ GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
3838

3939
void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
4040
int previousColumn) {
41+
// Early exit for invalid indices
42+
if (currentRow < 0 || currentColumn < 0) {
43+
cellClicked = false;
44+
validCellSelected = false;
45+
BackgroundMusicPlayer::getInstance().stopMusic();
46+
return;
47+
}
48+
4149
crtRow = currentRow;
4250
crtColumn = currentColumn;
4351
columnCnt = this->columnCount();
4452

53+
// Prevent integer overflow
54+
if (columnCnt <= 0 || crtRow > (std::numeric_limits<int>::max() / columnCnt)) {
55+
cellClicked = false;
56+
validCellSelected = false;
57+
BackgroundMusicPlayer::getInstance().stopMusic();
58+
return;
59+
}
60+
4561
auto itemID = (crtRow * columnCnt) + currentColumn;
46-
if (itemID > m_game_info->m_games.count() - 1) {
62+
if (itemID < 0 || itemID > m_game_info->m_games.count() - 1) {
4763
cellClicked = false;
4864
validCellSelected = false;
4965
BackgroundMusicPlayer::getInstance().stopMusic();
5066
return;
5167
}
68+
5269
cellClicked = true;
5370
validCellSelected = true;
5471
SetGridBackgroundImage(crtRow, crtColumn);
@@ -65,6 +82,8 @@ void GameGridFrame::PlayBackgroundMusic(QString path) {
6582
}
6683

6784
void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool fromSearch) {
85+
this->crtRow = -1;
86+
this->crtColumn = -1;
6887
QVector<GameInfo> m_games_;
6988
this->clearContents();
7089
if (fromSearch)
@@ -136,43 +155,48 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
136155
}
137156

138157
void GameGridFrame::SetGridBackgroundImage(int row, int column) {
139-
140158
int itemID = (row * this->columnCount()) + column;
141159
QWidget* item = this->cellWidget(row, column);
142-
if (item) {
143-
QString pic1Path;
144-
Common::FS::PathToQString(pic1Path, (*m_games_shared)[itemID].pic_path);
145-
const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
146-
(*m_games_shared)[itemID].serial / "pic1.png";
147-
QString blurredPic1PathQt;
148-
Common::FS::PathToQString(blurredPic1PathQt, blurredPic1Path);
149-
150-
backgroundImage = QImage(blurredPic1PathQt);
151-
if (backgroundImage.isNull()) {
152-
QImage image(pic1Path);
153-
backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16);
154-
155-
std::filesystem::path img_path =
156-
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
157-
(*m_games_shared)[itemID].serial;
158-
std::filesystem::create_directories(img_path);
159-
if (!backgroundImage.save(blurredPic1PathQt, "PNG")) {
160-
// qDebug() << "Error: Unable to save image.";
161-
}
162-
}
160+
if (!item) {
161+
// handle case where no item was clicked
162+
return;
163+
}
164+
165+
// If background images are hidden, clear the background image
166+
if (!Config::getShowBackgroundImage()) {
167+
backgroundImage = QImage();
168+
m_last_opacity = -1; // Reset opacity tracking when disabled
169+
m_current_game_path.clear(); // Reset current game path
163170
RefreshGridBackgroundImage();
171+
return;
164172
}
173+
174+
const auto& game = (*m_games_shared)[itemID];
175+
const int opacity = Config::getBackgroundImageOpacity();
176+
177+
// Recompute if opacity changed or we switched to a different game
178+
if (opacity != m_last_opacity || game.pic_path != m_current_game_path) {
179+
QImage original_image(QString::fromStdString(game.pic_path.string()));
180+
if (!original_image.isNull()) {
181+
backgroundImage = m_game_list_utils.ChangeImageOpacity(
182+
original_image, original_image.rect(), opacity / 100.0f);
183+
m_last_opacity = opacity;
184+
m_current_game_path = game.pic_path;
185+
}
186+
}
187+
188+
RefreshGridBackgroundImage();
165189
}
166190

167191
void GameGridFrame::RefreshGridBackgroundImage() {
168-
if (!backgroundImage.isNull()) {
169-
QPalette palette;
192+
QPalette palette;
193+
if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) {
170194
palette.setBrush(QPalette::Base,
171195
QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio)));
172-
QColor transparentColor = QColor(135, 206, 235, 40);
173-
palette.setColor(QPalette::Highlight, transparentColor);
174-
this->setPalette(palette);
175196
}
197+
QColor transparentColor = QColor(135, 206, 235, 40);
198+
palette.setColor(QPalette::Highlight, transparentColor);
199+
this->setPalette(palette);
176200
}
177201

178202
bool GameGridFrame::IsValidCellSelected() {

src/qt_gui/game_grid_frame.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public Q_SLOTS:
3333
std::shared_ptr<CompatibilityInfoClass> m_compat_info;
3434
std::shared_ptr<QVector<GameInfo>> m_games_shared;
3535
bool validCellSelected = false;
36+
int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation
37+
std::filesystem::path m_current_game_path; // Track current game path to detect changes
3638

3739
public:
3840
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,

src/qt_gui/game_list_frame.cpp

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int
8989
if (!item) {
9090
return;
9191
}
92+
m_current_item = item; // Store current item
9293
SetListBackgroundImage(item);
9394
PlayBackgroundMusic(item);
9495
}
@@ -104,6 +105,7 @@ void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
104105
}
105106

106107
void GameListFrame::PopulateGameList(bool isInitialPopulation) {
108+
this->m_current_item = nullptr;
107109
// Do not show status column if it is not enabled
108110
this->setColumnHidden(2, !Config::getCompatibilityEnabled());
109111
this->setColumnHidden(6, !Config::GetLoadGameSizeEnabled());
@@ -167,38 +169,41 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
167169
return;
168170
}
169171

170-
QString pic1Path;
171-
Common::FS::PathToQString(pic1Path, m_game_info->m_games[item->row()].pic_path);
172-
const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
173-
m_game_info->m_games[item->row()].serial / "pic1.png";
174-
QString blurredPic1PathQt;
175-
Common::FS::PathToQString(blurredPic1PathQt, blurredPic1Path);
176-
177-
backgroundImage = QImage(blurredPic1PathQt);
178-
if (backgroundImage.isNull()) {
179-
QImage image(pic1Path);
180-
backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16);
181-
182-
std::filesystem::path img_path =
183-
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
184-
m_game_info->m_games[item->row()].serial;
185-
std::filesystem::create_directories(img_path);
186-
if (!backgroundImage.save(blurredPic1PathQt, "PNG")) {
187-
// qDebug() << "Error: Unable to save image.";
172+
// If background images are hidden, clear the background image
173+
if (!Config::getShowBackgroundImage()) {
174+
backgroundImage = QImage();
175+
m_last_opacity = -1; // Reset opacity tracking when disabled
176+
m_current_game_path.clear(); // Reset current game path
177+
RefreshListBackgroundImage();
178+
return;
179+
}
180+
181+
const auto& game = m_game_info->m_games[item->row()];
182+
const int opacity = Config::getBackgroundImageOpacity();
183+
184+
// Recompute if opacity changed or we switched to a different game
185+
if (opacity != m_last_opacity || game.pic_path != m_current_game_path) {
186+
QImage original_image(QString::fromStdString(game.pic_path.string()));
187+
if (!original_image.isNull()) {
188+
backgroundImage = m_game_list_utils.ChangeImageOpacity(
189+
original_image, original_image.rect(), opacity / 100.0f);
190+
m_last_opacity = opacity;
191+
m_current_game_path = game.pic_path;
188192
}
189193
}
194+
190195
RefreshListBackgroundImage();
191196
}
192197

193198
void GameListFrame::RefreshListBackgroundImage() {
194-
if (!backgroundImage.isNull()) {
195-
QPalette palette;
199+
QPalette palette;
200+
if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) {
196201
palette.setBrush(QPalette::Base,
197202
QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio)));
198-
QColor transparentColor = QColor(135, 206, 235, 40);
199-
palette.setColor(QPalette::Highlight, transparentColor);
200-
this->setPalette(palette);
201203
}
204+
QColor transparentColor = QColor(135, 206, 235, 40);
205+
palette.setColor(QPalette::Highlight, transparentColor);
206+
this->setPalette(palette);
202207
}
203208

204209
void GameListFrame::SortNameAscending(int columnIndex) {
@@ -392,3 +397,7 @@ QString GameListFrame::GetPlayTime(const std::string& serial) {
392397
file.close();
393398
return playTime;
394399
}
400+
401+
QTableWidgetItem* GameListFrame::GetCurrentItem() {
402+
return m_current_item;
403+
}

src/qt_gui/game_list_frame.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,14 @@ public Q_SLOTS:
4444
QList<QAction*> m_columnActs;
4545
GameInfoClass* game_inf_get = nullptr;
4646
bool ListSortedAsc = true;
47+
QTableWidgetItem* m_current_item = nullptr;
48+
int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation
49+
std::filesystem::path m_current_game_path; // Track current game path to detect changes
4750

4851
public:
4952
void PopulateGameList(bool isInitialPopulation = true);
5053
void ResizeIcons(int iconSize);
51-
54+
QTableWidgetItem* GetCurrentItem();
5255
QImage backgroundImage;
5356
GameListUtils m_game_list_utils;
5457
GuiContextMenus m_gui_context_menus;

src/qt_gui/game_list_utils.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,30 @@ class GameListUtils : public QObject {
201201

202202
return result;
203203
}
204+
205+
// Opacity is a float between 0 and 1
206+
static QImage ChangeImageOpacity(const QImage& image, const QRect& rect, float opacity) {
207+
// Convert to ARGB32 format to ensure alpha channel support
208+
QImage result = image.convertToFormat(QImage::Format_ARGB32);
209+
210+
// Ensure opacity is between 0 and 1
211+
opacity = std::clamp(opacity, 0.0f, 1.0f);
212+
213+
// Convert opacity to integer alpha value (0-255)
214+
int alpha = static_cast<int>(opacity * 255);
215+
216+
// Process only the specified rectangle area
217+
for (int y = rect.top(); y <= rect.bottom(); ++y) {
218+
QRgb* line = reinterpret_cast<QRgb*>(result.scanLine(y));
219+
for (int x = rect.left(); x <= rect.right(); ++x) {
220+
// Get current pixel
221+
QRgb pixel = line[x];
222+
// Keep RGB values, but modify alpha while preserving relative transparency
223+
int newAlpha = (qAlpha(pixel) * alpha) / 255;
224+
line[x] = qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), newAlpha);
225+
}
226+
}
227+
228+
return result;
229+
}
204230
};

src/qt_gui/main_window.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,23 @@ void MainWindow::CreateConnects() {
297297
connect(settingsDialog, &SettingsDialog::CompatibilityChanged, this,
298298
&MainWindow::RefreshGameTable);
299299

300+
connect(settingsDialog, &SettingsDialog::BackgroundOpacityChanged, this,
301+
[this](int opacity) {
302+
Config::setBackgroundImageOpacity(opacity);
303+
if (m_game_list_frame) {
304+
QTableWidgetItem* current = m_game_list_frame->GetCurrentItem();
305+
if (current) {
306+
m_game_list_frame->SetListBackgroundImage(current);
307+
}
308+
}
309+
if (m_game_grid_frame) {
310+
if (m_game_grid_frame->IsValidCellSelected()) {
311+
m_game_grid_frame->SetGridBackgroundImage(m_game_grid_frame->crtRow,
312+
m_game_grid_frame->crtColumn);
313+
}
314+
}
315+
});
316+
300317
settingsDialog->exec();
301318
});
302319

0 commit comments

Comments
 (0)