2009-03-13 9 views
9

Estoy utilizando una biblioteca de terceros para representar una imagen en un GDI DC y necesito asegurarme de que cualquier texto se representa sin suavizado/antialiasing para poder convertir la imagen a una paleta predefinida con colores indexados.Desactivar antialiasing para un contexto de dispositivo GDI específico

La biblioteca de terceros que estoy utilizando para el procesamiento no es compatible con esto y solo representa el texto según la configuración actual de Windows para la representación de fuentes. También dijeron que es poco probable que agreguen la capacidad de desactivar el anti-aliasing en el corto plazo.

El mejor trabajo en todo lo que he encontrado hasta ahora es llamar a la biblioteca de terceros de esta manera (manejo de errores y controles de ajustes previos omitido por razones de brevedad):

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

Obviamente, esto tiene un efecto horrible en el sistema operativo, otras aplicaciones parpadean desde cleartype habilitado a desactivado y viceversa cada vez que renderizo la imagen.

Entonces, la pregunta es, ¿alguien sabe cómo puedo alterar la configuración de representación de fuente para un DC específico?

Incluso si pudiera hacer el proceso de cambios o el hilo específico en lugar de afectar todo el sistema operativo, ¡eso sería un gran paso adelante! (Eso me daría la opción de la agricultura de esta traducción a un proceso- por separado los resultados se escriben en el disco después de la prestación de todos modos)

EDIT: me gustaría añadir que no me importa si la solución es más complejo que solo unas pocas llamadas API. Incluso estaría contento con una solución que implicara conectar dlls del sistema si solo fuera un día de trabajo.

EDITAR: Información de fondo La biblioteca de terceros representa usando una paleta de aproximadamente 70 colores. Después de que la imagen (que en realidad es un mosaico de mapa) se represente en el DC, convierto cada píxel de su color de 32 bits a su índice de paleta y almaceno el resultado como una imagen de escala de grises de 8bpp. Esto se carga en la tarjeta de video como una textura. Durante el renderizado, vuelvo a aplicar la paleta (también almacenada como una textura) con un sombreador de píxeles que se ejecuta en la tarjeta de video. Esto me permite cambiar y fundir instantáneamente diferentes paletas en lugar de tener que volver a generar todas las fichas requeridas. Se tarda entre 10-60 segundos para generar y cargar todas las fichas para una vista típica del mundo.

EDIT: GraphicsDevice Renombrado de Gráficos El GraphicsDevice clase en la versión anterior de esta pregunta es en realidad System.Drawing.Graphics. Le cambié el nombre (usando GraphicsDevice = ...) porque el código en cuestión está en el espacio de nombres MyCompany.Graphics y el compilador no pudo resolverlo correctamente.

EDIT: ¡Éxito! Incluso pude portar la función PatchIat a continuación a C# con la ayuda de Marshal.GetFunctionPointerForDelegate. ¡El equipo de interoperabilidad de .NET realmente hizo un trabajo fantástico! Ahora estoy usando la siguiente sintaxis, donde Patch es un método de extensión en 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]; 
} 

Respuesta

3

a lo solicitado, que ha empaquetado el código que he escrito para resolver este problema y lo colocó en un repositorio GitHub: http://github.com/jystic/patch-iat

Se ve como una gran cantidad de código, porque tenía que reproducir todas las estructuras de Win32 para que esto funcione, y en el momento en que decidí poner cada una en su propio archivo.

Si desea ir directamente a la carne de del código está en: ImportAddressTable.cs

Se licencia con mucha libertad y es para todos los efectos, de dominio público, por lo que no dude en utilizarlo en cualquier proyecto que te gusta.

+0

si este código resolvió el problema, probablemente debería Marque su propia respuesta como la respuesta aceptada. –

+0

@Justin, sí, buen punto. Como se puede ver en las fechas, no puse el código C# hasta un año después, así que no pensé en hacer eso en ese momento. Espero que @Chris Becke no pierda representante por la respuesta que se reasigne, nunca hubiera resuelto este problema sin su ayuda. –

+0

¡Bien hecho! Tenga en cuenta que esto también funciona en la plataforma x64 pero necesita modificar ligeramente la estructura de ImageOptionalHeader ya que el campo _BaseOfData_ no está presente en IMAGE_OPTIONAL_HEADER64. – Poustic

0

¿Necesita más colores que el blanco y negro en sus fuentes? Si no, puede hacer que su objeto de mapa de bits una imagen de 1 bit por píxel (Formato1bppIndexed?).

El sistema probablemente no suavizará la representación de fuentes en imágenes de 1bpp.

+0

Lamentablemente necesito alrededor de ~ 70 colores :( –

+0

¿Se puede crear el objeto de mapa de bits como una imagen de 8bpp indexada utilizando la "paleta predefinida" solicitada? La representación de la fuente se suavizará pero al menos usaría la paleta que desee. – Unbeknown

+0

De hecho, lo intenté, y pensé que funcionaba muy bien, hasta que cambié a una paleta diferente y las partes antialias del texto hicieron referencia a colores en la nueva paleta, que eran totalmente diferentes a los surrounds. Desafortunadamente no 't controla las paletas, son proporcionadas por la biblioteca. –

0

es la clase GraphicsDevice una clase de terceros?

el camino Me gustaría hacer esto es:

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

o en su caso:

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

si la clase GraphicsDevice hereda la clase Graphics (de lo contrario tratar de usar la clase Graphics?)

+0

Gracias por la sugerencia, lo intenté, pero parece que configurar SmoothingMode solo afecta a la representación realizada a través del objeto Graphics en sí. Como la biblioteca de terceros usa el contexto del dispositivo GDI directamente, omite esta configuración. –

+0

En realidad, el código que proporciona ni siquiera maneja el texto dibujado usando Graphics. Para el texto, hay una propiedad TextRenderingHint separada. –

5

Desafortunadamente no puedes. La capacidad de controlar el aliasing de fuentes se realiza por fuente. La llamada de GDI CreateFontIndirect procesa miembros de la estructura de LOGFONT para determinar si se le permite usar cleartype, regular o sin aliasing.

Como mencionaste, la configuración del sistema es amplia. Desafortunadamente, cambiar la configuración del sistema es prácticamente la única forma (documentada) de degradar la calidad de la representación de fuente en un DC si no puede controlar el contenido de LOGFONT.


Este código no es mío. Es no administrado C. Y enganchará cualquier función importada por un archivo dll o exe si conoce su 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); 
} 

Se podría llamar a esto desde el código haciendo algo como (No he comprobado que esto de ninguna manera compila: P):

  1. Declarar un tipo de puntero a la Funciton desea para enganchar:

    typedef FARPROC (WINAPI* PFNCreateFontIndirect)(LOGFONT*); 
    
  2. implementar una función de gancho

    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. gancho de la función en algún momento durante la inicialización

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

Por supuesto, si el módulo que están conectando existe en .NET tierra es muy claro en cuanto a donde la llamada CreateFontIndirect va a proceder de. mscoree.dll? ¿El módulo real que llamas? Buena suerte supongo: P

+0

Esta es la conclusión a la que llegué :(Esperaba que alguien tuviera algo de magia para resolver mi problema. Me pregunto si es posible conectar la llamada a CreateFontIndirect y luego modificar LOGFONT para que no tenga anti-aliasing? –

+0

Ciertamente es posible. Suponiendo que está en proceso con el módulo que necesita ser enganchado, y conoce su manejador HMODULE, que es su dirección base, es fácil "parchear" la Tabla de direcciones de importación para enganchar una llamada de API. –

+0

Disculpa, me perdí tu edición hasta ahora, intentaré esto en los próximos días. –

Cuestiones relacionadas