image_snap 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +14 -0
- data/Rakefile +1 -0
- data/image_snap.gemspec +22 -0
- data/lib/image_snap.rb +18 -0
- data/lib/image_snap/version.rb +3 -0
- data/vendor/ImageSnap/ImageSnap.h +77 -0
- data/vendor/ImageSnap/ImageSnap.m +708 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0a5c9f2c0165330d8ca0ee2f6e05772291b0044d
|
4
|
+
data.tar.gz: efbad05a4948beb3acb1ca0d6946138cea469144
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 89bca9b26d1bb9e4e2cd52a6518498565230c56aeab20dc22ef1ff6313e93e6eeee3ff99b6072933e3d81227b01642c2d2e51730ddcfa04a655a695c0fb56795
|
7
|
+
data.tar.gz: 325cb1ea2850060ba8b4940037e3d3846848319e7d36fd50f12ce79023192a793d8f6ce5512c518ecadb1010facd92a2547f78d4352b84e2bacf0f31f61e6a14
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Claus Witt
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/image_snap.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'image_snap/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "image_snap"
|
8
|
+
spec.version = ImageSnap::VERSION
|
9
|
+
spec.authors = ["Claus Witt"]
|
10
|
+
spec.email = ["claus@wittnezz.dk"]
|
11
|
+
spec.summary = %q{Wrapper for image snap}
|
12
|
+
spec.description = %q{}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
end
|
data/lib/image_snap.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "image_snap/version"
|
2
|
+
|
3
|
+
unless defined?(Motion::Project::Config)
|
4
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
5
|
+
end
|
6
|
+
|
7
|
+
Motion::Project::App.setup do |app|
|
8
|
+
|
9
|
+
Dir.glob(File.join(File.dirname(__FILE__), "image_snap/**/*.rb")).each do |file|
|
10
|
+
app.files.unshift(file)
|
11
|
+
end
|
12
|
+
|
13
|
+
app.vendor_project(File.expand_path(File.join(File.dirname(__FILE__), '../vendor/ImageSnap')), :static)
|
14
|
+
|
15
|
+
unless app.frameworks.include?("QTKit")
|
16
|
+
app.frameworks << "QTKit"
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
//
|
2
|
+
// ImageSnap.h
|
3
|
+
// ImageSnap
|
4
|
+
//
|
5
|
+
// Created by Robert Harder on 9/10/09.
|
6
|
+
//
|
7
|
+
#import <Cocoa/Cocoa.h>
|
8
|
+
#import <QTKit/QTKit.h>
|
9
|
+
#include "ImageSnap.h"
|
10
|
+
|
11
|
+
|
12
|
+
@interface ImageSnap : NSObject {
|
13
|
+
|
14
|
+
QTCaptureSession *mCaptureSession;
|
15
|
+
QTCaptureDeviceInput *mCaptureDeviceInput;
|
16
|
+
QTCaptureDecompressedVideoOutput *mCaptureDecompressedVideoOutput;
|
17
|
+
QTCaptureVideoPreviewOutput *mCaptureVideoPreviewOutput;
|
18
|
+
CVImageBufferRef mCurrentImageBuffer;
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Returns all attached QTCaptureDevice objects that have video.
|
24
|
+
* This includes video-only devices (QTMediaTypeVideo) and
|
25
|
+
* audio/video devices (QTMediaTypeMuxed).
|
26
|
+
*
|
27
|
+
* @return autoreleased array of video devices
|
28
|
+
*/
|
29
|
+
+(NSArray *)videoDevices;
|
30
|
+
|
31
|
+
/**
|
32
|
+
* Returns the default QTCaptureDevice object for video
|
33
|
+
* or nil if none is found.
|
34
|
+
*/
|
35
|
+
+(QTCaptureDevice *)defaultVideoDevice;
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Returns the QTCaptureDevice with the given name
|
39
|
+
* or nil if the device cannot be found.
|
40
|
+
*/
|
41
|
+
+(QTCaptureDevice *)deviceNamed:(NSString *)name;
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Writes an NSImage to disk, formatting it according
|
45
|
+
* to the file extension. If path is "-" (a dash), then
|
46
|
+
* an jpeg representation is written to standard out.
|
47
|
+
*/
|
48
|
+
+ (BOOL) saveImage:(NSImage *)image toPath: (NSString*)path;
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Converts an NSImage to raw NSData according to a given
|
52
|
+
* format. A simple string search is performed for such
|
53
|
+
* characters as jpeg, tiff, png, and so forth.
|
54
|
+
*/
|
55
|
+
+(NSData *)dataFrom:(NSImage *)image asType:(NSString *)format;
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Primary one-stop-shopping message for capturing an image.
|
61
|
+
* Activates the video source, saves a frame, stops the source,
|
62
|
+
* and saves the file.
|
63
|
+
*/
|
64
|
+
+(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device toFile:(NSString *)path;
|
65
|
+
+(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device toFile:(NSString *)path withWarmup:(NSNumber *)warmup;
|
66
|
+
+(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device toFile:(NSString *)path withWarmup:(NSNumber *)warmup withTimelapse:(NSNumber *)timelapse;
|
67
|
+
|
68
|
+
-(id)init;
|
69
|
+
-(void)dealloc;
|
70
|
+
|
71
|
+
|
72
|
+
-(BOOL)startSession:(QTCaptureDevice *)device;
|
73
|
+
-(NSImage *)snapshot;
|
74
|
+
-(void)stopSession;
|
75
|
+
|
76
|
+
|
77
|
+
@end
|
@@ -0,0 +1,708 @@
|
|
1
|
+
//
|
2
|
+
// ImageSnap.m
|
3
|
+
// ImageSnap
|
4
|
+
//
|
5
|
+
// Created by Robert Harder on 9/10/09.
|
6
|
+
//
|
7
|
+
|
8
|
+
#import "ImageSnap.h"
|
9
|
+
|
10
|
+
|
11
|
+
#define error(...) fprintf(stderr, __VA_ARGS__)
|
12
|
+
#define console(...) (!g_quiet && printf(__VA_ARGS__))
|
13
|
+
#define verbose(...) (g_verbose && !g_quiet && fprintf(stderr, __VA_ARGS__))
|
14
|
+
|
15
|
+
BOOL g_verbose = NO;
|
16
|
+
BOOL g_quiet = NO;
|
17
|
+
//double g_timelapse = -1;
|
18
|
+
NSString *VERSION = @"0.2.5";
|
19
|
+
|
20
|
+
|
21
|
+
@interface ImageSnap()
|
22
|
+
|
23
|
+
|
24
|
+
- (void)captureOutput:(QTCaptureOutput *)captureOutput
|
25
|
+
didOutputVideoFrame:(CVImageBufferRef)videoFrame
|
26
|
+
withSampleBuffer:(QTSampleBuffer *)sampleBuffer
|
27
|
+
fromConnection:(QTCaptureConnection *)connection;
|
28
|
+
|
29
|
+
@end
|
30
|
+
|
31
|
+
|
32
|
+
@implementation ImageSnap
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
- (id)init{
|
37
|
+
self = [super init];
|
38
|
+
mCaptureSession = nil;
|
39
|
+
mCaptureDeviceInput = nil;
|
40
|
+
mCaptureDecompressedVideoOutput = nil;
|
41
|
+
mCaptureVideoPreviewOutput = nil;
|
42
|
+
mCurrentImageBuffer = nil;
|
43
|
+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceDisconnected:) name:QTCaptureDeviceWasDisconnectedNotification object:nil];
|
44
|
+
return self;
|
45
|
+
}
|
46
|
+
|
47
|
+
- (void)dealloc{
|
48
|
+
|
49
|
+
if( mCaptureSession ) [mCaptureSession release];
|
50
|
+
if( mCaptureDeviceInput ) [mCaptureDeviceInput release];
|
51
|
+
if( mCaptureDecompressedVideoOutput ) [mCaptureDecompressedVideoOutput release];
|
52
|
+
if( mCaptureVideoPreviewOutput ) [mCaptureVideoPreviewOutput release];
|
53
|
+
CVBufferRelease(mCurrentImageBuffer);
|
54
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
55
|
+
|
56
|
+
[super dealloc];
|
57
|
+
}
|
58
|
+
|
59
|
+
|
60
|
+
// Returns an array of video devices attached to this computer.
|
61
|
+
+ (NSArray *)videoDevices{
|
62
|
+
NSMutableArray *results = [NSMutableArray arrayWithCapacity:3];
|
63
|
+
[results addObjectsFromArray:[QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo]];
|
64
|
+
[results addObjectsFromArray:[QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeMuxed]];
|
65
|
+
return results;
|
66
|
+
}
|
67
|
+
|
68
|
+
// Returns the default video device or nil if none found.
|
69
|
+
+ (QTCaptureDevice *)defaultVideoDevice{
|
70
|
+
QTCaptureDevice *device = nil;
|
71
|
+
|
72
|
+
device = [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeVideo];
|
73
|
+
if( device == nil ){
|
74
|
+
device = [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeMuxed];
|
75
|
+
}
|
76
|
+
return device;
|
77
|
+
}
|
78
|
+
|
79
|
+
// Returns the named capture device or nil if not found.
|
80
|
+
+(QTCaptureDevice *)deviceNamed:(NSString *)name{
|
81
|
+
QTCaptureDevice *result = nil;
|
82
|
+
|
83
|
+
NSArray *devices = [ImageSnap videoDevices];
|
84
|
+
for( QTCaptureDevice *device in devices ){
|
85
|
+
if ( [name isEqualToString:[device description]] ){
|
86
|
+
result = device;
|
87
|
+
} // end if: match
|
88
|
+
} // end for: each device
|
89
|
+
|
90
|
+
return result;
|
91
|
+
} // end
|
92
|
+
|
93
|
+
|
94
|
+
// Saves an image to a file or standard out if path is nil or "-" (hyphen).
|
95
|
+
+ (BOOL) saveImage:(NSImage *)image toPath: (NSString*)path{
|
96
|
+
|
97
|
+
NSString *ext = [path pathExtension];
|
98
|
+
NSData *photoData = [ImageSnap dataFrom:image asType:ext];
|
99
|
+
|
100
|
+
// If path is a dash, that means write to standard out
|
101
|
+
if( path == nil || [@"-" isEqualToString:path] ){
|
102
|
+
NSUInteger length = [photoData length];
|
103
|
+
NSUInteger i;
|
104
|
+
char *start = (char *)[photoData bytes];
|
105
|
+
for( i = 0; i < length; ++i ){
|
106
|
+
putc( start[i], stdout );
|
107
|
+
} // end for: write out
|
108
|
+
return YES;
|
109
|
+
} else {
|
110
|
+
return [photoData writeToFile:path atomically:NO];
|
111
|
+
}
|
112
|
+
|
113
|
+
|
114
|
+
return NO;
|
115
|
+
}
|
116
|
+
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Converts an NSImage into NSData. Defaults to jpeg if
|
120
|
+
* format cannot be determined.
|
121
|
+
*/
|
122
|
+
+(NSData *)dataFrom:(NSImage *)image asType:(NSString *)format{
|
123
|
+
|
124
|
+
NSData *tiffData = [image TIFFRepresentation];
|
125
|
+
|
126
|
+
NSBitmapImageFileType imageType = NSJPEGFileType;
|
127
|
+
NSDictionary *imageProps = nil;
|
128
|
+
|
129
|
+
|
130
|
+
// TIFF. Special case. Can save immediately.
|
131
|
+
if( [@"tif" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ||
|
132
|
+
[@"tiff" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ){
|
133
|
+
return tiffData;
|
134
|
+
}
|
135
|
+
|
136
|
+
// JPEG
|
137
|
+
else if( [@"jpg" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ||
|
138
|
+
[@"jpeg" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ){
|
139
|
+
imageType = NSJPEGFileType;
|
140
|
+
imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.9] forKey:NSImageCompressionFactor];
|
141
|
+
|
142
|
+
}
|
143
|
+
|
144
|
+
// PNG
|
145
|
+
else if( [@"png" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ){
|
146
|
+
imageType = NSPNGFileType;
|
147
|
+
}
|
148
|
+
|
149
|
+
// BMP
|
150
|
+
else if( [@"bmp" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ){
|
151
|
+
imageType = NSBMPFileType;
|
152
|
+
}
|
153
|
+
|
154
|
+
// GIF
|
155
|
+
else if( [@"gif" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ){
|
156
|
+
imageType = NSGIFFileType;
|
157
|
+
}
|
158
|
+
|
159
|
+
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:tiffData];
|
160
|
+
NSData *photoData = [imageRep representationUsingType:imageType properties:imageProps];
|
161
|
+
|
162
|
+
return photoData;
|
163
|
+
} // end dataFrom
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
/**
|
168
|
+
* Primary one-stop-shopping message for capturing an image.
|
169
|
+
* Activates the video source, saves a frame, stops the source,
|
170
|
+
* and saves the file.
|
171
|
+
*/
|
172
|
+
|
173
|
+
+(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device toFile:(NSString *)path{
|
174
|
+
return [self saveSingleSnapshotFrom:device toFile:path withWarmup:nil];
|
175
|
+
}
|
176
|
+
|
177
|
+
+(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device toFile:(NSString *)path withWarmup:(NSNumber *)warmup{
|
178
|
+
return [self saveSingleSnapshotFrom:device toFile:path withWarmup:warmup withTimelapse:nil];
|
179
|
+
}
|
180
|
+
|
181
|
+
+(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device
|
182
|
+
toFile:(NSString *)path
|
183
|
+
withWarmup:(NSNumber *)warmup
|
184
|
+
withTimelapse:(NSNumber *)timelapse{
|
185
|
+
ImageSnap *snap;
|
186
|
+
NSImage *image = nil;
|
187
|
+
double interval = timelapse == nil ? -1 : [timelapse doubleValue];
|
188
|
+
|
189
|
+
snap = [[ImageSnap alloc] init]; // Instance of this ImageSnap class
|
190
|
+
verbose("Starting device...");
|
191
|
+
if( [snap startSession:device] ){ // Try starting session
|
192
|
+
verbose("Device started.\n");
|
193
|
+
|
194
|
+
if( warmup == nil ){
|
195
|
+
// Skip warmup
|
196
|
+
verbose("Skipping warmup period.\n");
|
197
|
+
} else {
|
198
|
+
double delay = [warmup doubleValue];
|
199
|
+
verbose("Delaying %.2lf seconds for warmup...",delay);
|
200
|
+
NSDate *now = [[NSDate alloc] init];
|
201
|
+
[[NSRunLoop currentRunLoop] runUntilDate:[now dateByAddingTimeInterval: [warmup doubleValue]]];
|
202
|
+
[now release];
|
203
|
+
verbose("Warmup complete.\n");
|
204
|
+
}
|
205
|
+
|
206
|
+
if ( interval > 0 ) {
|
207
|
+
|
208
|
+
verbose("Time lapse: snapping every %.2lf seconds to current directory.\n", interval);
|
209
|
+
|
210
|
+
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
211
|
+
[dateFormatter setDateFormat:@"yyyy-MM-dd_HH-mm-ss.SSS"];
|
212
|
+
|
213
|
+
// wait a bit to make sure the camera is initialized
|
214
|
+
//[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 1.0]];
|
215
|
+
|
216
|
+
for (unsigned long seq=0; ; seq++)
|
217
|
+
{
|
218
|
+
NSDate *now = [[NSDate alloc] init];
|
219
|
+
NSString *nowstr = [dateFormatter stringFromDate:now];
|
220
|
+
|
221
|
+
verbose(" - Snapshot %5lu", seq);
|
222
|
+
verbose(" (%s)\n", [nowstr UTF8String]);
|
223
|
+
|
224
|
+
// create filename
|
225
|
+
NSString *filename = [NSString stringWithFormat:@"snapshot-%05ld-%s.jpg", seq, [nowstr UTF8String]];
|
226
|
+
|
227
|
+
// capture and write
|
228
|
+
image = [snap snapshot]; // Capture a frame
|
229
|
+
if (image != nil) {
|
230
|
+
[ImageSnap saveImage:image toPath:filename];
|
231
|
+
console( "%s\n", [filename UTF8String]);
|
232
|
+
} else {
|
233
|
+
error( "Image capture failed.\n" );
|
234
|
+
}
|
235
|
+
|
236
|
+
// sleep
|
237
|
+
[[NSRunLoop currentRunLoop] runUntilDate:[now dateByAddingTimeInterval: interval]];
|
238
|
+
|
239
|
+
[now release];
|
240
|
+
}
|
241
|
+
|
242
|
+
} else {
|
243
|
+
image = [snap snapshot]; // Capture a frame
|
244
|
+
|
245
|
+
}
|
246
|
+
//NSLog(@"Stopping...");
|
247
|
+
[snap stopSession]; // Stop session
|
248
|
+
//NSLog(@"Stopped.");
|
249
|
+
} // end if: able to start session
|
250
|
+
|
251
|
+
[snap release];
|
252
|
+
|
253
|
+
if ( interval > 0 ){
|
254
|
+
return YES;
|
255
|
+
} else {
|
256
|
+
return image == nil ? NO : [ImageSnap saveImage:image toPath:path];
|
257
|
+
}
|
258
|
+
} // end
|
259
|
+
|
260
|
+
|
261
|
+
/**
|
262
|
+
* Returns current snapshot or nil if there is a problem
|
263
|
+
* or session is not started.
|
264
|
+
*/
|
265
|
+
-(NSImage *)snapshot{
|
266
|
+
verbose( "Taking snapshot...\n");
|
267
|
+
|
268
|
+
CVImageBufferRef frame = nil; // Hold frame we find
|
269
|
+
while( frame == nil ){ // While waiting for a frame
|
270
|
+
|
271
|
+
//verbose( "\tEntering synchronized block to see if frame is captured yet...");
|
272
|
+
@synchronized(self){ // Lock since capture is on another thread
|
273
|
+
frame = mCurrentImageBuffer; // Hold current frame
|
274
|
+
CVBufferRetain(frame); // Retain it (OK if nil)
|
275
|
+
} // end sync: self
|
276
|
+
//verbose( "Done.\n" );
|
277
|
+
|
278
|
+
if( frame == nil ){ // Still no frame? Wait a little while.
|
279
|
+
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 0.1]];
|
280
|
+
} // end if: still nothing, wait
|
281
|
+
|
282
|
+
} // end while: no frame yet
|
283
|
+
|
284
|
+
// Convert frame to an NSImage
|
285
|
+
CIImage *flippedImage = [self flipImage:[CIImage imageWithCVImageBuffer:frame]];
|
286
|
+
NSCIImageRep *imageRep = [NSCIImageRep imageRepWithCIImage:flippedImage];
|
287
|
+
NSImage *image = [[[NSImage alloc] initWithSize:[imageRep size]] autorelease];
|
288
|
+
[image addRepresentation:imageRep];
|
289
|
+
CVBufferRelease(frame); // Nick's addition. Maybe horribly unsafe or something, but was leaking crazy memory without this.
|
290
|
+
verbose( "Snapshot taken.\n" );
|
291
|
+
|
292
|
+
return image;
|
293
|
+
}
|
294
|
+
|
295
|
+
- (CIImage *)flipImage:(CIImage *)image {
|
296
|
+
CIImage *resultImage = image;
|
297
|
+
CIFilter *flipFilter = [CIFilter filterWithName:@"CIAffineTransform"];
|
298
|
+
[flipFilter setValue:resultImage forKey:@"inputImage"];
|
299
|
+
NSAffineTransform *flipTransform = [NSAffineTransform transform];
|
300
|
+
[flipTransform scaleXBy:-1 yBy:1.0]; // horizontal flip
|
301
|
+
[flipTransform translateXBy:-1280 yBy:0];
|
302
|
+
[flipFilter setValue:flipTransform forKey:@"inputTransform"];
|
303
|
+
resultImage = [flipFilter valueForKey:@"outputImage"];
|
304
|
+
return resultImage;
|
305
|
+
}
|
306
|
+
|
307
|
+
|
308
|
+
/**
|
309
|
+
* Blocks until session is stopped.
|
310
|
+
*/
|
311
|
+
-(void)stopSession{
|
312
|
+
verbose("Stopping session...\n" );
|
313
|
+
|
314
|
+
// Make sure we've stopped
|
315
|
+
while( mCaptureSession != nil ){
|
316
|
+
verbose("\tCaptureSession != nil\n");
|
317
|
+
|
318
|
+
verbose("\tStopping CaptureSession...");
|
319
|
+
[mCaptureSession stopRunning];
|
320
|
+
verbose("Done.\n");
|
321
|
+
|
322
|
+
if( [mCaptureSession isRunning] ){
|
323
|
+
verbose( "[mCaptureSession isRunning]");
|
324
|
+
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 0.1]];
|
325
|
+
}else {
|
326
|
+
verbose( "\tShutting down 'stopSession(..)'" );
|
327
|
+
if( mCaptureSession ) [mCaptureSession release];
|
328
|
+
if( mCaptureDeviceInput ) [mCaptureDeviceInput release];
|
329
|
+
if( mCaptureDecompressedVideoOutput ) [mCaptureDecompressedVideoOutput release];
|
330
|
+
if( mCaptureVideoPreviewOutput ) [mCaptureVideoPreviewOutput release];
|
331
|
+
|
332
|
+
mCaptureSession = nil;
|
333
|
+
mCaptureDeviceInput = nil;
|
334
|
+
mCaptureDecompressedVideoOutput = nil;
|
335
|
+
mCaptureVideoPreviewOutput = nil;
|
336
|
+
} // end if: stopped
|
337
|
+
|
338
|
+
} // end while: not stopped
|
339
|
+
}
|
340
|
+
|
341
|
+
|
342
|
+
/**
|
343
|
+
* Begins the capture session. Frames begin coming in.
|
344
|
+
*/
|
345
|
+
-(BOOL)startSession:(QTCaptureDevice *)device{
|
346
|
+
|
347
|
+
verbose( "Starting capture session...\n" );
|
348
|
+
|
349
|
+
if( device == nil ) {
|
350
|
+
verbose( "\tCannot start session: no device provided.\n" );
|
351
|
+
return NO;
|
352
|
+
}
|
353
|
+
|
354
|
+
NSError *error = nil;
|
355
|
+
|
356
|
+
// If we've already started with this device, return
|
357
|
+
if( [device isEqual:[mCaptureDeviceInput device]] &&
|
358
|
+
mCaptureSession != nil &&
|
359
|
+
[mCaptureSession isRunning] ){
|
360
|
+
return YES;
|
361
|
+
} // end if: already running
|
362
|
+
|
363
|
+
else if( mCaptureSession != nil ){
|
364
|
+
verbose( "\tStopping previous session.\n" );
|
365
|
+
[self stopSession];
|
366
|
+
} // end if: else stop session
|
367
|
+
|
368
|
+
|
369
|
+
// Create the capture session
|
370
|
+
verbose( "\tCreating QTCaptureSession..." );
|
371
|
+
mCaptureSession = [[QTCaptureSession alloc] init];
|
372
|
+
verbose( "Done.\n");
|
373
|
+
if( ![device open:&error] ){
|
374
|
+
error( "\tCould not create capture session.\n" );
|
375
|
+
[mCaptureSession release];
|
376
|
+
mCaptureSession = nil;
|
377
|
+
return NO;
|
378
|
+
}
|
379
|
+
|
380
|
+
|
381
|
+
// Create input object from the device
|
382
|
+
verbose( "\tCreating QTCaptureDeviceInput with %s...", [[device description] UTF8String] );
|
383
|
+
mCaptureDeviceInput = [[QTCaptureDeviceInput alloc] initWithDevice:device];
|
384
|
+
verbose( "Done.\n");
|
385
|
+
if (![mCaptureSession addInput:mCaptureDeviceInput error:&error]) {
|
386
|
+
error( "\tCould not convert device to input device.\n");
|
387
|
+
[mCaptureSession release];
|
388
|
+
[mCaptureDeviceInput release];
|
389
|
+
mCaptureSession = nil;
|
390
|
+
mCaptureDeviceInput = nil;
|
391
|
+
return NO;
|
392
|
+
}
|
393
|
+
|
394
|
+
|
395
|
+
// // Decompressed video output
|
396
|
+
// verbose( "\tCreating QTCaptureDecompressedVideoOutput...");
|
397
|
+
// mCaptureDecompressedVideoOutput = [[QTCaptureDecompressedVideoOutput alloc] init];
|
398
|
+
// [mCaptureDecompressedVideoOutput setDelegate:self];
|
399
|
+
// verbose( "Done.\n" );
|
400
|
+
// if (![mCaptureSession addOutput:mCaptureDecompressedVideoOutput error:&error]) {
|
401
|
+
// error( "\tCould not create decompressed output.\n");
|
402
|
+
// [mCaptureSession release];
|
403
|
+
// [mCaptureDeviceInput release];
|
404
|
+
// [mCaptureDecompressedVideoOutput release];
|
405
|
+
// mCaptureSession = nil;
|
406
|
+
// mCaptureDeviceInput = nil;
|
407
|
+
// mCaptureDecompressedVideoOutput = nil;
|
408
|
+
// return NO;
|
409
|
+
// }
|
410
|
+
|
411
|
+
// Preview video output
|
412
|
+
verbose( "\tCreating QTCaptureVideoPreviewOutput...");
|
413
|
+
mCaptureVideoPreviewOutput = [[QTCaptureVideoPreviewOutput alloc] init];
|
414
|
+
[mCaptureVideoPreviewOutput setDelegate:self];
|
415
|
+
verbose( "Done.\n" );
|
416
|
+
if (![mCaptureSession addOutput:mCaptureVideoPreviewOutput error:&error]) {
|
417
|
+
error( "\tCould not create preview output.\n");
|
418
|
+
[mCaptureSession release];
|
419
|
+
[mCaptureDeviceInput release];
|
420
|
+
[mCaptureVideoPreviewOutput release];
|
421
|
+
mCaptureSession = nil;
|
422
|
+
mCaptureDeviceInput = nil;
|
423
|
+
mCaptureVideoPreviewOutput = nil;
|
424
|
+
return NO;
|
425
|
+
}
|
426
|
+
|
427
|
+
// Clear old image?
|
428
|
+
verbose("\tEntering synchronized block to clear memory...");
|
429
|
+
@synchronized(self){
|
430
|
+
if( mCurrentImageBuffer != nil ){
|
431
|
+
CVBufferRelease(mCurrentImageBuffer);
|
432
|
+
mCurrentImageBuffer = nil;
|
433
|
+
} // end if: clear old image
|
434
|
+
} // end sync: self
|
435
|
+
verbose( "Done.\n");
|
436
|
+
|
437
|
+
[mCaptureSession startRunning];
|
438
|
+
verbose("Session started.\n");
|
439
|
+
|
440
|
+
return YES;
|
441
|
+
} // end startSession
|
442
|
+
|
443
|
+
|
444
|
+
|
445
|
+
// This delegate method is called whenever the QTCaptureDecompressedVideoOutput receives a frame
|
446
|
+
- (void)captureOutput:(QTCaptureOutput *)captureOutput
|
447
|
+
didOutputVideoFrame:(CVImageBufferRef)videoFrame
|
448
|
+
withSampleBuffer:(QTSampleBuffer *)sampleBuffer
|
449
|
+
fromConnection:(QTCaptureConnection *)connection
|
450
|
+
{
|
451
|
+
verbose( "." );
|
452
|
+
if (videoFrame == nil ) {
|
453
|
+
verbose( "'nil' Frame captured.\n" );
|
454
|
+
return;
|
455
|
+
}
|
456
|
+
|
457
|
+
// Swap out old frame for new one
|
458
|
+
CVImageBufferRef imageBufferToRelease;
|
459
|
+
CVBufferRetain(videoFrame);
|
460
|
+
|
461
|
+
@synchronized(self){
|
462
|
+
imageBufferToRelease = mCurrentImageBuffer;
|
463
|
+
mCurrentImageBuffer = videoFrame;
|
464
|
+
} // end sync
|
465
|
+
CVBufferRelease(imageBufferToRelease);
|
466
|
+
|
467
|
+
}
|
468
|
+
|
469
|
+
- (void)onDeviceDisconnected:(NSNotification *)note {
|
470
|
+
return; // This didn't work
|
471
|
+
[self stopSession];
|
472
|
+
[self startSession:[ImageSnap defaultVideoDevice]];
|
473
|
+
}
|
474
|
+
|
475
|
+
@end
|
476
|
+
|
477
|
+
|
478
|
+
// //////////////////////////////////////////////////////////
|
479
|
+
//
|
480
|
+
// //////// B E G I N C - L E V E L M A I N //////// //
|
481
|
+
//
|
482
|
+
// //////////////////////////////////////////////////////////
|
483
|
+
|
484
|
+
int processArguments(int argc, const char * argv[]);
|
485
|
+
void printUsage(int argc, const char * argv[]);
|
486
|
+
int listDevices();
|
487
|
+
NSString *generateFilename();
|
488
|
+
QTCaptureDevice *getDefaultDevice();
|
489
|
+
|
490
|
+
|
491
|
+
// Main entry point. Since we're using Cocoa and all kinds of fancy
|
492
|
+
// classes, we have to set up appropriate pools and loops.
|
493
|
+
// Thanks to the example http://lists.apple.com/archives/cocoa-dev/2003/Apr/msg01638.html
|
494
|
+
// for reminding me how to do it.
|
495
|
+
int mainRemnant (int argc, const char * argv[]) {
|
496
|
+
NSApplicationLoad(); // May be necessary for 10.5 not to crash.
|
497
|
+
|
498
|
+
NSAutoreleasePool *pool;
|
499
|
+
pool = [[NSAutoreleasePool alloc] init];
|
500
|
+
[NSApplication sharedApplication];
|
501
|
+
|
502
|
+
int result = processArguments(argc, argv);
|
503
|
+
|
504
|
+
// [pool release];
|
505
|
+
[pool drain];
|
506
|
+
return result;
|
507
|
+
}
|
508
|
+
|
509
|
+
|
510
|
+
|
511
|
+
/**
|
512
|
+
* Process command line arguments and execute program.
|
513
|
+
*/
|
514
|
+
int processArguments(int argc, const char * argv[] ){
|
515
|
+
|
516
|
+
NSString *filename = nil;
|
517
|
+
QTCaptureDevice *device = nil;
|
518
|
+
NSNumber *warmup = nil;
|
519
|
+
NSNumber *timelapse = nil;
|
520
|
+
|
521
|
+
|
522
|
+
int i;
|
523
|
+
for( i = 1; i < argc; ++i ){
|
524
|
+
|
525
|
+
// Handle command line switches
|
526
|
+
if (argv[i][0] == '-') {
|
527
|
+
|
528
|
+
// Dash only? Means write image to stdout
|
529
|
+
if( argv[i][1] == 0 ){
|
530
|
+
filename = @"-";
|
531
|
+
g_quiet = YES;
|
532
|
+
} else {
|
533
|
+
|
534
|
+
// Which switch was given
|
535
|
+
switch (argv[i][1]) {
|
536
|
+
|
537
|
+
// Help
|
538
|
+
case '?':
|
539
|
+
case 'h':
|
540
|
+
printUsage( argc, argv );
|
541
|
+
return 0;
|
542
|
+
break;
|
543
|
+
|
544
|
+
|
545
|
+
// Verbose
|
546
|
+
case 'v':
|
547
|
+
g_verbose = YES;
|
548
|
+
break;
|
549
|
+
|
550
|
+
case 'q':
|
551
|
+
g_quiet = YES;
|
552
|
+
break;
|
553
|
+
|
554
|
+
|
555
|
+
// List devices
|
556
|
+
case 'l':
|
557
|
+
listDevices();
|
558
|
+
return 0;
|
559
|
+
break;
|
560
|
+
|
561
|
+
// Specify device
|
562
|
+
case 'd':
|
563
|
+
if( i+1 < argc ){
|
564
|
+
device = [ImageSnap deviceNamed:[NSString stringWithUTF8String:argv[i+1]]];
|
565
|
+
if( device == nil ){
|
566
|
+
error( "Device \"%s\" not found.\n", argv[i+1] );
|
567
|
+
return 11;
|
568
|
+
} // end if: not found
|
569
|
+
++i; // Account for "follow on" argument
|
570
|
+
} else {
|
571
|
+
error( "Not enough arguments given with 'd' flag.\n" );
|
572
|
+
return (int)'d';
|
573
|
+
}
|
574
|
+
break;
|
575
|
+
|
576
|
+
// Specify a warmup period before picture snaps
|
577
|
+
case 'w':
|
578
|
+
if( i+1 < argc ){
|
579
|
+
warmup = [NSNumber numberWithFloat:[[NSString stringWithUTF8String:argv[i+1]] floatValue]];
|
580
|
+
++i; // Account for "follow on" argument
|
581
|
+
} else {
|
582
|
+
error( "Not enough arguments given with 'w' flag.\n" );
|
583
|
+
return (int)'w';
|
584
|
+
}
|
585
|
+
break;
|
586
|
+
|
587
|
+
// Timelapse
|
588
|
+
case 't':
|
589
|
+
if( i+1 < argc ){
|
590
|
+
timelapse = [NSNumber numberWithDouble:[[NSString stringWithUTF8String:argv[i+1]] doubleValue]];
|
591
|
+
//g_timelapse = [timelapse doubleValue];
|
592
|
+
++i; // Account for "follow on" argument
|
593
|
+
} else {
|
594
|
+
error( "Not enough arguments given with 't' flag.\n" );
|
595
|
+
return (int)'t';
|
596
|
+
}
|
597
|
+
break;
|
598
|
+
|
599
|
+
|
600
|
+
|
601
|
+
} // end switch: flag value
|
602
|
+
} // end else: not dash only
|
603
|
+
} // end if: '-'
|
604
|
+
|
605
|
+
// Else assume it's a filename
|
606
|
+
else {
|
607
|
+
filename = [NSString stringWithUTF8String:argv[i]];
|
608
|
+
}
|
609
|
+
|
610
|
+
} // end for: each command line argument
|
611
|
+
|
612
|
+
|
613
|
+
// Make sure we have a filename
|
614
|
+
if( filename == nil ){
|
615
|
+
filename = generateFilename();
|
616
|
+
verbose( "No filename specified. Using %s\n", [filename UTF8String] );
|
617
|
+
} // end if: no filename given
|
618
|
+
|
619
|
+
if( filename == nil ){
|
620
|
+
error( "No suitable filename could be determined.\n" );
|
621
|
+
return 1;
|
622
|
+
}
|
623
|
+
|
624
|
+
|
625
|
+
// Make sure we have a device
|
626
|
+
if( device == nil ){
|
627
|
+
device = getDefaultDevice();
|
628
|
+
verbose( "No device specified. Using %s\n", [[device description] UTF8String] );
|
629
|
+
} // end if: no device given
|
630
|
+
|
631
|
+
if( device == nil ){
|
632
|
+
error( "No video devices found.\n" );
|
633
|
+
return 2;
|
634
|
+
} else {
|
635
|
+
console( "Capturing image from device \"%s\"...", [[device description] UTF8String] );
|
636
|
+
}
|
637
|
+
|
638
|
+
|
639
|
+
// Image capture
|
640
|
+
if( [ImageSnap saveSingleSnapshotFrom:device toFile:filename withWarmup:warmup withTimelapse:timelapse] ){
|
641
|
+
console( "%s\n", [filename UTF8String] );
|
642
|
+
} else {
|
643
|
+
error( "Error.\n" );
|
644
|
+
} // end else
|
645
|
+
|
646
|
+
return 0;
|
647
|
+
}
|
648
|
+
|
649
|
+
|
650
|
+
|
651
|
+
void printUsage(int argc, const char * argv[]){
|
652
|
+
printf( "USAGE: %s [options] [filename]\n", argv[0] );
|
653
|
+
printf( "Version: %s\n", [VERSION UTF8String] );
|
654
|
+
printf( "Captures an image from a video device and saves it in a file.\n" );
|
655
|
+
printf( "If no device is specified, the system default will be used.\n" );
|
656
|
+
printf( "If no filename is specfied, snapshot.jpg will be used.\n" );
|
657
|
+
printf( "Supported image types: JPEG, TIFF, PNG, GIF, BMP\n" );
|
658
|
+
printf( " -h This help message\n" );
|
659
|
+
printf( " -v Verbose mode\n");
|
660
|
+
printf( " -l List available video devices\n" );
|
661
|
+
printf( " -t x.xx Take a picture every x.xx seconds\n" );
|
662
|
+
printf( " -q Quiet mode. Do not output any text\n");
|
663
|
+
printf( " -w x.xx Warmup. Delay snapshot x.xx seconds after turning on camera\n" );
|
664
|
+
printf( " -d device Use named video device\n" );
|
665
|
+
}
|
666
|
+
|
667
|
+
|
668
|
+
|
669
|
+
|
670
|
+
|
671
|
+
/**
|
672
|
+
* Prints a list of video capture devices to standard out.
|
673
|
+
*/
|
674
|
+
int listDevices(){
|
675
|
+
NSArray *devices = [ImageSnap videoDevices];
|
676
|
+
|
677
|
+
[devices count] > 0
|
678
|
+
? printf("Video Devices:\n")
|
679
|
+
: printf("No video devices found.\n");
|
680
|
+
|
681
|
+
for( QTCaptureDevice *device in devices ){
|
682
|
+
printf( "%s\n", [[device description] UTF8String] );
|
683
|
+
} // end for: each device
|
684
|
+
return (int)[devices count];
|
685
|
+
}
|
686
|
+
|
687
|
+
/**
|
688
|
+
* Generates a filename for saving the image, presumably
|
689
|
+
* because the user didn't specify a filename.
|
690
|
+
* Currently returns snapshot.tiff.
|
691
|
+
*/
|
692
|
+
NSString *generateFilename(){
|
693
|
+
NSString *result = @"snapshot.jpg";
|
694
|
+
return result;
|
695
|
+
} // end
|
696
|
+
|
697
|
+
|
698
|
+
/**
|
699
|
+
* Gets a default video device, or nil if none is found.
|
700
|
+
* For now, simply queries ImageSnap. May be fancier
|
701
|
+
* in the future.
|
702
|
+
*/
|
703
|
+
QTCaptureDevice *getDefaultDevice(){
|
704
|
+
return [ImageSnap defaultVideoDevice];
|
705
|
+
} // end
|
706
|
+
|
707
|
+
|
708
|
+
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: image_snap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Claus Witt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: ''
|
42
|
+
email:
|
43
|
+
- claus@wittnezz.dk
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- image_snap.gemspec
|
54
|
+
- lib/image_snap.rb
|
55
|
+
- lib/image_snap/version.rb
|
56
|
+
- vendor/ImageSnap/ImageSnap.h
|
57
|
+
- vendor/ImageSnap/ImageSnap.m
|
58
|
+
homepage: ''
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.2.1
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: Wrapper for image snap
|
82
|
+
test_files: []
|