This repository has been archived by the owner on Nov 3, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
viewfinder.js
307 lines (275 loc) · 8.64 KB
/
viewfinder.js
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
define(function(require, exports, module) {
'use strict';
/**
* Dependencies
*/
var debug = require('debug')('controller:viewfinder');
var bindAll = require('lib/bind-all');
var FocusView = require('views/focus');
var FacesView = require('views/faces');
var calculateFocusArea = require('lib/calculate-focus-area');
var convertFaceToPixels = require('lib/convert-face-to-pixel-coordinates');
/**
* Exports
*/
module.exports = function(app) { return new ViewfinderController(app); };
module.exports.ViewfinderController = ViewfinderController;
/**
* Initialize a new `ViewfinderController`
*
* @param {App} app
*/
function ViewfinderController(app) {
debug('initializing');
bindAll(this);
this.app = app;
this.views = {};
this.camera = app.camera;
this.activity = app.activity;
this.settings = app.settings;
this.views.viewfinder = app.views.viewfinder;
// Append focus ring to viewfinder
this.views.focus = new FocusView();
this.views.focus.appendTo(this.views.viewfinder.el);
this.views.faces = new FacesView();
this.views.faces.appendTo(this.views.viewfinder.el);
this.bindEvents();
this.configure();
debug('initialized');
}
/**
* Initial configuration.
*
* @private
*/
ViewfinderController.prototype.configure = function() {
var settings = this.app.settings;
var zoomSensitivity = settings.viewfinder.get('zoomGestureSensitivity');
this.sensitivity = zoomSensitivity * window.innerWidth;
this.configureScaleType();
this.configureGrid();
};
/**
* Configure the viewfinder scale type,
* aspect fill/fit, depending on setting.
*
* @private
*/
ViewfinderController.prototype.configureScaleType = function() {
var scaleType = this.app.settings.viewfinder.get('scaleType');
this.views.viewfinder.scaleType = scaleType;
debug('set scale type: %s', scaleType);
};
/**
* Show/hide grid depending on currently
* selected option.
*
* @private
*/
ViewfinderController.prototype.configureGrid = function() {
var grid = this.app.settings.grid.selected('key');
this.views.viewfinder.set('grid', grid);
};
/**
* Hides the viewfinder frame-grid.
*
* @private
*/
ViewfinderController.prototype.hideGrid = function() {
this.views.viewfinder.set('grid', 'off');
};
/**
* Bind to relavant events.
*
* @private
*/
ViewfinderController.prototype.bindEvents = function() {
this.app.settings.grid.on('change:selected',
this.views.viewfinder.setter('grid'));
this.views.viewfinder.on('click', this.app.firer('viewfinder:click'));
this.views.viewfinder.on('click', this.onViewfinderClicked);
this.camera.on('zoomchanged', this.onZoomChanged);
this.camera.on('zoomconfigured', this.onZoomConfigured);
this.app.on('camera:autofocuschanged', this.views.focus.showAutoFocusRing);
this.app.on('camera:focusconfigured', this.onFocusConfigured);
this.app.on('camera:focusstatechanged', this.views.focus.setFocusState);
this.app.on('camera:facesdetected', this.onFacesDetected);
this.app.on('camera:shutter', this.views.viewfinder.shutter);
this.app.on('camera:busy', this.views.viewfinder.disable);
this.app.on('camera:ready', this.views.viewfinder.enable);
this.app.on('previewgallery:closed', this.onPreviewGalleryClosed);
this.app.on('camera:configured', this.onCameraConfigured);
this.app.on('previewgallery:opened', this.stopStream);
this.app.on('settings:closed', this.configureGrid);
this.app.on('settings:opened', this.hideGrid);
this.app.on('hidden', this.stopStream);
this.app.on('pinchchanged', this.onPinchChanged);
};
/**
* Perform required viewfinder configuration
* once the camera has configured.
*
* @private
*/
ViewfinderController.prototype.onCameraConfigured = function() {
debug('configuring');
this.startStream();
this.configurePreview();
// BUG: We have to use a 300ms timeout here
// to conceal a Gecko rendering bug whereby the
// video element appears not to have painted the
// newly set dimensions before fading in.
// https://bugzilla.mozilla.org/show_bug.cgi?id=982230
if (!this.app.criticalPathDone) { this.show(); }
else { setTimeout(this.show, 280); }
};
ViewfinderController.prototype.show = function() {
this.views.viewfinder.fadeIn();
this.app.emit('viewfinder:visible');
};
/**
* Sets appropiate flags when the camera focus is configured
*/
ViewfinderController.prototype.onFocusConfigured = function(config) {
this.views.focus.setFocusMode(config.mode);
this.touchFocusEnabled = config.touchFocus;
this.views.faces.clear();
if (config.maxDetectedFaces > 0) {
this.views.faces.configure(config.maxDetectedFaces);
}
};
ViewfinderController.prototype.onFacesDetected = function(faces) {
var faceInPixels;
var facesInPixels = [];
var viewfinderSize = this.views.viewfinder.getSize();
var viewportHeight = viewfinderSize.height;
var viewportWidth = viewfinderSize.width;
faces.forEach(function(face, index) {
// Face comes in camera coordinates from gecko
faceInPixels = convertFaceToPixels(face, viewportWidth, viewportHeight);
facesInPixels.push(faceInPixels);
});
this.views.faces.show();
this.views.faces.render(facesInPixels);
};
/**
* Starts the stream, only if
* the app is currently visible.
*
* @private
*/
ViewfinderController.prototype.onPreviewGalleryClosed = function() {
if (this.app.hidden) { return; }
this.startStream();
};
/**
* Start the viewfinder stream flowing
* with the current camera configuration.
*
* This indirectly enforces a screen wakelock,
* meaning the device is unable to go to sleep.
*
* We don't want the stream to start flowing if
* the preview-gallery is open, as this prevents
* the device falling asleep.
*
* @private
*/
ViewfinderController.prototype.startStream = function() {
if (this.app.get('previewGalleryOpen')) { return; }
this.camera.loadStreamInto(this.views.viewfinder.els.video);
debug('stream started');
};
/**
* Stop the preview stream flowing.
*
* This indirectly removes the wakelock
* that is magically enforced by the
* flowing camera stream. Meaning the
* device is able to go to sleep.
*
* @private
*/
ViewfinderController.prototype.stopStream = function() {
this.views.viewfinder.stopStream();
debug('stream stopped');
};
/**
* Configure the size and postion
* of the preview video stream.
*
* @private
*/
ViewfinderController.prototype.configurePreview = function() {
var camera = this.app.settings.cameras.selected('key');
var isFrontCamera = camera === 'front';
var sensorAngle = this.camera.getSensorAngle();
var previewSize = this.camera.previewSize();
this.views.viewfinder.updatePreview(previewSize, sensorAngle, isFrontCamera);
};
/**
* Configures the viewfinder
* to the current camera.
*
* @private
*/
ViewfinderController.prototype.onZoomConfigured = function() {
var zoomSupported = this.camera.isZoomSupported();
var zoomEnabled = this.app.settings.zoom.enabled();
var enableZoom = zoomSupported && zoomEnabled;
if (!enableZoom) {
this.views.viewfinder.disableZoom();
return;
}
if (this.app.settings.zoom.get('useZoomPreviewAdjustment')) {
this.views.viewfinder.enableZoomPreviewAdjustment();
} else {
this.views.viewfinder.disableZoomPreviewAdjustment();
}
var minimumZoom = this.camera.getMinimumZoom();
var maximumZoom = this.camera.getMaximumZoom();
this.views.viewfinder.enableZoom(minimumZoom, maximumZoom);
};
/**
* Updates the zoom level on the camera
* when the pinch changes.
*
* @private
*/
ViewfinderController.prototype.onPinchChanged = function(deltaPinch) {
var zoom = this.views.viewfinder._zoom *
(1 + (deltaPinch / this.sensitivity));
this.views.viewfinder.setZoom(zoom);
this.camera.setZoom(zoom);
};
/**
* Responds to changes of the `zoom` value on the Camera to update the
* view's internal state so that the pinch-to-zoom gesture can resume
* zooming from the updated value. Also, updates the CSS scale transform
* on the <video/> tag to compensate for zooming beyond the
* `maxHardwareZoom` value.
*
* @param {Number} zoom
*/
ViewfinderController.prototype.onZoomChanged = function(zoom) {
var zoomPreviewAdjustment = this.camera.getZoomPreviewAdjustment();
this.views.viewfinder.setZoomPreviewAdjustment(zoomPreviewAdjustment);
this.views.viewfinder.setZoom(zoom);
};
ViewfinderController.prototype.onViewfinderClicked = function(e) {
if (!this.touchFocusEnabled || this.app.get('timerActive')) {
return;
}
var focusPoint = {
x: e.pageX,
y: e.pageY
};
this.views.faces.hide();
focusPoint.area = calculateFocusArea(
focusPoint.x, focusPoint.y,
this.views.viewfinder.el.clientWidth,
this.views.viewfinder.el.clientHeight);
this.views.focus.setPosition(focusPoint.x, focusPoint.y);
this.app.emit('viewfinder:focuspointchanged', focusPoint);
};
});