Domanda

Sto utilizzando una libreria di terze parti per eseguire il rendering di un'immagine in un controller di dominio di sola lettura GDI e devo assicurarmi che venga eseguito il rendering di qualsiasi testo senza alcun livellamento / antialiasing in modo da poter convertire l'immagine in una tavolozza predefinita con colori indicizzati.

/ p>

La libreria di terze parti che sto usando per il rendering non supporta questo e rende solo il testo secondo le attuali impostazioni di Windows per il rendering dei caratteri. Hanno anche detto che è improbabile che aggiungano presto la possibilità di disattivare l'antialiasing.

La soluzione migliore che ho trovato finora è quella di chiamare la libreria di terze parti in questo modo (gestione degli errori e controlli delle impostazioni precedenti ommittati per brevità):

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

Questo ovviamente ha un effetto orribile sul sistema operativo, altre applicazioni sfarfallano da cleartype abilitate a disabilitate e viceversa ogni volta che visualizzo l'immagine.

Quindi la domanda è: qualcuno sa come posso modificare le impostazioni di rendering dei caratteri per un controller di dominio specifico?

Anche se potessi solo rendere il processo di modifica o il thread specifico invece di influire sull'intero sistema operativo, sarebbe un grande passo avanti! (Ciò mi darebbe la possibilità di coltivare questo rendering in un processo separato: i risultati vengono comunque scritti su disco dopo il rendering)

MODIFICA: Vorrei aggiungere che non mi dispiace se la soluzione è più complessa di poche chiamate API. Sarei anche felice con una soluzione che prevedeva l'aggancio delle dll di sistema se fosse solo un giorno di lavoro.

MODIFICA: Informazioni di base Il rendering della libreria di terze parti utilizza una tavolozza di circa 70 colori. Dopo che l'immagine (che in realtà è un riquadro mappa) viene renderizzata nel controller di dominio, converto ogni pixel dal suo colore a 32 bit all'indice della tavolozza e memorizzo il risultato come immagine in scala di grigi a 8 bpp. Questo viene caricato sulla scheda video come trama. Durante il rendering, riapplico la palette (memorizzata anche come trama) con un pixel shader in esecuzione sulla scheda video. Questo mi permette di passare e sfumare istantaneamente tra le diverse tavolozze invece di dover rigenerare tutte le tessere richieste. Sono necessari tra 10 e 60 secondi per generare e caricare tutte le tessere per una visione tipica del mondo.

MODIFICA: Rinominato GraphicsDevice in Graphics La classe GraphicsDevice nella versione precedente di questa domanda è in realtà System.Drawing.Graphics. L'avevo rinominato (usando GraphicsDevice = ...) perché il codice in questione si trova nello spazio dei nomi MyCompany.Graphics e il compilatore non è stato in grado di risolverlo correttamente.

EDIT: successo! Sono anche riuscito a trasferire la funzione PatchIat in basso su C # con l'aiuto di Marshal.GetFunctionPointerForDelegate . Il team di interoperabilità .NET ha fatto davvero un lavoro fantastico! Ora sto usando la seguente sintassi, dove Patch è un metodo di estensione su 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];
}
È stato utile?

Soluzione 2

Come richiesto, ho impacchettato il codice che ho scritto per risolvere questo problema e l'ho inserito in un repository github: http://github.com/jystic/patch-iat

Sembra un sacco di codice perché ho dovuto riprodurre tutte le strutture Win32 per far funzionare queste cose, e al momento ho scelto di metterle ognuna nel suo file.

Se vuoi andare direttamente alla carne del codice in cui si trova: ImportAddressTable.cs

È concesso in licenza molto liberamente ed è a tutti gli effetti di dominio pubblico, quindi sentiti libero di usarlo in qualsiasi progetto che ti piace.

Altri suggerimenti

Purtroppo non puoi. La capacità di controllare l'anti aliasing dei caratteri viene eseguita per carattere. La chiamata GDI CreateFontIndirect elabora i membri della struttura LOGFONT per determinare se gli è consentito utilizzare cleartype, regolare o no anti aliasing.

Ci sono, come hai notato, impostazioni a livello di sistema. Sfortunatamente, la modifica delle impostazioni a livello di sistema è praticamente l'unico modo (documentato) per ridurre la qualità del rendering dei caratteri su un controller di dominio se non è possibile controllare il contenuto di LOGFONT.


Questo codice non è mio. È unmanaged C. E aggancerà qualsiasi funzione importata da un file dll o exe se conosci il suo 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 );
}

Lo chiameresti dal tuo codice facendo qualcosa del tipo (Non ho verificato che questo compili in alcun modo: P):

  1. Dichiara un tipo di puntatore alla funzione che vuoi agganciare:

    typedef FARPROC (WINAPI* PFNCreateFontIndirect)(LOGFONT*);
    
  2. Implementa una funzione hook

    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. Aggancia la funzione qualche volta durante l'inizializzazione

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

Ovviamente, se il modulo che stai collegando esiste in .NET non è molto chiaro da dove provenga la chiamata CreateFontIndirect . mscoree.dll ? Il modulo vero e proprio che chiami? Buona fortuna immagino: P

Hai bisogno di più colori rispetto al bianco e nero sui tuoi caratteri? In caso contrario, potresti rendere il tuo oggetto bitmap un'immagine a 1 bit per pixel ( Format1bppIndexed ?).

Probabilmente il sistema non uniforma il rendering dei caratteri sulle immagini 1bpp.

la classe GraphicsDevice è una classe di terze parti?

il modo in cui lo farei è:

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

o nel tuo caso:

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

se la classe GraphicsDevice eredita la classe Graphics (altrimenti prova a utilizzare la classe Graphics?)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top