
先日のエントリではちまちまと自力でDLLをロードしていましたが、実はWebKit Support Libraryとして入手できたようです。なので、これをどこかに置いて、*.hと*.libを参照すればAPIが利用可能になります。
SDKに含まれるヘッダは、CoreGraphicsのAPIを一通り宣言していますが、CoreGraphics全体の初期化を行うAPIが含まれていないので、これだけは自力で宣言する必要があります。C++の場合は以下のように宣言してください。
extern "C" BOOL InitializeCoreGraphics();
これをプログラムの開始時にコールします。
InitializeCoreGraphics();
この初期化を行わないとフォント関連のAPIのコールが失敗します。(逆に言うと、初期化しなくてもいくつかの機能は正常に動いてしまうのでトラブルの原因になりやすい)
CGContextは、すべての描画処理の対象となるオブジェクトで、GDIにおけるDCのようなものです。CoreGraphicsのCGContextは、描画内容を保持するためのメモリ領域と関連付けて作成されます。これは、ごく普通の(例えば、mallocで確保された)メモリ領域です。
これを画面に表示するためには、このメモリ領域をGDIのビットマップとも関連付ける必要があります。このための一般的な方法は、CreateDIBSectionを使う方法です。
unsigned char* pDIB; HBITMAP mHDIBSec; /*w is width, h is height*/ mBpp = 4; unsigned int bytesPerRow = w * mBpp; // DIB BITMAPINFO bi; ZeroMemory(&bi, sizeof(bi)); bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biWidth = w; bi.bmiHeader.biHeight = h; bi.bmiHeader.biCompression = BI_RGB; mHDIBSec = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, (void **)&pDIB, NULL, 0);
このコードにより、pDIBにGDIのビットマップと関連付けられたメモリ領域が得られました。次に、このメモリ領域を利用してCGContextを作成します。
CGContextRef context; const size_t bitsPerComponent = 8; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); context = CGBitmapContextCreate( pDIB,w ,h, bitsPerComponent, bytesPerRow, colorSpace, (CGImageAlphaInfo)(kCGBitmapByteOrder32Little|kCGImageAlphaNoneSkipFirst)); CGColorSpaceRelease(colorSpace);
Windows版で特別な点は、kCGBitmapByteOrder32Littleというフラグです。このフラグを指定することにより、GDIのビットマップのバイトオーダーに合わせて描画処理が行われます(もちろん、これを行わないと色が変になります)
CGContextを作成したら、Bottom-upのGDIビットマップに合わせて、以下のようにしてCGContextの座標系を上下反転しておきます。
CGContextTranslateCTM(context, 0, h); CGContextScaleCTM(context, 1, -1);
尚、CGContextの座標系では左下が原点なので注意してください。
実際の描画処理は、単純なAPIの組み合わせなので特に難しくないでしょう。
CGContextSetRGBFillColor(context, 1.0f, 0.2f, 0.1f, 1.0f); CGRect rc; rc.origin.x = 0; rc.origin.y = 0; rc.size.width = 100; rc.size.height = 100; CGContextFillRect(context, rc);
このコードは、赤い矩形を描画します。
文字列を描画するためには、まずCGFontオブジェクトを作成します。CGFontCreateWithFontNameというAPIでCoreGraphicsのCGFontオブジェクトを作成し、ここでGDIのFontオブジェクトも同時に作成しておきます。
LPCTSTR face = _T("Arial");
DWORD pitchAndFamily = DEFAULT_PITCH|FF_SWISS;
int height = 20;
int weight = FW_NORMAL;
HFONT hfont = NULL;
CGFontRef cgfont = NULL;
hfont = CreateFont(height, 0, 0, 0, weight,
FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, PROOF_QUALITY, pitchAndFamily, face);
CFStringRef cfName = CFStringCreateWithCharacters(NULL, (const UniChar*)face, _tcslen(face));
cgfont = CGFontCreateWithFontName(cfName);
CFRelease(cfName);
続いて描画処理です。文字列を指定するだけで描画してくれるAPIがありますが、これはうまく動きません。よって、文字列中の各文字のグリフインデックスを自力で取得する必要があります。
CGGlyph buf[6];
int len = 6;
const TCHAR* str = _T("Hello!");
HDC hdc = GetDC(0);
SaveDC(hdc);
SelectObject(hdc, hfont);
GetGlyphIndices(hdc, str, len, buf, GGI_MARK_NONEXISTING_GLYPHS);
RestoreDC(hdc, -1);
ReleaseDC(0, hdc);
for (unsigned i = 0; i < len; i++) {
if (buf[i] == 0xFFFFFFFF) {
return false;
}
}
GetGlyphIndicesは、WindowsのAPIです。このAPIを使用するためには、windows.hをincludeする前に #define _WIN32_WINNT 0x0500 と宣言してください(当然、Win9xでは使えません)
ここまでで準備は終わりです。以下のようなコードで文字列の描画を行えます。
CGContextSetFont(context, cgfont); CGContextSetFontSize(context, 50); CGContextSetTextPosition(context, 100, 200); CGContextShowGlyphs(context, buf, 15);
In my previous entry, I loaded API entries manually. However, Apple have distributed SDK as WebKit Support Library. So, you can use API using *.h and *.lib files.
Header files in the SDK seem to contain a complete set of API declarations. However, the API for initializing entire CoreGraphics is not declared. Therefore you have to declare it. In C++, like this:
extern "C" BOOL InitializeCoreGraphics();
And call it when your app starts up.
InitializeCoreGraphics();
APIs related to fonts will fail if you forgot to call InitializeCoreGraphics. In other words, some features work correctly without initialization. It's confusing behavior.
CGContext is a target object for drawing functions. It's similar to DC in GDI. CGContext must be created associating with a buffer. This buffer is general, not special one ( i.e. allocated by malloc() )
You have to associate this buffer with a GDI bitmap to display the content of this buffer. A generic way to do it is to use CreateDIBSection API.
unsigned char* pDIB; HBITMAP mHDIBSec; /*w is width, h is height*/ mBpp = 4; unsigned int bytesPerRow = w * mBpp; // DIB BITMAPINFO bi; ZeroMemory(&bi, sizeof(bi)); bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biWidth = w; bi.bmiHeader.biHeight = h; bi.bmiHeader.biCompression = BI_RGB; mHDIBSec = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, (void **)&pDIB, NULL, 0);
The code above assigns a pointer to the buffer associated with GDI bitmap to pDIB. Next, create new CGContext using pDIB.
CGContextRef context; const size_t bitsPerComponent = 8; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); context = CGBitmapContextCreate( pDIB,w ,h, bitsPerComponent, bytesPerRow, colorSpace, (CGImageAlphaInfo)(kCGBitmapByteOrder32Little|kCGImageAlphaNoneSkipFirst)); CGColorSpaceRelease(colorSpace);
Note that it's a special thing to use kCGBitmapByteOrder32Little flag. This flag makes CoreGraphics draw in corresponding byte-order of GDI's one, BGR order ( Of course, you'll get incorrect colors without this flag )
Next, invert y-axis to match bottom-up bitmap of GDI.
CGContextTranslateCTM(context, 0, h); CGContextScaleCTM(context, 1, -1);
Note that the origin of the coordinate system is left-bottom in CoreGraphics.
Drawing is not difficult. Just use simple apis like this:
CGContextSetRGBFillColor(context, 1.0f, 0.2f, 0.1f, 1.0f); CGRect rc; rc.origin.x = 0; rc.origin.y = 0; rc.size.width = 100; rc.size.height = 100; CGContextFillRect(context, rc);
This code draws a red rectangle.
At first, you have to create a CGFont object to draw text. Create new CGFont object using CGFontCreateWithFontName. And then, GDI Font object is also needed, so create it.
LPCTSTR face = _T("Arial");
DWORD pitchAndFamily = DEFAULT_PITCH|FF_SWISS;
int height = 20;
int weight = FW_NORMAL;
HFONT hfont = NULL;
CGFontRef cgfont = NULL;
hfont = CreateFont(height, 0, 0, 0, weight,
FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, PROOF_QUALITY, pitchAndFamily, face);
CFStringRef cfName = CFStringCreateWithCharacters(NULL, (const UniChar*)face, _tcslen(face));
cgfont = CGFontCreateWithFontName(cfName);
CFRelease(cfName);
CoreGraphics has APIs that draw text with just specifying a string. But they don't work. So you have to make glyph indecies of characters in the string.
GetGlyphIndices is a Windows API. #define _WIN32_WINNT 0x0500 is required before including windows.h to use this API. This API is not supported in Win9x.
CGGlyph buf[6];
int len = 6;
const TCHAR* str = _T("Hello!");
HDC hdc = GetDC(0);
SaveDC(hdc);
SelectObject(hdc, hfont);
GetGlyphIndices(hdc, str, len, buf, GGI_MARK_NONEXISTING_GLYPHS);
RestoreDC(hdc, -1);
ReleaseDC(0, hdc);
for (unsigned i = 0; i < len; i++) {
if (buf[i] == 0xFFFFFFFF) {
return false;
}
}
Now you are ready. Write a code like below to render.
CGContextSetFont(context, cgfont); CGContextSetFontSize(context, 50); CGContextSetTextPosition(context, 100, 200); CGContextShowGlyphs(context, buf, 15);