GDI+でEMF+をアンチエイリアスなしでレンダリングする
EMF+をレンダリングしようとすると、GDIだけではどうにもならないものの、かといって、GDI+に全てお任せにすると、アンチエイリアスがデフォルトでかかってしまう。一応、GDI+には、アンチエイリアス系をOFFにするメソッド、Graphics::SetTextRenderingHint、Graphics::SetSmoothingMode があるのだが、EMF+のレンダリング前にこのメソッドを
g.SetSmoothingMode(Gdiplus::SmoothingModeNone); g.SetTextRenderingHint(Gdiplus::TextRenderingHintSingleBitPerPixelGridFit);
のように呼び出しておいても全く効果がない。どうやら、ちゃんとEMF+のレコードを解析しないといけないらしい。
GDI+でEMR構造体を使う
Graphics::EnumerateMetafileで指定するコールバック関数、EnumerateMetafileProcを見てみると、EnumEnhMetaFileのコールバック関数EnhMetaFileProcとの比較はそんなに難しくないことがわかる。
結局、recordTypeやflagsが増えたが、これはENHMETARECORDの一部のデータが表に出てきただけ。その代わりに、pStrの先頭から、EMR構造体が無くなった感じ。正直、それだと、GDIの定義する構造体に直接キャストができなくて困るのだが、デバッガなどでメモリの内容を直接のぞいた感じでは、実は、このアドレスから、-8すれば、EMR構造体になっているようだ。つまり、
EMR& emr = *(EMR*)(pStr - 8);
という処理をしても問題ないということ。
EMF+で文字をレンダリングしているのは?
EMF+のレコードをデバッガで追っていて、面白いことを発見した。GDI+では、文字の描画は、Graphics::DrawStringメソッドを使うのだが、実は、その内部では、ExtTextOutが使われているのだ。ということは、逆に言えば、フォントのアンチエイリアスは、CreateFont/CreateFontIndirectしているところで、LOGFONT構造体のlfQualityをいじって、強制的にアンチエイリアスをOFFにしてしまえば良いわけだ。そして、どうやら、EmfRecordTypeExtCreateFontIndirect (EMR_EXTCREATEFONTINDIRECTW/構造体だと、EMREXTCREATEFONTINDIRECTW)を処理すればこれができるらしい。
ということで書いてみたコードは次のような感じ。
Graphics g(...); Metafile mf(...); Rect rc(...); // レンダリングしたいサイズで初期化 // 事前にアンチエイリアスをOFFにしておく g.SetSmoothingMode(Gdiplus::SmoothingModeNone); g.SetTextRenderingHint(Gdiplus::TextRenderingHintSingleBitPerPixelGridFit); // 描画 g.EnumerateMetafile(mf, rc, emfProc, &mf, NULL);
そして、本体から呼び出されるコールバックは次のような感じ。
static BOOL CALLBACK emfProc( Gdiplus::EmfPlusRecordType recordType, unsigned int flags, unsigned int dataSize, const unsigned char* pStr, void* callbackData) { BYTE* buf = NULL; Metafile* mf = (Metafile*)callbackData; if(recordType == Gdiplus::EmfPlusRecordTypeSetAntiAliasMode) { // 無視する return TRUE; } else if(recordType == EmfRecordTypeExtCreateFontIndirect) { // pStrを直接書き換えるのはリスクが高いので、 // コピーしてから書き換え、それをPlayRecordに渡す。 buf = new BYTE[dataSize + 8]; pStr = buf; CopyMemory(buf, pStr - 8, dataSize + 8); EMREXTCREATEFONTINDIRECTW& eecfi = *(EMREXTCREATEFONTINDIRECTW*)buf; // アンチエイリアスを強制的にOFFにする eecfi.elfw.elfLogFont.lfQuality = NONANTIALIASED_QUALITY; } BOOL ret = mf->PlayRecord(recordType, flags, dataSize, pStr); if(buf) delete[] buf; // バッファ解放 return ret; }
あとがき
僕の手元では、このコードは、Office 2007のExtending the Office (2007) Fixed-Format Export Featureが出力するEMFを解析する部分で使っているが、このコードは面白いほどちゃんと動作する。実は、EMF+のレコードとしては、他にもフォントレンダリング関連のものがあり、このコードでは全く考慮していないにもかかわらずだ。