強火で進め

このブログではプログラム関連の記事を中心に書いてます。

画像が綺麗にマスクされないときの対処法

画像のマスク処理についてStack Overflowで興味深い質問が上がっていたので紹介します。

Any idea why this image masking code does not work? - Stack Overflow
http://stackoverflow.com/questions/1133248/any-idea-why-this-image-masking-code-does-not-work

「画像にマスク処理をしたが綺麗にマスク出来ない、何か良いアイディア無い?」という質問。

自分でもこのコードで試してみました。画像ファイルとしては以下のものを準備しました。

背景にこの画像(bg.png)

ベースの画像に24bitと32bitのこちらの画像

24bitの画像(coloredImagePNG24.png)

32bitの画像(coloredImagePNG32.png)

マスク画像として8bit(256色)グレースケール画像(theMaskPNG8_Grayscale_NoAlpha.png)

メインのプログラムは投稿されたものに準拠して以下の様にしました。

	// 元画像が24bitのとき
	UIImage *image = [UIImage imageNamed:@"coloredImagePNG24.png"];
	UIImage *maskImage = [UIImage imageNamed:@"theMaskPNG8_Grayscale_NoAlpha.png"];
	UIImage *maskedImage = [MyGraphicUtils maskImage:image withMask:maskImage];
	UIImageView *testImageView = [[UIImageView alloc] initWithImage:maskedImage];
	testImageView.backgroundColor = [UIColor clearColor];
	testImageView.opaque = NO;
	[self.view addSubview:testImageView];
	[testImageView release];

	// 元画像が32bitのとき
	UIImage *image2 = [UIImage imageNamed:@"coloredImagePNG32.png"];
	UIImage *maskImage2 = [UIImage imageNamed:@"theMaskPNG8_Grayscale_NoAlpha.png"];
	UIImage *maskedImage2 = [MyGraphicUtils maskImage:image2 withMask:maskImage2];
	UIImageView *testImageView2 = [[UIImageView alloc] initWithImage:maskedImage2];
	testImageView2.backgroundColor = [UIColor clearColor];
	testImageView2.opaque = NO;
	testImageView2.frame = CGRectMake(100, 0, 100, 100);
	[self.view addSubview:testImageView2];
	[testImageView2 release];

プロジェクト全体をDLしたい場合はこちら

そして実際の結果がこちらです。左側が24bit(アルファ無し)、右側が32bit(アルファ有り)です。確かにどちらの場合も綺麗にマスクされて無い様です。

回答者の一人が提案されている方法が自身のブログでまとめて有ります。
問題となるのは CGImage でこれがとてもさまざまなフォーマットを受け入れるのが問題となる様です。

bunnyhero dev - Loading an image mask from a file
http://www.bunnyhero.org/2010/07/23/loading-an-image-mask-from-a-file/

解決方法としては以下になります。

  • マスクとして一番ベストな CGImage(CGImageRef) のフォーマットを使用
  • 具体的にはベストな Context を作成し、そこにファイルから読み込んだマスクデータを描画し、ベストなフォーマットのマスクデータを作成

実際のプログラムはこちら。

CGImageRef createMaskWithImage(CGImageRef image)
{
    int maskWidth               = CGImageGetWidth(image);
    int maskHeight              = CGImageGetHeight(image);
    //  round bytesPerRow to the nearest 16 bytes, for performance's sake
    int bytesPerRow             = (maskWidth + 15) & 0xfffffff0;
    int bufferSize              = bytesPerRow * maskHeight;

    //  allocate memory for the bits 
    CFMutableDataRef dataBuffer = CFDataCreateMutable(kCFAllocatorDefault, 0);
    CFDataSetLength(dataBuffer, bufferSize);

    //  the data will be 8 bits per pixel, no alpha
    CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceGray();
    CGContextRef ctx            = CGBitmapContextCreate(CFDataGetMutableBytePtr(dataBuffer),
                                                        maskWidth, maskHeight,
                                                        8, bytesPerRow, colourSpace, kCGImageAlphaNone);
    //  drawing into this context will draw into the dataBuffer.
    CGContextDrawImage(ctx, CGRectMake(0, 0, maskWidth, maskHeight), image);
    CGContextRelease(ctx);

    //  now make a mask from the data.
    CGDataProviderRef dataProvider  = CGDataProviderCreateWithCFData(dataBuffer);
    CGImageRef mask                 = CGImageMaskCreate(maskWidth, maskHeight, 8, 8, bytesPerRow,
                                                        dataProvider, NULL, FALSE);

    CGDataProviderRelease(dataProvider);
    CGColorSpaceRelease(colourSpace);
    CFRelease(dataBuffer);

    return mask;
}

プロジェクト全体をDLした場合はこちら

こちらのプログラムを使うとこの様に綺麗にマスクがかかります。 CGImageMaskCreate() を使うときは注意が必要ですね。

なお、ベースとなる画像が24bitのときはアルファ要素が入ってないので透過されないみたいです。

折角、マスクは別ファイルとして持つんだから24bitのファイルで透過もしたいところですがこれって32bitの CGImage をプログラム内で別途生成してそこにベースイメージをマスク処理して描画するしか無いのかなぁ?お手軽な方法をご存知の方はコメントお願いします!!