2011年4月30日土曜日

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

あしびきの山鳥の尾のしだり尾の長々しメソッドをひとりかもせむ、写真家にもそんな気分のときってあると思います。というわけで前回ご紹介した長々しメソッドを使ってみたいと思います。

If a coverage (alpha) plane exists, a bitmap’s color components are premultiplied with it. If you modify the contents of the bitmap, you are therefore responsible for premultiplying the data. For this reason, though, if you want to manipulate the actual data, an NSBitmapImageRep object is not recommended for storage. If you need to work with data that is not premultiplied, you should use Quartz, specifically CGImageCreate with kCGImageAlphaLast.
NSBitmapImageRep Class Reference "Alpha PreMultiplication"

アルファ・プレーンがある場合、ビットマップの色成分は事前にかけ合わされます。なのでビットマップの色成分を変更する場合はデータを手前で事前にかけ合わせろと。だから NSBitmapImageRep はビットマップ・データの操作を前提としたストレージにはむいていない(手間だもんね)。そうゆう場合には Quartz をつかってね。ということでしょうか、Quartz、Cでばりばり。そのうちやりましょう。



プレーン(plane)って?
分からなかったのでメモしときます。ビットマップ画像を構成成分の重ね合わせとしてみることができます。このとき重ね合わされている成分それぞれををプレーンと呼びます。構成成分を色成分でみたとき、カラー・プレーンと呼ばれます。正確な表現ではありませんが、イメージとしては Photoshop の RGB(CMYK)の各チャンネルで表現されているもの、みたいな感じです。その他にビットの深さを基準にみたビット・プレーンもあります。ビット・プレーンを利用した電子透かしの原理など、知りませんでしたので目から鱗が落ちました。


では NSBitmapImageRep はどんなときに使うのでしょうか。リファレンスのメソッド群を眺めますとプログラムからビットマップ・データを作成するというのが一番の使い方のような気がしますが。だれか教えてくださいませ。


下記の説明は画像工学が専門でない僕の理解に基づいているので間違っている可能性があります。その場合ご指摘いただけたら幸いに存じます。?と思ったら NSBitmapImageRep Class Reference を直接ご参照ください。

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
(unsigned char **)planes
  • イメージ・データのバッファを指定。プレーン構成(plane configuration)の場合それぞれのバッファに1つのプレーンがあり、ひとつのプレーンには1つの成分があり、
  •  
  • R1R2R3R4R5...G1G2G3G4G5...B1B2B3B4B5...(A1A2A3A4A5...)
  •  
  • の順番になります。
  • アルファ・プレーンが存在する場合は例によって、事前にかけ合わせろと。isPlanar が NO のときはメッシュ構成(meshed configuration)
  •  
  • R1G1B1(A1)R2G2B2(A2)R3G3B3(A3)...
  •  
  • で、先頭のバッファのみが読込まれます。
  •  
  • NULL(または NULL の配列)を設定するとオブジェクトが適当なメモリを確保してくれます。確保されたメモリはオブジェクトに所有され、オブジェクトを解放すれば同時に解放されます。NULL でない場合はオブジェクトはただこのバッファを参照するだけで、変更不可なものとみなします。オブジェクトを解放してもこのバッファは解放されません。この場合は別に自分で解放する必要があります。
(NSInteger)width
  • 作成したい画像の横幅ピクセル
(NSInteger)height
  • 作成したい画像の高さピクセル
(NSInteger)bps
  • 1ピクセル中の1つの成分が何ビットで構成されているかの指定です。サンプルあたりのビット数。すべての成分でサンプルごとに同じビットを持っていると仮定されます。例えば256階調ならば8。
  •  
  • 指定できるビット:1, 2, 4, 8, 12, 16 のどれか。
(NSInteger) spp
  • データの成分の数またはピクセルあたりのサンプル数。意味を持つのは1〜5まで。アルファ値をもったCMYKならば5。RGBならば3。
(BOOL)alpha
  • YES:アルファ値をもっている
  • NO:もっていない
(BOOl)isPlanar
  • YES:プレーン構成
  • NO:メッシュ構成
(NSString *)colorSpaceName
  • 次のうちどれか。bps で 12 を指定している場合はモノクロのカラースペースは指定できません。
  •  
  • NSCalibrateWhiteColorSpace
  • NSCalibrateBlackColorSpace
  • NSCalibrateRGBColorSpace
  • NSDeviceWhiteColorSpace
  • NSDeviceBlackColorSpace
  • NSDeviceRGBColorSpace
  • NSDeviceCMYKColorSpace
  • NSNamedColorSpace
  • NSCustomColorSpace
(NSBitmapFormat)bitmapFormat
  • 次の3つ。Cのビット演算でORして使ってね、とのこと。
  •  
  • NSAlphaFirstBitmapFormat = 1 << 0
  • NSNonPremultipliedBitmapFormat = 1 << 1
  • NSFloatingPointSamplesBitmapFormat = 1 << 2

  • NSAlphaFirstBitmapFormat が0ならば R1G1B1A1... とアルファ値が最後に置かれます。
  • NSNonPremultipliedBitmapFormat が0ならばアルファ値は事前にかけ合わされています。
  • NSFloatingPointSamplesBitmapFormat が0ならば整数です。
(NSInteger)rowBytes
  • width が実際に何バイトで構成されているかの指定。width を使って正確に計算した値でもよいですが、コンピュータはワードごと(16ビットとか32ビットなど CPU に依存します。)の方が読み出しが速いので、画像にしない部分に余分なデータをもたせることがあります。バイト数をあらかじめオブジェクトに教えてやるのはこのためです。0を指定してやりますと、パフォーマンスが最適になるような値を生成してくれます。
(NSInteger)pixelBits
  • 1ピクセルが実際に何ビットで構成されているかの指定。通常はプレーン構成ならば bps 、メッシュ構成ならば "bps * spp" に等しくなります。これもまたパフォーマンス上の理由でそうしない場合があります。例えば RGB で8bps、3pps のメッシュ構成でビット列が構成されていると仮定しますと通常、
  •  
  • R1G1B1R2G2B2R3G3B3...
  •  
  • となっていますが、32ビットごとの読み出しが速いこと(システムに依存します)を考慮して、
  •  
  • R1G1B1Empty1R2G2B2Empty2R3G3B3Empty3...
  •  
  • とする場合があります。この場合は24ビットではなく32ビットになります。
  • 0を指定してやると余分なデータなしで bps と spp の値でピクセルあたりのビット数を解釈します。


前置きがものすごくながくなりましたが、ここからが本番です。
手順は、
  1. ビットマップ・イメージ(99 * 99, 8ビット RGB )を作成する。
  2. 設定した通りにできているかビットマップ・データに何か色を設定する。
  3. ちゃんと設定されたたか View に描画してみてる。
  4. TIFF で書き出してみる。
です。ウィンドウにカスタム・ビューを貼付けてあります。

#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)drawImage
{
 if (bitmapImageRep)
  return;  
 
 // 中途半端にして bytesPerRow の最適化をみてみる。
 NSInteger pixelsWide = 99;
 NSInteger pixelsHigh = 99;
 
 // 手順1
 bitmapImageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
                pixelsWide:pixelsWide
                pixelsHigh:pixelsHigh
                bitsPerSample:8
              samplesPerPixel:3
                  hasAlpha:NO
                  isPlanar:NO
               colorSpaceName:NSCalibratedRGBColorSpace
                 bitmapFormat:0
               bytesPerRow:0
                 bitsPerPixel:0];
 
 // 手順2
 unsigned char* bitmapData = [bitmapImageRep bitmapData];
 NSInteger spp = [bitmapImageRep samplesPerPixel];
 NSInteger bpr = [bitmapImageRep bytesPerRow];
 
 NSInteger width, height;
 for (width = 0; width < pixelsWide; ++width) {
  
  for (height = 0; height < pixelsHigh; ++height) {
   
   // ビットマップ・データに値を設定。緑と青縞模様。
   if ((height & 1)) {
    *(bitmapData + (width * spp) + (height * bpr) + 1) = 255;    
   }
   else {
    *(bitmapData + (width * spp) + (height * bpr) + 2) = 255;    
   }
  }
 }
}

- (void)drawRect:(NSRect)dirtyRect
{

 [self drawImage];
 
 CGFloat pixelsWide, pixelsHigh;
 pixelsWide = (CGFloat)[bitmapImageRep pixelsWide];
 pixelsHigh = (CGFloat)[bitmapImageRep pixelsHigh];
 
 // 手順3
 [bitmapImageRep drawInRect:NSMakeRect(0.0, 0.0, pixelsWide, pixelsHigh)];

}

-(void)saveImage
{

 // 手順4
 NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/test.tif"];

 NSData *imageData = [bitmapImageRep TIFFRepresentation];

 [imageData writeToFile:filePath atomically:YES];

}
@end

手順1で作成した ビットマッピ・イメージは僕の環境では bytePerRow が 297バイトではなく 320バイトになっていました。bitsPerPixel で0を渡しているので、サンプルごとのパディングはなく、各行の最後 23バイトは余分なデータだということが分かります。
 
手順2でビットマップデータに直接値を設定しています。

手順3、手順4はそのままなので、特に説明はありません。


initWithBitimapDataPlanes:〜に渡す値は意外に NULL とか 0とか定数なのでらくちんでした。これでがしがしビットマップ・イメージが作れます。

0 件のコメント:

コメントを投稿