From a5059cbca81c31fbbfceb2180b82eb59b0fe2fcb Mon Sep 17 00:00:00 2001 From: Adam Scott Date: Tue, 24 Mar 2026 14:18:15 -0400 Subject: [PATCH] Add screen crop feature --- .../features/settings/model/BooleanSetting.kt | 3 +- .../features/settings/model/IntSetting.kt | 24 ++++ .../settings/ui/SettingsFragmentPresenter.kt | 66 +++++++++- .../app/src/main/res/values/strings.xml | 13 +- Source/Core/Core/Config/GraphicsSettings.cpp | 7 +- Source/Core/Core/Config/GraphicsSettings.h | 7 +- Source/Core/Core/HotkeyManager.cpp | 6 +- Source/Core/Core/HotkeyManager.h | 3 +- .../Config/Graphics/AdvancedWidget.cpp | 86 +++++++++++-- .../Config/Graphics/AdvancedWidget.h | 11 +- Source/Core/DolphinQt/HotkeyScheduler.cpp | 10 +- Source/Core/VideoCommon/Present.cpp | 116 +++++++++++++----- Source/Core/VideoCommon/VideoConfig.cpp | 7 +- Source/Core/VideoCommon/VideoConfig.h | 7 +- 14 files changed, 310 insertions(+), 56 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt index b6a03e6b61..cfa2377b98 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt @@ -678,7 +678,8 @@ enum class BooleanSetting( SYSCONF_WIIMOTE_MOTOR(Settings.FILE_SYSCONF, "BT", "MOT", true), GFX_VSYNC(Settings.FILE_GFX, Settings.SECTION_GFX_HARDWARE, "VSync", false), GFX_WIDESCREEN_HACK(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "wideScreenHack", false), - GFX_CROP(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "Crop", false), + GFX_CROP_TO_ASPECT_RATIO(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "Crop", false), + GFX_CROP_CUSTOM(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "CropCustom", false), GFX_SHOW_FPS(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "ShowFPS", false), GFX_SHOW_FTIMES(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "ShowFTimes", false), GFX_SHOW_VPS(Settings.FILE_GFX, Settings.SECTION_GFX_SETTINGS, "ShowVPS", false), diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.kt index faf9f71ea0..1802357e0d 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.kt @@ -133,6 +133,30 @@ enum class IntSetting( "PerfSampWindowMS", 1000 ), + GFX_CROP_CUSTOM_TOP( + Settings.FILE_GFX, + Settings.SECTION_GFX_SETTINGS, + "CropCustomTop", + 0 + ), + GFX_CROP_CUSTOM_BOTTOM( + Settings.FILE_GFX, + Settings.SECTION_GFX_SETTINGS, + "CropCustomBottom", + 0 + ), + GFX_CROP_CUSTOM_LEFT( + Settings.FILE_GFX, + Settings.SECTION_GFX_SETTINGS, + "CropCustomLeft", + 0 + ), + GFX_CROP_CUSTOM_RIGHT( + Settings.FILE_GFX, + Settings.SECTION_GFX_SETTINGS, + "CropCustomRight", + 0 + ), LOGGER_VERBOSITY(Settings.FILE_LOGGER, Settings.SECTION_LOGGER_OPTIONS, "Verbosity", 1), WIIMOTE_1_SOURCE(Settings.FILE_WIIMOTE, "Wiimote1", "Source", 1), WIIMOTE_2_SOURCE(Settings.FILE_WIIMOTE, "Wiimote2", "Source", 0), diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 1f68bbbd1a..c7df70f1fc 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1974,15 +1974,73 @@ class SettingsFragmentPresenter( ) ) - sl.add(HeaderSetting(context, R.string.misc, 0)) + sl.add(HeaderSetting(context, R.string.crop, 0)) sl.add( SwitchSetting( context, - BooleanSetting.GFX_CROP, - R.string.crop, - R.string.crop_description + BooleanSetting.GFX_CROP_TO_ASPECT_RATIO, + R.string.crop_to_aspect_ratio, + R.string.crop_to_aspect_ratio_description ) ) + sl.add( + SwitchSetting( + context, + BooleanSetting.GFX_CROP_CUSTOM, + R.string.crop_custom, + R.string.crop_custom_description + ) + ) + sl.add( + IntSliderSetting( + context, + IntSetting.GFX_CROP_CUSTOM_TOP, + R.string.crop_custom_top, + R.string.crop_custom_top_description, + 0, + 528, + "px", + 1, + ) + ) + sl.add( + IntSliderSetting( + context, + IntSetting.GFX_CROP_CUSTOM_BOTTOM, + R.string.crop_custom_bottom, + R.string.crop_custom_bottom_description, + 0, + 528, + "px", + 1, + ) + ) + sl.add( + IntSliderSetting( + context, + IntSetting.GFX_CROP_CUSTOM_LEFT, + R.string.crop_custom_left, + R.string.crop_custom_left_description, + min=0, + max=640, + units="px", + stepSize=1, + ) + ) + sl.add( + IntSliderSetting( + context, + IntSetting.GFX_CROP_CUSTOM_RIGHT, + R.string.crop_custom_right, + R.string.crop_custom_right_description, + 0, + 640, + "px", + 1, + ) + ) + + sl.add(HeaderSetting(context, R.string.misc, 0)) sl.add( SwitchSetting( context, diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 855cb56ab8..f282ab7675 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -338,7 +338,18 @@ Whether to dump mipmapped game textures to User/Dump/Textures/<game_id>/. Misc Crop - Crops the picture from its native aspect ratio to 4:3 or 16:9. If unsure, leave this unchecked. + To Aspect Ratio + Crops the picture from its native aspect ratio to 4:3 or 16:9. If unsure, leave this unchecked. + Custom + Enables options to crop the picture by discrete native pixels (independent of the internal resolution setting) in order to attain a specific user target aspect ratio. Useful to resolve letterboxing issues. If unsure, leave this unchecked. + Custom Top + Crops the picture top side by discrete native pixels (independent of the internal resolution setting) in order to attain a specific user target aspect ratio. If unsure, leave this value to 0. + Custom Bottom + Crops the picture bottom side by discrete native pixels (independent of the internal resolution setting) in order to attain a specific user target aspect ratio. If unsure, leave this value to 0. + Custom Left + Crops the picture left side by discrete native pixels (independent of the internal resolution setting) in order to attain a specific user target aspect ratio. If unsure, leave this value to 0. + Custom Right + Crops the picture right side by discrete native pixels (independent of the internal resolution setting) in order to attain a specific user target aspect ratio. If unsure, leave this value to 0. Enable Progressive Scan V-Sync This setting is unnecessary on most Android devices, because Android always enables V-Sync. If unsure, leave this unchecked. diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp index b401df3da9..2888e88339 100644 --- a/Source/Core/Core/Config/GraphicsSettings.cpp +++ b/Source/Core/Core/Config/GraphicsSettings.cpp @@ -34,7 +34,12 @@ const Info GFX_WIDESCREEN_HEURISTIC_STANDARD_RATIO{ {System::GFX, "Settings", "WidescreenHeuristicStandardRatio"}, 1.f}; const Info GFX_WIDESCREEN_HEURISTIC_WIDESCREEN_RATIO{ {System::GFX, "Settings", "WidescreenHeuristicWidescreenRatio"}, (16 / 9.f) / (4 / 3.f)}; -const Info GFX_CROP{{System::GFX, "Settings", "Crop"}, false}; +const Info GFX_CROP_TO_ASPECT_RATIO{{System::GFX, "Settings", "Crop"}, false}; +const Info GFX_CROP_CUSTOM{{System::GFX, "Settings", "CropCustom"}, false}; +const Info GFX_CROP_CUSTOM_LEFT{{System::GFX, "Settings", "CropCustomLeft"}, 0}; +const Info GFX_CROP_CUSTOM_TOP{{System::GFX, "Settings", "CropCustomTop"}, 0}; +const Info GFX_CROP_CUSTOM_RIGHT{{System::GFX, "Settings", "CropCustomRight"}, 0}; +const Info GFX_CROP_CUSTOM_BOTTOM{{System::GFX, "Settings", "CropCustomBottom"}, 0}; const Info GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES{ {System::GFX, "Settings", "SafeTextureCacheColorSamples"}, 128}; const Info GFX_SHOW_FPS{{System::GFX, "Settings", "ShowFPS"}, false}; diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h index 5c22f588ea..83fc3f9098 100644 --- a/Source/Core/Core/Config/GraphicsSettings.h +++ b/Source/Core/Core/Config/GraphicsSettings.h @@ -39,7 +39,12 @@ extern const Info GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD; extern const Info GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_SLOP; extern const Info GFX_WIDESCREEN_HEURISTIC_STANDARD_RATIO; extern const Info GFX_WIDESCREEN_HEURISTIC_WIDESCREEN_RATIO; -extern const Info GFX_CROP; +extern const Info GFX_CROP_TO_ASPECT_RATIO; +extern const Info GFX_CROP_CUSTOM; +extern const Info GFX_CROP_CUSTOM_LEFT; +extern const Info GFX_CROP_CUSTOM_TOP; +extern const Info GFX_CROP_CUSTOM_RIGHT; +extern const Info GFX_CROP_CUSTOM_BOTTOM; extern const Info GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES; extern const Info GFX_SHOW_FPS; extern const Info GFX_SHOW_FTIMES; diff --git a/Source/Core/Core/HotkeyManager.cpp b/Source/Core/Core/HotkeyManager.cpp index 92c6f2b831..5349731b6e 100644 --- a/Source/Core/Core/HotkeyManager.cpp +++ b/Source/Core/Core/HotkeyManager.cpp @@ -104,7 +104,8 @@ constexpr std::array s_hotkey_labels{{ _trans("Next Game Profile"), _trans("Previous Game Profile"), - _trans("Toggle Crop"), + _trans("Toggle Aspect Ratio Crop"), + _trans("Toggle Custom Crop"), _trans("Toggle Aspect Ratio"), _trans("Toggle Skip EFB Access"), _trans("Toggle EFB Copies"), @@ -190,7 +191,6 @@ constexpr std::array s_hotkey_labels{{ _trans("Volume Down"), _trans("Volume Up"), _trans("Volume Toggle Mute"), - _trans("1x"), _trans("2x"), _trans("3x"), @@ -310,7 +310,7 @@ constexpr std::array s_groups_info = { {_trans("Controller Profile 2"), HK_NEXT_WIIMOTE_PROFILE_2, HK_PREV_GAME_WIIMOTE_PROFILE_2}, {_trans("Controller Profile 3"), HK_NEXT_WIIMOTE_PROFILE_3, HK_PREV_GAME_WIIMOTE_PROFILE_3}, {_trans("Controller Profile 4"), HK_NEXT_WIIMOTE_PROFILE_4, HK_PREV_GAME_WIIMOTE_PROFILE_4}, - {_trans("Graphics Toggles"), HK_TOGGLE_CROP, HK_TOGGLE_TEXTURES}, + {_trans("Graphics Toggles"), HK_TOGGLE_CROP_TO_ASPECT_RATIO, HK_TOGGLE_TEXTURES}, {_trans("Internal Resolution"), HK_INCREASE_IR, HK_DECREASE_IR}, {_trans("Freelook"), HK_FREELOOK_TOGGLE, HK_FREELOOK_TOGGLE}, // i18n: Stereoscopic 3D diff --git a/Source/Core/Core/HotkeyManager.h b/Source/Core/Core/HotkeyManager.h index 7fb4fb77ac..d4bc98a1b4 100644 --- a/Source/Core/Core/HotkeyManager.h +++ b/Source/Core/Core/HotkeyManager.h @@ -92,7 +92,8 @@ enum Hotkey HK_NEXT_GAME_WIIMOTE_PROFILE_4, HK_PREV_GAME_WIIMOTE_PROFILE_4, - HK_TOGGLE_CROP, + HK_TOGGLE_CROP_TO_ASPECT_RATIO, + HK_TOGGLE_CROP_CUSTOM, HK_TOGGLE_AR, HK_TOGGLE_SKIP_EFB_ACCESS, HK_TOGGLE_EFBCOPIES, diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp index 98089e6d3d..281d4b264a 100644 --- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp @@ -153,12 +153,47 @@ void AdvancedWidget::CreateWidgets() m_png_compression_level->SetTitle(tr("PNG Compression Level")); dump_layout->addWidget(m_png_compression_level, 3, 1); + // Crop. + auto* crop_box = new QGroupBox(tr("Crop")); + auto* crop_box_layout = new QVBoxLayout(); + crop_box->setLayout(crop_box_layout); + + m_crop_to_aspect_ratio = + new ConfigBool(tr("To Aspect Ratio"), Config::GFX_CROP_TO_ASPECT_RATIO, m_game_layer); + m_crop_custom = new ConfigBool(tr("Custom"), Config::GFX_CROP_CUSTOM, m_game_layer); + + m_crop_custom_box = new QGroupBox(tr("Custom")); + auto* misc_crop_custom_layout = new QGridLayout(); + m_crop_custom_box->setLayout(misc_crop_custom_layout); + m_crop_custom_box->setDisabled(!m_crop_custom->isChecked()); + + m_crop_custom_left = new ConfigInteger(0, 640, Config::GFX_CROP_CUSTOM_LEFT, m_game_layer, 1); + auto crop_custom_left_label = new ConfigIntegerLabel(tr("Left"), m_crop_custom_left); + m_crop_custom_top = new ConfigInteger(0, 528, Config::GFX_CROP_CUSTOM_TOP, m_game_layer, 1); + auto crop_custom_top_label = new ConfigIntegerLabel(tr("Top"), m_crop_custom_top); + m_crop_custom_right = new ConfigInteger(0, 640, Config::GFX_CROP_CUSTOM_RIGHT, m_game_layer, 1); + auto crop_custom_right_label = new ConfigIntegerLabel(tr("Right"), m_crop_custom_right); + m_crop_custom_bottom = new ConfigInteger(0, 528, Config::GFX_CROP_CUSTOM_BOTTOM, m_game_layer, 1); + auto crop_custom_bottom_label = new ConfigIntegerLabel(tr("Bottom"), m_crop_custom_bottom); + + misc_crop_custom_layout->addWidget(crop_custom_left_label, 0, 0); + misc_crop_custom_layout->addWidget(m_crop_custom_left, 0, 1); + misc_crop_custom_layout->addWidget(crop_custom_top_label, 0, 2); + misc_crop_custom_layout->addWidget(m_crop_custom_top, 0, 3); + misc_crop_custom_layout->addWidget(crop_custom_right_label, 1, 0); + misc_crop_custom_layout->addWidget(m_crop_custom_right, 1, 1); + misc_crop_custom_layout->addWidget(crop_custom_bottom_label, 1, 2); + misc_crop_custom_layout->addWidget(m_crop_custom_bottom, 1, 3); + + crop_box_layout->addWidget(m_crop_to_aspect_ratio); + crop_box_layout->addWidget(m_crop_custom); + crop_box_layout->addWidget(m_crop_custom_box); + // Misc. auto* misc_box = new QGroupBox(tr("Misc")); auto* misc_layout = new QGridLayout(); misc_box->setLayout(misc_layout); - m_enable_cropping = new ConfigBool(tr("Crop"), Config::GFX_CROP, m_game_layer); m_enable_prog_scan = new ConfigBool(tr("Enable Progressive Scan"), Config::SYSCONF_PROGRESSIVE_SCAN, m_game_layer); m_backend_multithreading = new ConfigBool(tr("Backend Multithreading"), @@ -169,11 +204,11 @@ void AdvancedWidget::CreateWidgets() m_game_layer); m_cpu_cull = new ConfigBool(tr("Cull Vertices on the CPU"), Config::GFX_CPU_CULL, m_game_layer); - misc_layout->addWidget(m_enable_cropping, 0, 0); + misc_layout->addWidget(m_backend_multithreading, 0, 0); misc_layout->addWidget(m_enable_prog_scan, 0, 1); - misc_layout->addWidget(m_backend_multithreading, 1, 0); + misc_layout->addWidget(m_cpu_cull, 1, 0); misc_layout->addWidget(m_prefer_vs_for_point_line_expansion, 1, 1); - misc_layout->addWidget(m_cpu_cull, 2, 0); + #ifdef _WIN32 m_borderless_fullscreen = new ConfigBool(tr("Borderless Fullscreen"), Config::GFX_BORDERLESS_FULLSCREEN, m_game_layer); @@ -198,6 +233,7 @@ void AdvancedWidget::CreateWidgets() main_layout->addWidget(utility_box); main_layout->addWidget(texture_dump_box); main_layout->addWidget(dump_box); + main_layout->addWidget(crop_box); main_layout->addWidget(misc_box); main_layout->addWidget(experimental_box); main_layout->addStretch(); @@ -215,6 +251,8 @@ void AdvancedWidget::ConnectWidgets() }); connect(m_enable_graphics_mods, &QCheckBox::toggled, this, [](bool checked) { emit Settings::Instance().EnableGfxModsChanged(checked); }); + connect(m_crop_custom, &QCheckBox::toggled, this, + [this](bool checked) { m_crop_custom_box->setDisabled(!checked); }); #if defined(HAVE_FFMPEG) connect(m_dump_use_lossless, &QCheckBox::toggled, this, [this](bool checked) { m_dump_bitrate->setEnabled(!checked); }); @@ -318,10 +356,6 @@ void AdvancedWidget::AddDescriptions() "However, for PNG files, levels between 3 and 6 are generally about as good as " "level 9 but finish in significantly less time.

" "If unsure, leave this at 6."); - static const char TR_CROPPING_DESCRIPTION[] = QT_TR_NOOP( - "Crops the picture from its native aspect ratio (which rarely exactly matches 4:3 or 16:9)," - " to the specific user target aspect ratio (e.g. 4:3 or 16:9).

" - "If unsure, leave this unchecked."); static const char TR_PROGRESSIVE_SCAN_DESCRIPTION[] = QT_TR_NOOP( "Enables progressive scan if supported by the emulated software. Most games don't have " "any issue with this.

If unsure, leave this " @@ -367,6 +401,35 @@ void AdvancedWidget::AddDescriptions() "unchecked."); #endif + // Crop. + static const char TR_CROP_TO_ASPECT_RATIO_DESCRIPTION[] = QT_TR_NOOP( + "Crops the picture from its native aspect ratio (which rarely exactly matches 4:3 or 16:9, " + "because it often includes overscan) to the specific user target aspect ratio (e.g. 4:3 or " + "16:9)." + "

This option was previously known as Graphics/Advanced/Misc/Crop." + "

If unsure, leave this unchecked."); + static const char TR_CROP_CUSTOM_DESCRIPTION[] = QT_TR_NOOP( + "Enables options to crop the picture by discrete native pixels (independent of the internal " + "resolution setting) in order to attain a specific " + "user target aspect ratio. Useful to resolve letterboxing issues." + "

If unsure, leave this unchecked."); + static const char TR_CROP_CUSTOM_LEFT[] = QT_TR_NOOP( + "Crops the picture left side by discrete native pixels (independent of the internal " + "resolution setting) in order to attain a specific user target aspect ratio. " + "

If unsure, leave this value to 0."); + static const char TR_CROP_CUSTOM_TOP[] = QT_TR_NOOP( + "Crops the picture top side by discrete native pixels (independent of the internal " + "resolution setting) in order to attain a specific user target aspect ratio. " + "

If unsure, leave this value to 0."); + static const char TR_CROP_CUSTOM_RIGHT[] = QT_TR_NOOP( + "Crops the picture right side by discrete native pixels (independent of the internal " + "resolution setting) in order to attain a specific user target aspect ratio. " + "

If unsure, leave this value to 0."); + static const char TR_CROP_CUSTOM_BOTTOM[] = QT_TR_NOOP( + "Crops the picture bottom side by discrete native pixels (independent of the internal " + "resolution setting) in order to attain a specific user target aspect ratio. " + "

If unsure, leave this value to 0."); + static const char IF_UNSURE_UNCHECKED[] = QT_TR_NOOP("If unsure, leave this unchecked."); @@ -388,7 +451,6 @@ void AdvancedWidget::AddDescriptions() m_dump_use_lossless->SetDescription(tr(TR_USE_LOSSLESS_DESCRIPTION)); #endif m_png_compression_level->SetDescription(tr(TR_PNG_COMPRESSION_LEVEL_DESCRIPTION)); - m_enable_cropping->SetDescription(tr(TR_CROPPING_DESCRIPTION)); m_enable_prog_scan->SetDescription(tr(TR_PROGRESSIVE_SCAN_DESCRIPTION)); m_backend_multithreading->SetDescription(tr(TR_BACKEND_MULTITHREADING_DESCRIPTION)); QString vsexpand_extra; @@ -406,6 +468,12 @@ void AdvancedWidget::AddDescriptions() #ifdef _WIN32 m_borderless_fullscreen->SetDescription(tr(TR_BORDERLESS_FULLSCREEN_DESCRIPTION)); #endif + m_crop_to_aspect_ratio->SetDescription(tr(TR_CROP_TO_ASPECT_RATIO_DESCRIPTION)); + m_crop_custom->SetDescription(tr(TR_CROP_CUSTOM_DESCRIPTION)); + m_crop_custom_left->SetDescription(tr(TR_CROP_CUSTOM_LEFT)); + m_crop_custom_top->SetDescription(tr(TR_CROP_CUSTOM_TOP)); + m_crop_custom_right->SetDescription(tr(TR_CROP_CUSTOM_RIGHT)); + m_crop_custom_bottom->SetDescription(tr(TR_CROP_CUSTOM_BOTTOM)); m_defer_efb_access_invalidation->SetDescription(tr(TR_DEFER_EFB_ACCESS_INVALIDATION_DESCRIPTION)); m_manual_texture_sampling->SetDescription(tr(TR_MANUAL_TEXTURE_SAMPLING_DESCRIPTION)); } diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h index 96ed3acbab..3cf0269dd8 100644 --- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h +++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h @@ -3,6 +3,7 @@ #pragma once +#include #include class ConfigBool; @@ -54,13 +55,21 @@ private: ConfigInteger* m_png_compression_level; // Misc - ConfigBool* m_enable_cropping; ConfigBool* m_enable_prog_scan; ConfigBool* m_backend_multithreading; ConfigBool* m_prefer_vs_for_point_line_expansion; ConfigBool* m_cpu_cull; ConfigBool* m_borderless_fullscreen; + // Misc (Cropping) + ConfigBool* m_crop_to_aspect_ratio; + ConfigBool* m_crop_custom; + QGroupBox* m_crop_custom_box; + ConfigInteger* m_crop_custom_left; + ConfigInteger* m_crop_custom_top; + ConfigInteger* m_crop_custom_right; + ConfigInteger* m_crop_custom_bottom; + // Experimental ConfigBool* m_defer_efb_access_invalidation; ConfigBool* m_manual_texture_sampling; diff --git a/Source/Core/DolphinQt/HotkeyScheduler.cpp b/Source/Core/DolphinQt/HotkeyScheduler.cpp index e2722e9cfe..ae8003db3f 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt/HotkeyScheduler.cpp @@ -397,8 +397,14 @@ void HotkeyScheduler::Run() } } - if (IsHotkey(HK_TOGGLE_CROP)) - Config::SetCurrent(Config::GFX_CROP, !Config::Get(Config::GFX_CROP)); + if (IsHotkey(HK_TOGGLE_CROP_TO_ASPECT_RATIO)) + { + Config::SetCurrent(Config::GFX_CROP_TO_ASPECT_RATIO, + !Config::Get(Config::GFX_CROP_TO_ASPECT_RATIO)); + } + + if (IsHotkey(HK_TOGGLE_CROP_CUSTOM)) + Config::SetCurrent(Config::GFX_CROP_CUSTOM, !Config::Get(Config::GFX_CROP_CUSTOM)); if (IsHotkey(HK_TOGGLE_AR)) { diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 4374782464..7c26fb2b5d 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -498,33 +498,76 @@ void Presenter::AdjustRectanglesToFitBounds(MathUtil::Rectangle* target_rec MathUtil::Rectangle* source_rect, int fb_width, int fb_height) { - const int orig_target_width = target_rect->GetWidth(); - const int orig_target_height = target_rect->GetHeight(); - const int orig_source_width = source_rect->GetWidth(); - const int orig_source_height = source_rect->GetHeight(); - if (target_rect->left < 0) + const int efb_scale = g_framebuffer_manager->GetEFBScale(); + MathUtil::Rectangle original_source_rect = *source_rect; + + // Crop the content. + if (g_ActiveConfig.bCropCustom) { - const int offset = -target_rect->left; + const int crop_left = g_ActiveConfig.iCropCustomLeft * efb_scale; + const int crop_right = g_ActiveConfig.iCropCustomRight * efb_scale; + const int crop_top = g_ActiveConfig.iCropCustomTop * efb_scale; + const int crop_bottom = g_ActiveConfig.iCropCustomBottom * efb_scale; + + source_rect->left = std::min(source_rect->left + crop_left, source_rect->right); + source_rect->right = std::max(source_rect->right - crop_right, source_rect->left); + source_rect->top = std::min(source_rect->top + crop_top, source_rect->bottom); + source_rect->bottom = std::max(source_rect->bottom - crop_bottom, source_rect->top); + + const float ratio_diff_width = static_cast(source_rect->GetWidth()) / + static_cast(original_source_rect.GetWidth()); + const float ratio_diff_height = static_cast(source_rect->GetHeight()) / + static_cast(original_source_rect.GetHeight()); + + const int new_width = static_cast(target_rect->GetWidth()) * ratio_diff_width; + const int new_height = static_cast(target_rect->GetHeight()) * ratio_diff_height; + target_rect->left = 0; - source_rect->left += offset * orig_source_width / orig_target_width; - } - if (target_rect->right > fb_width) - { - const int offset = target_rect->right - fb_width; - target_rect->right -= offset; - source_rect->right -= offset * orig_source_width / orig_target_width; - } - if (target_rect->top < 0) - { - const int offset = -target_rect->top; + target_rect->right = new_width; target_rect->top = 0; - source_rect->top += offset * orig_source_height / orig_target_height; + target_rect->bottom = new_height; } - if (target_rect->bottom > fb_height) + + // Center the content. + const double source_aspect_ratio = + static_cast(source_rect->GetWidth()) / static_cast(source_rect->GetHeight()); + const double target_aspect_ratio = + static_cast(target_rect->GetWidth()) / static_cast(target_rect->GetHeight()); + const double framebuffer_aspect_ratio = + static_cast(fb_width) / static_cast(fb_height); + const double source_target_aspect_ratio = source_aspect_ratio / target_aspect_ratio; + const double source_target_framebuffer_aspect_ratio = + source_aspect_ratio / source_target_aspect_ratio / framebuffer_aspect_ratio; + + if (source_target_framebuffer_aspect_ratio < 1) { - const int offset = target_rect->bottom - fb_height; - target_rect->bottom -= offset; - source_rect->bottom -= offset * orig_source_height / orig_target_height; + // Width. + const int new_width = + static_cast(fb_width) * static_cast(source_target_framebuffer_aspect_ratio); + const int width_diff = fb_width - new_width; + const int half_width_diff = width_diff / 2; + + target_rect->left = half_width_diff; + target_rect->right = fb_width - half_width_diff; + + // Height. + target_rect->top = 0; + target_rect->bottom = fb_height; + } + else + { + // Width. + target_rect->left = 0; + target_rect->right = fb_width; + + // Height. + const int new_height = static_cast(fb_height) / + static_cast(source_target_framebuffer_aspect_ratio); + const int height_diff = fb_height - new_height; + const int half_height_diff = height_diff / 2; + + target_rect->top = half_height_diff; + target_rect->bottom = fb_height - half_height_diff; } } @@ -607,7 +650,8 @@ std::tuple Presenter::ApplyStandardAspectCrop(float width, float h if (!allow_stretch && aspect_mode == AspectMode::Stretch) aspect_mode = AspectMode::Auto; - if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch || aspect_mode == AspectMode::Raw) + if (!g_ActiveConfig.bCropToAspectRatio || aspect_mode == AspectMode::Stretch || + aspect_mode == AspectMode::Raw) return {width, height}; // Force aspect ratios by cropping the image. @@ -687,9 +731,9 @@ void Presenter::UpdateDrawRectangle() } // The rendering window size - const float win_width = static_cast(m_backbuffer_width); - const float win_height = static_cast(m_backbuffer_height); - const float win_aspect_ratio = win_width / win_height; + const int win_width = m_backbuffer_width; + const int win_height = m_backbuffer_height; + const float win_aspect_ratio = static_cast(win_width) / static_cast(win_height); // FIXME: this breaks at very low widget sizes // Make ControllerInterface aware of the render window region actually being used @@ -734,7 +778,7 @@ void Presenter::UpdateDrawRectangle() FindClosestIntegerResolution(draw_width, draw_height, updated_draw_aspect_ratio); int_draw_width = std::get<0>(int_draw_res); int_draw_height = std::get<1>(int_draw_res); - if (!g_ActiveConfig.bCrop) + if (!g_ActiveConfig.bCropToAspectRatio) { if (g_ActiveConfig.aspect_mode != AspectMode::Stretch) { @@ -752,8 +796,13 @@ void Presenter::UpdateDrawRectangle() int_draw_height = m_xfb_rect.GetHeight(); } - m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - int_draw_width / 2.0)); - m_target_rectangle.top = static_cast(std::round(win_height / 2.0 - int_draw_height / 2.0)); + const int half_win_width = win_width / 2; + const int half_win_height = win_height / 2; + const int half_draw_width = int_draw_width / 2; + const int half_draw_height = int_draw_height / 2; + + m_target_rectangle.left = half_win_width - half_draw_width; + m_target_rectangle.top = half_win_height - half_draw_height; m_target_rectangle.right = m_target_rectangle.left + int_draw_width; m_target_rectangle.bottom = m_target_rectangle.top + int_draw_height; } @@ -791,7 +840,7 @@ std::tuple Presenter::CalculateOutputDimensions(int width, int height, if (!allow_stretch && aspect_mode == AspectMode::Stretch) aspect_mode = AspectMode::Auto; - if (!g_ActiveConfig.bCrop && aspect_mode != AspectMode::Stretch) + if (!g_ActiveConfig.bCropToAspectRatio && aspect_mode != AspectMode::Stretch) { // Find the closest integer resolution for the aspect ratio, // this avoids a small black line from being drawn on one of the four edges @@ -811,6 +860,13 @@ std::tuple Presenter::CalculateOutputDimensions(int width, int height, height = static_cast(std::ceil(scaled_height)); } + if (g_ActiveConfig.bCropCustom) + { + width = std::max(width - (g_ActiveConfig.iCropCustomLeft + g_ActiveConfig.iCropCustomRight), 0); + height = + std::max(height - (g_ActiveConfig.iCropCustomTop + g_ActiveConfig.iCropCustomBottom), 0); + } + return std::make_tuple(width, height); } diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index d2cacd7276..8685adcf15 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -98,7 +98,12 @@ void VideoConfig::Refresh() Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_STANDARD_RATIO); widescreen_heuristic_widescreen_ratio = Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_WIDESCREEN_RATIO); - bCrop = Config::Get(Config::GFX_CROP); + bCropToAspectRatio = Config::Get(Config::GFX_CROP_TO_ASPECT_RATIO); + bCropCustom = Config::Get(Config::GFX_CROP_CUSTOM); + iCropCustomLeft = Config::Get(Config::GFX_CROP_CUSTOM_LEFT); + iCropCustomTop = Config::Get(Config::GFX_CROP_CUSTOM_TOP); + iCropCustomRight = Config::Get(Config::GFX_CROP_CUSTOM_RIGHT); + iCropCustomBottom = Config::Get(Config::GFX_CROP_CUSTOM_BOTTOM); iSafeTextureCache_ColorSamples = Config::Get(Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES); bShowFPS = Config::Get(Config::GFX_SHOW_FPS); bShowFTimes = Config::Get(Config::GFX_SHOW_FTIMES); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 3f82e3afe7..eae2ac6208 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -206,7 +206,12 @@ struct VideoConfig final float widescreen_heuristic_aspect_ratio_slop = 0.f; float widescreen_heuristic_standard_ratio = 0.f; float widescreen_heuristic_widescreen_ratio = 0.f; - bool bCrop = false; // Aspect ratio controls. + bool bCropToAspectRatio = false; + bool bCropCustom = false; + int iCropCustomLeft = 0; + int iCropCustomTop = 0; + int iCropCustomRight = 0; + int iCropCustomBottom = 0; bool bShaderCache = false; // Enhancements