Part of the Khronos Group
OpenGL.org

The Industry's Foundation for High Performance Graphics

from games to virtual reality, mobile phones to supercomputers

Results 1 to 3 of 3

Thread: Screenshot using Cocoa

  1. #1
    Junior Member Newbie
    Join Date
    Sep 2004
    Location
    Denmark
    Posts
    24

    Screenshot using Cocoa

    Hello,

    I am moving my code from GLUT to Cocoa (where I am a newbie), but I really like that GLUT on OSX allows you to copy a screenshot to the pasteboard just hitting cmd-c. The code below does that in a method belonging to a subclass of NSOpenGLView. It is largely a patchwork of ideas, which I found online.

    Q: Could this have been achieved simpler? In particular, without resorting to a low level for loop and perhaps just cloning the first image rather than constructing a new bitmap image? Spawning a new process, I could probably do it with just a few lines, but that would probably be a rather heavy operation.

    Thanks
    Andreas

    Code :
    -(IBAction)save_window_to_pasteboard:(id)sender
    {
     
        NSPasteboard *pb = [NSPasteboard generalPasteboard];
     
        // Telling the pasteboard what type of data we're going to send in
        [pb declareTypes:[NSArray arrayWithObjects:NSPasteboardTypePNG,nil] owner:self];
     
        NSRect backRect = [self convertRectToBacking: [self bounds]];
        NSSize sz;
        sz.width = NSWidth(backRect);
        sz.height = NSHeight(backRect);
     
        NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL
                                                                        pixelsWide: sz.width
                                                                        pixelsHigh: sz.height
                                                                     bitsPerSample: 8
                                                                   samplesPerPixel: 3
                                                                          hasAlpha: NO
                                                                          isPlanar: NO
                                                                    colorSpaceName: NSCalibratedRGBColorSpace
                                                                       bytesPerRow: 0                // indicates no empty bytes at row end
                                                                      bitsPerPixel: 0];
     
        glReadBuffer(GL_FRONT);
        int bytesPerRow = [rep bytesPerRow];
        glPixelStorei(GL_PACK_ROW_LENGTH, 8*bytesPerRow/[rep bitsPerPixel]);
        glReadPixels(0, 0, NSWidth(backRect), NSHeight(backRect), GL_RGB, GL_UNSIGNED_BYTE,  [rep bitmapData]);
     
        NSBitmapImageRep* flipped =  [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL
                                                                             pixelsWide: sz.width
                                                                             pixelsHigh: sz.height
                                                                          bitsPerSample: 8
                                                                        samplesPerPixel: 3
                                                                               hasAlpha: NO
                                                                               isPlanar: NO
                                                                         colorSpaceName: NSCalibratedRGBColorSpace
                                                                            bytesPerRow: 0                // indicates no empty bytes at row end
                                                                           bitsPerPixel: 0];
     
        for(int j=0; j< sz.height; ++j)
            for(int i=0;i<sz.width;++i)
            {
                NSUInteger pixels[4];
                [rep getPixel: pixels atX:i y:j];
                [flipped setPixel: pixels atX:i y:sz.height-j];
            }
     
        // Converting the representation to PNG and sending it to the pasteboard (with type indicated)
        [pb setData:[flipped representationUsingType:NSPNGFileType properties:nil] forType:NSPasteboardTypePNG];
     
     
     
    }

  2. #2
    Junior Member Newbie
    Join Date
    Sep 2004
    Location
    Denmark
    Posts
    24
    Hi Mac OpenGL Forum,

    Hmm the code for snapping a screenshot seems to work and nobody has posted something better, so I am sticking with that. however, there were a few issues, so I am posting a new version. The main problem was the good old one-off issue: I was not writing to the top row of the image. That has been fixed. Also, I am now telling both Cocoa and OpenGL that the image should be densely packed in memory. Previously it was padded. Maybe that has slight performance gains, but this code is more clear, I think.

    /Andreas

    PS: Looking at the screenshot in Preview, I find that it is ever so slightly less saturated. That is probably something that preview does but curious all the same.

    Code :
        // Get the pasteboard.
        NSPasteboard *pb = [NSPasteboard generalPasteboard];
     
        // Telling the pasteboard what type of data we're going to send in
        [pb declareTypes:[NSArray arrayWithObjects:NSPasteboardTypePNG,nil] owner:self];
     
        // Get the size of the image in a retina safe way
        NSRect backRect = [self convertRectToBacking: [self bounds]];
        int W = NSWidth(backRect);
        int H = NSHeight(backRect);
     
     
        // Create image. Note no alpha channel. I don't copy that.
        NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL
                                                                        pixelsWide: W
                                                                        pixelsHigh: H
                                                                     bitsPerSample: 8
                                                                   samplesPerPixel: 3
                                                                          hasAlpha: NO
                                                                          isPlanar: NO
                                                                    colorSpaceName: NSCalibratedRGBColorSpace
                                                                       bytesPerRow: 3*W
                                                                      bitsPerPixel: 0];
     
        // The following block does the actual reading of the image
        glPushAttrib(GL_PIXEL_MODE_BIT); // Save state about reading buffers
        glReadBuffer(GL_FRONT);
        glPixelStorei(GL_PACK_ALIGNMENT, 1); // Dense packing
        glReadPixels(0, 0, W, H, GL_RGB, GL_UNSIGNED_BYTE, [rep bitmapData]);
        glPopAttrib();
     
        // So we need one more image, since we must flip its orientation.
        NSBitmapImageRep* flipped =  [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL
                                                                             pixelsWide: W
                                                                             pixelsHigh: H
                                                                          bitsPerSample: 8
                                                                        samplesPerPixel: 3
                                                                               hasAlpha: NO
                                                                               isPlanar: NO
                                                                         colorSpaceName: NSCalibratedRGBColorSpace
                                                                            bytesPerRow: 3*W
                                                                           bitsPerPixel: 0];
     
        // Primitive double for loop flipping the row order. Should be a better way. Can't find it.
        for(int j=1; j< H+1; ++j)
            for(int i=0;i<W;++i)
            {
                NSUInteger pixels[4];
                [rep getPixel: pixels atX:i y:j];
                [flipped setPixel: pixels atX:i y:H-j];
            }
     
        // Converting the representation to PNG and sending it to the pasteboard (with type indicated)
        [pb setData:[flipped representationUsingType:NSPNGFileType properties:nil] forType:NSPasteboardTypePNG];

  3. #3
    Newbie Newbie
    Join Date
    Feb 2014
    Posts
    1

    Another solution

    Thanks for your code. It works a treat. I did find two ways to get this to work without the for loops, presumably using vector transforms in the CPU or GPU. One way is to use an NSImage with a "setFlipped" property, but it appears this has been deprecated since OSX 10.6. The other option is to use Core Image. I show this below. Here the user can specify to either write to a PNG image or to the clipboard.

    Code :
    - (void)saveScreenshotFromFileName:(NSString *) file_name //save PNG screenshot
    {
        // Get the size of the image in a retina safe way
        NSRect backRect = [self convertRectToBacking: [self bounds]];
        int W = NSWidth(backRect);
        int H = NSHeight(backRect);
        // Create image. Note no alpha channel. I don't copy that.
        NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL
            pixelsWide: W pixelsHigh: H bitsPerSample: 8 samplesPerPixel: 3 hasAlpha: NO
            isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 3*W bitsPerPixel: 0];
        // The following block does the actual reading of the image
        glPushAttrib(GL_PIXEL_MODE_BIT); // Save state about reading buffers
        glReadBuffer(GL_FRONT);
        glPixelStorei(GL_PACK_ALIGNMENT, 1); // Dense packing
        glReadPixels(0, 0, W, H, GL_RGB, GL_UNSIGNED_BYTE, [rep bitmapData]);
        glPopAttrib();
        CIImage* ciimag = [[CIImage alloc] initWithBitmapImageRep: rep];
        CGAffineTransform trans = CGAffineTransformIdentity;
        trans = CGAffineTransformMakeTranslation(0.0f, H);
        trans = CGAffineTransformScale(trans, 1.0, -1.0);
        ciimag = [ciimag imageByApplyingTransform:trans];
        rep = [[NSBitmapImageRep alloc] initWithCIImage: ciimag];
        if ([file_name length] < 1) { //save to clipboard
            NSImage *imag = [[[NSImage alloc] init] autorelease];
            [imag addRepresentation:rep];
            NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
            [pasteboard clearContents];
            NSArray *copiedObjects = [NSArray arrayWithObject:imag];
            [pasteboard writeObjects:copiedObjects];
            return;
        }
        NSData *data = [rep representationUsingType: NSPNGFileType properties: nil];
        [data writeToFile: file_name atomically: NO];
    }

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •