2009-08-30 15 views

risposta

1

MODIFICA: come indicato nelle note di seguito, non è più necessario eseguire il rendering del livello, è possibile (e dovrebbe) utilizzare ora il livello superiore [UIView drawViewHierarchyInRect:]. Oltre a quello; questo dovrebbe funzionare allo stesso modo.

Un EAGLView è solo un tipo di vista e il suo sottostante CAEAGLLayer è solo una sorta di livello. Ciò significa che il standard approach per la conversione di una vista/livello in una UIImmagine funzionerà. (Il fatto che la questione collegata è UIWebView non importa, questo è solo un altro tipo di vista.)

+3

Grazie per la risposta Rob, ma ho provato il "Standard Me" thod "e non ha funzionato. Anche se EAGLView mostra correttamente una texture caricata su di esso, l'unica UIImage che ho potuto estrarre utilizzando l'APPROCCIO STANDARD è quella con un colore bianco vuoto, che è esattamente come appare EAGLView in IB. Questo è strano, infatti, visto come EAGLView è solo una specie di UIView. Penso che forse ho bisogno di usare glReadPixels o qualcosa invece? Sto lavorando con un EAGLView dall'esempio GLImageProcessing del codice di esempio Apple. – RexOnRoids

+0

Eh ... Ho davvero parlato troppo velocemente. OpenGL non rende allo stesso modo di Quartz. Mi dispiace per quello Credo che questo thread risolverà il tuo problema. Leggi tutti i messaggi; lui risolve diversi bug lungo la strada. Suppongo che tu sappia già come gestire un CGContext e ottenere un CGImage da esso (e una UIImage da quello). http://lists.apple.com/archives/mac-opengl/2006//jan/msg00085.html –

+0

Tutte queste risposte sono obsolete e inutilmente prolisso. La soluzione richiede solo 4 righe di codice UIKit, vedere https://developer.apple.com/library/content/qa/qa1817/_index.html – demianturner

6
-(UIImage *) saveImageFromGLView 
{ 
    NSInteger myDataLength = 320 * 480 * 4; 
    // allocate array and read pixels into it. 
    GLubyte *buffer = (GLubyte *) malloc(myDataLength); 
    glReadPixels(0, 0, 320, 480, GL_RGBA, GL_UNSIGNED_BYTE, buffer); 
    // gl renders "upside down" so swap top to bottom into new array. 
    // there's gotta be a better way, but this works. 
    GLubyte *buffer2 = (GLubyte *) malloc(myDataLength); 
    for(int y = 0; y <480; y++) 
    { 
     for(int x = 0; x <320 * 4; x++) 
     { 
      buffer2[(479 - y) * 320 * 4 + x] = buffer[y * 4 * 320 + x]; 
     } 
    } 
    // make data provider with data. 
    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer2, myDataLength, NULL); 
    // prep the ingredients 
    int bitsPerComponent = 8; 
    int bitsPerPixel = 32; 
    int bytesPerRow = 4 * 320; 
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); 
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; 
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; 
    // make the cgimage 
    CGImageRef imageRef = CGImageCreate(320, 480, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); 
    // then make the uiimage from that 
    UIImage *myImage = [UIImage imageWithCGImage:imageRef]; 

    CGImageRelease(imageRef); 
    CGDataProviderRelease(provider); 
    CGColorSpaceRelease(colorSpaceRef); 
    free(buffer2); 

    return myImage; 
} 
+0

Il codice sopra riportato presenta alcune perdite, quindi raccomando vivamente di utilizzare il codice più efficiente fornito nella risposta di Bram. –

+2

Sono andato avanti e ho incorporato le correzioni di perdite di memoria più importanti dal codice di Bram, nel caso in cui i futuri sviluppatori copiano e incollino questo codice nelle proprie applicazioni. –

+0

Grazie per aver risolto le perdite! –

9

Ecco una versione ripulita del codice di Quakeboy. L'ho provato su iPad e funziona perfettamente. I miglioramenti includono:

  • funziona con qualsiasi dimensione EAGLView
  • opere con display retina (punto bilancia 2)
  • sostituiti ciclo annidato con memcpy
  • ripulito memoria perdite
  • salva l'UIImage in il fotoalbum come bonus.

utilizzare questo come un metodo nella EAGLView:

-(void)snapUIImage 
{ 
    int s = 1; 
    UIScreen* screen = [ UIScreen mainScreen ]; 
    if ([ screen respondsToSelector:@selector(scale) ]) 
     s = (int) [ screen scale ]; 

    const int w = self.frame.size.width; 
    const int h = self.frame.size.height; 
    const NSInteger myDataLength = w * h * 4 * s * s; 
    // allocate array and read pixels into it. 
    GLubyte *buffer = (GLubyte *) malloc(myDataLength); 
    glReadPixels(0, 0, w*s, h*s, GL_RGBA, GL_UNSIGNED_BYTE, buffer); 
    // gl renders "upside down" so swap top to bottom into new array. 
    // there's gotta be a better way, but this works. 
    GLubyte *buffer2 = (GLubyte *) malloc(myDataLength); 
    for(int y = 0; y < h*s; y++) 
    { 
     memcpy(buffer2 + (h*s - 1 - y) * w * 4 * s, buffer + (y * 4 * w * s), w * 4 * s); 
    } 
    free(buffer); // work with the flipped buffer, so get rid of the original one. 

    // make data provider with data. 
    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer2, myDataLength, NULL); 
    // prep the ingredients 
    int bitsPerComponent = 8; 
    int bitsPerPixel = 32; 
    int bytesPerRow = 4 * w * s; 
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); 
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; 
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; 
    // make the cgimage 
    CGImageRef imageRef = CGImageCreate(w*s, h*s, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); 
    // then make the uiimage from that 
    UIImage *myImage = [ UIImage imageWithCGImage:imageRef scale:s orientation:UIImageOrientationUp ]; 
    UIImageWriteToSavedPhotosAlbum(myImage, nil, nil, nil); 
    CGImageRelease(imageRef); 
    CGDataProviderRelease(provider); 
    CGColorSpaceRelease(colorSpaceRef); 
    free(buffer2); 
} 
+0

Nel codice precedente mancavano le versioni corrispondenti per lo spazio colori e il fornitore di dati, quindi ho aggiunto quelle alla fine del metodo per evitare perdite. –

+0

Inoltre, 'buffer2' deve effettivamente essere liberato manualmente per evitare perdite. Anche questo è stato risolto. –

+9

Ho provato questo, ma crea sempre un'immagine nera senza disegno, cosa posso fare di sbagliato? – Brodie

6

mi è stato in grado di ottenere le altre risposte qui per lavorare correttamente per me.

Dopo alcuni giorni ho finalmente trovato una soluzione funzionante. C'è un codice fornito da Apple che produce un UIImage da un EAGLView. Quindi è sufficiente capovolgere l'immagine verticalmente poiché UIkit è capovolto.

Metodo fornito da Apple - Modificato per essere all'interno della vista che si desidera creare in un'immagine.

-(UIImage *) drawableToCGImage 
{ 
GLint backingWidth2, backingHeight2; 
//Bind the color renderbuffer used to render the OpenGL ES view 
// If your application only creates a single color renderbuffer which is already bound at this point, 
// this call is redundant, but it is needed if you're dealing with multiple renderbuffers. 
// Note, replace "_colorRenderbuffer" with the actual name of the renderbuffer object defined in your class. 
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); 

// Get the size of the backing CAEAGLLayer 
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth2); 
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight2); 

NSInteger x = 0, y = 0, width2 = backingWidth2, height2 = backingHeight2; 
NSInteger dataLength = width2 * height2 * 4; 
GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte)); 

// Read pixel data from the framebuffer 
glPixelStorei(GL_PACK_ALIGNMENT, 4); 
glReadPixels(x, y, width2, height2, GL_RGBA, GL_UNSIGNED_BYTE, data); 

// Create a CGImage with the pixel data 
// If your OpenGL ES content is opaque, use kCGImageAlphaNoneSkipLast to ignore the alpha channel 
// otherwise, use kCGImageAlphaPremultipliedLast 
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL); 
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); 
CGImageRef iref = CGImageCreate(width2, height2, 8, 32, width2 * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast, 
           ref, NULL, true, kCGRenderingIntentDefault); 

// OpenGL ES measures data in PIXELS 
// Create a graphics context with the target size measured in POINTS 
NSInteger widthInPoints, heightInPoints; 
if (NULL != UIGraphicsBeginImageContextWithOptions) { 
    // On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration 
    // Set the scale parameter to your OpenGL ES view's contentScaleFactor 
    // so that you get a high-resolution snapshot when its value is greater than 1.0 
    CGFloat scale = self.contentScaleFactor; 
    widthInPoints = width2/scale; 
    heightInPoints = height2/scale; 
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(widthInPoints, heightInPoints), NO, scale); 
} 
else { 
    // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext 
    widthInPoints = width2; 
    heightInPoints = height2; 
    UIGraphicsBeginImageContext(CGSizeMake(widthInPoints, heightInPoints)); 
} 

CGContextRef cgcontext = UIGraphicsGetCurrentContext(); 

// UIKit coordinate system is upside down to GL/Quartz coordinate system 
// Flip the CGImage by rendering it to the flipped bitmap context 
// The size of the destination area is measured in POINTS 
CGContextSetBlendMode(cgcontext, kCGBlendModeCopy); 
CGContextDrawImage(cgcontext, CGRectMake(0.0, 0.0, widthInPoints, heightInPoints), iref); 

// Retrieve the UIImage from the current context 
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 

UIGraphicsEndImageContext(); 

// Clean up 
free(data); 
CFRelease(ref); 
CFRelease(colorspace); 
CGImageRelease(iref); 

return image; 

}

Ed ecco un metodo per capovolgere l'immagine

- (UIImage *) flipImageVertically:(UIImage *)originalImage { 
UIImageView *tempImageView = [[UIImageView alloc] initWithImage:originalImage]; 
UIGraphicsBeginImageContext(tempImageView.frame.size); 
CGContextRef context = UIGraphicsGetCurrentContext(); 
CGAffineTransform flipVertical = CGAffineTransformMake(
                 1, 0, 0, -1, 0, tempImageView.frame.size.height 
                 ); 
CGContextConcatCTM(context, flipVertical); 

[tempImageView.layer renderInContext:context]; 

UIImage *flippedImage = UIGraphicsGetImageFromCurrentImageContext(); 
UIGraphicsEndImageContext(); 
//[tempImageView release]; 

return flippedImage; 

}

Ed ecco un link alla pagina dev di Apple dove ho trovato il primo metodo di riferimento. http://developer.apple.com/library/ios/#qa/qa1704/_index.html

+0

Nate, ho spostato la tua risposta da quella domanda (che era un duplicato di questo) qui, in modo che le persone avrebbero avuto una fonte di riferimento per i vari modi di catturare da un OpenGL Scena ES. –

+0

@Nate: C'è un modo per sapere se c'è qualche contenuto scarabocchiato su EAGLView. Sto anche provando a prendere screenshot della vista che, come descritto sopra, ha funzionato, ma se l'utente prende screenshot o cancella lo schermo e prende lo screenshot, viene catturato lo schermo vuoto che voglio evitare. –

0

CGDataProviderCreateWithData è dotato di un callback rilascio di rilasciare i dati, in cui si dovrebbe fare il rilascio:

void releaseBufferData(void *info, const void *data, size_t size) 
{ 
    free((void*)data); 
} 

poi fare questo, come altri esempi, ma non per i dati sulla qui:

GLubyte *bufferData = (GLubyte *) malloc(bufferDataSize); 
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bufferData, bufferDataSize, releaseBufferData); 
.... 
CGDataProviderRelease(provider); 

O semplicemente utilizzare CGDataProviderCreateWithCFData senza materiale di richiamata di rilascio invece:

GLubyte *bufferData = (GLubyte *) malloc(bufferDataSize); 
NSData *data = [NSData dataWithBytes:bufferData length:bufferDataSize]; 
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)data); 
.... 
CGDataProviderRelease(provider); 
free(bufferData); // Remember to free it 

Per ulteriori informazioni, si prega di controllare questo discutere:

What's the right memory management pattern for buffer->CGImageRef->UIImage?

+0

Questo è corretto; usa solo i callback per liberare i dati nei contesti Core Graphics. –

0

Con questo codice sopra di Brad Larson, è necessario modificare l'EAGLView.m

- (id)initWithCoder:(NSCoder*)coder{ 
    self = [super initWithCoder:coder]; 
    if (self) { 
     CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; 
     eaglLayer.opaque = TRUE; 
     eaglLayer.drawableProperties = 
      [NSDictionary dictionaryWithObjectsAndKeys: 
       [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, 
       kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil]; 
    } 
    return self; 
} 

Devi cambio numberWithBool valore YES