>binaervarianz >projekte >CocoaRay
Kapitel 3: Anatomie eines Bitmaps

Nachdem wir unser darzustellendes Bild inzwischen als NSBitmapImageRep zur Verfügung stellen, drängt sich die Frage auf, wie wir dieses Bild nun mit unserer Ausgabe füllen. Trotz des komplizierten Namens ist diese Cocoa Klasse nichts anderes als ein klassisches Bitmap, seit den Anfängen von Windows als .bmp bekannt. Die Klasse übernimmt für uns lediglich die Verwaltung der Kopfdaten, auf den eigentlichen Bildinhalt haben wir über einen Zeiger direkten Zugriff.

Aber wie wird der Bildinhalt nun beschrieben? Was uns die Klasse als Nutzinhalt übergibt, ist ein Zeiger auf ein Feld aus unsigned char, einfach ausgedrückt also auf unzählige, einzelne Bytes. Ein Byte, also 8 Bit, reicht aus, um den Wert einer Grundfarbe zu speichern. Drei Byte definieren als Rot, Gelb und Blau (RGB) also die Farbe eines Pixels. Unsere Farbauflösung entspricht also 3*8 = 24 Bit, was Mac OS X als "Millionen von Farben" kennt.
Folgen die drei Bytes (Samples) eines Pixels direkt hintereinander, spricht man von "merged planes". Gibt man dagegen erst alle Rotwerte aller Pixel, dann alle Blauwerte usw. an, nennt man dies "planar". Als zusätzliches Sample in einer weiteren "plane" kann dabei auch noch ein Alphawert, also die "Durchsichtigkeit" angegeben werden. Wir beschränken uns auf RGB-Werte in einer "merged" Konfiguration für ein 360*240 Pixel Bild.

Also konfigurieren wir in der -(id)initWithRect:(NSRect)rect Funktion des BIRenderer nach der Initialisierung des 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	]; 

Alle Zahlen dürften dabei recht selbsterklärend sein, aber was hat es mit der Variable planes auf sich? Dieses stellt ein Array der eben erwähnten "planes" dar. Da wir jedoch eine "merged configuration" verwenden, muss nur die erste "plane" belegt sein. Diese initialisieren wir mit einem Pointer auf den Inhalt eines NSMutableData-Objekts. Das ist die Cocoa Version eines einfachen Byte-Buffers. Diesen Pointer heben wir gut auf, über diesen können wir anschließend Bytes in unserem Bild verändern. Damit haben wir haben uns jetzt rückwärts durch folgende Definitionen gehangelt, die natürlich vor die Initialisierung von eben gehören:

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};

Natürlich können wir statt einem NSMutableData auch ein unsigned char[360*240*3] verwenden. Allerdings müssten wir dies schon in der Deklaration dimensionieren, was bei späteren Änderungen hinderlich werden könnte.

Dann wollen wir doch mal sehen, ob wir etwas anzeigen können. Wir implementieren endlich unsere -(void)drawImage Methode und füllen sie mit Code. Wir brauchen zwei verschachtelte Schleifen, die alle Pixel zeilen- und spaltenweise durchgehen. Mit Hilfe der Spalten- und Zeilennummer müssen wir den Offset berechnen, also die "Hausnummern" der drei Farb-Bytes in unserem Buffer. Das schaffen wir mit:

Zeilennummer * Anzahl Bytes pro Zeile + Spaltennummer * Anzahl Bytes pro Pixel

Damit erhalten wir die Adresse des ersten Farbwerts des Pixels. Damit malen wir mal ein kleines, rotes Rechteck.


- (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!!! Unsere erste, selbst erstellte Ausgabe. Wer mag, kann jetzt ein bisschen das 2D-Vorstellungsvermögen trainieren und versuchen, Bildchen zu malen. Als erste Optimierungshausaufgabe (nur zum Stil, nicht zur Geschwindigkeit) kann man sämtliche bisher verwendeten Absolutzahlen in Variablen packen. Vieles kann man aus anderen Werten berechnen, die Pixelgrößen kann man sich zum Beispiel aus dem übergebenen NSRect extrahieren. Noch schöner wird's, wenn man alle größenunabhängigen Werte in der -(id)init festlegt, alle anderen in der -(id)initWithRect:(NSRect)rect. Letztere kann dann einfach erstere zur Grund-Initialisierung aufrufen.

>binaervarianz >projekte >CocoaRay