2011年4月28日木曜日

イメージを描画する(1)

たまにはイメージをバイト列でごりごり扱いたい、写真家にもそんな気分のときってあると思います。

The NSBitmapImageRep class renders an image from bitmap data. Bitmap data formats supported include GIF, JPEG, TIFF, PNG, and various permutations of raw bitmap data.

というわけで、そんな僕のために Apple は NSBitmapImageRep を用意してくれていました。生のビットマップ・データをあつかって1ピクセルずつ描画しようというわけです。ふつうはこんな方法でイメージを描画いたしません(描画にものすごく時間がかかります)のであしからず。なのでお急ぎの方はご遠慮くださいませ。

手順は
  1. NSBitmapImageRep からビットマップ・データを抽出。
  2. イメージ・データが持っている情報をもとに1ピクセルのカラーを作成。
  3. 作成したカラーを設定して View へ1ピクセル描画する。
  4. 2と3を繰り返す。
ウィンドウにカスタム・ビューを設定してあります。AppDelegate からオープンパネルを開き、指定したファイルから NSData のインスタンスを作ます。それを setBitmapImageRepWithData: でカスタムビューに送り、それが NSBitmapImageRep で扱うことができる場合に NSBitmapImageRep のインスタンスを生成するようにしてあります。

#import "CustomView.h"

NSBitmapImageRep *bitmapImageRep;

@implementation CustomView

- (id)initWithFrame:(NSRect)frame {
 
    self = [super initWithFrame:frame];
    if (self) {
        bitmapImageRep = nil;
    }
 
    return self;
}

- (void) dealloc
{ 
 [bitmapImageRep release];
 [super dealloc];
}

- (void)drawRect:(NSRect)dirtyRect
{
    [self drawImage];
}

- (void)drawImage
{
 
 if (!bitmapImageRep)
  return;
 
 //手順1
 unsigned char *bitmapData = [bitmapImageRep bitmapData];
// 手順2
 NSInteger bpr = [bitmapImageRep bytesPerRow];
 NSInteger bppPbps = [bitmapImageRep bitsPerPixel] / [bitmapImageRep bitsPerSample];
 
 NSUInteger pixelsWide, pixelsHigh, indexW, indexH, indexC;
 pixelsWide = [bitmapImageRep pixelsWide];
 pixelsHigh = [bitmapImageRep pixelsHigh];

 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
 [self lockFocus];
 
 for (indexW = 0; indexW < pixelsWide; indexW++ ) {
  
  for (indexH = 0; indexH < pixelsHigh; indexH++) {
   
   CGFloat *components = calloc(bppPbps, sizeof(CGFloat));
   
   for (indexC = 0; indexC < bppPbps; indexC++) {
    
    *(components + indexC) = (*(bitmapData + (indexW * bppPbps) + (indexH * bpr) + indexC)) / 255.0;
    
   }   
   [[NSColor colorWithColorSpace:[bitmapImageRep colorSpace]
          components:components
         count:bppPbps] set];
   // 手順3
   NSRectFill(NSMakeRect(indexW, (pixelsHigh - indexH), 1, 1));
   
   free(components);
  }
  
 }
 
 [self unlockFocus];
 [pool release];
 
}

- (BOOL)setBitmapImageRepWithData:(NSData *)imageData
{
 if (bitmapImageRep) {
  [bitmapImageRep release];
  bitmapImageRep = nil;
 }
 
 NSString *className = [[NSImageRep imageRepClassForData:imageData] className];
 
 if ([className isEqualToString:@"NSBitmapImageRep"] == YES) {
  
  bitmapImageRep = [[NSBitmapImageRep imageRepWithData:imageData] retain];
  
  [self drawImage];
  return YES;
  
 }
 
 return NO;
 
}
@end


手順1でビットマップ・データのポインタを取得しています。手順2のループのコードから分かるように、データが1次元でのみ構成されていると仮定してます。データが(例えば)カラー・プレーンで構成されている場合は isPlanar の戻り値をチェックして、それに応じて読み込む必要があります。ここはとりあえず決め打ちします。

手順2で bppPbps を計算するのはイメージ・データによっては適当にパディングされ、必ずしも

bitsPerPixel = bitsPerSample * samplesPerPixel

にならないためです。
ループ中の

*(components + indexC) = (*(bitmapData + (indexW * bppPbps) + (indexH * bpr) + indexC)) / 255.0;

は bitmapData からカラー・コンポーネントを取得しています。コンポーネントのフォーマットが整数で8ビットであることを仮定しています。ここも bitmapFormat の戻り値をチェックすると整数か小数か確認できますが決め打っています。NSColor が0〜1でカラー情報をもつので 255.0(8ビットを仮定しない場合はpow(2,[bitmapImageRep bitsPerSample]) - 1)で割ります。colorWithColorSpace:〜でbitmapImageRep と同じカラースペースを指定してやります。

手順3は設定した色を1ピクセルで View に塗りつぶします。 View の座標系が左下が原点となっているので(pixelHeight - indexH)。単に indexH としたい場合は -(BOOL)isFlipped を実装して YES を返すようにすれば左上が原点になります。


NSBitmapImageRep を使って View に普通に描画するには、View にフォーカスをロックしてdrawInRect:(NSRect)rect を使い

NSRect rect = NSMakeRect( 0.0, 0.0, pixelsWide, pixelsHigh);
[bitmapImageRep drawInRect:rect];

としますと、さらっといきます。

余談ですがこの NSBitmapImageRep は Cocoa で最も長いイニシャライザをもってます。

initWithBitmapDataPlanes:(unsigned char **)planes
                              pixelsWide:(NSInteger)width
                               pixelsHigh:(NSInteger)height
                        bitsPerSample:(NSInteger)bps
                     samplesPerPixel:(NSInteger)spp
                                  hasAlpha:(BOOL)alpha
                                     isPlanar:(BOOl)isPlanar
                    colorSpaceName:(NSString *)colorSpaceName
                         bitmapFormat:(NSBitmapFormat)bitmapFormat
                            bytesPerRow:(NSInteger)rowBytes
                               bitsPerPixel:(NSInteger)pixelBits

148文字です。ツイッターでつぶやかれる方ご注意ください。そんな NSBitmapImageRep ラヴ。

0 件のコメント:

コメントを投稿