Skip to content

Commit

Permalink
Draft ImageFromAsset svg
Browse files Browse the repository at this point in the history
  • Loading branch information
pthom committed Oct 30, 2024
1 parent 52ecf45 commit 27bbbaa
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 32 deletions.
16 changes: 13 additions & 3 deletions src/hello_imgui/image_from_asset.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,30 @@ bool ImageButtonFromAsset(const char *assetPath, const ImVec2& size = ImVec2(0,

// `ImTextureID HelloImGui::ImTextureIdFromAsset(assetPath)`:
// will return a texture ID for an image loaded from the assets.
ImTextureID ImTextureIdFromAsset(const char *assetPath);
// The size param is used for SVG images, otherwise it is ignored:
// leave empty to use default SVG image size, otherwise if only one dimension is provided,
// the other will be computed to keep the aspect ratio.
ImTextureID ImTextureIdFromAsset(const char *assetPath, ImVec2 svgSize = ImVec2(0, 0));

// `ImVec2 HelloImGui::ImageSizeFromAsset(assetPath)`:
// will return the size of an image loaded from the assets.
ImVec2 ImageSizeFromAsset(const char *assetPath);
// The size param is used for SVG images, otherwise it is ignored:
// leave empty to use default SVG image size, otherwise if only one dimension is provided,
// the other will be computed to keep the aspect ratio.
ImVec2 ImageSizeFromAsset(const char *assetPath, ImVec2 svgSize = ImVec2(0, 0));


// `HelloImGui::ImageAndSize HelloImGui::ImageAndSizeFromAsset(assetPath)`:
// will return the texture ID and the size of an image loaded from the assets.
// The size param is used for SVG images, otherwise it is ignored:
// leave empty to use default SVG image size, otherwise if only one dimension is provided,
// the other will be computed to keep the aspect ratio.
struct ImageAndSize
{
ImTextureID textureId = ImTextureID(0);
ImVec2 size = ImVec2(0.f, 0.f);
};
ImageAndSize ImageAndSizeFromAsset(const char *assetPath);
ImageAndSize ImageAndSizeFromAsset(const char *assetPath, ImVec2 svgSize = ImVec2(0, 0));


// `ImVec2 HelloImGui::ImageProportionalSize(askedSize, imageSize)`:
Expand All @@ -72,6 +81,7 @@ ImVec2 ImageProportionalSize(const ImVec2& askedSize, const ImVec2& imageSize);

namespace internal
{
// Frees the internal texture cache
void Free_ImageFromAssetMap();
}
}
209 changes: 180 additions & 29 deletions src/hello_imgui/internal/image_from_asset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@
#include <stdexcept>


struct SvgRgbaImage
{
std::vector<unsigned char> data;
int width;
int height;
};

static SvgRgbaImage priv_SvgToRgba(
const HelloImGui::AssetFileData& data,
ImVec2 size = ImVec2(0.f, 0.f) // Use intrinsic width if 0, Use intrinsic height if 0. Preserve aspect ratio if only one is provided
);


namespace HelloImGui
{
ImVec2 ImageProportionalSize(const ImVec2& askedSize, const ImVec2& imageSize)
Expand All @@ -35,13 +48,30 @@ namespace HelloImGui
return r;
}

static std::string priv_FilenameAndSizeKey(const char* assetPath, ImVec2 size)
{
std::string key = assetPath;
key += "_";
key += std::to_string(size.x);
key += "_";
key += std::to_string(size.y);
return key;
}

static bool priv_IsFilenameSvg(const std::string& filename)
{
return filename.size() > 4 && filename.substr(filename.size() - 4) == ".svg";
}

static std::unordered_map<std::string, ImageAbstractPtr > gImageFromAssetMap;


static ImageAbstractPtr _GetCachedImage(const char*assetPath)
static ImageAbstractPtr _GetCachedImage(const char*assetPath, ImVec2 sizeForSvg)
{
if (gImageFromAssetMap.find(assetPath) != gImageFromAssetMap.end())
return gImageFromAssetMap.at(assetPath);
std::string key = priv_FilenameAndSizeKey(assetPath, sizeForSvg);

if (gImageFromAssetMap.find(key) != gImageFromAssetMap.end())
return gImageFromAssetMap.at(key);

HelloImGui::RendererBackendType rendererBackendType = HelloImGui::GetRunnerParams()->rendererBackendType;
ImageAbstractPtr concreteImage;
Expand All @@ -65,40 +95,65 @@ namespace HelloImGui
if (concreteImage == nullptr)
{
HelloImGui::Log(LogLevel::Warning, "ImageFromAsset: not implemented for this rendering backend!");
gImageFromAssetMap[assetPath] = nullptr; // Cache the failure
gImageFromAssetMap[key] = nullptr; // Cache the failure
return nullptr;
}

unsigned char* image_data_rgba;
{
// Load the image using stbi_load_from_memory
auto assetData = LoadAssetFileData(assetPath);
IM_ASSERT(assetData.data != nullptr);
image_data_rgba = stbi_load_from_memory(
(unsigned char *)assetData.data, (int)assetData.dataSize,
&concreteImage->Width, &concreteImage->Height, NULL, 4);
FreeAssetFileData(&assetData);
}
bool isSvg = priv_IsFilenameSvg(assetPath);

if (image_data_rgba == NULL)
if (! isSvg)
{
IM_ASSERT(false && "_GetCachedImage: Failed to load image!");
throw std::runtime_error("_GetCachedImage: Failed to load image!");
// Load with stbimage
unsigned char* image_data_rgba;
{
// Load the image using stbi_load_from_memory
auto assetData = LoadAssetFileData(assetPath);
IM_ASSERT(assetData.data != nullptr);
image_data_rgba = stbi_load_from_memory(
(unsigned char *)assetData.data, (int)assetData.dataSize,
&concreteImage->Width, &concreteImage->Height, NULL, 4);
FreeAssetFileData(&assetData);
}

if (image_data_rgba == NULL)
{
IM_ASSERT(false && "_GetCachedImage: Failed to load image!");
throw std::runtime_error("_GetCachedImage: Failed to load image!");
}
concreteImage->_impl_StoreTexture(concreteImage->Width, concreteImage->Height, image_data_rgba);
stbi_image_free(image_data_rgba);

gImageFromAssetMap[key] = concreteImage;
return concreteImage;
}
concreteImage->_impl_StoreTexture(concreteImage->Width, concreteImage->Height, image_data_rgba);
stbi_image_free(image_data_rgba);
else
{
// Load SVG
SvgRgbaImage svgRgbaImage;
{
// Load the image using stbi_load_from_memory
auto assetData = LoadAssetFileData(assetPath);
IM_ASSERT(assetData.data != nullptr);
svgRgbaImage = priv_SvgToRgba(assetData, sizeForSvg);
FreeAssetFileData(&assetData);
concreteImage->Height = svgRgbaImage.height;
concreteImage->Width = svgRgbaImage.width;
}
concreteImage->_impl_StoreTexture(concreteImage->Width, concreteImage->Height, svgRgbaImage.data.data());

gImageFromAssetMap[assetPath] = concreteImage;
return concreteImage;
gImageFromAssetMap[key] = concreteImage;
return concreteImage;
}
}



void ImageFromAsset(
const char *assetPath, const ImVec2& size,
const ImVec2& uv0, const ImVec2& uv1,
const ImVec4& tint_col, const ImVec4& border_col)
{
auto cachedImage = _GetCachedImage(assetPath);
auto cachedImage = _GetCachedImage(assetPath, size);
if (cachedImage == nullptr)
{
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "ImageFromAsset: fail!");
Expand All @@ -112,7 +167,7 @@ namespace HelloImGui

bool ImageButtonFromAsset(const char *assetPath, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col)
{
auto cachedImage = _GetCachedImage(assetPath);
auto cachedImage = _GetCachedImage(assetPath, size);
if (cachedImage == nullptr)
{
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "ImageButtonFromAsset: fail!");
Expand All @@ -125,25 +180,25 @@ namespace HelloImGui
return clicked;
}

ImTextureID ImTextureIdFromAsset(const char *assetPath)
ImTextureID ImTextureIdFromAsset(const char *assetPath, ImVec2 svgSize)
{
auto cachedImage = _GetCachedImage(assetPath);
auto cachedImage = _GetCachedImage(assetPath, svgSize);
if (cachedImage == nullptr)
return ImTextureID(0);
return cachedImage->TextureID();
}

ImVec2 ImageSizeFromAsset(const char *assetPath)
ImVec2 ImageSizeFromAsset(const char *assetPath, ImVec2 svgSize)
{
auto cachedImage = _GetCachedImage(assetPath);
auto cachedImage = _GetCachedImage(assetPath, svgSize);
if (cachedImage == nullptr)
return ImVec2(0.f, 0.f);
return ImVec2((float)cachedImage->Width, (float)cachedImage->Height);
}

ImageAndSize ImageAndSizeFromAsset(const char *assetPath)
ImageAndSize ImageAndSizeFromAsset(const char *assetPath, ImVec2 svgSize)
{
auto cachedImage = _GetCachedImage(assetPath);
auto cachedImage = _GetCachedImage(assetPath, svgSize);
if (cachedImage == nullptr)
return {};
return {cachedImage->TextureID(), ImVec2((float)cachedImage->Width, (float)cachedImage->Height)};
Expand All @@ -158,3 +213,99 @@ namespace HelloImGui
}

}


#include "plutosvg.h"

static SvgRgbaImage priv_SvgToRgba(
const HelloImGui::AssetFileData& assetFileData,
ImVec2 size // Use intrinsic width if 0, Use intrinsic height if 0. Preserve aspect ratio if only one is provided
)
{
// Load SVG document
plutosvg_document_t* svg =
plutosvg_document_load_from_data((const char*)assetFileData.data, assetFileData.dataSize, 0, 0, NULL, NULL);
if (!svg) {
IM_ASSERT(false && "SvgToRgba: Failed to load SVG document.");
}

// Get intrinsic dimensions
float intrinsic_width = plutosvg_document_get_width(svg);
float intrinsic_height = plutosvg_document_get_height(svg);
if (intrinsic_width == 0 || intrinsic_height == 0) {
plutosvg_document_destroy(svg);
IM_ASSERT(false && "SvgToRgba: SVG document has invalid dimensions.");
}

// Determine render dimensions
float width = size.x;
float height = size.y;
float render_width = width;
float render_height = height;

if (width == 0.0f && height == 0.0f) {
render_width = intrinsic_width;
render_height = intrinsic_height;
} else if (width == 0.0f) {
// Calculate width to preserve aspect ratio
render_width = (height / intrinsic_height) * intrinsic_width;
} else if (height == 0.0f) {
// Calculate height to preserve aspect ratio
render_height = (width / intrinsic_width) * intrinsic_height;
}

// Create a surface with the determined dimensions
plutovg_surface_t* surface = plutovg_surface_create(static_cast<int>(render_width), static_cast<int>(render_height));
if (!surface) {
plutosvg_document_destroy(svg);
IM_ASSERT(false && "SvgToRgba: Failed to create Plutovg surface.");
}

// Create a canvas for drawing
plutovg_canvas_t* canvas = plutovg_canvas_create(surface);
if (!canvas) {
plutovg_surface_destroy(surface);
plutosvg_document_destroy(svg);
IM_ASSERT(false && "SvgToRgba: Failed to create Plutovg canvas.");
}

// Clear the surface with transparent color
plutovg_color_t transparent = PLUTOVG_MAKE_COLOR(1, 1, 1, 0);
//plutovg_surface_clear(surface, &transparent);

// Set up palette callback (optional, can be nullptr if not using CSS variables)
// For simplicity, we'll use nullptr here.
bool render_success = plutosvg_document_render(svg, nullptr, canvas, &transparent, nullptr, nullptr);
if (!render_success) {
plutovg_canvas_destroy(canvas);
plutovg_surface_destroy(surface);
plutosvg_document_destroy(svg);
IM_ASSERT(false && "SvgToRgba: Failed to render SVG document.");
}

// Access pixel data
unsigned char* pixel_data = plutovg_surface_get_data(surface);
if (!pixel_data) {
plutovg_canvas_destroy(canvas);
plutovg_surface_destroy(surface);
plutosvg_document_destroy(svg);
IM_ASSERT(false && "SvgToRgba: Failed to access surface pixel data.");
}

int stride = plutovg_surface_get_stride(surface);
int height_px = plutovg_surface_get_height(surface);
int width_px = plutovg_surface_get_width(surface);
IM_ASSERT(stride == width_px * 4 && "SvgToRgba: Unexpected stride.");

// Convert ARGB Premultiplied to RGBA Plain
// Plutovg uses premultiplied ARGB, but we need plain RGBA for ImGui
std::vector<unsigned char> rgba_data(width_px * height_px * 4);
plutovg_convert_argb_to_rgba(rgba_data.data(), pixel_data, width_px, height_px, stride);

// Cleanup
plutovg_canvas_destroy(canvas);
plutovg_surface_destroy(surface);
plutosvg_document_destroy(svg);

return SvgRgbaImage{rgba_data, width_px, height_px};
}

0 comments on commit 27bbbaa

Please sign in to comment.