Render to Mac OS X clipboard from GLUT OpenGL app

Alec Jacobson

January 25, 2014

weblog/

Here's a proof of concept application that renders the current viewport to the mac os x clipboard. Then you can paste into Preview.app or Photoshop etc. Save the following in render_to_clipboard.m:

#include <GLUT/GLUT.h>
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#include <stdlib.h>
#include <unistd.h>

void reshape(int width,int height)
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glViewport(0,0,width,height);
  gluPerspective(40, (double)width/height, 1, 10);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0,0,3, 0,0,0, 0,1,0);
  glTranslatef(0, 0.0f, -1);
}

bool buffer_to_clipboard(
  const GLubyte * pixels, 
  const int w, 
  const int h, 
  const int components)
{
  const int length = w*h*components;
  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  // http://stackoverflow.com/a/3416891/148668
  size_t bufferLength = w * h * components;
  CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixels, bufferLength, NULL);
  size_t bitsPerComponent = 8;
  size_t bitsPerPixel = bitsPerComponent * components;
  size_t bytesPerRow = components * w;
  CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
  CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast;
  CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;

  CGImageRef iref = CGImageCreate(
    w, 
    h, 
    bitsPerComponent, 
    bitsPerPixel, 
    bytesPerRow, 
    colorSpaceRef, 
    bitmapInfo, 
    provider,   // data provider
    NULL,       // decode
    YES,        // should interpolate
    renderingIntent);

  NSImage * temp_image = [[NSImage alloc] initWithCGImage:iref size:NSMakeSize(w, h)];

  // Painfully flip image across x-axis
  NSSize newSize = NSMakeSize(w,h);
  NSImage *image = [[NSImage alloc] initWithSize:newSize];
  [image lockFocus];
  NSAffineTransform *flip = [NSAffineTransform transform];
  [flip translateXBy: 0 yBy: h];
  [flip scaleXBy:1 yBy:-1];
  [flip concat];
  NSRect r1 = NSMakeRect(0, 0, w,h);
  [[temp_image bestRepresentationForRect:r1 context:nil hints:nil] drawInRect: r1];
  [image unlockFocus];

  BOOL copied = false;
  if (image != nil)
  {
    NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
    [pasteboard clearContents];
    NSArray *copiedObjects = [NSArray arrayWithObject:image];
    copied = [pasteboard writeObjects:copiedObjects];
    [pasteboard release];
  }
  [image release];
  [pool release];
  return true;
}

bool render_to_clipboard()
{
  GLenum format = GL_RGBA;
  int components = 4;
  int VP[4];
  glGetIntegerv(GL_VIEWPORT,VP);
  // OpenGL by default tries to read data in multiples of 4, if our data is
  // only RGB or BGR and the width is not divible by 4 then we need to alert
  // opengl
  if((VP[2] % 4) != 0 && 
   (format == GL_RGB || 
    format == GL_BGR))
  {
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
  }
  GLubyte *pixels;
  pixels = (unsigned char *) malloc (VP[2]* VP[3]* components);
  glReadPixels( VP[0], VP[1], VP[2], VP[3], format, GL_UNSIGNED_BYTE, pixels);
  return buffer_to_clipboard(pixels, VP[2], VP[3], components);
}

void display()
{
  glClearColor(1,0,0,1);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glutWireTeapot(1.0);
  glutSwapBuffers();
  render_to_clipboard();
}

void exitCB()
{
  exit(0);
}

int main(int argc, char * argv[])
{
  glutInit(&argc,argv);
  glutInitDisplayString( "rgba depth double samples>=8 ");
  glutInitWindowSize(401,301);
  glutCreateWindow("test");
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  // Render once and quit
  glutIdleFunc(exitCB);
  glutMainLoop();
  return 0;
}

Then you can compile with

cc -o render_to_clipboard render_to_clipboard.m -framework GLUT -framework OpenGL -lobjc -framework Foundation -framework AppKit

And run with:

./render_to_clipboard

This will flash a GLUT window, render once and quit. You can then paste your frame, which should be:

render teaport to mac os x clipboard