Replace the shared app logo (Overview header, first-run wizard, lock screen) with the DragonX mark rendered from res/img/logos/logo_dragonx.svg, recolored to each theme: the dragon body takes the theme accent (Primary()), the inner detail stays light. Vendor nanosvg (memononen, zlib/public-domain) in libs/nanosvg/ and add util::LoadTextureFromSvg (parse a copy, recolor shapes by luminance, rasterize to RGBA, upload via the existing CreateRawTexture — GL + DX11). The SVG is embedded as a string (src/embedded/logo_dragonx_svg.h) so it's available in every build. The load moves into App::ensureLogoTexture(), called at the top of render() BEFORE the wizard/lock early-returns (so those screens get the logo too, which they never did before), and re-rasterizes when the accent or the dark/light variant changes. The per-skin PNG remains a fallback if rasterization ever fails; logo-texture lifetime is now freed on replace + on theme switch (was leaked). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
76 lines
2.9 KiB
C++
76 lines
2.9 KiB
C++
// 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 <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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<unsigned int>(bodyColor) & 0x00FFFFFFu) | 0xFF000000u;
|
|
const unsigned int detail = (static_cast<unsigned int>(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<float>(pxSize) / img->width;
|
|
const int w = pxSize;
|
|
const int h = std::max(1, static_cast<int>(img->height * scale + 0.5f));
|
|
|
|
std::vector<unsigned char> rgba(static_cast<size_t>(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
|