2003
01 02 03 04 05 06 07 08 09 10 11 12
2006
01 02 03 04 05 06 07 08 09 10 11 12
2007
01 02 03 04 05 06 07 08 09 10 11 12
2008
01 02 03 04 05 06 07 08 09 10 11 12
2009
01 02 03 04 05 06 07 08 09 10 11 12
2010
01 02 03 04 05 06 07 08 09 10 11 12
2011
01 02 03 04 05 06 07 08 09 10 11 12
2017
01 02 03 04 05 06 07 08 09 10 11 12
2018
01 02 03 04 05 06 07 08 09 10 11 12
 
Mar
19
2004

NSBitmapImageRep

Cocoa 上的點陣圖處理

之前看 Cocoa 的書總是丈二金剛摸不著腦袋,每個範例都是用 Bezier Curve 來畫。哇哩咧,時代已經進步成這樣子了,之前組語課還在一個 pixel 一個 pixel 填 frame buffer,想在螢幕上畫線的演算法,現在要一下子轉換到向量繪圖還真不適應。而且我覺得啦,該用點陣的地方用 Bezier Curve 來模擬應該會有很重的 overhead 。拖拖拖拖到現在總算看了一下怎麼用…

NSBitmapImageRep
NSImage

這兩個是核心 class 吧,目前我試出(習慣)的方法是這樣子的,用一張 NSImage 當作 buffer ,將 NSBitmapImageRep 指定給那張 NSImage ,然後利用 -(unsigned char *) bitmapData: 取得一塊記憶體空間。要畫圖只要在這塊記憶體裡面填就 ok 了。

詳細的狀況是這樣:


#define width 200
#define height 300
// declare.
NSBitmapImageRep *imgRep;
NSImage *img;
unsigned char *frame;

// initialize.
imgRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL pixelsWide:width
pixelsHigh:height bitsPerSample:2
samplesPerPixel: 4
hasAlpha: YES
isPlanar: NO
colorSpaceName: NSCalibratedRGBColorSpace
bytesPerRow: width
bitsPerPixel: 8];

img = [[NSImage alloc] initWithSize:NSMakeSize(width,height)];

// assign imgRep to img.
[img addRepresentation: imgRep];

// get the raw image memory address.
frame = [imgRep bitmapData];

以上是準備的部份,應該在程式 initialize 的時候執行到。其中最可怕的就是 imgRep 的初始了!很長的 method name。
-(id)initWithBitmapDataPlanes:(unsigned char **)planes
pixelsWide:(int)width pixelsHigh:(int)height bitsPerSample:(int)bps
samplesPerPixel:(int)spp hasAlpha:(BOOL)alpha isPlanar:(BOOL)isPlanar
colorSpaceName:(NSString *)colorSpaceName bytesPerRow:(int)rowBytes
bitsPerPixel:(int)pixelBits

planes – 我傳 NULL 進去。不知道為啥,不傳 NULL 好像就會爛,我也懶得研究。總之可以用就好了。
width – 圖寬 pixel 數
height – 圖高 pixel 數
bps (bits per sample) – 一個 pixel 會用一個色彩來描述嘛,那個色彩會用不同的分量來表示,例如說R﹦0x00, G=0xFF, B=0xFF。而 bps 就是說一個分量要有幾個 bit 啦!可以填的數值有1, 2, 4, 8, 12, or 16. 我這邊因為是想把一個 pixel 用一個 byte 來表示,所以平均分配給 RGB 以及 alpha mask 一共 (3+1) =4 個分量,一個分量就是2bits。從Most Significant Bit 到 Least Significant Bit 是按照以下的順序排列:RRGGBBAA,最後兩個 bit 是 alpha mask。
[RRGGBBMM]

spp (samples per pixel) – 一個色彩有幾個分量,值的範圍從 1 ~ 5。
alpha – YES 的話就代表有 alpha channel,  NO 就沒有。
isPlanar – 是否要把各個分量放在不同的陣列裡面,有點像 photoshop 裡 channel 的概念。 YES 的話,就要有 spp 個平面,而一個平面是 char * ,所以如果這邊設 YES 的話 planes 就要傳 char ** 進去。
plane.png
colorSpaceName – 傳以下幾個用 enum 定義的值就可以了。我是選 RGB…

  • NSCalibratedWhiteColorSpace
  • NSCalibratedBlackColorSpace
  • NSCalibratedRGBColorSpace
  • NSDeviceWhiteColorSpace
  • NSDeviceBlackColorSpace
  • NSDeviceRGBColorSpace
  • NSDeviceCMYKColorSpace
  • NSNamedColorSpace
  • NSCustomColorSpace

rowBytes – 一個 row 有幾個 byte。是否為 planar 圖時算法不同,若
width*spp*bps(非planar)或width*spp(planar)不是剛好整數個 bytes 時就很麻煩了,這種狀況下有時一個 row 的尾端可能會 padding。因此你要自己算好 padding 後的長度填進來。如果是 0 的話,NSBitmapImageRep 就當作尾端沒有多餘的 padding 。所以其實我這裡也可以填 0 啦。
pixelBits – 和 rowBytes 一樣是用來處理 padding 的問題的,只不過我怎麼試都怪怪的,懶得理它,自己看。設成 0 的話也是當作沒有 padding。

接下來是要畫圖的時候:

for(i=0; i < height *width; i++) frame[i]++; [imageView setImage: img]; [imageView setNeedsDisplay: YES];

直接 access frame 就好啦!至於 imageView 是一個 NSImageView ,setNeedsDisplay: 是讓它 update。這點在任何 NSView 中應該是一樣的。

Reference:
[1] NSBitmapImageRep from Apple's Developer Documentation
[2] Bitmap Image Filters by Michael Beam.

 
 

Write Concisely