Отключить сглаживание для определенного контекста устройства GDI

StackOverflow https://stackoverflow.com/questions/641401

Вопрос

Я использую стороннюю библиотеку для визуализации изображения на контроллере GDI, и мне нужно убедиться, что любой текст отображается без сглаживания / сглаживания, чтобы я мог преобразовать изображение в предопределенную палитру с индексированными цветами.

Сторонняя библиотека, которую я использую для рендеринга, не поддерживает это и просто отображает текст в соответствии с текущими настройками 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, и компилятор не смог правильно его разрешить.

РЕДАКТИРОВАТЬ: Успех! Мне даже удалось перенести приведенную ниже функцию PatchIat на C # с помощью Marshal.GetFunctionPointerForDelegate . Команда взаимодействия .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];
}
Это было полезно?

Решение 2

В соответствии с просьбой я собрал код, который написал для решения этой проблемы, и поместил его в репозиторий github:

Другие советы

К сожалению, вы не можете. Возможность контролировать сглаживание шрифтов выполняется для каждого шрифта. Вызов GDI CreateFontIndirect обрабатывает члены структуры LOGFONT, чтобы определить, разрешено ли использование cleartype, обычного или без сглаживания.

Есть, как вы заметили, общесистемные настройки. К сожалению, изменение общесистемных настроек является в значительной степени единственным (задокументированным) способом понизить качество рендеринга шрифтов на контроллере домена, если вы не можете контролировать содержимое LOGFONT.

<Ч>

Этот код не мой. Неуправляемый C. И перехватит любую функцию, импортированную dll или exe-файлом, если вы знаете его 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 );
}

Вы бы назвали это из своего кода, выполнив что-то вроде (я не проверял, что это каким-либо образом компилируется: 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

    Вам нужно больше цветов, чем черный и белый на ваших шрифтах? Если нет, вы можете сделать объект растровым размером 1 бит на пиксель ( Format1bppIndexed ?).

    Система, вероятно, не будет сглаживать рендеринг шрифта на изображениях размером 1 bpp.

    Является ли 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 наследует класс Graphics (в противном случае попробуйте использовать класс Graphics?)

    scroll top