| /**************************************************************************** |
| ** |
| ** 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 |