Skip to content

Commit

Permalink
Fix iPad photo orientation
Browse files Browse the repository at this point in the history
On iPad the photos from the QML ImageCapture imageSaved signal do not have the
correct orientation. We could try to work out the orientation from the image
metadata or try to grab the device orientation at the point the photo is taken.
The preview image appears to have the correct orientation and same resolution
as the saved image. So I've changed the CameraQml class to use this instead.
  • Loading branch information
martinburchell committed Aug 24, 2023
1 parent 364f85a commit 0e7082f
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 114 deletions.
39 changes: 0 additions & 39 deletions tablet_qt/questionnairelib/quphoto.cpp
Expand Up @@ -246,8 +246,6 @@ void QuPhoto::takePhoto()
qDebug() << "... CameraQml() created";
#endif
connect(m_camera, &CameraQml::cancelled, this, &QuPhoto::cameraCancelled);
connect(m_camera, &CameraQml::rawImageCaptured,
this, &QuPhoto::rawImageCaptured);
connect(m_camera, &CameraQml::imageCaptured,
this, &QuPhoto::imageCaptured);

Expand Down Expand Up @@ -333,43 +331,6 @@ void QuPhoto::imageCaptured(const QImage& image)
}


void QuPhoto::rawImageCaptured(const QByteArray& data,
const QString& extension_without_dot,
const QString& mimetype)
{
#ifdef DEBUG_CAMERA
qDebug() << Q_FUNC_INFO;
#endif
if (!m_camera) {
qWarning() << Q_FUNC_INFO << "... no camera!";
return;
}
if (!m_questionnaire) {
qWarning() << Q_FUNC_INFO << "... no questionnaire!";
return;
}
bool changed = false;
{ // guard block
SlowGuiGuard guard = m_questionnaire->app().getSlowGuiGuard(
tr("Saving image..."),
tr("Saving"));
#ifdef DEBUG_CAMERA
qDebug() << "QuPhoto: setting field value to raw image...";
#endif
changed = m_fieldref->setRawImage(data,
extension_without_dot,
mimetype);
#ifdef DEBUG_CAMERA
qDebug() << "QuPhoto: ... field value set to raw image.";
#endif
m_camera->finish(); // close the camera
}
if (changed) {
emit elementValueChanged();
}
}


void QuPhoto::rotate(const int angle_degrees_clockwise)
{
if (m_fieldref->isNull()) {
Expand Down
5 changes: 0 additions & 5 deletions tablet_qt/questionnairelib/quphoto.h
Expand Up @@ -72,11 +72,6 @@ protected slots:
// "User cancelled taking a photo."
void cameraCancelled();

// "Camera sends you this captured raw image."
void rawImageCaptured(const QByteArray& data,
const QString& extension_without_dot,
const QString& mimetype);

// "Camera sends you this captured QImage."
void imageCaptured(const QImage& image);

Expand Down
4 changes: 2 additions & 2 deletions tablet_qt/resources/camcops/camera_qml/PhotoPreview.qml
Expand Up @@ -57,7 +57,7 @@ import QtMultimedia

Item {
signal closed
signal imageSavedToFile // RNC
signal previewSaved // RNC

Image { // http://doc.qt.io/qt-6.2/qml-qtquick-image.html
id: preview
Expand Down Expand Up @@ -85,7 +85,7 @@ Item {
text: qsTr("Save")
onClicked: {
console.log("Save button clicked")
imageSavedToFile()
previewSaved()
}
}
}
11 changes: 6 additions & 5 deletions tablet_qt/resources/camcops/camera_qml/camera.qml
Expand Up @@ -58,7 +58,8 @@ import QtMultimedia
Rectangle {
id : cameraUI

signal imageSavedToFile(string filename) // RNC
signal imageCaptured(variant previewImage)
signal previewSaved
signal fileNoLongerNeeded(string filename) // RNC

width: 800
Expand Down Expand Up @@ -139,8 +140,8 @@ Rectangle {
console.log("Image captured")
stillControls.previewAvailable = true
cameraUI.state = "PhotoPreview"
cameraUI.imageCaptured(previewImage)
}
// RNC:
onImageSaved: function(requestId, path) {
console.log("onImageSaved: ", path)
stillControls.fileSaved = true
Expand All @@ -160,9 +161,9 @@ Rectangle {
onClosed: cameraUI.state = "PhotoCapture"
visible: (cameraUI.state === "PhotoPreview")
focus: visible
onImageSavedToFile: {
console.log("Returning image with filename:", stillControls.filePath)
cameraUI.imageSavedToFile(stillControls.filePath)
onPreviewSaved: {
cameraUI.previewSaved()
cameraUI.fileNoLongerNeeded(stillControls.filePath)
}
}

Expand Down
64 changes: 15 additions & 49 deletions tablet_qt/widgets/cameraqml.cpp
Expand Up @@ -166,8 +166,9 @@ void CameraQml::qmlFinishedLoading()
Q_ASSERT(root);
// It's possible to connect to non-root objects, but it's much cleaner to
// route from QML child objects up to the QML root object, and then to C++.
connect(root, SIGNAL(imageSavedToFile(const QString&)),
this, SLOT(cameraHasCapturedImage(const QString&)));
connect(root, SIGNAL(imageCaptured(const QVariant&)),
this, SLOT(copyPreviewImage(const QVariant&)));
connect(root, SIGNAL(previewSaved()), this, SLOT(savePreviewImage()));
connect(root, SIGNAL(fileNoLongerNeeded(const QString&)),
this, SLOT(deleteSuperfluousFile(const QString&)));
// ... we have to use SIGNAL() and SLOT() since C++ has no idea of the
Expand All @@ -178,6 +179,18 @@ void CameraQml::qmlFinishedLoading()
}


void CameraQml::copyPreviewImage(const QVariant& preview)
{
m_preview = preview.value<QImage>();
}


void CameraQml::savePreviewImage()
{
emit imageCaptured(m_preview);
}


void CameraQml::deleteFile(const QString& filename) const
{
#ifdef DEBUG_CAMERA
Expand All @@ -198,50 +211,3 @@ void CameraQml::deleteSuperfluousFile(const QString& filename) const
#endif
deleteFile(filename);
}


void CameraQml::cameraHasCapturedImage(const QString& filename)
{
#ifdef DEBUG_CAMERA
qDebug() << Q_FUNC_INFO;
qDebug() << "Camera image has arrived via temporary file" << filename;
#endif

const QFileInfo fileinfo(filename);
const QString extension_without_dot = fileinfo.suffix();
const QMimeDatabase mime_db;
const QMimeType mime_type = mime_db.mimeTypeForFile(filename);
// ... default method is to use filename and contents
// ... it will ALWAYS BE VALID, but may be "application/octet-stream" if
// Qt doesn't know what it is:
// http://doc.qt.io/qt-5/qmimedatabase.html#mimeTypeForFile
const QString mimetype_name = mime_type.name();

QFile file(filename);
if (mimetype_name != "application/octet-stream" &&
file.open(QIODevice::ReadOnly)) {

// We know the MIME type (and can read the file), so we can use the
// higher-performance method.
const QByteArray data = file.readAll();
file.close();
deleteFile(filename);
#ifdef DEBUG_CAMERA
qDebug() << "Camera image data loaded";
#endif
emit rawImageCaptured(data, extension_without_dot, mimetype_name);

} else {

#ifdef DEBUG_CAMERA
qDebug() << "Camera image loaded";
#endif
QImage img;
img.load(filename);
deleteFile(filename);
emit imageCaptured(img);

}

close();
}
18 changes: 4 additions & 14 deletions tablet_qt/widgets/cameraqml.h
Expand Up @@ -86,16 +86,6 @@ class CameraQml : public OpenableWidget
void finish();

signals:
// If possible, we will emit rawImageCaptured, because performance is
// better. Failing that, we will emit imageCaptured. ONE OR THE OTHER will
// be emitted.

// "We've captured an image." High performance.
void rawImageCaptured(QByteArray data, // QByteArray is copy-on-write
QString extension_without_dot,
QString mimetype);

// "We've captured an image." Lower performance.
void imageCaptured(QImage image); // QImage is copy-on-write

// "User has cancelled the operation."
Expand Down Expand Up @@ -124,14 +114,14 @@ protected slots:
// Called from m_qml_view's QQuickWidget::statusChanged.
void qmlStatusChanged(QQuickWidget::Status status);

void copyPreviewImage(const QVariant& preview);
void savePreviewImage();
// "The camera QML says a temporary file is no longer needed."
// Called from the fileNoLongerNeeded signal defined in camera.qml.
void deleteSuperfluousFile(const QString& filename) const;

// "The camera QML has captured an image via a temporary file."
// Called from the imageSavedToFile signal defined in camera.qml.
void cameraHasCapturedImage(const QString& filename);

protected:
QPointer<QQuickWidget> m_qml_view; // our QML view widget
private:
QImage m_preview;
};

0 comments on commit 0e7082f

Please sign in to comment.