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