FreeType: LCD subpixel
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
#include "StdAfx.h"
|
||||
#include "FontManager.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_LCD_FILTER_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <sys/stat.h>
|
||||
@@ -54,6 +58,9 @@ bool CFontManager::Initialize()
|
||||
|
||||
m_bInitialized = true;
|
||||
|
||||
// Enable LCD subpixel filter to reduce color fringing
|
||||
FT_Library_SetLcdFilter(m_ftLibrary, FT_LCD_FILTER_DEFAULT);
|
||||
|
||||
// Register default font name -> file mappings
|
||||
// Korean system fonts
|
||||
m_fontPathMap["gulim"] = "gulim.ttc";
|
||||
|
||||
@@ -277,16 +277,16 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (FT_Load_Glyph(m_ftFace, glyphIndex, FT_LOAD_DEFAULT) != 0)
|
||||
if (FT_Load_Glyph(m_ftFace, glyphIndex, FT_LOAD_TARGET_LCD) != 0)
|
||||
return NULL;
|
||||
|
||||
if (FT_Render_Glyph(m_ftFace->glyph, FT_RENDER_MODE_NORMAL) != 0)
|
||||
if (FT_Render_Glyph(m_ftFace->glyph, FT_RENDER_MODE_LCD) != 0)
|
||||
return NULL;
|
||||
|
||||
FT_GlyphSlot slot = m_ftFace->glyph;
|
||||
FT_Bitmap& bitmap = slot->bitmap;
|
||||
|
||||
int glyphBitmapWidth = bitmap.width;
|
||||
int glyphBitmapWidth = bitmap.width / 3; // LCD bitmap is 3x wider (R,G,B per pixel)
|
||||
int glyphBitmapHeight = bitmap.rows;
|
||||
int bearingX = slot->bitmap_left;
|
||||
int bearingY = slot->bitmap_top;
|
||||
@@ -347,7 +347,7 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
|
||||
}
|
||||
}
|
||||
|
||||
// Copy grayscale FreeType bitmap into atlas buffer with gamma correction
|
||||
// Copy LCD subpixel FreeType bitmap into atlas buffer (R,G,B per-channel coverage)
|
||||
for (int row = 0; row < glyphBitmapHeight; ++row)
|
||||
{
|
||||
int atlasY = m_y + yOffset + row;
|
||||
@@ -359,11 +359,15 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
|
||||
|
||||
for (int col = 0; col < glyphBitmapWidth; ++col)
|
||||
{
|
||||
unsigned char alpha = srcRow[col];
|
||||
if (alpha)
|
||||
unsigned char r = srcRow[col * 3 + 0];
|
||||
unsigned char g = srcRow[col * 3 + 1];
|
||||
unsigned char b = srcRow[col * 3 + 2];
|
||||
if (r | g | b)
|
||||
{
|
||||
alpha = s_alphaGammaLUT.table[alpha];
|
||||
dstRow[col] = ((DWORD)alpha << 24) | 0x00FFFFFF;
|
||||
unsigned char a = (r > g) ? r : g;
|
||||
if (b > a) a = b;
|
||||
// A8R8G8B8: A=max_coverage, R=r_cov, G=g_cov, B=b_cov
|
||||
dstRow[col] = ((DWORD)a << 24) | ((DWORD)r << 16) | ((DWORD)g << 8) | (DWORD)b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,7 +387,7 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
|
||||
rNewCharInfo.advance = advance;
|
||||
rNewCharInfo.bearingX = (float)bearingX;
|
||||
|
||||
m_x += cellWidth;
|
||||
m_x += cellWidth + 1; // +1 horizontal padding to prevent bilinear bleed
|
||||
|
||||
if (m_step < cellHeight)
|
||||
m_step = cellHeight;
|
||||
|
||||
@@ -559,8 +559,10 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
|
||||
break;
|
||||
}
|
||||
|
||||
static std::unordered_map<LPDIRECT3DTEXTURE9, std::vector<SVertex>> s_vtxBatches;
|
||||
s_vtxBatches.clear();
|
||||
static std::unordered_map<LPDIRECT3DTEXTURE9, std::vector<SVertex>> s_outlineBatches;
|
||||
static std::unordered_map<LPDIRECT3DTEXTURE9, std::vector<SVertex>> s_mainBatches;
|
||||
s_outlineBatches.clear();
|
||||
s_mainBatches.clear();
|
||||
|
||||
STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
@@ -577,6 +579,10 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
|
||||
|
||||
// LCD subpixel rendering: mask alpha writes to prevent corruption during two-pass blending
|
||||
STATEMANAGER.SaveRenderState(D3DRS_COLORWRITEENABLE,
|
||||
D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE);
|
||||
|
||||
{
|
||||
const float fFontHalfWeight=1.0f;
|
||||
|
||||
@@ -647,7 +653,7 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
|
||||
fFontEy = fFontSy + fFontHeight;
|
||||
|
||||
pFontTexture->SelectTexture(pCurCharInfo->index);
|
||||
std::vector<SVertex>& vtxBatch = s_vtxBatches[pFontTexture->GetD3DTexture()];
|
||||
std::vector<SVertex>& vtxBatch = s_outlineBatches[pFontTexture->GetD3DTexture()];
|
||||
|
||||
akVertex[0].u=pCurCharInfo->left;
|
||||
akVertex[0].v=pCurCharInfo->top;
|
||||
@@ -755,7 +761,7 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
|
||||
fFontEy = fFontSy + fFontHeight;
|
||||
|
||||
pFontTexture->SelectTexture(pCurCharInfo->index);
|
||||
std::vector<SVertex>& vtxBatch = s_vtxBatches[pFontTexture->GetD3DTexture()];
|
||||
std::vector<SVertex>& vtxBatch = s_mainBatches[pFontTexture->GetD3DTexture()];
|
||||
|
||||
akVertex[0].x=fFontSx;
|
||||
akVertex[0].y=fFontSy;
|
||||
@@ -859,13 +865,46 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [pTexture, vtxBatch] : s_vtxBatches) {
|
||||
if (vtxBatch.empty())
|
||||
continue;
|
||||
// LCD subpixel two-pass rendering: correct per-channel alpha blending
|
||||
auto DrawBatchLCD = [](const std::unordered_map<LPDIRECT3DTEXTURE9, std::vector<SVertex>>& batches, bool skipPass2) {
|
||||
for (const auto& [pTexture, vtxBatch] : batches) {
|
||||
if (vtxBatch.empty())
|
||||
continue;
|
||||
|
||||
STATEMANAGER.SetTexture(0, pTexture);
|
||||
STATEMANAGER.DrawPrimitiveUP(D3DPT_TRIANGLELIST, vtxBatch.size() / 3, vtxBatch.data(), sizeof(SVertex));
|
||||
}
|
||||
STATEMANAGER.SetTexture(0, pTexture);
|
||||
|
||||
// Pass 1: dest.rgb *= (1 - coverage.rgb)
|
||||
STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
|
||||
STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.DrawPrimitiveUP(D3DPT_TRIANGLELIST,
|
||||
vtxBatch.size() / 3, vtxBatch.data(), sizeof(SVertex));
|
||||
|
||||
if (!skipPass2) {
|
||||
// Pass 2: dest.rgb += textColor.rgb * coverage.rgb
|
||||
STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
|
||||
STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
|
||||
STATEMANAGER.DrawPrimitiveUP(D3DPT_TRIANGLELIST,
|
||||
vtxBatch.size() / 3, vtxBatch.data(), sizeof(SVertex));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Draw outline batches first (skip Pass 2 for black outlines — MODULATE with black = 0)
|
||||
bool outlineIsBlack = ((m_dwOutLineColor & 0x00FFFFFF) == 0);
|
||||
DrawBatchLCD(s_outlineBatches, outlineIsBlack);
|
||||
|
||||
// Draw main text batches (always both passes)
|
||||
DrawBatchLCD(s_mainBatches, false);
|
||||
|
||||
if (m_isCursor)
|
||||
{
|
||||
@@ -973,6 +1012,7 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
|
||||
}
|
||||
}
|
||||
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_COLORWRITEENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_SRCBLEND);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_DESTBLEND);
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ void CTextBar::GetTextExtent(const char* c_szText, SIZE* p_size)
|
||||
}
|
||||
|
||||
if (FT_Load_Glyph(m_ftFace, glyphIndex, FT_LOAD_DEFAULT) == 0)
|
||||
totalAdvance += (int)ceilf((float)(m_ftFace->glyph->advance.x) / 64.0f);
|
||||
totalAdvance += (int)ceilf((float)(m_ftFace->glyph->advance.x) / 64.0f) + 1; // +1px letter spacing
|
||||
|
||||
prevIndex = glyphIndex;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ void CTextBar::TextOut(int ix, int iy, const char * c_szText)
|
||||
}
|
||||
}
|
||||
|
||||
penX += (int)ceilf((float)(slot->advance.x) / 64.0f);
|
||||
penX += (int)ceilf((float)(slot->advance.x) / 64.0f) + 1; // +1px letter spacing
|
||||
prevIndex = glyphIndex;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user