Image: Support rendering SVGs

This commit is contained in:
Stenzek 2024-10-08 22:57:16 +10:00
parent fb7dd7bc69
commit efbcbc93c9
No known key found for this signature in database
6 changed files with 143 additions and 14 deletions

View File

@ -1382,7 +1382,7 @@ void FullscreenUI::DrawLandingWindow()
{ {
ResetFocusHere(); 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."))) FSUI_CSTR("Launch a game from images scanned from your game directories.")))
{ {
SwitchToGameList(); SwitchToGameList();
@ -5777,7 +5777,8 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading)
} }
if (ActiveButton(FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Close Menu"), false, true, 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; is_open = false;
ignore_close_request = true; ignore_close_request = true;

View File

@ -77,7 +77,7 @@ target_precompile_headers(util PRIVATE "pch.h")
target_include_directories(util PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(util PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(util PUBLIC "${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 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) if(ENABLE_X11)
target_compile_definitions(util PRIVATE "-DENABLE_X11=1") target_compile_definitions(util PRIVATE "-DENABLE_X11=1")

View File

@ -5,6 +5,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/bitutils.h" #include "common/bitutils.h"
#include "common/error.h"
#include "common/fastjmp.h" #include "common/fastjmp.h"
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
@ -12,6 +13,8 @@
#include "common/scoped_guard.h" #include "common/scoped_guard.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "lunasvg_c.h"
#include <jpeglib.h> #include <jpeglib.h>
#include <png.h> #include <png.h>
#include <webp/decode.h> #include <webp/decode.h>
@ -151,6 +154,76 @@ bool RGBA8Image::LoadFromBuffer(std::string_view filename, const void* buffer, s
return handler->buffer_loader(this, buffer, buffer_size); 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<DynamicHeapArray<u8>> 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<const u8>(static_cast<const u8*>(buffer), buffer_size), width_hint, height_hint,
error);
}
else
{
return LoadFromBuffer(filename, buffer, buffer_size);
}
}
bool RGBA8Image::RasterizeSVG(const std::span<const u8> 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<lunasvg_document, void (*)(lunasvg_document*)> 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<lunasvg_bitmap, void (*)(lunasvg_bitmap*)> 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<lunasvg_matrix, void (*)(lunasvg_matrix*)> matrix(lunasvg_matrix_create(), lunasvg_matrix_destroy);
lunasvg_matrix_identity(matrix.get());
#endif
std::unique_ptr<lunasvg_bitmap, void (*)(lunasvg_bitmap*)> 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 bool RGBA8Image::SaveToFile(std::string_view filename, std::FILE* fp, u8 quality) const
{ {
const std::string_view extension(Path::GetExtension(filename)); const std::string_view extension(Path::GetExtension(filename));
@ -186,6 +259,23 @@ std::optional<std::vector<u8>> RGBA8Image::SaveToBuffer(std::string_view filenam
return ret; return ret;
} }
void RGBA8Image::SwapBGRAToRGBA()
{
// TODO: GSVectorify
for (u32 y = 0; y < m_height; y++)
{
u8* pixels = reinterpret_cast<u8*>(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 #if 0
void RGBA8Image::Resize(u32 new_width, u32 new_height) void RGBA8Image::Resize(u32 new_width, u32 new_height)

View File

@ -9,9 +9,12 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <optional> #include <optional>
#include <span>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
class Error;
template<typename PixelType> template<typename PixelType>
class Image class Image
{ {
@ -98,6 +101,28 @@ public:
m_pixels = std::move(pixels); 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<const PixelType*>(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<const u8*>(data);
for (u32 row = 0; row < height; row++)
{
std::memcpy(out_ptr, in_ptr, copy_width);
out_ptr += width;
in_ptr += stride;
}
}
std::vector<PixelType> TakePixels() std::vector<PixelType> TakePixels()
{ {
m_width = 0; m_width = 0;
@ -130,7 +155,16 @@ public:
bool LoadFromFile(std::string_view filename, std::FILE* fp); bool LoadFromFile(std::string_view filename, std::FILE* fp);
bool LoadFromBuffer(std::string_view filename, const void* buffer, size_t buffer_size); 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<const u8> data, u32 width, u32 height, Error* error = nullptr);
bool SaveToFile(const char* filename, u8 quality = DEFAULT_SAVE_QUALITY) const; 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; bool SaveToFile(std::string_view filename, std::FILE* fp, u8 quality = DEFAULT_SAVE_QUALITY) const;
std::optional<std::vector<u8>> SaveToBuffer(std::string_view filename, u8 quality = DEFAULT_SAVE_QUALITY) const; std::optional<std::vector<u8>> SaveToBuffer(std::string_view filename, u8 quality = DEFAULT_SAVE_QUALITY) const;
private:
void SwapBGRAToRGBA();
}; };

View File

@ -43,7 +43,7 @@ using MessageDialogCallbackVariant = std::variant<InfoMessageDialogCallback, Con
static constexpr float MENU_BACKGROUND_ANIMATION_TIME = 0.5f; static constexpr float MENU_BACKGROUND_ANIMATION_TIME = 0.5f;
static constexpr float SMOOTH_SCROLLING_SPEED = 3.5f; static constexpr float SMOOTH_SCROLLING_SPEED = 3.5f;
static std::optional<RGBA8Image> LoadTextureImage(std::string_view path); static std::optional<RGBA8Image> LoadTextureImage(std::string_view path, u32 width_hint, u32 height_hint);
static std::shared_ptr<GPUTexture> UploadTexture(std::string_view path, const RGBA8Image& image); static std::shared_ptr<GPUTexture> UploadTexture(std::string_view path, const RGBA8Image& image);
static void TextureLoaderThread(); static void TextureLoaderThread();
@ -298,8 +298,12 @@ std::unique_ptr<GPUTexture> ImGuiFullscreen::CreateTextureFromImage(const RGBA8I
return ret; return ret;
} }
std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view path) std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view path, u32 width_hint, u32 height_hint)
{ {
// TODO: GSVector
width_hint = static_cast<u32>(std::ceil(LayoutScale(static_cast<float>(width_hint))));
height_hint = static_cast<u32>(std::ceil(LayoutScale(static_cast<float>(height_hint))));
std::optional<RGBA8Image> image; std::optional<RGBA8Image> image;
if (Path::IsAbsolute(path)) if (Path::IsAbsolute(path))
{ {
@ -309,7 +313,7 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
if (fp) if (fp)
{ {
image = RGBA8Image(); 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); ERROR_LOG("Failed to read texture file '{}'", path);
image.reset(); image.reset();
@ -326,7 +330,7 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
if (data.has_value()) if (data.has_value())
{ {
image = RGBA8Image(); 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); ERROR_LOG("Failed to read texture resource '{}'", path);
image.reset(); image.reset();
@ -356,9 +360,9 @@ std::shared_ptr<GPUTexture> ImGuiFullscreen::UploadTexture(std::string_view path
return std::shared_ptr<GPUTexture>(texture.release(), GPUDevice::PooledTextureDeleter()); return std::shared_ptr<GPUTexture>(texture.release(), GPUDevice::PooledTextureDeleter());
} }
std::shared_ptr<GPUTexture> ImGuiFullscreen::LoadTexture(std::string_view path) std::shared_ptr<GPUTexture> ImGuiFullscreen::LoadTexture(std::string_view path, u32 width_hint, u32 height_hint)
{ {
std::optional<RGBA8Image> image(LoadTextureImage(path)); std::optional<RGBA8Image> image(LoadTextureImage(path, width_hint, height_hint));
if (image.has_value()) if (image.has_value())
{ {
std::shared_ptr<GPUTexture> ret(UploadTexture(path, image.value())); std::shared_ptr<GPUTexture> ret(UploadTexture(path, image.value()));
@ -369,12 +373,12 @@ std::shared_ptr<GPUTexture> ImGuiFullscreen::LoadTexture(std::string_view path)
return s_placeholder_texture; 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<GPUTexture>* tex_ptr = s_texture_cache.Lookup(name); std::shared_ptr<GPUTexture>* tex_ptr = s_texture_cache.Lookup(name);
if (!tex_ptr) if (!tex_ptr)
{ {
std::shared_ptr<GPUTexture> tex(LoadTexture(name)); std::shared_ptr<GPUTexture> tex(LoadTexture(name, width_hint, height_hint));
tex_ptr = s_texture_cache.Insert(std::string(name), std::move(tex)); 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(); s_texture_load_queue.pop_front();
lock.unlock(); lock.unlock();
std::optional<RGBA8Image> image(LoadTextureImage(path.c_str())); std::optional<RGBA8Image> image(LoadTextureImage(path.c_str(), 0, 0));
lock.lock(); lock.lock();
// don't bother queuing back if it doesn't exist // don't bother queuing back if it doesn't exist

View File

@ -130,8 +130,8 @@ void Shutdown();
/// Texture cache. /// Texture cache.
const std::shared_ptr<GPUTexture>& GetPlaceholderTexture(); const std::shared_ptr<GPUTexture>& GetPlaceholderTexture();
std::unique_ptr<GPUTexture> CreateTextureFromImage(const RGBA8Image& image); std::unique_ptr<GPUTexture> CreateTextureFromImage(const RGBA8Image& image);
std::shared_ptr<GPUTexture> LoadTexture(std::string_view path); std::shared_ptr<GPUTexture> LoadTexture(std::string_view path, u32 width_hint = 0, u32 height_hint = 0);
GPUTexture* GetCachedTexture(std::string_view name); GPUTexture* GetCachedTexture(std::string_view name, u32 width_hint = 0, u32 height = 0);
GPUTexture* GetCachedTextureAsync(std::string_view name); GPUTexture* GetCachedTextureAsync(std::string_view name);
bool InvalidateCachedTexture(const std::string& path); bool InvalidateCachedTexture(const std::string& path);
void UploadAsyncTextures(); void UploadAsyncTextures();