mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-04-02 13:37:42 -05:00
feat: Added Subpixel Font rendering (#2092)
Proof of concept for implementing subpixel processing in ImGui. This is work in progress, and it is bound to have problems. What it does: 1) Uses freetype own subpixel processing implementation to build a 32-bit color atlas for the default font only (no icons, no unifont) . 2) Avoids pixel perfect font conversion when possible. 3) Self contained, no ImGui source code changes. 4) Results in much improved legibility of fonts rendered on low dpi LCD screens that use horizontal RGB pixel layouts (no BRG or OLED or CRT if they even exist anymore) What it doesn't: 1) Fancy class based interface. The code is barely the minimum needed to show it can work. 2) Dual source color blending. That needs to be implemented in shader code, so it needs to change ImGui source code although minimally. This will result in some characters appearing dimmer than others. Easily fixed with small fragment and vertex shaders. 3) subpixel positioning. If characters are very thin they will look colored, or they can be moved to improve legibility. 4) deal with detection of fringe cases including rare pixel layouts, non LCD screens, Mac-OS not handling subpixel rendering and any other deviation from the standard LCD. 5) tries to be efficient in speed or memory use. Font Atlases will be 4 times the size they were before, but there are no noticeable delays in font loading in the examples I have tried. Any comments and code improvements are welcome. --------- Co-authored-by: Nik <werwolv98@gmail.com>
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_freetype.h>
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_LCD_FILTER_H
|
||||
#include FT_BITMAP_H
|
||||
|
||||
#include <memory>
|
||||
#include <list>
|
||||
@@ -24,6 +28,34 @@ namespace hex::fonts {
|
||||
return m_font->Descent;
|
||||
}
|
||||
|
||||
float calculateFontDescend(FT_Library ft, float fontSize) const {
|
||||
|
||||
if (ft == nullptr) {
|
||||
log::fatal("FreeType not initialized");
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
FT_Face face;
|
||||
if (FT_New_Memory_Face(ft, reinterpret_cast<const FT_Byte *>(m_font->ConfigData->FontData), m_font->ConfigData->FontDataSize, 0, &face) != 0) {
|
||||
log::fatal("Failed to load face");
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Calculate the expected font size
|
||||
auto size = fontSize;
|
||||
if (m_font->FontSize > 0.0F)
|
||||
size = m_font->FontSize * std::max(1.0F, std::floor(ImHexApi::System::getGlobalScale()));
|
||||
else
|
||||
size = std::max(1.0F, std::floor(size / ImHexApi::Fonts::DefaultFontSize)) * ImHexApi::Fonts::DefaultFontSize;
|
||||
|
||||
if (FT_Set_Pixel_Sizes(face, size, size) != 0) {
|
||||
log::fatal("Failed to set pixel size");
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return face->size->metrics.descender / 64.0F;
|
||||
}
|
||||
|
||||
ImFont* getFont() { return m_font; }
|
||||
|
||||
private:
|
||||
@@ -109,6 +141,10 @@ namespace hex::fonts {
|
||||
|
||||
Font addFontFromMemory(const std::vector<u8> &fontData, float fontSize, bool scalable, ImVec2 offset, const ImVector<ImWchar> &glyphRange = {}) {
|
||||
auto &storedFontData = m_fontData.emplace_back(fontData);
|
||||
if (storedFontData.empty()) {
|
||||
log::fatal("Failed to load font data");
|
||||
return Font();
|
||||
}
|
||||
|
||||
auto &config = m_fontConfigs.emplace_back(m_defaultConfig);
|
||||
config.FontDataOwnedByAtlas = false;
|
||||
@@ -152,7 +188,7 @@ namespace hex::fonts {
|
||||
|
||||
void setAntiAliasing(bool enabled) {
|
||||
if (enabled)
|
||||
m_defaultConfig.FontBuilderFlags &= ~ImGuiFreeTypeBuilderFlags_Monochrome | ImGuiFreeTypeBuilderFlags_MonoHinting;
|
||||
m_defaultConfig.FontBuilderFlags &= ~(ImGuiFreeTypeBuilderFlags_Monochrome | ImGuiFreeTypeBuilderFlags_MonoHinting);
|
||||
else
|
||||
m_defaultConfig.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Monochrome | ImGuiFreeTypeBuilderFlags_MonoHinting;
|
||||
}
|
||||
@@ -197,9 +233,16 @@ namespace hex::fonts {
|
||||
return m_fontAtlas;
|
||||
}
|
||||
|
||||
float calculateFontDescend(const ImHexApi::Fonts::Font &font, float fontSize) const {
|
||||
auto atlas = std::make_unique<ImFontAtlas>();
|
||||
auto cfg = m_defaultConfig;
|
||||
float calculateFontDescend( FT_Library ft, const ImHexApi::Fonts::Font &font, float fontSize) const {
|
||||
if (ft == nullptr) {
|
||||
log::fatal("FreeType not initialized");
|
||||
return 0.0f;
|
||||
}
|
||||
FT_Face face;
|
||||
if (FT_New_Memory_Face(ft, reinterpret_cast<const FT_Byte *>(font.fontData.data()), font.fontData.size(), 0, &face) != 0) {
|
||||
log::fatal("Failed to load face");
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Calculate the expected font size
|
||||
auto size = fontSize;
|
||||
@@ -208,28 +251,20 @@ namespace hex::fonts {
|
||||
else
|
||||
size = std::max(1.0F, std::floor(size / ImHexApi::Fonts::DefaultFontSize)) * ImHexApi::Fonts::DefaultFontSize;
|
||||
|
||||
cfg.MergeMode = false;
|
||||
cfg.SizePixels = size;
|
||||
cfg.FontDataOwnedByAtlas = false;
|
||||
|
||||
// Construct a range that only contains the first glyph of the font
|
||||
ImVector<ImWchar> queryRange;
|
||||
{
|
||||
auto firstGlyph = font.glyphRanges.empty() ? m_glyphRange.front() : font.glyphRanges.front().begin;
|
||||
queryRange.push_back(firstGlyph);
|
||||
queryRange.push_back(firstGlyph);
|
||||
if (FT_Set_Pixel_Sizes(face, size, size) != 0) {
|
||||
log::fatal("Failed to set pixel size");
|
||||
return false;
|
||||
}
|
||||
queryRange.push_back(0x00);
|
||||
|
||||
// Build the font atlas with the query range
|
||||
auto newFont = atlas->AddFontFromMemoryTTF(const_cast<u8 *>(font.fontData.data()), int(font.fontData.size()), 0, &cfg, queryRange.Data);
|
||||
atlas->Build();
|
||||
|
||||
return newFont->Descent;
|
||||
return face->size->metrics.descender / 64.0F;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_fontData.clear();
|
||||
m_glyphRange.clear();
|
||||
m_fontSizes.clear();
|
||||
m_fontConfigs.clear();
|
||||
m_fontAtlas->Clear();
|
||||
m_defaultConfig.MergeMode = false;
|
||||
}
|
||||
|
||||
@@ -255,5 +290,4 @@ namespace hex::fonts {
|
||||
|
||||
std::list<std::vector<u8>> m_fontData;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,8 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
#include <hex/api/content_registry.hpp>
|
||||
|
||||
namespace hex::fonts {
|
||||
class AntialiasPicker : public ContentRegistry::Settings::Widgets::DropDown {
|
||||
public:
|
||||
AntialiasPicker() : DropDown(
|
||||
std::vector<UnlocalizedString>({"hex.fonts.setting.font.antialias_none", "hex.fonts.setting.font.antialias_grayscale", "hex.fonts.setting.font.antialias_subpixel"}),
|
||||
std::vector<nlohmann::json>({"none" , "grayscale" , "subpixel"}),
|
||||
nlohmann::json("subpixel")
|
||||
){}
|
||||
};
|
||||
|
||||
class FontFilePicker : public ContentRegistry::Settings::Widgets::FilePicker {
|
||||
public:
|
||||
@@ -31,7 +40,7 @@ namespace hex::fonts {
|
||||
|
||||
class FontSelector : public ContentRegistry::Settings::Widgets::Widget {
|
||||
public:
|
||||
FontSelector() : m_fontSize(16, 2, 100), m_bold(false), m_italic(false), m_antiAliased(true) { }
|
||||
FontSelector() : m_fontSize(ImHexApi::Fonts::pointsToPixels(10), 2, 100), m_antiAliased(), m_bold(false), m_italic(false) { }
|
||||
|
||||
bool draw(const std::string &name) override;
|
||||
|
||||
@@ -43,7 +52,7 @@ namespace hex::fonts {
|
||||
[[nodiscard]] float getFontSize() const;
|
||||
[[nodiscard]] bool isBold() const;
|
||||
[[nodiscard]] bool isItalic() const;
|
||||
[[nodiscard]] bool isAntiAliased() const;
|
||||
[[nodiscard]] const std::string antiAliasingType() const;
|
||||
|
||||
private:
|
||||
bool drawPopup();
|
||||
@@ -51,7 +60,8 @@ namespace hex::fonts {
|
||||
private:
|
||||
FontFilePicker m_fontFilePicker;
|
||||
SliderPoints m_fontSize;
|
||||
ContentRegistry::Settings::Widgets::Checkbox m_bold, m_italic, m_antiAliased;
|
||||
AntialiasPicker m_antiAliased;
|
||||
ContentRegistry::Settings::Widgets::Checkbox m_bold, m_italic;
|
||||
|
||||
bool m_applyEnabled = false;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user