diff --git a/src/EterLib/FontManager.cpp b/src/EterLib/FontManager.cpp index d0ea800..7347ff9 100644 --- a/src/EterLib/FontManager.cpp +++ b/src/EterLib/FontManager.cpp @@ -1,6 +1,10 @@ #include "StdAfx.h" #include "FontManager.h" +#include +#include FT_FREETYPE_H +#include FT_LCD_FILTER_H + #include #include #include @@ -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"; diff --git a/src/EterLib/GrpFontTexture.cpp b/src/EterLib/GrpFontTexture.cpp index de18020..62bda14 100644 --- a/src/EterLib/GrpFontTexture.cpp +++ b/src/EterLib/GrpFontTexture.cpp @@ -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; diff --git a/src/EterLib/GrpTextInstance.cpp b/src/EterLib/GrpTextInstance.cpp index 0633852..3cc4072 100644 --- a/src/EterLib/GrpTextInstance.cpp +++ b/src/EterLib/GrpTextInstance.cpp @@ -559,8 +559,10 @@ void CGraphicTextInstance::Render(RECT * pClipRect) break; } - static std::unordered_map> s_vtxBatches; - s_vtxBatches.clear(); + static std::unordered_map> s_outlineBatches; + static std::unordered_map> 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& vtxBatch = s_vtxBatches[pFontTexture->GetD3DTexture()]; + std::vector& 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& vtxBatch = s_vtxBatches[pFontTexture->GetD3DTexture()]; + std::vector& 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>& 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); diff --git a/src/EterLib/TextBar.cpp b/src/EterLib/TextBar.cpp index 977d929..fcfd852 100644 --- a/src/EterLib/TextBar.cpp +++ b/src/EterLib/TextBar.cpp @@ -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; }