Home › Forums › Syphon › Syphon Development – Developer › NSImage to Syhon › Reply To: NSImage to Syhon
January 7, 2017 at 6:46 pm
#59230
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.