@@ -41,7 +41,9 @@ var Webcam = {
41
41
loaded : false , // true when webcam movie finishes loading
42
42
live : false , // true when webcam is initialized and ready to snap
43
43
userMedia : true , // true when getUserMedia is supported natively
44
-
44
+
45
+ iOS : / i P a d | i P h o n e | i P o d / . test ( navigator . userAgent ) && ! window . MSStream ,
46
+
45
47
params : {
46
48
width : 0 ,
47
49
height : 0 ,
@@ -58,7 +60,10 @@ var Webcam = {
58
60
swfURL : '' , // URI to webcam.swf movie (defaults to the js location)
59
61
flashNotDetectedText : 'ERROR: No Adobe Flash Player detected. Webcam.js relies on Flash for browsers that do not support getUserMedia (like yours).' ,
60
62
noInterfaceFoundText : 'No supported webcam interface found.' ,
61
- unfreeze_snap : true // Whether to unfreeze the camera after snap (defaults to true)
63
+ unfreeze_snap : true , // Whether to unfreeze the camera after snap (defaults to true)
64
+ iosPlaceholderText : 'Click here to open camera.' ,
65
+ user_callback : null , // callback function for snapshot (used if no user_callback parameter given to snap function)
66
+ user_canvas : null // user provided canvas for snapshot (used if no user_canvas parameter given to snap function)
62
67
} ,
63
68
64
69
errors : {
@@ -100,6 +105,123 @@ var Webcam = {
100
105
}
101
106
} ,
102
107
108
+ exifOrientation : function ( binFile ) {
109
+ // extract orientation information from the image provided by iOS
110
+ // algorithm based on exif-js
111
+ var dataView = new DataView ( binFile ) ;
112
+ if ( ( dataView . getUint8 ( 0 ) != 0xFF ) || ( dataView . getUint8 ( 1 ) != 0xD8 ) ) {
113
+ console . log ( 'Not a valid JPEG file' ) ;
114
+ return 0 ;
115
+ }
116
+ var offset = 2 ;
117
+ var marker = null ;
118
+ while ( offset < binFile . byteLength ) {
119
+ // find 0xFFE1 (225 marker)
120
+ if ( dataView . getUint8 ( offset ) != 0xFF ) {
121
+ console . log ( 'Not a valid marker at offset ' + offset + ', found: ' + dataView . getUint8 ( offset ) ) ;
122
+ return 0 ;
123
+ }
124
+ marker = dataView . getUint8 ( offset + 1 ) ;
125
+ if ( marker == 225 ) {
126
+ offset += 4 ;
127
+ var str = "" ;
128
+ for ( n = 0 ; n < 4 ; n ++ ) {
129
+ str += String . fromCharCode ( dataView . getUint8 ( offset + n ) ) ;
130
+ }
131
+ if ( str != 'Exif' ) {
132
+ console . log ( 'Not valid EXIF data found' ) ;
133
+ return 0 ;
134
+ }
135
+
136
+ offset += 6 ; // tiffOffset
137
+ var bigEnd = null ;
138
+
139
+ // test for TIFF validity and endianness
140
+ if ( dataView . getUint16 ( offset ) == 0x4949 ) {
141
+ bigEnd = false ;
142
+ } else if ( dataView . getUint16 ( offset ) == 0x4D4D ) {
143
+ bigEnd = true ;
144
+ } else {
145
+ console . log ( "Not valid TIFF data! (no 0x4949 or 0x4D4D)" ) ;
146
+ return 0 ;
147
+ }
148
+
149
+ if ( dataView . getUint16 ( offset + 2 , ! bigEnd ) != 0x002A ) {
150
+ console . log ( "Not valid TIFF data! (no 0x002A)" ) ;
151
+ return 0 ;
152
+ }
153
+
154
+ var firstIFDOffset = dataView . getUint32 ( offset + 4 , ! bigEnd ) ;
155
+ if ( firstIFDOffset < 0x00000008 ) {
156
+ console . log ( "Not valid TIFF data! (First offset less than 8)" , dataView . getUint32 ( offset + 4 , ! bigEnd ) ) ;
157
+ return 0 ;
158
+ }
159
+
160
+ // extract orientation data
161
+ var dataStart = offset + firstIFDOffset ;
162
+ var entries = dataView . getUint16 ( dataStart , ! bigEnd ) ;
163
+ for ( var i = 0 ; i < entries ; i ++ ) {
164
+ var entryOffset = dataStart + i * 12 + 2 ;
165
+ if ( dataView . getUint16 ( entryOffset , ! bigEnd ) == 0x0112 ) {
166
+ var valueType = dataView . getUint16 ( entryOffset + 2 , ! bigEnd ) ;
167
+ var numValues = dataView . getUint32 ( entryOffset + 4 , ! bigEnd ) ;
168
+ if ( valueType != 3 && numValues != 1 ) {
169
+ console . log ( 'Invalid EXIF orientation value type (' + valueType + ') or count (' + numValues + ')' ) ;
170
+ return 0 ;
171
+ }
172
+ var value = dataView . getUint16 ( entryOffset + 8 , ! bigEnd ) ;
173
+ if ( value < 1 || value > 8 ) {
174
+ console . log ( 'Invalid EXIF orientation value (' + value + ')' ) ;
175
+ return 0 ;
176
+ }
177
+ return value ;
178
+ }
179
+ }
180
+ } else {
181
+ offset += 2 + dataView . getUint16 ( offset + 2 ) ;
182
+ }
183
+ }
184
+ return 0 ;
185
+ } ,
186
+
187
+ fixOrientation : function ( origObjURL , orientation , targetImg ) {
188
+ // fix image orientation based on exif orientation data
189
+ // exif orientation information
190
+ // http://www.impulseadventure.com/photo/exif-orientation.html
191
+ // link source wikipedia (https://en.wikipedia.org/wiki/Exif#cite_note-20)
192
+ var img = new Image ( ) ;
193
+ img . addEventListener ( 'load' , function ( event ) {
194
+ var canvas = document . createElement ( 'canvas' ) ;
195
+ var ctx = canvas . getContext ( '2d' ) ;
196
+
197
+ // switch width height if orientation needed
198
+ if ( orientation < 5 ) {
199
+ canvas . width = img . width ;
200
+ canvas . height = img . height ;
201
+ } else {
202
+ canvas . width = img . height ;
203
+ canvas . height = img . width ;
204
+ }
205
+
206
+ // transform (rotate) image - see link at beginning this method
207
+ switch ( orientation ) {
208
+ case 2 : ctx . transform ( - 1 , 0 , 0 , 1 , img . width , 0 ) ; break ;
209
+ case 3 : ctx . transform ( - 1 , 0 , 0 , - 1 , img . width , img . height ) ; break ;
210
+ case 4 : ctx . transform ( 1 , 0 , 0 , - 1 , 0 , img . height ) ; break ;
211
+ case 5 : ctx . transform ( 0 , 1 , 1 , 0 , 0 , 0 ) ; break ;
212
+ case 6 : ctx . transform ( 0 , 1 , - 1 , 0 , img . height , 0 ) ; break ;
213
+ case 7 : ctx . transform ( 0 , - 1 , - 1 , 0 , img . height , img . width ) ; break ;
214
+ case 8 : ctx . transform ( 0 , - 1 , 1 , 0 , 0 , img . width ) ; break ;
215
+ }
216
+
217
+ ctx . drawImage ( img , 0 , 0 ) ;
218
+ // pass rotated image data to the target image container
219
+ targetImg . src = canvas . toDataURL ( ) ;
220
+ } , false ) ;
221
+ // start transformation by load event
222
+ img . src = origObjURL ;
223
+ } ,
224
+
103
225
attach : function ( elem ) {
104
226
// create webcam preview and attach to DOM element
105
227
// pass in actual DOM reference, ID, or CSS selector
@@ -201,6 +323,109 @@ var Webcam = {
201
323
}
202
324
} ) ;
203
325
}
326
+ else if ( this . iOS ) {
327
+ // prepare HTML elements
328
+ var div = document . createElement ( 'div' ) ;
329
+ div . id = this . container . id + '-ios_div' ;
330
+ div . className = 'webcamjs-ios-placeholder' ;
331
+ div . style . width = '' + this . params . width + 'px' ;
332
+ div . style . height = '' + this . params . height + 'px' ;
333
+ div . style . textAlign = 'center' ;
334
+ div . style . display = 'table-cell' ;
335
+ div . style . verticalAlign = 'middle' ;
336
+ div . style . backgroundRepeat = 'no-repeat' ;
337
+ div . style . backgroundSize = 'contain' ;
338
+ div . style . backgroundPosition = 'center' ;
339
+ var span = document . createElement ( 'span' ) ;
340
+ span . className = 'webcamjs-ios-text' ;
341
+ span . innerHTML = this . params . iosPlaceholderText ;
342
+ div . appendChild ( span ) ;
343
+ var img = document . createElement ( 'img' ) ;
344
+ img . id = this . container . id + '-ios_img' ;
345
+ img . style . width = '' + this . params . dest_width + 'px' ;
346
+ img . style . height = '' + this . params . dest_height + 'px' ;
347
+ img . style . display = 'none' ;
348
+ div . appendChild ( img ) ;
349
+ var input = document . createElement ( 'input' ) ;
350
+ input . id = this . container . id + '-ios_input' ;
351
+ input . setAttribute ( 'type' , 'file' ) ;
352
+ input . setAttribute ( 'accept' , 'image/*' ) ;
353
+ input . setAttribute ( 'capture' , 'camera' ) ;
354
+
355
+ var self = this ;
356
+ var params = this . params ;
357
+ // add input listener to load the selected image
358
+ input . addEventListener ( 'change' , function ( event ) {
359
+ if ( event . target . files . length > 0 && event . target . files [ 0 ] . type . indexOf ( 'image/' ) == 0 ) {
360
+ var objURL = URL . createObjectURL ( event . target . files [ 0 ] ) ;
361
+
362
+ // load image with auto scale and crop
363
+ var image = new Image ( ) ;
364
+ image . addEventListener ( 'load' , function ( event ) {
365
+ var canvas = document . createElement ( 'canvas' ) ;
366
+ canvas . width = params . dest_width ;
367
+ canvas . height = params . dest_height ;
368
+ var ctx = canvas . getContext ( '2d' ) ;
369
+
370
+ // crop and scale image for final size
371
+ ratio = Math . min ( image . width / params . dest_width , image . height / params . dest_height ) ;
372
+ var sw = params . dest_width * ratio ;
373
+ var sh = params . dest_height * ratio ;
374
+ var sx = ( image . width - sw ) / 2 ;
375
+ var sy = ( image . height - sh ) / 2 ;
376
+ ctx . drawImage ( image , sx , sy , sw , sh , 0 , 0 , params . dest_width , params . dest_height ) ;
377
+
378
+ var dataURL = canvas . toDataURL ( ) ;
379
+ img . src = dataURL ;
380
+ div . style . backgroundImage = "url('" + dataURL + "')" ;
381
+ } , false ) ;
382
+
383
+ // read EXIF data
384
+ var fileReader = new FileReader ( ) ;
385
+ fileReader . addEventListener ( 'load' , function ( e ) {
386
+ var orientation = self . exifOrientation ( e . target . result ) ;
387
+ if ( orientation > 1 ) {
388
+ // image need to rotate (see comments on fixOrientation method for more information)
389
+ // transform image and load to image object
390
+ self . fixOrientation ( objURL , orientation , image ) ;
391
+ } else {
392
+ // load image data to image object
393
+ image . src = objURL ;
394
+ }
395
+ } , false ) ;
396
+
397
+ // Convert image data to blob format
398
+ var http = new XMLHttpRequest ( ) ;
399
+ http . open ( "GET" , objURL , true ) ;
400
+ http . responseType = "blob" ;
401
+ http . onload = function ( e ) {
402
+ if ( this . status == 200 || this . status === 0 ) {
403
+ fileReader . readAsArrayBuffer ( this . response ) ;
404
+ }
405
+ } ;
406
+ http . send ( ) ;
407
+
408
+ }
409
+ } , false ) ;
410
+ input . style . display = 'none' ;
411
+ elem . appendChild ( input ) ;
412
+ // make div clickable for open camera interface
413
+ div . addEventListener ( 'click' , function ( event ) {
414
+ if ( params . user_callback ) {
415
+ // global user_callback defined - create the snapshot
416
+ self . snap ( params . user_callback , params . user_canvas ) ;
417
+ } else {
418
+ // no global callback definied for snapshot, load image and wait for external snap method call
419
+ input . style . display = 'block' ;
420
+ input . focus ( ) ;
421
+ input . click ( ) ;
422
+ input . style . display = 'none' ;
423
+ }
424
+ } , false ) ;
425
+ elem . appendChild ( div ) ;
426
+ this . loaded = true ;
427
+ this . live = true ;
428
+ }
204
429
else if ( this . params . enable_flash && this . detectFlash ( ) ) {
205
430
// flash fallback
206
431
window . Webcam = Webcam ; // needed for flash-to-js interface
@@ -254,7 +479,7 @@ var Webcam = {
254
479
delete this . video ;
255
480
}
256
481
257
- if ( ( this . userMedia !== true ) && this . loaded ) {
482
+ if ( ( this . userMedia !== true ) && this . loaded && ! this . iOS ) {
258
483
// call for turn off camera in flash
259
484
var movie = this . getMovie ( ) ;
260
485
if ( movie && movie . _releaseCamera ) movie . _releaseCamera ( ) ;
@@ -567,6 +792,10 @@ var Webcam = {
567
792
} ,
568
793
569
794
snap : function ( user_callback , user_canvas ) {
795
+ // use global callback and canvas if not defined as parameter
796
+ if ( ! user_callback ) user_callback = this . params . user_callback ;
797
+ if ( ! user_canvas ) user_canvas = this . params . user_canvas ;
798
+
570
799
// take snapshot and return image data uri
571
800
var self = this ;
572
801
var params = this . params ;
@@ -645,6 +874,30 @@ var Webcam = {
645
874
// fire callback right away
646
875
func ( ) ;
647
876
}
877
+ else if ( this . iOS ) {
878
+ var div = document . getElementById ( this . container . id + '-ios_div' ) ;
879
+ var img = document . getElementById ( this . container . id + '-ios_img' ) ;
880
+ var input = document . getElementById ( this . container . id + '-ios_input' ) ;
881
+ // function for handle snapshot event (call user_callback and reset the interface)
882
+ iFunc = function ( event ) {
883
+ func . call ( img ) ;
884
+ img . removeEventListener ( 'load' , iFunc ) ;
885
+ div . style . backgroundImage = 'none' ;
886
+ img . removeAttribute ( 'src' ) ;
887
+ input . value = null ;
888
+ } ;
889
+ if ( ! input . value ) {
890
+ // No image selected yet, activate input field
891
+ img . addEventListener ( 'load' , iFunc ) ;
892
+ input . style . display = 'block' ;
893
+ input . focus ( ) ;
894
+ input . click ( ) ;
895
+ input . style . display = 'none' ;
896
+ } else {
897
+ // Image already selected
898
+ iFunc ( null ) ;
899
+ }
900
+ }
648
901
else {
649
902
// flash fallback
650
903
var raw_data = this . getMovie ( ) . _snap ( ) ;
0 commit comments