禁用特定GDI设备上下文的抗锯齿

我正在使用第三方库将图像渲染到GDI DC,我需要确保在没有任何平滑/抗锯齿的情况下渲染任何文本,以便我可以将图像转换为具有索引颜色的预定义调色板。

我用于渲染的第三方库不支持这个,只是根据字体渲染的当前窗口设置呈现文本。 他们还表示他们不太可能在短时间内添加切换抗锯齿function。

到目前为止,我发现的最好的工作是以这种方式调用第三方库(为简洁起见,error handling和先前的设置检查被省略):

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); 

这显然会对操作系统产生可怕的影响,其他应用程序会在每次渲染图像时从禁用类型闪烁到禁用和返回。

所以问题是,有谁知道如何改变特定DC的字体渲染设置?

即使我可以只更改进程或线程而不是影响整个操作系统,这将是向前迈出的一大步! (这样我就可以选择将这个渲染转换为一个单独的进程 – 无论如何都会在渲染后将结果写入磁盘)

编辑:我想补充一点,我不介意解决方案是否比一些API调用更复杂。 我甚至会对一个解决方案感到满意,如果只需要一天的工作就可以挂钩系统dll。

编辑:背景信息第三方库使用约70种颜色的调色板进行渲染。 在将图像(实际上是地图图块)渲染到DC之后,我将每个像素从其32位颜色转换回其调色板索引,并将结果存储为8bpp灰度图像。 这将作为纹理上传到video卡。 在渲染过程中,我重新应用调色板(也存储为纹理),并在video卡上执行像素着色器。 这允许我瞬间在不同的调色板之间切换和淡入淡出,而不需要重新生成所有需要的切片。 生成并上传所有瓷砖需要10-60秒才能获得典型的世界观。

编辑:将GraphicsDevice重命名为Graphics这个问题的上一个版本中的GraphicsDevice类实际上是System.Drawing.Graphics。 我已经重命名它(使用GraphicsDevice = …)因为有问题的代码在命名空间MyCompany.Graphics中,并且编译器无法正确解析它。

编辑:成功! 我甚至设法在Marshal.GetFunctionPointerForDelegate的帮助下将下面的PatchIat函数移植到C#。 .NET互操作团队确实做得非常出色! 我现在使用以下语法,其中PatchSystem.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):

  1. 声明要挂钩的函数的指针类型:

     typedef FARPROC (WINAPI* PFNCreateFontIndirect)(LOGFONT*); 
  2. 实现一个钩子函数

     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); } 
  3. 在初始化期间的某个时间挂钩function

     HMODULE h = LoadLibrary(TEXT("OtherDll")); PatchIat(h, "USER32.DLL", "CreateFontIndirectW", MyNewCreateFontIndirectProc, (void**)&OldCreateFontIndirectProc); 

当然,如果您正在挂起的模块存在于.NET中,那么对于CreateFontIndirect调用将来自哪个地方则非常不清楚。 mscoree.dll ? 你打电话的实际模块? 祝你好运:P

根据要求,我打包了我编写的代码来解决这个问题并将其放在一个github存储库中: http : //github.com/jystic/patch-iat

它看起来像很多代码,因为我必须重现所有Win32结构才能使用这些东西,当时我选择将每个代码放在自己的文件中。

如果你想直接了解它的代码: ImportAddressTable.cs

它的许可非常自由,并且用于所有意图和目的,公共领域,因此可以随意在任何您喜欢的项目中使用它。

你的字体需要更多的颜色而不是黑白吗? 如果没有,您可以使您的位图对象为每像素1位图像( Format1bppIndexed ?)。

系统可能无法平滑1bpp图像上的字体渲染。

GraphicsDevice Class是第三方类吗?

我这样做的方式是:

 Graphics g = Graphics.FromImage(memImg); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; 

或者在你的情况下:

 GraphicsDevice graphics = GraphicsDevice.FromImage(bitmap) graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; 

如果GraphicsDevice类inheritanceGraphics类(否则尝试使用Graphics类?)