Push-Nachrichten von MacTechNews.de
Würden Sie gerne aktuelle Nachrichten aus der Apple-Welt direkt über Push-Nachrichten erhalten?
Forum>Entwickler>NSImage Durchschnittsfarbe (GCD?)

NSImage Durchschnittsfarbe (GCD?)

Christoph_M
Christoph_M31.03.1202:27
Moin,

ich versuche gerade an meinen 27" iMac eine Art "Self Made Ambilight" ranzubauen.
Habe bereits die LEDs hinten dran und kann dieser per Arduino ansteuern.
Die zweite Komponente ist eine Cocoa App welche von 7 Blöcken am rechten Bildschirmrand und 7 Blöcken am Linken Rand (jeweils 20% rein) die Durchschnittsfarbe ausrechnet.
Das klappt auch schon super (siehe Screenshot, wobei die Ergebnisse derzeit nur in der Konsole, nicht auf der GUI landen).

Mein Problem ist, dass die Berechnung zu lange dauert (0,5 bis 1 Sekunde) und ich mich frage ob das nicht optimierbar ist, indem z.B. GCD genutzt wird.
Derzeit schaue ich nur jeden 2. Pixel in jeder 2. Reihe an, hier würde auch weniger gehen, aber ich würde vorher lieber erst meinen Code optimieren.

Hier ist mein Code:

for(int x=xStart;x<xEnde;x=x+2) {
                for(int y=yStart;y<yEnde;y=y+2) {
                    color = [bitmapRep colorAtX:x y:y];
                    sumRed   += color.redComponent;
                    sumGreen += color.greenComponent;
                    sumBlue  += color.blueComponent;
                    counter++;
                    if(y==yStart || yEnde-y < 2) // Das ist nur zur Visualisierung drin
                        [bitmapRep setColor:[NSColor blueColor] atX:x y:y];
                    else
                        [bitmapRep setColor:[NSColor redColor] atX:x y:y];
                }
            }

xStart,xEnde,yStart und yEnde markieren jeweils Anfang und Ende des Blockes (die Blocks sieht man sehr gut im Screenshot).

Im Moment laufe ich das Ganze in einer extra GCD Queue durch. Hatte auch schon getestet für jeden Block eine eigene Queue zu verwenden, das ganze wurde dadurch aber sogar langsamer.
Da der Prozessor irgendwo bei 80% rumdümpelt, frage ich mich, wie ich mehr aus der Hardware (i7) holen kann.

Irgendwelche Ideen?

Danke schonmal und viele Grüße,
Christoph

p.s. wenn das Projekt fertig ist mach ich ein Journal
0

Kommentare

Marcel Bresink31.03.1212:01
Erzwinge, dass Mac OS X aus dem gegebenen NSImage (oder NSImageRep) eine NSBitmapImageRep mit gegebenen Parametern rendert und lese die Pixel dann als rohe Bytes, durch Zugriff auf die -bitmapData. Das wird um viele Größenordnungen schneller sein und der Zugriff auf nur jedes zweite Pixel kann man sich schenken.

Das Rendern kann man hinkriegen, indem man eine leere NSBitmapImageRep mit den gewünschten Werten anlegt und dann das Bild mithilfe eines [NSGraphicsContext graphicsContextWithBitmapImageRep: x] und einem -drawInRect: dort hineinzeichnet.

Noch was anderes: Ein beliebter Fehler ist es, die Durchschnittsfarbe nach dem Motto (r+g+b)/3 zu berechnen. Das darf man nicht so machen, da das Ergebnis dann violettstichig wird.
0
Marcel Bresink31.03.1213:13
Nachtrag: Den letzten Absatz bitte streichen. Ich hatte schon weitergedacht und die Farbwerte in Luminanz und Chrominanz getrennt. Für Christophs Vorhaben ist das unnötig.
0
Christoph_M
Christoph_M31.03.1221:49
Hi Marcel,

vielen Dank für deine schnelle Antwort!

Ich hatte mich schon gefreut, weil das Bild sowieso als NSBitmapImageRep vorliegt
Allerdings muss ich sagen dass ich mich mit allen C Konstrukten nicht auskenne, hab also erstmal sowas versucht (bitmapRep ist mein Screenshot):

unsigned short* destData = (unsigned short*) [bitmapRep bitmapData];
    
    for(int i=0;i<sizeof(destData);i++) {
        NSLog(@"%c",destData[i]);
    }
    
da kommt dann aber nur sowas:

2012-03-31 21:06:25.164 MacAmbilight[23771:403] ı
2012-03-31 21:06:25.165 MacAmbilight[23771:403] ˇ
2012-03-31 21:06:25.165 MacAmbilight[23771:403] Ù


Habe jetzt noch versucht die 14 Blöcke per dispatch_apply(14, queue, ^(size_t idx){...
auszuführen, allerdings ist dort die Laufzeit doppelt so lange wie bei einem einfachen dispatch_sync.

grüße,
Christoph
0
Marcel Bresink01.04.1211:09
Ich hatte mich schon gefreut, weil das Bild sowieso als NSBitmapImageRep vorliegt

Das nützt nichts, weil Du im Vorhinein nicht weißt, wie das Bild im Speicher intern abgelegt ist. Das kann je nach Quelle des Bildes, verwendeter Grafikkarte, usw. ganz verschieden sein. Du musst Mac OS X dazu zwingen, das Bild in einem von Dir definierten Layout im Speicher abzulegen, z.B. 8 Bit pro Pixel in der Reihenfolge rot-grün-blau-alpha (RGBA). Dazu muss das Bild nochmals intern gerendert werden.
da kommt dann aber nur sowas:

Der Code enthält zwei Fehler. Zum einen liegen die Daten in den seltensten Fällen als 16-Bit-Werte (unsigned short) vor, zum anderen läuft die Schleife nur über die Größe des Pointers. Mit anderen Worten, auf einem 32-Bit-Rechner wird die Schleife 4-mal, bei 64 Bit acht Mal durchlaufen.

Wenn ich Zeit habe, kann ich mal eine richtige Lösung posten.
0
Marcel Bresink01.04.1211:16
Im letzten Beitrag muss es "8 Bit pro Farbkomponente, 32 Bit pro Pixel" heißen.
0
Marcel Bresink02.04.1214:08
Hier eine mögliche Quick-and-Dirty-Lösung aus der Mittagspause:

    NSArray             *inputReps = [NSBitmapImageRep imageRepsWithContentsOfFile: @"/PathToFile"];
    NSBitmapImageRep    *sourceRep, *scanRep;
    NSGraphicsContext   *bitmapContext;
    NSInteger           width, height, x, y, pixelCount;
    unsigned char       *p, *pLine;
    unsigned long long  rSum, gSum, bSum;
    struct timeval      before, after;
    
    sourceRep = [inputReps objectAtIndex: 0];
    width = [sourceRep pixelsWide];
    height = [sourceRep pixelsHigh];
    scanRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL
                                                      pixelsWide: width
                                                      pixelsHigh: height 
                                                   bitsPerSample: 8
                                                 samplesPerPixel: 4
                                                        hasAlpha: YES
                                                        isPlanar: NO
                                                  colorSpaceName: NSCalibratedRGBColorSpace
                                                     bytesPerRow: 0
                                                    bitsPerPixel: 0];
    
    bitmapContext = [NSGraphicsContext graphicsContextWithBitmapImageRep: scanRep];
    [NSGraphicsContext saveGraphicsState];
    [NSGraphicsContext setCurrentContext: bitmapContext];
    [sourceRep drawInRect: NSMakeRect(0.0, 0.0, (CGFloat)width, (CGFloat)height)];
    [NSGraphicsContext restoreGraphicsState];
    
    pLine = (unsigned char*)[scanRep bitmapData];
    rSum = 0;
    gSum = 0;
    bSum = 0;
    pixelCount = 0;
    gettimeofday(&before, NULL);
    for (y = 0; y < height; y++) {
       p = pLine;
        for (x = 0; x < width; x++) {
            rSum += *p++;
            gSum += *p++;
            bSum += *p++;
            p++;
            pixelCount++;
        }
        pLine += [scanRep bytesPerRow];
    }
    gettimeofday(&after, NULL);
    [scanRep release];
    NSLog(@"Average color: (%f, %f, %f) (%lld pixels)\n", (double)rSum / 256.0 / (double)pixelCount,
          (double)gSum / 256.0 / (double)pixelCount,( double)bSum / 256.0 / (double)pixelCount,
          (long long)pixelCount);
    NSLog(@"Time: %f s\n", after.tv_sec - before.tv_sec + (after.tv_usec - before.tv_usec) / 1000000.0);

Um sämtliche Pixel des angesprochenen 3,6 Megapixel-Bildes zu berücksichtigen, braucht das Programm auf meinem Rechner gerade einmal 0,04 Sekunden. Auf einem einzelnen Prozessor, ohne GCD-Parallelisierung.
0
qbert
qbert02.04.1215:26
Klingt für mich irgendwie aufwendig die Pixel alle auszulesen. Kannst Du nicht mit der Grafikkarte erst mal einen Filter über den Screenshot laufen lassen, der das Bild runterskaliert/blurt damit der Arbeitsaufwand kleiner wird?
0
Christoph_M
Christoph_M12.04.1222:36
Hallo Marcel,

ich wollte mich nur kurz melden um mich für das Codeschnipsel zu bedanken!
Ich bin leider noch nicht dazugekommen es auszutesten weil ich beruflich kurzfristig weg musste, aber das Dankeschön sei dir trotzdem schon gewiss!
Meine Erfahrungen poste ich dann hier oder vielleicht eröffne ich auch ein Journal.

Beste Grüße,
Christoph
0

Kommentieren

Diese Diskussion ist bereits mehr als 3 Monate alt und kann daher nicht mehr kommentiert werden.