我正在使用第三方库将图像渲染到GDI DC,并且需要确保渲染任何文本时都没有进行任何平滑/抗锯齿处理,以便可以将图像转换为带有索引颜色的预定义调色板。
我用于渲染的第三方库不支持此功能,仅根据当前Windows字体渲染的Windows设置来渲染文本。他们还说过,他们不太可能会很快增加关闭抗锯齿功能的能力。
到目前为止,我发现最好的解决方法是以这种方式调用第三方库(为简洁起见,省略了错误处理和先前的设置检查):
private static void SetFontSmoothing(bool enabled)
{
int pv = 0;
SystemParametersInfo(Spi.SetFontSmoothing, enabled ? 1 : 0, ref pv, Spif.None);
}
// snip
Graphics graphics = Graphics.FromImage(bitmap)
IntPtr deviceContext = graphics.GetHdc();
SetFontSmoothing(false);
thirdPartyComponent.Render(deviceContext);
SetFontSmoothing(true);
这显然对操作系统产生了可怕的影响,其他应用程序从启用的cleartype闪烁到禁用的状态,并在每次渲染图像时返回。
所以问题是,有人知道我可以如何更改特定DC的字体渲染设置吗?
即使我可以使更改过程或线程特定于而不是影响整个操作系统,这也将是一大进步! (这使我可以选择将此渲染工作分配到一个单独的进程中-无论如何,渲染后将结果写入磁盘)
编辑:我想补充一点,我不介意解决方案是否比仅几个API调用更为复杂。如果只需要几天的工作,我什至会对涉及 Hook 系统dll的解决方案感到满意。
编辑:背景信息
第三方库使用大约70种颜色的调色板进行渲染。将图像(实际上是 map 图块)渲染到DC后,我将其32位颜色的每个像素转换回其调色板索引,并将结果存储为8bpp灰度图像。这作为纹理上传到视频卡。在渲染期间,我使用在视频卡上执行的像素着色器重新应用调色板(也存储为纹理)。这使我可以立即在不同的调色板之间切换和淡入淡出,而无需重新生成所有必需的瓦片。生成和上传所有图块需要10到60秒的时间才能获得典型的世界风景。
编辑:将GraphicsDevice重命名为Graphics
此问题的先前版本中的GraphicsDevice类实际上是System.Drawing.Graphics。我已将其重命名(使用GraphicsDevice = ...),因为存在问题的代码位于MyCompany.Graphics namespace 中,并且编译器无法正确解析它。
编辑:成功!
我什至在
PatchIat
的帮助下,将下面的Marshal.GetFunctionPointerForDelegate
函数移植到了C#中。 .NET互操作团队确实做了出色的工作!我现在使用以下语法,其中Patch
是System.Diagnostics.ProcessModule
的扩展方法:module.Patch(
"Gdi32.dll",
"CreateFontIndirectA",
(CreateFontIndirectA original) => font =>
{
font->lfQuality = NONANTIALIASED_QUALITY;
return original(font);
});
private unsafe delegate IntPtr CreateFontIndirectA(LOGFONTA* lplf);
private const int NONANTIALIASED_QUALITY = 3;
[StructLayout(LayoutKind.Sequential)]
private struct LOGFONTA
{
public int lfHeight;
public int lfWidth;
public int lfEscapement;
public int lfOrientation;
public int lfWeight;
public byte lfItalic;
public byte lfUnderline;
public byte lfStrikeOut;
public byte lfCharSet;
public byte lfOutPrecision;
public byte lfClipPrecision;
public byte lfQuality;
public byte lfPitchAndFamily;
public unsafe fixed sbyte lfFaceName [32];
}
最佳答案
不幸的是你不能。每个字体都具有控制字体抗锯齿的功能。 GDI调用CreateFontIndirect处理LOGFONT结构的成员,以确定其是否允许使用cleartype,常规或不使用抗锯齿。
如您所述,有系统范围的设置。不幸的是,如果您无法控制LOGFONT的内容,更改系统范围的设置几乎是降低DC上字体渲染质量的唯一(已记录)方法。
此代码不是我的。是不受管的C。如果知道HMODULE,它将钩住dll或exe文件导入的任何功能。
#define PtrFromRva( base, rva ) ( ( ( PBYTE ) base ) + rva )
/*++
Routine Description:
Replace the function pointer in a module's IAT.
Parameters:
Module - Module to use IAT from.
ImportedModuleName - Name of imported DLL from which
function is imported.
ImportedProcName - Name of imported function.
AlternateProc - Function to be written to IAT.
OldProc - Original function.
Return Value:
S_OK on success.
(any HRESULT) on failure.
--*/
HRESULT PatchIat(
__in HMODULE Module,
__in PSTR ImportedModuleName,
__in PSTR ImportedProcName,
__in PVOID AlternateProc,
__out_opt PVOID *OldProc
)
{
PIMAGE_DOS_HEADER DosHeader = ( PIMAGE_DOS_HEADER ) Module;
PIMAGE_NT_HEADERS NtHeader;
PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor;
UINT Index;
assert( Module );
assert( ImportedModuleName );
assert( ImportedProcName );
assert( AlternateProc );
NtHeader = ( PIMAGE_NT_HEADERS )
PtrFromRva( DosHeader, DosHeader->e_lfanew );
if( IMAGE_NT_SIGNATURE != NtHeader->Signature )
{
return HRESULT_FROM_WIN32( ERROR_BAD_EXE_FORMAT );
}
ImportDescriptor = ( PIMAGE_IMPORT_DESCRIPTOR )
PtrFromRva( DosHeader,
NtHeader->OptionalHeader.DataDirectory
[ IMAGE_DIRECTORY_ENTRY_IMPORT ].VirtualAddress );
//
// Iterate over import descriptors/DLLs.
//
for ( Index = 0;
ImportDescriptor[ Index ].Characteristics != 0;
Index++ )
{
PSTR dllName = ( PSTR )
PtrFromRva( DosHeader, ImportDescriptor[ Index ].Name );
if ( 0 == _strcmpi( dllName, ImportedModuleName ) )
{
//
// This the DLL we are after.
//
PIMAGE_THUNK_DATA Thunk;
PIMAGE_THUNK_DATA OrigThunk;
if ( ! ImportDescriptor[ Index ].FirstThunk ||
! ImportDescriptor[ Index ].OriginalFirstThunk )
{
return E_INVALIDARG;
}
Thunk = ( PIMAGE_THUNK_DATA )
PtrFromRva( DosHeader,
ImportDescriptor[ Index ].FirstThunk );
OrigThunk = ( PIMAGE_THUNK_DATA )
PtrFromRva( DosHeader,
ImportDescriptor[ Index ].OriginalFirstThunk );
for ( ; OrigThunk->u1.Function != NULL;
OrigThunk++, Thunk++ )
{
if ( OrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG )
{
//
// Ordinal import - we can handle named imports
// ony, so skip it.
//
continue;
}
PIMAGE_IMPORT_BY_NAME import = ( PIMAGE_IMPORT_BY_NAME )
PtrFromRva( DosHeader, OrigThunk->u1.AddressOfData );
if ( 0 == strcmp( ImportedProcName,
( char* ) import->Name ) )
{
//
// Proc found, patch it.
//
DWORD junk;
MEMORY_BASIC_INFORMATION thunkMemInfo;
//
// Make page writable.
//
VirtualQuery(
Thunk,
&thunkMemInfo,
sizeof( MEMORY_BASIC_INFORMATION ) );
if ( ! VirtualProtect(
thunkMemInfo.BaseAddress,
thunkMemInfo.RegionSize,
PAGE_EXECUTE_READWRITE,
&thunkMemInfo.Protect ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
//
// Replace function pointers (non-atomically).
//
if ( OldProc )
{
*OldProc = ( PVOID ) ( DWORD_PTR )
Thunk->u1.Function;
}
#ifdef _WIN64
Thunk->u1.Function = ( ULONGLONG ) ( DWORD_PTR )
AlternateProc;
#else
Thunk->u1.Function = ( DWORD ) ( DWORD_PTR )
AlternateProc;
#endif
//
// Restore page protection.
//
if ( ! VirtualProtect(
thunkMemInfo.BaseAddress,
thunkMemInfo.RegionSize,
thunkMemInfo.Protect,
&junk ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
return S_OK;
}
}
//
// Import not found.
//
return HRESULT_FROM_WIN32( ERROR_PROC_NOT_FOUND );
}
}
//
// DLL not found.
//
return HRESULT_FROM_WIN32( ERROR_MOD_NOT_FOUND );
}
您可以通过执行类似的操作从代码中调用它(我还没有检查过它是否可以编译:P):
typedef FARPROC (WINAPI* PFNCreateFontIndirect)(LOGFONT*);
static PFNCreateFontIndirect OldCreateFontIndirect = NULL;
WINAPI MyNewCreateFontIndirectCall(LOGFONT* plf)
{
// do stuff to plf (probably better to create a copy than tamper with passed in struct)
// chain to old proc
if(OldCreateFontIndirect)
return OldCreateFontIndirect(plf);
}
HMODULE h = LoadLibrary(TEXT("OtherDll"));
PatchIat(h, "USER32.DLL", "CreateFontIndirectW", MyNewCreateFontIndirectProc, (void**)&OldCreateFontIndirectProc);
当然,如果您要挂接的模块存在于.NET中,则不清楚
CreateFontIndirect
调用的来源。 mscoree.dll
?您实际调用的模块?祝你好运:P