mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 13:30: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,29 +300,86 @@ TextureAtlas::Impl::LoadImage(const std::string& imagePath)
|
|||
|
||||
boost::gil::rgba8_image_t image;
|
||||
|
||||
QFile imageFile(imagePath.c_str());
|
||||
QUrl url = QUrl::fromUserInput(QString::fromStdString(imagePath));
|
||||
|
||||
imageFile.open(QIODevice::ReadOnly);
|
||||
|
||||
if (!imageFile.isOpen())
|
||||
if (url.isLocalFile())
|
||||
{
|
||||
logger_->error("Could not open image: {}", imagePath);
|
||||
return image;
|
||||
QFile imageFile(imagePath.c_str());
|
||||
|
||||
imageFile.open(QIODevice::ReadOnly);
|
||||
|
||||
if (!imageFile.isOpen())
|
||||
{
|
||||
logger_->error("Could not open image: {}", imagePath);
|
||||
return image;
|
||||
}
|
||||
|
||||
boost::iostreams::stream<util::IoDeviceSource> dataStream(imageFile);
|
||||
|
||||
try
|
||||
{
|
||||
boost::gil::read_and_convert_image(
|
||||
dataStream, image, boost::gil::png_tag());
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
logger_->error("Error reading image: {}", ex.what());
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
boost::iostreams::stream<util::IoDeviceSource> dataStream(imageFile);
|
||||
|
||||
boost::gil::image<boost::gil::rgba8_pixel_t, false> x;
|
||||
|
||||
try
|
||||
else
|
||||
{
|
||||
boost::gil::read_and_convert_image(
|
||||
dataStream, image, boost::gil::png_tag());
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
logger_->error("Error reading image: {}", ex.what());
|
||||
return image;
|
||||
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