// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "svg_texture.h" #include "texture_loader.h" // CreateRawTexture #include "logger.h" // nanosvg (memononen, zlib/public-domain, vendored in libs/nanosvg). Implementations compiled ONLY here. #define NANOSVG_IMPLEMENTATION #include "nanosvg/nanosvg.h" #define NANOSVGRAST_IMPLEMENTATION #include "nanosvg/nanosvgrast.h" #include #include #include namespace dragonx { namespace util { // Perceptual brightness of a nanosvg color (byte order R,G,B,A — matches ImU32/IM_COL32). static inline float svgLuma(unsigned int c) { const float r = (c & 0xFFu) / 255.0f; const float g = ((c >> 8) & 0xFFu) / 255.0f; const float b = ((c >> 16) & 0xFFu) / 255.0f; return 0.299f * r + 0.587f * g + 0.114f * b; } bool LoadTextureFromSvg(const char* svgText, int pxSize, ImU32 bodyColor, ImU32 detailColor, ImTextureID* outTex, int* outW, int* outH) { if (!svgText || pxSize <= 0 || !outTex) return false; // nsvgParse mutates its input buffer — parse a copy. std::string buf(svgText); NSVGimage* img = nsvgParse(&buf[0], "px", 96.0f); if (!img || img->width <= 0.0f || img->height <= 0.0f) { if (img) nsvgDelete(img); DEBUG_LOGF("LoadTextureFromSvg: parse failed / empty image\n"); return false; } // Recolor by brightness: light fills -> detail, darker fills -> body. Force opaque so a theme color // with partial alpha can't make the mark translucent. ImU32 and nanosvg color share byte order. const unsigned int body = (static_cast(bodyColor) & 0x00FFFFFFu) | 0xFF000000u; const unsigned int detail = (static_cast(detailColor) & 0x00FFFFFFu) | 0xFF000000u; for (NSVGshape* s = img->shapes; s; s = s->next) { if (s->fill.type == NSVG_PAINT_COLOR) s->fill.color = (svgLuma(s->fill.color) > 0.6f) ? detail : body; if (s->stroke.type == NSVG_PAINT_COLOR) s->stroke.color = (svgLuma(s->stroke.color) > 0.6f) ? detail : body; } const float scale = static_cast(pxSize) / img->width; const int w = pxSize; const int h = std::max(1, static_cast(img->height * scale + 0.5f)); std::vector rgba(static_cast(w) * h * 4, 0); NSVGrasterizer* rast = nsvgCreateRasterizer(); if (!rast) { nsvgDelete(img); return false; } nsvgRasterize(rast, img, 0.0f, 0.0f, scale, rgba.data(), w, h, w * 4); nsvgDeleteRasterizer(rast); nsvgDelete(img); if (!CreateRawTexture(rgba.data(), w, h, /*repeat=*/false, outTex)) { DEBUG_LOGF("LoadTextureFromSvg: CreateRawTexture failed (%dx%d)\n", w, h); return false; } if (outW) *outW = w; if (outH) *outH = h; return true; } } // namespace util } // namespace dragonx