浏览代码

quicklook example

Former-commit-id: 085991b08220b6521bce2e71b75b39d84c2aa010
Alec Jacobson (jalec 11 年之前
父节点
当前提交
41efd576ca

+ 125 - 0
examples/quicklook-mesh/Info.plist

@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleDocumentTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleTypeRole</key>
+			<string>QLGenerator</string>
+			<key>LSItemContentTypes</key>
+			<array>
+				<string>org.mesh</string>
+				<string>org.obj</string>
+				<string>org.off</string>
+			</array>
+		</dict>
+	</array>
+	<key>CFBundleExecutable</key>
+	<string>Mesh</string>
+	<key>CFBundleIconFile</key>
+	<string></string>
+	<key>CFBundleIdentifier</key>
+	<string>org.mesh.quicklook</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>Mesh</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.1</string>
+	<key>CFBundleVersion</key>
+	<string>1.2</string>
+	<key>CFPlugInDynamicRegisterFunction</key>
+	<string></string>
+	<key>CFPlugInDynamicRegistration</key>
+	<false/>
+	<key>CFPlugInFactories</key>
+	<dict>
+		<key>C00EB589-55DF-45ED-B4B8-8AFFA367CFCF</key>
+		<string>QuickLookGeneratorPluginFactory</string>
+	</dict>
+	<key>CFPlugInTypes</key>
+	<dict>
+		<key>5E2D9680-5022-40FA-B806-43349622E5B9</key>
+		<array>
+			<string>C00EB589-55DF-45ED-B4B8-8AFFA367CFCF</string>
+		</array>
+	</dict>
+	<key>CFPlugInUnloadFunction</key>
+	<string></string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright 2013 Alec Jacobson</string>
+	<key>QLNeedsToBeRunInMainThread</key>
+	<true/>
+	<key>QLPreviewHeight</key>
+	<real>405</real>
+	<key>QLPreviewWidth</key>
+	<real>720</real>
+	<key>QLSupportsConcurrentRequests</key>
+	<true/>
+	<key>QLThumbnailMinimumSize</key>
+	<integer>32</integer>
+	<key>UTImportedTypeDeclarations</key>
+	<array>
+		<dict>
+			<key>UTTypeConformsTo</key>
+			<array>
+				<string>public.image</string>
+			</array>
+			<key>UTTypeDescription</key>
+			<string>3d tetrahedral mesh</string>
+			<key>UTTypeIdentifier</key>
+			<string>org.mesh</string>
+			<key>UTTypeReferenceURL</key>
+			<string>http://www.ann.jussieu.fr/frey/publications/RT-0253.pdf#page=33</string>
+			<key>UTTypeTagSpecification</key>
+			<dict>
+				<key>public.filename-extension</key>
+				<array>
+					<string>mesh</string>
+				</array>
+			</dict>
+		</dict>
+		<dict>
+			<key>UTTypeConformsTo</key>
+			<array>
+				<string>public.image</string>
+			</array>
+			<key>UTTypeDescription</key>
+			<string>3D model format</string>
+			<key>UTTypeIdentifier</key>
+			<string>org.obj</string>
+			<key>UTTypeReferenceURL</key>
+			<string>http://en.wikipedia.org/wiki/Wavefront_.obj_file#File_format</string>
+			<key>UTTypeTagSpecification</key>
+			<dict>
+				<key>public.filename-extension</key>
+				<array>
+					<string>obj</string>
+				</array>
+			</dict>
+		</dict>
+		<dict>
+			<key>UTTypeConformsTo</key>
+			<array>
+				<string>public.image</string>
+			</array>
+			<key>UTTypeDescription</key>
+			<string>Geomview's polyhedral file format</string>
+			<key>UTTypeIdentifier</key>
+			<string>org.off</string>
+			<key>UTTypeReferenceURL</key>
+			<string>http://tetgen.berlios.de/fformats.off.html</string>
+			<key>UTTypeTagSpecification</key>
+			<dict>
+				<key>public.filename-extension</key>
+				<array>
+					<string>off</string>
+				</array>
+			</dict>
+		</dict>
+	</array>
+</dict>
+</plist>

+ 82 - 0
examples/quicklook-mesh/Makefile

@@ -0,0 +1,82 @@
+.PHONY: all, install
+
+# Seems that we unfortunately must use llvm or else we get issues trying to
+# include the Foundation headers
+CXX=llvm-g++
+C=llvm-gcc
+
+EIGEN=/opt/local/include/eigen3/
+EIGEN3_INC=-I$(EIGEN) -I$(EIGEN)/unsupported
+
+LIBIGL=/usr/local/igl/libigl/
+LIBIGL_LIB=-L$(LIBIGL)/lib -ligl -liglmatlab
+LIBIGL_INC=-I $(LIBIGL)/include
+
+# Do not use the GLU that comes with the macports Mesa:
+# http://www.alecjacobson.com/weblog/?p=2827
+GLU=/usr/local/
+GLU_INC=-I$(GLU)/include
+GLU_LIB=-L$(GLU)/lib -lGLU
+
+MESA=/opt/local/
+MESA_INC=-I$(MESA)/include
+MESA_LIB=-L$(MESA)/lib -lOSMesa -lGL
+
+OBJC_LIB=-lobjc
+
+all: obj Mesh.qlgenerator
+install:
+	rm -rf /Library/QuickLook/Mesh.qlgenerator
+	cp -R Mesh.qlgenerator /Library/QuickLook/Mesh.qlgenerator
+	qlmanage -r
+	qlmanage -r cache
+
+#CFLAGS += -Wall -g -O0
+# openmp is unfortunately not supported by llvm
+CFLAGS += -O3 -Wall -DNDEBUG -Winvalid-pch -m64 -msse4.2
+
+CPP_FILES=$(wildcard src/*.cpp)
+C_FILES=$(wildcard src/*.c)
+M_FILES=$(wildcard src/*.m)
+OBJ_FILES=$(addprefix obj/,$(notdir $(CPP_FILES:.cpp=.o))) \
+  $(addprefix obj/,$(notdir $(M_FILES:.m=.o))) \
+  $(addprefix obj/,$(notdir $(C_FILES:.c=.o)))
+
+LIB+=$(LIBIGL_LIB) $(GLU_LIB) $(MESA_LIB) $(OBJC_LIB) -framework Foundation \
+  -framework AppKit -framework QuickLook
+INC+=$(EIGEN3_INC) $(LIBIGL_INC) $(GLU_INC) $(MESA_INC)
+
+.PHONY:
+
+Mesh.qlgenerator: Mesh.qlgenerator/Contents/MacOS/ \
+  Mesh.qlgenerator/Contents/Resources \
+  Mesh.qlgenerator/Contents/MacOS/Mesh \
+  Mesh.qlgenerator/Contents/Info.plist
+
+Mesh.qlgenerator/Contents/Info.plist: Info.plist
+	cp Info.plist Mesh.qlgenerator/Contents/Info.plist
+
+Mesh.qlgenerator/Contents/MacOS/:
+	mkdir -p $@
+
+Mesh.qlgenerator/Contents/Resources/:
+	mkdir -p $@
+
+Mesh.qlgenerator/Contents/MacOS/Mesh: $(OBJ_FILES)
+	${CXX} $(CFLAGS) -bundle -o $@ $(OBJ_FILES) $(LIB)
+
+obj:
+	mkdir -p obj
+
+obj/%.o: src/%.cpp src/%.h
+	${CXX} $(CFLAGS) -o $@ -c $< $(INC) 
+
+obj/%.o: src/%.m
+	${CXX} $(CFLAGS) -o $@ -c $< $(INC)
+
+obj/%.o: src/%.c
+	${C} $(CFLAGS) -o $@ -c $< $(INC)
+
+clean:
+	rm -f $(OBJ_FILES)
+	rm -rf Mesh.qlgenerator

+ 17 - 0
examples/quicklook-mesh/README

@@ -0,0 +1,17 @@
+This is a small application which builds a "QuickLook generator" for Mac OS.
+This will generate previews (selecting a file in Finder and hitting space) and
+thumbnails ("Cover flow" view in Finder) for .obj, .off and .mesh 3D model
+files.
+
+To build issue:
+
+    make
+
+This will compile the source and build the application in ./Mesh.qlgenerator/
+
+To install issue:
+
+    sudo make install
+
+This will copy the directory ./Mesh.qlgenerator/ into /Library/QuickLook/ and
+reload the Quicklook Manager (so that you see your changes in Finder).

+ 93 - 0
examples/quicklook-mesh/src/GeneratePreviewForURL.m

@@ -0,0 +1,93 @@
+// Adapted from QuickLook-pfm-master/src/GeneratePreviewForURL.m by Andreas
+// Steinel (https://github.com/lnxbil/quicklook-pfm)
+#import <CoreFoundation/CoreFoundation.h>
+#import <CoreServices/CoreServices.h>
+#import <QuickLook/QuickLook.h>
+#import <Foundation/Foundation.h>
+#import <AppKit/NSImage.h>
+
+#include "render_to_buffer.h"
+
+// Generate a preview of a given file.
+OSStatus GeneratePreviewForURL(
+  void *thisInterface, 
+  QLPreviewRequestRef preview, 
+  CFURLRef url, 
+  CFStringRef contentTypeUTI, 
+  CFDictionaryRef options)
+{
+    if (QLPreviewRequestIsCancelled(preview))
+        return noErr;
+    
+    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+    
+    CFStringRef file = CFURLCopyFileSystemPath(url,kCFURLPOSIXPathStyle);
+    if (QLPreviewRequestIsCancelled(preview))
+        return noErr;
+    // http://stackoverflow.com/a/3797923/148668
+    // Multi-sampling for anti-aliasing.
+    const double MS = 2;
+    const int width = 720;
+    const int height = 405;
+    const int bwidth = MS*width;
+    const int bheight = MS*height;
+    const int bchannels = 4;
+    UInt8 buffer[bwidth * bheight * bchannels];
+
+    const char *cs = CFStringGetCStringPtr( file, CFStringGetSystemEncoding()) ;
+    char cs_buf[1024];
+    // http://stackoverflow.com/a/1609664/148668
+    if(cs == NULL)
+    {
+      CFStringGetCString(
+        file, 
+        cs_buf, 
+        [(NSString*)file length]+1, 
+        kCFStringEncodingUTF8);
+      cs = cs_buf;
+    }
+
+    render_to_buffer(cs,bwidth,bheight,buffer);
+    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
+    CFDataRef rgbData = CFDataCreate(NULL, buffer, bwidth * bheight * 4);
+    CGDataProviderRef provider = CGDataProviderCreateWithCFData(rgbData);
+    // http://forum.sparrow-framework.org/topic/create-uiimage-from-pixel-data-problems
+    CGImageRef image = 
+      CGImageCreate(
+        bwidth, 
+        bheight, 
+        8, 
+        8*4, 
+        bwidth * 4, 
+        colorspace, 
+        kCGBitmapByteOrderDefault|kCGImageAlphaPremultipliedLast, 
+        provider, 
+        NULL, 
+        true, 
+        kCGRenderingIntentDefault); 
+    CFRelease(rgbData);
+    CGDataProviderRelease(provider);
+    CGColorSpaceRelease(colorspace);
+    NSSize size = {width,height};
+
+    if (QLPreviewRequestIsCancelled(preview))
+        return noErr;
+
+    // Draw onto context as textured rectangle
+    CGContextRef cgContext = QLPreviewRequestCreateContext(preview, *(CGSize *)&size, true, NULL);
+    CGRect rect = CGRectMake(0,0, width, height);
+    CGContextDrawImage(cgContext, rect, image);
+    QLPreviewRequestFlushContext(preview, cgContext);
+
+    CFRelease(cgContext);
+    CGImageRelease(image);
+    [pool release];
+    return noErr;
+}
+
+
+void CancelPreviewGeneration(void* thisInterface, QLPreviewRequestRef preview)
+{
+    // implement only if supported
+}
+

+ 163 - 0
examples/quicklook-mesh/src/GenerateThumbnailForURL.m

@@ -0,0 +1,163 @@
+// Adapted from QuickLook-pfm-master/src/GenerateThumbnailForURL.m by Andreas
+// Steinel (https://github.com/lnxbil/quicklook-pfm)
+#import <CoreFoundation/CoreFoundation.h>
+#import <CoreServices/CoreServices.h>
+#import <QuickLook/QuickLook.h>
+#import <Foundation/Foundation.h>
+#import <AppKit/NSImage.h>
+
+#include "render_to_buffer.h"
+
+// Creates a bitmap context for ARGB images (not sure if we really need a
+// separate function for this... I think there should be something builtin that
+// works just as well)
+// Taken from https://github.com/lnxbil/quicklook-pfm
+CGContextRef CreateARGBBitmapContext (CGSize size)
+{
+    CGContextRef    context = NULL;
+    CGColorSpaceRef colorSpace;
+    void *          bitmapData;
+    int             bitmapByteCount;
+    int             bitmapBytesPerRow;
+    
+    // Get image width, height. We'll use the entire image.
+    size_t pixelsWide = (size_t) size.width; //CGImageGetWidth(inImage);
+    size_t pixelsHigh = (size_t) size.height; //CGImageGetHeight(inImage);
+    
+    // Declare the number of bytes per row. Each pixel in the bitmap in this
+    // example is represented by 4 bytes; 8 bits each of red, green, blue, and
+    // alpha.
+    bitmapBytesPerRow   = (pixelsWide * 4);
+    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);
+    
+    // Use the generic RGB color space.
+    // FIXME: Das verstehe ich net. Die Doku sagt nicht, dass es Deprecated ist und
+    //        gibt auch keine Auswahlmöglichkeit an :-(
+    colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
+    if (colorSpace == NULL)
+    {
+        NSLog(@"Error allocating color space\n");
+        return NULL;
+    }
+    
+    // Allocate memory for image data. This is the destination in memory
+    // where any drawing to the bitmap context will be rendered.
+    bitmapData = malloc( bitmapByteCount );
+    if (bitmapData == NULL) 
+    {
+        NSLog(@"Memory not allocated!");
+        CGColorSpaceRelease( colorSpace );
+        return NULL;
+    }
+    
+    // Create the bitmap context. We want pre-multiplied ARGB, 8-bits 
+    // per component. Regardless of what the source image format is 
+    // (CMYK, Grayscale, and so on) it will be converted over to the format
+    // specified here by CGBitmapContextCreate.
+    context = CGBitmapContextCreate (bitmapData,
+                                     pixelsWide,
+                                     pixelsHigh,
+                                     8,      // bits per component
+                                     bitmapBytesPerRow,
+                                     colorSpace,
+                                     kCGBitmapByteOrderDefault|kCGImageAlphaPremultipliedLast);
+    if (context == NULL)
+    {
+        free (bitmapData);
+        NSLog(@"Context not created!");
+    }
+    
+    // Make sure and release colorspace before returning
+    CGColorSpaceRelease( colorSpace );
+    
+    return context;
+}
+
+
+
+
+// Generate a thumbnail for a given file
+OSStatus GenerateThumbnailForURL(
+  void *thisInterface, 
+  QLThumbnailRequestRef thumbnail, 
+  CFURLRef url, 
+  CFStringRef contentTypeUTI, 
+  CFDictionaryRef options, 
+  CGSize maxSize)
+{
+    if (QLThumbnailRequestIsCancelled(thumbnail))
+        return noErr;
+    
+    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+    
+    CFStringRef file = CFURLCopyFileSystemPath(url,kCFURLPOSIXPathStyle);
+    
+    if (QLThumbnailRequestIsCancelled(thumbnail))
+        return noErr;
+
+    // raw pixel data memory of 64 * 64 pixel size
+    // http://stackoverflow.com/a/3797923/148668
+    // Multi-sampling for anti-aliasing
+    const double MS = 2;
+    const int width = maxSize.width;
+    // Widescreen aspect ratio
+    const int height = (270./460.)*maxSize.height;
+    const int bwidth = MS*width;
+    const int bheight = MS*height;
+    const int bchannels = 4;
+    UInt8 buffer[bwidth * bheight * bchannels];
+    const char *cs = CFStringGetCStringPtr( file, CFStringGetSystemEncoding()) ;
+    char cs_buf[1024];
+    // http://stackoverflow.com/a/1609664/148668
+    if(cs == NULL)
+    {
+      CFStringGetCString(file, cs_buf, [(NSString*)file length]+1, kCFStringEncodingUTF8);
+      cs = cs_buf;
+    }
+    render_to_buffer(cs,bwidth,bheight,buffer);
+    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
+    CFDataRef rgbData = CFDataCreate(NULL, buffer, bwidth * bheight * 4);
+    CGDataProviderRef provider = CGDataProviderCreateWithCFData(rgbData);
+    // http://forum.sparrow-framework.org/topic/create-uiimage-from-pixel-data-problems
+    CGImageRef image = CGImageCreate(
+      bwidth,
+      bheight,
+      8,
+      8*4,
+      bwidth * 4,
+      colorspace,
+      kCGBitmapByteOrderDefault|kCGImageAlphaPremultipliedLast,
+      provider,
+      NULL,
+      true,
+      kCGRenderingIntentDefault); 
+    CFRelease(rgbData);
+    CGDataProviderRelease(provider);
+    CGColorSpaceRelease(colorspace);
+    NSSize size = {width,height};
+    
+    if (QLThumbnailRequestIsCancelled(thumbnail))
+        return noErr;
+
+    // Draw onto context as textured rectangle
+    CGContextRef cgctx = CreateARGBBitmapContext(size);
+    CGRect rect = CGRectMake(0,0, width, height);
+    CGContextClearRect(cgctx,rect);
+    CGContextDrawImage(cgctx, rect, image); 
+    CGImageRef newCGImage = CGBitmapContextCreateImage(cgctx);
+    CGContextRelease(cgctx);
+    
+    QLThumbnailRequestSetImage(thumbnail, newCGImage, NULL);
+    
+    // Releasing image
+    CGImageRelease(newCGImage);
+    [pool release];
+    return noErr;
+}
+
+
+void CancelThumbnailGeneration(void* thisInterface, QLThumbnailRequestRef thumbnail)
+{
+    // implement only if supported
+}
+

+ 218 - 0
examples/quicklook-mesh/src/main.c

@@ -0,0 +1,218 @@
+//==============================================================================
+//
+//	DO NO MODIFY THE CONTENT OF THIS FILE
+//
+//	This file contains the generic CFPlug-in code necessary for your generator
+//	To complete your generator implement the function in GenerateThumbnailForURL/GeneratePreviewForURL.c
+//
+//==============================================================================
+
+
+
+
+
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreFoundation/CFPlugInCOM.h>
+#include <CoreServices/CoreServices.h>
+#include <QuickLook/QuickLook.h>
+
+// -----------------------------------------------------------------------------
+//	constants
+// -----------------------------------------------------------------------------
+
+// Don't modify this line
+#define PLUGIN_ID "C00EB589-55DF-45ED-B4B8-8AFFA367CFCF"
+
+//
+// Below is the generic glue code for all plug-ins.
+//
+// You should not have to modify this code aside from changing
+// names if you decide to change the names defined in the Info.plist
+//
+
+
+// -----------------------------------------------------------------------------
+//	typedefs
+// -----------------------------------------------------------------------------
+
+// The thumbnail generation function to be implemented in GenerateThumbnailForURL.c
+OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize);
+void CancelThumbnailGeneration(void* thisInterface, QLThumbnailRequestRef thumbnail);
+
+// The preview generation function to be implemented in GeneratePreviewForURL.c
+OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options);
+void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview);
+
+// The layout for an instance of QuickLookGeneratorPlugIn
+typedef struct __QuickLookGeneratorPluginType
+{
+    void        *conduitInterface;
+    CFUUIDRef    factoryID;
+    UInt32       refCount;
+} QuickLookGeneratorPluginType;
+
+// -----------------------------------------------------------------------------
+//	prototypes
+// -----------------------------------------------------------------------------
+//	Forward declaration for the IUnknown implementation.
+//
+
+QuickLookGeneratorPluginType  *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID);
+void                         DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance);
+HRESULT                      QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv);
+void                        *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID);
+ULONG                        QuickLookGeneratorPluginAddRef(void *thisInstance);
+ULONG                        QuickLookGeneratorPluginRelease(void *thisInstance);
+
+// -----------------------------------------------------------------------------
+//	myInterfaceFtbl	definition
+// -----------------------------------------------------------------------------
+//	The QLGeneratorInterfaceStruct function table.
+//
+static QLGeneratorInterfaceStruct myInterfaceFtbl = {
+    NULL,
+    QuickLookGeneratorQueryInterface,
+    QuickLookGeneratorPluginAddRef,
+    QuickLookGeneratorPluginRelease,
+    NULL,
+    NULL,
+    NULL,
+    NULL
+};
+
+
+// -----------------------------------------------------------------------------
+//	AllocQuickLookGeneratorPluginType
+// -----------------------------------------------------------------------------
+//	Utility function that allocates a new instance.
+//      You can do some initial setup for the generator here if you wish
+//      like allocating globals etc...
+//
+QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID)
+{
+    QuickLookGeneratorPluginType *theNewInstance;
+
+    theNewInstance = (QuickLookGeneratorPluginType *)malloc(sizeof(QuickLookGeneratorPluginType));
+    memset(theNewInstance,0,sizeof(QuickLookGeneratorPluginType));
+
+        /* Point to the function table Malloc enough to store the stuff and copy the filler from myInterfaceFtbl over */
+    theNewInstance->conduitInterface = malloc(sizeof(QLGeneratorInterfaceStruct));
+    memcpy(theNewInstance->conduitInterface,&myInterfaceFtbl,sizeof(QLGeneratorInterfaceStruct));
+
+        /*  Retain and keep an open instance refcount for each factory. */
+    theNewInstance->factoryID = CFRetain(inFactoryID);
+    CFPlugInAddInstanceForFactory(inFactoryID);
+
+        /* This function returns the IUnknown interface so set the refCount to one. */
+    theNewInstance->refCount = 1;
+    return theNewInstance;
+}
+
+// -----------------------------------------------------------------------------
+//	DeallocQuickLookGeneratorPluginType
+// -----------------------------------------------------------------------------
+//	Utility function that deallocates the instance when
+//	the refCount goes to zero.
+//      In the current implementation generator interfaces are never deallocated
+//      but implement this as this might change in the future
+//
+void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance)
+{
+    CFUUIDRef theFactoryID;
+
+    theFactoryID = thisInstance->factoryID;
+        /* Free the conduitInterface table up */
+    free(thisInstance->conduitInterface);
+
+        /* Free the instance structure */
+    free(thisInstance);
+    if (theFactoryID){
+        CFPlugInRemoveInstanceForFactory(theFactoryID);
+        CFRelease(theFactoryID);
+    }
+}
+
+// -----------------------------------------------------------------------------
+//	QuickLookGeneratorQueryInterface
+// -----------------------------------------------------------------------------
+//	Implementation of the IUnknown QueryInterface function.
+//
+HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv)
+{
+    CFUUIDRef interfaceID;
+
+    interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault,iid);
+
+    if (CFEqual(interfaceID,kQLGeneratorCallbacksInterfaceID)){
+            /* If the Right interface was requested, bump the ref count,
+             * set the ppv parameter equal to the instance, and
+             * return good status.
+             */
+        ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GenerateThumbnailForURL = GenerateThumbnailForURL;
+        ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelThumbnailGeneration = CancelThumbnailGeneration;
+        ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GeneratePreviewForURL = GeneratePreviewForURL;
+        ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelPreviewGeneration = CancelPreviewGeneration;
+        ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType*)thisInstance)->conduitInterface)->AddRef(thisInstance);
+        *ppv = thisInstance;
+        CFRelease(interfaceID);
+        return S_OK;
+    }else{
+        /* Requested interface unknown, bail with error. */
+        *ppv = NULL;
+        CFRelease(interfaceID);
+        return E_NOINTERFACE;
+    }
+}
+
+// -----------------------------------------------------------------------------
+// QuickLookGeneratorPluginAddRef
+// -----------------------------------------------------------------------------
+//	Implementation of reference counting for this type. Whenever an interface
+//	is requested, bump the refCount for the instance. NOTE: returning the
+//	refcount is a convention but is not required so don't rely on it.
+//
+ULONG QuickLookGeneratorPluginAddRef(void *thisInstance)
+{
+    ((QuickLookGeneratorPluginType *)thisInstance )->refCount += 1;
+    return ((QuickLookGeneratorPluginType*) thisInstance)->refCount;
+}
+
+// -----------------------------------------------------------------------------
+// QuickLookGeneratorPluginRelease
+// -----------------------------------------------------------------------------
+//	When an interface is released, decrement the refCount.
+//	If the refCount goes to zero, deallocate the instance.
+//
+ULONG QuickLookGeneratorPluginRelease(void *thisInstance)
+{
+    ((QuickLookGeneratorPluginType*)thisInstance)->refCount -= 1;
+    if (((QuickLookGeneratorPluginType*)thisInstance)->refCount == 0){
+        DeallocQuickLookGeneratorPluginType((QuickLookGeneratorPluginType*)thisInstance );
+        return 0;
+    }else{
+        return ((QuickLookGeneratorPluginType*) thisInstance )->refCount;
+    }
+}
+
+// -----------------------------------------------------------------------------
+//  QuickLookGeneratorPluginFactory
+// -----------------------------------------------------------------------------
+void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID)
+{
+    QuickLookGeneratorPluginType *result;
+    CFUUIDRef                 uuid;
+
+        /* If correct type is being requested, allocate an
+         * instance of kQLGeneratorTypeID and return the IUnknown interface.
+         */
+    if (CFEqual(typeID,kQLGeneratorTypeID)){
+        uuid = CFUUIDCreateFromString(kCFAllocatorDefault,CFSTR(PLUGIN_ID));
+        result = AllocQuickLookGeneratorPluginType(uuid);
+        CFRelease(uuid);
+        return result;
+    }
+        /* If the requested type is incorrect, return NULL. */
+    return NULL;
+}
+

+ 438 - 0
examples/quicklook-mesh/src/render_to_buffer.cpp

@@ -0,0 +1,438 @@
+// This required to get correct linking with Objective-C files
+extern "C" {
+#include "render_to_buffer.h"
+};
+// We're probably didn't build libigl.a with LLVM so just use the headers only
+// version.
+#define IGL_HEADER_ONLY
+#include <igl/per_face_normals.h>
+#include <igl/normalize_row_lengths.h>
+#include <igl/get_seconds.h>
+#include <igl/draw_mesh.h>
+#include <igl/draw_floor.h>
+#include <igl/material_colors.h>
+#include <igl/pathinfo.h>
+#include <igl/readOBJ.h>
+#include <igl/readOFF.h>
+#include <igl/readMESH.h>
+#include <igl/boundary_faces.h>
+#include <igl/barycenter.h>
+#include <igl/doublearea.h>
+#include <igl/EPS.h>
+#include <igl/camera.h>
+#include <igl/canonical_quaternions.h>
+#include <igl/quat_to_mat.h>
+
+#include <Eigen/Core>
+#include <GL/glu.h>
+
+#include <algorithm>
+
+static int width,height;
+static Eigen::MatrixXd V,N;
+static Eigen::MatrixXi F;
+static double bbd;
+static Eigen::Vector3d Vmean, Vmax,Vmin;
+static bool invert = false;
+
+// Small viewports struct for keeping track of size and camera info
+#define NUM_VIEWPORTS 6
+struct Viewport
+{
+  int x,y,width,height;
+  igl::Camera camera;
+  Viewport():
+    x(0),y(0),width(0),height(0),camera(){};
+  Viewport(
+    const int x, 
+    const int y, 
+    const int width,
+    const int height, 
+    const igl::Camera & camera):
+    x(x),
+    y(y),
+    width(width),
+    height(height),
+    camera(camera)
+  {
+  };
+  void reshape(
+    const int x, 
+    const int y, 
+    const int width,
+    const int height)
+  {
+    this->x = x;
+    this->y = y;
+    this->width = width;
+    this->height = height;
+  };
+} viewports[NUM_VIEWPORTS];
+
+// Red screen for errors
+void red(const int width, const int height, GLubyte * buffer)
+{
+  for(int h = 0;h<height;h++)
+  {
+    for(int w = 0;w<width;w++)
+    {
+      for(int c = 0;c<4;c++)
+      {
+        if(c == 0 || c==3)
+        {
+          buffer[c+4*w+4*width*h] = 255;
+        }else
+        {
+          buffer[c+4*w+4*width*h] = 0;
+        }
+      }
+    }
+  }
+}
+
+// Initialize the viewport angles and camera rotations
+void init_viewports()
+{
+  using namespace igl;
+  using namespace std;
+  viewports[0].camera.angle = 10;
+  viewports[1].camera.angle = 10;
+  viewports[2].camera.angle = 10;
+  viewports[3].camera.angle = 10;
+  viewports[4].camera.angle = 10;
+  viewports[5].camera.angle = 10;
+  // Above view
+  double XZ_PLANE_QUAT_D_FLIP[4];
+  copy(XZ_PLANE_QUAT_D,XZ_PLANE_QUAT_D+4,XZ_PLANE_QUAT_D_FLIP);
+  XZ_PLANE_QUAT_D_FLIP[0] *= -1.0;
+  // Straight on
+  copy(XY_PLANE_QUAT_D,XY_PLANE_QUAT_D+4,viewports[0].camera.rotation);
+  // Left side view
+  copy(
+    CANONICAL_VIEW_QUAT_D[9],
+    CANONICAL_VIEW_QUAT_D[9]+4,
+    viewports[1].camera.rotation);
+  copy(
+    CANONICAL_VIEW_QUAT_D[14],
+    CANONICAL_VIEW_QUAT_D[14]+4,
+    viewports[2].camera.rotation);
+  // Straight on
+  copy(
+    CANONICAL_VIEW_QUAT_D[4],
+    CANONICAL_VIEW_QUAT_D[4]+4,
+    viewports[3].camera.rotation);
+  copy(XZ_PLANE_QUAT_D,XZ_PLANE_QUAT_D+4,viewports[4].camera.rotation);
+  copy(XZ_PLANE_QUAT_D_FLIP,XZ_PLANE_QUAT_D_FLIP+4,viewports[5].camera.rotation);
+}
+
+// Viewports are arranged to see all sides
+//
+// /-----.-----.-----\
+// |  3  |  2  |  1  |
+// -------------------
+// |  4  |           |
+// -------     0     |
+// |  5  |           |
+// \-----.-----------/
+void reshape_viewports()
+{
+  using namespace igl;
+  using namespace std;
+  viewports[0].reshape(1./3.*width,            0,2./3.*width,2./3.*height);
+  viewports[1].reshape(2./3.*width, 2./3.*height,1./3.*width,1./3.*height);
+  viewports[2].reshape(1./3.*width, 2./3.*height,1./3.*width,1./3.*height);
+  viewports[3].reshape(          0, 2./3.*height,1./3.*width,1./3.*height);
+  viewports[4].reshape(          0, 1./3.*height,1./3.*width,1./3.*height);
+  viewports[5].reshape(          0,            0,1./3.*width,1./3.*height);
+}
+
+void reshape(int width, int height)
+{
+  using namespace std;
+  ::width = width;
+  ::height = height;
+  reshape_viewports();
+}
+
+// Simple two-sided, diffuse-only light
+void lights()
+{
+  using namespace std;
+  glEnable(GL_LIGHTING);
+  glLightModelf(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
+  glEnable(GL_LIGHT0);
+  glEnable(GL_LIGHT1);
+  float WHITE[4] =  {0.8,0.8,0.8,1.};
+  float GREY[4] =  {0.4,0.4,0.4,1.};
+  float BLACK[4] =  {0.,0.,0.,1.};
+  float pos[4];
+  float light_pos[4] = {0.1,0.1,1.0,0.0};
+  copy(light_pos,light_pos+4,pos);
+  glLightfv(GL_LIGHT0,GL_AMBIENT,GREY);
+  glLightfv(GL_LIGHT0,GL_DIFFUSE,WHITE);
+  glLightfv(GL_LIGHT0,GL_SPECULAR,BLACK);
+  glLightfv(GL_LIGHT0,GL_POSITION,pos);
+  pos[0] *= -1;
+  pos[1] *= -1;
+  pos[2] *= -1;
+  glLightfv(GL_LIGHT1,GL_AMBIENT,GREY);
+  glLightfv(GL_LIGHT1,GL_DIFFUSE,WHITE);
+  glLightfv(GL_LIGHT1,GL_SPECULAR,BLACK);
+  glLightfv(GL_LIGHT1,GL_POSITION,pos);
+}
+
+// Push scene based on viewport
+void push_scene(const Viewport & vp)
+{
+  using namespace igl;
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  const double angle = vp.camera.angle;
+  const double * rot = vp.camera.rotation;
+  gluPerspective(angle,(double)width/(double)height,1e-2,10);
+  const double z_fix = 2.*tan(angle/2./360.*2.*M_PI);
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+  // -1 because buffer y-coordinates are flipped
+  gluLookAt(0,0,2.3,0,0,0,0,-1,0);
+  glPushMatrix();
+  double mat[4*4];
+  quat_to_mat(rot,mat);
+  glMultMatrixd(mat);
+  glScaled(z_fix,z_fix,z_fix);
+}
+
+// Scale and shift for object so that it fits current view
+void push_object(const Viewport & vp)
+{
+  using namespace Eigen;
+  glPushMatrix();
+  const double * rot = vp.camera.rotation;
+  Quaterniond q(rot[3],rot[0],rot[1],rot[2]);
+  Matrix3d m = q.matrix();
+  Vector3d eff_Vmax  = m*Vmax;
+  Vector3d eff_Vmin  = m*Vmin;
+  Vector3d eff_Vmean = m*Vmean;
+  //glScaled(2./bbd,2./bbd,2./bbd);
+  const double dy = fabs(eff_Vmax(1,0)-eff_Vmin(1,0));
+  const double dx = fabs(eff_Vmax(0,0)-eff_Vmin(0,0));
+  //const double dz = fabs(eff_Vmax(2,0)-eff_Vmin(2,0));
+  // Assumes height < width
+  const double sx = dx*(double)height/(double)width;
+  const double sy = dy;
+  const double s = 2./(sy > sx ? sy : sx);
+  glScaled(s,s,s);
+  glTranslated(-Vmean(0,0),-Vmean(1,0),-Vmean(2,0));
+  // Hack. Should really just figure out max scale so that full model fits on
+  // screen with given perspective.
+  //const double dz_off = (dz > 2.*dy && dz > 2.*dx ? dz/2. : 0);
+  //glTranslated(0,0,-dz_off);
+}
+
+void pop_object()
+{
+  glPopMatrix();
+}
+
+void pop_scene()
+{
+  glPopMatrix();
+}
+
+void display()
+{
+  using namespace std;
+  using namespace igl;
+  glClearColor(0,0,0,0);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_NORMALIZE);
+  glDisable(GL_CULL_FACE);
+  glCullFace(GL_BACK);
+
+  // "Flash light" attached to camera
+  lights();
+  // Draw for each viewport
+  for(int vp = 0;vp<NUM_VIEWPORTS;vp++)
+  {
+    glViewport(
+      viewports[vp].x,
+      viewports[vp].y,
+      viewports[vp].width,
+      viewports[vp].height);
+    push_scene(viewports[vp]);
+    push_object(viewports[vp]);
+
+    // Set material properties
+    glDisable(GL_COLOR_MATERIAL);
+    glMaterialfv(GL_FRONT, GL_AMBIENT,  GOLD_AMBIENT);
+    glMaterialfv(GL_FRONT, GL_DIFFUSE,  GOLD_DIFFUSE  );
+    glMaterialfv(GL_FRONT, GL_SPECULAR, GOLD_SPECULAR);
+    glMaterialf (GL_FRONT, GL_SHININESS, 128);
+    glMaterialfv(GL_BACK, GL_AMBIENT,  SILVER_AMBIENT);
+    glMaterialfv(GL_BACK, GL_DIFFUSE,  FAST_GREEN_DIFFUSE  );
+    glMaterialfv(GL_BACK, GL_SPECULAR, SILVER_SPECULAR);
+    glMaterialf (GL_BACK, GL_SHININESS, 128);
+
+    // Draw the mesh, inverted if need be.
+    if(invert)
+    {
+      glFrontFace(GL_CW);
+    }
+    draw_mesh(V,F,N);
+    if(invert)
+    {
+      glFrontFace(GL_CCW);
+    }
+    pop_object();
+
+    // Draw a nice floor unless we're looking from beneath it
+    if(vp != 4)
+    {
+      glPushMatrix();
+      glTranslated(0,-1,0);
+      glScaled(4,4,4);
+      draw_floor();
+      glPopMatrix();
+    }
+
+    pop_scene();
+  }
+
+  // Screen space
+  glDisable(GL_DEPTH_TEST);
+  glDisable(GL_LIGHTING);
+  glViewport(0,0,width,height);
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  gluOrtho2D(0,width,0,height);
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+
+  // Draw separation lines between (around) viewports
+  const double BAR_THICKNESS = 3.0;
+  glColor3f(0.5,0.5,0.5);
+  for(int vp = 0;vp<NUM_VIEWPORTS;vp++)
+  {
+    glLineWidth(BAR_THICKNESS);
+    glBegin(GL_LINE_STRIP);
+    glVertex2f(viewports[vp].x,viewports[vp].y);
+    glVertex2f(viewports[vp].x+viewports[vp].width,viewports[vp].y);
+    glVertex2f(viewports[vp].x+viewports[vp].width,viewports[vp].y+viewports[vp].height);
+    glVertex2f(                    viewports[vp].x,viewports[vp].y+viewports[vp].height);
+    glEnd();
+  }
+
+  glFinish();
+}
+
+
+bool render_to_buffer(
+  const char * filename,
+  const int width,
+  const int height,
+  GLubyte * buffer)
+{
+  using namespace igl;
+  using namespace std;
+  using namespace Eigen;
+  double ts = get_seconds();
+
+  // Read and prepare mesh
+  // dirname, basename, extension and filename
+  string d,b,ext,f;
+  pathinfo(filename,d,b,ext,f);
+  // Convert extension to lower case
+  transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
+  if(ext == "obj")
+  {
+    // Convert extension to lower case
+    if(!igl::readOBJ(filename,V,F))
+    {
+      red(width,height,buffer);
+      return false;
+    }
+  }else if(ext == "off")
+  {
+    // Convert extension to lower case
+    if(!igl::readOFF(filename,V,F))
+    {
+      red(width,height,buffer);
+      return false;
+    }
+  }else
+  {
+    // Convert extension to lower case
+    MatrixXi T;
+    if(!igl::readMESH(filename,V,T,F))
+    {
+      red(width,height,buffer);
+      return false;
+    }
+    //if(F.size() > T.size() || F.size() == 0)
+    {
+      boundary_faces(T,F);
+    }
+  }
+  cout<<"IO: "<<(get_seconds()-ts)<<"s"<<endl;
+  ts = get_seconds();
+
+  // Computer already normalized per triangle normals
+  per_face_normals(V,F,N);
+  //Vmean = 0.5*(V.colwise().maxCoeff()+V.colwise().minCoeff());
+  Vmax = V.colwise().maxCoeff();
+  Vmin = V.colwise().minCoeff();
+  Vmean = 0.5*(Vmax + Vmin);
+  bbd = (Vmax - Vmin).maxCoeff();
+
+  // Figure out if normals should be flipped (hopefully this is never a
+  // bottleneck)
+  MatrixXd BC;
+  VectorXd dblA;
+  barycenter(V,F,BC);
+  BC.col(0).array() -= Vmean(0,0);
+  BC.col(1).array() -= Vmean(1,0);
+  BC.col(2).array() -= Vmean(2,0);
+  doublearea(V,F,dblA);
+  VectorXd BCDN = (BC.array() * N.array()).rowwise().sum();
+  const double tot_dp = dblA.transpose() * BCDN;
+  invert = tot_dp < 0;
+  cout<<"Normals: "<<(get_seconds()-ts)<<"s"<<endl;
+  ts = get_seconds();
+
+  // Initialize MESA
+  OSMesaContext ctx;
+  /* Create an RGBA-mode context */
+#  if OSMESA_MAJOR_VERSION * 100 + OSMESA_MINOR_VERSION >= 305
+  /* specify Z, stencil, accum sizes */
+  ctx = OSMesaCreateContextExt( OSMESA_RGBA, 32, 0, 0, NULL );
+#  else
+  ctx = OSMesaCreateContext( OSMESA_RGBA, NULL );
+#  endif
+  if (!ctx)
+  {
+    fprintf(stderr,"OSMesaCreateContext failed!\n");
+    red(width,height,buffer);
+    return false;
+  }
+
+  /* Bind the buffer to the context and make it current */
+  if (!OSMesaMakeCurrent( ctx, buffer, GL_UNSIGNED_BYTE, width, height))
+  {
+    fprintf(stderr,"OSMesaMakeCurrent failed!\n");
+    red(width,height,buffer);
+    return false;
+  }
+    
+  // Render
+  init_viewports();
+  reshape(width,height);
+  display();
+  cout<<"Display: "<<(get_seconds()-ts)<<"s"<<endl;
+  ts = get_seconds();
+
+  /* destroy the context */
+  OSMesaDestroyContext( ctx );
+  
+  return true;
+}

+ 15 - 0
examples/quicklook-mesh/src/render_to_buffer.h

@@ -0,0 +1,15 @@
+#define GL_GLEXT_PROTOTYPES
+#include <GL/osmesa.h>
+
+// Inputs:
+//   filename   path to mesh
+//   width  width of image buffer
+//   height  height of image buffer
+// Outputs:
+//   buffer   width*height*4 buffer of bytes (should already be allocated) RGBA
+// Returns true only only upon success.
+bool render_to_buffer(
+  const char * filename,
+  const int width,
+  const int height,
+  GLubyte * buffer);

+ 11 - 1
include/igl/per_face_normals.cpp

@@ -1,6 +1,7 @@
 #include "per_face_normals.h"
 #include <Eigen/Geometry>
 
+#define SQRT_ONE_OVER_THREE 0.57735026918962573
 template <typename DerivedV, typename DerivedF>
 IGL_INLINE void igl::per_face_normals(
   const Eigen::PlainObjectBase<DerivedV>& V,
@@ -15,7 +16,16 @@ IGL_INLINE void igl::per_face_normals(
   {
     Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v1 = V.row(F(i,1)) - V.row(F(i,0));
     Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v2 = V.row(F(i,2)) - V.row(F(i,0));
-    N.row(i) = (v1.cross(v2)).normalized();
+    N.row(i) = v1.cross(v2);//.normalized();
+    if(N.row(i).sum() == 0)
+    {
+      N(i,0) = SQRT_ONE_OVER_THREE;
+      N(i,1) = SQRT_ONE_OVER_THREE;
+      N(i,2) = SQRT_ONE_OVER_THREE;
+    }else
+    {
+      N.row(i) = N.row(i).normalized().eval();
+    }
   }
 }
 #ifndef IGL_HEADER_ONLY