×
Namespaces

Variants
Actions
(Difference between revisions)

QML horizon component for camera apps

From Nokia Developer Wiki
Jump to: navigation, search
lildeimos (Talk | contribs)
(Lildeimos - start)
 
lildeimos (Talk | contribs)
(Lildeimos -)
Line 24: Line 24:
 
|author= [[User:lildeimos]]
 
|author= [[User:lildeimos]]
 
}}
 
}}
 +
 +
{{Note|This is an entry in the [[PureView Imaging Competition 2012Q2]]}}
  
 
== Introduction ==
 
== Introduction ==
  
 
This QML component draws a overlay horizon line on top of the parent Item.
 
This QML component draws a overlay horizon line on top of the parent Item.
[[File:portrait.jpg|x200px|horizon in portrait]]
+
 
[[File:landscape.jpg|x300px|horizon in landscape]]
+
<gallery>
 +
File:portrait.jpg|horizon in portrait
 +
File:landscape.jpg|horizon in landscape
 +
</gallery>
  
 
== Overview ==
 
== Overview ==
  
While developing this component, I chosen to use QAccelerometer sensor intead QRotationSensor. The reson is that some devices doesn't support Z -axis and for example my Nokia C7 has a step of 15° deg of values. Should not be a big deal to replace the current accelerometer with the rotation sensor.
+
While developing this component, I chosen to use [http://doc.qt.nokia.com/qtmobility/qaccelerometer.html QAccelerometer] sensor instead [http://doc.qt.nokia.com/qtmobility/qrotationsensor.html QRotationSensor]. The reson is that some devices doesn't support Z-axis and for example my Nokia C7 has a snap of 15° deg of values. Should not be a big deal to replace the current accelerometer with the rotation sensor.
Another problem I have encountered, is that the sensors are not calibrated all the same. To resolve this problem, I added 6 calibration properties (min and max values for each axis). The calibration properties in the example, are taken asking to the user to put the device in 3 different orientation. The drawback is that values are lost the next time the application run. A solution could be store those with QSettings as done in the OMCcam project.
+
Another problem I have encountered, is that the sensors are not calibrated all the same. To resolve this problem, I added 6 calibration properties (min and max values for each axis). The calibration properties in the example, are taken asking to the user to put the device in 3 different orientations. The drawback is that values are lost the next time the application run. A solution could be store those with [http://qt-project.org/doc/qt-4.8/qsettings.html QSettings] as done in the OMCcam project.
  
 
== Usage ==
 
== Usage ==
  
Start copying horizon.cpp and horizon.h into your project source directory. In the main.cpp file include the .h:
+
Start copying {{Icode|horizon.cpp}} and {{Icode|horizon.h}} into your project source directory. In the {{Icode|main.cpp}} file include the .h:
 
<code>
 
<code>
 
#include "horizon.h"
 
#include "horizon.h"
Line 48: Line 53:
 
</code>
 
</code>
  
In you qml file you can now declare Horizon component:
+
In you qml file you can now declare {{Icode|Horizon}} component:
 
<code java>
 
<code java>
 
Rectangle {
 
Rectangle {
Line 62: Line 67:
 
}
 
}
 
</code>
 
</code>
active property will activate the accelerometer sensor to display the line on top of mainRect with a black color and a penWidth of 3 pixels.
+
{{Icode|active}} property will activate the accelerometer sensor to display a line on top of {{Icode|mainRect}} with a black {{Icode|color}} and a {{Icode|penWidth}} of 3 pixels.
 +
{{Icode|Horizon}} component has also {{Icode|calibrate[Min|Max][X|Y|Z]} (six properties) that are needed to calculate the position of the line. By default, min values are set to zero, max values are set to 9.8. Every devices need a sensor calibration, to do this, the user help is needed. The following code ask to the user to put the device in vertical portrait mode, then in landscape and finally horizontal with display facing upward:
 +
<code java>
 +
Dialog {
 +
    id: calibratePortrait
 +
    title: Text {
 +
        anchors.horizontalCenter: parent.horizontalCenter
 +
        font.pixelSize: 28
 +
        color: "white"
 +
        text: "Rotation sensor calibration"
 +
    }
 +
 
 +
    content: Item {
 +
        height: 50
 +
        width: parent.width
 +
        Text {
 +
            font.pixelSize: 22
 +
            anchors.centerIn: parent
 +
            color: "white"
 +
            text: "Put your phone in portrait and press Ok"
 +
        }
 +
    }
 +
 
 +
    buttons: ButtonRow {
 +
        anchors.horizontalCenter: parent.horizontalCenter
 +
        spacing: 30
 +
        Button {
 +
            width: 100
 +
            text: "OK"
 +
            onClicked: {
 +
                horizonMenu.open=false;
 +
                horizon.calibrateMinX=horizon.averageRotX;
 +
                horizon.calibrateMaxY=horizon.averageRotY;
 +
                horizon.calibrateMinZ=horizon.averageRotZ;
 +
                calibratePortrait.accept();
 +
                calibrateLandscape.open();
 +
            }
 +
        }
 +
        Button {width: 100; text: "Cancel"; onClicked: calibratePortrait.reject() }
 +
    }
 +
} // calibratePortrait
 +
 
 +
 
 +
Dialog {
 +
    id: calibrateLandscape
 +
    visualParent: mainPage
 +
    title: Text {
 +
        anchors.horizontalCenter: parent.horizontalCenter
 +
        font.pixelSize: 28
 +
        color: "white"
 +
        text: "Rotation sensor calibration"
 +
    }
 +
 
 +
    content: Item {
 +
        height: 50
 +
        width: parent.width
 +
        Text {
 +
            font.pixelSize: 22
 +
            anchors.centerIn: parent
 +
            color: "white"
 +
            text: "Put your phone in landscape and press Ok"
 +
        }
 +
    }
 +
 
 +
    buttons: ButtonRow {
 +
        anchors.horizontalCenter: parent.horizontalCenter
 +
        spacing: 30
 +
        Button {
 +
            width: 100
 +
            text: "OK"
 +
            onClicked: {
 +
                horizon.calibrateMaxX=horizon.averageRotX;
 +
                horizon.calibrateMinY=horizon.averageRotY;
 +
                calibrateLandscape.accept();
 +
                calibratePlane.open();
 +
            }
 +
        }
 +
        Button {width: 100; text: "Cancel"; onClicked: calibrateLandscape.reject()}
 +
    }
 +
} // calibrateLandscape
 +
 
 +
Dialog {
 +
    id: calibratePlane
 +
    visualParent: mainPage
 +
    title: Text {
 +
        anchors.horizontalCenter: parent.horizontalCenter
 +
        font.pixelSize: 28
 +
        color: "white"
 +
        text: "Rotation sensor calibration"
 +
    }
 +
 
 +
    content: Item {
 +
        height: 50
 +
        width: parent.width
 +
        Text {
 +
            font.pixelSize: 22
 +
            anchors.centerIn: parent
 +
            color: "white"
 +
            wrapMode: Text.WrapAnywhere
 +
            text: "Put your phone on a horizontal plane (face up) and press Ok"
 +
        }
 +
    }
 +
 
 +
    buttons: ButtonRow {
 +
        spacing: 30
 +
        anchors.horizontalCenter: parent.horizontalCenter
 +
        Button {
 +
            width: 100
 +
            text: "OK"
 +
            onClicked: {
 +
                horizon.calibrateMaxZ=horizon.averageRotZ;
 +
                calibratePlane.accept();
 +
            }
 +
        }
 +
        Button {width: 100;text: "Cancel"; onClicked: calibratePlane.reject()}
 +
    }
 +
} // calibrateLandscape
 +
</code>
 +
You can bind the open event of the first dialog ( {{Icode|calibratePortrait}} ) to a button, when accepted the second dialog will rise ans so for the third.
 +
 
 +
As you can see, here are appeared another 3 properties: {{Icode|averageRotX averageRotY averageRotZ}}. To avoid flickering of the horizon line, the component makes an average calculation of a defined number of samples of the accelerometer sensor. In these properties is stored the actual average of the last n sensor readings.
 +
 
 +
== Implementation ==
 +
 
 +
I will explain here how I thought to implement {{Icode|Horizon}}. I remind you to download section for the source code.
 +
In the header file is defined
 +
<code>
 +
#define ROT_SAMPLES 5
 +
</code>
 +
this defines the number of rotation readings to be averaged. As mentioned before, this is useful to avoid shaking of the horizon line since the readings values are very wobble.
 +
I opted to use a circular list to store the last {{Icode|ROT_SAMPLES}}. This list is composed by this struct and declarations:
 +
<code>
 +
typedef struct listRot_ {
 +
    qreal value;
 +
    struct listRot_ *curr,*prev,*next;
 +
} listRot;
 +
 
 +
listRot *rotX_samples[ROT_SAMPLES];
 +
listRot *rotY_samples[ROT_SAMPLES];
 +
listRot *rotZ_samples[ROT_SAMPLES];
 +
</code>

Revision as of 23:54, 7 May 2012

This article explains how to use QML horizon line component

Article Metadata
Tested with
SDK: Nokia Qt SDK 1.2.0
Devices(s): Nokia C7-00, Nokia N950
Compatibility
Platform(s): Symbian^3 and later, Harmattan
Symbian
Device(s): All* (must have internal Accelerometer senso)
Article
Keywords: Horizon, QDeclarativeItem, QAccelerometer
Created: lildeimos (08 May 2012)
Last edited: lildeimos (07 May 2012)

Note.pngNote: This is an entry in the PureView Imaging Competition 2012Q2

Contents

Introduction

This QML component draws a overlay horizon line on top of the parent Item.

Overview

While developing this component, I chosen to use QAccelerometer sensor instead QRotationSensor. The reson is that some devices doesn't support Z-axis and for example my Nokia C7 has a snap of 15° deg of values. Should not be a big deal to replace the current accelerometer with the rotation sensor. Another problem I have encountered, is that the sensors are not calibrated all the same. To resolve this problem, I added 6 calibration properties (min and max values for each axis). The calibration properties in the example, are taken asking to the user to put the device in 3 different orientations. The drawback is that values are lost the next time the application run. A solution could be store those with QSettings as done in the OMCcam project.

Usage

Start copying horizon.cpp and horizon.h into your project source directory. In the main.cpp file include the .h:

#include "horizon.h"

then register the component:

qmlRegisterType<Horizon>("Horizon", 1, 0, "Horizon");

In you qml file you can now declare Horizon component:

Rectangle {
id: mainRect
Horizon {
id: horizon
anchors.fill: parent
clip: true
active: true
penWidth: 3
color: "black"
} // Horizon
}

active property will activate the accelerometer sensor to display a line on top of mainRect with a black color and a penWidth of 3 pixels. Horizon component has also {{Icode|calibrate[Min|Max][X|Y|Z]} (six properties) that are needed to calculate the position of the line. By default, min values are set to zero, max values are set to 9.8. Every devices need a sensor calibration, to do this, the user help is needed. The following code ask to the user to put the device in vertical portrait mode, then in landscape and finally horizontal with display facing upward:

Dialog {
id: calibratePortrait
title: Text {
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 28
color: "white"
text: "Rotation sensor calibration"
}
 
content: Item {
height: 50
width: parent.width
Text {
font.pixelSize: 22
anchors.centerIn: parent
color: "white"
text: "Put your phone in portrait and press Ok"
}
}
 
buttons: ButtonRow {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 30
Button {
width: 100
text: "OK"
onClicked: {
horizonMenu.open=false;
horizon.calibrateMinX=horizon.averageRotX;
horizon.calibrateMaxY=horizon.averageRotY;
horizon.calibrateMinZ=horizon.averageRotZ;
calibratePortrait.accept();
calibrateLandscape.open();
}
}
Button {width: 100; text: "Cancel"; onClicked: calibratePortrait.reject() }
}
} // calibratePortrait
 
 
Dialog {
id: calibrateLandscape
visualParent: mainPage
title: Text {
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 28
color: "white"
text: "Rotation sensor calibration"
}
 
content: Item {
height: 50
width: parent.width
Text {
font.pixelSize: 22
anchors.centerIn: parent
color: "white"
text: "Put your phone in landscape and press Ok"
}
}
 
buttons: ButtonRow {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 30
Button {
width: 100
text: "OK"
onClicked: {
horizon.calibrateMaxX=horizon.averageRotX;
horizon.calibrateMinY=horizon.averageRotY;
calibrateLandscape.accept();
calibratePlane.open();
}
}
Button {width: 100; text: "Cancel"; onClicked: calibrateLandscape.reject()}
}
} // calibrateLandscape
 
Dialog {
id: calibratePlane
visualParent: mainPage
title: Text {
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 28
color: "white"
text: "Rotation sensor calibration"
}
 
content: Item {
height: 50
width: parent.width
Text {
font.pixelSize: 22
anchors.centerIn: parent
color: "white"
wrapMode: Text.WrapAnywhere
text: "Put your phone on a horizontal plane (face up) and press Ok"
}
}
 
buttons: ButtonRow {
spacing: 30
anchors.horizontalCenter: parent.horizontalCenter
Button {
width: 100
text: "OK"
onClicked: {
horizon.calibrateMaxZ=horizon.averageRotZ;
calibratePlane.accept();
}
}
Button {width: 100;text: "Cancel"; onClicked: calibratePlane.reject()}
}
} // calibrateLandscape

You can bind the open event of the first dialog ( calibratePortrait ) to a button, when accepted the second dialog will rise ans so for the third.

As you can see, here are appeared another 3 properties: averageRotX averageRotY averageRotZ. To avoid flickering of the horizon line, the component makes an average calculation of a defined number of samples of the accelerometer sensor. In these properties is stored the actual average of the last n sensor readings.

Implementation

I will explain here how I thought to implement Horizon. I remind you to download section for the source code. In the header file is defined

#define ROT_SAMPLES 5

this defines the number of rotation readings to be averaged. As mentioned before, this is useful to avoid shaking of the horizon line since the readings values are very wobble. I opted to use a circular list to store the last ROT_SAMPLES. This list is composed by this struct and declarations:

typedef struct listRot_ {
qreal value;
struct listRot_ *curr,*prev,*next;
} listRot;
 
listRot *rotX_samples[ROT_SAMPLES];
listRot *rotY_samples[ROT_SAMPLES];
listRot *rotZ_samples[ROT_SAMPLES];
137 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×