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();
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;

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 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")

View File

@ -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 <jpeglib.h>
#include <png.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);
}
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
{
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;
}
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
void RGBA8Image::Resize(u32 new_width, u32 new_height)

View File

@ -9,9 +9,12 @@
#include <cstdio>
#include <cstring>
#include <optional>
#include <span>
#include <string_view>
#include <vector>
class Error;
template<typename PixelType>
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<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()
{
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<const u8> 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<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 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 void TextureLoaderThread();
@ -298,8 +298,12 @@ std::unique_ptr<GPUTexture> ImGuiFullscreen::CreateTextureFromImage(const RGBA8I
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;
if (Path::IsAbsolute(path))
{
@ -309,7 +313,7 @@ std::optional<RGBA8Image> 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<RGBA8Image> 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<GPUTexture> ImGuiFullscreen::UploadTexture(std::string_view path
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())
{
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;
}
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);
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));
}
@ -441,7 +445,7 @@ void ImGuiFullscreen::TextureLoaderThread()
s_texture_load_queue.pop_front();
lock.unlock();
std::optional<RGBA8Image> image(LoadTextureImage(path.c_str()));
std::optional<RGBA8Image> image(LoadTextureImage(path.c_str(), 0, 0));
lock.lock();
// don't bother queuing back if it doesn't exist

View File

@ -130,8 +130,8 @@ void Shutdown();
/// Texture cache.
const std::shared_ptr<GPUTexture>& GetPlaceholderTexture();
std::unique_ptr<GPUTexture> CreateTextureFromImage(const RGBA8Image& image);
std::shared_ptr<GPUTexture> LoadTexture(std::string_view path);
GPUTexture* GetCachedTexture(std::string_view name);
std::shared_ptr<GPUTexture> 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();