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

发布于 2024-07-15 04:18:58 字数 2452 浏览 3 评论 0原文

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

我用于渲染的第三方库不支持此功能,并且仅根据当前 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 调用更复杂。 如果只需要大约一天的工作,我什至会对涉及挂钩系统 dll 的解决方案感到满意。

编辑:背景信息 第三方库使用大约 70 种颜色的调色板进行渲染。 将图像(实际上是地图图块)渲染到 DC 后,我将每个像素从 32 位颜色转换回其调色板索引,并将结果存储为 8bpp 灰度图像。 这将作为纹理上传到视频卡。 在渲染过程中,我通过在显卡上执行的像素着色器重新应用调色板(也存储为纹理)。 这使我可以立即在不同调色板之间切换和淡入淡出,而无需重新生成所有所需的图块。 生成并上传典型世界视图的所有图块需要 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];
}

I'm using a third party library to render an image to a GDI DC and I need to ensure that any text is rendered without any smoothing/antialiasing so that I can convert the image to a predefined palette with indexed colors.

The third party library i'm using for rendering doesn't support this and just renders text as per the current windows settings for font rendering. They've also said that it's unlikely they'll add the ability to switch anti-aliasing off any time soon.

The best work around I've found so far is to call the third party library in this way (error handling and prior settings checks ommitted for brevity):

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

This obviously has a horrible effect on the operating system, other applications flicker from cleartype enabled to disabled and back every time I render the image.

So the question is, does anyone know how I can alter the font rendering settings for a specific DC?

Even if I could just make the changes process or thread specific instead of affecting the whole operating system, that would be a big step forward! (That would give me the option of farming this rendering out to a separate process- the results are written to disk after rendering anyway)

EDIT: I'd like to add that I don't mind if the solution is more complex than just a few API calls. I'd even be happy with a solution that involved hooking system dlls if it was only about a days work.

EDIT: Background Information
The third-party library renders using a palette of about 70 colors. After the image (which is actually a map tile) is rendered to the DC, I convert each pixel from it's 32-bit color back to it's palette index and store the result as an 8bpp greyscale image. This is uploaded to the video card as a texture. During rendering, I re-apply the palette (also stored as a texture) with a pixel shader executing on the video card. This allows me to switch and fade between different palettes instantaneously instead of needing to regenerate all the required tiles. It takes between 10-60 seconds to generate and upload all the tiles for a typical view of the world.

EDIT: Renamed GraphicsDevice to Graphics
The class GraphicsDevice in the previous version of this question is actually System.Drawing.Graphics. I had renamed it (using GraphicsDevice = ...) because the code in question is in the namespace MyCompany.Graphics and the compiler wasn't able resolve it properly.

EDIT: Success!
I even managed to port the PatchIat function below to C# with the help of Marshal.GetFunctionPointerForDelegate. The .NET interop team really did a fantastic job! I'm now using the following syntax, where Patch is an extension method on 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];
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

水水月牙 2024-07-22 04:18:58

不幸的是你不能。 控制字体抗锯齿的能力是按字体完成的。 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) 
      { 
        // 对 plf 做一些事情(创建一个副本可能比篡改传入的结构更好) 
        // 链接到旧进程 
        if(OldCreateFontIndirect) 
          返回 OldCreateFontIndirect(plf); 
      } 
      
  3. 在初始化期间的某个时间钩住该函数

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

当然,如果您要挂钩的模块存在于 .NET 领域,则非常不清楚 CreateFontIndirect 调用将源自何处。 mscoree.dll? 您调用的实际模块? 我想祝你好运:P

Unfortunately you cant. The ability to control font anti aliasing is done per font. The GDI call CreateFontIndirect processes members of the LOGFONT struct to determine if its allowed to use cleartype, regular or no anti aliasing.

There are, as you noted, system wide settings. Unfortunately, changing the systemwide setting is pretty much the only (documented) way to downgrade the quality of font rendering on a DC if you cannot control the contents of the LOGFONT.


This code is not mine. Is unmanaged C. And will hook any function imported by a dll or exe file if you know its HMODULE.

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

You would call this from your code by doing something like (I haven't checked that this in any way compiles :P):

  1. Declare a pointer type to the funciton you want to hook:

    typedef FARPROC (WINAPI* PFNCreateFontIndirect)(LOGFONT*);
    
  2. Implement a hook function

    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. Hook the function sometime during initialization

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

Of course, if the module you are hooking exists in .NET land its very unclear as to where the CreateFontIndirect call is going to originate from. mscoree.dll? The actual module you call? Good luck I guess :P

谈场末日恋爱 2024-07-22 04:18:58

按照要求,我已经打包了我为解决这个问题而编写的代码,并将其放在github存储库中: http ://github.com/jystic/patch-iat

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

如果您想直接了解代码的核心部分,请访问:ImportAddressTable.cs

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

As requested, I have packaged up the code I wrote to solve this problem and placed it in a github repository: http://github.com/jystic/patch-iat

It looks like a lot of code because I had to reproduce all the Win32 structures for this stuff to work, and at the time I chose to put each one in its own file.

If you want to go straight to the meat of of the code it's in: ImportAddressTable.cs

It's licensed very freely and is for all intents and purposes, public domain, so feel free to use it in any project that you like.

小猫一只 2024-07-22 04:18:58

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

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

Do you need more colours than black and white on your fonts?
If not, you could make your bitmap object a 1 bit per pixel image (Format1bppIndexed?).

The system will probably not smooth font rendering on 1bpp images.

身边 2024-07-22 04:18:58

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 类?)

is the GraphicsDevice Class a 3rd party class?

the way i would do this is:

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

or in your case:

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

if the GraphicsDevice Class inherits the Graphics class (otherwise try using the Graphics class?)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文