Merge lp:~fboucault/camera-app/resolution_options into lp:camera-app/staging

Proposed by Florian Boucault
Status: Merged
Approved by: Florian Boucault
Approved revision: 604
Merged at revision: 611
Proposed branch: lp:~fboucault/camera-app/resolution_options
Merge into: lp:camera-app/staging
Diff against target: 473 lines (+289/-19)
5 files modified
BottomEdgeIndicators.qml (+1/-1)
CameraApp/advancedcamerasettings.cpp (+125/-0)
CameraApp/advancedcamerasettings.h (+11/-0)
ViewFinderOverlay.qml (+137/-2)
ViewFinderView.qml (+15/-16)
To merge this branch: bzr merge lp:~fboucault/camera-app/resolution_options
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Pending
Bill Filler Pending
Review via email: mp+278621@code.launchpad.net

This proposal supersedes a proposal from 2015-10-14.

Commit message

New option for the user to choose between the maximum resolution the sensor allows or the resolution that fits the screen.

To post a comment you must log in.
Revision history for this message
Florian Boucault (fboucault) wrote : Posted in a previous version of this proposal
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Bill Filler (bfiller) wrote : Posted in a previous version of this proposal

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

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Florian Boucault (fboucault) wrote : Posted in a previous version of this proposal
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'BottomEdgeIndicators.qml'
2--- BottomEdgeIndicators.qml 2015-10-22 12:21:14 +0000
3+++ BottomEdgeIndicators.qml 2015-11-25 17:47:21 +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:21 +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:21 +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 'ViewFinderOverlay.qml'
215--- ViewFinderOverlay.qml 2015-11-25 17:03:06 +0000
216+++ ViewFinderOverlay.qml 2015-11-25 17:47:21 +0000
217@@ -51,6 +51,12 @@
218 property bool preferRemovableStorage: false
219 property string videoResolution: "1920x1080"
220 property bool playShutterSound: true
221+ // FIXME: stores the resolution selected for 2 cameras. Instead it should:
222+ // - support any number of cameras
223+ // - not rely on the camera index but on Camera.deviceId
224+ // Ref.: http://doc.qt.io/qt-5/qml-qtmultimedia-camera.html#deviceId-prop
225+ property string photoResolution0
226+ property string photoResolution1
227
228 onFlashModeChanged: if (flashMode != Camera.FlashOff) hdrEnabled = false;
229 onHdrEnabledChanged: if (hdrEnabled) flashMode = Camera.FlashOff
230@@ -88,12 +94,83 @@
231 value: settings.videoResolution
232 }
233
234+ Binding {
235+ target: camera.imageCapture
236+ property: "resolution"
237+ value: settings["photoResolution" + camera.advanced.activeCameraIndex]
238+ }
239+
240+ Connections {
241+ target: camera.imageCapture
242+ onResolutionChanged: {
243+ // FIXME: this is a necessary workaround because:
244+ // - Neither camera.viewfinder.resolution nor camera.advanced.resolution
245+ // emit a changed signal when the underlying AalViewfinderSettingsControl's
246+ // resolution changes
247+ // - we know that qtubuntu-camera changes the resolution of the
248+ // viewfinder automatically when the capture resolution is set
249+ // - we need camera.viewfinder.resolution to hold the right
250+ // value
251+ camera.viewfinder.resolution = camera.advanced.resolution;
252+ }
253+ }
254+
255+ Connections {
256+ target: camera.videoRecorder
257+ onResolutionChanged: {
258+ // FIXME: see workaround setting camera.viewfinder.resolution above
259+ camera.viewfinder.resolution = camera.advanced.resolution;
260+ }
261+ }
262+
263+ Connections {
264+ target: camera
265+ onCaptureModeChanged: {
266+ // FIXME: see workaround setting camera.viewfinder.resolution above
267+ camera.viewfinder.resolution = camera.advanced.resolution;
268+ }
269+ }
270+
271 function resolutionToLabel(resolution) {
272 // takes in a resolution string (e.g. "1920x1080") and returns a nicer
273 // form of it for display in the UI: "1080p"
274 return resolution.split("x").pop() + "p";
275 }
276
277+ function sizeToString(size) {
278+ return size.width + "x" + size.height;
279+ }
280+
281+ function stringToSize(resolution) {
282+ var r = resolution.split("x");
283+ return Qt.size(r[0], r[1]);
284+ }
285+
286+ function sizeToAspectRatio(size) {
287+ var ratio = Math.max(size.width, size.height) / Math.min(size.width, size.height);
288+ var maxDenominator = 12;
289+ var epsilon;
290+ var numerator;
291+ var denominator;
292+ var bestDenominator;
293+ var bestEpsilon = 10000;
294+ for (denominator = 2; denominator <= maxDenominator; denominator++) {
295+ numerator = ratio * denominator;
296+ epsilon = Math.abs(Math.round(numerator) - numerator);
297+ if (epsilon < bestEpsilon) {
298+ bestEpsilon = epsilon;
299+ bestDenominator = denominator;
300+ }
301+ }
302+ numerator = Math.round(ratio * bestDenominator);
303+ return "%1:%2".arg(numerator).arg(bestDenominator);
304+ }
305+
306+ function sizeToMegapixels(size) {
307+ var megapixels = (size.width * size.height) / 1000000;
308+ return parseFloat(megapixels.toFixed(1))
309+ }
310+
311 function updateVideoResolutionOptions() {
312 // Clear and refill videoResolutionOptionsModel with available resolutions
313 // Try to only display well known resolutions: 1080p, 720p and 480p
314@@ -111,25 +188,71 @@
315 }
316
317 // If resolution setting chosen is not supported select the highest available resolution
318- if (supported.indexOf(settings.videoResolution) == -1) {
319+ if (supported.length > 0 && supported.indexOf(settings.videoResolution) == -1) {
320 settings.videoResolution = supported[supported.length - 1];
321 }
322 }
323
324+ function updatePhotoResolutionOptions() {
325+ // Clear and refill photoResolutionOptionsModel with available resolutions
326+ photoResolutionOptionsModel.clear();
327+
328+ var optionMaximum = {"icon": "",
329+ "label": "%1 (%2MP)".arg(sizeToAspectRatio(camera.advanced.maximumResolution))
330+ .arg(sizeToMegapixels(camera.advanced.maximumResolution)),
331+ "value": sizeToString(camera.advanced.maximumResolution)};
332+
333+ var optionFitting = {"icon": "",
334+ "label": "%1 (%2MP)".arg(sizeToAspectRatio(camera.advanced.fittingResolution))
335+ .arg(sizeToMegapixels(camera.advanced.fittingResolution)),
336+ "value": sizeToString(camera.advanced.fittingResolution)};
337+
338+ photoResolutionOptionsModel.insert(0, optionMaximum);
339+ if (camera.advanced.fittingResolution != camera.advanced.maximumResolution) {
340+ photoResolutionOptionsModel.insert(1, optionFitting);
341+ }
342+
343+ var photoResolution = settings["photoResolution" + camera.advanced.activeCameraIndex];
344+ // If resolution setting chosen is not supported select the fitting resolution
345+ if (photoResolution != optionFitting.value &&
346+ photoResolution != optionMaximum.value) {
347+ settings["photoResolution" + camera.advanced.activeCameraIndex] = optionFitting.value;
348+ }
349+ }
350+
351 Component.onCompleted: {
352+ camera.cameraState = Camera.LoadedState;
353 updateVideoResolutionOptions();
354+ updatePhotoResolutionOptions();
355+ // FIXME: see workaround setting camera.viewfinder.resolution above
356+ camera.viewfinder.resolution = camera.advanced.resolution;
357+ camera.cameraState = Camera.ActiveState;
358 }
359
360 Connections {
361 target: camera.advanced
362 onVideoSupportedResolutionsChanged: updateVideoResolutionOptions();
363+ onFittingResolutionChanged: updatePhotoResolutionOptions();
364+ onMaximumResolutionChanged: updatePhotoResolutionOptions();
365 }
366
367 Connections {
368 target: camera.advanced
369 onActiveCameraIndexChanged: {
370+ var hasPhotoResolutionSetting = (settings["photoResolution" + camera.advanced.activeCameraIndex] != "")
371+ // FIXME: use camera.advanced.imageCaptureResolution instead of camera.imageCapture.resolution
372+ // because the latter is not updated when the backend changes the resolution
373+ settings["photoResolution" + camera.advanced.activeCameraIndex] = sizeToString(camera.advanced.imageCaptureResolution);
374+ settings.videoResolution = sizeToString(camera.advanced.videoRecorderResolution);
375+ updatePhotoResolutionOptions();
376 updateVideoResolutionOptions();
377- camera.videoRecorder.resolution = settings.videoResolution;
378+ // FIXME: see workaround setting camera.viewfinder.resolution above
379+ camera.viewfinder.resolution = camera.advanced.resolution;
380+
381+ // If no resolution has ever been chosen, select the one that fits the screen
382+ if (!hasPhotoResolutionSetting) {
383+ settings["photoResolution" + camera.advanced.activeCameraIndex] = sizeToString(camera.advanced.fittingResolution);
384+ }
385 }
386 }
387
388@@ -428,6 +551,18 @@
389 label: QT_TR_NOOP("Off")
390 value: false
391 }
392+ },
393+ ListModel {
394+ id: photoResolutionOptionsModel
395+
396+ property string settingsProperty: "photoResolution" + camera.advanced.activeCameraIndex
397+ property string icon: ""
398+ property string label: sizeToAspectRatio(stringToSize(settings[settingsProperty]))
399+ property bool isToggle: false
400+ property int selectedIndex: bottomEdge.indexForValue(photoResolutionOptionsModel, settings[settingsProperty])
401+ property bool available: true
402+ property bool visible: camera.captureMode == Camera.CaptureStillImage
403+ property bool showInIndicators: false
404 }
405 ]
406
407
408=== modified file 'ViewFinderView.qml'
409--- ViewFinderView.qml 2015-11-16 15:54:25 +0000
410+++ ViewFinderView.qml 2015-11-25 17:47:21 +0000
411@@ -36,6 +36,7 @@
412 Camera {
413 id: camera
414 captureMode: Camera.CaptureStillImage
415+ cameraState: Camera.UnloadedState
416 StateSaver.properties: "captureMode"
417
418 function manualFocus(x, y) {
419@@ -67,10 +68,6 @@
420 StateSaver.properties: "activeCameraIndex"
421 }
422
423- Component.onCompleted: {
424- camera.start();
425- }
426-
427 /* Use only digital zoom for now as it's what phone cameras mostly use.
428 TODO: if optical zoom is available, maximumZoom should be the combined
429 range of optical and digital zoom and currentZoom should adjust the two
430@@ -121,12 +118,14 @@
431 target: Qt.application
432 onActiveChanged: {
433 if (Qt.application.active) {
434- camera.start()
435+ if (camera.cameraState == Camera.LoadedState) {
436+ camera.cameraState = Camera.ActiveState;
437+ }
438 } else if (!application.desktopMode) {
439 if (camera.videoRecorder.recorderState == CameraRecorder.RecordingState) {
440 camera.videoRecorder.stop();
441 }
442- camera.stop()
443+ camera.cameraState = Camera.LoadedState;
444 }
445 }
446 }
447@@ -250,16 +249,16 @@
448 axis.x: 0; axis.y: 1; axis.z: 0
449 angle: application.desktopMode ? 180 : 0
450 }
451-
452- ViewFinderGeometry {
453- id: viewFinderGeometry
454- anchors.centerIn: parent
455-
456- cameraResolution: camera.advanced.resolution
457- viewFinderHeight: viewFinder.height
458- viewFinderWidth: viewFinder.width
459- viewFinderOrientation: viewFinder.orientation
460- }
461+ }
462+
463+ ViewFinderGeometry {
464+ id: viewFinderGeometry
465+ anchors.centerIn: parent
466+
467+ cameraResolution: camera.viewfinder.resolution
468+ viewFinderHeight: viewFinder.height
469+ viewFinderWidth: viewFinder.width
470+ viewFinderOrientation: viewFinder.orientation
471 }
472
473 Item {

Subscribers

People subscribed via source and target branches