feat: SVG support in Image viewer app
This commit is contained in:
@@ -1626,6 +1626,46 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
|
||||
// ---------------------------------------------------------------------------
|
||||
// Load SVG from VFS and render
|
||||
// ---------------------------------------------------------------------------
|
||||
// Extract intrinsic dimensions from an SVG document. Prefers width/height
|
||||
// attributes, falls back to viewBox, then to a 256x256 default. Used by the
|
||||
// image viewer to render SVGs at their natural size rather than a fixed icon
|
||||
// size.
|
||||
inline void svg_get_natural_size(const char* svg_data, int svg_len,
|
||||
int* out_w, int* out_h) {
|
||||
int w = 0, h = 0;
|
||||
const char* svg_tag = svg_strstr(svg_data, svg_len, "<svg");
|
||||
if (svg_tag) {
|
||||
int tag_offset = (int)(svg_tag - svg_data);
|
||||
int tag_end = tag_offset;
|
||||
while (tag_end < svg_len && svg_data[tag_end] != '>') ++tag_end;
|
||||
int tag_len = tag_end - tag_offset + 1;
|
||||
|
||||
char attr_buf[64];
|
||||
if (svg_get_attr(svg_tag, tag_len, " width", attr_buf, sizeof(attr_buf)) > 0)
|
||||
w = svg_parse_int(attr_buf);
|
||||
if (svg_get_attr(svg_tag, tag_len, " height", attr_buf, sizeof(attr_buf)) > 0)
|
||||
h = svg_parse_int(attr_buf);
|
||||
|
||||
if ((w <= 0 || h <= 0) &&
|
||||
svg_get_attr(svg_tag, tag_len, " viewBox", attr_buf, sizeof(attr_buf)) > 0) {
|
||||
fixed_t vbx, vby, vbw, vbh;
|
||||
const char* vp = attr_buf;
|
||||
int c;
|
||||
c = svg_parse_fixed(vp, &vbx); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
|
||||
c = svg_parse_fixed(vp, &vby); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
|
||||
c = svg_parse_fixed(vp, &vbw); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
|
||||
svg_parse_fixed(vp, &vbh);
|
||||
(void)vbx; (void)vby;
|
||||
if (w <= 0) w = fixed_to_int(vbw);
|
||||
if (h <= 0) h = fixed_to_int(vbh);
|
||||
}
|
||||
}
|
||||
if (w <= 0) w = 256;
|
||||
if (h <= 0) h = 256;
|
||||
if (out_w) *out_w = w;
|
||||
if (out_h) *out_h = h;
|
||||
}
|
||||
|
||||
inline SvgIcon svg_load(const char* vfs_path, int target_w, int target_h, Color fill_color) {
|
||||
int fd = montauk::open(vfs_path);
|
||||
if (fd < 0) {
|
||||
|
||||
@@ -45,7 +45,9 @@ bool str_ends_with(const char* s, const char* suffix) {
|
||||
}
|
||||
|
||||
bool is_image_file(const char* name) {
|
||||
return str_ends_with(name, ".jpg") || str_ends_with(name, ".jpeg");
|
||||
return str_ends_with(name, ".jpg")
|
||||
|| str_ends_with(name, ".jpeg")
|
||||
|| str_ends_with(name, ".svg");
|
||||
}
|
||||
|
||||
bool is_font_file(const char* name) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
#include <gui/svg.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <string.h>
|
||||
@@ -99,6 +100,20 @@ static const char* basename(const char* path) {
|
||||
return last;
|
||||
}
|
||||
|
||||
static bool path_has_ext_ci(const char* path, const char* ext) {
|
||||
int pl = 0; while (path[pl]) pl++;
|
||||
int el = 0; while (ext[el]) el++;
|
||||
if (pl < el) return false;
|
||||
for (int i = 0; i < el; i++) {
|
||||
char a = path[pl - el + i];
|
||||
char b = ext[i];
|
||||
if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a');
|
||||
if (b >= 'A' && b <= 'Z') b = (char)(b - 'A' + 'a');
|
||||
if (a != b) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Image loading
|
||||
// ============================================================================
|
||||
@@ -135,6 +150,41 @@ static bool load_image(const char* path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// SVG: vector path through our own rasterizer. stb_image can't decode SVG.
|
||||
if (path_has_ext_ci(path, ".svg")) {
|
||||
int nat_w = 0, nat_h = 0;
|
||||
svg_get_natural_size((const char*)filedata, bytes_read, &nat_w, &nat_h);
|
||||
|
||||
// Cap longest edge to keep memory bounded (4 bytes/pixel).
|
||||
constexpr int MAX_EDGE = 2048;
|
||||
int max_edge = nat_w > nat_h ? nat_w : nat_h;
|
||||
if (max_edge > MAX_EDGE) {
|
||||
nat_w = (int)((int64_t)nat_w * MAX_EDGE / max_edge);
|
||||
nat_h = (int)((int64_t)nat_h * MAX_EDGE / max_edge);
|
||||
}
|
||||
if (nat_w < 1) nat_w = 1;
|
||||
if (nat_h < 1) nat_h = 1;
|
||||
|
||||
// fill_color is the currentColor substitute. Real SVG files mostly
|
||||
// specify their own fills, so this only matters for paths that use
|
||||
// currentColor; default to black for sensible fallback.
|
||||
SvgIcon icon = svg_render((const char*)filedata, bytes_read,
|
||||
nat_w, nat_h, Color::from_rgb(0, 0, 0));
|
||||
montauk::mfree(filedata);
|
||||
|
||||
if (!icon.pixels) {
|
||||
montauk::strcpy(g_status, "Error: SVG render failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_image = icon.pixels;
|
||||
g_img_w = icon.width;
|
||||
g_img_h = icon.height;
|
||||
g_has_alpha = true;
|
||||
snprintf(g_status, 256, "%s %dx%d SVG", basename(path), g_img_w, g_img_h);
|
||||
return true;
|
||||
}
|
||||
|
||||
int w, h, channels;
|
||||
unsigned char* rgba = stbi_load_from_memory(filedata, bytes_read, &w, &h, &channels, 4);
|
||||
montauk::mfree(filedata);
|
||||
|
||||
Reference in New Issue
Block a user