add new example 'camera', showing how to use QML Camera on mobile (plus a web-server)

This commit is contained in:
pls.153 2022-11-06 20:55:58 +01:00
parent df4f72e2a9
commit 614737cbd4
13 changed files with 552 additions and 0 deletions

2
examples/camera/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

7
examples/camera/app.asd Normal file
View file

@ -0,0 +1,7 @@
(defsystem :app
:serial t
:depends-on (#-:depends-loaded :s-http-server)
:components ((:file "lisp/package")
(:file "lisp/web-server")
(:file "lisp/main")))

108
examples/camera/app.pro Normal file
View file

@ -0,0 +1,108 @@
LISP_FILES = $$files(lisp/*) app.asd make.lisp
android {
32bit {
ECL = $$(ECL_ANDROID_32)
} else {
ECL = $$(ECL_ANDROID)
}
lisp.commands = $$ECL/../ecl-android-host/bin/ecl \
-norc -shell $$PWD/make.lisp
} else:ios {
lisp.commands = $$(ECL_IOS)/../ecl-ios-host/bin/ecl \
-norc -shell $$PWD/make.lisp
} else:unix {
lisp.commands = /usr/local/bin/ecl -shell $$PWD/make.lisp
} else:win32 {
lisp.commands = ecl.exe -shell $$PWD/make.lisp
}
lisp.input = LISP_FILES
win32: lisp.output = tmp/app.lib
!win32: lisp.output = tmp/libapp.a
QMAKE_EXTRA_COMPILERS += lisp
win32: PRE_TARGETDEPS = tmp/app.lib
!win32: PRE_TARGETDEPS = tmp/libapp.a
QT += quick qml multimedia
TEMPLATE = app
CONFIG += c++17 no_keywords release
DEFINES = DESKTOP_APP INI_LISP INI_ECL_CONTRIB
INCLUDEPATH = /usr/local/include
ECL_VERSION = $$lower($$system(ecl -v))
ECL_VERSION = $$replace(ECL_VERSION, " ", "-")
LIBS = -L/usr/local/lib -lecl
LIBS += -L/usr/local/lib/$$ECL_VERSION
LIBS += -lecl-help -ldeflate -lecl-cdb -lecl-curl -lql-minitar -lsockets
DESTDIR = .
TARGET = app
OBJECTS_DIR = tmp
MOC_DIR = tmp
linux: LIBS += -L../../../platforms/linux/lib
macx: LIBS += -L../../../platforms/macos/lib
win32: LIBS += -L../../../platforms/windows/lib
win32 {
LIBS += -lws2_32
include(../../src/windows.pri)
}
android {
QT += androidextras
DEFINES -= DESKTOP_APP
INCLUDEPATH = $$ECL/include
ECL_VERSION = $$lower($$system($$ECL/../ecl-android-host/bin/ecl -v))
ECL_VERSION = $$replace(ECL_VERSION, " ", "-")
LIBS = -L$$ECL/lib -lecl
LIBS += -L$$ECL/lib/$$ECL_VERSION
LIBS += -lecl-help -ldeflate -lecl-cdb -lecl-curl -lql-minitar -lsockets
LIBS += -L../../../platforms/android/lib
ANDROID_EXTRA_LIBS += $$ECL/lib/libecl.so
ANDROID_PACKAGE_SOURCE_DIR = ../platforms/android
32bit {
ANDROID_ABIS = "armeabi-v7a"
} else {
ANDROID_ABIS = "arm64-v8a"
}
}
ios {
DEFINES -= DESKTOP_APP
INCLUDEPATH = $$(ECL_IOS)/include
ECL_VERSION = $$lower($$system($ECL_IOS/../ecl-ios-host/bin/ecl -v))
ECL_VERSION = $$replace(ECL_VERSION, " ", "-")
LIBS = -L$$(ECL_IOS)/lib -lecl
LIBS += -leclatomic -leclffi -leclgc -leclgmp
LIBS += -L$$(ECL_IOS)/lib/$$ECL_VERSION
LIBS += -lecl-help -ldeflate -lecl-cdb -lecl-curl -lql-minitar -lsockets
LIBS += -L../../../platforms/ios/lib
assets.files = $$files($$PWD/platforms/ios/assets)
QMAKE_BUNDLE_DATA += assets
QMAKE_INFO_PLIST = platforms/ios/Info.plist
}
32bit {
LIBS += -llqml32 -llisp32
} else {
LIBS += -llqml -llisp
}
LIBS += -Ltmp -lapp
HEADERS += ../../src/cpp/main.h
SOURCES += ../../src/cpp/main.cpp
RESOURCES += $$files(qml/*)
RESOURCES += $$files(i18n/*.qm)
lupdate_only {
SOURCES += i18n/tr.h
}

View file

@ -0,0 +1,3 @@
# install/update (keeps app data)
adb install -r android-build/*.apk
adb shell am start -n org.qtproject.example.camera/org.qtproject.qt5.android.bindings.QtActivity # Qt5

View file

@ -0,0 +1,2 @@
(in-package :camera)

View file

@ -0,0 +1,7 @@
(defpackage :camera
(:use :cl :qml :s-http-server)
(:export
#:*image-path*
#:*web-server*
#:create-index.html))

View file

@ -0,0 +1,48 @@
;;; This starts a local web-server in order to preview/download the taken
;;; pictures on your desktop computer. Make sure you are in the same WiFi,
;;; and open:
;;;
;;; http://192.168.1.x:1701/
(in-package :camera)
(defvar *web-server* nil)
(defvar *image-path* nil)
(defvar *index.html*
"<!doctype html>
<html>
<head>
<style type=\"text/css\">
img { width: 50px; border-width: 10px; border-style: solid; border-color: white }
</style>
</head>
<body>
~A
</body>
</html>")
(defvar *img.htm* "<a href=~S><img src=~S /></a>")
(defun ini (image-path)
(setf *web-server* (make-s-http-server))
(start-server *web-server*)
(register-context-handler *web-server* "/" 'static-resource-handler
:arguments (list image-path)))
(defun create-index.html (image-path) ; called from QML
"Creates 'index.html' for local web-server."
(unless *image-path*
(ini image-path))
(setf *image-path* image-path)
(with-open-file (s (merge-pathnames "index.html" image-path)
:direction :output :if-exists :supersede)
(format s *index.html*
(x:join (mapcar (lambda (file)
(let ((name (x:cc (pathname-name file) ".jpg")))
(format nil *img.htm* name name)))
(sort (directory (merge-pathnames "*_0*.jpg" image-path))
'string< :key 'pathname-name))
#\Newline)))
(values)) ; no return value to QML

90
examples/camera/make.lisp Normal file
View file

@ -0,0 +1,90 @@
;;; check target
(let ((arg (first (ext:command-args))))
(mapc (lambda (name feature)
(when (search name arg)
(pushnew feature *features*)))
(list "/ecl-android" "/ecl-ios")
(list :android :ios)))
#+(or android ios)
(pushnew :mobile *features*)
;;; copy ECL '*.doc' and 'encodings/' (mobile only)
(defun cc (&rest args)
(apply 'concatenate 'string args))
#+mobile
(defvar *assets* #+android "../platforms/android/assets/lib/"
#+ios "../platforms/ios/assets/Library/")
#+mobile
(defun shell (command)
(ext:run-program "sh" (list "-c" command)))
#+mobile
(unless (probe-file (cc *assets* "encodings"))
(ensure-directories-exist *assets*)
(let ((lib (cc (ext:getenv #+android "ECL_ANDROID" #+ios "ECL_IOS")
"/lib/ecl-*/")))
(shell (cc "cp " lib "*.doc " *assets*))
(shell (cc "cp -r " lib "encodings " *assets*))))
;;; compile ASDF system
(require :asdf)
(require :cmp)
(push (merge-pathnames "../")
asdf:*central-registry*)
(setf *default-pathname-defaults*
(truename (merge-pathnames "../../../"))) ; LQML root
(defvar *current*
(let ((name (namestring *load-truename*)))
(subseq name
(length (namestring *default-pathname-defaults*))
(1+ (position #\/ name :from-end t)))))
(dolist (file (list "package" "x" "ecl-ext" "ini" "qml")) ; load LQML symbols
(load (merge-pathnames file "src/lisp/")))
(progn
(defvar cl-user::*tr-path* (truename (cc *current* "i18n/")))
(load "src/lisp/tr"))
#-mobile
(asdf:make-build "app"
:monolithic t
:type :static-library
:move-here (cc *current* "build/tmp/")
:init-name "ini_app")
#+mobile
(progn
(pushnew :interpreter *features*)
(defvar *asdf-system* "app")
(defvar *ql-libs* (cc *current* "ql-libs.lisp"))
(defvar *init-name* "ini_app")
(defvar *library-path* (format nil "~Abuild-~A/tmp/"
*current*
#+android "android"
#+ios "ios"))
(defvar *require* (list :ecl-curl))
(load "platforms/shared/make"))
;;; rename lib
(let* ((from #-mobile (cc *current* (format nil "build/tmp/app--all-systems.~A"
#+msvc "lib"
#-msvc "a"))
#+mobile (cc *library-path* "app--all-systems.a"))
(to #+msvc "app.lib"
#-msvc "libapp.a")
(to* #-mobile (cc *current* "build/tmp/" to)
#+mobile (cc *library-path* to)))
(when (probe-file to*)
(delete-file to*))
(rename-file from to))

View file

@ -0,0 +1,81 @@
<?xml version="1.0"?>
<manifest package="org.qtproject.example.camera" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="Camera" android:extractNativeLibs="true">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="Camera" android:screenOrientation="unspecified" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="app"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroidBearer.jar:jar/QtAndroidExtras.jar:jar/QtMultimedia.jar"/>
<meta-data android:name="android.app.static_init_classes" android:value="org.qtproject.qt5.android.multimedia.QtMultimediaUtils"/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
</manifest>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Camera</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>${ASSETCATALOG_COMPILER_APPICON_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${QMAKE_SHORT_VERSION}</string>
<key>CFBundleSignature</key>
<string>${QMAKE_PKGINFO_TYPEINFO}</string>
<key>CFBundleVersion</key>
<string>${QMAKE_FULL_VERSION}</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>${IPHONEOS_DEPLOYMENT_TARGET}</string>
<key>NOTE</key>
<string>This file was generated by Qt/QMake.</string>
<key>NSCameraUsageDescription</key>
<string>To manually take pictures.</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,90 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtMultimedia 5.15
Rectangle {
width: 640
height: 360
color: "gray"
Camera {
id: camera
objectName: "camera"
imageCapture {
onImageSaved: {
imagePaths.append({"path": "file://" + path})
listView.positionViewAtEnd()
Lisp.call("camera:create-index.html", path)
}
}
}
VideoOutput {
objectName: "output"
source: camera
anchors.fill: parent
anchors.bottomMargin: listView.height + 10
focus: visible // to receive focus and capture key events when visible
}
// menu buttons
Column {
anchors.right: parent.right
padding: 10
spacing: 10
ComboBox {
id: cameras
width: 170
model: QtMultimedia.availableCameras
textRole: "displayName"
valueRole: "deviceId"
onActivated: camera.deviceId = currentValue
}
RoundButton {
anchors.right: parent.right
anchors.rightMargin: parent.spacing
width: 80
height: width
text: "Photo"
onClicked: camera.imageCapture.capture()
}
}
// list of taken images
ListModel {
id: imagePaths
}
ListView {
id: listView
height: parent.height / 5
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: 10
orientation: ListView.Horizontal
model: imagePaths
delegate: Image {
required property string path
height: parent.height
source: path
fillMode: Image.PreserveAspectFit
}
Rectangle {
anchors.fill: parent
anchors.topMargin: -10
color: "black"
opacity: 0.5
}
}
}

42
examples/camera/readme.md Normal file
View file

@ -0,0 +1,42 @@
Info
----
This shows how to use the QML Camera item. Only very basic usage is covered
here -- of course you can set many parameters, if you want to dive into all the
details (see Qt Assistant).
Additionally, a basic web-server is integrated, in order to watch/download the
images (mobile device) on the desktop computer. On every new image taken, the
`index.html` is updated.
To display the images on the desktop, enter the following link, using the IP of
the mobile device (assuming both desktop and mobile are in the same WiFi):
```
http://192.168.1.x:1701/
```
**1701** is the default port of `:s-http-server` (from Quicklisp), which was
chosen here because it's both relatively small and works well on mobile.
You may wonder about the "wrong" orientation of the images in the browser: this
depends both on the camera orientation and on the mobile OS.
Run
---
```
lqml run.lisp
```
Optionally pass `-slime` to start a Swank server, and connect from Emacs with
`M-x slime-connect`.
During development you can pass `-auto`, which will releoad all QML files after
you made a change to any of them and saved it. For re-initialization after
reloading, file `lisp/qml-reload/on-reloaded` will be loaded.
Closing the window quits the app. If you try to kill it with `ctrl-c`, you need
an additional `ctrl-d` to exit from ECL. To quit from Slime, do `(qq)` which is
short for `(qquit)`.

31
examples/camera/run.lisp Normal file
View file

@ -0,0 +1,31 @@
(in-package :qml-user)
(require :asdf)
(asdf:load-system :s-http-server)
(push (merge-pathnames "./")
asdf:*central-registry*)
(push :depends-loaded *features*)
(asdf:operate 'asdf:load-source-op :app)
(qset *quick-view*
|x| 75
|y| 75)
(defun option (name)
(find name (ext:command-args) :test 'search))
;;; trivial auto reload of all QML files after saving any change
(when (option "-auto")
(load "lisp/qml-reload/auto-reload"))
;;; for Slime after copying 'lqml-start-swank.lisp' from LQML sources
;;; to your Slime directory, which is assumed to be '~/slime/'
(when (option "-slime")
(load "~/slime/lqml-start-swank")) ; for 'slime-connect' from Emacs