特定のGDIデバイスコンテキストのアンチエイリアスを無効にする
質問
サードパーティのライブラリを使用して画像をGDI DCにレンダリングしていますが、画像をインデックス付きカラーの事前定義パレットに変換できるように、スムージング/アンチエイリアスなしでテキストをレンダリングする必要があります
レンダリングに使用しているサードパーティライブラリはこれをサポートせず、フォントレンダリングの現在の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をフックするだけで1日しかかからなかった場合でも、ソリューションに満足できます。
編集:背景情報 サードパーティライブラリは、約70色のパレットを使用してレンダリングします。画像(実際にはマップタイル)がDCにレンダリングされた後、各ピクセルを32ビットカラーからパレットインデックスに戻し、結果を8bppグレースケール画像として保存します。これは、テクスチャとしてビデオカードにアップロードされます。レンダリング中に、ビデオカード上で実行されるピクセルシェーダーを使用して、パレット(テクスチャとしても保存)を再適用します。これにより、必要なすべてのタイルを再生成する必要なく、さまざまなパレットを瞬時に切り替えてフェードできます。世界の典型的なビューのすべてのタイルを生成してアップロードするには、10〜60秒かかります。
編集:GraphicsDeviceの名前をGraphicsに変更 この質問の前のバージョンのGraphicsDeviceクラスは、実際にはSystem.Drawing.Graphicsです。問題のコードが名前空間MyCompany.Graphicsにあり、コンパイラーが適切に解決できなかったため、(GraphicsDevice = ...を使用して)名前を変更しました。
編集:成功!
Marshal.GetFunctionPointerForDelegate
を使用して、以下の PatchIat
関数をC#に移植することさえできました。 .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リポジトリに配置しました。 http://github.com/jystic/patch-iat
このようなものが機能するためにはすべてのWin32構造を再現しなければならなかったので、多くのコードのように見えます。
コードの中身に直接アクセスしたい場合: ImportAddressTable.cs
非常に自由にライセンスされており、すべての意図と目的、パブリックドメインのため、好きなプロジェクトで自由に使用してください。
他のヒント
残念ながらできません。フォントのアンチエイリアシングを制御する機能は、フォントごとに実行されます。 GDI呼び出しはCreateFontIndirectを処理してLOGFONT構造体のメンバーを処理し、cleartype、通常のアンチエイリアス、またはアンチエイリアスの使用を許可しているかどうかを判断します。
お気付きのとおり、システム全体の設定があります。残念ながら、LOGFONTの内容を制御できない場合に、システム全体の設定を変更することは、DCでのフォントレンダリングの品質を低下させる唯一の(文書化された)方法です。
このコードは私のものではありません。アンマネージドCです。また、HMODULEがわかっている場合、dllまたはexeファイルによってインポートされた関数をフックします。
#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
フォントに白黒よりも多くの色が必要ですか? そうでない場合は、 bitmap オブジェクトをピクセルあたり1ビットの画像にすることができます( Format1bppIndexed ?)。
システムは、おそらく1bpp画像のフォントレンダリングをスムーズにしません。
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クラスを使用してみてください)