PDA

View Full Version : Screenshot using Cocoa



baerentzen
10-07-2013, 07:54 PM
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



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



}

baerentzen
10-28-2013, 08:59 AM
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.



// 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];

christopher
02-11-2014, 01:20 PM
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.



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