2009-06-25 5 views
35

Attualmente sto cercando di ottenere il valore alfa di un pixel in UIImageView. Ho ottenuto CGImage da [UIImageView image] e ho creato un array di byte RGBA da questo. Alpha è premoltiplicato.Recupero di un valore alfa pixel per una 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); 

Ho quindi calcolare l'indice di matrice per il canale alfa in base alle coordinate del UIImageView.

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

Tuttavia non ottengo i valori che mi aspetto. Per un'area completamente nera trasparente dell'immagine ottengo valori diversi da zero per il canale alfa. Devo tradurre le coordinate tra UIKit e Core Graphics - cioè: l'asse y è invertito? O ho frainteso i valori alfa premoltiplicati?

Aggiornamento:

suggerimento di @Nikolai Ruhe è stato fondamentale per questo. In realtà non avevo bisogno di tradurre tra coordinate UIKit e coordinate di Core Graphics. Tuttavia, dopo aver impostato il metodo di fusione miei valori alfa erano quello che mi aspettavo:

CGContextSetBlendMode(context, kCGBlendModeCopy); 
+0

Hey teabot - Mi rendo conto che questa domanda ha già una risposta, ma stavo facendo qualcosa di simile a questo qualche giorno fa. Invece di disegnare l'intera immagine e quindi indicizzarla nell'array di byte, dovresti disegnare solo 1px dell'immagine sorgente in un contesto bitmap 1px per 1pz. Puoi usare il parametro rect in CGContextDrawImage per inserire il pixel giusto, ed è come 1000 volte più veloce :-) –

+0

Grazie per il commento @Ben Gotow - Io disegno solo l'immagine una volta e poi continuo ad usare lo stesso array di byte. @Nikolai Ruhe ha anche suggerito il disegno a pixel singolo, ma ha detto che il mio approccio con array sarebbe stato più veloce se non avessi avuto bisogno di disegnare l'immagine più di una volta, ma dovessi cercare ripetutamente l'alfa. – teabot

+1

Solo un rapido aggiornamento (anni dopo) dopo l'utilizzo di questa e un'altra domanda qui per un problema simile: Il parametro rect su CGContextDrawImage viene utilizzato per controllare "La posizione e le dimensioni nello spazio utente del riquadro di delimitazione in cui disegnare l'immagine. " Quindi, se rendi quella dimensione 1x1, riduci l'intera immagine a 1x1 prima di disegnarla. Per ottenere il pixel giusto è necessario utilizzare CGContextTranslateCTM come nella risposta di Nikolai e lasciare la dimensione del rect uguale all'immagine sorgente per evitare il ridimensionamento. – roguenet

risposta

10

Sì, CGContexts hanno il loro asse y rivolto verso l'alto, mentre in UIKit punta verso il basso. See the docs.

Modifica dopo aver letto il codice:

Anche voi volete impostare il metodo di fusione per sostituire prima di disegnare l'immagine poiché si desidera dell'immagine valore alfa, non quello che era nel buffer del contesto prima:

CGContextSetBlendMode(context, kCGBlendModeCopy); 

Edit dopo aver pensato:

Si potrebbe fare la ricerca molto più efficiente costruendo il CGBitmapContext più piccolo possibile (1x1 pixel? forse 8x8? avere una prova) e la conversione del contesto nella posizione desiderata prima di trarre:

CGContextTranslateCTM(context, xOffset, yOffset); 
+0

@Nikolai Ruhe - Buona chiamata - avrei dovuto leggere i documenti. – teabot

+0

Il CGImageRef rappresenta un'immagine statica. Una volta ottenuto l'array di byte, butto via CGImage e quindi uso ripetutamente l'array di byte per eseguire la ricerca alfa. La tua ottimizzazione funzionerebbe in questo scenario? – teabot

+0

No, ovviamente avrai bisogno dell'immagine per disegnarla ripetutamente.Il mio approccio potrebbe essere più efficiente per poche ricerche. Se stai facendo molte ricerche rispetta il tuo codice. –

3

Ho bisogno di tradurre le coordinate tra UIKit e Core Graphics - cioè: è l'asse y invertita?

È possibile. In CGImage, i dati dei pixel sono in inglese in ordine di lettura: da sinistra a destra, dall'alto verso il basso. Quindi, il primo pixel dell'array è in alto a sinistra; il secondo pixel è uno da sinistra nella riga in alto; ecc.

Supponendo che tu abbia quel diritto, dovresti anche assicurarti di guardare il componente corretto all'interno di un pixel. Forse ti stai aspettando RGBA ma stai chiedendo ARGB o viceversa. O forse hai sbagliato l'ordine dei byte (non so quale sia l'endianità dell'iPhone).

Oppure ho frainteso valori alfa premoltiplicati?

Non sembra.

Per chi non lo sapesse: Premultiplied significa che i componenti del colore sono premoltiplicati dall'alfa; il componente alfa è lo stesso sia che i componenti del colore siano premoltiplicati o meno. È possibile invertire questo (in modo non moltiplicativo) dividendo le componenti del colore per l'alfa.

+0

@Peter Hosey - Grazie per chiarire come funziona l'alpha premoltiplicato - ero incerto. – teabot

3

Ho trovato questa domanda/risposta durante la ricerca su come eseguire il rilevamento di collisione tra sprite utilizzando il valore alfa dei dati dell'immagine, piuttosto che un rettangolo di selezione. Il contesto è un'app per iPhone ... Sto provando a fare il progetto di 1 pixel sopra suggerito e sto ancora avendo problemi a farlo funzionare, ma ho trovato un modo più semplice di creare un CGContextRef usando i dati dall'immagine stessa e il funzioni di supporto qui:

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

Questo bypassa tutti i brutti codec hard nell'esempio sopra. L'ultimo valore può essere recuperato chiamando CGImageGetBitmapInfo() ma nel mio caso restituisce un valore dall'immagine che ha causato un errore nella funzione ContextCreate. Solo alcune combinazioni sono valide come documentato qui: http://developer.apple.com/qa/qa2001/qa1037.html

Spero che questo sia utile!

+2

Questo è utile. Tieni a mente che, qui, BytesPerRow potrebbe non essere width * bytesPerPixel. Per l'ottimizzazione, può essere riempito con limiti di 16 byte. Mentre attraversi rawData, se non rendi conto di ciò, finirai per utilizzare i padding byte come dati pixel. – mahboudz

+0

Ciò significa anche che i rawData che hai spostato con il malloced potrebbero essere troppo piccoli per contenere la bitmap e potrebbe verificarsi un sovraccarico del buffer. – mahboudz

+1

Mi chiedo se questo è il motivo per cui ho difficoltà a far funzionare questo su un iPhone, ma funziona correttamente sul simulatore? http://stackoverflow.com/questions/7506248/alpha-detection-in-layer-ok-on-simulator-not-iphone –

36

Se tutto ciò che si desidera è il valore alfa di un singolo punto, tutto ciò che serve è un buffer a punto singolo alfa. Credo che questo dovrebbe essere sufficiente:

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

Se l'UIImage non deve essere ricreato ogni volta, questo è molto efficiente.

EDIT 8 dicembre 2011:

Un commentatore fa notare che, in determinate circostanze, l'immagine può essere capovolto. Ci stavo pensando, e sono un po 'dispiaciuto di non aver scritto il codice direttamente con UIImage, come questo (penso che il motivo sia che all'epoca non capivo di):

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

Penso che avrebbe risolto il problema di flipping.

+0

funziona come un incantesimo per me! –

+3

Per qualche motivo ho dovuto usare la seguente riga per 'CGContextDrawImage (...': 'CGContextDrawImage (context, CGRectMake (-point.x, - (image.size.height-point.y), CGImageGetWidth (cgim) , CGImageGetHeight (cgim)), cgim); ' –

+0

@MattLeff - Questo ha perfettamente senso se l'immagine è capovolta, cosa che può facilmente accadere a causa dell'impedenza di mismatch tra Core Graphics (dove l'origine è in basso) e UIKit (dove l'origine è in cima) – matt