CoreImageView.m
/* |
File: CoreImageView.m |
Abstract: n/a |
Version: 2.1 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2014 Apple Inc. All Rights Reserved. |
*/ |
#import "CoreImageView.h" |
#import "FunHouseWindowController.h" |
#import "FunHouseDocument.h" |
#import "EffectStack.h" |
#import "ParameterView.h" |
#define originHandleSize 4.0 |
@implementation CoreImageView |
/* |
Initialzation |
*/ |
- (id)initWithFrame: (NSRect)frameRect |
{ |
if((self = [super initWithFrame:frameRect]) != nil) |
{ |
} |
return self; |
} |
- (void)awakeFromNib |
{ |
if (initialized) |
return; |
initialized = YES; |
displayingPoints = NO; |
movingNow = NO; |
// we set up a tracking region so we can get mouseEntered and mouseExited events |
lastTrack = [self addTrackingRect:[self bounds] owner:self userData:nil assumeInside:NO]; |
viewTransformScale = 1.0; |
viewTransformOffsetX = 0.0; |
viewTransformOffsetY = 0.0; |
} |
- (void)dealloc |
{ |
[super dealloc]; |
} |
/* |
View properties |
*/ |
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent |
{ |
return YES; |
} |
- (BOOL)isOpaque |
{ |
return YES; |
} |
/* |
Accessors and Setters |
*/ |
- (CIContext *)context |
{ |
return [[NSGraphicsContext currentContext] CIContext]; |
} |
- (void)setFunHouseWindowController:(FunHouseWindowController *)c |
{ |
controller = c; |
} |
// set the scale for the view transform |
- (void)setViewTransformScale:(CGFloat)scale |
{ |
viewTransformScale = scale; |
} |
// set the offset for the view transform |
- (void)setViewTransformOffsetX:(CGFloat)x andY:(CGFloat)y |
{ |
viewTransformOffsetX = x; |
viewTransformOffsetY = y; |
} |
// return YES if the view transform is not scaled at 1::1 |
- (BOOL)isScaled |
{ |
return viewTransformScale != 1.0; |
} |
// this is our glue for handling undo of filter changes |
// call this when we want to set a filter value for a key |
- (void)setFilter:(CIFilter *)f value:(id)val forKey:(NSString *)key |
{ |
FunHouseDocument *d; |
id oldValue; |
d = (FunHouseDocument *)[controller document]; |
oldValue = [[f valueForKey:key] retain]; |
[f setValue:val forKey:key]; |
// this is the special way the undo manager saves old object values so it can undo properly |
[[[d undoManager] prepareWithInvocationTarget:self] setFilter:f value:oldValue forKey:key]; |
[oldValue release]; |
} |
// call this to get the undo string (shown in the edit menu) |
- (NSString *)actionNameForFilter:(CIFilter *)f key:(NSString *)key |
{ |
NSString *str = unInterCap([key substringFromIndex:5]); |
return [NSString stringWithFormat:@"Change to %@ %@", [CIFilter localizedNameForFilterName:NSStringFromClass([f class])], str, nil]; |
} |
// this is our glue for handling undo image and text placement attribute changes |
// call this when we want to set a effect stack dictionary value for a key |
- (void)setDict:(NSMutableDictionary *)dict value:(id)val forKey:(NSString *)key |
{ |
FunHouseDocument *d; |
id oldValue; |
d = (FunHouseDocument *)[controller document]; |
oldValue = [[dict valueForKey:key] retain]; |
[dict setValue:val forKey:key]; |
[[[d undoManager] prepareWithInvocationTarget:self] setDict:dict value:oldValue forKey:key]; |
[oldValue release]; |
} |
// call this to set the undo string for a filter |
- (void)setActionNameForFilter:(CIFilter *)f key:(NSString *)key |
{ |
FunHouseDocument *d; |
d = (FunHouseDocument *)[controller document]; |
[[d undoManager] setActionName:[self actionNameForFilter:f key:key]]; |
} |
// call this when setting a text placement change to an attribute to set the undo string |
- (void)setActionNameForTextLayerKey:(NSString *)key |
{ |
FunHouseDocument *d; |
d = (FunHouseDocument *)[controller document]; |
NSString *str = unInterCap([key substringFromIndex:5]); |
[[d undoManager] setActionName:[NSString stringWithFormat:@"Change to Text %@", str, nil]]; |
} |
// glue code to set the image offset, saving the proper info for undo |
- (void)setImageByIndex:(NSNumber *)index offsetX:(NSNumber *)x andY:(NSNumber *)y |
{ |
FunHouseDocument *d; |
EffectStack *es; |
NSPoint oldoffset; |
d = (FunHouseDocument *)[controller document]; |
es = [d effectStack]; |
oldoffset = [es offsetAtIndex:parmIndex]; |
[es setImageLayer:[index integerValue] offset:NSMakePoint([x doubleValue], [y doubleValue])]; |
[[[d undoManager] prepareWithInvocationTarget:self] setImageByIndex:index offsetX:[NSNumber numberWithDouble:oldoffset.x] andY:[NSNumber numberWithDouble:oldoffset.y]]; |
} |
// glue code to set the text placement offset (baseline point), saving the proper info for undo |
- (void)setTextByIndex:(NSNumber *)index offsetX:(NSNumber *)x andY:(NSNumber *)y |
{ |
FunHouseDocument *d; |
EffectStack *es; |
NSPoint oldoffset; |
d = (FunHouseDocument *)[controller document]; |
es = [d effectStack]; |
oldoffset = [es offsetAtIndex:parmIndex]; |
[es setTextLayer:[index integerValue] offset:NSMakePoint([x doubleValue], [y doubleValue])]; |
[[[d undoManager] prepareWithInvocationTarget:self] setTextByIndex:index offsetX:[NSNumber numberWithDouble:oldoffset.x] andY:[NSNumber numberWithDouble:oldoffset.y]]; |
} |
/* |
Drawing |
*/ |
// draw an onscreen handle for an image origin, text origin, or filter point |
// the handle is a "center symbol" - a circle with crosshairs through it. |
// the handle is labelled with the string "str". |
// all items are "shadowed" |
- (void)drawPoint:(NSPoint)pt label:(NSString *)str intoContext:(CGContextRef)cg |
{ |
CGRect R; |
CGFloat size; |
pt.x = pt.x * viewTransformScale + viewTransformOffsetX; |
pt.y = pt.y * viewTransformScale + viewTransformOffsetY; |
size = originHandleSize + 0.5; |
pt.x = floor(pt.x) + 0.5; |
pt.y = floor(pt.y) + 0.5; |
CGContextSetRGBStrokeColor(cg, 0.0, 0.0, 0.0, 1.0); |
CGContextSetLineWidth(cg, 1.0); |
CGContextMoveToPoint(cg, pt.x - originHandleSize * 2.0 + 1.0, pt.y - 1.0); |
CGContextAddLineToPoint(cg, pt.x + originHandleSize * 2.0 + 1.0, pt.y - 1.0); |
CGContextStrokePath(cg); |
R = CGRectMake(pt.x - size + 1.0, pt.y - size - 1.0, size * 2.0, size * 2.0); |
CGContextStrokeEllipseInRect(cg, R); |
CGContextStrokePath(cg); |
CGContextMoveToPoint(cg, pt.x + 1.0, pt.y - originHandleSize * 2.0 - 1.0); |
CGContextAddLineToPoint(cg, pt.x + 1.0, pt.y + originHandleSize * 2.0 - 1.0); |
CGContextStrokePath(cg); |
CGContextSetRGBStrokeColor(cg, 1.0, 1.0, 1.0, 1.0); |
CGContextSetLineWidth(cg, 1.0); |
CGContextMoveToPoint(cg, pt.x - originHandleSize * 2.0, pt.y); |
CGContextAddLineToPoint(cg, pt.x + originHandleSize * 2.0, pt.y); |
CGContextStrokePath(cg); |
R = CGRectMake(pt.x - size, pt.y - size, size * 2.0, size * 2.0); |
CGContextStrokeEllipseInRect(cg, R); |
CGContextStrokePath(cg); |
CGContextMoveToPoint(cg, pt.x, pt.y - originHandleSize * 2.0); |
CGContextAddLineToPoint(cg, pt.x, pt.y + originHandleSize * 2.0); |
CGContextStrokePath(cg); |
CGContextSetRGBFillColor(cg, 0.0, 0.0, 0.0, 1.0); |
if (!movingNow) |
{ |
NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cg flipped:NO]; |
[NSGraphicsContext setCurrentContext:graphicsContext]; |
[str drawAtPoint:NSMakePoint(pt.x + 4.5, pt.y + 1.5) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[NSFont systemFontOfSize:13.0], NSFontNameAttribute, [NSColor blackColor], NSForegroundColorAttributeName, nil]]; |
CGContextSetRGBFillColor(cg, 1.0, 1.0, 1.0, 1.0); |
[str drawAtPoint:NSMakePoint(pt.x + 3.0, pt.y + 3.0) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[NSFont systemFontOfSize:13.0], NSFontNameAttribute, [NSColor whiteColor], NSForegroundColorAttributeName, nil]]; |
} |
} |
// render origin handles using AppKit directly |
- (CIImage *)drawPoints:(CIImage *)im |
{ |
NSInteger i, count; |
CGFloat x, y, width, height; |
CIContext *context; |
CIFilter *f; |
NSEnumerator *e; |
CIVector *vec; |
NSDictionary *attr, *parameter; |
NSString *key, *typestring, *classstring, *type; |
NSArray *inputKeys; |
FunHouseDocument *d; |
EffectStack *es; |
NSPoint pt; |
NSAffineTransform *tr; |
NSAffineTransformStruct S; |
NSString *str, *str2, *localizedParameter; |
CGContextRef cg; |
CGLayerRef layer; |
NSRect bounds; |
CIImage *image; |
NSGraphicsContext *savedContext = [NSGraphicsContext currentContext]; |
context = [[NSGraphicsContext currentContext] CIContext]; |
bounds = [self bounds]; |
layer = [context createCGLayerWithSize:CGSizeMake(NSWidth(bounds), NSHeight(bounds)) info:nil]; |
cg = CGLayerGetContext(layer); |
d = (FunHouseDocument *)[controller document]; |
// enumerate filters, images, text placements in the effect stack (bottom-to-top) |
es = [d effectStack]; |
count = [es layerCount]; |
for (i = 0; i < count; i++) |
{ |
// if the layer isn't enabled, don't show the handle either |
if (![es layerEnabled:i]) |
continue; |
type = [es typeAtIndex:i]; |
if ([type isEqualToString:@"filter"]) |
{ |
// filter effect stack element |
f = [es filterAtIndex:i]; |
if (f == nil) { |
CGLayerRelease(layer); |
return nil; |
} |
attr = [f attributes]; |
// iterate over parameters, look for parameters containing an origin to be displayed |
inputKeys = [f inputKeys]; |
e = [inputKeys objectEnumerator]; |
while ((key = [e nextObject]) != nil) |
{ |
parameter = [attr objectForKey:key]; |
classstring = [parameter objectForKey:kCIAttributeClass]; |
localizedParameter = [parameter objectForKey:kCIAttributeDisplayName]; |
str = [NSString stringWithFormat:@"%@ %@", [CIFilter localizedNameForFilterName:NSStringFromClass([f class])], |
localizedParameter, nil]; |
if ([classstring isEqualToString:@"CIVector"]) |
{ |
typestring = [parameter objectForKey:kCIAttributeType]; |
if ([typestring isEqualToString:kCIAttributeTypePosition]) |
{ |
// 2D position (point) like a center |
vec = [f valueForKey:key]; |
pt.x = [vec X]; |
pt.y = [vec Y]; |
[self drawPoint:pt label:str intoContext:cg]; |
} |
else if ([typestring isEqualToString:kCIAttributeTypeRectangle]) |
{ |
// rectangle - show 4 handles, labelled properly |
vec = [f valueForKey:key]; |
// make the 4 points |
x = [vec X]; |
y = [vec Y]; |
width = [vec Z]; |
height = [vec W]; |
pt.x = x; |
pt.y = y; |
str2 = [str stringByAppendingString:@" bottom left"]; |
[self drawPoint:pt label:str2 intoContext:cg]; |
pt.x = x + width; |
pt.y = y; |
str2 = [str stringByAppendingString:@" bottom right"]; |
[self drawPoint:pt label:str2 intoContext:cg]; |
pt.x = x; |
pt.y = y + height; |
str2 = [str stringByAppendingString:@" top left"]; |
[self drawPoint:pt label:str2 intoContext:cg]; |
pt.x = x + width; |
pt.y = y + height; |
str2 = [str stringByAppendingString:@" top right"]; |
[self drawPoint:pt label:str2 intoContext:cg]; |
} |
else if ([typestring isEqualToString:kCIAttributeTypePosition3]) |
{ |
// 3D position, only view the (x,y) components |
vec = [f valueForKey:key]; |
// make the 4 points |
x = [vec X]; |
y = [vec Y]; |
pt.x = x; |
pt.y = y; |
[self drawPoint:pt label:str intoContext:cg]; |
} |
} |
else if ([classstring isEqualToString:@"NSAffineTransform"]) |
{ |
// affine transform origin |
tr = [f valueForKey:key]; |
S = [tr transformStruct]; |
pt.x = S.tX; |
pt.y = S.tY; |
str2 = [str stringByAppendingString:@" origin"]; |
[self drawPoint:pt label:str2 intoContext:cg]; |
} |
} |
} |
else if ([type isEqualToString:@"image"]) |
{ |
// image effect stack element |
// show an image origin (in its center) |
CGRect r = [[es imageAtIndex:i] extent]; |
NSPoint offset = [es offsetAtIndex:i]; |
pt.x = offset.x + (r.origin.x + r.size.width * 0.5); |
pt.y = offset.y + (r.origin.y + r.size.height * 0.5); |
str = [[es filenameAtIndex:i] stringByAppendingString:@" center"]; |
[self drawPoint:pt label:str intoContext:cg]; |
} |
else if ([type isEqualToString:@"text"]) |
{ |
// text effect stack element |
// show a text origin (baseline point) |
NSPoint offset = [es offsetAtIndex:i]; |
pt.x = offset.x; |
pt.y = offset.y; |
[self drawPoint:pt label:@"text origin" intoContext:cg]; |
} |
} |
image = [[[CIImage alloc] initWithCGLayer:layer] autorelease]; |
CGLayerRelease(layer); |
f = [CIFilter filterWithName:@"CISourceOverCompositing"]; |
[f setValue:im forKey:@"inputBackgroundImage"]; |
[f setValue:image forKey:@"inputImage"]; |
[NSGraphicsContext setCurrentContext:savedContext]; |
return [f valueForKey:@"outputImage"]; |
} |
// compute the whole core image graph for the view (not yet evaluated!) |
- (CIImage *)coreImageResult |
{ |
FunHouseDocument *d = (FunHouseDocument *)[controller document]; |
EffectStack *es = [d effectStack]; |
// if the effect stack core image graph isn't complete (dued to a missing image) then |
// we do not redraw |
if ([es hasMissingImage]) |
return nil; |
// compute the core image graph for our view |
CIImage *res = [es coreImageResultForRect:[self bounds]]; |
// overlay onto a constant color (black) to show alpha |
CIFilter *f = [CIFilter filterWithName:@"CIConstantColorGenerator"]; |
[f setValue:[CIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:1.0] forKey:@"inputColor"]; |
CIImage *black = [f valueForKey:@"outputImage"]; |
if (res == nil) |
return black; |
f = [CIFilter filterWithName:@"CISourceOverCompositing"]; |
[f setValue:black forKey:@"inputBackgroundImage"]; |
[f setValue:res forKey:@"inputImage"]; |
res = [f valueForKey:@"outputImage"]; |
if (viewTransformScale != 1.0 || viewTransformOffsetX != 0.0 || viewTransformOffsetY != 0.0) |
{ |
// if the view transform is not unity, apply an affine transform to view the result |
// this is necessary when the input image is larger than the screen... |
f = [CIFilter filterWithName:@"CIAffineTransform"]; |
NSAffineTransform *t = [NSAffineTransform transform]; |
[t scaleBy:viewTransformScale]; |
[t translateXBy:viewTransformOffsetX yBy:viewTransformOffsetY]; |
[f setValue:t forKey:@"inputTransform"]; |
[f setValue:res forKey:@"inputImage"]; |
res = [f valueForKey:@"outputImage"]; |
} |
return res; |
} |
- (void)viewBoundsDidChange:(NSRect)bounds |
{ |
// we set up a tracking region so we can get mouseEntered and mouseExited events |
[self removeTrackingRect:lastTrack]; |
lastTrack = [self addTrackingRect:bounds owner:self userData:nil assumeInside:NO]; |
} |
- (void)drawRect:(NSRect)r inCIContext:(CIContext *)context |
{ |
CGRect cgr; |
CIImage *im; |
cgr = CGRectMake(r.origin.x, r.origin.y, r.size.width, r.size.height); |
// note: surround a core image evaluation with its own autorelease pool to prevent a huge amount of buildup |
// during a slider drag, for instance. |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
// compute the core image graph for the view (based on the effect stack) |
im = [self coreImageResult]; |
// display origin handles when the mouse is inside the view, and when a modal vwindow isn't present... |
if (displayingPoints && [NSApp modalWindow] == nil) |
im = [self drawPoints:im]; |
// if successful, draw the image |
if (im != nil && context != nil) |
{ |
if ([NSGraphicsContext currentContextDrawingToScreen]) |
{ |
[context drawImage:im inRect:cgr fromRect:cgr]; |
} |
else |
{ |
CGImageRef cgImage; |
cgImage = [context createCGImage:im fromRect:cgr format:kCIFormatRGBA16 colorSpace:nil]; |
if (cgImage != NULL) |
{ |
CGContextDrawImage ([[NSGraphicsContext currentContext] |
graphicsPort], cgr, cgImage); |
CGImageRelease (cgImage); |
} |
} |
} |
[pool release]; |
} |
/* |
Event handling |
*/ |
// when entering the view, turn on origin handle display |
- (void)mouseEntered:(NSEvent *)theEvent |
{ |
displayingPoints = YES; |
[self setNeedsDisplay:YES]; |
} |
// when exiting the view, turn off origin handle display |
- (void)mouseExited:(NSEvent *)theEvent |
{ |
displayingPoints = NO; |
[self setNeedsDisplay:YES]; |
} |
// standard procedure for view subclasses to handle mouse down events |
- (void)mouseDown:(NSEvent *)event |
{ |
NSInteger i, count; |
CGFloat dx, dy, dist, closest, x, y, width, height; |
CIFilter *f; |
NSEnumerator *e; |
CIVector *vec; |
NSDictionary *attr, *parameter; |
NSString *key, *typestring, *classstring, *type; |
NSArray *inputKeys; |
NSPoint pt, pp; |
CGPoint p; |
FunHouseDocument *d; |
EffectStack *es; |
NSAffineTransform *tr; |
NSAffineTransformStruct S; |
movingNow = YES; |
pt = [self convertPoint:[event locationInWindow] fromView: nil]; |
d = (FunHouseDocument *)[controller document]; |
es = [d effectStack]; |
p = CGPointMake(pt.x, pt.y); |
// account for the view transform |
pt.x = (p.x - viewTransformOffsetX) / viewTransformScale; |
pt.y = (p.y - viewTransformOffsetY) / viewTransformScale; |
parmIndex = -1; |
parmKey = nil; |
parmMode = pmNone; |
count = [es layerCount]; |
// explicitly group all the changes during the entire mouse move |
[[d undoManager] beginUndoGrouping]; |
savedActionName = nil; |
// pick which origin handle is nearest the mouse point, identify its type for |
// easy handling in the mouseDragged code |
// enumerate the effect stack layers (filter, image, text) |
// this is a pick, so we operate in inverted (front-to-back) order |
// note: this must exactly match the drawPoints: method |
for (i = count - 1; i >= 0; i--) |
{ |
type = [es typeAtIndex:i]; |
if ([type isEqualToString:@"filter"]) |
{ |
// filter effect stack element |
f = [es filterAtIndex:i]; |
if (f == nil) |
return; |
attr = [f attributes]; |
// iterate over parameters, decide which parameter we are closest to, set up to modify it in mouse loop |
inputKeys = [f inputKeys]; |
e = [inputKeys objectEnumerator]; |
while ((key = [e nextObject]) != nil) |
{ |
parameter = [attr objectForKey:key]; |
classstring = [parameter objectForKey:kCIAttributeClass]; |
if ([classstring isEqualToString:@"CIVector"]) |
{ |
typestring = [parameter objectForKey: kCIAttributeType]; |
if ([typestring isEqualToString: kCIAttributeTypePosition]) |
{ |
// 2D position (point) such as an inputCenter |
vec = [f valueForKey:key]; |
pp.x = [vec X]; |
pp.y = [vec Y]; |
dx = pp.x - pt.x; |
dy = pp.y - pt.y; |
dist = dx*dx + dy*dy; |
if (parmMode == pmNone || dist < closest) |
{ |
parmIndex = i; |
parmKey = key; |
closest = dist; |
parmMode = pmPoint; |
} |
} |
else if ([typestring isEqualToString: kCIAttributeTypeRectangle]) |
{ |
// rectangle such as an inputRectangle |
// enumerate the 4 points |
vec = [f valueForKey:key]; |
// make the 4 points |
x = [vec X]; |
y = [vec Y]; |
width = [vec Z]; |
height = [vec W]; |
pp.x = x + width; |
pp.y = y + height; |
dx = pp.x - pt.x; |
dy = pp.y - pt.y; |
dist = dx*dx + dy*dy; |
if (parmMode == pmNone || dist < closest) |
{ |
parmIndex = i; |
parmKey = key; |
closest = dist; |
parmMode = pmTopRight; |
} |
pp.x = x; |
pp.y = y + height; |
dx = pp.x - pt.x; |
dy = pp.y - pt.y; |
dist = dx*dx + dy*dy; |
if (parmMode == pmNone || dist < closest) |
{ |
parmIndex = i; |
parmKey = key; |
closest = dist; |
parmMode = pmTopLeft; |
} |
pp.x = x + width; |
pp.y = y; |
dx = pp.x - pt.x; |
dy = pp.y - pt.y; |
dist = dx*dx + dy*dy; |
if (parmMode == pmNone || dist < closest) |
{ |
parmIndex = i; |
parmKey = key; |
closest = dist; |
parmMode = pmBottomRight; |
} |
pp.x = x; |
pp.y = y; |
dx = pp.x - pt.x; |
dy = pp.y - pt.y; |
dist = dx*dx + dy*dy; |
if (parmMode == pmNone || dist < closest) |
{ |
parmIndex = i; |
parmKey = key; |
closest = dist; |
parmMode = pmBottomLeft; |
} |
} |
else if ([typestring isEqualToString:kCIAttributeTypePosition3]) |
{ |
// 3D position (we only care about x and y in this code) |
vec = [f valueForKey:key]; |
// make the 4 points |
x = [vec X]; |
y = [vec Y]; |
pp.x = x; |
pp.y = y; |
dx = pp.x - pt.x; |
dy = pp.y - pt.y; |
dist = dx*dx + dy*dy; |
if (parmMode == pmNone || dist < closest) |
{ |
parmIndex = i; |
parmKey = key; |
closest = dist; |
parmMode = pm3DPoint; |
} |
} |
} |
else if ([classstring isEqualToString:@"NSAffineTransform"]) |
{ |
// affine transform origin |
tr = [f valueForKey:key]; |
S = [tr transformStruct]; |
pp.x = S.tX; |
pp.y = S.tY; |
dx = pp.x - pt.x; |
dy = pp.y - pt.y; |
dist = dx*dx + dy*dy; |
if (parmMode == pmNone || dist < closest) |
{ |
parmIndex = i; |
parmKey = key; |
closest = dist; |
parmMode = pmTransformOffset; |
} |
} |
} |
} |
else if ([type isEqualToString:@"image"]) |
{ |
// image effect stack element |
CGRect r = [[es imageAtIndex:i] extent]; |
NSPoint offset = [es offsetAtIndex:i]; |
pp.x = offset.x + (r.origin.x + r.size.width * 0.5); |
pp.y = offset.y + (r.origin.y + r.size.height * 0.5); |
dx = pp.x - pt.x; |
dy = pp.y - pt.y; |
dist = dx*dx + dy*dy; |
if (parmMode == pmNone || dist < closest) |
{ |
parmIndex = i; |
closest = dist; |
parmMode = pmImageOffset; |
} |
} |
else if ([type isEqualToString:@"text"]) |
{ |
// text effect stack element |
NSPoint offset = [es offsetAtIndex:i]; |
pp.x = offset.x; |
pp.y = offset.y; |
dx = pp.x - pt.x; |
dy = pp.y - pt.y; |
dist = dx*dx + dy*dy; |
if (parmMode == pmNone || dist < closest) |
{ |
parmIndex = i; |
closest = dist; |
parmMode = pmTextOffset; |
} |
} |
} |
// save a string so we have an easier time evaluating the undo string at mouse up |
if (parmMode == pmImageOffset) |
savedActionName = @"Image Move"; |
else if (parmMode == pmTextOffset) |
savedActionName = @"Text Move"; |
else if (parmMode!= pmNone) |
savedActionName = [[self actionNameForFilter:f key:parmKey] retain]; |
[self setNeedsDisplay:YES]; |
} |
// handle mouse movement |
- (void)mouseDragged:(NSEvent *)event |
{ |
CGFloat left, right, top, bottom, temp; |
CIFilter *f; |
CIVector *vec; |
NSPoint pt, offset; |
CGPoint p; |
CGRect r; |
FunHouseDocument *d; |
EffectStack *es; |
NSAffineTransform *tr; |
NSAffineTransformStruct S; |
pt = [self convertPoint:[event locationInWindow] fromView: nil]; |
if (parmMode == pmNone || parmIndex == -1) |
return; |
d = (FunHouseDocument *)[controller document]; |
es = [d effectStack]; |
p = CGPointMake(pt.x, pt.y); |
// account for the view transform |
pt.x = (p.x - viewTransformOffsetX) / viewTransformScale; |
pt.y = (p.y - viewTransformOffsetY) / viewTransformScale; |
f = [es filterAtIndex:parmIndex]; |
if (parmMode != pmNone) |
{ |
// dispatch on the type of thing being moved (note: each thing gets an origin handle) |
switch (parmMode) |
{ |
case pmPoint: |
// move some filter's point |
[self setFilter:f value:[CIVector vectorWithX:pt.x Y:pt.y Z:0 W:0] forKey:parmKey]; |
break; |
case pmBottomLeft: |
case pmBottomRight: |
case pmTopLeft: |
case pmTopRight: |
// move some filter's corner of a rectangle |
// get vector as rectangle |
vec = [f valueForKey:parmKey]; |
left = [vec X]; |
bottom = [vec Y]; |
right = [vec X] + [vec Z]; |
top = [vec Y] + [vec W]; |
// replace the salient portion |
switch (parmMode) |
{ |
case pmBottomLeft: |
left = pt.x; |
bottom = pt.y; |
break; |
case pmBottomRight: |
right = pt.x; |
bottom = pt.y; |
break; |
case pmTopLeft: |
left = pt.x; |
top = pt.y; |
break; |
case pmTopRight: |
right = pt.x; |
top = pt.y; |
break; |
default: |
break; |
} |
// check for inversion along either axis |
if (left > right) |
{ |
temp = left; |
left = right; |
right = temp; |
parmMode = ((parmMode - pmBottomLeft) ^ 1) + pmBottomLeft; |
} |
if (bottom > top) |
{ |
temp = top; |
top = bottom; |
bottom = temp; |
parmMode = ((parmMode - pmBottomLeft) ^ 2) + pmBottomLeft; |
} |
// pack back into rectangle |
[self setFilter:f value:[CIVector vectorWithX:left Y:bottom Z:right - left W:top - bottom] forKey:parmKey]; |
break; |
case pmImageOffset: |
// move an image layer |
r = [[es imageAtIndex:parmIndex] extent]; |
offset.x = pt.x - (r.origin.x + r.size.width * 0.5); |
offset.y = pt.y - (r.origin.y + r.size.height * 0.5); |
[self setImageByIndex:[NSNumber numberWithInteger:parmIndex] offsetX:[NSNumber numberWithDouble:offset.x] andY:[NSNumber numberWithDouble:offset.y]]; |
break; |
case pmTextOffset: |
// move a text placement |
offset.x = pt.x; |
offset.y = pt.y; |
[self setTextByIndex:[NSNumber numberWithInteger:parmIndex] offsetX:[NSNumber numberWithDouble:offset.x] andY:[NSNumber numberWithDouble:offset.y]]; |
break; |
case pmTransformOffset: |
// move an affine transform offset |
tr = [f valueForKey:parmKey]; |
S = [tr transformStruct]; |
S.tX = pt.x; |
S.tY = pt.y; |
[tr setTransformStruct:S]; |
[self setFilter:f value:tr forKey:parmKey]; |
break; |
case pm3DPoint: |
// move a 3D point (only the x and y matters) |
vec = [f valueForKey:parmKey]; |
temp = [vec Z]; |
// pack back into rectangle |
[self setFilter:f value:[CIVector vectorWithX:pt.x Y:pt.y Z:temp] forKey:parmKey]; |
break; |
default: |
break; |
} |
[self setNeedsDisplay:YES]; |
} |
} |
// the standard procedure overridden in view subclasses to handle the mouse up event |
- (void)mouseUp:(NSEvent *)event |
{ |
FunHouseDocument *d; |
movingNow = NO; |
d = (FunHouseDocument *)[controller document]; |
// now that we have finished the move, set the action name for the undo group |
[[d undoManager] setActionName:savedActionName]; |
// explicitly group all the changes during the entire mouse move |
[[d undoManager] endUndoGrouping]; |
if (savedActionName != nil) |
[savedActionName release]; |
[self setNeedsDisplay:YES]; |
} |
@end |
Copyright © 2014 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2014-05-07