Skip to content

Commit

Permalink
Add subpixel rendering proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
KyrietS committed Mar 12, 2024
1 parent c794cb7 commit 9b861a1
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 14 deletions.
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ add_example_project(Example_4_FontFromMemory)
add_example_project(Example_5_RenderFontSDF)
add_example_project(Example_6_TextShaping)
add_example_project(Example_7_MeasureText)
add_example_project(Example_8_RenderFontSubpixel)

# Add Trex library
add_subdirectory(../ trexlib)
Expand Down
68 changes: 68 additions & 0 deletions examples/Example_8_RenderFontSubpixel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "Trex/Atlas.hpp"
#include "raylib.h"

Image GetAtlasAsBitmapImage( Trex::Atlas& atlas )
{
Image atlasImage;
atlasImage.data = atlas.GetBitmap().data(); // pointer to the atlas bitmap data
atlasImage.width = (int)atlas.GetWidth(); // width of the atlas bitmap
atlasImage.height = (int)atlas.GetHeight(); // height of the atlas bitmap
atlasImage.mipmaps = 1;
atlasImage.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8;
//atlasImage.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
return atlasImage;
}

void RenderGlyph( float x, float y, const Trex::Glyph& glyph, Texture2D& atlasTexture )
{
Rectangle atlasFragment = {
.x = (float)glyph.x / 3, // top-left corner of the glyph in the atlas
.y = (float)glyph.y, // top-left corner of the glyph in the atlas
.width = (float)glyph.width / 3, // width of the glyph in the atlas
.height = (float)glyph.height // height of the glyph in the atlas
};

// Draw a texture fragment from the atlas
DrawTextureRec( atlasTexture, atlasFragment, { x, y }, WHITE );
}

int main()
{
constexpr int FONT_SIZE = 64;
const char* FONT_PATH = "fonts/Roboto-Regular.ttf";

Trex::Atlas atlas( FONT_PATH, FONT_SIZE, Trex::Charset::Ascii() );

atlas.SaveToFile( "LCD_Atlas.png" );

InitWindow( 500, 250, "RenderSingleCharacters Example" );

// Load atlas texture
Image atlasImage = GetAtlasAsBitmapImage( atlas );
Texture2D atlasTexture = LoadTextureFromImage( atlasImage );

while( !WindowShouldClose() )
{
BeginDrawing();
ClearBackground( WHITE );

// Character 'a'
const Trex::Glyph& glyphA = atlas.GetGlyphByCodepoint( 'a' );
RenderGlyph( 50, 50, glyphA, atlasTexture );

// Character '@'
const Trex::Glyph& glyphAtSign = atlas.GetGlyphByCodepoint( '@' );
RenderGlyph( 150, 50, glyphAtSign, atlasTexture );

// Character from outside the ASCII charset
const Trex::Glyph& glyphUndefined = atlas.GetGlyphByCodepoint( (char)178 );
RenderGlyph( 250, 50, glyphUndefined, atlasTexture );

EndDrawing();
}

UnloadTexture( atlasTexture );
CloseWindow();

return 0;
}
2 changes: 1 addition & 1 deletion include/Trex/Atlas.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Trex
int bearingX, bearingY;
};

enum class RenderMode { DEFAULT, SDF, /* LCD */ };
enum class RenderMode { DEFAULT, SDF, LCD };

using AtlasBitmap = std::vector<uint8_t>; // 1-byte GRAYSCALE
using AtlasGlyphs = std::map<uint32_t, Glyph>; // key: glyph index in font (NOT codepoint!)
Expand Down
48 changes: 35 additions & 13 deletions src/Atlas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,24 @@ namespace Trex
return fontFace->glyph;
}

FT_GlyphSlot LoadGlyphWithSubpixelRender( FT_Face fontFace, uint32_t codepoint )
{
auto glyphNormal = LoadGlyphWithoutRender( fontFace, codepoint, RenderMode::DEFAULT );
auto normalWidth = glyphNormal->bitmap.width;

FT_Load_Char( fontFace, codepoint, FT_LOAD_DEFAULT );
FT_Render_Glyph( fontFace->glyph, FT_RENDER_MODE_LCD );

auto& glyph = fontFace->glyph;

return fontFace->glyph;
}

FT_GlyphSlot LoadGlyphWithRender(FT_Face fontFace, uint32_t codepoint, RenderMode mode)
{
// Temporary to test subpixel rendering
return LoadGlyphWithSubpixelRender( fontFace, codepoint );

auto glyph = LoadGlyphWithoutRender(fontFace, codepoint, mode);

// Use bsdf renderer instead of sdf renderer.
Expand Down Expand Up @@ -100,11 +116,14 @@ namespace Trex
allMetrics.reserve(charset.Size());
for (uint32_t codepoint : charset.Codepoints())
{
auto glyph = LoadGlyphWithoutRender(fontFace, codepoint, mode);
// The size changes when glyph is rendered with subpixel rendering.
// I don't know if it's possible to get the size of the glyph without rendering it.
auto glyph = LoadGlyphWithSubpixelRender(fontFace, codepoint);
//auto glyph = LoadGlyphWithoutRender(fontFace, codepoint, mode);

GlyphMetrics metrics
{
.width = glyph->bitmap.width,
.width = glyph->bitmap.width, // in bytes, not pixels
.height = glyph->bitmap.rows
};
allMetrics.push_back(metrics);
Expand All @@ -121,15 +140,15 @@ namespace Trex
unsigned int maxHeight = 0;
for (const auto& glyph : metrics)
{
unsigned int glyphWidth = glyph.width + padding * 2;
unsigned int glyphWidth = glyph.width/3 + padding * 2; // in pixels
unsigned int glyphHeight = glyph.height + padding * 2;

maxHeight = std::max(maxHeight, glyphHeight);
if (x + glyphWidth > atlasSize) // Next row
{
x = 0;
y += static_cast<int>(maxHeight);
maxHeight = 0;
maxHeight = glyphHeight;
}
if (y + glyphHeight > atlasSize)
{
Expand All @@ -153,7 +172,8 @@ namespace Trex

std::pair<AtlasBitmap, AtlasGlyphs> BuildAtlasBitmap(Font& font, const Charset& charset, unsigned int atlasSize, int padding, RenderMode mode)
{
AtlasBitmap bitmap(atlasSize * atlasSize);
unsigned int altasWidth = atlasSize * 3; // in bytes
AtlasBitmap bitmap(atlasSize * altasWidth );
std::fill(bitmap.begin(), bitmap.end(), 255); // Fill with white
AtlasGlyphs glyphs;

Expand All @@ -164,32 +184,33 @@ namespace Trex
for (uint32_t codepoint : charset.Codepoints())
{
auto glyph = LoadGlyphWithRender(face, codepoint, mode);
unsigned int glyphWidth = glyph->bitmap.width;
unsigned int glyphWidth = glyph->bitmap.width; // in bytes
unsigned int glyphHeight = glyph->bitmap.rows;
int glyphPitch = glyph->bitmap.pitch; // also known as stride

int glyphWidthPadding = static_cast<int>(glyphWidth) + padding * 2;
int glyphWidthPadding = static_cast<int>(glyphWidth) + padding*3 * 2; // in bytes
int glyphHeightPadding = static_cast<int>(glyphHeight) + padding * 2;

maxHeight = std::max(maxHeight, glyphHeightPadding);
// If we are out of atlas bounds, go to the next line
if (atlasX + glyphWidthPadding > atlasSize)
if (atlasX + glyphWidthPadding > altasWidth)
{
atlasX = 0;
atlasY += maxHeight;
maxHeight = 0;
maxHeight = glyphHeightPadding;
}
// Copy glyph bitmap to atlas bitmap
for (unsigned int glyphY = 0; glyphY < glyphHeight; ++glyphY)
{
for (unsigned int glyphX = 0; glyphX < glyphWidth; ++glyphX)
{
unsigned int atlasBitmapIndex = (atlasY + glyphY + padding) * atlasSize + (atlasX + glyphX + padding);
unsigned int glyphIndex = glyphY * glyphWidth + glyphX;
unsigned int atlasBitmapIndex = (atlasY + glyphY + padding) * altasWidth + (atlasX + glyphX + padding*3);
unsigned int glyphIndex = glyphY * glyphPitch + glyphX;
bitmap.at(atlasBitmapIndex) = 255 - glyph->bitmap.buffer[glyphIndex];
}
}

int x0 = atlasX + padding;
int x0 = atlasX + padding*3;
int y0 = atlasY + padding;

// Multiple glyphs can have index=0.
Expand Down Expand Up @@ -287,12 +308,13 @@ namespace Trex
void Atlas::SaveToFile(const std::string& path) const
{
constexpr int GRAY_SCALE = 1;
constexpr int RGB = 3;
const int width = static_cast<int>(m_Width);
const int height = static_cast<int>(m_Height);

if (path.ends_with(".png"))
{
stbi_write_png(path.c_str(), width, height, GRAY_SCALE, m_Data.data(), width);
stbi_write_png(path.c_str(), width, height, RGB, m_Data.data(), 0);
}
else if (path.ends_with(".bmp"))
{
Expand Down

0 comments on commit 9b861a1

Please sign in to comment.