mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-01-09 06:23:21 +08:00
Test a multi-shape image
This commit is contained in:
parent
71c8f0a9bb
commit
74874bb81e
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
Loading…
x
Reference in New Issue
Block a user