Reply To: NSImage to Syhon

Home Forums Syphon Syphon Development – Developer NSImage to Syhon Reply To: NSImage to Syhon

#59230
Rick Burnett
Participant

I’ve cleaned out a lot of unnecessary code, but here is a copy of what I am basically running at the moment:

#import <Cocoa/Cocoa.h>
#import <OpenGL/gl.h>
#import <cairo.h>
#import <cairo-gl.h>
#import <CoreMIDI/CoreMIDI.h>
#import <Syphon/Syphon.h>
#import <GLKit/GLKit.h>

#define WIDTH 640
#define HEIGHT 480

#define SLOTS 12
int drawTable[SLOTS][4];
int dtPtr;

@interface NSImage (SSWPNGAdditions)

- (BOOL)writePNGToURL:(NSURL*)URL outputSizeInPixels:(NSSize)outputSizePx error:(NSError*__autoreleasing*)error;

@end

@implementation NSImage (SSWPNGAdditions)

- (BOOL)writePNGToURL:(NSURL*)URL outputSizeInPixels:(NSSize)outputSizePx error:(NSError*__autoreleasing*)error
{
    BOOL result = YES;
    NSImage* scalingImage = [NSImage imageWithSize:[self size] flipped:NO drawingHandler:^BOOL(NSRect dstRect) {
        [self drawAtPoint:NSMakePoint(0.0, 0.0) fromRect:dstRect operation:NSCompositeSourceOver fraction:1.0];
        return YES;
    }];
    NSRect proposedRect = NSMakeRect(0.0, 0.0, outputSizePx.width, outputSizePx.height);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    CGContextRef cgContext = CGBitmapContextCreate(NULL, proposedRect.size.width, proposedRect.size.height, 8, 4*proposedRect.size.width, colorSpace, kCGBitmapByteOrderDefault|kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);
    NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
    CGContextRelease(cgContext);
    CGImageRef cgImage = [scalingImage CGImageForProposedRect:&proposedRect context:context hints:nil];
    CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)(URL), kUTTypePNG, 1, NULL);
    CGImageDestinationAddImage(destination, cgImage, nil);
    if(!CGImageDestinationFinalize(destination))
    {
        NSDictionary* details = @{NSLocalizedDescriptionKey:@"Error writing PNG image"};
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        *error = [NSError errorWithDomain:@"SSWPNGAdditionsErrorDomain" code:10 userInfo:details];
        result = NO;
    }
    CFRelease(destination);
    return result;
}

@end

@interface StretchView : NSOpenGLView {
@private
    NSTimer *renderTimer;
    cairo_surface_t *surface;
    cairo_t *cr;
    NSTimeInterval now;
    MIDIClientRef midiClient;
    MIDIPortRef inputPort;
    MIDIEndpointRef virtDest;
    SyphonServer *myServer;
    GLuint texture; 
    NSOpenGLContext *context;
}
- (void) draw;

@end

@implementation StretchView

	static void memxor(unsigned char *dst, unsigned char *src, unsigned int bytes)
	{
		while (bytes--) *dst++ ^= *src++;
	}
	
	static void memswap(unsigned char *a, unsigned char *b, unsigned int bytes)
	{
		memxor(a, b, bytes);
		memxor(b, a, bytes);
		memxor(a, b, bytes);
	}

- (NSImage *) imageFromView
{
	NSRect bounds;
	int height, width, row, bytesPerRow;
	NSBitmapImageRep *imageRep;
	unsigned char *bitmapData;
	NSImage *image;
	
	bounds = [self bounds];
	
	height = bounds.size.height;
	width = bounds.size.width;
	
	imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL
				pixelsWide: width
				pixelsHigh: height
				bitsPerSample: 8
				samplesPerPixel: 4
				hasAlpha: YES
				isPlanar: NO
				colorSpaceName: NSCalibratedRGBColorSpace
				bytesPerRow: 0				// indicates no empty bytes at row end
				bitsPerPixel: 0];
				
	[[self openGLContext] makeCurrentContext];
				
	bitmapData = [imageRep bitmapData];

	bytesPerRow = [imageRep bytesPerRow];
	
	glPixelStorei(GL_PACK_ROW_LENGTH, 8*bytesPerRow/[imageRep bitsPerPixel]);

	glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bitmapData);

	// Flip the bitmap vertically to account for OpenGL coordinate system difference
	// from NSImage coordinate system.
	
	for (row = 0; row < height/2; row++)
	{
		unsigned char *a, *b;
		
		a = bitmapData + row * bytesPerRow;
		b = bitmapData + (height - 1 - row) * bytesPerRow;
		
		memswap(a, b, bytesPerRow);
	}

	// Create the NSImage from the bitmap

	image = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
	[image addRepresentation: imageRep];
	
	// Release memory
	
	[imageRep release];

	// Previously we did not flip the bitmap, and instead did [image setFlipped:YES];
	// This does not work properly (i.e., the image remained inverted) when pasting 
	// the image to AppleWorks or GraphicConvertor.

	return image;
}
+ (NSOpenGLPixelFormat*)defaultPixelFormat
{	

     NSOpenGLPixelFormatAttribute attrs[] =
    {
		NSOpenGLPFADoubleBuffer,
		NSOpenGLPFAAccelerated,
		NSOpenGLPFADepthSize, 24,
		NSOpenGLPFAMultisample,
		NSOpenGLPFASampleBuffers, 1,
		NSOpenGLPFASamples, 4,
		(NSOpenGLPixelFormatAttribute) 0
    };
	
    NSOpenGLPixelFormat *classPixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attrs] autorelease];
    if (!classPixelFormat)
	    exit (-1);

    return classPixelFormat;
}

- (void)prepareOpenGL
{  
    context = [super openGLContext];

    [context makeCurrentContext];
    glEnable(GL_TEXTURE_2D);
    glGenTextures(1,&texture);
	
    //GLint swapInt = 1;
    //[context setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; // set to vbl sync

    cairo_device_t *device = cairo_nsgl_device_create (context);
		 
    surface = cairo_gl_surface_create_for_view (device, self, WIDTH, HEIGHT);

    cr = cairo_create (surface);

    
    renderTimer = [NSTimer timerWithTimeInterval:0.001   //a 1ms time interval
                                target:self
                                selector:@selector(timerFired:)
                                userInfo:nil
                                repeats:YES];
 
    [[NSRunLoop currentRunLoop] addTimer:renderTimer
                                forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] addTimer:renderTimer
                                forMode:NSEventTrackingRunLoopMode]; //Ensure timer fires during resize

   // Initialize the draw table
   for(int i=0;i<SLOTS;i++){
      for(int param=0;param<4;param++) {
         drawTable[i][param]=0;
      }
   }
   dtPtr=0;

   //start syphon
   myServer = [[SyphonServer alloc] initWithName:@"Cairo Output" context:[context CGLContextObj]  options:nil];

}
// Timer callback method
- (void)timerFired:(id)sender
{
    // It is good practice in a Cocoa application to allow the system to send the -drawRect:
    // message when it needs to draw, and not to invoke it directly from the timer.
    // All we do here is tell the display it needs a refresh
    //[self setNeedsDisplay:YES];
    [self draw];
}

int first = 1;

- (void)drawRect:(NSRect)dirtyRect 
{   //CGLSetCurrentContext( myServer.context );
    cairo_save (cr);
    cairo_rectangle (cr, 0, 0, WIDTH, HEIGHT);
    if(first) {
      cairo_set_source_rgb (cr, 0, 0, 0);
      first = 0;
    } else {
      cairo_set_source_rgba (cr, 0, 0, 0, 0.01);
    }
    cairo_fill (cr);

    for(int i=0; i<SLOTS;i++){
      if( drawTable[i][2] > 0 ) {
         int x = drawTable[i][0];
         int y = drawTable[i][1];
         float lum = drawTable[i][2]/256.0f;

         cairo_set_source_rgba (cr, lum,lum,lum, 0.8);
         cairo_arc(cr, x,y, drawTable[i][2], 0, 2* M_PI);
         cairo_stroke(cr);
         //cairo_set_font_size (cr, 18);
         //cairo_show_text (cr, "ONE NIGHT IN CAIRO!");
         drawTable[i][1]+=drawTable[i][3]/10;
         drawTable[i][2]-=8;
      } else {
         drawTable[i][2] = 256;
         drawTable[i][0] = i*30;
      }
   }

   glFinish();
   glFlushRenderAPPLE();
    
   NSImage *im = [self imageFromView];
   //[im writePNGToURL: [NSURL fileURLWithPath:@"./test.png"]  outputSizeInPixels: NSMakeSize(640,480) error:nil];
   CGLSetCurrentContext( [context CGLContextObj] );
   glEnable(GL_TEXTURE_2D);

   glBindTexture(GL_TEXTURE_2D, texture);
   NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithData:[im TIFFRepresentation]];
   glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)[bitmap pixelsWide]);

   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

   NSInteger samplesPerPixel = [bitmap samplesPerPixel];
   
    if(![bitmap isPlanar] && (samplesPerPixel == 3 || samplesPerPixel == 4)) {

        glTexImage2D(GL_TEXTURE_2D, 0,
                     samplesPerPixel == 4 ? GL_RGBA8 : GL_RGB8,
                     (GLint)[bitmap pixelsWide],
                     (GLint)[bitmap pixelsHigh],
                     0,
                     samplesPerPixel == 4 ? GL_RGBA : GL_RGB,
                     GL_UNSIGNED_BYTE,
                     [bitmap bitmapData]);
    }else{
        NSLog(@"Problem");
        [[NSException exceptionWithName:@"ImageFormat" reason:@"Unsupported image format" userInfo:nil] raise];
    }
    //exprimental test
    //glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,2,2,0,GL_RGBA,GL_UNSIGNED_BYTE,@"312313532433");
    [myServer publishFrameTexture:texture textureTarget:GL_TEXTURE_2D imageRegion:NSMakeRect(0, 0, 640,480 ) textureDimensions:NSMakeSize(640, 480) flipped:NO];
  
   

     cairo_gl_surface_swapbuffers (surface);
     cairo_restore (cr);

    //[im unlockFocus];
}

- (void) draw
{
    [self setNeedsDisplay: YES];
}
	
@end

int main (int argc, char **argv)
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    [NSApplication sharedApplication];
    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
    
    int style = NSClosableWindowMask |
    		NSTexturedBackgroundWindowMask | NSTitledWindowMask |
    		NSMiniaturizableWindowMask;

    NSScreen *screen = [NSScreen mainScreen];
    NSRect frame = [screen visibleFrame];
    int frame_height = frame.size.height;

    NSWindow *win = [[ NSWindow alloc] initWithContentRect: NSMakeRect (0, frame_height - HEIGHT, WIDTH, HEIGHT)
					styleMask: style
					backing: NSBackingStoreBuffered
					defer: NO];

    StretchView *view = [[StretchView alloc] initWithFrame: NSMakeRect (0, 0, WIDTH, HEIGHT) ];
    [win setContentView: view];
    [win makeKeyAndOrderFront: win];
    [NSApp run];

    [win release];
    [view release];

    [pool release];

    return 0;
}

And the compile command I am using is:

gcc -Wall -framework GLKit -framework Syphon -framework CoreMidi -framework Cocoa -framework OpenGL vade.m -I/usr/local/Cellar/cairo/1.14.8/include/cairo -lcairo -L/usr/local/lib -o vade.app

For this stripped down version. It pops up a window showing the output (a bunch of circles drawing on top of themselves, normally it has midi driven, but I pulled all that out and just hacked that part together).

Simple Client connects to this and just shows black.