mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 12:07:31 +01:00
UI: Rounded rectangles from precomputed atlas corners
UIAtlas generates an antialiased white corner for each radius in ROUND_RECT_RADII (8, 16, 24, 32 UI units) and each style (filled, and thin/medium/thick outlines from ROUND_RECT_THICKNESS) at the atlas DPI. DrawBuffer::DrawRoundRect mirrors the corner into all four corners and stretches the corner's last texel row/column along the edges, so a round rect is 9 quads (8 for outlines). Other radii use the nearest corner, scaled. The LogoScreen temporarily draws some for testing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
42bb9b134b
commit
110fb7fd6a
5 files changed
+182
-2
No files matched your search
+37
-1
@@ -9,6 +9,7 @@
|
||||
#include "Common/UI/Context.h"
|
||||
#include "Common/Data/Format/PNGLoad.h"
|
||||
#include "Common/Render/AtlasGen.h"
|
||||
#include "Common/Render/DrawBuffer.h"
|
||||
#include "Common/Render/ManagedTexture.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/Thread/ParallelLoop.h"
|
||||
@@ -40,6 +41,9 @@ Atlas *GetUIAtlas() {
|
||||
struct ImageMeta {
|
||||
std::string_view id;
|
||||
bool addShadow = false;
|
||||
// If set, the image is a generated round rect corner (see DrawBuffer::DrawRoundRect). In UI units.
|
||||
float cornerRadius = 0.0f;
|
||||
float cornerThickness = 0.0f; // 0 = filled
|
||||
};
|
||||
|
||||
// We add shadows to all line-art images that are used for buttons, to improve visibility.
|
||||
@@ -375,6 +379,28 @@ static bool RasterizeSVG(std::string_view filename, float dpiScale, int maxTextu
|
||||
return true;
|
||||
}
|
||||
|
||||
// White, premultiplied, antialiased by distance to the edge.
|
||||
static void GenerateRoundRectCorner(Image &img, float radius, float thickness, float dpiScale) {
|
||||
const float r = radius * dpiScale;
|
||||
const float t = thickness * dpiScale;
|
||||
const int center = RoundRectCornerCenter(r);
|
||||
img.resize(center + 1, center + 1);
|
||||
img.scale = dpiScale;
|
||||
for (int y = 0; y <= center; y++) {
|
||||
const float dy = std::max((float)center - (y + 0.5f), 0.0f);
|
||||
for (int x = 0; x <= center; x++) {
|
||||
const float dx = std::max((float)center - (x + 0.5f), 0.0f);
|
||||
const float d = sqrtf(dx * dx + dy * dy);
|
||||
float coverage = std::clamp(r - d + 0.5f, 0.0f, 1.0f);
|
||||
if (t > 0.0f) {
|
||||
coverage -= std::clamp(r - t - d + 0.5f, 0.0f, 1.0f);
|
||||
}
|
||||
const u32 a = (u32)(coverage * 255.0f + 0.5f);
|
||||
img.set1(x, y, a * 0x01010101);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool GenerateUIAtlasImage(Atlas *atlas, float dpiScale, Image *dest, int maxTextureSize, const ImageMeta *imageIDs, size_t imageCount) {
|
||||
Bucket bucket;
|
||||
|
||||
@@ -445,6 +471,8 @@ static bool GenerateUIAtlasImage(Atlas *atlas, float dpiScale, Image *dest, int
|
||||
} else if (equals(imageIDs[i].id, "I_EMPTY")) {
|
||||
img.resize(16, 16);
|
||||
img.fill(0);
|
||||
} else if (imageIDs[i].cornerRadius > 0.0f) {
|
||||
GenerateRoundRectCorner(img, imageIDs[i].cornerRadius, imageIDs[i].cornerThickness, dpiScale);
|
||||
} else {
|
||||
std::string name = "ui_images/";
|
||||
std::string pngName = PNGNameFromID(imageIDs[i].id);
|
||||
@@ -491,6 +519,7 @@ static bool GenerateUIAtlasImage(Atlas *atlas, float dpiScale, Image *dest, int
|
||||
atlas->images = new AtlasImage[genAtlasImages.size()];
|
||||
std::copy(genAtlasImages.begin(), genAtlasImages.end(), atlas->images);
|
||||
atlas->num_images = (int)genAtlasImages.size();
|
||||
atlas->dpiScale = dpiScale;
|
||||
|
||||
// For debug, write out the atlas.
|
||||
if (SAVE_DEBUG_ATLAS) {
|
||||
@@ -510,8 +539,15 @@ Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas, float dpiS
|
||||
INFO_LOG(Log::G3D, "Regenerating atlas (empty: %s). Dpi scale (changed: %s): %0.2f (invalidate=%d)",
|
||||
g_cachedUIAtlasImage.IsEmpty() ? "true" : "false", dpiScale != g_cachedDpiScale ? "true" : "false", dpiScale, invalidate);
|
||||
|
||||
std::vector<ImageMeta> imageIDs(std::begin(g_uiImageIDs), std::end(g_uiImageIDs));
|
||||
for (int r = 0; r < ROUND_RECT_RADIUS_COUNT; r++) {
|
||||
for (int style = 0; style < (int)RoundRectStyle::COUNT; style++) {
|
||||
imageIDs.push_back({ RoundRectCornerImageName(r, (RoundRectStyle)style), false, ROUND_RECT_RADII[r], ROUND_RECT_THICKNESS[style] });
|
||||
}
|
||||
}
|
||||
|
||||
g_cachedUIAtlasImage.clear();
|
||||
if (!GenerateUIAtlasImage(atlas, dpiScale, &g_cachedUIAtlasImage, draw->GetDeviceCaps().maxTextureSize, g_uiImageIDs, ARRAY_SIZE(g_uiImageIDs))) {
|
||||
if (!GenerateUIAtlasImage(atlas, dpiScale, &g_cachedUIAtlasImage, draw->GetDeviceCaps().maxTextureSize, imageIDs.data(), imageIDs.size())) {
|
||||
ERROR_LOG(Log::G3D, "Failed to generate UI atlas!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user