Friday, October 5, 2007

R 2.7.0 Quartz graphics device clipboard support

Tom Elliot recently posted a request to the R-SIG-Mac list looking for a way to programmatically put graphics onto the clipboard under OS X. Right now this is pretty hard, but I was inspired to patch R-devel's (the future 2.7.0) new Quartz device to allow for clipboard output.

After applying the patch, you can specify file="clipboard://" and when the device is closed (yes, you MUST close the graphics device to see output. It would probably be a good idea to write the file on a NewPage as well, come to think of it). Then you can wrap up a simple function to get copying of the current device to the clipboard (or you can simply open it directly).


copy.to.clipboard = function(dpi=300) { dev.copy(device=quartz,type="png",file="clipboard://",dpi=dpi);dev.close(); }


I've attached the patch for the brave souls willing to try it out:



Index: qdBitmap.c
===================================================================
--- qdBitmap.c (revision 43081)
+++ qdBitmap.c (working copy)
@@ -49,16 +49,45 @@
/* On 10.4+ we can employ the CGImageDestination API to create a
variety of different bitmap formats */
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
- CFURLRef path = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,(const UInt8*)qbd->path,strlen(qbd->path),FALSE);
- CFStringRef type = CFStringCreateWithBytes(kCFAllocatorDefault,(UInt8*)qbd->uti,strlen(qbd->uti),kCFStringEncodingUTF8,FALSE);
- CGImageDestinationRef dest = CGImageDestinationCreateWithURL(path,type,1,NULL);
- CGImageRef image = CGBitmapContextCreateImage(qbd->bitmap);
- CGImageDestinationAddImage(dest,image,NULL);
- CGImageDestinationFinalize(dest);
- CFRelease(image);
- CFRelease(dest);
- CFRelease(type);
+ CFStringRef pathString = CFStringCreateWithBytes(kCFAllocatorDefault,(UInt8*)qbd->path,strlen(qbd->path),kCFStringEncodingUTF8,FALSE);
+ CFURLRef path;
+ if(CFStringFind(pathString,CFSTR("://"),0).location != kCFNotFound) {
+ CFStringRef pathEscaped= CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,pathString,NULL,NULL,kCFStringEncodingUTF8);
+ path = CFURLCreateWithString(kCFAllocatorDefault,pathEscaped,NULL);
+ CFRelease(pathEscaped);
+ } else {
+ path = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,(const UInt8*)qbd->path,strlen(qbd->path),FALSE);
+ }
+ CFRelease(pathString);
+
+ CFStringRef scheme = CFURLCopyScheme(path);
+ CFStringRef type = CFStringCreateWithBytes(kCFAllocatorDefault,(UInt8*)qbd->uti,strlen(qbd->uti),kCFStringEncodingUTF8,FALSE);
+ CGImageRef image = CGBitmapContextCreateImage(qbd->bitmap);
+ if(CFStringCompare(scheme,CFSTR("file"),0) == 0) {
+ CGImageDestinationRef dest = CGImageDestinationCreateWithURL(path,type,1,NULL);
+ CGImageDestinationAddImage(dest,image,NULL);
+ CGImageDestinationFinalize(dest);
+ CFRelease(dest);
+ } else if(CFStringCompare(scheme,CFSTR("clipboard"),0) == 0) {
+ //Copy our image into data
+ CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault,0);
+ CGImageDestinationRef dest = CGImageDestinationCreateWithData(data,type,1,NULL);
+ CGImageDestinationAddImage(dest,image,NULL);
+ CGImageDestinationFinalize(dest);
+ CFRelease(dest);
+ PasteboardRef pb = NULL;
+ if(noErr == PasteboardCreate(kPasteboardClipboard,&pb)) {
+ PasteboardClear(pb);
+ PasteboardSyncFlags syncFlags = PasteboardSynchronize(pb);
+ PasteboardPutItemFlavor(pb,(PasteboardItemID)1,type,data,0);
+ }
+ CFRelease(data);
+ } else
+ warning("Not a supported scheme, no image data written.");
+ CFRelease(scheme);
+ CFRelease(type);
CFRelease(path);
+ CFRelease(image);
#endif
}
/* Free ourselves */

2 comments:

Anonymous said...

if(CFStringCompare(scheme,CFSTR("file"),0) == 0)

should be written as

if(CFEqual(scheme,CFSTR("file")))

Byron said...

What difference does it make?