Test a multi-shape image

This commit is contained in:
Henrik Rydgård 2025-09-19 10:24:54 -06:00
parent 71c8f0a9bb
commit 74874bb81e
6 changed files with 147 additions and 24 deletions

2
.gitmodules vendored
View File

@ -64,4 +64,4 @@
url = https://github.com/hrydgard/freetype.git url = https://github.com/hrydgard/freetype.git
[submodule "ext/nanosvg"] [submodule "ext/nanosvg"]
path = ext/nanosvg path = ext/nanosvg
url = git@github.com:memononen/nanosvg.git url = https://github.com/hrydgard/nanosvg.git

View File

@ -80,6 +80,7 @@ void Bucket::AddImage(Image &&img, int id) {
dat.ey = (int)img.height(); dat.ey = (int)img.height();
dat.w = dat.ex; dat.w = dat.ex;
dat.h = dat.ey; dat.h = dat.ey;
dat.scale = img.scale;
dat.redToWhiteAlpha = false; dat.redToWhiteAlpha = false;
images.emplace_back(std::move(img)); images.emplace_back(std::move(img));
data.push_back(dat); data.push_back(dat);
@ -173,8 +174,9 @@ AtlasImage ToAtlasImage(int id, std::string_view name, float tw, float th, const
img.v1 = results[i].sy / th + toffy; img.v1 = results[i].sy / th + toffy;
img.u2 = results[i].ex / tw - toffx; img.u2 = results[i].ex / tw - toffx;
img.v2 = results[i].ey / th - toffy; img.v2 = results[i].ey / th - toffy;
img.w = results[i].ex - results[i].sx; // The w and h here is the UI-pixels width/height. So if we rasterized at another DPI than 1.0f, we need to scale here.
img.h = results[i].ey - results[i].sy; img.w = results[i].w / results[i].scale;
img.h = results[i].h / results[i].scale;
truncate_cpy(img.name, name); truncate_cpy(img.name, name);
return img; return img;
} }

View File

@ -33,6 +33,8 @@ struct Image {
Image(Image &&) = default; Image(Image &&) = default;
Image &operator=(Image &&) = default; Image &operator=(Image &&) = default;
float scale = 1.0f;
int w = 0; int w = 0;
int h = 0; int h = 0;
@ -82,6 +84,8 @@ struct Data {
// distance to move the origin forward // distance to move the origin forward
float wx; float wx;
float scale;
bool redToWhiteAlpha; bool redToWhiteAlpha;
int charNum; int charNum;
}; };

View File

@ -159,7 +159,21 @@ Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas) {
// There's a couple of approaches here, either we can pick apart the SVG and render each piece separately, // There's a couple of approaches here, either we can pick apart the SVG and render each piece separately,
// or we just rasterize the whole thing in one go and use the bounding boxes to pick out the sub-images. // or we just rasterize the whole thing in one go and use the bounding boxes to pick out the sub-images.
// We'll start with the latter, although the momentary memory requirements are higher. // We'll start with the latter, although the momentary memory requirements are higher.
std::vector<NSVGshape *> usedShapes; struct UsedShape {
float minX = 1000000.0f;
float maxX = -1000000.0f;
float minY = 1000000.0f;
float maxY = -1000000.0f;
void Merge(NSVGshape *shape) {
if (shape->bounds[0] < minX) minX = shape->bounds[0];
if (shape->bounds[1] < minY) minY = shape->bounds[1];
if (shape->bounds[2] > maxX) maxX = shape->bounds[2];
if (shape->bounds[3] > maxY) maxY = shape->bounds[3];
}
};
std::map<std::string, UsedShape> usedShapes;
if (image) { if (image) {
// Loop through the shapes to list them, and to hide them if irrelevant. // Loop through the shapes to list them, and to hide them if irrelevant.
NSVGshape *shape = image->shapes; NSVGshape *shape = image->shapes;
@ -169,8 +183,12 @@ Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas) {
INFO_LOG(Log::G3D, "Ignoring shape %s", shape->id); INFO_LOG(Log::G3D, "Ignoring shape %s", shape->id);
shape->flags &= ~NSVG_FLAGS_VISIBLE; shape->flags &= ~NSVG_FLAGS_VISIBLE;
} else { } else {
INFO_LOG(Log::G3D, "Found shape: %s (%0.2f %0.2f %0.2f %0.2f)", shape->id, shape->bounds[0], shape->bounds[1], shape->bounds[2], shape->bounds[3]); if (usedShapes.find(shape->id) != usedShapes.end()) {
usedShapes.push_back(shape); INFO_LOG(Log::G3D, "Duplicate shape ID in SVG, merging bboxes: %s", shape->id);
} else {
INFO_LOG(Log::G3D, "Found shape: %s (%0.2f %0.2f %0.2f %0.2f)", shape->id, shape->bounds[0], shape->bounds[1], shape->bounds[2], shape->bounds[3]);
}
usedShapes[shape->id].Merge(shape);
} }
shape = shape->next; shape = shape->next;
} }
@ -180,46 +198,53 @@ Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas) {
// Rasterize here, and add into image list. // Rasterize here, and add into image list.
rast = nsvgCreateRasterizer(); rast = nsvgCreateRasterizer();
INFO_LOG(Log::G3D, "Rasterizing SVG: %d x %d", (int)image->width, (int)image->height); float scale = 2.0f;
int svgWidth = image->width * scale;
int svgHeight = image->height * scale;
char *svgImg = new char[(int)image->width * (int)image->height * 4]; INFO_LOG(Log::G3D, "Rasterizing SVG: %d x %d at scale %0.2f", svgWidth, svgHeight, scale);
memset(svgImg, 0, (int)image->width * (int)image->height * 4);
nsvgRasterize(rast, image, 0, 0, 1.0f, (unsigned char *)svgImg, (int)image->width, (int)image->height, (int)image->width * 4); char *svgImg = new char[svgWidth * svgHeight * 4];
memset(svgImg, 0, svgWidth * svgHeight * 4);
nsvgRasterize(rast, image, 0, 0, scale, (unsigned char *)svgImg, svgWidth, svgHeight, svgWidth * 4);
// Now, loop through the shapes again and copy out the ones we care about. // Now, loop through the shapes again and copy out the ones we care about.
for (auto shape : usedShapes) { for (auto &[shapeId, bounds] : usedShapes) {
int index = GetImageIndex(shape->id); int index = GetImageIndex(shapeId);
_dbg_assert_(index != -1); _dbg_assert_(index != -1);
if (index == -1) { if (index == -1) {
continue; continue;
} }
Image &img = images[index]; Image &img = images[index];
int minX = std::max(0, (int)floorf(shape->bounds[0])); int minX = std::max(0, (int)floorf(bounds.minX * scale));
int minY = std::max(0, (int)floorf(shape->bounds[1])); int minY = std::max(0, (int)floorf(bounds.minY * scale));
int maxX = std::min((int)image->width, (int)ceilf(shape->bounds[2])); int maxX = std::min(svgWidth, (int)ceilf(bounds.maxX * scale));
int maxY = std::min((int)image->height, (int)ceilf(shape->bounds[3])); int maxY = std::min(svgHeight, (int)ceilf(bounds.maxY * scale));
int w = maxX - minX; int w = maxX - minX;
int h = maxY - minY; int h = maxY - minY;
if (w <= 0 || h <= 0) { if (w <= 0 || h <= 0) {
ERROR_LOG(Log::G3D, "Invalid size for %s: %dx%d", shape->id, w, h); ERROR_LOG(Log::G3D, "Invalid size for %s: %dx%d", shapeId.c_str(), w, h);
continue; continue;
} }
img.resize(w, h); img.resize(w, h);
for (int y = 0; y < h; y++) { for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) { for (int x = 0; x < w; x++) {
int sx = (int)(shape->bounds[0] + x); int sx = minX + x;
int sy = (int)(shape->bounds[1] + y); int sy = minY + y;
const u32 *src = (u32 *)svgImg + (sy * (int)image->width + sx); const u32 *src = (u32 *)svgImg + (sy * svgWidth + sx);
u32 col = *src; u32 col = *src;
img.set1(x, y, col); img.set1(x, y, col);
} }
} }
img.scale = scale;
// pngSave(Path(std::string("../buttons_") + PNGNameFromID(shape->id)), img.data(), img.width(), img.height(), 4); // pngSave(Path(std::string("../buttons_") + PNGNameFromID(shape->id)), img.data(), img.width(), img.height(), 4);
} }
// pngSave(Path("../buttons_rasterized.png"), svgImg, (int)image->width, (int)image->height, 4);
// pngSave(Path("../buttons_rasterized.png"), svgImg, svgWidth, svgHeight, 4);
delete[] svgImg; delete[] svgImg;
nsvgDeleteRasterizer(rast); nsvgDeleteRasterizer(rast);
@ -227,7 +252,7 @@ Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas) {
} }
} }
INFO_LOG(Log::G3D, " - Rasterized svg image in %.2f ms\n", images.size(), svgStart.ElapsedMs()); INFO_LOG(Log::G3D, " - Rasterized svg image in %0.2f ms\n", svgStart.ElapsedMs());
Instant pngStart = Instant::Now(); Instant pngStart = Instant::Now();

View File

@ -8,7 +8,7 @@
version="1.1" version="1.1"
id="svg1" id="svg1"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)" inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
sodipodi:docname="buttons.svg" sodipodi:docname="images.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -178,6 +178,76 @@
operator="over" operator="over"
result="composite2" /> result="composite2" />
</filter> </filter>
<filter
id="filter5132-3"
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
width="1.5621654"
height="1.1492101"
x="-0.2810827"
y="-0.074605025">
<feFlood
id="feFlood5134-8"
flood-opacity="0.20000000000000001"
flood-color="rgb(0,0,0)"
result="flood" />
<feComposite
id="feComposite5136-8"
in2="SourceGraphic"
in="flood"
operator="in"
result="composite1" />
<feGaussianBlur
id="feGaussianBlur5138-1"
stdDeviation="0.5"
result="blur" />
<feOffset
id="feOffset5140-6"
dx="0"
dy="0"
result="offset" />
<feComposite
id="feComposite5142-3"
in2="offset"
in="SourceGraphic"
operator="over"
result="composite2" />
</filter>
<filter
id="filter2"
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
width="3.3160448"
height="2.4104021"
x="-1.1216338"
y="-0.70520079">
<feFlood
id="feFlood1"
flood-opacity="0.20000000000000001"
flood-color="rgb(0,0,0)"
result="flood" />
<feComposite
id="feComposite1"
in2="SourceGraphic"
in="flood"
operator="in"
result="composite1" />
<feGaussianBlur
id="feGaussianBlur1"
stdDeviation="0.5"
result="blur" />
<feOffset
id="feOffset1"
dx="0"
dy="0"
result="offset" />
<feComposite
id="feComposite2"
in2="offset"
in="SourceGraphic"
operator="over"
result="composite2" />
</filter>
</defs> </defs>
<g <g
inkscape:label="Layer 1" inkscape:label="Layer 1"
@ -220,5 +290,27 @@
inkscape:export-xdpi="135" inkscape:export-xdpi="135"
inkscape:export-ydpi="135" inkscape:export-ydpi="135"
transform="matrix(0.39648704,0,0,0.39648704,-24.975676,6.2728297)" /> transform="matrix(0.39648704,0,0,0.39648704,-24.975676,6.2728297)" />
<g
transform="matrix(0.38834816,0,0,0.38834816,-66.861088,-102.74791)"
id="I_PAUSE"
inkscape:export-xdpi="133.41901"
inkscape:export-ydpi="133.41901">
<rect
style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;filter:url(#filter5132-3)"
id="rect1484"
width="6.9374599"
height="26.13765"
x="263.30356"
y="245.75505"
transform="matrix(0.9517119,0,0,0.85404454,13.366799,63.169462)" />
<rect
transform="matrix(0.9517119,0,0,0.85404454,27.341328,63.169462)"
y="245.75505"
x="263.30356"
height="26.13765"
width="6.9374599"
id="rect1486"
style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;filter:url(#filter5132-3)" />
</g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

@ -1 +1 @@
Subproject commit ea6a6aca009422bba0dbad4c80df6e6ba0c82183 Subproject commit 478dbb8f7ed11c3d9b20b3986a8ee2283f483ef7