Drew
Drew

Reputation: 8963

GLPaint save image

I'm trying to develop a complex painting application on the iPhone. I'm currently drawing using Quartz (e.g. CGContext...). Unfortunately the Quartz overhead is just too slow for the type of drawing I'm doing, and I'm porting to OpenGL calls using the GLPaint example as a reference point.

Is there a way to get a UIImage/CGImage from the EAGLview class (the equivalent of Quartz's UIGraphicsGetImageFromCurrentImageContext)? Basically I need to save the pictures drawn by the GLPaint application.

Upvotes: 4

Views: 3643

Answers (5)

Patrick T Nelson
Patrick T Nelson

Reputation: 1254

This code will not leak memory like the above solution and accounts for dynamic view size as well as retina vs standard displays:

-(BOOL)iPhoneRetina{
    return ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] && ([UIScreen mainScreen].scale == 2.0))?YES:NO;
}

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

-(UIImage *) glToUIImage{

    int imageWidth, imageHeight;

    int scale = [self iPhoneRetina]?2:1;

    imageWidth = self.frame.size.width*scale;
    imageHeight = self.frame.size.height*scale;

    NSInteger myDataLength = imageWidth * imageHeight * 4;

    // allocate array and read pixels into it.
    GLubyte *buffer = (GLubyte *) malloc(myDataLength);
    glReadPixels(0, 0, imageWidth, imageHeight, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

    // make data provider with data.
    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, myDataLength, releasePixels);

    // prep the ingredients
    int bitsPerComponent = 8;
    int bitsPerPixel = 32;
    int bytesPerRow = 4 * imageWidth;
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo =  kCGImageAlphaPremultipliedLast;
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;

    // make the cgimage

    CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

    UIImage *myImage = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationDownMirrored]; //Render image flipped, since OpenGL's data is mirrored

    CGImageRelease(imageRef);
    CGColorSpaceRelease(colorSpaceRef);

    CGDataProviderRelease(provider);

    return myImage;
}

The others leak memory because the last parameter to CGDataProviderCreateWithData is supposed to be a function to free memory, and they also leave out the CGRelease functions.

Upvotes: 0

zlog
zlog

Reputation: 3316

Same as @Quakeboy's answer, but passing in the view so that the size can be dynamically determined (I used this for my universal app):

- (UIImage *)saveImageFromGLView:(UIView *)glView {
    int width = glView.frame.size.width;
    int height = glView.frame.size.height;

    NSInteger myDataLength = width * height * 4;
    // allocate array and read pixels into it.
    GLubyte *buffer = (GLubyte *) malloc(myDataLength);
    glReadPixels(0, 0, width, height, 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 < height; y++)
    {
        for(int x = 0; x < width * 4; x++)
        {
            buffer2[((height - 1) - y) * width * 4 + x] = buffer[y * 4 * width + 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 * width;
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
    // make the cgimage
    CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
    // then make the uiimage from that
    UIImage *myImage = [UIImage imageWithCGImage:imageRef];
    return myImage;
}

Upvotes: 7

new soul
new soul

Reputation: 322

void SaveScreenImage()

{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CGImageRef cgImage = UIGetScreenImage();
void *imageBytes = NULL;
if (cgImage == NULL) {
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
imageBytes = malloc(320 * 480 * 4);
CGContextRef context = CGBitmapContextCreate(imageBytes, 320, 480, 8, 320 * 4, colorspace, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorspace);
for (UIWindow *window in [[UIApplication sharedApplication] windows]) {
        CGRect bounds = [window bounds];
        CALayer *layer = [window layer];
        CGContextSaveGState(context);
        if ([layer contentsAreFlipped]) {
            CGContextTranslateCTM(context, 0.0f, bounds.size.height);
            CGContextScaleCTM(context, 1.0f, -1.0f);
        }
[layer renderInContext:(CGContextRef)context];
        CGContextRestoreGState(context);
    }
    cgImage = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
}
UIImage *image=[UIImage imageWithCGImage:cgImage];
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); 
[pool release];
}

This code will save as you see on the screen.But it maybe private api.

Upvotes: -1

Rajavanya Subramaniyan
Rajavanya Subramaniyan

Reputation: 2155

-(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];
    return myImage;
}

Upvotes: 15

Ben Gotow
Ben Gotow

Reputation: 14893

It's definitely possible. The trick is to use glReadPixels to pull the image data out of the OpenGL framebuffer into memory you can use. Once you have a pointer to the image data, you can use CGDataProviderCreateWithData and CGImageCreate to create a CGImage from the data. I'm working on an OpenGL-based drawing app that uses this technique a lot!

Upvotes: 3

Related Questions