mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 01:50:06 +00:00 
			
		
		
		
	Load placefile icons into texture atlas cache
This commit is contained in:
		
							parent
							
								
									1f2d5227c4
								
							
						
					
					
						commit
						0f9fbdbf63
					
				
					 12 changed files with 237 additions and 44 deletions
				
			
		|  | @ -46,7 +46,8 @@ set(HDR_CONFIG source/scwx/qt/config/county_database.hpp | |||
|                source/scwx/qt/config/radar_site.hpp) | ||||
| set(SRC_CONFIG source/scwx/qt/config/county_database.cpp | ||||
|                source/scwx/qt/config/radar_site.cpp) | ||||
| set(SRC_EXTERNAL source/scwx/qt/external/stb_rect_pack.cpp) | ||||
| set(SRC_EXTERNAL source/scwx/qt/external/stb_image.cpp | ||||
|                  source/scwx/qt/external/stb_rect_pack.cpp) | ||||
| set(HDR_GL source/scwx/qt/gl/gl.hpp | ||||
|            source/scwx/qt/gl/gl_context.hpp | ||||
|            source/scwx/qt/gl/shader_program.hpp | ||||
|  | @ -203,6 +204,7 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp | |||
|              source/scwx/qt/util/geographic_lib.hpp | ||||
|              source/scwx/qt/util/json.hpp | ||||
|              source/scwx/qt/util/maplibre.hpp | ||||
|              source/scwx/qt/util/network.hpp | ||||
|              source/scwx/qt/util/streams.hpp | ||||
|              source/scwx/qt/util/texture_atlas.hpp | ||||
|              source/scwx/qt/util/q_file_buffer.hpp | ||||
|  | @ -215,6 +217,7 @@ set(SRC_UTIL source/scwx/qt/util/color.cpp | |||
|              source/scwx/qt/util/geographic_lib.cpp | ||||
|              source/scwx/qt/util/json.cpp | ||||
|              source/scwx/qt/util/maplibre.cpp | ||||
|              source/scwx/qt/util/network.cpp | ||||
|              source/scwx/qt/util/texture_atlas.cpp | ||||
|              source/scwx/qt/util/q_file_buffer.cpp | ||||
|              source/scwx/qt/util/q_file_input_stream.cpp | ||||
|  |  | |||
							
								
								
									
										4
									
								
								scwx-qt/source/scwx/qt/external/stb_image.cpp
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								scwx-qt/source/scwx/qt/external/stb_image.cpp
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| #define STB_IMAGE_IMPLEMENTATION | ||||
| #define STBI_ASSERT(x) | ||||
| #define STBI_FAILURE_USERMSG | ||||
| #include <stb_image.h> | ||||
|  | @ -10,6 +10,7 @@ | |||
| 
 | ||||
| #include <aws/core/Aws.h> | ||||
| #include <boost/asio.hpp> | ||||
| #include <fmt/format.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <QApplication> | ||||
| #include <QTranslator> | ||||
|  |  | |||
|  | @ -1,4 +1,6 @@ | |||
| #include <scwx/qt/manager/placefile_manager.hpp> | ||||
| #include <scwx/qt/manager/resource_manager.hpp> | ||||
| #include <scwx/qt/util/network.hpp> | ||||
| #include <scwx/gr/placefile.hpp> | ||||
| #include <scwx/network/cpr.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
|  | @ -35,7 +37,7 @@ public: | |||
|    explicit Impl(PlacefileManager* self) : self_ {self} {} | ||||
|    ~Impl() {} | ||||
| 
 | ||||
|    static std::string NormalizeUrl(const std::string& urlString); | ||||
|    static void LoadResources(const std::shared_ptr<gr::Placefile>& placefile); | ||||
| 
 | ||||
|    boost::asio::thread_pool threadPool_ {1u}; | ||||
| 
 | ||||
|  | @ -67,7 +69,7 @@ public: | |||
| 
 | ||||
|    void Update(); | ||||
|    void UpdateAsync(); | ||||
|    void UpdatePlacefile(std::shared_ptr<gr::Placefile> placefile); | ||||
|    void UpdatePlacefile(const std::shared_ptr<gr::Placefile>& placefile); | ||||
| 
 | ||||
|    Impl* p; | ||||
| 
 | ||||
|  | @ -78,10 +80,6 @@ public: | |||
|    boost::asio::thread_pool       threadPool_ {1u}; | ||||
|    boost::asio::steady_timer      refreshTimer_ {threadPool_}; | ||||
|    std::mutex                     refreshMutex_ {}; | ||||
| 
 | ||||
| signals: | ||||
|    void Updated(const std::string&             name, | ||||
|                 std::shared_ptr<gr::Placefile> placefile); | ||||
| }; | ||||
| 
 | ||||
| PlacefileManager::PlacefileManager() : p(std::make_unique<Impl>(this)) {} | ||||
|  | @ -167,7 +165,7 @@ void PlacefileManager::set_placefile_thresholded(const std::string& name, | |||
| void PlacefileManager::set_placefile_url(const std::string& name, | ||||
|                                          const std::string& newUrl) | ||||
| { | ||||
|    std::string normalizedUrl = Impl::NormalizeUrl(newUrl); | ||||
|    std::string normalizedUrl = util::network::NormalizeUrl(newUrl); | ||||
| 
 | ||||
|    std::unique_lock lock(p->placefileRecordLock_); | ||||
| 
 | ||||
|  | @ -235,7 +233,7 @@ PlacefileManager::GetActivePlacefiles() | |||
| 
 | ||||
| void PlacefileManager::AddUrl(const std::string& urlString) | ||||
| { | ||||
|    std::string normalizedUrl = Impl::NormalizeUrl(urlString); | ||||
|    std::string normalizedUrl = util::network::NormalizeUrl(urlString); | ||||
| 
 | ||||
|    std::unique_lock lock(p->placefileRecordLock_); | ||||
| 
 | ||||
|  | @ -318,6 +316,8 @@ void PlacefileManager::LoadFile(const std::string& filename) | |||
| 
 | ||||
| void PlacefileManager::Impl::PlacefileRecord::Update() | ||||
| { | ||||
|    logger_->debug("Update: {}", name_); | ||||
| 
 | ||||
|    // Make a copy of name in the event it changes.
 | ||||
|    const std::string name {name_}; | ||||
| 
 | ||||
|  | @ -399,6 +399,9 @@ void PlacefileManager::Impl::PlacefileRecord::Update() | |||
| 
 | ||||
|    if (updatedPlacefile != nullptr) | ||||
|    { | ||||
|       // Load placefile resources
 | ||||
|       Impl::LoadResources(updatedPlacefile); | ||||
| 
 | ||||
|       // Check the name matches, in case the name updated
 | ||||
|       if (name_ == name) | ||||
|       { | ||||
|  | @ -421,7 +424,7 @@ void PlacefileManager::Impl::PlacefileRecord::UpdateAsync() | |||
| } | ||||
| 
 | ||||
| void PlacefileManager::Impl::PlacefileRecord::UpdatePlacefile( | ||||
|    std::shared_ptr<gr::Placefile> placefile) | ||||
|    const std::shared_ptr<gr::Placefile>& placefile) | ||||
| { | ||||
|    // Update placefile
 | ||||
|    placefile_ = placefile; | ||||
|  | @ -448,22 +451,22 @@ std::shared_ptr<PlacefileManager> PlacefileManager::Instance() | |||
|    return placefileManager; | ||||
| } | ||||
| 
 | ||||
| std::string PlacefileManager::Impl::NormalizeUrl(const std::string& urlString) | ||||
| void PlacefileManager::Impl::LoadResources( | ||||
|    const std::shared_ptr<gr::Placefile>& placefile) | ||||
| { | ||||
|    std::string normalizedUrl; | ||||
|    const auto iconFiles = placefile->icon_files(); | ||||
| 
 | ||||
|    // Normalize URL string
 | ||||
|    QUrl url = QUrl::fromUserInput(QString::fromStdString(urlString)); | ||||
|    if (url.isLocalFile()) | ||||
|    { | ||||
|       normalizedUrl = QDir::toNativeSeparators(url.toLocalFile()).toStdString(); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       normalizedUrl = urlString; | ||||
|    } | ||||
|    const QUrl baseUrl = | ||||
|       QUrl::fromUserInput(QString::fromStdString(placefile->name())); | ||||
| 
 | ||||
|    return normalizedUrl; | ||||
|    // TODO: Parallelize
 | ||||
|    for (auto& iconFile : iconFiles) | ||||
|    { | ||||
|       QUrl fileUrl     = QUrl(QString::fromStdString(iconFile->filename_)); | ||||
|       QUrl resolvedUrl = baseUrl.resolved(fileUrl); | ||||
| 
 | ||||
|       ResourceManager::LoadImageResource(resolvedUrl.toString().toStdString()); | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| } // namespace manager
 | ||||
|  |  | |||
|  | @ -5,9 +5,8 @@ | |||
| #include <scwx/qt/util/texture_atlas.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| #include <imgui.h> | ||||
| 
 | ||||
| #include <QFontDatabase> | ||||
| #include <imgui.h> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
|  | @ -62,6 +61,12 @@ std::shared_ptr<util::Font> Font(types::Font font) | |||
|    return nullptr; | ||||
| } | ||||
| 
 | ||||
| void LoadImageResource(const std::string& urlString) | ||||
| { | ||||
|    util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); | ||||
|    textureAtlas.CacheTexture(urlString, urlString); | ||||
| } | ||||
| 
 | ||||
| static void LoadFonts() | ||||
| { | ||||
|    for (auto& fontName : fontNames_) | ||||
|  |  | |||
|  | @ -18,6 +18,8 @@ void Shutdown(); | |||
| int                         FontId(types::Font font); | ||||
| std::shared_ptr<util::Font> Font(types::Font font); | ||||
| 
 | ||||
| void LoadImageResource(const std::string& urlString); | ||||
| 
 | ||||
| } // namespace ResourceManager
 | ||||
| } // namespace manager
 | ||||
| } // namespace qt
 | ||||
|  |  | |||
							
								
								
									
										36
									
								
								scwx-qt/source/scwx/qt/util/network.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								scwx-qt/source/scwx/qt/util/network.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| #include <scwx/qt/util/network.hpp> | ||||
| 
 | ||||
| #include <QDir> | ||||
| #include <QUrl> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace util | ||||
| { | ||||
| namespace network | ||||
| { | ||||
| 
 | ||||
| std::string NormalizeUrl(const std::string& urlString) | ||||
| { | ||||
|    std::string normalizedUrl; | ||||
| 
 | ||||
|    // Normalize URL string
 | ||||
|    QUrl url = QUrl::fromUserInput(QString::fromStdString(urlString)); | ||||
|    if (url.isLocalFile()) | ||||
|    { | ||||
|       normalizedUrl = QDir::toNativeSeparators(url.toLocalFile()).toStdString(); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       normalizedUrl = urlString; | ||||
|    } | ||||
| 
 | ||||
|    return normalizedUrl; | ||||
| } | ||||
| 
 | ||||
| } // namespace network
 | ||||
| } // namespace util
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
							
								
								
									
										26
									
								
								scwx-qt/source/scwx/qt/util/network.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								scwx-qt/source/scwx/qt/util/network.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace util | ||||
| { | ||||
| namespace network | ||||
| { | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Converts a local or remote URL to a consistent format. | ||||
|  * | ||||
|  * @param [in] urlString URL to normalize | ||||
|  * | ||||
|  * @return Normalized URL string | ||||
|  */ | ||||
| std::string NormalizeUrl(const std::string& urlString); | ||||
| 
 | ||||
| } // namespace network
 | ||||
| } // namespace util
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
|  | @ -1,5 +1,6 @@ | |||
| #include <scwx/qt/util/texture_atlas.hpp> | ||||
| #include <scwx/qt/util/streams.hpp> | ||||
| #include <scwx/network/cpr.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| #include <shared_mutex> | ||||
|  | @ -13,8 +14,11 @@ | |||
| 
 | ||||
| #include <boost/gil/extension/io/png.hpp> | ||||
| #include <boost/iostreams/stream.hpp> | ||||
| #include <cpr/cpr.h> | ||||
| #include <stb_image.h> | ||||
| #include <stb_rect_pack.h> | ||||
| #include <QFile> | ||||
| #include <QUrl> | ||||
| 
 | ||||
| #if defined(_MSC_VER) | ||||
| #   pragma warning(pop) | ||||
|  | @ -48,6 +52,9 @@ public: | |||
|    std::unordered_map<std::string, std::string> texturePathMap_; | ||||
|    std::shared_mutex                            texturePathMutex_; | ||||
| 
 | ||||
|    std::shared_mutex textureCacheMutex_; | ||||
|    std::unordered_map<std::string, boost::gil::rgba8_image_t> textureCache_; | ||||
| 
 | ||||
|    boost::gil::rgba8_image_t                          atlas_; | ||||
|    std::unordered_map<std::string, TextureAttributes> atlasMap_; | ||||
|    std::shared_mutex                                  atlasMutex_; | ||||
|  | @ -66,6 +73,34 @@ void TextureAtlas::RegisterTexture(const std::string& name, | |||
|    p->texturePathMap_.insert_or_assign(name, path); | ||||
| } | ||||
| 
 | ||||
| bool TextureAtlas::CacheTexture(const std::string& name, | ||||
|                                 const std::string& path) | ||||
| { | ||||
|    // If the image is already loaded, we don't need to load it again
 | ||||
|    { | ||||
|       std::shared_lock lock(p->textureCacheMutex_); | ||||
| 
 | ||||
|       if (p->textureCache_.contains(path)) | ||||
|       { | ||||
|          return false; | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    // Attempt to load the image
 | ||||
|    boost::gil::rgba8_image_t image = TextureAtlas::Impl::LoadImage(path); | ||||
| 
 | ||||
|    // If the image is valid
 | ||||
|    if (image.width() > 0 && image.height() > 0) | ||||
|    { | ||||
|       // Store it in the texture cache
 | ||||
|       std::unique_lock lock(p->textureCacheMutex_); | ||||
| 
 | ||||
|       p->textureCache_.emplace(name, std::move(image)); | ||||
|    } | ||||
| 
 | ||||
|    return true; | ||||
| } | ||||
| 
 | ||||
| void TextureAtlas::BuildAtlas(size_t width, size_t height) | ||||
| { | ||||
|    logger_->debug("Building {}x{} texture atlas", width, height); | ||||
|  | @ -110,6 +145,11 @@ void TextureAtlas::BuildAtlas(size_t width, size_t height) | |||
|                     }); | ||||
|    } | ||||
| 
 | ||||
|    // TODO: Cached images
 | ||||
|    { | ||||
| 
 | ||||
|    } | ||||
| 
 | ||||
|    // Pack images
 | ||||
|    { | ||||
|       logger_->trace("Packing {} images", images.size()); | ||||
|  | @ -260,6 +300,10 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) | |||
| 
 | ||||
|    boost::gil::rgba8_image_t image; | ||||
| 
 | ||||
|    QUrl url = QUrl::fromUserInput(QString::fromStdString(imagePath)); | ||||
| 
 | ||||
|    if (url.isLocalFile()) | ||||
|    { | ||||
|       QFile imageFile(imagePath.c_str()); | ||||
| 
 | ||||
|       imageFile.open(QIODevice::ReadOnly); | ||||
|  | @ -272,8 +316,6 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) | |||
| 
 | ||||
|       boost::iostreams::stream<util::IoDeviceSource> dataStream(imageFile); | ||||
| 
 | ||||
|    boost::gil::image<boost::gil::rgba8_pixel_t, false> x; | ||||
| 
 | ||||
|       try | ||||
|       { | ||||
|          boost::gil::read_and_convert_image( | ||||
|  | @ -284,6 +326,61 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath) | |||
|          logger_->error("Error reading image: {}", ex.what()); | ||||
|          return image; | ||||
|       } | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       auto response = cpr::Get(cpr::Url {imagePath}, network::cpr::GetHeader()); | ||||
| 
 | ||||
|       if (cpr::status::is_success(response.status_code)) | ||||
|       { | ||||
|          // Use stbi, since we can only guess the image format
 | ||||
|          static constexpr int desiredChannels = 4; | ||||
| 
 | ||||
|          int width; | ||||
|          int height; | ||||
|          int numChannels; | ||||
| 
 | ||||
|          unsigned char* pixelData = stbi_load_from_memory( | ||||
|             reinterpret_cast<const unsigned char*>(response.text.data()), | ||||
|             static_cast<int>( | ||||
|                std::clamp<std::size_t>(response.text.size(), 0, INT32_MAX)), | ||||
|             &width, | ||||
|             &height, | ||||
|             &numChannels, | ||||
|             desiredChannels); | ||||
| 
 | ||||
|          if (pixelData == nullptr) | ||||
|          { | ||||
|             logger_->error("Error loading image: {}", stbi_failure_reason()); | ||||
|             return image; | ||||
|          } | ||||
| 
 | ||||
|          // Create a view pointing to the STB image data
 | ||||
|          auto imageView = boost::gil::interleaved_view( | ||||
|             width, | ||||
|             height, | ||||
|             reinterpret_cast<boost::gil::rgba8_pixel_t*>(pixelData), | ||||
|             width * desiredChannels); | ||||
| 
 | ||||
|          // Copy the view to the destination image
 | ||||
|          image = boost::gil::rgba8_image_t(imageView); | ||||
| 
 | ||||
|          if (numChannels == 3) | ||||
|          { | ||||
|             // TODO: If no alpha channel, replace black with transparent
 | ||||
|          } | ||||
| 
 | ||||
|          stbi_image_free(pixelData); | ||||
|       } | ||||
|       else if (response.status_code == 0) | ||||
|       { | ||||
|          logger_->error("Error loading image: {}", response.error.message); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          logger_->error("Error loading image: {}", response.status_line); | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    return image; | ||||
| } | ||||
|  |  | |||
|  | @ -67,6 +67,7 @@ public: | |||
|    static TextureAtlas& Instance(); | ||||
| 
 | ||||
|    void   RegisterTexture(const std::string& name, const std::string& path); | ||||
|    bool   CacheTexture(const std::string& name, const std::string& path); | ||||
|    void   BuildAtlas(size_t width, size_t height); | ||||
|    GLuint BufferAtlas(gl::OpenGLFunctions& gl); | ||||
| 
 | ||||
|  |  | |||
|  | @ -109,6 +109,8 @@ public: | |||
|     */ | ||||
|    std::vector<std::shared_ptr<DrawItem>> GetDrawItems(); | ||||
| 
 | ||||
|    std::vector<std::shared_ptr<const IconFile>> icon_files(); | ||||
| 
 | ||||
|    std::string                                            name() const; | ||||
|    std::string                                            title() const; | ||||
|    std::unordered_map<std::size_t, std::shared_ptr<Font>> fonts(); | ||||
|  |  | |||
|  | @ -86,6 +86,19 @@ std::vector<std::shared_ptr<Placefile::DrawItem>> Placefile::GetDrawItems() | |||
|    return p->drawItems_; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::shared_ptr<const Placefile::IconFile>> Placefile::icon_files() | ||||
| { | ||||
|    std::vector<std::shared_ptr<const Placefile::IconFile>> iconFiles {}; | ||||
|    iconFiles.reserve(p->iconFiles_.size()); | ||||
| 
 | ||||
|    std::transform(p->iconFiles_.begin(), | ||||
|                   p->iconFiles_.end(), | ||||
|                   std::back_inserter(iconFiles), | ||||
|                   [](auto& iconFile) { return iconFile.second; }); | ||||
| 
 | ||||
|    return iconFiles; | ||||
| } | ||||
| 
 | ||||
| std::string Placefile::name() const | ||||
| { | ||||
|    return p->name_; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat