|
/* |
File: AnnotationDocument.m |
Abstract: The AnnotationDocument is the document object of the app. It contains the layers. |
Version: 1.3 |
|
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) 2012 Apple Inc. All Rights Reserved. |
|
*/ |
|
#import "AnnotationDocument.h" |
#import "AnnotationView.h" |
|
@interface AnnotationDocument (private) |
- (void)createOutputImage; |
@end |
|
@implementation AnnotationDocument |
|
#pragma mark - |
#pragma mark Init and Dealloc |
|
|
- (id)initWithImageURL:(NSURL*)inURL renderView:(AnnotationView*)inRenderView |
{ |
CGRect documentRect; |
CIFilter *colorFilter = [CIFilter filterWithName:@"CIConstantColorGenerator"]; |
CIFilter *cropFilter = [CIFilter filterWithName:@"CICrop"]; |
|
self = [super init]; |
if(!self) |
return nil; |
renderView = inRenderView; |
// create the layer that holds the image we want to annotate |
imageLayer = [[CIImageLayer alloc] initWithDelegate:self imageURL:inURL]; |
documentRect = [imageLayer getRect]; // the image determines the size of our docment |
// create the text annotation layer |
textLayer = [[CITextLayer alloc] initWithDelegate:self targetRect:documentRect ciContext:[renderView ciContext]]; |
// create the paint annotation layer |
paintLayer = [[CIPaintLayer alloc] initWithDelegate:self targetRect:documentRect]; |
// create the peel off filter - page curl transition |
peelOffFilter = [[CIFilter filterWithName:@"CIPageCurlTransition"] retain]; |
[peelOffFilter setDefaults]; |
annotationPeel = 0.0; // by default show both images |
[peelOffFilter setValue:[NSNumber numberWithDouble:annotationPeel] forKey:@"inputTime"]; |
// set the shine image as it is static |
[peelOffFilter setValue:[CIImage imageWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"restrictedshine" ofType: @"tiff"]]] forKey:@"inputShadingImage"]; |
[peelOffFilter setValue:[NSNumber numberWithDouble:0.3] forKey:@"inputAngle"]; // this is the angle at which we want to peel off the annotations |
// create a filter to composite the text and paint annotations together |
annotationCompositeFilter = [[CIFilter filterWithName:@"CISourceOverCompositing"] retain]; |
[annotationCompositeFilter setDefaults]; |
// create a filter that composites the annotation composite with a backing/parchment image for the backside of the peel off |
annotationCompositeBackingFilter = [[CIFilter filterWithName:@"CISourceOverCompositing"] retain]; |
[annotationCompositeBackingFilter setDefaults]; |
[colorFilter setValue:[CIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.8] forKey:@"inputColor"]; |
[cropFilter setValue:[colorFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; |
[cropFilter setValue:[CIVector vectorWithX:documentRect.origin.x Y:documentRect.origin.y Z:documentRect.size.width W:documentRect.size.height] forKey:@"inputRectangle"]; |
parchmentBackingImage = [[cropFilter valueForKey:@"outputImage"] retain]; |
// setup the view size |
[renderView setFrame:*(NSRect*)&documentRect]; |
[renderView setBounds:*(NSRect*)&documentRect]; |
[[renderView window] setMaxSize:*(NSSize*)&(documentRect.size)]; |
// setup if lens should be shown |
[imageLayer showLens:(annotationPeel <= 0.0)]; |
return self; |
} |
|
- (void)dealloc |
{ |
[renderView setImage:nil]; |
[textLayer release]; |
[paintLayer release]; |
[imageLayer release]; |
[peelOffFilter release]; |
[annotationCompositeFilter release]; |
[super dealloc]; |
} |
|
#pragma mark - |
#pragma mark Rendering methods |
|
- (void)createOutputImage |
{ |
CIImage *textImage = [textLayer getLayerImage]; |
CIImage *paintImage = [paintLayer getLayerImage]; |
|
// composite the text and paint annotations |
[annotationCompositeFilter setValue:textImage forKey:@"inputImage"]; |
[annotationCompositeFilter setValue:paintImage forKey:@"inputBackgroundImage"]; |
// special case the 'no peel' and 'full peel' states by leaving out the transition filter in those cases for better performance |
if(annotationPeel <= 0.0) |
{ |
[annotationCompositeBackingFilter setValue:[annotationCompositeFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; |
[annotationCompositeBackingFilter setValue:[imageLayer getLayerImage] forKey:@"inputBackgroundImage"]; |
outputImage = [annotationCompositeBackingFilter valueForKey:@"outputImage"]; |
} else { |
[annotationCompositeBackingFilter setValue:parchmentBackingImage forKey:@"inputImage"]; |
[annotationCompositeBackingFilter setValue:[annotationCompositeFilter valueForKey:@"outputImage"] forKey:@"inputBackgroundImage"]; |
[peelOffFilter setValue:[imageLayer getLayerImage] forKey:@"inputTargetImage"]; |
[peelOffFilter setValue:[annotationCompositeFilter valueForKey:@"outputImage"] forKey:@"inputImage"]; |
[peelOffFilter setValue:[annotationCompositeBackingFilter valueForKey:@"outputImage"] forKey:@"inputBacksideImage"]; |
outputImage = [peelOffFilter valueForKey:@"outputImage"]; |
} |
} |
|
|
- (void)exportImageToURL:(NSURL*)inURL |
{ |
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); |
NSDictionary *options = @{ kCIContextOutputColorSpace:(id)colorSpace }; |
CIContext *exportContext = [CIContext contextWithCGContext:[[[renderView window] graphicsContext] graphicsPort] options:options]; |
CGColorSpaceRelease(colorSpace); // Owned by the context. |
// create an image destination (ImageIO's way of saying we want to save to a file format) |
// note: public.jpeg denotes that we are saving to JPEG |
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithURL((CFURLRef)inURL, (CFStringRef)@"public.jpeg", 1, nil); |
|
if (imageDestination == NULL) { |
NSLog(@"Problem creating image destination."); |
return; |
} |
|
CGImageRef renderedImage = [exportContext createCGImage:outputImage fromRect:[outputImage extent]]; |
// add image to the ImageIO destination (specify the image we want to save) |
CGImageDestinationAddImage(imageDestination, renderedImage, NULL); |
// finalize: this saves the image to the JPEG format as data |
if (!CGImageDestinationFinalize(imageDestination)) |
{ |
NSLog(@"Problem writing JPEG file."); |
} |
CFRelease(imageDestination); |
CGImageRelease(renderedImage); |
} |
|
#pragma mark - |
#pragma mark User interaction methods |
|
- (void)setMode:(AnnotationEditingMode)inMode |
{ |
editMode = inMode; |
} |
|
- (void)setBrightness:(CGFloat)inValue |
{ |
[imageLayer setImageSetting:kBrightnessSetting value:inValue]; |
} |
|
- (void)setContrast:(CGFloat)inValue |
{ |
[imageLayer setImageSetting:kContrastSetting value:inValue]; |
} |
|
- (void)setSaturation:(CGFloat)inValue |
{ |
[imageLayer setImageSetting:kSaturationSetting value:inValue]; |
} |
|
- (void)mouseDown:(NSEvent *)theEvent |
{ |
if([theEvent modifierFlags] & NSAlternateKeyMask) // peel off the annotation layers |
{ |
BOOL dragActive = YES; |
NSPoint location; |
NSAutoreleasePool *myPool = nil; |
NSEvent* event = NULL; |
NSWindow *targetWindow = [renderView window]; |
|
|
myPool = [[NSAutoreleasePool alloc] init]; |
while (dragActive) |
{ |
event = [targetWindow nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask) |
untilDate:[NSDate distantFuture] |
inMode:NSEventTrackingRunLoopMode |
dequeue:YES]; |
|
if(!event) |
continue; |
location = [renderView convertPoint:[event locationInWindow] fromView:nil]; |
switch ([event type]) |
{ |
case NSLeftMouseDragged: |
annotationPeel = (location.x * 2.0 / [renderView bounds].size.width); |
[imageLayer showLens:(annotationPeel <= 0.0)]; |
[peelOffFilter setValue:[NSNumber numberWithDouble:annotationPeel] forKey:@"inputTime"]; |
[self refresh]; |
break; |
|
case NSLeftMouseUp: |
dragActive = NO; |
break; |
|
default: |
break; |
} |
} |
[myPool release]; |
} else { |
// handle mouse down events in the respective layer depending on the current edit mode |
switch(editMode) |
{ |
case kMagnifyingMode: |
[imageLayer mouseDown:theEvent view:renderView]; |
break; |
|
case kPaintMode: |
[paintLayer mouseDown:theEvent view:renderView]; |
break; |
|
case kTextMode: |
[textLayer mouseDown:theEvent view:renderView]; |
break; |
} |
} |
} |
|
#pragma mark - |
#pragma mark Delegate methods |
|
/* |
|
Delegate methods |
|
*/ |
|
- (void)refresh:(CGRect)dirtyRect |
{ |
[self createOutputImage]; |
[renderView setImage:outputImage dirtyRect:dirtyRect]; |
} |
|
- (void)refresh |
{ |
[self createOutputImage]; |
[renderView setImage:outputImage dirtyRect:[outputImage extent]]; |
} |
|
- (void)doTextEditSession:(TextObject*)textObject |
{ |
//Create editor window if necessary |
if(!editorWindow) |
{ |
editorWindow = [[OverlayWindow alloc] initWithContentRect:[[renderView window] frame] styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; |
[editorWindow setParentWindow:[renderView window]]; |
[editorWindow setOpaque:NO]; |
[editorWindow setReleasedWhenClosed:NO]; |
textView = [[NSTextView alloc] initWithFrame:[[renderView window] frame]]; |
[textView setRichText:YES]; |
[textView setFieldEditor:YES]; |
[[textView textContainer] setWidthTracksTextView:NO]; |
[[textView textContainer] setHeightTracksTextView:NO]; |
[textView setVerticallyResizable:YES]; |
[textView setHorizontallyResizable:YES]; |
[textView setDrawsBackground:YES]; |
[textView setAllowsDocumentBackgroundColorChange:NO]; |
[textView setBackgroundColor:[NSColor colorWithCalibratedRed:1.0 green:1.0 blue:1.0 alpha:0.4]]; |
[textView setSelectable:YES]; |
[textView setImportsGraphics:NO]; |
[textView setDelegate:(id)self]; |
[[editorWindow contentView] addSubview:textView]; |
} |
[editorWindow setFrame:[[renderView window] frame] display:NO]; |
|
//Preset string |
if([textObject getText]) |
[[textView textStorage] setAttributedString:[textObject getText]]; |
else |
[textView setString:@" "]; |
|
[textView selectAll:nil]; |
//Run editor modally |
NSPoint fieldOrigin = [textObject getTextRect].origin; |
fieldOrigin.x -= [renderView visibleRect].origin.x; |
fieldOrigin.y -= [renderView visibleRect].origin.y - 16.0; //offset for the titlebar |
[textView setFrameOrigin:fieldOrigin]; |
[textView setFrameSize:[textObject getTextRect].size]; |
[textView setConstrainedFrameSize:[textObject getTextRect].size]; |
[textView sizeToFit]; |
[editorWindow setBackgroundColor:[NSColor clearColor]]; |
[[editorWindow contentView] display]; |
[[renderView window] addChildWindow:editorWindow ordered:NSWindowAbove]; |
[editorWindow makeKeyAndOrderFront:nil]; |
[editorWindow makeFirstResponder:textView]; |
|
if([NSApp runModalForWindow:editorWindow] != NSRunAbortedResponse) |
{ |
[textObject setText:[textView textStorage]]; |
} |
} |
|
-(void)textDidEndEditing:(id)sender |
{ |
[[renderView window] removeChildWindow:editorWindow]; |
[editorWindow close]; |
[NSApp stopModal]; |
} |
|
-(void)textDidChange:(id)sender |
{ |
[editorWindow display]; // otherwise the resizing text view will leave artifacts behind |
} |
|
@end |