I am trying to get a UIImage from what is displayed in my EAGLView. Any suggestions on how to do this?
6 Answers
Here is a cleaned up version of Quakeboy's code. I tested it on iPad, and works just fine. The improvements include:
- works with any size EAGLView
- works with retina display (point scale 2)
- replaced nested loop with memcpy
- cleaned up memory leaks
- saves the UIImage in the photoalbum as a bonus.
Use this as a method in your 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);
}

- 170,088
- 45
- 397
- 571

- 7,440
- 3
- 52
- 94
-
The above code was missing matching releases for the colorspace and data provider, so I've added those at the end of the method to prevent leaks. – Brad Larson Nov 25 '11 at 21:26
-
Also, `buffer2` does indeed need to be freed manually to avoid a leak. This has also been fixed. – Brad Larson Nov 25 '11 at 21:50
-
9I have tried this but always creates a black image with no drawing, what may I be doing wrong? – Brodie Mar 24 '12 at 19:22
-
@Brodie Check to see if OpenGL generated an error with glGetError(). – dkaranovich May 29 '13 at 00:56
-
Hi Guys i am also facing this problems this methods return only a black image. please help me. – Mohit Tomar Mar 27 '14 at 08:40
-
Crashing on iOS8 SDK with Device iPad mini – gmuhammad Feb 21 '15 at 11:55
I was unable to get the other answers here to work correctly for me.
After a few days I finally got a working solution to this. There is code provided by Apple which produces a UIImage from a EAGLView. Then you simply need to flip the image vertically since UIkit is upside down.
Apple Provided Method - Modified to be inside the view you want to make into an image.
-(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;
}
And heres a method to flip the image
- (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;
}
And here's a link to the Apple dev page where I found the first method for reference. http://developer.apple.com/library/ios/#qa/qa1704/_index.html

- 170,088
- 45
- 397
- 571

- 671
- 1
- 6
- 12
-
Nate, I moved your answer from that question (which was a duplicate of this one) into here, so that people would have one go-to source for the various ways of capturing from an OpenGL ES scene. – Brad Larson Aug 08 '12 at 18:29
-
@Nate: Is there any way to know if there is any content scribbled on the EAGLView. I am also trying to take screenshot of the view which as described above worked but if user takes screenshot or clears the screen and takes the screenshot then empty screen is captured which i want to avoid. – Arun Gupta Mar 01 '16 at 17:07
-(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;
}

- 170,088
- 45
- 397
- 571

- 2,155
- 19
- 39
-
The above code has a few leaks in it, so I'd highly recommend using the more efficient code provided in Bram's answer instead. – Brad Larson Nov 25 '11 at 21:27
-
2I went ahead and incorporated the more critical memory leak fixes from Bram's code, just in case future developers copy and paste this code into their own applications. – Brad Larson Nov 25 '11 at 21:56
-
-
Hello guys, I don't know OpenGL and I don't understand, where in this code you indicate needed EAGLView ? – Igor Bidiniuc Dec 25 '12 at 01:48
-
I have GPUImageView and I can't get from it UIImage and I thought that simple way is to get image from it... How I can do this ? Please any suggestions. P.S. Brad, respect for framework (v) – Igor Bidiniuc Dec 25 '12 at 01:51
-
I'm having a problem with the free(buffer2) at the end which is causing a crash somewhere. – Christian J. B. May 25 '14 at 22:43
-
Using `[UIImage imageWithCGImage:imageRef]` causes `myImage` to depend on `buffer2`. That means `free(buffer2)` will cause memory corruption, since `myImage` is still needed later. I recommend drawing `imageRef` in a context, then getting the image from it. – Jeffrey Sun May 02 '15 at 20:25
-
Nope, this is the correct answer. I've used similar successfully. I've posted the code here: https://demonicactivity.blogspot.com/2016/11/tech-serious-ios-developers-use-every.html As to the memory issue, you'll see how I addressed that. Quite easy. – James Bush Nov 05 '16 at 06:42
EDIT: as demianturner notes below, you no longer need to render the layer, you can (and should) now use the higher-level [UIView drawViewHierarchyInRect:]
. Other than that; this should work the same.
An EAGLView
is just a kind of view, and its underlying CAEAGLLayer
is just a kind of layer. That means, that the standard approach for converting a view/layer into a UIImage will work. (The fact that the linked question is UIWebview doesn't matter; that's just yet another kind of view.)

- 1
- 1

- 286,113
- 34
- 456
- 610
-
3Thanks for the answer Rob, but I tried the "Standard Method" and it did not work. Even though my EAGLView is correctly displaying a texture loaded to it, the only UIImage I have been able to extract from it using the STANDARD APPROACH is one with a blank white color, which is exactly how the EAGLView appears in IB. This is strange, indeed, seeing how EAGLView is just a kind of UIView. I think maybe I need to use glReadPixels or something instead? I am working with an EAGLView from the Apple Sample Code GLImageProcessing example. – RexOnRoids Aug 30 '09 at 03:31
-
Eh... I really did speak too fast. OpenGL doesn't render the same way as Quartz. Sorry about that. I believe this thread will address your problem. Read through all the messages; he works out several bugs along the way. I'm assuming you already know how to deal with a CGContext and get a CGImage out of it (and a UIImage from that). http://lists.apple.com/archives/mac-opengl/2006//jan/msg00085.html – Rob Napier Aug 30 '09 at 04:09
-
1All these answers are out of date and unnecessarily long-winded. The solution only requires 4 lines of UIKit code, see https://developer.apple.com/library/content/qa/qa1817/_index.html – Demian Turner Apr 25 '17 at 16:42
-
@demianturner That looks exactly like the linked answer (under "the standard approach.") Is there something else you meant? – Rob Napier Apr 25 '17 at 18:49
-
Hi @RobNapier - yes my linked answer is different. This SA page wasted about 3 hours of my time. The linked answer (following link 'standard approach' which leads to http://stackoverflow.com/questions/858788/convert-uiwebview-contents-to-a-uiimage ) suggests you call UIView's `renderInContext` method. This is incorrect. What I linked to gives the right answer which is that the `drawViewHierarchyInRect` method is needed. All answers on this page, and I tried each one, sugges going down to OpenGL which is not necessary. – Demian Turner Apr 26 '17 at 08:29
CGDataProviderCreateWithData
comes with a release callback to release the data, where you should do the release:
void releaseBufferData(void *info, const void *data, size_t size)
{
free((void*)data);
}
Then do this like other examples, but NOT to free data here:
GLubyte *bufferData = (GLubyte *) malloc(bufferDataSize);
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bufferData, bufferDataSize, releaseBufferData);
....
CGDataProviderRelease(provider);
Or simply use CGDataProviderCreateWithCFData
without release callback stuff instead:
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
For more information, please check this discuss:
What's the right memory management pattern for buffer->CGImageRef->UIImage?
-
This is correct; only use callbacks for freeing data in Core Graphics contexts. – James Bush Nov 08 '16 at 00:11
With this above code of Brad Larson, you have to edit your 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;
}
You have to change numberWithBool
value YES

- 7,789
- 1
- 24
- 17

- 5,173
- 2
- 33
- 40