SSブログ

NSSetとライフゲーム(その6、NSImageなど) [プログラミング - NSSetとライフゲーム]

何回か前に出たGoLSparseGameOfLifeWithImageのimageメソッドについて。このメソッドでは生きたセルの位置のドットに色を付けたNSImageを返す。

NSImageは実際の中身、例えばビットマップだったりBezierパスだったりするのを一手に引き受ける。そのため中身はNSImageRepという別のクラスに持たせる。NSImageはNSImageRepのインスタンスを複数持てる。いろんな形式の表現が保持できるようになっている。
今回はセル一つが1ピクセルになるようなビットマップを作ってNSImageに持たせることにした。ビットマップはNSImageRepのさらにサブクラスのNSBitmapImageRepというクラスがあるのでそれを使う。

NSImageには表示されるときの大きさに関する

- (id)initWithSize:(NSSize)aSize;
- (void)setSize:(NSSize)aSize;
- (NSSize)size;


がある。ここでのsizeは中身とは独立に設定できる。つまり持っているビットマップのピクセルサイズとは無関係な値にすることができる。NSImageRepには

- (int)pixelsHigh;
- (int)pixelsWide;


というメソッドがあって、ピクセルサイズを表す。NSImageのサイズとピクセルサイズが違っている場合、表示のときには補間されるようである。インスタンス変数のenlargeRatioはこの機能を使って1セル1ピクセルを拡大表示するようにした。ピクセルの位置によってアンチエイリアスのかかり方が違ったりするが、自分で書くと大変なのでこれでいいでしょう。

実際の表示にはNSImageViewを使った。これはNSViewのサブクラスでNSImageをsetImage:で渡すだけで表示してくれる。中身を見るだけならこれで十分。
それぞれの関係を絵に描くと

NSImageViewにはNSImageの描き方をいろいろ指定できるが、表示の大きさについて

- (NSImageScaling)imageScaling;
- (void)setImageScaling:(NSImageScaling)scaling;


と言うメソッドがある。NSImageScalingは

typedef enum {
   NSScaleProportionally = 0,
   NSScaleToFit,
   NSScaleNone
} NSImageScaling;


で、NSImageViewの大きさと表示するNSImageの大きさが違うときどうするかというのを指定できる。NSScaleProportionallyは、NSImageの方が大きいと縦横比を保って縮小する。小さい場合は中央にそのままのサイズで表示する。NSScaleToFitはNSImageViewいっぱいに拡大縮小して表示する。この場合縦横比は保たれない。NSScaleNoneは拡大縮小無し。NSImageの方が大きいと周辺がクリップされる。

NSBitmapImageRepのピクセルサイズとNSImageのsizeとさらにNSImageViewのサイズがそれぞれ違ってNSScaleNone以外の場合は2段階に拡大縮小されると思われるが、ちゃんと調べてないのでどのようになっているか良くわからない。今回の場合、効率が明らかに違うと言うほどでは無さそう。

GoLSparseGameOfLifeWithImageはimageメソッドが呼ばれると
1.boundsを計算する
2.その大きさのNSBitmapImageRepを作る
3.セルの状態に対応した位置にセル年齢に従ってカラーテーブルから色をつける
4.そのNSBitmapImageRepをNSImageに入れて返す
ということをする。
このために2回全部の生きたセルを走査することになるが、これはしかたないでしょう。ということでプライベートメソッドとして

- (NSRect)bounds;
- (NSBitmapImageRep *)prepareImageRep:(NSRect)bounds withBackground:(unsigned int)backColor;


の二つとCのstatic関数

static GoLPoint   findOriginFromArray(NSArray *pointArray);


を作る。

なぜC関数なのかと言うと、これは単なる習慣。メソッドにするかCの関数にするかは効率とは無関係に
1.オブジェクトでなくmalloc()などで確保されたメモリ領域を返すときは関数に
2.インスタンス変数のアクセスが少なくしかもそれなりのボリュームがあってひとまとまりの作業と見なせるような場合は関数に
3.インスタンス変数の依存が多い、あるいはオブジェクトとしての仕事であると見なした方がわかりやすい場合にはメソッドに
というようなガイドラインにしている。findOriginFromArray()は2.のパターン。

この場合とは関係ないが、特に1.のパターン貰ったポインタを必ずどこかでfree()してやらなければならず、これを忘れないようにするためにこうしている。逆にメソッドの返り値としてオブジェクトが返されたとき、プライベートメソッドであってもかならずautoreleaseされたオブジェクトを返すようにして、メモリ管理を気にしなくて良いようにしている。効率にとっては不利な場合があるが、メソッドの返り値としてmalloc()を返した場合は、オブジェクトと同じようなautoreleaseができないので統一性が損なわれて絶対後でわからなくなる。このガイドラインは記憶力が普通の人より低い場合、有効である。と言いながらこのガイドラインそのものを忘れるんだよなあ。

malloc()されたメモリ領域に関して言うと
1.インスタンス変数として確保する場合
2.作業領域として確保する場合
に分かれる。インスタンス変数として確保した場合は、かならずdeallocを定義してそこにfree()を書く。作業領域として確保した場合は、ポインタを得たメソッド(直接malloc()を呼ぶかポインタを返すC関数を呼んだメソッド)が必ずfree()を呼ぶようにする。こうするとfree忘れが減るし、副作用としてreleaseのタイミングに集中できる、という利点もある。

そんなもんObjectAllocツールですぐわかるよ、と言う人もいるかもしれないけど。

さて、boundsメソッドは中身がすぐわかるとは思うけど(縦横それぞれoriginから一番遠いセルを探す)、prepareImageRep:withBackground:はちょっと中身を見る必要がある。

- (NSBitmapImageRep	*)prepareImageRep:(NSRect)bounds withBackground:(unsigned int)backColor
{
    NSBitmapImageRep    *irep;
    unsigned int    *bmp;
    int     width = (int)bounds.size.width;
    int     height = (int)bounds.size.height;
    int     i;
	
    irep = [[NSBitmapImageRep alloc]
                    initWithBitmapDataPlanes: NULL
                                  pixelsWide: width
                                  pixelsHigh: height
                               bitsPerSample: 8
                             samplesPerPixel: 4
                                    hasAlpha: YES
                                    isPlanar: NO
                              colorSpaceName: NSCalibratedRGBColorSpace
                                bitmapFormat: NSAlphaNonpremultipliedBitmapFormat
                                 bytesPerRow: width * 4
                                bitsPerPixel: 0];
    if (irep == nil)
        return nil;
    bmp = (unsigned int *)[irep bitmapData];
    for (i = 0 ; i < width * height ; i ++)
        bmp[i] = backColor;
    return [irep autorelease];
}
このinitWithBitmapDataPlanes:〜のメソッドにはちょっと引いてしまうが、ようするに普通に使われるビットマップデータを作るためのメソッドで、インデクスカラー以外のビットマップならほぼどんな形式でも作ることができる。全部メモると大変だが、DataPlanes:の引数にNULLを渡すとNSBitmapImageRepが自分でその領域を確保する。確保済みのものを使うならここにポインタのポインタ(プレーナ型(RGBARGBA...でなくRRR...GGG...BBB...)に対応するため)を渡す。確保済みの場合はNSBitmapImageRepがreleaseされてもこの領域はfree()されないので要注意。 このコードの場合はアルファ付き8ビットチャンネルのパックされたRGBAのビットマップを作っている。まあ、これができれば十分でしょう。作った後はNSBitmapImageRepのbitmapDataメソッドでRGBA領域へのポインタをもらってバックグランドカラーで塗りつぶしている。 たぶん自分で0からビットマップイメージを作るときはおそらくこれが一番手っ取り早い。あまりビットバイトした感じのコードが嫌いな向きはNSBitmapImageRepのメソッド
- (void)setColor:(NSColor *)color atX:(int)x y:(int)y
ひとつずつ色を置くか、もっとNSImageのメソッド
    NSImage *image = [[NSImage alloc] initWithSize:aSize];
    [image lockFocus];
//  imageに対する描画
    [image unlockFocus];
なんかでNSRectFill(const NSRect aRect)を使ったり、NSBezierPathのappendBezierPathWithOvalInRect:メソッドで丸を並べるときっと奇麗にできると思う。
nice!(0)  コメント(4)  トラックバック(0) 

nice! 0

コメント 4

KeyHoleTV

KeyHoleTVの開発者です。このサイトのおかげで、Mac版KeyHoleTVの開発ができました。ありがとうございます。
KeyHoleTVの場合、YUVのデータをRGBに変換して、NSBitmapImageRepのデータに値をいれているのですが、
このとき、[AA RR GG BB ... の順番にいれる必要がありました。また、AAは、0xFFをいれないととんでもない映像になるようです。Windowsでは、0でよかったので、そのまま利用していました。これがなかなかわかりませんでした。
by KeyHoleTV (2009-03-06 16:16) 

decafish

コメントありがとうございます。Mac版リリースおめでとうございます。「このサイトのおかげで」などと言っていただけるなんて大変光栄です。さっそくダウンロードさせていただきました。僕の環境では問題なく動作します。非常に軽いです。これまでWindowsの人たちが使っているのを横目で見ていたのですがこれで溜飲が下がりました。ありがとうございます。
Windowsではアルファ値が0でも表示されるのですか。僕にはそっちの方が不思議な気がします。
Macのオーディオ関連のAPIはAudioUnitをはじめ充実しているのですが残念ながらドキュメントが不親切で使いづらいです。
by decafish (2009-03-06 22:01) 

KeyHoleTV開発者

KeyHoleTVの音の出力なんですが、44100Hz 16 bitsで再生すると、
WindowsやLinuxと違って、Macでは単位時間が違っているように感じます。どうも再生時間が長いようです。ですから、しばらく見ていると、映像の後に音が出てくる現象になりました。これは、私のMacMiniが悪いのか(一台目のMacMiniが壊れたため、もう一台購入しました。二台とも同一現象です)仕様なのかがわかりません。これに対応するため、44500Hzで音を出すようし、また、OS10.4でも動作するバージョンKeyHoleTV1.1をリリースしました。
誠に申し訳ありませんが、音の違いを教えていただけば幸いです。

RGBのαの問題ですが、Windowsでは、4バイトでデータを獲得するほうが早いので、αを使っていないと思います。
by KeyHoleTV開発者 (2009-03-09 01:24) 

decafish

返事が遅くなって申し訳ありません。僕の環境でも音がだんだん遅れてきました。音程は下がって聴こえなかったので、僕の環境のレイテンシの問題かと思っていました。
1.3を落とさせてもらいました。1.3では問題ないです。
周波数1%の差では音程がわからなかったということでしょうか。
by decafish (2009-03-15 22:16) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

自転車で宮城スタジアム10/20献立10/20 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。