2009-08-30 11 views

Respuesta

1

EDITAR: como notas de demianturner a continuación, ya no es necesario procesar la capa, puede (y debe) ahora utilizar el nivel superior [UIView drawViewHierarchyInRect:]. Aparte de eso; esto debería funcionar igual.

Un EAGLView es solo un tipo de vista, y su CAEAGLLayer subyacente es solo un tipo de capa. Eso significa que el standard approach para convertir una vista/capa en un UIImage funcionará. (El hecho de que la cuestión vinculada es UIWebView no importa, que es aún más que otro tipo de visión.)

+3

Gracias por la respuesta Rob, pero probé "Standard Me thod "y no funcionó. Aunque mi EAGLView muestra correctamente una textura cargada, el único UIImage que he podido extraer de él con el ENFOQUE ESTÁNDAR es uno con un color blanco en blanco, que es exactamente cómo aparece el EAGLView en IB. Esto es extraño, de hecho, al ver cómo EAGLView es solo una especie de UIView. Creo que tal vez necesito usar glReadPixels o algo en su lugar? Estoy trabajando con un EAGLView del ejemplo del Código de Muestra de Apple GLImageProcessing. – RexOnRoids

+0

Eh ... Realmente hablé demasiado rápido. OpenGL no se procesa de la misma manera que Quartz. Lo siento por eso. Creo que este hilo resolverá tu problema. Lee todos los mensajes; el trabaja varios errores en el camino. Supongo que ya sabes cómo manejar un CGContext y sacarle un CGImage (y un UIImage de eso). http://lists.apple.com/archives/mac-opengl/2006//jan/msg00085.html –

+0

Todas estas respuestas están desactualizadas e innecesariamente largas. La solución solo requiere 4 líneas de código UIKit, consulte 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

El código anterior tiene algunas filtraciones, por lo que recomiendo usar el código más eficiente proporcionado en la respuesta de Bram. –

+2

Seguí adelante e incorporé las correcciones de pérdida de memoria más críticas del código de Bram, en caso de que los futuros desarrolladores copien y peguen este código en sus propias aplicaciones. –

+0

¡Gracias por arreglar las filtraciones! –

9

Aquí es una versión limpia del código de Quakeboy. Lo probé en iPad y funciona muy bien. Las mejoras incluyen:

  • funciona con cualquier tamaño EAGLView
  • obras con pantalla de la retina (punto de escala 2)
  • reemplazados bucle anidado con memcpy
  • limpiado pérdidas de memoria
  • ataja el UIImage en el álbum de fotos como un extra.

utilizar esto como un método en su 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

Al código anterior le faltaron lanzamientos coincidentes para el espacio de color y el proveedor de datos, así que los agregué al final del método para evitar fugas. –

+0

Además, 'buffer2' de hecho necesita ser liberado manualmente para evitar una fuga. Esto también ha sido arreglado. –

+9

He intentado esto pero siempre crea una imagen negra sin dibujo, ¿qué puedo estar haciendo mal? – Brodie

6

que era incapaz de conseguir las otras respuestas aquí para trabajar correctamente para mí.

Después de unos días finalmente obtuve una solución de trabajo para esto. Hay un código proporcionado por Apple que produce un UIImage desde un EAGLView. Entonces simplemente necesita voltear la imagen verticalmente ya que UIkit está boca abajo.

Método proporcionado por Apple: se modifica para que esté dentro de la vista que desea hacer en una imagen.

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

}

Y aquí hay un método para voltear la imagen

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

}

Y aquí hay un enlace a la página dev de Apple, donde me encontré con el primer método de referencia. http://developer.apple.com/library/ios/#qa/qa1704/_index.html

+0

Nate, moví tu respuesta de esa pregunta (que era un duplicado de esta) aquí, para que la gente tuviera una fuente de acceso para las diversas formas de capturar desde un OpenGL Escena de ES –

+0

@Nate: ¿Hay alguna forma de saber si hay algún contenido garabateado en EAGLView? También estoy tratando de tomar una captura de pantalla de la vista que, como se describió anteriormente, funcionó, pero si el usuario toma una captura de pantalla o borra la pantalla y toma la captura de pantalla, se captura la pantalla vacía que quiero evitar. –

0

CGDataProviderCreateWithData viene con una devolución de llamada de liberación para liberar los datos, donde se debe hacer el lanzamiento:

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

Entonces hacer esto al igual que otros ejemplos, pero no a los datos de forma gratuita aquí:

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

O simplemente use CGDataProviderCreateWithCFData sin liberar la devolución de llamada en su lugar:

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 

Para obtener más información, por favor marque esta discutir:

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

+0

Esto es correcto; solo use devoluciones de llamada para liberar datos en contextos de Core Graphics. –

0

Con este código de Brad Larson, usted tiene que editar su 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; 
} 

Tienes que cambio numberWithBool valor YES