GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

public
Description: An iPhoto plugin to export photos to Gallery.
Homepage: http://zwily.com/iphoto
Clone URL: git://github.com/zwily/iphototogallery.git
iphototogallery / Source / ZWTransitionImageView.m
100644 235 lines (190 sloc) 10.917 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//
// Copyright (c) Zach Wily
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// - Neither the name of Zach Wily nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific prior
// written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
 
#import "ZWTransitionImageView.h"
#import <Quartz/Quartz.h>
 
static NSBitmapImageRep *BitmapImageRepFromNSImage(NSImage *nsImage);
 
@interface MyViewAnimation : NSAnimation
@end
 
@implementation ZWTransitionImageView
 
- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    return self;
}
 
- (void)dealloc
{
    [image release];
    [oldImage release];
    [super dealloc];
}
 
- (void)awakeFromNib {
    // Preload shading bitmap to use in transitions (borrowed from the "Fun House" Core Image example).
    /*
NSData *shadingBitmapData = [NSData dataWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForResource:@"restrictedshine" ofType:@"tiff"]];
NSBitmapImageRep *shadingBitmap = [[[NSBitmapImageRep alloc] initWithData:shadingBitmapData] autorelease];
inputShadingImage = [[CIImage alloc] initWithBitmapImageRep:shadingBitmap];
*/
}
 
#pragma mark Accessors
 
- (void)setImage:(NSImage *)newImage
{
    BOOL imageWasBlank = imageIsBlank;
    imageIsBlank = (newImage == nil);
    
    // save the previous image
    [oldImage autorelease];
    oldImage = image;
    
    // create a new matted image with the image we were given
    NSRect borderRect = NSInsetRect([self bounds], 4, 4);
    NSSize newImageSize = [newImage size];
    mattedImageRect = NSInsetRect(borderRect, 5, 5);
    image = [[NSImage alloc] initWithSize:[self bounds].size];
    
    float aspect = newImageSize.width / newImageSize.height;
    
    NSRect newImageRect = NSZeroRect;
    if (aspect > 1.0) {
        newImageRect.size.width = mattedImageRect.size.width;
        newImageRect.size.height = newImageRect.size.width / aspect;
        newImageRect.origin.x = mattedImageRect.origin.x;
        newImageRect.origin.y = (mattedImageRect.size.height - newImageRect.size.height) / 2 + mattedImageRect.origin.y;
    }
    else {
        newImageRect.size.height = mattedImageRect.size.height;
        newImageRect.size.width = newImageRect.size.height * aspect;
        newImageRect.origin.y = mattedImageRect.origin.y;
        newImageRect.origin.x = (mattedImageRect.size.width - newImageRect.size.width) / 2 + mattedImageRect.origin.x;
    }
    
    [image lockFocus]; {
 
        NSBezierPath* path = [NSBezierPath bezierPath];
        float radius = MIN(5, 0.5f * MIN(NSWidth(borderRect), NSHeight(borderRect)));
        NSRect rect = NSInsetRect(borderRect, radius, radius);
        [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMinY(rect)) radius:radius startAngle:180.0 endAngle:270.0];
        [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMinY(rect)) radius:radius startAngle:270.0 endAngle:360.0];
        [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMaxY(rect)) radius:radius startAngle: 0.0 endAngle: 90.0];
        [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMaxY(rect)) radius:radius startAngle: 90.0 endAngle:180.0];
        [path closePath];
        [path setLineWidth:2.0];
        
        // background
        [[NSColor whiteColor] setFill];
        [path fill];
        
        // black border
        [[NSColor grayColor] set];
        [path stroke];
        
        [newImage drawInRect:newImageRect
                    fromRect:NSMakeRect(0, 0, newImageSize.width, newImageSize.height)
                   operation:NSCompositeSourceOver
                    fraction:1.0];
 
        [path stroke];
 
    } [image unlockFocus];
 
    Class CIImageClass = NSClassFromString(@"CIImage");
    if (oldImage && image && !imageIsBlank && !imageWasBlank && actualOldImage != newImage && CIImageClass != nil) {
        NSBitmapImageRep *initialBitmap = BitmapImageRepFromNSImage(oldImage);
        NSBitmapImageRep *finalBitmap = BitmapImageRepFromNSImage(image);
        
        id initialCIImage = [[CIImageClass alloc] initWithBitmapImageRep:initialBitmap];
        id finalCIImage = [[CIImageClass alloc] initWithBitmapImageRep:finalBitmap];
 
        NSRect rect = [self bounds];
        
        /* Page curl transition - busted at small sizes like we need
transitionFilter = [[CIFilter filterWithName:@"CIPageCurlTransition"] retain];
[transitionFilter setDefaults];
[transitionFilter setValue:[NSNumber numberWithFloat:-M_PI_4] forKey:@"inputAngle"];
[transitionFilter setValue:initialCIImage forKey:@"inputBacksideImage"];
[transitionFilter setValue:inputShadingImage forKey:@"inputShadingImage"];
[transitionFilter setValue:[CIVector vectorWithX:rect.origin.x Y:rect.origin.y Z:rect.size.width W:rect.size.height] forKey:@"inputExtent"];
*/
        
        Class CIFilterClass = NSClassFromString(@"CIFilter");
        transitionFilter = [[CIFilterClass filterWithName:@"CICopyMachineTransition"] retain];
        [transitionFilter setDefaults];
        Class CIVectorClass = NSClassFromString(@"CIVector");
        [transitionFilter setValue:[CIVectorClass vectorWithX:rect.origin.x Y:rect.origin.y Z:rect.size.width W:rect.size.height] forKey:@"inputExtent"];
        
        [transitionFilter setValue:initialCIImage forKey:@"inputImage"];
        [transitionFilter setValue:finalCIImage forKey:@"inputTargetImage"];
        
        [initialCIImage release];
        [finalCIImage release];
 
        animation = [[MyViewAnimation alloc] initWithDuration:0.7 animationCurve:NSAnimationEaseInOut];
        [animation setDelegate:self];
        [animation setAnimationBlockingMode:NSAnimationNonblocking];
        
        [animation startAnimation];
    }
    
    [self setNeedsDisplay:YES];
    
    [actualOldImage autorelease];
    actualOldImage = [newImage retain];
}
 
- (NSImage *)image
{
    return image;
}
 
- (void)animationDidEnd:(NSAnimation*)theAnimation {
    [animation autorelease];
    animation = nil;
    [transitionFilter release];
    transitionFilter = nil;
}
 
#pragma mark NSView
 
- (void)drawRect:(NSRect)rect
{
    if (animation != nil) {
        [transitionFilter setValue:[NSNumber numberWithFloat:[animation currentValue]] forKey:@"inputTime"];
        id outputCIImage = [transitionFilter valueForKey:@"outputImage"];
        
        // Composite the outputImage into the view (which triggers on-demand rendering of the result).
        [outputCIImage drawInRect:[self bounds] fromRect:NSMakeRect(0, 0, [self bounds].size.width, [self bounds].size.height) operation:NSCompositeSourceOver fraction:1.0];
    }
    else {
        [image drawAtPoint:NSMakePoint(0, 0)
                  fromRect:NSMakeRect(0, 0, [self bounds].size.width, [self bounds].size.height)
                 operation:NSCompositeSourceOver
                  fraction:1.0];
    }
}
 
@end
 
@implementation MyViewAnimation
 
// Override NSAnimation's -setCurrentProgress: method, and use it as our point to hook in and advance our Core Image transition effect to the next time slice.
- (void)setCurrentProgress:(NSAnimationProgress)progress {
    // First, invoke super's implementation, so that the NSAnimation will remember the proposed progress value and hand it back to us when we ask for it in AnimatingTabView's -drawRect: method.
    [super setCurrentProgress:progress];
    
    // Now ask the AnimatingTabView (which set itself as our delegate) to display. Sending a -display message differs from sending -setNeedsDisplay: or -setNeedsDisplayInRect: in that it demands an immediate, syncrhonous redraw of the view. Most of the time, it's preferrable to send a -setNeedsDisplay... message, which gives AppKit the opportunity to coalesce potentially numerous display requests and update the window efficiently when it's convenient. But for a syncrhonously executing animation, it's appropriate to use -display.
    [[self delegate] display];
}
 
@end
 
static NSBitmapImageRep *BitmapImageRepFromNSImage(NSImage *nsImage) {
    // See if the NSImage has an NSBitmapImageRep. If so, return the first NSBitmapImageRep encountered. An NSImage that is initialized by loading the contents of a bitmap image file (such as JPEG, TIFF, or PNG) and, not subsequently rescaled, will usually have a single NSBitmapImageRep.
    NSEnumerator *enumerator = [[nsImage representations] objectEnumerator];
    NSImageRep *representation;
    while (representation = [enumerator nextObject]) {
        if ([representation isKindOfClass:[NSBitmapImageRep class]]) {
            return (NSBitmapImageRep *)representation;
        }
    }
    
    // If we didn't find an NSBitmapImageRep (perhaps because we received a PDF image), we can create one using one of two approaches: (1) lock focus on the NSImage, and create the bitmap using -[NSBitmapImageRep initWithFocusedViewRect:], or (2) (Tiger and later) create an NSBitmapImageRep, and an NSGraphicsContext that draws into it using +[NSGraphicsContext graphicsContextWithBitmapImageRep:], and composite the NSImage into the bitmap graphics context. We'll use approach (1) here, since it is simple and supported on all versions of Mac OS X.
    NSSize size = [nsImage size];
    [nsImage lockFocus];
    NSBitmapImageRep *bitmapImageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, size.width, size.height)];
    [nsImage unlockFocus];
    
    return [bitmapImageRep autorelease];
}