Pregunta

Estoy usando una biblioteca de terceros para representar una imagen en un GDI DC y necesito asegurarme de que cualquier texto se represente sin suavizado / suavizado para poder convertir la imagen en una paleta predefinida con colores indexados.

La biblioteca de terceros que estoy usando para la representación 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 han dicho que es poco probable que agreguen la capacidad de desactivar el suavizado en el corto plazo.

El mejor trabajo que he encontrado hasta ahora es llamar a la biblioteca de terceros de esta manera (manejo de errores y verificaciones de configuraciones anteriores omitidas por 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);

Esto obviamente tiene un efecto horrible en el sistema operativo, otras aplicaciones parpadean desde cleartype habilitado a deshabilitado 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 que el proceso de cambios o el subproceso sean específicos en lugar de afectar a todo el sistema operativo, ¡eso sería un gran paso adelante! (Eso me daría la opción de cultivar este renderizado en un proceso separado: los resultados se escriben en el disco después de renderizar de todos modos)

EDITAR: me gustaría agregar que no me importa si la solución es más compleja que solo unas pocas llamadas API. Incluso estaría contento con una solución que involucrara dlls del sistema de enganche si solo fuera un día de trabajo.

EDITAR: Información general La biblioteca de terceros renderiza utilizando una paleta de unos 70 colores. Después de que la imagen (que en realidad es un mosaico de mapa) se procesa en DC, convierto cada píxel de su color de 32 bits a su índice de paleta y almaceno el resultado como una imagen en escala de grises de 8 bpp. 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 ejecutándose en la tarjeta de video. Esto me permite cambiar y desvanecer instantáneamente entre diferentes paletas en lugar de tener que regenerar todas las fichas requeridas. Se tarda entre 10 y 60 segundos en generar y cargar todos los mosaicos para obtener una vista típica del mundo.

EDITAR: Renombrado GraphicsDevice a Graphics La clase GraphicsDevice en la versión anterior de esta pregunta es en realidad System.Drawing.Graphics. Lo renombré (usando GraphicsDevice = ...) porque el código en cuestión está en el espacio de nombres MyCompany.Graphics y el compilador no pudo resolverlo correctamente.

EDITAR: ¡Éxito! Incluso logré 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];
}
¿Fue útil?

Solución 2

Según lo solicitado, he empaquetado el código que escribí para resolver este problema y lo coloqué en un repositorio de github: http://github.com/jystic/patch-iat

Parece mucho código porque tuve que reproducir todas las estructuras de Win32 para que estas cosas funcionen, y en ese momento decidí poner cada una en su propio archivo.

Si quiere ir directamente al meollo del código, está en: ImportAddressTable.cs

Tiene licencia de forma muy gratuita y es para todos los efectos, dominio público, así que siéntase libre de usarlo en cualquier proyecto que desee.

Otros consejos

Lamentablemente no puedes. La capacidad de controlar el suavizado de fuentes se realiza por fuente. La llamada GDI CreateFontIndirect procesa miembros de la estructura LOGFONT para determinar si está permitido usar cleartype, regular o no anti aliasing.

Hay, como notó, configuraciones de todo el sistema. Desafortunadamente, cambiar la configuración de todo el sistema es prácticamente la única forma (documentada) de degradar la calidad de la representación de fuentes en un DC si no puede controlar el contenido de LOGFONT.


Este código no es mío. No está administrado C. Y conectará 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 );
}

Llamaría esto desde su código haciendo algo como (no he comprobado que esto compila de ninguna manera: P):

  1. Declare un tipo de puntero a la función que desea enganchar:

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

    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. Enganche 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á conectando existe en .NET land, no está claro de dónde se originará la llamada CreateFontIndirect . mscoree.dll ? ¿El módulo real al que llamas? Buena suerte, supongo: P

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

El sistema probablemente no suavizará el renderizado de fuentes en imágenes de 1bpp.

¿La clase GraphicsDevice es una clase de terceros?

la forma en que haría 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, intente usar la clase Graphics?)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top