Revision control

Copy as Markdown

// Copyright (C) 2016 Research In Motion
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qqnximagecapture_p.h"
#include "qqnxplatformcamera_p.h"
#include "qqnxmediacapturesession_p.h"
#include "qqnxcamera_p.h"
#include "qfile.h"
#include <private/qmediastoragelocation_p.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qfuture.h>
#include <QtCore/qpromise.h>
#include <QtCore/qthread.h>
#include <camera/camera_api.h>
using namespace Qt::Literals::StringLiterals;
static QString formatExtension(QImageCapture::FileFormat format)
{
switch (format) {
case QImageCapture::JPEG:
return u"jpg"_s;
case QImageCapture::PNG:
return u"png"_s;
case QImageCapture::WebP:
case QImageCapture::Tiff:
case QImageCapture::UnspecifiedFormat:
break;
}
return {};
}
static QString resolveFileName(const QString &fileName, QImageCapture::FileFormat format)
{
const QString extension = formatExtension(format);
if (extension.isEmpty())
return {};
if (fileName.isEmpty()) {
return QMediaStorageLocation::generateFileName(QString(),
QStandardPaths::PicturesLocation, extension);
}
if (fileName.endsWith(extension))
return QFileInfo(fileName).canonicalFilePath();
QString path = fileName;
path.append(u".%1"_s.arg(extension));
return QFileInfo(path).canonicalFilePath();
}
QT_BEGIN_NAMESPACE
QQnxImageCapture::QQnxImageCapture(QImageCapture *parent)
: QPlatformImageCapture(parent)
{
}
bool QQnxImageCapture::isReadyForCapture() const
{
return m_camera && m_camera->isActive();
}
int QQnxImageCapture::capture(const QString &fileName)
{
if (!isReadyForCapture()) {
Q_EMIT error(-1, QImageCapture::NotReadyError, QPlatformImageCapture::msgCameraNotReady());
return -1;
}
// default to PNG format if no format has been specified
const QImageCapture::FileFormat format =
m_settings.format() == QImageCapture::UnspecifiedFormat
? QImageCapture::PNG : m_settings.format();
const QString resolvedFileName = resolveFileName(fileName, format);
if (resolvedFileName.isEmpty()) {
const QString errorMessage = (u"Invalid file format: %1"_s).arg(
QImageCapture::fileFormatName(format));
Q_EMIT error(-1, QImageCapture::NotSupportedFeatureError, errorMessage);
return -1;
}
const int id = m_lastId++;
auto callback = [this, id, fn=std::move(resolvedFileName)](const QVideoFrame &frame) {
saveFrame(id, frame, fn);
};
m_camera->requestVideoFrame(std::move(callback));
return id;
}
int QQnxImageCapture::captureToBuffer()
{
if (!isReadyForCapture()) {
Q_EMIT error(-1, QImageCapture::NotReadyError, QPlatformImageCapture::msgCameraNotReady());
return -1;
}
const int id = m_lastId++;
auto callback = [this, id](const QVideoFrame &frame) { decodeFrame(id, frame); };
m_camera->requestVideoFrame(std::move(callback));
return id;
}
QFuture<QImage> QQnxImageCapture::decodeFrame(int id, const QVideoFrame &frame)
{
if (!frame.isValid()) {
Q_EMIT error(id, QImageCapture::NotReadyError, u"Invalid frame"_s);
return {};
}
QPromise<QImage> promise;
QFuture<QImage> future = promise.future();
// converting a QVideoFrame to QImage is an expensive operation
// run it on a background thread to prevent it from stalling the UI
auto runner = [frame, promise=std::move(promise)]() mutable {
promise.start();
promise.addResult(frame.toImage());
promise.finish();
};
auto *worker = QThread::create(std::move(runner));
auto onFinished = [this, worker, id, future]() mutable {
worker->deleteLater();
if (future.isValid()) {
Q_EMIT imageCaptured(id, future.result());
} else {
qWarning("QQnxImageCapture: failed to capture image to buffer");
}
};
connect(worker, &QThread::finished, this, std::move(onFinished));
Q_EMIT imageExposed(id);
worker->start();
return future;
}
void QQnxImageCapture::saveFrame(int id, const QVideoFrame &frame, const QString &fileName)
{
QFuture<QImage> decodeFuture = decodeFrame(id, frame);
if (decodeFuture.isCanceled())
return;
QPromise<bool> promise;
QFuture<bool> saveFuture = promise.future();
// writing a QImage to disk is a _very_ expensive operation
// run it on a background thread to prevent it from stalling the UI
auto runner = [future=std::move(decodeFuture),
promise=std::move(promise), fileName]() mutable {
promise.start();
promise.addResult(future.result().save(fileName));
promise.finish();
};
auto *worker = QThread::create(std::move(runner));
auto onFinished = [this, worker, id, future=std::move(saveFuture), fn=std::move(fileName)]() {
worker->deleteLater();
if (future.isValid() && future.result())
Q_EMIT imageSaved(id, fn);
else
Q_EMIT error(id, QImageCapture::NotSupportedFeatureError, u"Failed to save image"_s);
};
connect(worker, &QThread::finished, this, std::move(onFinished));
worker->start();
}
QImageEncoderSettings QQnxImageCapture::imageSettings() const
{
return m_settings;
}
void QQnxImageCapture::setImageSettings(const QImageEncoderSettings &settings)
{
m_settings = settings;
}
void QQnxImageCapture::setCaptureSession(QQnxMediaCaptureSession *captureSession)
{
if (m_session == captureSession)
return;
if (m_session)
m_session->disconnect(this);
m_session = captureSession;
if (m_session) {
connect(m_session, &QQnxMediaCaptureSession::cameraChanged,
this, &QQnxImageCapture::onCameraChanged);
}
onCameraChanged();
}
void QQnxImageCapture::onCameraChanged()
{
if (m_camera)
m_camera->disconnect(this);
m_camera = m_session ? static_cast<QQnxPlatformCamera*>(m_session->camera()) : nullptr;
if (m_camera) {
connect(m_camera, &QQnxPlatformCamera::activeChanged,
this, &QQnxImageCapture::onCameraChanged);
}
updateReadyForCapture();
}
void QQnxImageCapture::onCameraActiveChanged(bool active)
{
Q_UNUSED(active);
updateReadyForCapture();
}
void QQnxImageCapture::updateReadyForCapture()
{
const bool readyForCapture = isReadyForCapture();
if (m_lastReadyForCapture == readyForCapture)
return;
m_lastReadyForCapture = readyForCapture;
Q_EMIT readyForCaptureChanged(m_lastReadyForCapture);
}
QT_END_NAMESPACE
#include "moc_qqnximagecapture_p.cpp"