From aeb268ac95d1d75490e627a7927db2c61f25759f Mon Sep 17 00:00:00 2001 From: Alexander Rutz Date: Fri, 26 Jan 2024 15:56:59 -0700 Subject: [PATCH] * Initial support for camera2 API * Remove gst and aal dependency * Depend on droidmedia * Split main.qml in different files. * Add selection of different resolutions. Might not be used for pictures, but we want it later for recording. Just thinking of CPU load when recording with highest resolution. * Add scene-mode selection What is working: * Viewfinder * Zoom * Taking pictures The source is right now a little messy, old code and new code. As we might want to reuse some functions/classes. For example flash is not working because of the backend still WIP --- CMakeLists.txt | 46 +- debian/control | 36 +- debian/rules | 11 +- droidian-camera.conf | 8 +- icons/icons.qrc | 6 - sounds/sounds.qrc | 5 - src/CMakeLists.txt | 37 + src/dc-device.cpp | 436 ++++++++ src/dc-device.h | 82 ++ src/droidian-camera.cpp | 209 ++++ src/droidian-camera.h | 80 ++ {icons => src/icons}/shutter.svg | 0 {icons => src/icons}/timer.svg | 0 src/main.cpp | 79 +- src/qml/DcBlur.qml | 47 + src/qml/DcResolutions.qml | 46 + src/qml/DcSceneModes.qml | 46 + src/qml/DcSettings.qml | 294 ++++++ src/qml/main.qml | 1140 ++------------------- src/qml/qml.qrc | 10 - {sounds => src/sounds}/camera-shutter.wav | Bin 21 files changed, 1384 insertions(+), 1234 deletions(-) delete mode 100644 icons/icons.qrc delete mode 100644 sounds/sounds.qrc create mode 100644 src/CMakeLists.txt create mode 100644 src/dc-device.cpp create mode 100644 src/dc-device.h create mode 100644 src/droidian-camera.cpp create mode 100644 src/droidian-camera.h rename {icons => src/icons}/shutter.svg (100%) rename {icons => src/icons}/timer.svg (100%) create mode 100644 src/qml/DcBlur.qml create mode 100644 src/qml/DcResolutions.qml create mode 100644 src/qml/DcSceneModes.qml create mode 100644 src/qml/DcSettings.qml delete mode 100644 src/qml/qml.qrc rename {sounds => src/sounds}/camera-shutter.wav (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c30647..6244b77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,52 +1,22 @@ cmake_minimum_required(VERSION 3.16) -project (droidian-camera LANGUAGES CXX) +project (droidian-camera LANGUAGES C CXX) -set(CMAKE_INCLUDE_CURRENT_DIR ON) - -set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) - -find_package(Qt5 REQUIRED COMPONENTS Core Widgets Quick Qml Multimedia) - -execute_process(COMMAND pkg-config --cflags gstreamer-1.0 OUTPUT_VARIABLE GST_CFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) -execute_process(COMMAND pkg-config --libs gstreamer-1.0 OUTPUT_VARIABLE GST_LIBS OUTPUT_STRIP_TRAILING_WHITESPACE) -execute_process(COMMAND pkg-config --variable=libdir glib-2.0 OUTPUT_VARIABLE GLIB_LIBDIR OUTPUT_STRIP_TRAILING_WHITESPACE) - -set(APP_SOURCES - ${CMAKE_SOURCE_DIR}/src/main.cpp - ${CMAKE_SOURCE_DIR}/src/thumbnailgenerator.cpp - ${CMAKE_SOURCE_DIR}/src/capturefilter.cpp - ${CMAKE_SOURCE_DIR}/src/gstdevicerange.cpp - ${CMAKE_SOURCE_DIR}/src/flashlightcontroller.cpp - ${CMAKE_SOURCE_DIR}/src/filemanager.cpp) - -set(APP_HEADERS - ${CMAKE_SOURCE_DIR}/src/filemanager.h - ${CMAKE_SOURCE_DIR}/src/capturefilter.h - ${CMAKE_SOURCE_DIR}/src/gstdevicerange.h - ${CMAKE_SOURCE_DIR}/src/flashlightcontroller.h - ${CMAKE_SOURCE_DIR}/src/thumbnailgenerator.h) +set(CMAKE_INCLUDE_CURRENT_DIR ON) -qt5_add_resources(APP_RESOURCES - ${CMAKE_SOURCE_DIR}/sounds/sounds.qrc - ${CMAKE_SOURCE_DIR}/icons/icons.qrc - ${CMAKE_SOURCE_DIR}/src/qml/qml.qrc) +find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick Qml Multimedia) +qt_standard_project_setup() -add_executable(${PROJECT_NAME} ${APP_SOURCES} ${APP_HEADERS} ${APP_RESOURCES}) +add_subdirectory(src) target_include_directories(${PROJECT_NAME} PUBLIC - /usr/include/gstreamer-1.0 - /usr/include/glib-2.0 - ${GLIB_LIBDIR}/glib-2.0/include -) + ${CMAKE_CURRENT_SOURCE_DIR}/src) -target_compile_options(${PROJECT_NAME} PUBLIC ${GST_CFLAGS}) -target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Core Qt5::Widgets Qt5::Quick Qt5::Qml Qt5::Multimedia ${GST_LIBS}) +target_link_libraries(${PROJECT_NAME} PUBLIC Qt6::Core Qt6::Gui Qt6::Quick Qt6::Qml Qt6::Multimedia droidmedia) install(TARGETS ${PROJECT_NAME} DESTINATION /usr/bin) install(FILES ${CMAKE_SOURCE_DIR}/droidian-camera.desktop DESTINATION /usr/share/applications) install(FILES ${CMAKE_SOURCE_DIR}/camera-app.svg DESTINATION /usr/share/icons) -install(FILES ${CMAKE_SOURCE_DIR}/droidian-camera.conf DESTINATION /etc) +install(FILES ${CMAKE_SOURCE_DIR}/droidian-camera.conf DESTINATION /etc) \ No newline at end of file diff --git a/debian/control b/debian/control index 3d5997e..dc35306 100644 --- a/debian/control +++ b/debian/control @@ -5,14 +5,12 @@ Maintainer: Erik Inkinen Alexander Rutz Priority: optional Build-Depends: cmake, - qtbase5-dev, - qtdeclarative5-dev, - libqt5multimedia5-plugins, - qttools5-dev-tools, - libz-dev, - qtmultimedia5-dev, - libgstreamer1.0-dev, - pkgconf + libgles-dev, + qt6-base-dev, + pkg-config, + qt6-declarative-dev, + qt6-multimedia-dev, + libdroidmedia-dev, Standards-Version: 4.5.1 Package: droidian-camera @@ -21,20 +19,14 @@ Multi-Arch: foreign Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends}, ${shlibs:Depends}, - qml-module-qtmultimedia, - libqt5multimedia5-plugins, - qml-module-qtquick2, - qml-module-qtquick-controls2, - qml-module-qtquick-window2, - qt5-cameraplugin-aal, - qml-module-qt-labs-platform, - qml-module-qt-labs-folderlistmodel, - qml-module-qt-labs-settings, - qml-module-qtquick-layouts, - qml-module-qtgraphicaleffects, - libqt5svg5, - libgstreamer1.0-0, - gstreamer1.0-droid + libqt6gui6-gles, + qt6-wayland, + qml6-module-qtquick-layouts, + qml6-module-qtquick-controls, + qml6-module-qtquick, + qml6-module-qtmultimedia, + qml6-module-qtquick-window, + qml6-module-qtqml-workerscript Provides: pinhole Conflicts: pinhole Description: This package contains Droidian's default camera app. diff --git a/debian/rules b/debian/rules index ed7f152..8d6d3ba 100644 --- a/debian/rules +++ b/debian/rules @@ -1,4 +1,13 @@ #!/usr/bin/make -f +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) + +export QT_SELECT := qt6 + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + %: - dh $@ \ No newline at end of file + dh $@ --buildsystem=cmake \ No newline at end of file diff --git a/droidian-camera.conf b/droidian-camera.conf index 02b9a27..2928794 100644 --- a/droidian-camera.conf +++ b/droidian-camera.conf @@ -1,3 +1,5 @@ -[General] -blacklist="" -backend=aal +[BlacklistedCameras] +# In this exaple camera 0 and camera 2 would be blacklisted. The size value is the amount of total blacklisted cameras. +#camera\1\camId=0 +#camera\2\camId=2 +#camera\size=2 \ No newline at end of file diff --git a/icons/icons.qrc b/icons/icons.qrc deleted file mode 100644 index 2ef45ce..0000000 --- a/icons/icons.qrc +++ /dev/null @@ -1,6 +0,0 @@ - - - shutter.svg - timer.svg - - diff --git a/sounds/sounds.qrc b/sounds/sounds.qrc deleted file mode 100644 index aaeb5bf..0000000 --- a/sounds/sounds.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - camera-shutter.wav - - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..8a6b250 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.16) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +file(GLOB_RECURSE resources_SRC CONFIGURE_DEPENDS + "qml/*.qml" + "icons/*.svg" + "sounds/*.wav" +) + +foreach(path IN LISTS resources_SRC) + list(REMOVE_ITEM resources_SRC ${path}) + string(REPLACE ${CMAKE_CURRENT_SOURCE_DIR}/ "" path ${path}) + list(APPEND resources_SRC ${path}) +endforeach() + +set(APP_SOURCES + main.cpp + droidian-camera.cpp + dc-device.cpp) + +set(APP_HEADERS + droidian-camera.h + dc-device.h) + +qt_add_executable(${PROJECT_NAME} ${APP_SOURCES} ${APP_HEADERS}) + +qt_add_qml_module(${PROJECT_NAME} + URI DroidianCamera + VERSION 1.0 + DEPENDENCIES QtQuick +) + +qt_add_resources(${PROJECT_NAME} "resource" + PREFIX / + FILES + ${resources_SRC}) diff --git a/src/dc-device.cpp b/src/dc-device.cpp new file mode 100644 index 0000000..e076012 --- /dev/null +++ b/src/dc-device.cpp @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2024 Droidian Project +// +// Authors: +// Bardia Moshiri +// Erik Inkinen +// Alexander Rutz + +#include +#include +#include +#include + +void dc_focus_callback(void *data, int arg) +{ + //qDebug()<<"dc_focus_callback"< DcDevice::getResolutions(bool wide) +{ + QList ret; + + float aspect; + + if(wide){ + aspect = 16.0/9.0; + } else { + aspect = 4.0/3.0; + } + + for (const auto &res : m_pictureResolutions) { + if(aspect == (float)res.width()/(float)res.height()){ + ret.append(res); + } + } + + return ret; +} + +void DcDevice::loadCommonSettings() +{ + int arrSize; + QString filePath; + QFileInfo primaryConfig("/usr/lib/droidian/device/droidian-camera.conf"); + QFileInfo secodaryConfig("/etc/droidian-camera.conf"); + + if (primaryConfig.exists()) { + filePath = primaryConfig.absoluteFilePath(); + } else if (secodaryConfig.exists()) { + filePath = secodaryConfig.absoluteFilePath(); + } else { + qWarning()<<"No common configuration files found"; + qWarning()<<"Tried"<data, mem->size); + video_frame.setRotationAngle(QVideoFrame::RotationAngle(dc->m_camInfo.orientation)); + + if(!dc->m_camInfo.facing) + video_frame.setMirrored(true); + + video_frame.unmap(); + video_frame.toImage().save(dc->getFilename(0)); +} + +bool DcDevice::dc_frame_available(void *data, DroidMediaBuffer *buffer) +{ + DcDevice *dc = (DcDevice*)data; + DroidMediaBufferYCbCr ycbcr; + + DroidMediaBufferInfo info; + + droid_media_buffer_lock_ycbcr(buffer, DROID_MEDIA_BUFFER_LOCK_READ, &ycbcr); + droid_media_buffer_get_info(buffer, &info); + + QVideoFrame video_frame(QVideoFrameFormat (QSize(info.width, info.height),QVideoFrameFormat::Format_NV21)); + + if(!video_frame.map(QVideoFrame::WriteOnly)) + { + qWarning() << "QVideoFrame is not writable"; + } + + memcpy(video_frame.bits(0), ycbcr.y, video_frame.mappedBytes(0)); + memcpy(video_frame.bits(1), ycbcr.cr, video_frame.mappedBytes(1)); + + if(dc->m_camInfo.facing == 0){ + video_frame.setMirrored(true); + video_frame.setRotationAngle(QVideoFrame::RotationAngle(dc->m_camInfo.orientation)); + } else { + video_frame.setRotationAngle(QVideoFrame::RotationAngle(dc->m_camInfo.orientation)); + } + + video_frame.unmap(); + + dc->m_videoSink->setVideoFrame(video_frame); + + if(dc->m_saveFrame){ + video_frame.toImage().save(dc->getFilename(0)); + dc->m_saveFrame = false; + } + + droid_media_buffer_unlock(buffer); + droid_media_buffer_release (buffer, NULL, NULL); + return true; +} + +void DcDevice::dc_shutter_callback(void *data) +{ + DcDevice *dc = (DcDevice*)data; + emit dc->imageCaptured(); +} \ No newline at end of file diff --git a/src/dc-device.h b/src/dc-device.h new file mode 100644 index 0000000..05f6153 --- /dev/null +++ b/src/dc-device.h @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2024 Droidian Project +// +// Authors: +// Bardia Moshiri +// Erik Inkinen +// Alexander Rutz + +#ifndef DC_DEVICE_H +#define DC_DEVICE_H + +#include +#include +#include + +#include +#include +#include +#include + +#include + +class DcDevice : public QObject +{ + Q_OBJECT + +public: + DcDevice(int id); + bool start(); + void stop(); + float getMaxZoom(); + void takePicture(); + QStringList getSceneModes(); + QList getResolutions(bool wide); + bool setResolution(QSize res); + void setZoom(float zoom); + void setSceneMode(QString mode); + void setVideoSink(QVideoSink* videoSink); + double getMP(); + bool isBlacklisted(); + +public Q_SLOTS: + +signals: + void imageCaptured(); + +private: + void init(); + void setAvailableResolutions(QString type, QStringList values); + void setupCb(); + QStringList m_focus_modes; + QStringList m_scene_modes; + QStringList m_whitebalance_values; + float m_maxZoom; + QStringList m_flash_modes; + QList m_pictureResolutions; + QList m_previewResolutions; + QList m_videoResolutions; + DroidMediaCameraInfo m_camInfo; + QSize m_currentRes; + int m_camId; + DroidMediaCamera *m_camera = nullptr; + QVideoSink* m_videoSink = nullptr; + DroidMediaCameraCallbacks m_cb; + DroidMediaBufferQueueCallbacks m_cbBuffer; + DroidMediaBufferQueue *m_bufQueue = nullptr; + bool m_isWide = false; + bool m_saveFrame = false; + bool m_isBlacklisted = false; + QString m_picturesLocation; + QString m_videosLocation; + void loadCommonSettings(); + QString getFilename(int type); + static bool dc_buffer_created(void *data, DroidMediaBuffer *buffer); + static bool dc_frame_available(void *data, DroidMediaBuffer *buffer); + static void dc_compressed_image_callback(void *data, DroidMediaData *mem); + static void dc_shutter_callback(void *data); + + int m_maxResolutionOnly = 0; +}; + +#endif // DC_DEVICE_H \ No newline at end of file diff --git a/src/droidian-camera.cpp b/src/droidian-camera.cpp new file mode 100644 index 0000000..a539345 --- /dev/null +++ b/src/droidian-camera.cpp @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2024 Droidian Project +// +// Authors: +// Bardia Moshiri +// Erik Inkinen +// Alexander Rutz + +#include +#include +#include +#include + +#include + +DroidianCamera::DroidianCamera() { + init_cameras(); + m_videoSink = new QVideoSink(); +} + +DroidianCamera::~DroidianCamera() +{ + saveCameraSettings(); +} + +void DroidianCamera::init_cameras() +{ + int numOfCameras; + numOfCameras = droid_media_camera_get_number_of_cameras(); + for (int cam = 0; cam < numOfCameras; cam++) { + DcDevice *newCam = new DcDevice(cam); + m_cameras.append(newCam); + } +} + +void DroidianCamera::connectSignals() +{ + connect(m_cameras.at(m_cameraId), &DcDevice::imageCaptured, this, &DroidianCamera::imageCaptured); +} + +QVideoSink* DroidianCamera::get_video_sink() +{ + return m_videoSink; +} + +void DroidianCamera::setVideoSink(QVideoSink *newVideoSink) +{ + if (m_videoSink == newVideoSink) + return; + m_videoSink = newVideoSink; + m_cameras.at(m_cameraId)->setVideoSink(m_videoSink); + emit videoSinkChanged(); +} + +void DroidianCamera::takePicture() +{ + m_cameras.at(m_cameraId)->takePicture(); +} + +void DroidianCamera::setZoom(float zoom) +{ + m_cameras.at(m_cameraId)->setZoom(zoom); +} + +int DroidianCamera::get_cameraId() +{ + return m_cameraId; +} + +int DroidianCamera::get_maxZoom() +{ + return m_maxZoom; +} + +int DroidianCamera::get_aspectRatio() +{ + return m_aspectWide; +} + +QStringList DroidianCamera::get_sceneModes() +{ + return m_sceneModes; +} + +int DroidianCamera::get_sceneModeIndex() +{ + return m_sceneModeIndex; +} + +QList DroidianCamera::get_availableResolutions() +{ + return m_availableResolutions; +} + +int DroidianCamera::get_availableResolutionIndex() +{ + return m_availableResolutionIndex; +} + +void DroidianCamera::setCameraId(int id) +{ + int counter = 0; + + if(m_isFirstCall) + m_isFirstCall = false; + else + saveCameraSettings(); + + if(id > m_cameras.size()-1) + id = 0; + + m_cameras.at(m_cameraId)->stop(); + m_cameraId = id; + + while(m_cameras.at(m_cameraId)->isBlacklisted()){ + if(id == m_cameras.size()-1) + id = 0; + else + id += 1; + m_cameraId = id; + counter += 1; + + if(counter > m_cameras.size()) + qFatal("No accessible cameras found"); + } + + qDebug()<<"Start camera"<getMP()<<"MP"; + m_cameras.at(m_cameraId)->start(); + m_cameras.at(m_cameraId)->setVideoSink(m_videoSink); + emit cameraIdChanged(); + + m_maxZoom = m_cameras.at(m_cameraId)->getMaxZoom(); + emit maxZoomChanged(); + + m_sceneModes = m_cameras.at(m_cameraId)->getSceneModes(); + emit sceneModesChanged(); + + m_availableResolutions = m_cameras.at(m_cameraId)->getResolutions(m_aspectWide); + emit availableResolutionsChanged(); + + loadCameraSettings(); + connectSignals(); +} + +void DroidianCamera::setAspectRatio(int asp) +{ + if(asp != m_aspectWide){ + m_aspectWide = asp; + emit aspectRatioChanged(); + } + m_availableResolutions = m_cameras.at(m_cameraId)->getResolutions(m_aspectWide); + emit availableResolutionsChanged(); + + setAvailableResolutionIndex(0); + m_cameras.at(m_cameraId)->setResolution(m_availableResolutions.at(m_availableResolutionIndex)); + + m_cameras.at(m_cameraId)->setSceneMode(m_sceneModes.at(m_sceneModeIndex)); +} + +void DroidianCamera::setSceneModeIndex(int index) +{ + if(index != m_sceneModeIndex){ + m_sceneModeIndex = index; + m_cameras.at(m_cameraId)->setSceneMode(m_sceneModes.at(m_sceneModeIndex)); + emit sceneModeIndexChanged(); + } +} + +void DroidianCamera::setAvailableResolutionIndex(int index) +{ + if(index != m_availableResolutionIndex){ + m_availableResolutionIndex = index; + m_cameras.at(m_cameraId)->setResolution(m_availableResolutions.at(m_availableResolutionIndex)); + m_cameras.at(m_cameraId)->setSceneMode(m_sceneModes.at(m_sceneModeIndex)); + emit availableResolutionsChanged(); + } +} + +void DroidianCamera::saveCameraSettings() +{ + QSettings settings; + + QString str = "Camera"; + str.append(QString::number(m_cameraId)); + + settings.beginGroup(str); + settings.setValue("aspectRatio", get_aspectRatio()); + settings.setValue("resolutionIndex", get_availableResolutionIndex()); + settings.setValue("sceneModeIndex", get_sceneModeIndex()); + settings.endGroup(); + + settings.sync(); +} + +void DroidianCamera::loadCameraSettings() +{ + QSettings settings; + + QString str = "Camera"; + str.append(QString::number(m_cameraId)); + + settings.beginGroup(str); + setAspectRatio(settings.value("aspectRatio").toInt()); + setAvailableResolutionIndex(settings.value("resolutionIndex").toInt()); + setSceneModeIndex(settings.value("sceneModeIndex").toInt()); + settings.endGroup(); + + qDebug()<<"Settings loaded for"< +// Erik Inkinen +// Alexander Rutz + +#ifndef DROIDIAN_CAMERA_H +#define DROIDIAN_CAMERA_H + +#include +#include +#include + +class DroidianCamera : public QObject +{ + Q_OBJECT + Q_PROPERTY(QVideoSink* videoSink READ get_video_sink WRITE setVideoSink NOTIFY videoSinkChanged) + Q_PROPERTY(int cameraId READ get_cameraId WRITE setCameraId NOTIFY cameraIdChanged) + Q_PROPERTY(int maxZoom READ get_maxZoom NOTIFY maxZoomChanged) + Q_PROPERTY(int aspectRatio READ get_aspectRatio WRITE setAspectRatio NOTIFY aspectRatioChanged) + Q_PROPERTY(QStringList sceneModes READ get_sceneModes NOTIFY sceneModesChanged) + Q_PROPERTY(int sceneModeIndex READ get_sceneModeIndex WRITE setSceneModeIndex NOTIFY sceneModeIndexChanged) + Q_PROPERTY(QList availableResolutions READ get_availableResolutions NOTIFY availableResolutionsChanged) + Q_PROPERTY(int availableResolutionIndex READ get_availableResolutionIndex WRITE setAvailableResolutionIndex NOTIFY availableResolutionIndexChanged) + QML_ELEMENT + +public: + DroidianCamera(); + ~DroidianCamera(); + + void init(); + Q_INVOKABLE void takePicture(); + Q_INVOKABLE void setZoom(float zoom); + QVideoSink* get_video_sink(); + void setVideoSink(QVideoSink *newVideoSink); + int get_cameraId(); + int get_maxZoom(); + int get_aspectRatio(); + QStringList get_sceneModes(); + int get_sceneModeIndex(); + QList get_availableResolutions(); + int get_availableResolutionIndex(); + void setCameraId(int id); + void setAspectRatio(int asp); + void setSceneModeIndex(int index); + void setAvailableResolutionIndex(int index); + +public Q_SLOTS: + +signals: + void videoSinkChanged(); + void cameraIdChanged(); + void maxZoomChanged(); + void aspectRatioChanged(); + void sceneModesChanged(); + void imageCaptured(); + void sceneModeIndexChanged(); + void availableResolutionsChanged(); + void availableResolutionIndexChanged(); + +private: + QList m_cameras; + QVideoSink* m_videoSink = nullptr; + int m_cameraId = 0; + int m_maxZoom = 1; + int m_aspectWide = 0; + QStringList m_sceneModes; + int m_sceneModeIndex = 0; + QList m_availableResolutions; + int m_availableResolutionIndex = 0; + void init_cameras(); + void connectSignals(); + void saveCameraSettings(); + void loadCameraSettings(); + bool m_isFirstCall = true; +}; + +#endif // DROIDIAN_CAMERA_H \ No newline at end of file diff --git a/icons/shutter.svg b/src/icons/shutter.svg similarity index 100% rename from icons/shutter.svg rename to src/icons/shutter.svg diff --git a/icons/timer.svg b/src/icons/timer.svg similarity index 100% rename from icons/timer.svg rename to src/icons/timer.svg diff --git a/src/main.cpp b/src/main.cpp index 8c6a2fd..e397b73 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -// Copyright (C) 2023 Droidian Project +// Copyright (C) 2024 Droidian Project // // Authors: // Bardia Moshiri @@ -8,20 +8,15 @@ #include #include +#include #include #include -#include -#include "flashlightcontroller.h" -#include "filemanager.h" -#include "thumbnailgenerator.h" -#include "capturefilter.h" -#include "gstdevicerange.h" +#include +#include int main(int argc, char *argv[]) { -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#endif + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QGuiApplication app(argc, argv); @@ -32,75 +27,15 @@ int main(int argc, char *argv[]) QIcon::setThemeSearchPaths(QStringList("/usr/share/icons")); QQmlApplicationEngine engine; - FlashlightController flashlightController; - FileManager fileManager; - ThumbnailGenerator thumbnailGenerator; - CameraDeviceRangeWrapper cameraDeviceRangeWrapper; - QString mainQmlPath = "qrc:/main.qml"; + const QUrl url(QStringLiteral("qrc:/qml/main.qml")); - QString configFilePath = fileManager.getConfigFile(); - - qDebug() << "config file path: " << configFilePath; - QFile configFile(configFilePath); - - bool backend_selected = false; - - if (configFile.open(QIODevice::ReadOnly)) { - QTextStream in(&configFile); - QString line; - while (in.readLineInto(&line)) { - if (line.startsWith("backend=")) { - QString backendValue = line.split("=")[1].trimmed(); - if (backendValue == "aal") { - backend_selected = true; - mainQmlPath = "qrc:/main.qml"; - qDebug() << "selected aal backend"; - } else if (backendValue == "gst") { - backend_selected = true; - mainQmlPath = "qrc:/main-gst.qml"; - qDebug() << "selected gst backend"; - - cameraDeviceRangeWrapper.fetchCameraDeviceRange(); - engine.rootContext()->setContextProperty("cameraDeviceRangeWrapper", &cameraDeviceRangeWrapper); - qmlRegisterType("org.droidian.CameraDeviceRangeWrapper", 1, 0, "CameraDeviceRangeWrapper"); - } else { - backend_selected = true; - qDebug() << "defaulting to aal backend"; - } - - break; - } - } - - configFile.close(); - } else { - backend_selected = true; - qWarning() << "could not open config file at " << configFilePath; - qDebug() << "defaulting to aal backend"; - } - - if (backend_selected == false) { - qDebug() << "defaulting to aal backend"; - } - - fileManager.removeGStreamerCacheDirectory(); - - engine.rootContext()->setContextProperty("flashlightController", &flashlightController); - engine.rootContext()->setContextProperty("fileManager", &fileManager); - engine.rootContext()->setContextProperty("thumbnailGenerator", &thumbnailGenerator); - - const QUrl url(mainQmlPath); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); - - qmlRegisterType("org.droidian.Camera.CaptureFilter", 1, 0, "CaptureFilter"); - qmlRegisterType("org.droidian.CameraDeviceRangeWrapper", 1, 0, "CameraDeviceRangeWrapper"); - engine.load(url); return app.exec(); -} +} \ No newline at end of file diff --git a/src/qml/DcBlur.qml b/src/qml/DcBlur.qml new file mode 100644 index 0000000..2688743 --- /dev/null +++ b/src/qml/DcBlur.qml @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2024 Droidian Project +// +// Authors: +// Bardia Moshiri +// Erik Inkinen +// Alexander Rutz +// Joaquin Philco + +import QtQuick +import Qt5Compat.GraphicalEffects + +Item{ + anchors.fill: parent + property var blurSrc; + property var blureOn: 0; + + FastBlur { + id: vBlur + anchors.fill: parent + opacity: blureOn ? 1 : 0 + source: blurSrc + radius: 128 + visible: opacity != 0 + transparentBorder: false + Behavior on opacity { + NumberAnimation { + duration: 300 + } + } + } + + Glow { + anchors.fill: vBlur + opacity: blureOn ? 1 : 0 + radius: 4 + samples: 1 + color: "black" + source: vBlur + visible: opacity != 0 + Behavior on opacity { + NumberAnimation { + duration: 300 + } + } + } +} \ No newline at end of file diff --git a/src/qml/DcResolutions.qml b/src/qml/DcResolutions.qml new file mode 100644 index 0000000..e61c734 --- /dev/null +++ b/src/qml/DcResolutions.qml @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2024 Droidian Project +// +// Authors: +// Bardia Moshiri +// Erik Inkinen +// Alexander Rutz +// Joaquin Philco + +import QtQuick +import QtQuick.Controls + +ListView { + id: resolutions + anchors.fill: parent + anchors.leftMargin: 40 + model: dcCam.availableResolutions + visible: true + focus: true + spacing: 8 + currentIndex: dcCam.availableResolutionIndex + delegate: Button { + width: 140 + height: 40 + palette.buttonText: resolutions.currentIndex == index ? "orange" : "white" + + font.pixelSize: 14 + font.bold: true + text: modelData.width+" x "+modelData.height + + background: Rectangle { + anchors.fill: parent + color: "transparent" + border.width: 1 + border.color: resolutions.currentIndex == index ? "orange" : "white" + radius: 8 + } + + onClicked: { + resolutions.currentIndex = index + dcCam.availableResolutionIndex = index + drawer.close() + } + } + highlight: Rectangle { color: "transparent"; radius: 8; border.width: 3; border.color: "orange" } +} \ No newline at end of file diff --git a/src/qml/DcSceneModes.qml b/src/qml/DcSceneModes.qml new file mode 100644 index 0000000..96e6792 --- /dev/null +++ b/src/qml/DcSceneModes.qml @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2024 Droidian Project +// +// Authors: +// Bardia Moshiri +// Erik Inkinen +// Alexander Rutz +// Joaquin Philco + +import QtQuick +import QtQuick.Controls + +ListView { + id: scene + anchors.fill: parent + anchors.leftMargin: 40 + model: dcCam.sceneModes + visible: true + focus: true + spacing: 8 + currentIndex: dcCam.sceneModeIndex + delegate: Button { + width: 140 + height: 40 + palette.buttonText: scene.currentIndex == index ? "orange" : "white" + + font.pixelSize: 14 + font.bold: true + text: modelData + + background: Rectangle { + anchors.fill: parent + color: "transparent" + border.width: 1 + border.color: scene.currentIndex == index ? "orange" : "white" + radius: 8 + } + + onClicked: { + scene.currentIndex = index + dcCam.sceneModeIndex = index + drawer.close() + } + } + highlight: Rectangle { color: "transparent"; radius: 8; border.width: 3; border.color: "orange" } +} \ No newline at end of file diff --git a/src/qml/DcSettings.qml b/src/qml/DcSettings.qml new file mode 100644 index 0000000..5ce0644 --- /dev/null +++ b/src/qml/DcSettings.qml @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2024 Droidian Project +// +// Authors: +// Bardia Moshiri +// Erik Inkinen +// Alexander Rutz +// Joaquin Philco + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Drawer { + id: drawer + width: settingsLoader.active ? 300 : 100 + height: parent.height + dim: false + background: Rectangle { + id: background + anchors.fill: parent + color: "black" + opacity: 0.5 + } + + Behavior on width { + NumberAnimation { duration: 250 } + } + + ColumnLayout { + id: btnContainer + spacing: 25 + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 20 + Button { + id: camSwitchBtn + + height: width + Layout.alignment: Qt.AlignHCenter + icon.name: "camera-switch-symbolic" + icon.height: 40 + icon.width: 40 + icon.color: "white" + visible: true + + background: Rectangle { + anchors.fill: parent + color: "transparent" + } + + onClicked: { + dcCam.cameraId = dcCam.cameraId+1 + } + } + + Button { + id: cameraSelectButton + Layout.topMargin: -35 + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + icon.name: "view-more-horizontal-symbolic" + icon.height: 40 + icon.width: 40 + icon.color: "white" + + background: Rectangle { + anchors.fill: parent + color: "transparent" + } + + visible: true + + onClicked: { + // delayTime.visible = false + // backCamSelect.visible = true + // optionContainer.state = "opened" + // drawer.close() + // window.blurView = 1 + } + } + + Button { + id: flashButton + + height: width + Layout.alignment: Qt.AlignHCenter + icon.name: "thunderbolt-symbolic" + icon.height: 40 + icon.width: 40 + icon.color: "white" + //state: settings.flash + + visible: true + + states: [ + State { + name: "flashOff" + PropertyChanges { + target: camera + flash.mode: Camera.FlashOff + } + + PropertyChanges { + target: settings + flash: "flashOff" + } + }, + + State { + name: "flashOn" + PropertyChanges { + target: camera + flash.mode: Camera.FlashOn + } + + PropertyChanges { + target: settings + flash: "flashOn" + } + }, + + State { + name: "flashAuto" + PropertyChanges { + target: camera + flash.mode: Camera.FlashAuto + } + + PropertyChanges { + target: settings + flash: "flashAuto" + } + } + ] + + background: Rectangle { + anchors.fill: parent + color: "transparent" + } + + onClicked: { + if (camera.position !== Camera.FrontFace) { + if (flashButton.state == "flashOff") { + flashButton.state = "flashOn" + } else if (flashButton.state == "flashOn") { + flashButton.state = "flashAuto" + } else if (flashButton.state == "flashAuto") { + flashButton.state = "flashOff" + } + } + } + + Text { + anchors.fill: parent + text: flashButton.state == "flashOn" ? "\u2714" : + flashButton.state == "flashOff" ? "\u2718" : "A" + color: "white" + z: parent.z + 1 + font.pixelSize: 32 + font.bold: true + style: Text.Outline; + styleColor: "black" + bottomPadding: 10 + } + } + + Button { + id: aspectRatioButton + Layout.preferredWidth: 60 + Layout.preferredHeight: 40 + Layout.alignment: Qt.AlignHCenter + palette.buttonText: settingsLoader.source == "DcResolutions.qml" ? "orange" : "white" + + font.pixelSize: 14 + font.bold: true + text: dcCam.aspectRatio == 1 ? "16:9" : "4:3" + + visible: true //!window.videoCaptured + + background: Rectangle { + anchors.fill: parent + color: "transparent" + border.width: 2 + border.color: settingsLoader.source == "DcResolutions.qml" ? "orange" : "white" + radius: 8 + } + + onClicked: { + dcCam.aspectRatio = !dcCam.aspectRatio + drawer.close() + } + } + + Button { + id: resolutionButton + Layout.topMargin: -25 + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + icon.name: "view-more-horizontal-symbolic" + icon.height: 40 + icon.width: 40 + icon.color: settingsLoader.source == "DcResolutions.qml" ? "orange" : "white" + + background: Rectangle { + anchors.fill: parent + color: "transparent" + } + + onClicked: { + if(settingsLoader.active && settingsLoader.source == "DcResolutions.qml"){ + settingsLoader.source = "" + settingsLoader.active = false + } else { + settingsLoader.source = "DcResolutions.qml" + settingsLoader.active = true + } + } + } + + Button { + id: soundButton + //property var soundOn: settings.soundOn + + height: width + Layout.alignment: Qt.AlignHCenter + icon.name: soundButton.soundOn == 1 ? "audio-volume-high-symbolic" : "audio-volume-muted-symbolic" + icon.height: 40 + icon.width: 40 + icon.color: "white" + + background: Rectangle { + anchors.fill: parent + color: "transparent" + } + + onClicked: { + if (soundButton.soundOn == 1) { + soundButton.soundOn = 0 + settings.setValue("soundOn", 0) + } else { + soundButton.soundOn = 1 + settings.setValue("soundOn", 1) + } + } + } + + Button { + id: sceneButton + Layout.preferredWidth: 60 + Layout.preferredHeight: 40 + Layout.alignment: Qt.AlignHCenter + palette.buttonText: settingsLoader.source == "DcSceneModes.qml" ? "orange" : "white" + + font.pixelSize: 14 + font.bold: true + text: "SCENE" + + visible: true //!window.videoCaptured + + background: Rectangle { + anchors.fill: parent + color: "transparent" + border.width: 2 + border.color: settingsLoader.source == "DcSceneModes.qml" ? "orange" : "white" + radius: 8 + } + + onClicked: { + if(settingsLoader.active && settingsLoader.source == "DcSceneModes.qml"){ + settingsLoader.source = "" + settingsLoader.active = false + } else { + settingsLoader.source = "DcSceneModes.qml" + settingsLoader.active = true + } + } + } + + } + onClosed: { + blurItm.blureOn = 0 + settingsLoader.source = "" + settingsLoader.active = false + } + + Loader { + id: settingsLoader + active: false + width: 200 + height: parent.height * 0.8 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + opacity: parent.width / 300 + } +} \ No newline at end of file diff --git a/src/qml/main.qml b/src/qml/main.qml index 1bf44ee..4280178 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -// Copyright (C) 2023 Droidian Project +// Copyright (C) 2024 Droidian Project // // Authors: // Bardia Moshiri @@ -7,777 +7,64 @@ // Alexander Rutz // Joaquin Philco -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Window 2.12 -import QtGraphicalEffects 1.0 -import QtMultimedia 5.15 -import QtQuick.Layouts 1.15 -import Qt.labs.settings 1.0 -import Qt.labs.platform 1.1 +import QtQuick +import QtQuick.Controls +import QtQuick.Window +import QtQuick.Layouts +import QtMultimedia +import DroidianCamera ApplicationWindow { - id: window + id: mainWindow width: 400 height: 800 visible: true - title: "Camera" - property alias cam: camGst - property bool videoCaptured: false + title: qsTr("Droidian-Camera") + color: "black" - property var countDown: 0 - property var blurView: drawer.position == 0.0 && optionContainer.state == "closed" && tmDrawer.position == 0.0 ? 0 : 1 - property var useFlash: 0 - property var frontCameras: 0 - property var backCameras: 0 + DroidianCamera{ + id: dcCam + cameraId: 0 + videoSink: viewfinder.videoSink - Settings { - id: settings - property int cameraId: 0 - property int aspWide: 0 - property var flash: "flashAuto" - property var cameras: [{"cameraId": 0, "resolution": 0}, - {"cameraId": 1, "resolution": 0}, - {"cameraId": 2, "resolution": 0}, - {"cameraId": 3, "resolution": 0}, - {"cameraId": 4, "resolution": 0}, - {"cameraId": 5, "resolution": 0}, - {"cameraId": 6, "resolution": 0}, - {"cameraId": 7, "resolution": 0}, - {"cameraId": 8, "resolution": 0}, - {"cameraId": 9, "resolution": 0}] - - property var soundOn: 1 - property var hideTimerInfo: 0 - } - - Settings { - id: settingsCommon - fileName: fileManager.getConfigFile(); //"/etc/droidian-camera.conf" or "/usr/lib/droidian/device/droidian-camera.conf" - - property var blacklist: 0 - } - - ListModel { - id: allCamerasModel - Component.onCompleted: { - var blacklist - - if (settingsCommon.blacklist != "") { - blacklist = settingsCommon.blacklist.split(',') - } - - for (var i = 0; i < QtMultimedia.availableCameras.length; i++) { - var cameraInfo = QtMultimedia.availableCameras[i]; - var isBlacklisted = false; - - for (var p in blacklist) { - if (blacklist[p] == cameraInfo.deviceId) { - console.log("Camera with the id:", blacklist[p], "is blacklisted, not adding to camera list!"); - isBlacklisted = true; - break; - } - } - - if (isBlacklisted) { - continue; - } - - if (cameraInfo.position === Camera.BackFace) { - append({"cameraId": cameraInfo.deviceId, "index": i, "position": cameraInfo.position}); - window.backCameras += 1; - } else if (cameraInfo.position === Camera.FrontFace) { - insert(0, {"cameraId": cameraInfo.deviceId, "index": i, "position": cameraInfo.position}); - window.frontCameras += 1; - } - } - } - } - - background: Rectangle { - color: "black" - } - - Item { - id: cslate - - state: "PhotoCapture" - - states: [ - State { - name: "PhotoCapture" - }, - State { - name: "VideoCapture" - } - ] + onImageCaptured: sound.play() } SoundEffect { id: sound - source: "sounds/camera-shutter.wav" - } - - VideoOutput { - id: viewfinder - anchors.fill: parent - source: camera - autoOrientation: true - - Rectangle { - id: focusPointRect - border { - width: 3 - color: "#000000" - } - - color: "transparent" - radius: 90 - width: 80 - height: 80 - visible: false - - Timer { - id: visTm - interval: 500; running: false; repeat: false - onTriggered: focusPointRect.visible = false - } - } - - Rectangle { - anchors.fill: parent - opacity: blurView ? 1 : 0 - color: "#40000000" - visible: opacity != 0 - - Behavior on opacity { - NumberAnimation { - duration: 300 - } - } - } - } - - FastBlur { - id: vBlur - anchors.fill: parent - opacity: blurView ? 1 : 0 - source: viewfinder - radius: 128 - visible: opacity != 0 - transparentBorder: false - Behavior on opacity { - NumberAnimation { - duration: 300 - } - } - } - - Glow { - anchors.fill: vBlur - opacity: blurView ? 1 : 0 - radius: 4 - samples: 1 - color: "black" - source: vBlur - visible: opacity != 0 - Behavior on opacity { - NumberAnimation { - duration: 300 - } - } - } - - function gcd(a, b) { - if (b == 0) { - return a; - } else { - return gcd(b, a % b); - } - } - - function fnAspectRatio() { - var maxResolution = {width: 0, height: 0}; - var new43 = 0; - var new169 = 0; - - for (var p in camera.imageCapture.supportedResolutions) { - var res = camera.imageCapture.supportedResolutions[p]; - - var gcdValue = gcd(res.width, res.height); - var aspectRatio = (res.width / gcdValue) + ":" + (res.height / gcdValue); - - if (res.width * res.height > maxResolution.width * maxResolution.height) { - maxResolution = res; - } - - if (aspectRatio === "4:3" && !new43) { - new43 = 1; - camera.firstFourThreeResolution = res; - } - - if (aspectRatio === "16:9" && !new169) { - new169 = 1; - camera.firstSixteenNineResolution = res; - } - } - - if (camera.aspWide) { - camera.imageCapture.resolution = camera.firstSixteenNineResolution; - } else { - camera.imageCapture.resolution = camera.firstFourThreeResolution - } - - if (settings.cameras[camera.deviceId] && settings.cameras[camera.deviceId].resolution !== undefined) { - settings.cameras[camera.deviceId].resolution = Math.round( - (camera.imageCapture.supportedResolutions[0].width * camera.imageCapture.supportedResolutions[0].height) / 1000000 - ); - } - } - - Camera { - id: camera - captureMode: Camera.CaptureStillImage - - property variant firstFourThreeResolution - property variant firstSixteenNineResolution - property var aspWide: 0 - - focus { - focusMode: Camera.FocusMacro - focusPointMode: Camera.FocusPointCustom - } - - imageProcessing { - denoisingLevel: 1.0 - sharpeningLevel: 1.0 - whiteBalanceMode: CameraImageProcessing.WhiteBalanceAuto - } - - flash.mode: Camera.FlashOff - - imageCapture { - onImageCaptured: { - if (soundButton.soundOn) { - sound.play() - } - - if (settings.hideTimerInfo == 0) { - tmDrawer.open() - } - - if (mediaView.index < 0) { - mediaView.folder = StandardPaths.writableLocation(StandardPaths.PicturesLocation) + "/droidian-camera" - } - } - } - - Component.onCompleted: { - camera.stop() - var currentCam = settings.cameraId - for (var i = 0; i < QtMultimedia.availableCameras.length; i++) { - if (settings.cameras[i].resolution == 0) - camera.deviceId = i - } - - if (settings.aspWide == 1 || settings.aspWide == 0) { - camera.aspWide = settings.aspWide - } - - window.fnAspectRatio() - - camera.deviceId = currentCam - camera.start() - } - - onCameraStatusChanged: { - if (camera.cameraStatus == Camera.LoadedStatus) { - window.fnAspectRatio() - } - } - - onDeviceIdChanged: { - settings.setValue("cameraId", deviceId); - } - - onAspWideChanged: { - settings.setValue("aspWide", aspWide); - } - } - - MediaPlayer { - id: camGst - autoPlay: false - videoOutput: viewfinder - property var backendId: 0 - property string outputPath: StandardPaths.writableLocation(StandardPaths.MoviesLocation).toString().replace("file://","") + - "/droidian-camera/video" + Qt.formatDateTime(new Date(), "yyyyMMdd_hhmmsszzz") + ".mkv" - - Component.onCompleted: { - fileManager.createDirectory("/Videos/droidian-camera"); - } - - property var backends: [ - { - front: "gst-pipeline: droidcamsrc mode=2 camera-device=1 ! video/x-raw ! videoconvert ! qtvideosink", - frontRecord: "gst-pipeline: droidcamsrc camera_device=1 mode=2 ! tee name=t t. ! queue ! video/x-raw, width=" + camera.viewfinder.resolution.width + ", height=" + camera.viewfinder.resolution.height + " ! videoconvert ! videoflip video-direction=2 ! qtvideosink t. ! queue ! video/x-raw, width=" + camera.viewfinder.resolution.width + ", height=" + camera.viewfinder.resolution.height + " ! videoconvert ! videoflip video-direction=auto ! jpegenc ! mkv. autoaudiosrc ! queue ! audioconvert ! droidaenc ! mkv. matroskamux name=mkv ! filesink location=" + outputPath, - back: "gst-pipeline: droidcamsrc mode=2 camera-device=" + camera.deviceId + " ! video/x-raw ! videoconvert ! qtvideosink", - backRecord: "gst-pipeline: droidcamsrc camera_device=" + camera.deviceId + " mode=2 ! tee name=t t. ! queue ! video/x-raw, width=" + camera.viewfinder.resolution.width + ", height=" + camera.viewfinder.resolution.height + " ! videoconvert ! qtvideosink t. ! queue ! video/x-raw, width=" + camera.viewfinder.resolution.width + ", height=" + camera.viewfinder.resolution.height + " ! videoconvert ! videoflip video-direction=auto ! jpegenc ! mkv. autoaudiosrc ! queue ! audioconvert ! droidaenc ! mkv. matroskamux name=mkv ! filesink location=" + outputPath - } - ] - - onError: { - if (backendId + 1 in backends) { - backendId++; - } - } - } - - function handleVideoRecording() { - if (window.videoCaptured == false) { - camGst.outputPath = StandardPaths.writableLocation(StandardPaths.MoviesLocation).toString().replace("file://","") + - "/droidian-camera/video" + Qt.formatDateTime(new Date(), "yyyyMMdd_hhmmsszzz") + ".mkv" - - if (camera.position === Camera.BackFace) { - camGst.source = camGst.backends[camGst.backendId].backRecord; - } else { - camGst.source = camGst.backends[camGst.backendId].frontRecord; - } - - camera.stop(); - - camGst.play(); - window.videoCaptured = true; - } else { - camGst.stop(); - window.videoCaptured = false; - camera.cameraState = Camera.UnloadedState; - camera.start(); - } + source: "../sounds/camera-shutter.wav" } Item { id: camZoom - property real zoomFactor: 2.0 - property real zoom: 0 - NumberAnimation on zoom { - duration: 200 - easing.type: Easing.InOutQuad - } - onScaleChanged: { - camera.setDigitalZoom(scale * zoomFactor) + dcCam.setZoom(scale) } } - Timer { - id: swappingDelay - interval: 400 - repeat: false - - onTriggered: { - videoBtn.rotation += 180 - shutterBtn.rotation += 180 - cslate.state = (cslate.state == "VideoCapture") ? "PhotoCapture" : "VideoCapture" - window.blurView = 0 - } - } - - PinchArea { - id: pinchArea - width: parent.width - height: parent.height * 0.85 - pinch.target: camZoom - pinch.maximumScale: camera.maximumDigitalZoom / camZoom.zoomFactor - pinch.minimumScale: 0 - enabled: !mediaView.visible && !window.videoCaptured - - MouseArea { - id: dragArea - hoverEnabled: true - anchors.fill: parent - enabled: !mediaView.visible && !window.videoCaptured - property real startX: 0 - property real startY: 0 - - onPressed: { - startX = mouse.x - startY = mouse.y - } - - onReleased: { - var deltaX = mouse.x - startX - var deltaY = mouse.y - startY - - if (Math.abs(deltaX) > Math.abs(deltaY)) { - if (deltaX > 0 && cslate.state != "PhotoCapture") { - window.blurView = 1 - swappingDelay.start() - } else if (deltaX < 0 && cslate.state != "VideoCapture") { - window.blurView = 1 - videoBtn.rotation += 180 - shutterBtn.rotation += 180 - swappingDelay.start() - } - } else { - camera.focus.customFocusPoint = Qt.point(mouse.x / dragArea.width, mouse.y / dragArea.height) - camera.focus.focusMode = Camera.FocusMacro - focusPointRect.width = 60 - focusPointRect.height = 60 - focusPointRect.visible = true - focusPointRect.x = mouse.x - (focusPointRect.width / 2) - focusPointRect.y = mouse.y - (focusPointRect.height / 2) - visTm.start() - camera.searchAndLock() - } - } - } - - onPinchUpdated: { - camZoom.zoom = pinch.scale * camZoom.zoomFactor - } - } - - Drawer { - id: drawer - width: 100 - height: parent.height - dim: false - background: Rectangle { - id: background - anchors.fill: parent - color: "transparent" - } - - ColumnLayout { - id: btnContainer - spacing: 25 - anchors.centerIn: parent - - Button { - id: camSwitchBtn - - height: width - Layout.alignment: Qt.AlignHCenter - icon.name: "camera-switch-symbolic" - icon.height: 40 - icon.width: 40 - icon.color: "white" - visible: camera.position !== Camera.UnspecifiedPosition && !window.videoCaptured - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - if (camera.position === Camera.BackFace) { - drawer.close() - camera.position = Camera.FrontFace; - } else if (camera.position === Camera.FrontFace) { - drawer.close() - camera.position = Camera.BackFace; - } - } - } - - Button { - id: cameraSelectButton - Layout.topMargin: -35 - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - icon.name: "view-more-horizontal-symbolic" - icon.height: 40 - icon.width: 40 - icon.color: "white" - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - visible: window.backCameras > 1 && window.videoCaptured == false - - onClicked: { - delayTime.visible = false - backCamSelect.visible = true - optionContainer.state = "opened" - drawer.close() - window.blurView = 1 - } - } - - Button { - id: flashButton - - height: width - Layout.alignment: Qt.AlignHCenter - icon.name: "thunderbolt-symbolic" - icon.height: 40 - icon.width: 40 - icon.color: "white" - state: settings.flash - - visible: !window.videoCaptured - - states: [ - State { - name: "flashOff" - PropertyChanges { - target: camera - flash.mode: Camera.FlashOff - } - - PropertyChanges { - target: settings - flash: "flashOff" - } - }, - - State { - name: "flashOn" - PropertyChanges { - target: camera - flash.mode: Camera.FlashOn - } - - PropertyChanges { - target: settings - flash: "flashOn" - } - }, - - State { - name: "flashAuto" - PropertyChanges { - target: camera - flash.mode: Camera.FlashAuto - } - - PropertyChanges { - target: settings - flash: "flashAuto" - } - } - ] - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - if (camera.position !== Camera.FrontFace) { - if (flashButton.state == "flashOff") { - flashButton.state = "flashOn" - } else if (flashButton.state == "flashOn") { - flashButton.state = "flashAuto" - } else if (flashButton.state == "flashAuto") { - flashButton.state = "flashOff" - } - } - } - - Text { - anchors.fill: parent - text: flashButton.state == "flashOn" ? "\u2714" : - flashButton.state == "flashOff" ? "\u2718" : "A" - color: "white" - z: parent.z + 1 - font.pixelSize: 32 - font.bold: true - style: Text.Outline; - styleColor: "black" - bottomPadding: 10 - } - } - - Button { - id: aspectRatioButton - Layout.preferredWidth: 60 - Layout.preferredHeight: 40 - Layout.alignment: Qt.AlignHCenter - palette.buttonText: "white" - - font.pixelSize: 14 - font.bold: true - text: camera.aspWide ? "16:9" : "4:3" - - visible: !window.videoCaptured - - background: Rectangle { - anchors.fill: parent - color: "transparent" - border.width: 2 - border.color: "white" - radius: 8 - } - - onClicked: { - if (!camera.aspWide) { - drawer.close() - camera.aspWide = 1; - camera.imageCapture.resolution = camera.firstSixteenNineResolution - } else { - drawer.close() - camera.aspWide = 0; - camera.imageCapture.resolution = camera.firstFourThreeResolution - } - } - } - - Button { - id: soundButton - property var soundOn: settings.soundOn - - height: width - Layout.alignment: Qt.AlignHCenter - icon.name: soundButton.soundOn == 1 ? "audio-volume-high-symbolic" : "audio-volume-muted-symbolic" - icon.height: 40 - icon.width: 40 - icon.color: "white" - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } + VideoOutput { + id: viewfinder + anchors.fill: parent + visible: true - onClicked: { - if (soundButton.soundOn == 1) { - soundButton.soundOn = 0 - settings.setValue("soundOn", 0) - } else { - soundButton.soundOn = 1 - settings.setValue("soundOn", 1) - } - } - } - } - onClosed: { - window.blurView = optionContainer.state == "opened" ? 1 : 0 + PinchArea { + id: pinchArea + width: parent.width + height: parent.height * 0.85 + pinch.target: camZoom + pinch.maximumScale: dcCam.maxZoom + pinch.minimumScale: 1.0 } } - Rectangle { - id: optionContainer - width: parent.width - height: parent.height * .5 - anchors.verticalCenter: parent.verticalCenter - state: "closed" - - color: "transparent" - - states: [ - State { - name: "opened" - PropertyChanges { - target: optionContainer - x: window.width / 2 - optionContainer.width / 2 - } - }, - - State { - name: "closed" - PropertyChanges { - target: optionContainer - x: window.width - } - } - ] - - ColumnLayout { - anchors.fill: parent - - Tumbler { - id: delayTime - - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: true - Layout.preferredWidth: parent.width * 0.9 - model: 60 - - delegate: Text { - text: modelData == 0 ? "Off" : modelData - color: "white" - font.bold: true - font.pixelSize: 42 - horizontalAlignment: Text.AlignHCenter - style: Text.Outline; - styleColor: "black" - opacity: 0.4 + Math.max(0, 1 - Math.abs(Tumbler.displacement)) * 0.6 - } - } - - ColumnLayout { - id: backCamSelect - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: true - - function getSpaces(numDigits) { - if (numDigits === 1) { - return " "; - } else if (numDigits === 2) { - return " "; - } else if (numDigits === 3) { - return " "; - } else { - return ""; - } - } - - Repeater { - model: allCamerasModel - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: parent.width * 0.9 - Button { - property var pos: model.position == 1 ? "Back" : "Front" - property var numDigits: settings.cameras[model.cameraId].resolution.toString().length - Layout.alignment: Qt.AlignLeft - visible: parent.visible - icon.name: "camera-video-symbolic" - icon.color: "white" - icon.width: 48 - icon.height: 48 - palette.buttonText: "white" - - font.pixelSize: 32 - font.bold: true - text: " " + settings.cameras[model.cameraId].resolution + "MP" + backCamSelect.getSpaces(numDigits) + pos - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - window.blurView = 0 - camera.deviceId = model.cameraId - optionContainer.state = "closed" - } - } - } - } - } - - Behavior on x { - PropertyAnimation { - duration: 300 - } - } + DcBlur { + id: blurItm + blurSrc: viewfinder + blureOn: 0 } - Timer { - id: preCaptureTimer - interval: 1000 - onTriggered: { - countDown -= 1 - if (countDown < 1) { - camera.imageCapture.capture(); - preCaptureTimer.stop(); - } - } - - running: false - repeat: true + DcSettings{ + id: dcSettings } Drawer { @@ -792,221 +79,52 @@ ApplicationWindow { opacity: 0.9 } - GridLayout { - columnSpacing: 5 - rowSpacing: 25 - anchors.centerIn: parent - width: parent.width * 0.9 - columns: 2 - rows: 2 - - Button { - icon.name: "help-about-symbolic" - icon.color: "lightblue" - icon.width: 48 - icon.height: 48 - Layout.preferredWidth: icon.width * 1.5 - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - Layout.topMargin: 10 - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - } - - Text { - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - Layout.topMargin: 10 - text: "Press & hold to use the timer" - horizontalAlignment: Text.AlignHCenter - color: "white" - font.pixelSize: 32 - font.bold: true - style: Text.Outline; - styleColor: "black" - wrapMode: Text.WordWrap - } - - Button { - icon.name: "emblem-default-symbolic" - icon.color: "white" - icon.width: 48 - icon.height: 48 - Layout.columnSpan: 2 - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - tmDrawer.close() - settings.hideTimerInfo = 1 - settings.setValue("hideTimerInfo", 1); - } - } - } onClosed: { - window.blurView = 0; + blurItm.blureOn = 0 } - } - Rectangle { - id: bottomFrame - anchors.bottom: parent.bottom - height: 125 - width: parent.width - color: Qt.rgba(0, 0, 0, 0.6) - enabled: false + onOpened: { + blurItm.blureOn = 1 + } } - Rectangle { - id: menuBtnFrame + RowLayout { + width: parent.width + height: 140 anchors.bottom: parent.bottom - anchors.right: parent.right - height: 60 - width: 60 - color: "transparent" - anchors.rightMargin: 50 - anchors.bottomMargin: 35 - visible: !window.videoCaptured - + spacing: 6 + Button { - id: menuBtn - anchors.fill: parent + Layout.alignment: Qt.AlignHCenter icon.name: "open-menu-symbolic" icon.color: "white" icon.width: 32 icon.height: 32 - enabled: !window.videoCaptured - visible: drawer.position == 0.0 && optionContainer.state == "closed" - background: Rectangle { color: "black" opacity: 0.4 } onClicked: { - if (!mediaView.visible) { - window.blurView = 1 - drawer.open() - } - } - } - } - - Rectangle { - id: reviewBtnFrame - anchors.bottom: parent.bottom - anchors.left: parent.left - height: 60 - radius: 90 - width: 60 - anchors.leftMargin: 50 - anchors.bottomMargin: 35 - enabled: !window.videoCaptured - visible: !window.videoCaptured - - Rectangle { - id: reviewBtn - width: parent.width - height: parent.height - radius: 90 - color: "black" - layer.enabled: true - - layer.effect: OpacityMask { - maskSource: Item { - width: reviewBtn.width - height: reviewBtn.height - - Rectangle { - anchors.centerIn: parent - width: reviewBtn.adapt ? reviewBtn.width : Math.min(reviewBtn.width, reviewBtn.height) - height: reviewBtn.adapt ? reviewBtn.height : width - radius: 90 - } - } - } - - Image { - anchors.centerIn: parent - autoTransform: true - transformOrigin: Item.Center - fillMode: Image.PreserveAspectFit - smooth: true - source: (cslate.state == "PhotoCapture")? mediaView.lastImg : "" - scale: Math.min(parent.width / width, parent.height / height) - } - } - - Rectangle { - anchors.fill: reviewBtn - color: "transparent" - border.width: 2 - border.color: "white" - radius: 90 - - MouseArea { - anchors.fill: parent - onClicked: mediaView.visible = true + blurItm.blureOn = !blurItm.blureOn } } - } - Rectangle { - id: videoBtnFrame - height: 90 - width: 90 - radius: 70 - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottomMargin: 15 - visible: cslate.state == "VideoCapture" Button { - id: videoBtn - anchors.fill: videoBtnFrame - anchors.centerIn: parent - enabled: cslate.state == "VideoCapture" - - Rectangle { - anchors.centerIn: parent - width: videoBtnFrame.width - 40 - height: videoBtnFrame.height - 40 - color: "red" - radius: videoBtnFrame.radius - visible: window.videoCaptured ? false : true - } - - Rectangle { - anchors.centerIn: parent - visible: window.videoCaptured ? true : false - width: videoBtnFrame.width - 50 - height: videoBtnFrame.height - 50 - color: "black" - } - - text: preCaptureTimer.running ? countDown : "" - - palette.buttonText: "white" - - font.pixelSize: 64 - font.bold: true - + id: shutterBtn + Layout.alignment: Qt.AlignHCenter + icon.color: "white" + icon.source: "../icons/shutter.svg" + icon.width: 80 + icon.height: 80 background: Rectangle { - anchors.centerIn: parent - width: videoBtnFrame.width - 20 - height: videoBtnFrame.height - 20 - color: "white" - radius: videoBtnFrame.radius - 20 + color: "black" + radius: 90 } onClicked: { - handleVideoRecording() + dcCam.takePicture() + shutterBtn.rotation += 180 } Behavior on rotation { @@ -1016,154 +134,22 @@ ApplicationWindow { } } } - } - - Rectangle { - id: shutterBtnFrame - height: 90 - width: 90 - radius: 70 - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottomMargin: 15 - - visible: cslate.state == "PhotoCapture" Button { - id: shutterBtn - anchors.fill: parent.fill - anchors.centerIn: parent - enabled: cslate.state == "PhotoCapture" - icon.name: preCaptureTimer.running ? "" : - optionContainer.state == "opened" && delayTime.currentIndex < 1 || - optionContainer.state == "opened" && backCamSelect.visible ? "window-close-symbolic" : - cslate.state == "VideoCapture" ? "media-playback-stop-symbolic" : "shutter" - - icon.source: preCaptureTimer.running ? "" : - optionContainer.state == "opened" && delayTime.currentIndex > 0 ? "icons/timer.svg" : "icons/shutter.svg" - + Layout.alignment: Qt.AlignHCenter + icon.name: "open-menu-symbolic" icon.color: "white" - icon.width: shutterBtnFrame.width - icon.height: shutterBtnFrame.height - - text: preCaptureTimer.running ? countDown : "" - - palette.buttonText: "red" - - font.pixelSize: 64 - font.bold: true - visible: true - + icon.width: 32 + icon.height: 32 background: Rectangle { - anchors.centerIn: parent - width: shutterBtnFrame.width - height: shutterBtnFrame.height color: "black" - radius: shutterBtnFrame.radius + opacity: 0.4 } onClicked: { - pinchArea.enabled = true - window.blurView = 0 - shutterBtn.rotation += optionContainer.state == "opened" ? 0 : 180 - - if (optionContainer.state == "opened" && delayTime.currentIndex > 0 && !backCamSelect.visible) { - optionContainer.state = "closed" - countDown = delayTime.currentIndex - preCaptureTimer.start() - } else if (optionContainer.state == "opened" && delayTime.currentIndex < 1 || - optionContainer.state == "opened" && backCamSelect.visible) { - optionContainer.state = "closed" - } else { - camera.imageCapture.capture() - } - } - - onPressAndHold: { - optionContainer.state = "opened" - pinchArea.enabled = false - window.blurView = 1 - shutterBtn.rotation = 0 - delayTime.visible = true - backCamSelect.visible = false - } - - Behavior on rotation { - RotationAnimation { - duration: (shutterBtn.rotation >= 180 && optionContainer.state == "opened") ? 0 : 250 - direction: RotationAnimation.Counterclockwise - } - } - } - } - - MediaReview { - id : mediaView - anchors.fill : parent - onClosed: camera.start() - focus: visible - } - - Rectangle { - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - width: 400 - height: 270 - color: "transparent" - - RowLayout { - anchors.centerIn: parent - visible: !mediaView.visible && !window.videoCaptured - enabled: !mediaView.visible && !window.videoCaptured - Rectangle { - width: 80 - height: 30 - radius: 5 - color: "transparent" - - Text { - anchors.centerIn: parent - text: "Camera" - font.bold: true - color: cslate.state == "PhotoCapture" ? "orange" : "lightgray" - } - - MouseArea { - anchors.fill: parent - onClicked: { - if (cslate.state != "PhotoCapture") { - window.blurView = 1 - swappingDelay.start() - } - } - } - } - - Rectangle { - width: 80 - height: 30 - radius: 5 - color: "transparent" - - Text { - anchors.centerIn: parent - text: "Video" - font.bold: true - color: cslate.state == "VideoCapture" ? "orange" : "lightgray" - } - - MouseArea { - anchors.fill: parent - onClicked: { - if (cslate.state != "VideoCapture") { - window.blurView = 1 - videoBtn.rotation += 180 - shutterBtn.rotation += 180 - swappingDelay.start() - } - } - } + blurItm.blureOn = !blurItm.blureOn + dcSettings.open() } } } -} +} \ No newline at end of file diff --git a/src/qml/qml.qrc b/src/qml/qml.qrc deleted file mode 100644 index 6cb7d1f..0000000 --- a/src/qml/qml.qrc +++ /dev/null @@ -1,10 +0,0 @@ - - - main.qml - main-gst.qml - ZoomControl.qml - CameraSelect.qml - CameraListPopup.qml - MediaReview.qml - - diff --git a/sounds/camera-shutter.wav b/src/sounds/camera-shutter.wav similarity index 100% rename from sounds/camera-shutter.wav rename to src/sounds/camera-shutter.wav