blob: 4ed208febf2d0dae7975dcb16c0daff0de82a679 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qwinrtcameraimagecapturecontrol.h"
#include "qwinrtcameracontrol.h"
#include "qwinrtimageencodercontrol.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QGlobalStatic>
#include <QtCore/QPointer>
#include <QtCore/QStandardPaths>
#include <QtCore/QVector>
#include <QtCore/qfunctions_winrt.h>
#include <QtCore/private/qeventdispatcher_winrt_p.h>
#include <QtMultimedia/private/qmediastoragelocation_p.h>
#include <functional>
#include <wrl.h>
#include <windows.media.capture.h>
#include <windows.media.devices.h>
#include <windows.media.mediaproperties.h>
#include <windows.storage.streams.h>
#include <windows.graphics.imaging.h>
#include <robuffer.h>
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Media::Capture;
using namespace ABI::Windows::Media::Devices;
using namespace ABI::Windows::Media::MediaProperties;
using namespace ABI::Windows::Storage::Streams;
using namespace ABI::Windows::Graphics::Imaging;
QT_BEGIN_NAMESPACE
#define wchar(str) reinterpret_cast<const wchar_t *>(str.utf16())
struct QWinRTCameraImageCaptureControlGlobal
{
QWinRTCameraImageCaptureControlGlobal()
{
HRESULT hr;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Media_MediaProperties_ImageEncodingProperties).Get(),
&encodingPropertiesFactory);
Q_ASSERT_SUCCEEDED(hr);
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(),
&bufferFactory);
Q_ASSERT_SUCCEEDED(hr);
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataReader).Get(),
&dataReaderFactory);
}
ComPtr<IImageEncodingPropertiesStatics2> encodingPropertiesFactory;
ComPtr<IBufferFactory> bufferFactory;
ComPtr<IDataReaderFactory> dataReaderFactory;
};
Q_GLOBAL_STATIC(QWinRTCameraImageCaptureControlGlobal, g)
struct CaptureRequest
{
quint16 id;
QString fileName;
ComPtr<IImageEncodingProperties> imageFormat;
ComPtr<IRandomAccessStream> stream;
ComPtr<IAsyncAction> op;
};
// Do not use CoTaskMemFree directly for image cleanup as it leads to crashes in release
static void freeImageData(void *data)
{
CoTaskMemFree(data);
}
class QWinRTCameraImageCaptureControlPrivate
{
public:
QWinRTCameraImageCaptureControlPrivate()
: isActive(false)
{
}
QPointer<QWinRTCameraControl> cameraControl;
QHash<IAsyncAction *, CaptureRequest> requests;
quint16 currentCaptureId;
QMediaStorageLocation location;
bool isActive;
};
QWinRTCameraImageCaptureControl::QWinRTCameraImageCaptureControl(QWinRTCameraControl *parent)
: QCameraImageCaptureControl(parent), d_ptr(new QWinRTCameraImageCaptureControlPrivate)
{
qCDebug(lcMMCamera) << __FUNCTION__ << parent;
Q_D(QWinRTCameraImageCaptureControl);
d->cameraControl = parent;
connect(d->cameraControl, &QCameraControl::stateChanged,
this, &QWinRTCameraImageCaptureControl::onCameraStateChanged);
d->currentCaptureId = 0;
}
bool QWinRTCameraImageCaptureControl::isReadyForCapture() const
{
Q_D(const QWinRTCameraImageCaptureControl);
return d->isActive;
}
QCameraImageCapture::DriveMode QWinRTCameraImageCaptureControl::driveMode() const
{
return QCameraImageCapture::SingleImageCapture;
}
void QWinRTCameraImageCaptureControl::setDriveMode(QCameraImageCapture::DriveMode mode)
{
Q_UNUSED(mode);
}
int QWinRTCameraImageCaptureControl::capture(const QString &fileName)
{
qCDebug(lcMMCamera) << __FUNCTION__ << fileName;
Q_D(QWinRTCameraImageCaptureControl);
++d->currentCaptureId;
ComPtr<IMediaCapture> capture = d->cameraControl->handle();
if (!capture) {
emit error(d->currentCaptureId, QCameraImageCapture::NotReadyError, tr("Camera not ready"));
return -1;
}
CaptureRequest request = {
d->currentCaptureId,
d->location.generateFileName(fileName, QMediaStorageLocation::Pictures, QStringLiteral("IMG_"),
fileName.isEmpty() ? QStringLiteral("jpg") : QFileInfo(fileName).suffix()),
nullptr, nullptr, nullptr
};
HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([this, d, capture, &request]() {
HRESULT hr;
hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream).Get(),
&request.stream);
Q_ASSERT_SUCCEEDED(hr);
hr = g->encodingPropertiesFactory->CreateBmp(&request.imageFormat);
Q_ASSERT_SUCCEEDED(hr);
const QSize imageSize = static_cast<QWinRTImageEncoderControl*>(d->cameraControl->imageEncoderControl())->imageSettings().resolution();
hr = request.imageFormat->put_Width(UINT32(imageSize.width()));
Q_ASSERT_SUCCEEDED(hr);
hr = request.imageFormat->put_Height(UINT32(imageSize.height()));
Q_ASSERT_SUCCEEDED(hr);
hr = capture->CapturePhotoToStreamAsync(request.imageFormat.Get(), request.stream.Get(), &request.op);
Q_ASSERT_SUCCEEDED(hr);
if (!request.op) {
qErrnoWarning("Camera photo capture failed.");
return E_FAIL;
}
emit captureQueueChanged(false);
d->requests.insert(request.op.Get(), request);
hr = request.op->put_Completed(Callback<IAsyncActionCompletedHandler>(
this, &QWinRTCameraImageCaptureControl::onCaptureCompleted).Get());
Q_ASSERT_SUCCEEDED(hr);
return hr;
});
if (FAILED(hr))
return -1;
return request.id;
}
void QWinRTCameraImageCaptureControl::cancelCapture()
{
qCDebug(lcMMCamera) << __FUNCTION__;
Q_D(QWinRTCameraImageCaptureControl);
QHash<IAsyncAction *, CaptureRequest>::iterator it = d->requests.begin();
while (it != d->requests.end()) {
ComPtr<IAsyncInfo> info;
it->op.As(&info);
info->Cancel();
it = d->requests.erase(it);
}
emit captureQueueChanged(true);
}
void QWinRTCameraImageCaptureControl::onCameraStateChanged(QCamera::State state)
{
Q_D(QWinRTCameraImageCaptureControl);
const bool newActive = state == QCamera::ActiveState;
if (d->isActive == newActive)
return;
d->isActive = newActive;
emit readyForCaptureChanged(newActive);
}
HRESULT QWinRTCameraImageCaptureControl::onCaptureCompleted(IAsyncAction *asyncInfo, AsyncStatus status)
{
qCDebug(lcMMCamera) << __FUNCTION__;
Q_D(QWinRTCameraImageCaptureControl);
if (status == Canceled || !d->requests.contains(asyncInfo))
return S_OK;
CaptureRequest request = d->requests.take(asyncInfo);
emit captureQueueChanged(d->requests.isEmpty());
HRESULT hr;
if (status == Error) {
hr = asyncInfo->GetResults();
emit error(request.id, QCameraImageCapture::ResourceError, qt_error_string(hr));
return S_OK;
}
quint64 dataLength;
hr = request.stream->get_Size(&dataLength);
Q_ASSERT_SUCCEEDED(hr);
if (dataLength == 0 || dataLength > INT_MAX) {
emit error(request.id, QCameraImageCapture::FormatError, tr("Invalid photo data length."));
return S_OK;
}
ComPtr<IBitmapDecoderStatics> bitmapFactory;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Graphics_Imaging_BitmapDecoder).Get(),
&bitmapFactory);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IAsyncOperation<BitmapDecoder *>> op;
hr = bitmapFactory->CreateAsync(request.stream.Get(), &op);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IBitmapDecoder> decoder;
hr = QWinRTFunctions::await(op, decoder.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IAsyncOperation<BitmapFrame *>> op2;
hr = decoder->GetFrameAsync(0, &op2);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IBitmapFrame> frame;
hr = QWinRTFunctions::await(op2, frame.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IBitmapTransform> transform;
hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Graphics_Imaging_BitmapTransform).Get(),
&transform);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IAsyncOperation<PixelDataProvider *>> op3;
hr = frame->GetPixelDataTransformedAsync(BitmapPixelFormat_Rgba8, BitmapAlphaMode_Straight,
transform.Get(), ExifOrientationMode_IgnoreExifOrientation,
ColorManagementMode_DoNotColorManage, &op3);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IPixelDataProvider> pixelDataProvider;
hr = QWinRTFunctions::await(op3, pixelDataProvider.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
UINT32 pixelDataSize;
BYTE *pixelData;
hr = pixelDataProvider->DetachPixelData(&pixelDataSize, &pixelData);
UINT32 pixelHeight;
hr = frame->get_PixelHeight(&pixelHeight);
Q_ASSERT_SUCCEEDED(hr);
UINT32 pixelWidth;
hr = frame->get_PixelWidth(&pixelWidth);
Q_ASSERT_SUCCEEDED(hr);
const QImage image(pixelData, int(pixelWidth), int(pixelHeight),
QImage::Format_RGBA8888,
reinterpret_cast<QImageCleanupFunction>(&freeImageData), pixelData);
emit imageCaptured(request.id, image);
QWinRTImageEncoderControl *imageEncoderControl = static_cast<QWinRTImageEncoderControl*>(d->cameraControl->imageEncoderControl());
int imageQuality = 100;
switch (imageEncoderControl->imageSettings().quality()) {
case QMultimedia::VeryLowQuality:
imageQuality = 20;
break;
case QMultimedia::LowQuality:
imageQuality = 40;
break;
case QMultimedia::NormalQuality:
imageQuality = 60;
break;
case QMultimedia::HighQuality:
imageQuality = 80;
break;
case QMultimedia::VeryHighQuality:
imageQuality = 100;
break;
}
if (image.save(request.fileName, imageEncoderControl->imageSettings().codec().toLatin1().data(), imageQuality))
emit imageSaved(request.id, request.fileName);
else
emit error(request.id, QCameraImageCapture::ResourceError, tr("Image saving failed"));
return S_OK;
}
QT_END_NAMESPACE