Merge lp:~fboucault/camera-app/resolution_options into lp:camera-app
- resolution_options
- Merge into trunk
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~fboucault/camera-app/resolution_options | ||||
Merge into: | lp:camera-app | ||||
Diff against target: |
728 lines (+396/-50) 9 files modified
BottomEdgeIndicators.qml (+1/-1) CameraApp/advancedcamerasettings.cpp (+125/-0) CameraApp/advancedcamerasettings.h (+11/-0) OptionButton.qml (+1/-1) ViewFinderOverlay.qml (+170/-4) ViewFinderView.qml (+15/-16) tests/autopilot/camera_app/emulators/main_window.py (+20/-1) tests/autopilot/camera_app/tests/test_capture.py (+6/-27) tests/autopilot/camera_app/tests/test_zoom.py (+47/-0) |
||||
To merge this branch: | bzr merge lp:~fboucault/camera-app/resolution_options | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Bill Filler (community) | Needs Fixing | ||
Review via email: mp+274402@code.launchpad.net |
This proposal has been superseded by a proposal from 2015-11-25.
Commit message
New option for the user to choose between the maximum resolution the sensor allows or the resolution that fits the screen.
Description of the change
Florian Boucault (fboucault) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:586
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:587
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:588
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:589
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:592
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Bill Filler (bfiller) wrote : | # |
Framework version needs to be 15.04.3 as the required qtubuntu-camera change is not in the current 15.04.2 version that is in ota8
- 598. By Florian Boucault
-
Bumping framework version required by the app.
- 599. By Florian Boucault
-
Reverted framework bumps
- 600. By Florian Boucault
-
Dynamically show/hide the resolution options depending on the presence of framework ubuntu-sdk-15.04.3 (OTA9)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:597
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:600
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 601. By Florian Boucault
-
Add /usr/share/
click/framework s/ to apparmor read path so that available frameworks can be determined.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:601
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 602. By Florian Boucault
-
Merged with trunk
Florian Boucault (fboucault) wrote : | # |
https:/
- 603. By Florian Boucault
-
Merged staging
- 604. By Florian Boucault
-
Reverted making the resolution options contingent on the presence of framework
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:602
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Unmerged revisions
Preview Diff
1 | === modified file 'BottomEdgeIndicators.qml' |
2 | --- BottomEdgeIndicators.qml 2015-10-22 12:21:14 +0000 |
3 | +++ BottomEdgeIndicators.qml 2015-11-25 17:47:09 +0000 |
4 | @@ -84,7 +84,7 @@ |
5 | id: indicatorIcon |
6 | anchors.fill: parent |
7 | color: "white" |
8 | - name: modelData && modelData.isToggle ? modelData.icon : modelData.get(model.selectedIndex).icon |
9 | + name: modelData && modelData.isToggle ? modelData.icon : (modelData.get(model.selectedIndex) ? modelData.get(model.selectedIndex).icon : "") |
10 | source: name ? "image://theme/%1".arg(name) : (modelData.iconSource || "") |
11 | visible: source != "" |
12 | } |
13 | |
14 | === modified file 'CameraApp/advancedcamerasettings.cpp' |
15 | --- CameraApp/advancedcamerasettings.cpp 2015-10-27 09:17:27 +0000 |
16 | +++ CameraApp/advancedcamerasettings.cpp 2015-11-25 17:47:09 +0000 |
17 | @@ -26,6 +26,10 @@ |
18 | #include <QtMultimedia/QVideoDeviceSelectorControl> |
19 | #include <QtMultimedia/QCameraFlashControl> |
20 | #include <QtMultimedia/QCameraExposureControl> |
21 | +#include <QGuiApplication> |
22 | +#include <QScreen> |
23 | + |
24 | +#include <cmath> |
25 | |
26 | // Definition of this enum value is duplicated in qtubuntu-camera |
27 | static const QCameraExposure::ExposureMode ExposureHdr = static_cast<QCameraExposure::ExposureMode>(QCameraExposure::ExposureModeVendor + 1); |
28 | @@ -222,6 +226,12 @@ |
29 | QObject::connect(m_cameraControl, |
30 | SIGNAL(captureModeChanged(QCamera::CaptureModes)), |
31 | this, SIGNAL(resolutionChanged())); |
32 | + QObject::connect(m_cameraControl, |
33 | + SIGNAL(captureModeChanged(QCamera::CaptureModes)), |
34 | + this, SIGNAL(maximumResolutionChanged())); |
35 | + QObject::connect(m_cameraControl, |
36 | + SIGNAL(captureModeChanged(QCamera::CaptureModes)), |
37 | + this, SIGNAL(fittingResolutionChanged())); |
38 | } |
39 | |
40 | m_cameraFlashControl = flashControlFromCamera(m_camera); |
41 | @@ -237,6 +247,8 @@ |
42 | m_videoEncoderControl = videoEncoderControlFromCamera(m_camera); |
43 | |
44 | Q_EMIT resolutionChanged(); |
45 | + Q_EMIT maximumResolutionChanged(); |
46 | + Q_EMIT fittingResolutionChanged(); |
47 | Q_EMIT hasFlashChanged(); |
48 | Q_EMIT hasHdrChanged(); |
49 | Q_EMIT hdrEnabledChanged(); |
50 | @@ -260,6 +272,8 @@ |
51 | } |
52 | Q_EMIT activeCameraIndexChanged(); |
53 | Q_EMIT resolutionChanged(); |
54 | + Q_EMIT maximumResolutionChanged(); |
55 | + Q_EMIT fittingResolutionChanged(); |
56 | Q_EMIT hasFlashChanged(); |
57 | Q_EMIT videoSupportedResolutionsChanged(); |
58 | } |
59 | @@ -277,6 +291,117 @@ |
60 | return QSize(); |
61 | } |
62 | |
63 | +QSize AdvancedCameraSettings::imageCaptureResolution() const |
64 | +{ |
65 | + if (m_imageEncoderControl != 0) { |
66 | + return m_imageEncoderControl->imageSettings().resolution(); |
67 | + } |
68 | + |
69 | + return QSize(); |
70 | +} |
71 | + |
72 | +QSize AdvancedCameraSettings::videoRecorderResolution() const |
73 | +{ |
74 | + if (m_videoEncoderControl != 0) { |
75 | + return m_videoEncoderControl->videoSettings().resolution(); |
76 | + } |
77 | + |
78 | + return QSize(); |
79 | +} |
80 | + |
81 | +QSize AdvancedCameraSettings::maximumResolution() const |
82 | +{ |
83 | + if (m_imageEncoderControl) { |
84 | + QList<QSize> sizes = m_imageEncoderControl->supportedResolutions( |
85 | + m_imageEncoderControl->imageSettings()); |
86 | + |
87 | + QSize maximumSize; |
88 | + long maximumPixels = 0; |
89 | + |
90 | + QList<QSize>::const_iterator it = sizes.begin(); |
91 | + while (it != sizes.end()) { |
92 | + const long pixels = ((long)((*it).width())) * ((long)((*it).height())); |
93 | + if (pixels > maximumPixels) { |
94 | + maximumSize = *it; |
95 | + maximumPixels = pixels; |
96 | + } |
97 | + ++it; |
98 | + } |
99 | + |
100 | + return maximumSize; |
101 | + } |
102 | + |
103 | + return QSize(); |
104 | +} |
105 | + |
106 | +float AdvancedCameraSettings::getScreenAspectRatio() const |
107 | +{ |
108 | + float screenAspectRatio; |
109 | + QScreen *screen = QGuiApplication::primaryScreen(); |
110 | + Q_ASSERT(!screen); |
111 | + const int kScreenWidth = screen->geometry().width(); |
112 | + const int kScreenHeight = screen->geometry().height(); |
113 | + Q_ASSERT(kScreenWidth > 0 && kScreenHeight > 0); |
114 | + |
115 | + screenAspectRatio = (kScreenWidth > kScreenHeight) ? |
116 | + ((float)kScreenWidth / (float)kScreenHeight) : ((float)kScreenHeight / (float)kScreenWidth); |
117 | + |
118 | + return screenAspectRatio; |
119 | +} |
120 | + |
121 | +QSize AdvancedCameraSettings::fittingResolution() const |
122 | +{ |
123 | + QList<float> prioritizedAspectRatios; |
124 | + prioritizedAspectRatios.append(getScreenAspectRatio()); |
125 | + const float backAspectRatios[4] = { 16.0f/9.0f, 3.0f/2.0f, 4.0f/3.0f, 5.0f/4.0f }; |
126 | + for (int i=0; i<4; ++i) { |
127 | + if (!prioritizedAspectRatios.contains(backAspectRatios[i])) { |
128 | + prioritizedAspectRatios.append(backAspectRatios[i]); |
129 | + } |
130 | + } |
131 | + |
132 | + if (m_imageEncoderControl) { |
133 | + QList<QSize> sizes = m_imageEncoderControl->supportedResolutions( |
134 | + m_imageEncoderControl->imageSettings()); |
135 | + |
136 | + QSize optimalSize; |
137 | + long optimalPixels = 0; |
138 | + |
139 | + if (!sizes.empty()) { |
140 | + float aspectRatio; |
141 | + |
142 | + // Loop over all reported camera resolutions until we find the highest |
143 | + // one that matches the current prioritized aspect ratio. If it doesn't |
144 | + // find one on the current aspect ration, it selects the next ratio and |
145 | + // tries again. |
146 | + QList<float>::const_iterator ratioIt = prioritizedAspectRatios.begin(); |
147 | + while (ratioIt != prioritizedAspectRatios.end()) { |
148 | + // Don't update the aspect ratio when using this function for finding |
149 | + // the optimal thumbnail size as it will affect the preview window size |
150 | + aspectRatio = (*ratioIt); |
151 | + |
152 | + QList<QSize>::const_iterator it = sizes.begin(); |
153 | + while (it != sizes.end()) { |
154 | + const float ratio = (float)(*it).width() / (float)(*it).height(); |
155 | + const long pixels = ((long)((*it).width())) * ((long)((*it).height())); |
156 | + const float EPSILON = 0.02; |
157 | + if (fabs(ratio - aspectRatio) < EPSILON && pixels > optimalPixels) { |
158 | + optimalSize = *it; |
159 | + optimalPixels = pixels; |
160 | + } |
161 | + ++it; |
162 | + } |
163 | + if (optimalPixels > 0) break; |
164 | + ++ratioIt; |
165 | + } |
166 | + } |
167 | + |
168 | + return optimalSize; |
169 | + } |
170 | + |
171 | + return QSize(); |
172 | +} |
173 | + |
174 | QStringList AdvancedCameraSettings::videoSupportedResolutions() const |
175 | { |
176 | if (m_videoEncoderControl) { |
177 | |
178 | === modified file 'CameraApp/advancedcamerasettings.h' |
179 | --- CameraApp/advancedcamerasettings.h 2014-12-08 19:12:52 +0000 |
180 | +++ CameraApp/advancedcamerasettings.h 2015-11-25 17:47:09 +0000 |
181 | @@ -40,6 +40,10 @@ |
182 | Q_PROPERTY (int activeCameraIndex READ activeCameraIndex WRITE setActiveCameraIndex |
183 | NOTIFY activeCameraIndexChanged) |
184 | Q_PROPERTY (QSize resolution READ resolution NOTIFY resolutionChanged) |
185 | + Q_PROPERTY (QSize imageCaptureResolution READ imageCaptureResolution) |
186 | + Q_PROPERTY (QSize videoRecorderResolution READ videoRecorderResolution) |
187 | + Q_PROPERTY (QSize maximumResolution READ maximumResolution NOTIFY maximumResolutionChanged) |
188 | + Q_PROPERTY (QSize fittingResolution READ fittingResolution NOTIFY fittingResolutionChanged) |
189 | Q_PROPERTY (QStringList videoSupportedResolutions READ videoSupportedResolutions NOTIFY videoSupportedResolutionsChanged) |
190 | Q_PROPERTY (bool hasFlash READ hasFlash NOTIFY hasFlashChanged) |
191 | Q_PROPERTY (bool hdrEnabled READ hdrEnabled WRITE setHdrEnabled NOTIFY hdrEnabledChanged) |
192 | @@ -53,6 +57,11 @@ |
193 | void setCamera(QObject* camera); |
194 | void setActiveCameraIndex(int index); |
195 | QSize resolution() const; |
196 | + QSize imageCaptureResolution() const; |
197 | + QSize videoRecorderResolution() const; |
198 | + QSize maximumResolution() const; |
199 | + QSize fittingResolution() const; |
200 | + float getScreenAspectRatio() const; |
201 | QStringList videoSupportedResolutions() const; |
202 | bool hasFlash() const; |
203 | bool hasHdr() const; |
204 | @@ -66,6 +75,8 @@ |
205 | void cameraChanged(); |
206 | void activeCameraIndexChanged(); |
207 | void resolutionChanged(); |
208 | + void maximumResolutionChanged(); |
209 | + void fittingResolutionChanged(); |
210 | void hasFlashChanged(); |
211 | void hasHdrChanged(); |
212 | void hdrEnabledChanged(); |
213 | |
214 | === modified file 'OptionButton.qml' |
215 | --- OptionButton.qml 2015-10-22 12:21:14 +0000 |
216 | +++ OptionButton.qml 2015-11-25 17:47:09 +0000 |
217 | @@ -23,7 +23,7 @@ |
218 | property var model |
219 | property string settingsProperty: model.settingsProperty |
220 | |
221 | - iconName: model.isToggle || !model.get(model.selectedIndex).icon ? model.icon : model.get(model.selectedIndex).icon |
222 | + iconName: !model.get(model.selectedIndex).icon ? model.icon : model.get(model.selectedIndex).icon |
223 | iconSource: (model && model.iconSource) ? model.iconSource : "" |
224 | on: model.isToggle ? model.get(model.selectedIndex).value : true |
225 | enabled: model.available |
226 | |
227 | === modified file 'ViewFinderOverlay.qml' |
228 | --- ViewFinderOverlay.qml 2015-10-22 12:21:14 +0000 |
229 | +++ ViewFinderOverlay.qml 2015-11-25 17:47:09 +0000 |
230 | @@ -50,6 +50,13 @@ |
231 | property bool gridEnabled: false |
232 | property bool preferRemovableStorage: false |
233 | property string videoResolution: "1920x1080" |
234 | + property bool playShutterSound: true |
235 | + // FIXME: stores the resolution selected for 2 cameras. Instead it should: |
236 | + // - support any number of cameras |
237 | + // - not rely on the camera index but on Camera.deviceId |
238 | + // Ref.: http://doc.qt.io/qt-5/qml-qtmultimedia-camera.html#deviceId-prop |
239 | + property string photoResolution0 |
240 | + property string photoResolution1 |
241 | |
242 | onFlashModeChanged: if (flashMode != Camera.FlashOff) hdrEnabled = false; |
243 | onHdrEnabledChanged: if (hdrEnabled) flashMode = Camera.FlashOff |
244 | @@ -87,12 +94,83 @@ |
245 | value: settings.videoResolution |
246 | } |
247 | |
248 | + Binding { |
249 | + target: camera.imageCapture |
250 | + property: "resolution" |
251 | + value: settings["photoResolution" + camera.advanced.activeCameraIndex] |
252 | + } |
253 | + |
254 | + Connections { |
255 | + target: camera.imageCapture |
256 | + onResolutionChanged: { |
257 | + // FIXME: this is a necessary workaround because: |
258 | + // - Neither camera.viewfinder.resolution nor camera.advanced.resolution |
259 | + // emit a changed signal when the underlying AalViewfinderSettingsControl's |
260 | + // resolution changes |
261 | + // - we know that qtubuntu-camera changes the resolution of the |
262 | + // viewfinder automatically when the capture resolution is set |
263 | + // - we need camera.viewfinder.resolution to hold the right |
264 | + // value |
265 | + camera.viewfinder.resolution = camera.advanced.resolution; |
266 | + } |
267 | + } |
268 | + |
269 | + Connections { |
270 | + target: camera.videoRecorder |
271 | + onResolutionChanged: { |
272 | + // FIXME: see workaround setting camera.viewfinder.resolution above |
273 | + camera.viewfinder.resolution = camera.advanced.resolution; |
274 | + } |
275 | + } |
276 | + |
277 | + Connections { |
278 | + target: camera |
279 | + onCaptureModeChanged: { |
280 | + // FIXME: see workaround setting camera.viewfinder.resolution above |
281 | + camera.viewfinder.resolution = camera.advanced.resolution; |
282 | + } |
283 | + } |
284 | + |
285 | function resolutionToLabel(resolution) { |
286 | // takes in a resolution string (e.g. "1920x1080") and returns a nicer |
287 | // form of it for display in the UI: "1080p" |
288 | return resolution.split("x").pop() + "p"; |
289 | } |
290 | |
291 | + function sizeToString(size) { |
292 | + return size.width + "x" + size.height; |
293 | + } |
294 | + |
295 | + function stringToSize(resolution) { |
296 | + var r = resolution.split("x"); |
297 | + return Qt.size(r[0], r[1]); |
298 | + } |
299 | + |
300 | + function sizeToAspectRatio(size) { |
301 | + var ratio = Math.max(size.width, size.height) / Math.min(size.width, size.height); |
302 | + var maxDenominator = 12; |
303 | + var epsilon; |
304 | + var numerator; |
305 | + var denominator; |
306 | + var bestDenominator; |
307 | + var bestEpsilon = 10000; |
308 | + for (denominator = 2; denominator <= maxDenominator; denominator++) { |
309 | + numerator = ratio * denominator; |
310 | + epsilon = Math.abs(Math.round(numerator) - numerator); |
311 | + if (epsilon < bestEpsilon) { |
312 | + bestEpsilon = epsilon; |
313 | + bestDenominator = denominator; |
314 | + } |
315 | + } |
316 | + numerator = Math.round(ratio * bestDenominator); |
317 | + return "%1:%2".arg(numerator).arg(bestDenominator); |
318 | + } |
319 | + |
320 | + function sizeToMegapixels(size) { |
321 | + var megapixels = (size.width * size.height) / 1000000; |
322 | + return parseFloat(megapixels.toFixed(1)) |
323 | + } |
324 | + |
325 | function updateVideoResolutionOptions() { |
326 | // Clear and refill videoResolutionOptionsModel with available resolutions |
327 | // Try to only display well known resolutions: 1080p, 720p and 480p |
328 | @@ -110,25 +188,71 @@ |
329 | } |
330 | |
331 | // If resolution setting chosen is not supported select the highest available resolution |
332 | - if (supported.indexOf(settings.videoResolution) == -1) { |
333 | + if (supported.length > 0 && supported.indexOf(settings.videoResolution) == -1) { |
334 | settings.videoResolution = supported[supported.length - 1]; |
335 | } |
336 | } |
337 | |
338 | + function updatePhotoResolutionOptions() { |
339 | + // Clear and refill photoResolutionOptionsModel with available resolutions |
340 | + photoResolutionOptionsModel.clear(); |
341 | + |
342 | + var optionMaximum = {"icon": "", |
343 | + "label": "%1 (%2MP)".arg(sizeToAspectRatio(camera.advanced.maximumResolution)) |
344 | + .arg(sizeToMegapixels(camera.advanced.maximumResolution)), |
345 | + "value": sizeToString(camera.advanced.maximumResolution)}; |
346 | + |
347 | + var optionFitting = {"icon": "", |
348 | + "label": "%1 (%2MP)".arg(sizeToAspectRatio(camera.advanced.fittingResolution)) |
349 | + .arg(sizeToMegapixels(camera.advanced.fittingResolution)), |
350 | + "value": sizeToString(camera.advanced.fittingResolution)}; |
351 | + |
352 | + photoResolutionOptionsModel.insert(0, optionMaximum); |
353 | + if (camera.advanced.fittingResolution != camera.advanced.maximumResolution) { |
354 | + photoResolutionOptionsModel.insert(1, optionFitting); |
355 | + } |
356 | + |
357 | + var photoResolution = settings["photoResolution" + camera.advanced.activeCameraIndex]; |
358 | + // If resolution setting chosen is not supported select the fitting resolution |
359 | + if (photoResolution != optionFitting.value && |
360 | + photoResolution != optionMaximum.value) { |
361 | + settings["photoResolution" + camera.advanced.activeCameraIndex] = optionFitting.value; |
362 | + } |
363 | + } |
364 | + |
365 | Component.onCompleted: { |
366 | + camera.cameraState = Camera.LoadedState; |
367 | updateVideoResolutionOptions(); |
368 | + updatePhotoResolutionOptions(); |
369 | + // FIXME: see workaround setting camera.viewfinder.resolution above |
370 | + camera.viewfinder.resolution = camera.advanced.resolution; |
371 | + camera.cameraState = Camera.ActiveState; |
372 | } |
373 | |
374 | Connections { |
375 | target: camera.advanced |
376 | onVideoSupportedResolutionsChanged: updateVideoResolutionOptions(); |
377 | + onFittingResolutionChanged: updatePhotoResolutionOptions(); |
378 | + onMaximumResolutionChanged: updatePhotoResolutionOptions(); |
379 | } |
380 | |
381 | Connections { |
382 | target: camera.advanced |
383 | onActiveCameraIndexChanged: { |
384 | + var hasPhotoResolutionSetting = (settings["photoResolution" + camera.advanced.activeCameraIndex] != "") |
385 | + // FIXME: use camera.advanced.imageCaptureResolution instead of camera.imageCapture.resolution |
386 | + // because the latter is not updated when the backend changes the resolution |
387 | + settings["photoResolution" + camera.advanced.activeCameraIndex] = sizeToString(camera.advanced.imageCaptureResolution); |
388 | + settings.videoResolution = sizeToString(camera.advanced.videoRecorderResolution); |
389 | + updatePhotoResolutionOptions(); |
390 | updateVideoResolutionOptions(); |
391 | - camera.videoRecorder.resolution = settings.videoResolution; |
392 | + // FIXME: see workaround setting camera.viewfinder.resolution above |
393 | + camera.viewfinder.resolution = camera.advanced.resolution; |
394 | + |
395 | + // If no resolution has ever been chosen, select the one that fits the screen |
396 | + if (!hasPhotoResolutionSetting) { |
397 | + settings["photoResolution" + camera.advanced.activeCameraIndex] = sizeToString(camera.advanced.fittingResolution); |
398 | + } |
399 | } |
400 | } |
401 | |
402 | @@ -280,7 +404,7 @@ |
403 | property bool isToggle: true |
404 | property int selectedIndex: bottomEdge.indexForValue(hdrOptionsModel, settings.hdrEnabled) |
405 | property bool available: camera.advanced.hasHdr |
406 | - property bool visible: true |
407 | + property bool visible: camera.captureMode === Camera.CaptureStillImage |
408 | property bool showInIndicators: true |
409 | |
410 | ListElement { |
411 | @@ -404,6 +528,41 @@ |
412 | property bool available: true |
413 | property bool visible: camera.captureMode == Camera.CaptureVideo |
414 | property bool showInIndicators: false |
415 | + }, |
416 | + ListModel { |
417 | + id: shutterSoundOptionsModel |
418 | + |
419 | + property string settingsProperty: "playShutterSound" |
420 | + property string icon: "" |
421 | + property string label: "" |
422 | + property bool isToggle: true |
423 | + property int selectedIndex: bottomEdge.indexForValue(shutterSoundOptionsModel, settings.playShutterSound) |
424 | + property bool available: true |
425 | + property bool visible: camera.captureMode === Camera.CaptureStillImage |
426 | + property bool showInIndicators: false |
427 | + |
428 | + ListElement { |
429 | + icon: "audio-volume-high" |
430 | + label: QT_TR_NOOP("On") |
431 | + value: true |
432 | + } |
433 | + ListElement { |
434 | + icon: "audio-volume-muted" |
435 | + label: QT_TR_NOOP("Off") |
436 | + value: false |
437 | + } |
438 | + }, |
439 | + ListModel { |
440 | + id: photoResolutionOptionsModel |
441 | + |
442 | + property string settingsProperty: "photoResolution" + camera.advanced.activeCameraIndex |
443 | + property string icon: "" |
444 | + property string label: sizeToAspectRatio(stringToSize(settings[settingsProperty])) |
445 | + property bool isToggle: false |
446 | + property int selectedIndex: bottomEdge.indexForValue(photoResolutionOptionsModel, settings[settingsProperty]) |
447 | + property bool available: true |
448 | + property bool visible: camera.captureMode == Camera.CaptureStillImage |
449 | + property bool showInIndicators: false |
450 | } |
451 | ] |
452 | |
453 | @@ -571,18 +730,25 @@ |
454 | function completeSwitch() { |
455 | viewFinderSwitcherAnimation.restart(); |
456 | camera.switchInProgress = false; |
457 | + zoomControl.value = camera.currentZoom; |
458 | } |
459 | |
460 | function changeRecordMode() { |
461 | if (camera.captureMode == Camera.CaptureVideo) camera.videoRecorder.stop() |
462 | camera.captureMode = (camera.captureMode == Camera.CaptureVideo) ? Camera.CaptureStillImage : Camera.CaptureVideo |
463 | + zoomControl.value = camera.currentZoom |
464 | + } |
465 | + |
466 | + Connections { |
467 | + target: Qt.application |
468 | + onActiveChanged: if (active) zoomControl.value = camera.currentZoom |
469 | } |
470 | |
471 | Timer { |
472 | id: shootingTimer |
473 | repeat: true |
474 | triggeredOnStart: true |
475 | - |
476 | + |
477 | property int remainingSecs: 0 |
478 | |
479 | onTriggered: { |
480 | |
481 | === modified file 'ViewFinderView.qml' |
482 | --- ViewFinderView.qml 2015-11-16 15:54:25 +0000 |
483 | +++ ViewFinderView.qml 2015-11-25 17:47:09 +0000 |
484 | @@ -36,6 +36,7 @@ |
485 | Camera { |
486 | id: camera |
487 | captureMode: Camera.CaptureStillImage |
488 | + cameraState: Camera.UnloadedState |
489 | StateSaver.properties: "captureMode" |
490 | |
491 | function manualFocus(x, y) { |
492 | @@ -67,10 +68,6 @@ |
493 | StateSaver.properties: "activeCameraIndex" |
494 | } |
495 | |
496 | - Component.onCompleted: { |
497 | - camera.start(); |
498 | - } |
499 | - |
500 | /* Use only digital zoom for now as it's what phone cameras mostly use. |
501 | TODO: if optical zoom is available, maximumZoom should be the combined |
502 | range of optical and digital zoom and currentZoom should adjust the two |
503 | @@ -121,12 +118,14 @@ |
504 | target: Qt.application |
505 | onActiveChanged: { |
506 | if (Qt.application.active) { |
507 | - camera.start() |
508 | + if (camera.cameraState == Camera.LoadedState) { |
509 | + camera.cameraState = Camera.ActiveState; |
510 | + } |
511 | } else if (!application.desktopMode) { |
512 | if (camera.videoRecorder.recorderState == CameraRecorder.RecordingState) { |
513 | camera.videoRecorder.stop(); |
514 | } |
515 | - camera.stop() |
516 | + camera.cameraState = Camera.LoadedState; |
517 | } |
518 | } |
519 | } |
520 | @@ -250,16 +249,16 @@ |
521 | axis.x: 0; axis.y: 1; axis.z: 0 |
522 | angle: application.desktopMode ? 180 : 0 |
523 | } |
524 | - |
525 | - ViewFinderGeometry { |
526 | - id: viewFinderGeometry |
527 | - anchors.centerIn: parent |
528 | - |
529 | - cameraResolution: camera.advanced.resolution |
530 | - viewFinderHeight: viewFinder.height |
531 | - viewFinderWidth: viewFinder.width |
532 | - viewFinderOrientation: viewFinder.orientation |
533 | - } |
534 | + } |
535 | + |
536 | + ViewFinderGeometry { |
537 | + id: viewFinderGeometry |
538 | + anchors.centerIn: parent |
539 | + |
540 | + cameraResolution: camera.viewfinder.resolution |
541 | + viewFinderHeight: viewFinder.height |
542 | + viewFinderWidth: viewFinder.width |
543 | + viewFinderOrientation: viewFinder.orientation |
544 | } |
545 | |
546 | Item { |
547 | |
548 | === modified file 'tests/autopilot/camera_app/emulators/main_window.py' |
549 | --- tests/autopilot/camera_app/emulators/main_window.py 2015-11-20 15:01:02 +0000 |
550 | +++ tests/autopilot/camera_app/emulators/main_window.py 2015-11-25 17:47:09 +0000 |
551 | @@ -7,7 +7,7 @@ |
552 | |
553 | from camera_app.emulators.panel import Panel |
554 | from autopilot.matchers import Eventually |
555 | -from testtools.matchers import Equals |
556 | +from testtools.matchers import Equals, NotEquals |
557 | |
558 | |
559 | class MainWindow(object): |
560 | @@ -163,3 +163,22 @@ |
561 | tx, ty, (tx + view_switcher.width // 2), ty, rate=1) |
562 | viewfinder = self.get_viewfinder() |
563 | testCase.assertThat(viewfinder.inView, Eventually(Equals(True))) |
564 | + |
565 | + def switch_cameras(self): |
566 | + # Swap cameras and wait for camera to settle |
567 | + shoot_button = self.get_exposure_button() |
568 | + swap_camera_button = self.get_swap_camera_button() |
569 | + self.app.pointing_device.move_to_object(swap_camera_button) |
570 | + self.app.pointing_device.click() |
571 | + shoot_button.enabled.wait_for(True) |
572 | + |
573 | + def switch_recording_mode(self): |
574 | + record_control = self.get_record_control() |
575 | + |
576 | + # Wait for the camera overlay to be loaded |
577 | + record_control.enabled.wait_for(True) |
578 | + record_control.width.wait_for(NotEquals(0)) |
579 | + record_control.height.wait_for(NotEquals(0)) |
580 | + |
581 | + self.app.pointing_device.move_to_object(record_control) |
582 | + self.app.pointing_device.click() |
583 | |
584 | === modified file 'tests/autopilot/camera_app/tests/test_capture.py' |
585 | --- tests/autopilot/camera_app/tests/test_capture.py 2015-10-05 13:14:12 +0000 |
586 | +++ tests/autopilot/camera_app/tests/test_capture.py 2015-11-25 17:47:09 +0000 |
587 | @@ -90,13 +90,11 @@ |
588 | |
589 | """ |
590 | # Get all the elements |
591 | - record_control = self.main_window.get_record_control() |
592 | stop_watch = self.main_window.get_stop_watch() |
593 | exposure_button = self.main_window.get_exposure_button() |
594 | |
595 | # Click the record button to toggle photo/video mode |
596 | - self.pointing_device.move_to_object(record_control) |
597 | - self.pointing_device.click() |
598 | + self.main_window.switch_recording_mode() |
599 | |
600 | # Before recording the stop watch should read zero recording time |
601 | # and not be visible anyway. |
602 | @@ -137,8 +135,7 @@ |
603 | # Now stop the video and go back to picture mode and check if |
604 | # everything resets itself to previous states |
605 | self.pointing_device.click() |
606 | - self.pointing_device.move_to_object(record_control) |
607 | - self.pointing_device.click() |
608 | + self.main_window.switch_recording_mode() |
609 | |
610 | self.assertThat(stop_watch.opacity, Eventually(Equals(0.0))) |
611 | |
612 | @@ -268,7 +265,7 @@ |
613 | """Test recording videos at a set resolution and switching cameras""" |
614 | def test_video_resolution_setting_switching_cameras(self): |
615 | # switch to video recording and empty video folder |
616 | - self.switch_to_video_recording() |
617 | + self.main_window.switch_recording_mode() |
618 | self.delete_all_videos() |
619 | |
620 | # select the first resolution for the current camera |
621 | @@ -277,14 +274,14 @@ |
622 | self.set_video_resolution(initial_resolution) |
623 | |
624 | # switch cameras and select the last resolution for the current camera |
625 | - self.switch_cameras() |
626 | + self.main_window.switch_cameras() |
627 | resolutions = self.get_available_video_resolutions() |
628 | expected_resolution = resolutions[-1] |
629 | self.assertThat(expected_resolution, NotEquals(initial_resolution)) |
630 | self.set_video_resolution(expected_resolution) |
631 | |
632 | # switch back to the initial camera and record a video |
633 | - self.switch_cameras() |
634 | + self.main_window.switch_cameras() |
635 | self.record_video(2) |
636 | video_file = self.get_first_video() |
637 | height = self.read_video_height(video_file) |
638 | @@ -292,17 +289,9 @@ |
639 | expected_resolution) |
640 | self.assertThat(height, Equals(expected_height)) |
641 | |
642 | - def switch_cameras(self): |
643 | - # Swap cameras and wait for camera to settle |
644 | - shoot_button = self.main_window.get_exposure_button() |
645 | - swap_camera_button = self.main_window.get_swap_camera_button() |
646 | - self.pointing_device.move_to_object(swap_camera_button) |
647 | - self.pointing_device.click() |
648 | - self.assertThat(shoot_button.enabled, Eventually(Equals(True))) |
649 | - |
650 | """Test recording videos at various resolutions""" |
651 | def test_video_resolution_setting(self): |
652 | - self.switch_to_video_recording() |
653 | + self.main_window.switch_recording_mode() |
654 | resolutions = self.get_available_video_resolutions() |
655 | |
656 | for resolution_label in resolutions: |
657 | @@ -316,16 +305,6 @@ |
658 | self.assertThat(height, Equals(expected_height)) |
659 | self.dismiss_first_photo_hint() |
660 | |
661 | - def switch_to_video_recording(self): |
662 | - record_control = self.main_window.get_record_control() |
663 | - # Wait for the camera overlay to be loaded |
664 | - self.assertThat(record_control.enabled, Eventually(Equals(True))) |
665 | - self.assertThat(record_control.width, Eventually(NotEquals(0))) |
666 | - self.assertThat(record_control.height, Eventually(NotEquals(0))) |
667 | - |
668 | - self.pointing_device.move_to_object(record_control) |
669 | - self.pointing_device.click() |
670 | - |
671 | def get_available_video_resolutions(self): |
672 | # open bottom edge |
673 | bottom_edge = self.main_window.get_bottom_edge() |
674 | |
675 | === modified file 'tests/autopilot/camera_app/tests/test_zoom.py' |
676 | --- tests/autopilot/camera_app/tests/test_zoom.py 2015-04-29 15:56:44 +0000 |
677 | +++ tests/autopilot/camera_app/tests/test_zoom.py 2015-11-25 17:47:09 +0000 |
678 | @@ -68,3 +68,50 @@ |
679 | |
680 | self.pointing_device.drag(tx, ty, (tx - zoom_control.width), ty) |
681 | self.assertThat(zoom_control.value, Eventually(Equals(1.0))) |
682 | + |
683 | + """Tests zoom is reset to minimum on camera switch""" |
684 | + def test_zoom_reset_on_camera_change(self): |
685 | + zoom_control = self.main_window.get_zoom_control() |
686 | + zoom_slider = self.main_window.get_zoom_slider() |
687 | + |
688 | + self.activate_zoom() |
689 | + x, y, w, h = zoom_slider.globalRect |
690 | + tx = x + (w // 2) |
691 | + ty = y + (h // 2) |
692 | + self.pointing_device.drag(tx, ty, (tx + zoom_control.width), ty) |
693 | + self.assertThat( |
694 | + zoom_control.value, Eventually(Equals(zoom_control.maximumValue))) |
695 | + |
696 | + self.main_window.switch_cameras() |
697 | + self.assertThat( |
698 | + zoom_control.value, Eventually(Equals(zoom_control.minimumValue))) |
699 | + |
700 | + self.activate_zoom() |
701 | + self.pointing_device.drag(tx, ty, (tx + zoom_control.width), ty) |
702 | + self.assertThat( |
703 | + zoom_control.value, Eventually(Equals(zoom_control.maximumValue))) |
704 | + |
705 | + self.main_window.switch_cameras() |
706 | + self.assertThat( |
707 | + zoom_control.value, Eventually(Equals(zoom_control.minimumValue))) |
708 | + |
709 | + """Tests zoom is reset to minimum on recording mode switch""" |
710 | + def test_zoom_reset_on_recording_mode_change(self): |
711 | + zoom_control = self.main_window.get_zoom_control() |
712 | + zoom_slider = self.main_window.get_zoom_slider() |
713 | + |
714 | + self.activate_zoom() |
715 | + x, y, w, h = zoom_slider.globalRect |
716 | + tx = x + (w // 2) |
717 | + ty = y + (h // 2) |
718 | + self.pointing_device.drag(tx, ty, (tx + zoom_control.width), ty) |
719 | + self.assertThat( |
720 | + zoom_control.value, Eventually(Equals(zoom_control.maximumValue))) |
721 | + |
722 | + self.main_window.switch_recording_mode() |
723 | + self.assertThat( |
724 | + zoom_control.value, Eventually(Equals(zoom_control.minimumValue))) |
725 | + |
726 | + # Ideally we should test the same thing when switching back to photo |
727 | + # mode, however due to http://pad.lv/1191088 zooming when recording |
728 | + # video is disabled, so adding that test is pointless until fixed. |
Depends on https:/ /code.launchpad .net/~fboucault /qtubuntu- camera/ resolution_ fixes/+ merge/274404