Annotations in screenshots


#1

Hi,

I’m using your screenshot code from the blog (http://www.shinobicontrols.com/blog/posts/2012/03/26/taking-a-shinobichart-screenshot-from-your-app/) and it works really well.  However, when I include annotations on my chart, they appear “in the background”.  Is there a way to fix that?

Thanks!


#2

Seems to me that you’ll have to carefully cut (or copy) the part of the UI snapshot (the one without the OpenGL content) out into a new temporary image, so that you can layer it on top of the GL content. But be careful to get the alpha channel right, or you will end up with a hidden chart in the screenshot :wink:

Actually, this is one thing thats missing from ShinobiCharts. A robust snapshot feature, that handles these kinds of details. The best would be if it also allowed for offscreen rendering and snapshotting, since there are many use cases for preview images of charts. *hint hint*


#3

Ok, thanks that makes sense.  Do you have any sample code for that?


#4

@pit_garbe is absolutely right. Our screenshot code from the blog you mentioned is including the annotations in chartImageView, which then gets the glImageView added ontop. You can add a loop to snapshot all the annotations just after the [chartImageView addSubview:glImageView] line and before where we “Turn our composite into a single image”. The following code seems to do the trick.

    //snapshot the annotations
    for (SChartAnnotation *ann in self.annotations) {
        if (ann.position == SChartAnnotationAboveData) {
            //if above gl, then add to top of glImageView (annotations under gl are already part of the chartImageView)
            if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
                UIGraphicsBeginImageContextWithOptions(ann.frame.size, NO, [UIScreen mainScreen].scale);
            } else {
                UIGraphicsBeginImageContext(ann.frame.size);
            }
            [ann.layer renderInContext:UIGraphicsGetCurrentContext()];
            UIImage *annotationImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            
            //Turn the annotation image into a view and add to glImageView
            UIImageView *annImageView = [[UIImageView alloc] initWithFrame:ann.frame];
            [annImageView setImage:annotationImage];
            [glImageView addSubview:annImageView];
        }
    }
 
 Hope the above helps!
 
 @pit_garbe - offscreen rendering and snapshotting is an interesting suggestion and I'll be sure to add it onto our list of things to look into.

#5

Perfect, thanks!


#6

I am just trying to take a screenshot and I keep getting an error. 

I followed the example perfectly. :o(

(12264,0xacd71a28) malloc: *** mmap(size=1972596736) failed (error code=12)

*** error: can’t allocate region

*** set a breakpoint in malloc_error_break to debug

Feb  5 12:15:05 mHager-W2OGroup.local redbull[12264] <Error>: CGImageCreate: invalid image provider: NULL.


#7

I am just trying to take a screenshot and I keep getting an error. 

I followed the example perfectly. :o(

(12264,0xacd71a28) malloc: *** mmap(size=1972596736) failed (error code=12)

*** error: can’t allocate region

*** set a breakpoint in malloc_error_break to debug

Feb  5 12:15:05 mHager-W2OGroup.local redbull[12264] <Error>: CGImageCreate: invalid image provider: NULL.


#8

Figured it out…

Coded to Add

@implementation SChartGLView (Screenshot)

   

  • (UIImage*)snapshot

{

**[**EAGLContextsetCurrentContext:_context];

    GLint backingWidth, backingHeight;

    

    // Bind the color renderbuffer used to render the OpenGL ES view

    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);

    

    // Get the size of the backing CAEAGLLayer

    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);

    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);

    

    NSInteger x = 0, y = 0, width = backingWidth, height = backingHeight;

    NSInteger dataLength = width * height * 4;

    GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));

    

    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);

    

    // Read pixel data from the framebuffer

    glPixelStorei(GL_PACK_ALIGNMENT, 4);

    glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);

    

    // Create a CGImage with the pixel data

    CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);

    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();

    CGImageRef iref = CGImageCreate(width, height, 8, 32, width * 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 = width / scale;

        heightInPoints = height / scale;

        UIGraphicsBeginImageContextWithOptions(CGSizeMake(widthInPoints, heightInPoints), NO, scale);

    }

    else {

        // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext

        widthInPoints = width;

        heightInPoints = height;

        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;

}