diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index f2aeda4c2..de14ed688 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -1382,7 +1382,7 @@ void FullscreenUI::DrawLandingWindow() { ResetFocusHere(); - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/address-book-new.png"), FSUI_CSTR("Game List"), + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/pepe.svg", 256, 256), FSUI_CSTR("Game List"), FSUI_CSTR("Launch a game from images scanned from your game directories."))) { SwitchToGameList(); @@ -5777,7 +5777,8 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading) } if (ActiveButton(FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Close Menu"), false, true, - LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) || WantsToCloseMenu()) + LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) || + WantsToCloseMenu()) { is_open = false; ignore_close_request = true; diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index c47acf614..38e1e9a19 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -77,7 +77,7 @@ target_precompile_headers(util PRIVATE "pch.h") target_include_directories(util PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(util PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") target_link_libraries(util PUBLIC common simpleini imgui) -target_link_libraries(util PRIVATE libchdr JPEG::JPEG PNG::PNG WebP::libwebp ZLIB::ZLIB SoundTouch::SoundTouchDLL xxhash Zstd::Zstd reshadefx) +target_link_libraries(util PRIVATE libchdr JPEG::JPEG PNG::PNG WebP::libwebp lunasvg::lunasvg ZLIB::ZLIB SoundTouch::SoundTouchDLL xxhash Zstd::Zstd reshadefx) if(ENABLE_X11) target_compile_definitions(util PRIVATE "-DENABLE_X11=1") diff --git a/src/util/image.cpp b/src/util/image.cpp index 4a7201ea7..f01214fb4 100644 --- a/src/util/image.cpp +++ b/src/util/image.cpp @@ -5,6 +5,7 @@ #include "common/assert.h" #include "common/bitutils.h" +#include "common/error.h" #include "common/fastjmp.h" #include "common/file_system.h" #include "common/log.h" @@ -12,6 +13,8 @@ #include "common/scoped_guard.h" #include "common/string_util.h" +#include "lunasvg_c.h" + #include #include #include @@ -151,6 +154,76 @@ bool RGBA8Image::LoadFromBuffer(std::string_view filename, const void* buffer, s return handler->buffer_loader(this, buffer, buffer_size); } +bool RGBA8Image::LoadFromFileWithSizeHint(std::string_view filename, std::FILE* fp, u32 width_hint, u32 height_hint, + Error* error) +{ + if (StringUtil::EqualNoCase(Path::GetExtension(filename), "svg")) + { + const std::optional> data = FileSystem::ReadBinaryFile(fp, error); + if (!data.has_value()) + return false; + + return RasterizeSVG(data->cspan(), width_hint, height_hint, error); + } + + return LoadFromFile(filename, fp); +} + +bool RGBA8Image::LoadFromBufferWithSizeHint(std::string_view filename, const void* buffer, size_t buffer_size, + u32 width_hint, u32 height_hint, Error* error /*= nullptr*/) +{ + if (StringUtil::EqualNoCase(Path::GetExtension(filename), "svg")) + { + return RasterizeSVG(std::span(static_cast(buffer), buffer_size), width_hint, height_hint, + error); + } + else + { + return LoadFromBuffer(filename, buffer, buffer_size); + } +} + +bool RGBA8Image::RasterizeSVG(const std::span data, u32 width, u32 height, Error* error) +{ + if (width == 0 || height == 0) + { + Error::SetStringFmt(error, "Invalid dimensions: {}x{}", width, height); + return false; + } + + std::unique_ptr doc( + lunasvg_document_load_from_data(data.data(), data.size()), lunasvg_document_destroy); + if (!doc) + { + Error::SetStringView(error, "lunasvg_document_load_from_data() failed"); + return false; + } + +#if 0 + std::unique_ptr bitmap(lunasvg_bitmap_create_with_size(width, height), + lunasvg_bitmap_destroy); + if (!bitmap) + { + Error::SetStringView(error, "lunasvg_bitmap_create_with_size() failed"); + return false; + } + + std::unique_ptr matrix(lunasvg_matrix_create(), lunasvg_matrix_destroy); + lunasvg_matrix_identity(matrix.get()); +#endif + std::unique_ptr bitmap( + lunasvg_document_render_to_bitmap(doc.get(), width, height, 0), lunasvg_bitmap_destroy); + if (!bitmap) + { + Error::SetStringView(error, "lunasvg_document_render_to_bitmap() failed"); + return false; + } + + SetPixels(width, height, lunasvg_bitmap_data(bitmap.get()), lunasvg_bitmap_stride(bitmap.get())); + SwapBGRAToRGBA(); + return true; +} + bool RGBA8Image::SaveToFile(std::string_view filename, std::FILE* fp, u8 quality) const { const std::string_view extension(Path::GetExtension(filename)); @@ -186,6 +259,23 @@ std::optional> RGBA8Image::SaveToBuffer(std::string_view filenam return ret; } +void RGBA8Image::SwapBGRAToRGBA() +{ + // TODO: GSVectorify + for (u32 y = 0; y < m_height; y++) + { + u8* pixels = reinterpret_cast(m_pixels.data()) + (y * sizeof(u32) * m_width); + for (u32 x = 0; x < m_width; x++) + { + u32 pixel; + std::memcpy(&pixel, pixels, sizeof(pixel)); + pixel = (pixel & 0xFF00FF00) | ((pixel & 0xFF) << 16) | ((pixel >> 16) & 0xFF); + std::memcpy(pixels, &pixel, sizeof(pixel)); + pixels += sizeof(pixel); + } + } +} + #if 0 void RGBA8Image::Resize(u32 new_width, u32 new_height) diff --git a/src/util/image.h b/src/util/image.h index 463e3327c..518b40c8e 100644 --- a/src/util/image.h +++ b/src/util/image.h @@ -9,9 +9,12 @@ #include #include #include +#include #include #include +class Error; + template class Image { @@ -98,6 +101,28 @@ public: m_pixels = std::move(pixels); } + void SetPixels(u32 width, u32 height, const void* data, u32 stride) + { + const u32 copy_width = width * sizeof(PixelType); + if (stride == copy_width) + { + SetPixels(width, height, static_cast(data)); + return; + } + + m_width = width; + m_height = height; + m_pixels.resize(width, height); + PixelType* out_ptr = m_pixels.data(); + const u8* in_ptr = static_cast(data); + for (u32 row = 0; row < height; row++) + { + std::memcpy(out_ptr, in_ptr, copy_width); + out_ptr += width; + in_ptr += stride; + } + } + std::vector TakePixels() { m_width = 0; @@ -130,7 +155,16 @@ public: bool LoadFromFile(std::string_view filename, std::FILE* fp); bool LoadFromBuffer(std::string_view filename, const void* buffer, size_t buffer_size); + bool LoadFromFileWithSizeHint(std::string_view filename, std::FILE* fp, u32 width_hint, u32 height_hint, + Error* error = nullptr); + bool LoadFromBufferWithSizeHint(std::string_view filename, const void* buffer, size_t buffer_size, u32 width_hint, + u32 height_hint, Error* error = nullptr); + + bool RasterizeSVG(const std::span data, u32 width, u32 height, Error* error = nullptr); + bool SaveToFile(const char* filename, u8 quality = DEFAULT_SAVE_QUALITY) const; bool SaveToFile(std::string_view filename, std::FILE* fp, u8 quality = DEFAULT_SAVE_QUALITY) const; std::optional> SaveToBuffer(std::string_view filename, u8 quality = DEFAULT_SAVE_QUALITY) const; +private: + void SwapBGRAToRGBA(); }; diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index bcbb642c5..d125a773c 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -43,7 +43,7 @@ using MessageDialogCallbackVariant = std::variant LoadTextureImage(std::string_view path); +static std::optional LoadTextureImage(std::string_view path, u32 width_hint, u32 height_hint); static std::shared_ptr UploadTexture(std::string_view path, const RGBA8Image& image); static void TextureLoaderThread(); @@ -298,8 +298,12 @@ std::unique_ptr ImGuiFullscreen::CreateTextureFromImage(const RGBA8I return ret; } -std::optional ImGuiFullscreen::LoadTextureImage(std::string_view path) +std::optional ImGuiFullscreen::LoadTextureImage(std::string_view path, u32 width_hint, u32 height_hint) { + // TODO: GSVector + width_hint = static_cast(std::ceil(LayoutScale(static_cast(width_hint)))); + height_hint = static_cast(std::ceil(LayoutScale(static_cast(height_hint)))); + std::optional image; if (Path::IsAbsolute(path)) { @@ -309,7 +313,7 @@ std::optional ImGuiFullscreen::LoadTextureImage(std::string_view pat if (fp) { image = RGBA8Image(); - if (!image->LoadFromFile(path_str.c_str(), fp.get())) + if (!image->LoadFromFileWithSizeHint(path_str.c_str(), fp.get(), width_hint, height_hint)) { ERROR_LOG("Failed to read texture file '{}'", path); image.reset(); @@ -326,7 +330,7 @@ std::optional ImGuiFullscreen::LoadTextureImage(std::string_view pat if (data.has_value()) { image = RGBA8Image(); - if (!image->LoadFromBuffer(path, data->data(), data->size())) + if (!image->LoadFromBufferWithSizeHint(path, data->data(), data->size(), width_hint, height_hint)) { ERROR_LOG("Failed to read texture resource '{}'", path); image.reset(); @@ -356,9 +360,9 @@ std::shared_ptr ImGuiFullscreen::UploadTexture(std::string_view path return std::shared_ptr(texture.release(), GPUDevice::PooledTextureDeleter()); } -std::shared_ptr ImGuiFullscreen::LoadTexture(std::string_view path) +std::shared_ptr ImGuiFullscreen::LoadTexture(std::string_view path, u32 width_hint, u32 height_hint) { - std::optional image(LoadTextureImage(path)); + std::optional image(LoadTextureImage(path, width_hint, height_hint)); if (image.has_value()) { std::shared_ptr ret(UploadTexture(path, image.value())); @@ -369,12 +373,12 @@ std::shared_ptr ImGuiFullscreen::LoadTexture(std::string_view path) return s_placeholder_texture; } -GPUTexture* ImGuiFullscreen::GetCachedTexture(std::string_view name) +GPUTexture* ImGuiFullscreen::GetCachedTexture(std::string_view name, u32 width_hint, u32 height_hint) { std::shared_ptr* tex_ptr = s_texture_cache.Lookup(name); if (!tex_ptr) { - std::shared_ptr tex(LoadTexture(name)); + std::shared_ptr tex(LoadTexture(name, width_hint, height_hint)); tex_ptr = s_texture_cache.Insert(std::string(name), std::move(tex)); } @@ -441,7 +445,7 @@ void ImGuiFullscreen::TextureLoaderThread() s_texture_load_queue.pop_front(); lock.unlock(); - std::optional image(LoadTextureImage(path.c_str())); + std::optional image(LoadTextureImage(path.c_str(), 0, 0)); lock.lock(); // don't bother queuing back if it doesn't exist diff --git a/src/util/imgui_fullscreen.h b/src/util/imgui_fullscreen.h index a83a14e03..cc502307f 100644 --- a/src/util/imgui_fullscreen.h +++ b/src/util/imgui_fullscreen.h @@ -130,8 +130,8 @@ void Shutdown(); /// Texture cache. const std::shared_ptr& GetPlaceholderTexture(); std::unique_ptr CreateTextureFromImage(const RGBA8Image& image); -std::shared_ptr LoadTexture(std::string_view path); -GPUTexture* GetCachedTexture(std::string_view name); +std::shared_ptr LoadTexture(std::string_view path, u32 width_hint = 0, u32 height_hint = 0); +GPUTexture* GetCachedTexture(std::string_view name, u32 width_hint = 0, u32 height = 0); GPUTexture* GetCachedTextureAsync(std::string_view name); bool InvalidateCachedTexture(const std::string& path); void UploadAsyncTextures();