Image: Support rendering SVGs
This commit is contained in:
parent
fb7dd7bc69
commit
efbcbc93c9
|
@ -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;
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue