2009-06-25 18 views
35

Ich versuche derzeit, den Alpha-Wert eines Pixels in einem UIImageView zu erhalten. Ich habe den CGImage von [UIImageView image] erhalten und daraus ein RGBA-Byte-Array erstellt. Alpha wird vormultipliziert.Abrufen eines Pixel Alpha-Werts für ein UIImage

CGImageRef image = uiImage.CGImage; 
NSUInteger width = CGImageGetWidth(image); 
NSUInteger height = CGImageGetHeight(image); 
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
rawData = malloc(height * width * 4); 
bytesPerPixel = 4; 
bytesPerRow = bytesPerPixel * width; 

NSUInteger bitsPerComponent = 8; 
CGContextRef context = CGBitmapContextCreate(
    rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, 
    kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big 
); 
CGColorSpaceRelease(colorSpace); 

CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); 
CGContextRelease(context); 

I für den gegebenen alpha-Kanal die Koordinaten aus dem UIImageView unter Verwendung des Array-Index dann berechnen.

int byteIndex = (bytesPerRow * uiViewPoint.y) + uiViewPoint.x * bytesPerPixel; 
unsigned char alpha = rawData[byteIndex + 3]; 

Allerdings bekomme ich nicht die Werte, die ich erwarte. Für einen vollständig schwarzen transparenten Bereich des Bildes bekomme ich Werte ungleich null für den Alpha-Kanal. Muss ich die Koordinaten zwischen UIKit und Core Graphics übersetzen - d. H. Ist die Y-Achse invertiert? Oder habe ich vorgemerkte Alpha-Werte falsch verstanden?

Update:

@Nikolai Ruhe Vorschlag war zu diesen Schlüsseln. Ich musste nicht zwischen UIKit-Koordinaten und Core-Graphics-Koordinaten übersetzen. Doch nach dem Mischmodus meiner Alpha-Werte Einstellung waren, was ich erwartet habe:

CGContextSetBlendMode(context, kCGBlendModeCopy); 
+0

Hey Teabot - Ich weiß, dass diese Frage bereits beantwortet ist, aber ich habe etwas ähnliches vor ein paar Tagen gemacht. Anstatt das gesamte Bild zu zeichnen und dann in das Byte-Array zu indizieren, sollten Sie nur 1px des Quellbilds in einen 1px-1px-Bitmap-Kontext zeichnen. Sie können den rect-Parameter in CGContextDrawImage verwenden, um das richtige Pixel einzufügen, und es ist 1000-mal schneller :-) –

+0

Danke für den Kommentar @Ben Gotow - Ich zeichne das Bild nur einmal und benutze dann dasselbe Byte-Array. @Nikolai Ruhe schlug auch das Zeichnen eines einzelnen Pixels vor, sagte aber, dass mein Array-Ansatz schneller wäre, wenn ich das Bild nicht mehr als einmal zeichnen müsste, sondern das Alpha wiederholt suchen müsste. – teabot

+1

Nur ein kurzes Update (Jahre später) nach der Verwendung dieser und einer anderen Frage hier für ein ähnliches Problem: Der rect-Parameter auf CGContextDrawImage wird verwendet, um "Die Position und Abmessungen im Benutzerbereich des Begrenzungsrahmens, in dem das Bild zu zeichnen. " Also, wenn Sie das Rect 1x1 in der Größe machen, wird es das gesamte Bild auf 1x1 skalieren, bevor es gezeichnet wird. Um das richtige Pixel zu erhalten, müssen Sie CGContextTranslateCTM wie in Nikolais Antwort verwenden und die Größe des Rechtecks ​​gleich dem Quellbild belassen, um eine Skalierung zu vermeiden. – roguenet

Antwort

10

Ja, haben CGContexts ihre y-Achse zeigt nach oben, während in UIKit es nach unten zeigt. See the docs.

bearbeiten nach Codelese:

Sie wollen auch den Mischmodus einzustellen, bevor Sie zeichnen das Bild zu ersetzen, da Sie das Bild des Alpha-Wert wollen, nicht derjenige, der zuvor in den Kontext des Puffers war:

CGContextSetBlendMode(context, kCGBlendModeCopy); 

bearbeiten nach Denken:

Sie könnten den Nachschlag-viel tun effizienter durch den kleinstmöglichen CGBitmapContext (1x1 Pixel? vielleicht 8x8? haben einen Versuch) und den Kontext auf die gewünschte Position zu übersetzen vor dem Zeichnen:

CGContextTranslateCTM(context, xOffset, yOffset); 
+0

@Nikolai Ruhe - Guter Anruf - ich hätte die Unterlagen lesen sollen. – teabot

+0

Der CGImageRef repräsentiert ein statisches Bild. Sobald ich das Byte-Array habe, werfe ich das CGImage weg und verwende dann wiederholt das Byte-Array, um die Alpha-Suche durchzuführen. Würde Ihre Optimierung in diesem Szenario funktionieren? – teabot

+0

Nein, natürlich brauchen Sie das Bild, um es wiederholt zu zeichnen.Mein Ansatz könnte für wenige Nachschlagewerke effizienter sein. Wenn Sie viele Lookups machen, halten Sie sich an Ihren Code. –

3

Muss ich die Koordinaten zwischen UIKit und Core Graphics übersetzen - das heißt: die y-Achse invertiert?

Es ist möglich. In CGImage sind die Pixeldaten in englischer Leserichtung: von links nach rechts, von oben nach unten. Das erste Pixel im Array ist also oben links; das zweite Pixel ist eins von links in der oberen Reihe; etc.

Angenommen, Sie haben das Recht, sollten Sie auch sicherstellen, dass Sie die richtige Komponente innerhalb eines Pixels betrachten. Vielleicht erwarten Sie RGBA, fragen aber nach ARGB oder umgekehrt. Oder, vielleicht hast du die Byte-Reihenfolge falsch (ich weiß nicht, was der Endian des iPhone ist).

Oder habe ich vorgemerkte Alpha-Werte falsch verstanden?

Es klingt nicht so.

Für diejenigen, die nicht wissen: Premultiplied bedeutet, dass die Farbkomponenten durch das Alpha multipliziert werden; Die Alphakomponente ist die gleiche, unabhängig davon, ob die Farbkomponenten vormultipliziert werden oder nicht. Sie können dies (prompt) umkehren, indem Sie die Farbkomponenten durch das Alpha dividieren.

+0

@Peter Hosey - Danke für die Klärung, wie premultipliziert alpha arbeitet - ich war unsicher. – teabot

3

Ich habe diese Frage/Antwort bei der Erforschung der Kollisionserkennung zwischen Sprites mit dem Alpha-Wert der Bilddaten, anstatt einer rechteckigen Bounding Box gefunden. Der Kontext ist eine iPhone App ... Ich versuche, das oben vorgeschlagene 1 Pixel Draw zu machen und ich habe immer noch Probleme, dies zu arbeiten, aber ich fand einen einfacheren Weg, einen CGContextRef mit Daten aus dem Bild selbst zu erstellen, und die Hilfsfunktionen hier:

CGContextRef context = CGBitmapContextCreate(
       rawData, 
       CGImageGetWidth(cgiRef), 
       CGImageGetHeight(cgiRef), 
       CGImageGetBitsPerComponent(cgiRef), 
       CGImageGetBytesPerRow(cgiRef), 
       CGImageGetColorSpace(cgiRef), 
       kCGImageAlphaPremultipliedLast  
    ); 

Dies umgeht alle hässlichen Hardcoding im obigen Beispiel. Der letzte Wert kann durch Aufrufen von CGImageGetBitmapInfo() abgerufen werden, aber in meinem Fall gibt es einen Wert aus dem Bild zurück, der einen Fehler in der ContextCreate-Funktion verursacht hat. Nur bestimmte Kombinationen sind gültig wie hier dokumentiert: http://developer.apple.com/qa/qa2001/qa1037.html

Wir hoffen, dass dies hilfreich ist!

+2

Dies ist hilfreich. Denken Sie daran, dass hier Ihre BytesPerRow nicht wide * bytesPerPixel sein darf. Zur Optimierung kann es auf 16-Byte-Grenzen aufgefüllt werden. Wenn Sie rowData durchlaufen, werden Sie, wenn Sie dies nicht berücksichtigen, Padding-Bytes als Pixeldaten verwenden. – mahboudz

+0

Das bedeutet auch, dass Ihre rawData, die Sie malloced haben, möglicherweise zu klein ist, um die Bitmap zu speichern, und möglicherweise ein Pufferüberlauf vorliegt. – mahboudz

+1

Ich frage mich, ob das ist, warum ich Schwierigkeiten habe, dies auf einem iPhone zu funktionieren, aber es funktioniert gut auf dem Simulator? http://stackoverflow.com/questions/7506248/alpha-detection-in-layer-ok-on-simulator-not-iphone –

36

Wenn Sie nur den Alpha-Wert eines einzelnen Punktes haben möchten, brauchen Sie nur einen Alpha-Punkt-Puffer. Ich glaube, das sollte ausreichen:

// assume im is a UIImage, point is the CGPoint to test 
CGImageRef cgim = im.CGImage; 
unsigned char pixel[1] = {0}; 
CGContextRef context = CGBitmapContextCreate(pixel, 
             1, 1, 8, 1, NULL, 
             kCGImageAlphaOnly); 
CGContextDrawImage(context, CGRectMake(-point.x, 
            -point.y, 
            CGImageGetWidth(cgim), 
            CGImageGetHeight(cgim)), 
       cgim); 
CGContextRelease(context); 
CGFloat alpha = pixel[0]/255.0; 
BOOL transparent = alpha < 0.01; 

Wenn die UIImage nicht über jedes Mal neu erstellt werden, das sehr effizient ist.

EDIT 8. Dezember 2011:

Ein Kommentator weist darauf hin, dass unter bestimmten Umständen das Bild umgedreht werden kann. Ich habe darüber nachgedacht, und es tut mir leid, dass ich den Code mit dem UIImage nicht direkt geschrieben habe, so (ich glaube der Grund dafür ist, dass ich zu diesem Zeitpunkt nichts über UIGraphicsPushContext verstanden habe):

// assume im is a UIImage, point is the CGPoint to test 
unsigned char pixel[1] = {0}; 
CGContextRef context = CGBitmapContextCreate(pixel, 
              1, 1, 8, 1, NULL, 
              kCGImageAlphaOnly); 
UIGraphicsPushContext(context); 
[im drawAtPoint:CGPointMake(-point.x, -point.y)]; 
UIGraphicsPopContext(); 
CGContextRelease(context); 
CGFloat alpha = pixel[0]/255.0; 
BOOL transparent = alpha < 0.01; 

Ich denke, das hätte das Flipping Problem gelöst.

+0

funktioniert wie ein Charme für mich! –

+3

Aus irgendeinem Grund musste ich die folgende Zeile für die 'CGContextDrawImage (...': 'CGContextDrawImage (Kontext, CGRectMake (-Point.x, - (image.size.height-point.y), CGImageGetWidth (cgim) , CGImageGetHeight (cgim)), cgim); ' –

+0

@MattLeff - Das macht Sinn, wenn Ihr Bild umgedreht wird, was leicht passieren kann, weil die Impedanz zwischen Core Graphics (wo der Ursprung unten ist) und UIKit (wo y Ursprung ist oben). – matt

Verwandte Themen