2009-03-13 5 views
9

Я использую стороннюю библиотеку для рендеринга изображения в GDI DC, и мне нужно убедиться, что любой текст отображается без сглаживания/сглаживания, чтобы я мог преобразовать изображение в предопределенная палитра с индексированными цветами.Отключить сглаживание для определенного контекста устройства 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?

Даже если бы я мог просто сделать процесс изменений или конкретный поток, а не влиять на всю операционную систему, это было бы большим шагом вперед! (Это дало бы мне возможность обрабатывать этот рендеринг в отдельном процессе - результаты записываются на диск после рендеринга в любом случае)

EDIT: Я хотел бы добавить, что я не против, если решение сложнее, чем просто несколько вызовов API. Я даже был бы доволен решением, связанным с подключением системных DLL-систем, если бы это работало всего на несколько дней.

EDIT: информация о форекс Сторонняя библиотека отображает с использованием палитры около 70 цветов. После того, как изображение (которое на самом деле является плитой карты) передается в DC, я конвертирую каждый пиксель из его 32-битного цвета обратно в индекс его палитры и сохраняю результат как изображение с серой шкалой 8bpp. Это загружается на видеокарту в виде текстуры. Во время рендеринга я снова применяю палитру (также сохраненную как текстуру) с помощью пиксельного шейдера, выполняемого на видеокарте. Это позволяет мне мгновенно переключаться и исчезать между разными палитрами вместо необходимости регенерировать все необходимые плитки. Для создания и загрузки всех фрагментов для типичного представления мира требуется от 10 до 60 секунд.

EDIT: Переименован GraphicsDevice к Graphics Класс GraphicsDevice в предыдущей версии этого вопроса на самом деле System.Drawing.Graphics. Я переименовал его (используя GraphicsDevice = ...), потому что данный код находится в пространстве имен MyCompany.Graphics, и компилятор не смог его правильно решить.

EDIT: Success! Мне даже удалось установить функцию PatchIat ниже на C# с помощью Marshal.GetFunctionPointerForDelegate. Команда .NET interop действительно сделала фантастическую работу! Я сейчас, используя следующий синтаксис, где 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]; 
} 

ответ

3

В соответствии с просьбой, я упаковано код, который я написал, чтобы решить эту проблему, и поместил его в хранилище GitHub: http://github.com/jystic/patch-iat

Это выглядит как много кода, потому что я должен был воспроизвести все структуры Win32 для работы этого материала, и в то время я решил поместить каждый в свой собственный файл.

Если вы хотите, чтобы перейти прямо к мясу кода он находится в: ImportAddressTable.cs

Это лицензированное очень свободно и для всех намерений и целей, общественного достояния, так что не стесняйтесь использовать его в любом проекте, тебе нравится.

+0

, если этот код решил проблему, вы должны, вероятно, отметьте свой собственный ответ как принятый ответ. –

+0

@ Юстин, да, хорошая точка. Как вы можете видеть из дат, я не добавлял код C# до года спустя, поэтому я не думал об этом делать в то время. Надеюсь, что @ Chris Becke не потеряет репутацию для ответа, который был переназначен, я бы никогда не решил эту проблему без его помощи. –

+0

Молодцы! Обратите внимание, что это также работает на платформе x64, но вам нужно немного изменить структуру ImageOptionalHeader, поскольку поле _BaseOfData_ отсутствует в IMAGE_OPTIONAL_HEADER64. – Poustic

0

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

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

+0

К сожалению, мне нужно около ~ 70 цветов :( –

+0

Можете ли вы создать растровый объект в виде индексированного изображения 8bpp с использованием запрошенной «предопределенной палитры»? Отрисовка шрифтов будет сглажена, но по крайней мере она будет использовать палитру, которую вы хотите. – Unbeknown

+0

Я действительно пробовал это, и я думал, что он отлично работает, пока я не переключился на другую палитру, и сглаженные части текста, на которые ссылаются цвета в новой палитре, которые были совершенно разными для окружающих. К сожалению, я не 't управляют палитрами, они предоставляются библиотекой. –

0

- это класс 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?)

+0

Спасибо за предложение, я попробовал, но похоже, что настройка SmoothingMode влияет только на рендеринг, выполненный через сам объект Graphics. Поскольку сторонняя библиотека напрямую использует контекст устройства GDI, она обходит этот параметр. –

+0

На самом деле код, который вы предоставляете, даже не обрабатывает текст, нарисованный с использованием самой графики. Для текста есть отдельное свойство TextRenderingHint. –

5

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

Есть, как вы отметили, системные настройки. К сожалению, изменение общесистемного параметра в значительной степени является единственным (документированным) способом понизить качество рендеринга шрифтов на DC, если вы не можете контролировать содержимое 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):

  1. Объявите тип указателя на Funciton вы хотите зацепить:

    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. Hook функция когда-то во время инициализации

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

Конечно, если модуль, который вы подключаете существует в .NET приземлится его очень неясно, где CreateFontIndirect вызов будет происходить из. mscoree.dll? Фактический модуль, который вы вызываете? Успехов я думаю: P

+0

Это в значительной степени вывод, к которому я пришел :(Я надеялся, что у кого-то есть волшебство, чтобы решить мою проблему. Интересно, можно ли перехватить вызов CreateFontIndirect, а затем изменить LOGFONT, чтобы он не имел сглаживания? –

+0

Это, безусловно, возможно. Предполагая, что вы работаете с модулем, который нужно подключить, и вы знаете его дескриптор HMODULE, который, оказывается, является его базовым адресом, его «легко» исправляет таблицу адресов импорта, чтобы подключить вызов api. –

+0

Извините, я пропустил ваше редактирование до сих пор, я дам это попробовать в ближайшие несколько дней. –

Смежные вопросы