
After representing our picture as a NSBitmapImageRep, the question arises how to fill this image with content. Besides its comlicated name, this Cocoa class is nothing more than a clasiscal bitmap, known since the dawn of Windows as .bmp. The class just takes responibilty for managing the header data. The image data can be accessed with a pointer to it.
But how is this content represented in code? What our class is
returning as image data is essentially a pointer to
a field of unsigned char. Siplier put, these are
just raw bytes. A byte, meaning 8 bit, is enough to save
the information of one base color. Three bytes define the color of a
pixel
as red, green and blue. our color resolution therefor is 3*8 = 24
bit, thats what Mac OS X is calling 'millions of colors'
If the three bytes of one pixel are aligned directly after one another,
the configuration is called 'merged planes'.
If all red values of all pixels come first, then all blue values and so
on, it's a 'planar' configuration.
As another sample, or another plane, an alpha value can be stored to
represent the transparency if that pixel.
We will only use three samples and order them in a merged configuration
for a 360*240px image.
This configuration is done in the -(id)initWithRect:(NSRect)rect method of the BIRenderer after the initialisation of the NSBitmapImageRep:
[image initWithBitmapDataPlanes:planes
pixelsWide:360 pixelsHigh:240
bitsPerSample:8 samplesPerPixel:3
hasAlpha:NO isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bytesPerRow:280*3*8/8 bitsPerPixel: 3*8 ];
All values should be pretty self-explaining, but what is it about that planes variable? This is an array for the mentioned planes of the image. Because we use a merged configuration we only use the first plane. This first plane is initialised to the contents of a NSMutableData object. It is the Cocoa version of a simple byte buffer. We have to keep the pointer to this, cause with this we will be able to change the bytes in oure image. Now we pushed ourselfs backwards through the following definitions, which of course must appear befor the initialisation:
Header:
NSMutableData* pixels;
unsigned char* baseAddr;
-(id)initWithRect:(NSRect)rect:
pixels = [[NSMutableData alloc] initWithLength:360*240*3];
baseAddr = [pixels mutableBytes];
unsigned char* planes[4]={baseAddr, NULL, NULL, NULL};
Of course instead of a NSMutableData we could have used an unsigned char[360*240*3]. But this would need a fixed size at time of definition, while the Cocoa class can be resized.
So lets see if we can display something. We noe implementa our
-(void)drawImage
method and put some code in. We need two nested loops to go through all
pixels row- and columnwise.
With the row- and column index we can then calculate the offset for the
pointer to access the pixel.
The offset can be seen as the address of the three color bytes in the
buffer.
We achieve this with:
With this we get the address of oure first color byte (red) of the indexed pixel. Lets try to draw a red rectangle:
- (void)drawImage{
int columnIndex=0, rowIndex=0;
for(rowIndex=0;rowIndex<360;rowIndex++){
for(columnIndex=0;columnIndex<240;columnIndex++){
if(columnIndex>100 && columnIndex<280 && rowIndex>100 && rowIndex<140){
baseAddr[rowIndex*3*360 + columnIndex*3+0]='\xFF';
baseAddr[rowIndex*3*360 + columnIndex*3+1]='\x00';
baseAddr[rowIndex*3*360 + columnIndex*3+2]='\x00';
}
else{
baseAddr[rowIndex*3*360 + columnIndex*3+0]='\x00';
baseAddr[rowIndex*3*360 + columnIndex*3+1]='\x00';
baseAddr[rowIndex*3*360 + columnIndex*3+2]='\x00';
}
}
}
}
TADAAAA!!! Our first, self created output. You now can draw some pictures if you like to train your 2D imagination capabilitys.
The first optimization homework you can do, is to put all those absolut numbers we used in variables and see
if you can calculate them out of each other. For example the pixel sizes can be retrieved from the NSRect
given for initialisation. The codng style would be improved, if you can manage to define all independent parameters in a general
-(id)init method while setting all somewhat dependent variables inside the -(id)initWithRect:(NSRect)rect.
The last mentioned can call the first one to initialise.