/* FreeJ * (c) Copyright 2009 Andrea Guzzo * * This source code is free software; you can redistribute it and/or * modify it under the terms of the GNU Public License as published * by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * This source code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * Please refer to the GNU Public License for more details. * * You should have received a copy of the GNU Public License along with * this source code; if not, write to: * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #import #import #import #import #import #import /* Utility to set a SInt32 value in a CFDictionary */ static OSStatus SetNumberValue(CFMutableDictionaryRef inDict, CFStringRef inKey, SInt32 inValue) { CFNumberRef number; number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &inValue); if (NULL == number) return coreFoundationUnknownErr; CFDictionarySetValue(inDict, inKey, number); CFRelease(number); return noErr; } @implementation CVLayerController : NSObject - (void)awakeFromNib { //[self init]; } - (id)init { return [self initWithContext:nil]; } - (id)initWithContext:(CFreej *)ctx { CGLPixelFormatObj pFormat; GLint npix; const int attrs[2] = { kCGLPFADoubleBuffer, NULL}; CGLError err = CGLChoosePixelFormat ( (CGLPixelFormatAttribute *)attrs, &pFormat, &npix ); freej = ctx; err = CGLCreateContext(pFormat , NULL, &glContext); lock = [[NSRecursiveLock alloc] init]; [layerView setNeedsDisplay:NO]; layer = NULL; doFilters = true; currentFrame = NULL; posterImage = NULL; doPreview = YES; imageParams = [[NSMutableDictionary dictionary] retain]; return self; } /* - (void)setContext:(CFreej *)ctx { freej = ctx; } */ - (void)dealloc { [colorCorrectionFilter release]; [compositeFilter release]; [alphaFilter release]; [exposureAdjustFilter release]; [rotateFilter release]; [scaleFilter release]; [translateFilter release]; ///[timeCodeOverlay release]; if (currentFrame) CVOpenGLTextureRelease(currentFrame); if (imageParams) [imageParams release]; [lock release]; [super dealloc]; } - (void)prepareOpenGL { NSAutoreleasePool *pool; pool = [[NSAutoreleasePool alloc] init]; // Setup the timecode overlay /* NSDictionary *fontAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont labelFontOfSize:24.0f], NSFontAttributeName, [NSColor colorWithCalibratedRed:1.0f green:0.2f blue:0.2f alpha:0.60f], NSForegroundColorAttributeName, nil]; */ //timeCodeOverlay = [[TimeCodeOverlay alloc] initWithAttributes:fontAttributes targetSize:NSMakeSize(720.0,480.0 / 4.0)]; // text overlay will go in the bottom quarter of the display GLint params[] = { 1 }; CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval, params ); [pool release]; } - (void)feedFrame:(CVPixelBufferRef)frame { CIImage *renderedImage = nil; Layer *fjLayer = NULL; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (!filtersInitialized) [self initFilters]; // initialize on first use if (layer) fjLayer = layer->fjLayer(); else return; [lock lock]; if (currentFrame) CVPixelBufferRelease(currentFrame); CVReturn err = CVPixelBufferCreate ( NULL, fjLayer->geo.w, fjLayer->geo.h, k32ARGBPixelFormat, NULL, ¤tFrame ); //currentFrame = CVPixelBufferRetain(frame); // TODO - check error code //fjLayer->lock(); CIImage *inputImage = [CIImage imageWithCVImageBuffer:frame]; CGRect bounds = CGRectMake(0, 0, fjLayer->geo.w, fjLayer->geo.h); CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); CVPixelBufferLockBaseAddress(currentFrame, 0); // ensure to start drawing on a black background memset(CVPixelBufferGetBaseAddress(currentFrame), 0, CVPixelBufferGetDataSize(currentFrame)); CGContextRef context = CGBitmapContextCreateWithData( CVPixelBufferGetBaseAddress(currentFrame), fjLayer->geo.w, fjLayer->geo.h, 8, fjLayer->geo.w*4, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big, NULL, NULL ); CVPixelBufferUnlockBaseAddress(currentFrame, 0); if (!context) { //fjLayer->unlock(); // todo - error messages return; } NSDictionary *ciContextOptions = [NSDictionary dictionaryWithObject:(NSString*)kCGColorSpaceGenericRGB forKey: kCIContextOutputColorSpace]; CIContext *ciCtx = [CIContext contextWithCGContext:context options:ciContextOptions]; CGContextRelease(context); if (doFilters) { [colorCorrectionFilter setValue:inputImage forKey:@"inputImage"]; [exposureAdjustFilter setValue:[colorCorrectionFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; [alphaFilter setValue:[exposureAdjustFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; [rotateFilter setValue:[alphaFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; if (fjLayer && (fjLayer->geo.x || fjLayer->geo.y)) { NSAffineTransform *translateTransform = [NSAffineTransform transform]; [translateTransform translateXBy:fjLayer->geo.x yBy:fjLayer->geo.y]; [translateFilter setValue:translateTransform forKey:@"inputTransform"]; [translateFilter setValue:[rotateFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; renderedImage = [translateFilter valueForKey:@"outputImage"]; } else { renderedImage = [rotateFilter valueForKey:@"outputImage"]; } } else { renderedImage = inputImage; } [ciCtx drawImage:renderedImage inRect:bounds fromRect:bounds]; [lock unlock]; [pool release]; } - (IBAction)toggleFilters:(id)sender { doFilters = doFilters?false:true; } - (IBAction)toggleVisibility:(id)sender { if (layer) if (layer->isActive()) layer->deactivate(); else layer->activate(); } - (IBAction)togglePreview:(id)sender { doPreview = doPreview?NO:YES; } - (void) setLayer:(CVCocoaLayer *)lay { if (lay) { layer = lay; //layer->fps.set(30); } } - (NSDictionary *)imageParams { return imageParams; } - (IBAction)setValue:(NSNumber *)value forImageParameter:(NSString *)parameter { Layer *fjLayer = NULL; NSAutoreleasePool *pool; float deg = 0; float x = 0; float y = 0; NSAffineTransform *rotateTransform; NSAffineTransform *rototranslateTransform; NSString *paramName = NULL; pool = [[NSAutoreleasePool alloc] init]; if (layer) fjLayer = layer->fjLayer(); else return; // TODO - optimize the logic in this routine ... it's becoming huge!! // to prevent its run() method to try rendering // a frame while we change filter parameters [lock lock]; #if 0 switch([sender tag]) { case 0: // opacity (AlphaFade) [alphaFilter setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:@"outputOpacity"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender label]]; break; case 1: //brightness (ColorCorrection) [colorCorrectionFilter setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:@"inputBrightness"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender label]]; break; case 2: // saturation (ColorCorrection) [colorCorrectionFilter setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:@"inputSaturation"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender label]]; break; case 3: // contrast (ColorCorrection) [colorCorrectionFilter setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:@"inputContrast"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender label]]; break; case 4: // exposure (ExposureAdjust) [exposureAdjustFilter setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:@"inputEV"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender label]]; break; case 5: // rotate rotateTransform = [NSAffineTransform transform]; [rotateTransform rotateByDegrees:[sender floatValue]]; deg = ([sender floatValue]*M_PI)/180.0; if (deg && fjLayer) { x = ((fjLayer->geo.w)-((fjLayer->geo.w)*cos(deg)-(fjLayer->geo.h)*sin(deg)))/2; y = ((fjLayer->geo.h)-((fjLayer->geo.w)*sin(deg)+(fjLayer->geo.h)*cos(deg)))/2; } rototranslateTransform = [NSAffineTransform transform]; [rototranslateTransform translateXBy:x yBy:y]; [rotateTransform appendTransform:rototranslateTransform]; [rotateTransform concat]; [rototranslateTransform concat]; [rotateFilter setValue:rotateTransform forKey:@"inputTransform"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender label]]; break; case 6: // traslate X if (fjLayer) fjLayer->geo.x = [sender floatValue]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender toolTip]]; break; case 7: // traslate Y if (fjLayer) fjLayer->geo.y = [sender floatValue]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender toolTip]]; break; default: break; } #endif [lock unlock]; [pool release]; } - (IBAction)setImageParameter:(id)sender { Layer *fjLayer = NULL; NSAutoreleasePool *pool; float deg = 0; float x = 0; float y = 0; NSAffineTransform *rotateTransform; NSAffineTransform *rototranslateTransform; NSString *paramName = NULL; pool = [[NSAutoreleasePool alloc] init]; if (layer) fjLayer = layer->fjLayer(); else return; // TODO - optimize the logic in this routine ... it's becoming huge!! // to prevent its run() method to try rendering // a frame while we change filter parameters [lock lock]; switch([sender tag]) { case 0: // opacity (AlphaFade) [alphaFilter setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:@"outputOpacity"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender toolTip]]; break; case 1: //brightness (ColorCorrection) [colorCorrectionFilter setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:@"inputBrightness"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender toolTip]]; break; case 2: // saturation (ColorCorrection) [colorCorrectionFilter setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:@"inputSaturation"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender toolTip]]; break; case 3: // contrast (ColorCorrection) [colorCorrectionFilter setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:@"inputContrast"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender toolTip]]; break; case 4: // exposure (ExposureAdjust) [exposureAdjustFilter setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:@"inputEV"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender toolTip]]; break; case 5: // rotate rotateTransform = [NSAffineTransform transform]; [rotateTransform rotateByDegrees:[sender floatValue]]; deg = ([sender floatValue]*M_PI)/180.0; if (deg && fjLayer) { x = ((fjLayer->geo.w)-((fjLayer->geo.w)*cos(deg)-(fjLayer->geo.h)*sin(deg)))/2; y = ((fjLayer->geo.h)-((fjLayer->geo.w)*sin(deg)+(fjLayer->geo.h)*cos(deg)))/2; } rototranslateTransform = [NSAffineTransform transform]; [rototranslateTransform translateXBy:x yBy:y]; [rotateTransform appendTransform:rototranslateTransform]; [rotateTransform concat]; [rototranslateTransform concat]; [rotateFilter setValue:rotateTransform forKey:@"inputTransform"]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender toolTip]]; break; case 6: // traslate X if (fjLayer) fjLayer->geo.x = [sender floatValue]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender toolTip]]; break; case 7: // traslate Y if (fjLayer) fjLayer->geo.y = [sender floatValue]; [imageParams setValue:[NSNumber numberWithFloat:[sender floatValue]] forKey:[sender toolTip]]; break; default: break; } [lock unlock]; [pool release]; } - (void)setBlendMode:(NSString *)mode { if (layer) layer->blendMode = mode; } - (void)setFilterCenterFromMouseLocation:(NSPoint)where { CIVector *centerVector = nil; //[lock lock]; centerVector = [CIVector vectorWithX:where.x Y:where.y]; #if 0 [effectFilter setValue:centerVector forKey:@"inputCenter"]; #endif //[lock unlock]; } - (void)renderPreview:(CVPixelBufferRef)frame { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if ([self doPreview] && previewTarget) { // scale the frame to fit the preview if (![previewTarget isHiddenOrHasHiddenAncestor]) [previewTarget renderFrame:frame]; } [pool release]; } - (void)initFilters { // Create CIFilters used for both preview and main frame if (!colorCorrectionFilter) { colorCorrectionFilter = [[CIFilter filterWithName:@"CIColorControls"] retain]; // Color filter [colorCorrectionFilter setDefaults]; // set the filter to its default values } if (!exposureAdjustFilter) { exposureAdjustFilter = [[CIFilter filterWithName:@"CIExposureAdjust"] retain]; [exposureAdjustFilter setDefaults]; // adjust exposure [exposureAdjustFilter setValue:[NSNumber numberWithFloat:0.0] forKey:@"inputEV"]; } // rotate if (!rotateFilter) { NSAffineTransform *rotateTransform = [NSAffineTransform transform]; rotateFilter = [[CIFilter filterWithName:@"CIAffineTransform"] retain]; [rotateTransform rotateByDegrees:0.0]; [rotateFilter setValue:rotateTransform forKey:@"inputTransform"]; } if (!translateFilter) { translateFilter = [[CIFilter filterWithName:@"CIAffineTransform"] retain]; NSAffineTransform *translateTransform = [NSAffineTransform transform]; [translateTransform translateXBy:0.0 yBy:0.0]; [translateFilter setValue:translateTransform forKey:@"inputTransform"]; } if (!scaleFilter) { scaleFilter = [[CIFilter filterWithName:@"CIAffineTransform"] retain]; //CIFilter *scaleFilter = [CIFilter filterWithName:@"CILanczosScaleTransform"]; [scaleFilter setDefaults]; // set the filter to its default values } //[scaleFilter setValue:[NSNumber numberWithFloat:scaleFactor] forKey:@"inputScale"]; if (!compositeFilter) { compositeFilter = [[CIFilter filterWithName:@"CISourceOverCompositing"] retain]; // Composite filter [compositeFilter setDefaults]; } //[CIAlphaFade class]; if (!alphaFilter) { alphaFilter = [[CIFilter filterWithName:@"CIAlphaFade"] retain]; // AlphaFade filter [alphaFilter setDefaults]; // XXX - setDefaults doesn't work properly #if MAC_OS_X_VERSION_10_6 [alphaFilter setValue:[NSNumber numberWithFloat:1.0] forKey:@"outputOpacity"]; // set default value #else [alphaFilter setValue:[NSNumber numberWithFloat:0.5] forKey:@"outputOpacity"]; // set default value #endif } filtersInitialized = true; } - (CVPixelBufferRef)currentFrame { CVPixelBufferRef frame; [lock lock]; frame = CVPixelBufferRetain(currentFrame); [lock unlock]; return frame; } - (void)frameFiltered:(void *)buffer { // we have been notified that the frame has been filtered // and 'buffer' holds the data for the filtered image // (it will still have the some pixelformat and dimensions of original image) // XXX - actually we don't store the filtered image, but we use it just for preview if (layer && doPreview) { CVPixelBufferRef pixelBufferFiltered; CVReturn cvRet = CVPixelBufferCreateWithBytes ( NULL, layer->width(), layer->height(), k32ARGBPixelFormat, buffer, layer->width()*4, NULL, NULL, NULL, &pixelBufferFiltered ); // TODO - Error Messages if (cvRet == noErr) { [self renderPreview:pixelBufferFiltered]; CVPixelBufferRelease(pixelBufferFiltered); } } } - (void)startPreview { doPreview = YES; } - (void)start { if (!layer) { /* TODO - avoid creating a CVLayer directly, we should only know about CVCocoaLayer here */ CVLayer *cvLayer = new CVLayer(self); if (freej) { // TODO Geometry should expose a proper API Context *ctx = [freej getContext]; /* cvLayer->geo.w = ctx->screen->geo.w; cvLayer->geo.h = ctx->screen->geo.h; */ cvLayer->init(ctx->screen->geo.w, ctx->screen->geo.h, 32); } else { cvLayer->init(); } cvLayer->activate(); layer = (CVCocoaLayer *)cvLayer; } } - (void)stop { if (layer) { layer->deactivate(); delete layer; layer = NULL; } } - (CVPreview *)getPreviewTarget { return previewTarget; } - (void)setPreviewTarget:(CVPreview *)targetView { [lock lock]; previewTarget = targetView; [lock unlock]; } - (void)stopPreview { doPreview = NO; } - (void)lock { [lock lock]; } - (void)unlock { [lock unlock]; } - (bool)isVisible { if (layer) return layer->isVisible(); return NO; } - (void)activate { if (layer) { // ensure activating the underlying layer ... // this won't do anything if the layer is already active layer->activate(); if (freej) { Layer *fjLayer = layer->fjLayer(); if (!fjLayer->screen) { Context *ctx = [freej getContext]; ctx->screen->add_layer(layer->fjLayer()); } } } } - (NSString *)blendMode { if (layer) return layer->blendMode; return NULL; } - (void)deactivate { if (layer) layer->deactivate(); } - (void)rotateBy:(float)deg { if (layer) { } } - (void)translateXby:(float)x Yby:(float)y { if (layer) layer->setOrigin(x, y); } - (void)toggleFilters { doFilters = doFilters?false:true; } - (void)toggleVisibility { if (layer) if (layer->isActive()) layer->deactivate(); else layer->activate(); } - (void)togglePreview { doPreview = doPreview?NO:YES; } - (BOOL)doPreview { return doPreview; } - (char *)name { //if (layer) // return layer->fj_name(); if (layerView) return (char *)[[layerView toolTip] UTF8String]; return (char*)"CVCocoaLayer"; } - (Linklist *)activeFilters { NSMutableArray *result = nil; if (layer) { Layer *fjLayer = layer->fjLayer(); if (fjLayer) { return &fjLayer->filters; } } return NULL; } - (int)width { if (layer) { return layer->width(); } else if (freej) { Context *ctx = [freej getContext]; return ctx->screen->geo.w; } return 0; } - (int)height { if (layer) { return layer->height(); } else if (freej) { Context *ctx = [freej getContext]; return ctx->screen->geo.h; } return 0; } - (void)setRepeat:(BOOL)repeat { wantsRepeat = repeat; } - (BOOL)wantsRepeat { return wantsRepeat; } @synthesize freej; @synthesize layer; @synthesize layerView; @synthesize wantsRepeat; @synthesize doPreview; @synthesize doFilters; @end