Revision control

++ // 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"