From 7a18e0dfa8adcf8cbf370b9212aac97ae025b574 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Fri, 8 Oct 2021 23:00:22 -0500 Subject: [PATCH] InputCommon: add support for overlaying host textures onto the final texture instead of overwriting --- .../DynamicInputTextures/DITConfiguration.cpp | 40 ++++++++++++++----- .../DynamicInputTextures/DITConfiguration.h | 3 +- .../DynamicInputTextures/DITData.h | 8 ++++ .../DynamicInputTextures/DITSpecification.cpp | 23 +++++++++++ Source/Core/InputCommon/ImageOperations.cpp | 22 ++++++++++ Source/Core/InputCommon/ImageOperations.h | 2 + .../DynamicInputTexturesV2.md | 12 ++++++ 7 files changed, 99 insertions(+), 11 deletions(-) diff --git a/Source/Core/InputCommon/DynamicInputTextures/DITConfiguration.cpp b/Source/Core/InputCommon/DynamicInputTextures/DITConfiguration.cpp index aeb0d394eba7..71b5160b9d18 100644 --- a/Source/Core/InputCommon/DynamicInputTextures/DITConfiguration.cpp +++ b/Source/Core/InputCommon/DynamicInputTextures/DITConfiguration.cpp @@ -205,7 +205,8 @@ bool Configuration::ApplyEmulatedEntry(const Configuration::HostEntries& host_en section->Get(entry.m_key, &host_key); return ApplyEmulatedSingleEntry( host_entries, std::vector{host_key}, entry.m_tag, - entry.m_region, image_to_write, preserve_aspect_ratio); + entry.m_region, entry.m_copy_type, image_to_write, + preserve_aspect_ratio); }, [&, this](const Data::EmulatedMultiEntry& entry) { return ApplyEmulatedMultiEntry(host_entries, entry, section, @@ -218,7 +219,8 @@ bool Configuration::ApplyEmulatedEntry(const Configuration::HostEntries& host_en bool Configuration::ApplyEmulatedSingleEntry(const Configuration::HostEntries& host_entries, const std::vector keys, const std::optional tag, - const Rect& region, ImagePixelData& image_to_write, + const Rect& region, Data::CopyType copy_type, + ImagePixelData& image_to_write, bool preserve_aspect_ratio) const { for (auto& host_entry : host_entries) @@ -243,8 +245,16 @@ bool Configuration::ApplyEmulatedSingleEntry(const Configuration::HostEntries& h Resize(ResizeMode::Nearest, *host_key_image, region.GetWidth(), region.GetHeight()); } - CopyImageRegion(pixel_data, image_to_write, Rect{0, 0, region.GetWidth(), region.GetHeight()}, - region); + if (copy_type == DynamicInputTextures::Data::CopyType::Overwrite) + { + CopyImageRegion(pixel_data, image_to_write, + Rect{0, 0, region.GetWidth(), region.GetHeight()}, region); + } + else + { + OverlayImageRegion(pixel_data, image_to_write, + Rect{0, 0, region.GetWidth(), region.GetHeight()}, region); + } return true; } @@ -270,8 +280,8 @@ bool Configuration::ApplyEmulatedMultiEntry(const Configuration::HostEntries& ho host_keys.push_back(host_key); } if (ApplyEmulatedSingleEntry(host_entries, host_keys, emulated_entry.m_combined_tag, - emulated_entry.m_combined_region, image_to_write, - preserve_aspect_ratio)) + emulated_entry.m_combined_region, emulated_entry.m_copy_type, + image_to_write, preserve_aspect_ratio)) { return true; } @@ -287,10 +297,20 @@ bool Configuration::ApplyEmulatedMultiEntry(const Configuration::HostEntries& ho if (apply) { - CopyImageRegion(temporary_pixel_data, image_to_write, - Rect{0, 0, emulated_entry.m_combined_region.GetWidth(), - emulated_entry.m_combined_region.GetHeight()}, - emulated_entry.m_combined_region); + if (emulated_entry.m_copy_type == DynamicInputTextures::Data::CopyType::Overwrite) + { + CopyImageRegion(temporary_pixel_data, image_to_write, + Rect{0, 0, emulated_entry.m_combined_region.GetWidth(), + emulated_entry.m_combined_region.GetHeight()}, + emulated_entry.m_combined_region); + } + else + { + OverlayImageRegion(temporary_pixel_data, image_to_write, + Rect{0, 0, emulated_entry.m_combined_region.GetWidth(), + emulated_entry.m_combined_region.GetHeight()}, + emulated_entry.m_combined_region); + } } return apply; diff --git a/Source/Core/InputCommon/DynamicInputTextures/DITConfiguration.h b/Source/Core/InputCommon/DynamicInputTextures/DITConfiguration.h index e6fbc9c9aec7..64f2b973e295 100644 --- a/Source/Core/InputCommon/DynamicInputTextures/DITConfiguration.h +++ b/Source/Core/InputCommon/DynamicInputTextures/DITConfiguration.h @@ -35,7 +35,8 @@ class Configuration bool ApplyEmulatedSingleEntry(const HostEntries& host_entries, const std::vector keys, const std::optional tag, const Rect& region, - ImagePixelData& image_to_write, bool preserve_aspect_ratio) const; + Data::CopyType copy_type, ImagePixelData& image_to_write, + bool preserve_aspect_ratio) const; bool ApplyEmulatedMultiEntry(const HostEntries& host_entries, const Data::EmulatedMultiEntry& emulated_entry, const IniFile::Section* section, ImagePixelData& image_to_write, diff --git a/Source/Core/InputCommon/DynamicInputTextures/DITData.h b/Source/Core/InputCommon/DynamicInputTextures/DITData.h index b9760e5f332a..9e1f312d422b 100644 --- a/Source/Core/InputCommon/DynamicInputTextures/DITData.h +++ b/Source/Core/InputCommon/DynamicInputTextures/DITData.h @@ -23,17 +23,25 @@ struct Data struct EmulatedMultiEntry; using EmulatedEntry = std::variant; + enum class CopyType + { + Overwrite, + Overlay + }; + struct EmulatedSingleEntry { std::string m_key; std::optional m_tag; Rect m_region; + CopyType m_copy_type = CopyType::Overwrite; }; struct EmulatedMultiEntry { std::string m_combined_tag; Rect m_combined_region; + CopyType m_copy_type = CopyType::Overwrite; std::vector m_sub_entries; }; diff --git a/Source/Core/InputCommon/DynamicInputTextures/DITSpecification.cpp b/Source/Core/InputCommon/DynamicInputTextures/DITSpecification.cpp index 1a52fda61d8b..2ddbbbe4eac3 100644 --- a/Source/Core/InputCommon/DynamicInputTextures/DITSpecification.cpp +++ b/Source/Core/InputCommon/DynamicInputTextures/DITSpecification.cpp @@ -98,6 +98,29 @@ std::optional GetEmulatedEntry(const picojson::object& entr entry.m_key = *entry_key; entry.m_tag = GetJsonValueFromMap(entry_obj, "tag", json_file, false); + const auto copy_type = + GetJsonValueFromMap(entry_obj, "copy_type", json_file, false); + if (copy_type) + { + const auto& copy_type_str = *copy_type; + if (copy_type_str == "overwrite") + { + entry.m_copy_type = DynamicInputTextures::Data::CopyType::Overwrite; + } + else if (copy_type_str == "overlay") + { + entry.m_copy_type = DynamicInputTextures::Data::CopyType::Overlay; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Failed to load dynamic input json file '{}' because field " + "'copy_type' had invalid value '{}'", + json_file, copy_type_str); + return std::nullopt; + } + } + const auto entry_region = GetRect(entry_obj, "region", json_file); if (!entry_region) return std::nullopt; diff --git a/Source/Core/InputCommon/ImageOperations.cpp b/Source/Core/InputCommon/ImageOperations.cpp index c7dac141c90d..24b41ab359c3 100644 --- a/Source/Core/InputCommon/ImageOperations.cpp +++ b/Source/Core/InputCommon/ImageOperations.cpp @@ -45,6 +45,28 @@ void CopyImageRegion(const ImagePixelData& src, ImagePixelData& dst, const Rect& } } +void OverlayImageRegion(const ImagePixelData& src, ImagePixelData& dst, const Rect& src_region, + const Rect& dst_region, const Pixel& pixel_skip_color) +{ + if (src_region.GetWidth() != dst_region.GetWidth() || + src_region.GetHeight() != dst_region.GetHeight()) + { + return; + } + + for (u32 x = 0; x < dst_region.GetWidth(); x++) + { + for (u32 y = 0; y < dst_region.GetHeight(); y++) + { + const auto& src_pixel = src.pixels[(y + src_region.top) * src.width + x + src_region.left]; + if (src_pixel == pixel_skip_color) + continue; + + dst.pixels[(y + dst_region.top) * dst.width + x + dst_region.left] = src_pixel; + } + } +} + std::optional LoadImage(const std::string& path) { File::IOFile file; diff --git a/Source/Core/InputCommon/ImageOperations.h b/Source/Core/InputCommon/ImageOperations.h index da8b938c15c6..86b722db2623 100644 --- a/Source/Core/InputCommon/ImageOperations.h +++ b/Source/Core/InputCommon/ImageOperations.h @@ -47,6 +47,8 @@ struct ImagePixelData void CopyImageRegion(const ImagePixelData& src, ImagePixelData& dst, const Rect& src_region, const Rect& dst_region); +void OverlayImageRegion(const ImagePixelData& src, ImagePixelData& dst, const Rect& src_region, + const Rect& dst_region, const Pixel& pixel_skip_color = Pixel{}); std::optional LoadImage(const std::string& path); diff --git a/docs/DynamicInputTextures/DynamicInputTexturesV2.md b/docs/DynamicInputTextures/DynamicInputTexturesV2.md index 0856f1b03797..a743ed4f303f 100644 --- a/docs/DynamicInputTextures/DynamicInputTexturesV2.md +++ b/docs/DynamicInputTextures/DynamicInputTexturesV2.md @@ -84,6 +84,17 @@ Here is an example of a single key with a tag: } ``` +By default the region expressed by the key will be overwritten by the host image. For many games though, there are colors or details that we do not want to completely remove and therefore want to overlay the host image on top of the underlying game texture. To do this you can use "copy_type". By default "copy_type" defaults to 'overwrite' but you may also set it to 'overlay'. Here is an example of a key with the 'overlay' copy_type: + +```js +{ + "bind_type": "single", + "key": "Buttons/A", + "copy_type": "overlay", + "region": [0, 0, 30, 30] +} +``` + ##### Multi Key The single key case works well when an image maps directly to Dolphin's emulated controller bindings. However, what about the case where there is a dpad icon? Dolphin has four mappings for that. This is where a "bind_type" of ``multi`` is helpful. @@ -96,6 +107,7 @@ Here's an example of that: { "bind_type": "multi", "tag": "dpad", + "copy_type": "overlay", "region": [0, 0, 45, 45], "sub_entries": [ {