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

C# 禁用特定GDI设备上下文的抗锯齿,c#,winapi,hook,gdi,cleartype,C#,Winapi,Hook,Gdi,Cleartype,我正在使用第三方库将图像渲染到GDI DC,我需要确保渲染任何文本时没有任何平滑/抗锯齿,以便我可以将图像转换为具有索引颜色的预定义调色板 我用于渲染的第三方库不支持此功能,仅根据当前windows字体渲染设置渲染文本。他们还表示,他们不太可能在短期内添加关闭抗锯齿功能的功能 到目前为止,我发现最好的解决方法是以这种方式调用第三方库(为简洁起见,需要进行错误处理和先前的设置检查): 这显然会对操作系统产生可怕的影响,每次我渲染图像时,其他应用程序都会从cleartype enabled闪烁到di

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

我用于渲染的第三方库不支持此功能,仅根据当前windows字体渲染设置渲染文本。他们还表示,他们不太可能在短期内添加关闭抗锯齿功能的功能

到目前为止,我发现最好的解决方法是以这种方式调用第三方库(为简洁起见,需要进行错误处理和先前的设置检查):

这显然会对操作系统产生可怕的影响,每次我渲染图像时,其他应用程序都会从cleartype enabled闪烁到disabled再闪烁回来

所以问题是,有人知道我如何更改特定DC的字体呈现设置吗

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

编辑:我想补充一点,我不介意解决方案是否比几个API调用更复杂。我甚至会很高兴有一个解决方案,涉及挂钩系统DLL,如果它只是一天的工作

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

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

编辑:成功
Marshal.GetFunctionPointerForDelegate
的帮助下,我甚至成功地将下面的
PatchIat
函数移植到了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];
}

你的字体需要比黑白更多的颜色吗? 如果没有,您可以将位图对象设置为每像素1位图像(Format1Bppined?)


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

GraphicsDevice类别是否为第三方类别

我的做法是:

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类继承了Graphics类(否则请尝试使用Graphics类?

很遗憾,您不能。控制字体抗锯齿的功能是按字体进行的。GDI调用CreateFontIndirect处理LOGFONT结构的成员,以确定是否允许其使用cleartype、regular或no抗锯齿

正如您所指出的,系统范围内有一些设置。不幸的是,如果无法控制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

    根据要求,我已经打包了我为解决此问题而编写的代码,并将其放在github存储库中:

    它看起来像是很多代码,因为我必须复制所有的Win32结构才能让它工作,当时我选择将每一个都放在自己的文件中

    如果您想直接进入代码的核心,它位于:
    importadresstable.cs


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

    不幸的是,我需要大约70种颜色:(您可以使用要求的“预定义调色板”将位图对象创建为索引的8bpp图像吗?字体渲染会变得平滑,但至少它会使用你想要的调色板…实际上我试过了,我认为效果很好,直到我切换到另一个调色板,文本的抗锯齿部分引用了新调色板中的颜色,这与周围的颜色完全不同。不幸的是,我无法控制palETTE,它们是由图书馆提供的。我想没有任何改变,你可以在不同的计算机上进行所有的离线处理,在那里你的当前技术不会冒犯任何用户。这是我可以考虑的事情。但是,在线生成瓦片是非常有用的,因为它允许用户安装额外的细节的新软件包。打开和关闭图层。当进行这些类型的更改时,所有内容都必须重新生成。谢谢您的建议
    HMODULE h = LoadLibrary(TEXT("OtherDll"));
    PatchIat(h, "USER32.DLL", "CreateFontIndirectW", MyNewCreateFontIndirectProc, (void**)&OldCreateFontIndirectProc);