diff --git a/Common/Render/AtlasGen.cpp b/Common/Render/AtlasGen.cpp index 0c805716b5..8707581e49 100644 --- a/Common/Render/AtlasGen.cpp +++ b/Common/Render/AtlasGen.cpp @@ -23,7 +23,6 @@ #include "Common/File/FileUtil.h" #include "Common/File/VFS/VFS.h" #include "Common/Render/AtlasGen.h" -#include "Common/Render/DropShadow.h" typedef unsigned short u16; @@ -241,10 +240,7 @@ void Add1PxTransparentBorder(Image &img) { img.h += 2; } -void AddDropShadow(Image &img, int shadowSize, float intensity) { - DropShadowParams params; - params.radius = std::max(1, (int)(shadowSize * img.scale)); - params.intensity = intensity; +void AddDropShadow(Image &img, const DropShadowParams ¶ms) { const int pad = params.Padding(); std::vector newData((img.w + pad * 2) * (img.h + pad * 2)); CompositeDropShadow(img.dat.data(), img.w, img.h, newData.data(), img.w + pad * 2, params); diff --git a/Common/Render/AtlasGen.h b/Common/Render/AtlasGen.h index 460339355d..dc10d41afa 100644 --- a/Common/Render/AtlasGen.h +++ b/Common/Render/AtlasGen.h @@ -5,6 +5,7 @@ #include "Common/CommonTypes.h" +#include "Common/Render/DropShadow.h" #include "Common/Render/TextureAtlas.h" struct ImageU8 { @@ -92,7 +93,7 @@ struct Image { }; // Slow. -void AddDropShadow(Image &img, int shadowSize, float intensity); +void AddDropShadow(Image &img, const DropShadowParams ¶ms); // To improve filtering. void Add1PxTransparentBorder(Image &img); diff --git a/Common/Render/DropShadow.cpp b/Common/Render/DropShadow.cpp index 51858d335e..29bb492599 100644 --- a/Common/Render/DropShadow.cpp +++ b/Common/Render/DropShadow.cpp @@ -20,6 +20,17 @@ #include "Common/Render/DropShadow.h" +// In UI units. +static constexpr float UI_SHADOW_RADIUS = 3.0f; +static constexpr float UI_SHADOW_INTENSITY = 0.66f; + +DropShadowParams UIDropShadowParams(float pixelsPerUnit) { + DropShadowParams params; + params.radius = std::max(1, (int)(UI_SHADOW_RADIUS * pixelsPerUnit)); + params.intensity = UI_SHADOW_INTENSITY; + return params; +} + void CompositeDropShadow(const u32 *src, int w, int h, u32 *dst, int dstStride, const DropShadowParams ¶ms) { const int radius = std::max(1, params.radius); const int pad = params.Padding(); diff --git a/Common/Render/DropShadow.h b/Common/Render/DropShadow.h index a163a1c655..95f8593ea2 100644 --- a/Common/Render/DropShadow.h +++ b/Common/Render/DropShadow.h @@ -38,6 +38,9 @@ struct DropShadowParams { } }; +// The UI's shadow, used for both the atlas images and text, so the two match on screen. +DropShadowParams UIDropShadowParams(float pixelsPerUnit); + // src is premultiplied RGBA with alpha in the top byte. Writes it composited over its shadow to dst, // (w + 2 * padding) x (h + 2 * padding) pixels with the image centered. Since the shadow is black, // only alpha changes under the image. diff --git a/Common/Render/Text/draw_text.cpp b/Common/Render/Text/draw_text.cpp index 3d6f9c0ad3..70242c7b62 100644 --- a/Common/Render/Text/draw_text.cpp +++ b/Common/Render/Text/draw_text.cpp @@ -3,11 +3,10 @@ #if defined(__linux__) || defined(__FreeBSD__) && !defined(__ANDROID__) # include #elif defined(_WIN32) -# include +# include "Common/CommonWindows.h" #endif #include -#include #include #include "ext/xxhash.h" @@ -46,13 +45,6 @@ static bool IsRenderDocLoaded() { #endif } -// Drop shadow blur radius and offset as fractions of the font size, which is in UI units. The bitmap -// has 1/dpiScale_ pixels per UI unit, so at higher DPI the blur spans more pixels. -static constexpr float SHADOW_BLUR = 1.0f / 7.0f; -static constexpr float SHADOW_OFFSET = 1.0f / 16.0f; -// Blurring thin strokes spreads them thin, so boost the shadow's alpha a bit (clamped). -static constexpr float SHADOW_STRENGTH = 1.5f; - size_t TextCacheKeyHash::operator()(const TextCacheKey &key) const { uint32_t maxWidthBits, scaleBits; memcpy(&maxWidthBits, &key.maxWidth, sizeof(maxWidthBits)); @@ -274,11 +266,8 @@ bool TextDrawer::DrawStringBitmapRect(std::vector &bitmapData, TextStri } void TextDrawer::AddDropShadow(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat srcFormat) const { - const float pixelsPerUnit = 1.0f / dpiScale_; - DropShadowParams params; - params.radius = std::max(1, (int)roundf(fontStyle_.sizePts * SHADOW_BLUR * pixelsPerUnit)); - params.offsetX = params.offsetY = std::max(1, (int)roundf(fontStyle_.sizePts * SHADOW_OFFSET * pixelsPerUnit)); - params.intensity = SHADOW_STRENGTH; + // The bitmap has 1/dpiScale_ pixels per UI unit, so at higher DPI the blur spans more pixels. + const DropShadowParams params = UIDropShadowParams(1.0f / dpiScale_); const int pad = params.Padding(); const int srcW = entry.bmWidth; diff --git a/UI/UIAtlas.cpp b/UI/UIAtlas.cpp index 3430114e95..c776eec9de 100644 --- a/UI/UIAtlas.cpp +++ b/UI/UIAtlas.cpp @@ -401,7 +401,7 @@ static bool GenerateUIAtlasImage(Atlas *atlas, float dpiScale, Image *dest, int if (!images[i].IsEmpty()) { if (imageIDs[i].addShadow) { // DEBUG_LOG(Log::G3D, "Adding drop shadow to %.*s", STR_VIEW(imageIDs[i].id)); - AddDropShadow(images[i], 3, 0.66f); + AddDropShadow(images[i], UIDropShadowParams(images[i].scale)); } else { // Make sure there are transparent pixels to filter from. Add1PxTransparentBorder(images[i]); diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 678d2d9163..6122f58ba3 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -3697,7 +3697,7 @@ bool TestTextDrawerCache() { EXPECT_EQ_INT((int)bitmap.size(), entry1.bmWidth * entry1.bmHeight * 4); const uint32_t *pixels = (const uint32_t *)bitmap.data(); auto pixel = [&](int x, int y) { return pixels[(y + pad) * entry1.bmWidth + x + pad]; }; - // The text stays opaque white, the shadow is black and partly transparent below-right of it, and the far corner is empty. + // The text stays opaque white, the shadow around it is black and partly transparent, and the far corner is empty. EXPECT_EQ_INT(pixel(8, 8), 0xFFFFFFFF); EXPECT_EQ_INT(pixel(12, 12) & 0x00FFFFFF, 0); EXPECT_TRUE((pixel(12, 12) >> 24) > 0); @@ -3708,7 +3708,7 @@ bool TestTextDrawerCache() { return true; } -// The atlas shadow has no offset, so it pads the image evenly and stays symmetric around it. +// The UI shadow has no offset, so it pads the image evenly and stays symmetric around it. bool TestAtlasDropShadow() { Image img; img.scale = 2.0f; @@ -3718,7 +3718,7 @@ bool TestAtlasDropShadow() { img.set1(x, y, 0xFFFFFFFF); } } - AddDropShadow(img, 3, 0.66f); + AddDropShadow(img, UIDropShadowParams(img.scale)); EXPECT_EQ_INT(img.w, 8 + 12); EXPECT_EQ_INT(img.h, 8 + 12); EXPECT_EQ_HEX(img.get1(6 + 3, 6 + 3), 0xFFFFFFFF);