Entries

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

CGContextを同じ絵で埋め尽くす

タグ: Quartz Mac Objective-C

CGContextの任意の領域を特定の画像で埋め尽くす方法は二つある。

一つは CGContextDrawTiledImage()

Mac OS X 10.5で追加されたこの関数は、指定した画像でクリッピング領域を埋め尽くしてくれる。

const CGPoint starPoints[] = {
	{ 30, 0 }, { 130, 200 }, { 230, 0 }, { 0, 120 }, { 260, 120 }, { 30, 0 }
};

NSString* resourcePath = [[NSBundle mainBundle] resourcePath];
NSString* tileImagePath = [resourcePath stringByAppendingPathComponent:@"64x48.jpg"];
NSBitmapImageRep* tileImage = [NSBitmapImageRep imageRepWithContentsOfFile:tileImagePath];

CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];

CGContextSaveGState(context);
CGContextAddLines(context, starPoints, sizeof(starPoints) / sizeof(*starPoints));
CGContextClip(context);
CGContextDrawTiledImage(context, CGRectMake(0, 0, 64, 48), tileImage.CGImage);
CGContextRestoreGState(context);
DrawTiled1.png

対象はクリッピング領域なので、当然マスクも使える。
この関数の第2引数はタイルの初期位置(多分……)とサイズで、画像はこの矩形にフィットするよう拡大縮小される。

NSString* maskImagePath = [resourcePath stringByAppendingPathComponent:@"mask.png"];
NSBitmapImageRep* maskImage = [NSBitmapImageRep imageRepWithContentsOfFile:maskImagePath];

CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];

CGContextSaveGState(context);
CGContextClipToMask(context, NSRectToCGRect(rect), maskImage.CGImage);
CGContextRotateCTM(context, -0.1);
CGContextDrawTiledImage(context, CGRectMake(20, 20, 48, 36), tileImage.CGImage);
CGContextRestoreGState(context);
DrawTiled2.png

もう一つの方法は CGContextSetFillPattern()
これはADCにも解説がある
この関数は、 CGContextFillPath() 等によって普段は単色で塗りつぶされる領域を、任意のコールバック関数が描いたタイルで埋め尽くすようにする。
なのでこの関数を使うには、タイルを描くコールバック関数(と、必要があれば描画用情報オブジェクトを解放するコールバック関数)を定義しなければならない。

// 描画コールバック
void DrawPattern(void* info, CGContextRef context)
{
	NSBitmapImageRep* image = info;
	CGContextDrawImage(
		context,
		CGRectMake(0, 0, image.pixelsWide, image.pixelsHigh),
		image.CGImage
	);
}

// 解放コールバック
void ReleaseImage(void* info)
{
	[(NSBitmapImageRep*)info release];
}

とりあえず解放用のコールバックも用意したが、今回の例では(tileImageをretainしなければ)必要なかったりする。

static const CGPatternCallbacks callbacks = { 0, &DrawPattern, &ReleaseImage };

CGPatternRef pattern =
	CGPatternCreate(
		[tileImage retain],
		CGRectMake(0, 0, 64, 48),
		CGAffineTransformIdentity,
		64,
		48,
		kCGPatternTilingConstantSpacing,
		true,
		&callbacks
	);

CGContextSaveGState (context);

CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace(context, patternSpace);
CGColorSpaceRelease (patternSpace);

CGFloat alpha = 1;
CGContextSetFillPattern(context, pattern, &alpha);
CGContextAddLines(context, starPoints, sizeof(starPoints) / sizeof(*starPoints));
CGContextFillPath(context);

CGContextRestoreGState(context);

CGPatternRelease(pattern);
FillPattern1.png

CGPatternCreate() の第2引数がよく分からない。とりあえずはタイルのサイズと同じにしておけば良いようだが……

タイルを変形させたり、各タイル間の間隔を変える(これは CGContextDrawTiledImage() には不可能)には第3?5の引数をいじる。

CGPatternRef pattern =
	CGPatternCreate(
		[tileImage retain],
		CGRectMake(0, 0, 64, 48),
		CGAffineTransformMakeRotation(0.1),
		50,
		48,
		kCGPatternTilingConstantSpacing,
		true,
		&callbacks
	);

CGContextSaveGState (context);

CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace (context, patternSpace);
CGColorSpaceRelease (patternSpace);	

CGFloat alpha = 1;
CGContextSetFillPattern(context, pattern, &alpha);
CGContextClipToMask(context, CGContextGetClipBoundingBox(context), maskImage.CGImage);
CGContextAddRect(context, CGContextGetClipBoundingBox(context));
CGContextRotateCTM(context, 3.0);
CGContextFillPath(context);

CGContextRestoreGState(context);

CGPatternRelease(pattern);
FillPattern2.png

CGContextDrawTiledImageが「すべてのタイルを敷き詰めた後でCTMを適用する」のに対し、CGPatternは「各タイルにCGPatternCreateの第3引数を適用し、CTMは適用しない」。
タイル毎に変形させるため、うまく処理しないとタイル間に隙間が発生してしまう。
その辺がどう処理されるかは、CGPatternCreateの第6引数で決まる。

使用画像:
スポンサーサイト

コメント

コメントの投稿

コメントの投稿
管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://idlysphere.blog66.fc2.com/tb.php/160-4fa09d46
この記事にトラックバックする(FC2ブログユーザー)

Appendix

タグ

Blog内検索

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。