From ef5dce916239196991f5e7dc9073e32fdaacd817 Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Lazo <30321688+EddyTheCo@users.noreply.github.com> Date: Sun, 22 Dec 2024 13:16:42 +0100 Subject: [PATCH] Align naming for components and namespaces * do not choose the camera format * Align names and use cmake presets * add the components to the find package config * engine.loadFromModule in the examples and fix component namings * Create a single thread for decoding that waits. #81 * use the latest dependencies by tag --- .github/workflows/build-test-install.yml | 27 +- .github/workflows/code-format.yml | 14 + .github/workflows/qml-lint.yml | 35 + .gitignore | 4 + CMakeLists.txt | 46 +- CMakePresets.json | 161 ++++ Config.cmake.in | 8 +- QrDec/CMakeLists.txt | 139 +-- QrDec/README.md | 14 +- QrDec/include/esterv/utils/qrcode_dec.hpp | 12 + QrDec/include/qrcodedec.hpp | 12 - QrDec/qrcodedec.cpp | 15 - QrDec/src/qrcode_dec.cpp | 15 + QrGen/CMakeLists.txt | 31 +- QrGen/README.md | 15 +- QrGen/include/esterv/utils/qrcode_gen.hpp | 616 +++++++++++++ QrGen/include/qrcodegen.hpp | 560 ------------ QrGen/src/qrcode_gen.cpp | 844 ++++++++++++++++++ QrGen/src/qrcodegen.cpp | 831 ----------------- QrGen/src/utils.cpp | 48 +- QtQrDec/CMakeLists.txt | 180 ++-- QtQrDec/Qrimagedecoder.cpp | 245 ----- QtQrDec/README.md | 32 +- QtQrDec/examples/CMakeLists.txt | 147 +-- QtQrDec/examples/Qrcam.cpp | 23 + QtQrDec/examples/Qrtext.cpp | 23 + QtQrDec/examples/qml/{qrcam.qml => Qrcam.qml} | 52 +- QtQrDec/examples/qml/Qrtext.qml | 28 + QtQrDec/examples/qml/qrtext.qml | 37 - QtQrDec/examples/qrcam.cpp | 24 - QtQrDec/examples/qrtext.cpp | 24 - QtQrDec/include/Qrimagedecoder.hpp | 88 -- .../include/esterv/utils/qr_image_decoder.hpp | 96 ++ QtQrDec/qml/QrCam.qml | 23 +- QtQrDec/qml/QrDecPop.qml | 31 +- QtQrDec/qml/QrTextField.qml | 43 +- QtQrDec/src/qr_image_decoder.cpp | 228 +++++ QtQrGen/CMakeLists.txt | 181 ++-- QtQrGen/Qrimageprovider.cpp | 33 - QtQrGen/README.md | 30 +- QtQrGen/examples/CMakeLists.txt | 135 +-- QtQrGen/examples/Image.cpp | 23 + QtQrGen/examples/Text.cpp | 22 + QtQrGen/examples/image.cpp | 23 - QtQrGen/examples/qml/Image.qml | 27 + QtQrGen/examples/qml/Text.qml | 27 + QtQrGen/examples/qml/image.qml | 40 - QtQrGen/examples/qml/text.qml | 40 - QtQrGen/examples/text.cpp | 23 - QtQrGen/include/Qrimageprovider.hpp | 25 - .../esterv/utils/qr_image_provider.hpp | 28 + QtQrGen/qml/QrGenImage.qml | 24 +- QtQrGen/qml/QrGenPop.qml | 58 +- QtQrGen/qml/QrText.qml | 43 +- QtQrGen/src/qr_image_provider.cpp | 33 + README.md | 25 +- 56 files changed, 2970 insertions(+), 2641 deletions(-) create mode 100644 .github/workflows/code-format.yml create mode 100644 .github/workflows/qml-lint.yml create mode 100644 CMakePresets.json create mode 100644 QrDec/include/esterv/utils/qrcode_dec.hpp delete mode 100644 QrDec/include/qrcodedec.hpp delete mode 100644 QrDec/qrcodedec.cpp create mode 100644 QrDec/src/qrcode_dec.cpp create mode 100644 QrGen/include/esterv/utils/qrcode_gen.hpp delete mode 100644 QrGen/include/qrcodegen.hpp create mode 100644 QrGen/src/qrcode_gen.cpp delete mode 100644 QrGen/src/qrcodegen.cpp delete mode 100644 QtQrDec/Qrimagedecoder.cpp create mode 100644 QtQrDec/examples/Qrcam.cpp create mode 100644 QtQrDec/examples/Qrtext.cpp rename QtQrDec/examples/qml/{qrcam.qml => Qrcam.qml} (59%) create mode 100644 QtQrDec/examples/qml/Qrtext.qml delete mode 100644 QtQrDec/examples/qml/qrtext.qml delete mode 100644 QtQrDec/examples/qrcam.cpp delete mode 100644 QtQrDec/examples/qrtext.cpp delete mode 100644 QtQrDec/include/Qrimagedecoder.hpp create mode 100644 QtQrDec/include/esterv/utils/qr_image_decoder.hpp create mode 100644 QtQrDec/src/qr_image_decoder.cpp delete mode 100644 QtQrGen/Qrimageprovider.cpp create mode 100644 QtQrGen/examples/Image.cpp create mode 100644 QtQrGen/examples/Text.cpp delete mode 100644 QtQrGen/examples/image.cpp create mode 100644 QtQrGen/examples/qml/Image.qml create mode 100644 QtQrGen/examples/qml/Text.qml delete mode 100644 QtQrGen/examples/qml/image.qml delete mode 100644 QtQrGen/examples/qml/text.qml delete mode 100644 QtQrGen/examples/text.cpp delete mode 100644 QtQrGen/include/Qrimageprovider.hpp create mode 100644 QtQrGen/include/esterv/utils/qr_image_provider.hpp create mode 100644 QtQrGen/src/qr_image_provider.cpp diff --git a/.github/workflows/build-test-install.yml b/.github/workflows/build-test-install.yml index 654b563..75002b8 100644 --- a/.github/workflows/build-test-install.yml +++ b/.github/workflows/build-test-install.yml @@ -13,23 +13,15 @@ jobs: matrix: os: [ubuntu-latest,macos-latest,windows-latest] - uses: EddyTheCo/Common/.github/workflows/build-test-install.yml@v0.3.0 + uses: EddyTheCo/Common/.github/workflows/build-test-install.yml@v0.5.0 with: os: ${{ matrix.os }} - qtVersion: '6.6.0' - cmakeArgs: "-DQTDEPLOY=ON -DBUILD_EXAMPLES=ON -DCPACK_PACKAGE_CONTACT=estervtech@gmail.com -DREPO_URL=https://eddytheco.github.io/qrCode -DCPACK_PACKAGE_VENDOR=estervtech" + qtVersion: '6.8.1' qtModules: 'qtshadertools qtmultimedia' - test: false - build_doxygen: - if: startsWith(github.ref, 'refs/tags/v') - uses: EddyTheCo/Common/.github/workflows/build-docs.yml@v0.3.0 - with: - cmakeArgs: "-DBUILD_QRDEC=OFF -DBUILD_QRGEN=OFF" - release: if: startsWith(github.ref, 'refs/tags/v') - needs: [build_test_package, build_doxygen] + needs: build_test_package runs-on: ubuntu-latest permissions: @@ -48,13 +40,11 @@ jobs: - uses: actions/download-artifact@v4 with: path: artifacts - - name: Display structure of downloaded files - run: ls -R - name: Move repositories to webpage run: | - mv artifacts/docs/html github-pages - rm -rf artifacts/docs + mv artifacts/common/html github-pages + mv artifacts/common . mkdir github-pages/packages/ ${{runner.temp}}/platforms/ mv artifacts/*/_CPack_Packages/* ${{runner.temp}}/platforms/ for i in ${{runner.temp}}/platforms/*; do mv $i/IFW/*/repository/* $i/; rm -rf $i/IFW $i/TGZ; done; @@ -68,13 +58,14 @@ jobs: cmake -E tar c ${{runner.temp}}/page-packages/github-pages.tar -- . - name: Releases - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: - files: ./artifacts/*/* + files: ./artifacts/*/* + body_path: ./common/CHANGELOG.md - uses: actions/upload-artifact@v4 with: - name: 'github-pages' + name: 'github-pages' path: ${{runner.temp}}/page-packages/* - name: Deploy to GitHub Pages diff --git a/.github/workflows/code-format.yml b/.github/workflows/code-format.yml new file mode 100644 index 0000000..8d4698c --- /dev/null +++ b/.github/workflows/code-format.yml @@ -0,0 +1,14 @@ +name: code-format +run-name: Format code and do PR +on: + pull_request_target: + types: [closed] + branches: [develop] +jobs: + checkout-format-pr: + uses: EddyTheCo/Common/.github/workflows/code-format.yml@v0.5.0 + if: ${{ (github.event.pull_request.merged == true) && (startsWith(github.base_ref, 'develop')) }} + permissions: + contents: write + pull-requests: write + diff --git a/.github/workflows/qml-lint.yml b/.github/workflows/qml-lint.yml new file mode 100644 index 0000000..af0482e --- /dev/null +++ b/.github/workflows/qml-lint.yml @@ -0,0 +1,35 @@ +name: qmllintbot +run-name: Linting QML for PR +on: + + workflow_run: + workflows: [push-build-release] + types: + - completed +jobs: + download-comment: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + actions: read + pull-requests: write + steps: + - name: 'Download artifact' + uses: actions/download-artifact@v4 + with: + name: common + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Read pr number + id: getprn + run: | + cat pr_number + echo "prn=$(cat pr_number)" >> $GITHUB_OUTPUT + + - name: 'Run reviewer' + uses: EddyTheCo/qmllint-action/@v0.1.0 + with: + jsondir: ${{ github.workspace }} + pr_number: ${{ steps.getprn.outputs.prn }} + diff --git a/.gitignore b/.gitignore index 5b73495..04766dc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ doc/html/ doc/*.tag *.swp +CMakeLists.txt.user +build* +tags +CMakeUserPresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 605030e..e39692a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,24 @@ cmake_minimum_required(VERSION 3.24) + option(BUILD_QRDEC "Build Decoding part" ON) -option(BUILD_QRENC "Build Encoding part" ON) +option(BUILD_QRGEN "Build Generation part" ON) +option(OpenCV_DOWNLOAD "Download prebuilts if find_package fails" ON) +option(BUILD_EXAMPLES "Build the examples" ON) + include(${CMAKE_CURRENT_BINARY_DIR}/local_conf.cmake OPTIONAL) include(FetchContent) FetchContent_Declare( - ccommon - GIT_REPOSITORY https://github.com/EddyTheCo/Common.git - GIT_TAG v0.3.0 - ) + ccommon + GIT_REPOSITORY https://github.com/EddyTheCo/Common.git + GIT_TAG v0.5.0) FetchContent_MakeAvailable(ccommon) -version_from_git( - LOG OFF - TIMESTAMP "%Y%m%d%H%M%S" - ) -project(qrCode VERSION ${VERSION} DESCRIPTION "library for qr code manipulation" LANGUAGES CXX) +version_from_git(LOG OFF TIMESTAMP "%Y%m%d%H%M%S") +project( + EstervQrCode + VERSION ${VERSION} + DESCRIPTION "Library for qr code manipulation" + LANGUAGES CXX) set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_CXX_STANDARD 17) @@ -24,20 +28,28 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) include(GNUInstallDirs) if(BUILD_QRDEC) - add_subdirectory(QrDec) + add_subdirectory(QrDec) endif(BUILD_QRDEC) -if(BUILD_QRENC) - add_subdirectory(QrGen) -endif(BUILD_QRENC) +if(BUILD_QRGEN) + add_subdirectory(QrGen) +endif(BUILD_QRGEN) + add_subdirectory(QtQrDec) add_subdirectory(QtQrGen) +set(SET_COMPONENTS "") +foreach(component "QrDec" "QrGen" "QtQrDec" "QtQrGen") + if(TARGET "${component}") + string(APPEND SET_COMPONENTS + "set(${PROJECT_NAME}_${component}_FOUND \"ON\")\n") + endif() +endforeach() build_exports() if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - build_cpack() + build_cpack() endif(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) if(BUILD_DOCS) - build_docs() -endif() + build_docs() +endif(BUILD_DOCS) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..b8c3bfe --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,161 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "default-release", + "displayName": "Default Release", + "description": "Release Static library using Ninja generator", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "REPO_URL": "https://eddytheco.github.io/Esterv.Utils.QrCode" + } + }, + { + "name": "default-develop", + "displayName": "Default Config for development(Release)", + "description": "Release build type using Ninja generator, add tests and docs", + "inherits": "default-release", + "cacheVariables": { + "BUILD_DOCS": "ON", + "BUILD_EXAMPLES": "ON", + "QTDEPLOY": "ON" + } + }, + { + "name": "default-develop-debug", + "displayName": "Default Config for development(Debug)", + "description": "Debug build type using Ninja generator", + "inherits": "default-develop", + "binaryDir": "${sourceDir}/build/debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + } + ], + "buildPresets": [ + { + "name": "default-release", + "configurePreset": "default-release" + }, + { + "name": "default-develop", + "configurePreset": "default-develop" + }, + { + "name": "default-develop-debug", + "configurePreset": "default-develop-debug" + }, + { + "name": "default-documentation", + "configurePreset": "default-develop", + "targets": "doxygen_docs" + }, + { + "name": "default-qmllint", + "configurePreset": "default-develop", + "targets": "all_qmllint_json" + } + ], + "workflowPresets": [ + { + "name": "default-release", + "steps": [ + { + "type": "configure", + "name": "default-release" + }, + { + "type": "build", + "name": "default-release" + } + ] + }, + { + "name": "default-develop", + "steps": [ + { + "type": "configure", + "name": "default-develop" + }, + { + "type": "build", + "name": "default-develop" + } + ] + }, + { + "name": "default-documentation", + "steps": [ + { + "type": "configure", + "name": "default-develop" + }, + { + "type": "build", + "name": "default-documentation" + } + ] + }, + { + "name": "default-qmllint", + "steps": [ + { + "type": "configure", + "name": "default-develop" + }, + { + "type": "build", + "name": "default-qmllint" + } + ] + }, + { + "name": "default-develop-debug", + "steps": [ + { + "type": "configure", + "name": "default-develop-debug" + }, + { + "type": "build", + "name": "default-develop-debug" + } + ] + } + ], + "packagePresets": [ + { + "name": "default-release", + "description": "Release runtime components", + "configurePreset": "default-release", + "generators": [ + "IFW", + "TGZ" + ], + "variables": { + "CPACK_ARCHIVE_COMPONENT_INSTALL": "ON", + "CPACK_COMPONENTS_ALL": "EstervQrGen;EstervQtQrGen;EstervQrDec;EstervQtQrDec;EstervQtQrGen-qml;EstervQtQrDec-qml", + "CPACK_PACKAGE_CONTACT": "estervtech@gmail.com", + "CPACK_PACKAGE_VENDOR": "estervtech" + }, + "packageDirectory": "packages" + }, + { + "name": "default-develop", + "description": "Release development components", + "configurePreset": "default-develop", + "generators": [ + "IFW", + "TGZ" + ], + "variables": { + "CPACK_COMPONENTS_GROUPING": "ALL_COMPONENTS_IN_ONE", + "CPACK_PACKAGE_CONTACT": "estervtech@gmail.com", + "CPACK_PACKAGE_VENDOR": "estervtech" + }, + "packageDirectory": "packages-dev" + } + ] +} diff --git a/Config.cmake.in b/Config.cmake.in index df85b34..c6bd0ec 100644 --- a/Config.cmake.in +++ b/Config.cmake.in @@ -1,6 +1,8 @@ @PACKAGE_INIT@ +@SET_COMPONENTS@ include(CMakeFindDependencyMacro) -find_dependency(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick Svg OPTIONAL_COMPONENTS Multimedia ) +find_dependency(Qt6 COMPONENTS Core Gui Qml Quick Svg OPTIONAL_COMPONENTS Multimedia ) find_dependency(OpenCV COMPONENTS core objdetect imgproc flann features2d calib3d ) -find_dependency(EstervDesigns 1.2 COMPONENTS SimpleStyle CustomControls CONFIG) -include ( "${CMAKE_CURRENT_LIST_DIR}/qrCode-config.cmake" ) +find_dependency(EstervDesigns 2 COMPONENTS SimpleStyle CustomControls CONFIG) +find_package_handle_standard_args(@PROJECT_NAME@ HANDLE_COMPONENTS) +include ( "${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-config.cmake" ) diff --git a/QrDec/CMakeLists.txt b/QrDec/CMakeLists.txt index 2c02e87..4a0037e 100755 --- a/QrDec/CMakeLists.txt +++ b/QrDec/CMakeLists.txt @@ -1,68 +1,89 @@ -find_package(OpenCV COMPONENTS core objdetect imgproc flann features2d calib3d ) +find_package(OpenCV COMPONENTS core objdetect imgproc flann features2d calib3d) -option(OpenCV_DOWNLOAD "On or off" ON) if(NOT OpenCV_FOUND AND OpenCV_DOWNLOAD) - set(builturl "https://github.com/EddyTheCo/install-OpenCV-action/releases/latest/download/OpenCV-ubuntu-latest-wasm_false-android_none.tar") - if(WIN32) - set(builturl "https://github.com/EddyTheCo/install-OpenCV-action/releases/latest/download/OpenCV-windows-latest-wasm_false-android_none.tar") - endif(WIN32) - if (APPLE) - set(builturl "https://github.com/EddyTheCo/install-OpenCV-action/releases/latest/download/OpenCV-macos-latest-wasm_false-android_none.tar") - endif(APPLE) - if(EMSCRIPTEN) - set(builturl "https://github.com/EddyTheCo/install-OpenCV-action/releases/latest/download/OpenCV-ubuntu-latest-wasm_true-android_none.tar") - endif(EMSCRIPTEN) - if(ANDROID) - set(builturl "https://github.com/EddyTheCo/install-OpenCV-action/releases/latest/download/OpenCV-ubuntu-latest-wasm_false-android_${ANDROID_ABI}.tar") - endif(ANDROID) - FetchContent_Declare( - OpenCV - DOWNLOAD_EXTRACT_TIMESTAMP true - URL ${builturl} - ) - FetchContent_GetProperties(OpenCV) - if(NOT OpenCV_POPULATED) - message(STATUS "OpenCV prebuilt will be downloaded from ${builturl} if not found on the system") - FetchContent_Populate(OpenCV) - message(STATUS "OpenCV is installed in ${opencv_SOURCE_DIR}") - set(OpenCV_DIR ${opencv_SOURCE_DIR}/lib/cmake/opencv4) - if(WIN32) - set(OpenCV_DIR ${opencv_SOURCE_DIR}) - endif(WIN32) - if(ANDROID) - set(OpenCV_DIR ${opencv_SOURCE_DIR}/sdk/native/jni) - endif(ANDROID) - find_package(OpenCV COMPONENTS core objdetect imgproc flann features2d calib3d ) - endif() - if(OpenCV_FOUND AND NOT ANDROID AND NOT EMSCRIPTEN) - install(IMPORTED_RUNTIME_ARTIFACTS ${OpenCV_LIBS} - DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT QrDec - ) - endif() + set(builturl + "https://github.com/EddyTheCo/install-OpenCV-action/releases/latest/download/OpenCV-ubuntu-latest-wasm_false-android_none.tar" + ) + if(WIN32) + set(builturl + "https://github.com/EddyTheCo/install-OpenCV-action/releases/latest/download/OpenCV-windows-latest-wasm_false-android_none.tar" + ) + endif(WIN32) + if(APPLE) + set(builturl + "https://github.com/EddyTheCo/install-OpenCV-action/releases/latest/download/OpenCV-macos-latest-wasm_false-android_none.tar" + ) + endif(APPLE) + if(EMSCRIPTEN) + set(builturl + "https://github.com/EddyTheCo/install-OpenCV-action/releases/latest/download/OpenCV-ubuntu-latest-wasm_true-android_none.tar" + ) + endif(EMSCRIPTEN) + if(ANDROID) + set(builturl + "https://github.com/EddyTheCo/install-OpenCV-action/releases/latest/download/OpenCV-ubuntu-latest-wasm_false-android_${ANDROID_ABI}.tar" + ) + endif(ANDROID) + FetchContent_Declare( + OpenCV + DOWNLOAD_EXTRACT_TIMESTAMP true + URL ${builturl}) + FetchContent_GetProperties(OpenCV) + if(NOT OpenCV_POPULATED) + message( + STATUS + "OpenCV prebuilt will be downloaded from ${builturl} if not found on the system" + ) + FetchContent_Populate(OpenCV) + message(STATUS "OpenCV is installed in ${opencv_SOURCE_DIR}") + set(OpenCV_DIR ${opencv_SOURCE_DIR}/lib/cmake/opencv4) + if(WIN32) + set(OpenCV_DIR ${opencv_SOURCE_DIR}) + endif(WIN32) + if(ANDROID) + set(OpenCV_DIR ${opencv_SOURCE_DIR}/sdk/native/jni) + endif(ANDROID) + find_package(OpenCV COMPONENTS core objdetect imgproc flann features2d + calib3d) + endif() + if(OpenCV_FOUND + AND NOT ANDROID + AND NOT EMSCRIPTEN) + install( + IMPORTED_RUNTIME_ARTIFACTS + ${OpenCV_LIBS} + DESTINATION + ${CMAKE_INSTALL_LIBDIR} + COMPONENT + OpenCV-dep + RUNTIME + COMPONENT + OpenCV-dep) + endif() endif() - - if(OpenCV_FOUND) - add_library(QrDec qrcodedec.cpp) + add_library(QrDec src/qrcode_dec.cpp) - add_library(${PROJECT_NAME}::QrDec ALIAS QrDec) - set_target_properties(QrDec PROPERTIES VERSION ${VERSION} SOVERSION ${VERSION_MAJOR}) - target_include_directories(QrDec PUBLIC $) - target_link_libraries(QrDec PUBLIC ${OpenCV_LIBS}) + add_library(Esterv::QrDec ALIAS QrDec) + set_target_properties(QrDec PROPERTIES VERSION ${VERSION} SOVERSION + ${VERSION_MAJOR}) + target_include_directories(QrDec + PUBLIC $) + target_link_libraries(QrDec PUBLIC ${OpenCV_LIBS}) - target_include_directories(QrDec PUBLIC $ - "$") - install(TARGETS QrDec - EXPORT ${PROJECT_NAME}-config - DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT QrDec - ) - install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/Esterv - COMPONENT ${PROJECT_NAME} - ) + target_include_directories( + QrDec PUBLIC $ + "$") + install( + TARGETS QrDec + EXPORT ${PROJECT_NAME}-config + COMPONENT EstervQrDec + ARCHIVE COMPONENT ${PROJECT_NAME}-dev) + install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT ${PROJECT_NAME}-dev) else() - message(STATUS "The QRCODE decoding library will not be built") + message(STATUS "The QRCODE decoding library will not be built") endif(OpenCV_FOUND) diff --git a/QrDec/README.md b/QrDec/README.md index 1d91ca7..1fd8661 100644 --- a/QrDec/README.md +++ b/QrDec/README.md @@ -1,4 +1,4 @@ -# QrDec +# Esterv.Utils.QrDec [TOC] @@ -7,17 +7,18 @@ The detection and decoding is performed by [OpenCV](https://opencv.org/) librari In case OpenCV is not found on your system CMake will download pre compiled libraries from [my action releases](https://github.com/EddyTheCo/install-OpenCV-action). ## Adding the libraries to your CMake project + ``` include(FetchContent) FetchContent_Declare( - qrCode - GIT_REPOSITORY https://github.com/EddyTheCo/qrCode.git + EstervQrCode + GIT_REPOSITORY https://github.com/EddyTheCo/Esterv.Utils.QrCode.git GIT_TAG vMAJOR.MINOR.PATCH - FIND_PACKAGE_ARGS MAJOR.MINOR COMPONENTS QrDec CONFIG + FIND_PACKAGE_ARGS MAJOR.MINOR COMPONENTS QrDec CONFIG ) -FetchContent_MakeAvailable(qrCode) +FetchContent_MakeAvailable(EstervQrCode) -target_link_libraries( qrCode::QrDec) +target_link_libraries( Esterv::QrDec) ``` @@ -26,3 +27,4 @@ target_link_libraries( qrCode::QrDec) + diff --git a/QrDec/include/esterv/utils/qrcode_dec.hpp b/QrDec/include/esterv/utils/qrcode_dec.hpp new file mode 100644 index 0000000..fe6bd7f --- /dev/null +++ b/QrDec/include/esterv/utils/qrcode_dec.hpp @@ -0,0 +1,12 @@ +#pragma once +#include +#include + +namespace Esterv::Utils::QrDec { +class QRDecoder : public cv::QRCodeDetectorAruco { + +public: + QRDecoder(){}; + std::string decode_grey(unsigned char *img, int rows, int cols); +}; +} // namespace Esterv::Utils::QrDec diff --git a/QrDec/include/qrcodedec.hpp b/QrDec/include/qrcodedec.hpp deleted file mode 100644 index b775d24..0000000 --- a/QrDec/include/qrcodedec.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include -#include - -class QRDecoder : public cv::QRCodeDetectorAruco -{ - -public: - QRDecoder(){}; - std::string decode_grey(unsigned char* img, int rows,int cols); -}; - diff --git a/QrDec/qrcodedec.cpp b/QrDec/qrcodedec.cpp deleted file mode 100644 index dc0d16b..0000000 --- a/QrDec/qrcodedec.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - -std::string QRDecoder::decode_grey(unsigned char* img,int rows ,int cols) -{ - cv::Mat greyImg = cv::Mat(rows,cols, CV_8UC1, img); - const auto str=detectAndDecode(greyImg); - if(str!="") - return str; - cv::bitwise_not(greyImg,greyImg); - - return detectAndDecode(greyImg); -} - - - diff --git a/QrDec/src/qrcode_dec.cpp b/QrDec/src/qrcode_dec.cpp new file mode 100644 index 0000000..33ea8b2 --- /dev/null +++ b/QrDec/src/qrcode_dec.cpp @@ -0,0 +1,15 @@ +#include + +namespace Esterv::Utils::QrDec { + +std::string QRDecoder::decode_grey(unsigned char *img, int rows, int cols) { + cv::Mat greyImg = cv::Mat(rows, cols, CV_8UC1, img); + const auto str = detectAndDecode(greyImg); + if (str != "") + return str; + cv::bitwise_not(greyImg, greyImg); + + return detectAndDecode(greyImg); +} + +} // namespace Esterv::Utils::QrDec diff --git a/QrGen/CMakeLists.txt b/QrGen/CMakeLists.txt index 3bfadff..ec2f9c7 100755 --- a/QrGen/CMakeLists.txt +++ b/QrGen/CMakeLists.txt @@ -1,20 +1,21 @@ -add_library(QrGen src/qrcodegen.cpp src/utils.cpp) -add_library(${PROJECT_NAME}::QrGen ALIAS QrGen) +add_library(QrGen src/qrcode_gen.cpp src/utils.cpp) +add_library(Esterv::QrGen ALIAS QrGen) set_target_properties(QrGen PROPERTIES POSITION_INDEPENDENT_CODE ON) -set_target_properties(QrGen PROPERTIES VERSION ${VERSION} SOVERSION ${VERSION_MAJOR}) +set_target_properties(QrGen PROPERTIES VERSION ${VERSION} SOVERSION + ${VERSION_MAJOR}) -target_include_directories(QrGen PUBLIC $ - "$") - -install(TARGETS QrGen - EXPORT ${PROJECT_NAME}-config - DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT QrGen - ) -install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/Esterv - COMPONENT ${PROJECT_NAME} - ) +target_include_directories( + QrGen PUBLIC $ + "$") +install( + TARGETS QrGen + EXPORT ${PROJECT_NAME}-config + COMPONENT EstervQrGen + ARCHIVE COMPONENT ${PROJECT_NAME}-dev) +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT ${PROJECT_NAME}-dev) diff --git a/QrGen/README.md b/QrGen/README.md index 4844de8..9d00a33 100644 --- a/QrGen/README.md +++ b/QrGen/README.md @@ -1,21 +1,22 @@ -# QrGen +# Esterv.Utils.QrGen [TOC] The code is based on [QR Code generator library](https://github.com/nayuki/QR-Code-generator) and produce a library for the generation of a QR code of certain data. -## Adding the libraries to your CMake project +## Adding the libraries to your CMake project + ``` include(FetchContent) FetchContent_Declare( - qrCode - GIT_REPOSITORY https://github.com/EddyTheCo/qrCode.git + EstervQrCode + GIT_REPOSITORY https://github.com/EddyTheCo/Esterv.Utils.QrCode.git GIT_TAG vMAJOR.MINOR.PATCH - FIND_PACKAGE_ARGS MAJOR.MINOR COMPONENTS QrGen CONFIG + FIND_PACKAGE_ARGS MAJOR.MINOR COMPONENTS QrGen CONFIG ) -FetchContent_MakeAvailable(qrCode) +FetchContent_MakeAvailable(EstervQrCode) -target_link_libraries( qrCode::QrGen) +target_link_libraries( Esterv::QrGen) ``` diff --git a/QrGen/include/esterv/utils/qrcode_gen.hpp b/QrGen/include/esterv/utils/qrcode_gen.hpp new file mode 100644 index 0000000..a54087d --- /dev/null +++ b/QrGen/include/esterv/utils/qrcode_gen.hpp @@ -0,0 +1,616 @@ +/* + * QR Code generator library (C++) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising + * from, out of or in connection with the Software or the use or other dealings + * in the Software. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Esterv::Utils::QrGen { + +/* + * A segment of character/binary/control data in a QR Code symbol. + * Instances of this class are immutable. + * The mid-level way to create a segment is to take the payload data + * and call a static factory function such as QrSegment::makeNumeric(). + * The low-level way to create a segment is to custom-make the bit buffer + * and call the QrSegment() constructor with appropriate values. + * This segment class imposes no length restrictions, but QR Codes have + * restrictions. Even in the most favorable conditions, a QR Code can only hold + * 7089 characters of data. Any segment longer than this is meaningless for the + * purpose of generating QR Codes. + */ + +class QrSegment final { + + /*---- Public helper enumeration ----*/ + + /* + * Describes how a segment's data bits are interpreted. Immutable. + */ +public: + class Mode final { + + /*-- Constants --*/ + + public: + static const Mode NUMERIC; + + public: + static const Mode ALPHANUMERIC; + + public: + static const Mode BYTE; + + public: + static const Mode KANJI; + + public: + static const Mode ECI; + + /*-- Fields --*/ + + // The mode indicator bits, which is a uint4 value (range 0 to 15). + private: + int modeBits; + + // Number of character count bits for three different version ranges. + private: + int numBitsCharCount[3]; + + /*-- Constructor --*/ + + private: + Mode(int mode, int cc0, int cc1, int cc2); + + /*-- Methods --*/ + + /* + * (Package-private) Returns the mode indicator bits, which is an unsigned + * 4-bit value (range 0 to 15). + */ + public: + int getModeBits() const; + + /* + * (Package-private) Returns the bit width of the character count field for + * a segment in this mode in a QR Code at the given version number. The + * result is in the range [0, 16]. + */ + public: + int numCharCountBits(int ver) const; + }; + + /*---- Static factory functions (mid level) ----*/ + + /* + * Returns a segment representing the given binary data encoded in + * byte mode. All input byte vectors are acceptable. Any text string + * can be converted to UTF-8 bytes and encoded as a byte mode segment. + */ +public: + static QrSegment makeBytes(const std::vector &data); + + /* + * Returns a segment representing the given string of decimal digits encoded + * in numeric mode. + */ +public: + static QrSegment makeNumeric(const char *digits); + + /* + * Returns a segment representing the given text string encoded in + * alphanumeric mode. The characters allowed are: 0 to 9, A to Z (uppercase + * only), space, dollar, percent, asterisk, plus, hyphen, period, slash, + * colon. + */ +public: + static QrSegment makeAlphanumeric(const char *text); + + /* + * Returns a list of zero or more segments to represent the given text string. + * The result may use various segment modes and switch modes to optimize the + * length of the bit stream. + */ +public: + static std::vector makeSegments(const char *text); + + /* + * Returns a segment representing an Extended Channel Interpretation + * (ECI) designator with the given assignment value. + */ +public: + static QrSegment makeEci(long assignVal); + + /*---- Public static helper functions ----*/ + + /* + * Tests whether the given string can be encoded as a segment in numeric mode. + * A string is encodable iff each character is in the range 0 to 9. + */ +public: + static bool isNumeric(const char *text); + + /* + * Tests whether the given string can be encoded as a segment in alphanumeric + * mode. A string is encodable iff each character is in the following set: 0 + * to 9, A to Z (uppercase only), space, dollar, percent, asterisk, plus, + * hyphen, period, slash, colon. + */ +public: + static bool isAlphanumeric(const char *text); + + /*---- Instance fields ----*/ + + /* The mode indicator of this segment. Accessed through getMode(). */ +private: + const Mode *mode; + + /* The length of this segment's unencoded data. Measured in characters for + * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + * Always zero or positive. Not the same as the data's bit length. + * Accessed through getNumChars(). */ +private: + int numChars; + + /* The data bits of this segment. Accessed through getData(). */ +private: + std::vector data; + + /*---- Constructors (low level) ----*/ + + /* + * Creates a new QR Code segment with the given attributes and data. + * The character count (numCh) must agree with the mode and the bit buffer + * length, but the constraint isn't checked. The given bit buffer is copied + * and stored. + */ +public: + QrSegment(const Mode &md, int numCh, const std::vector &dt); + + /* + * Creates a new QR Code segment with the given parameters and data. + * The character count (numCh) must agree with the mode and the bit buffer + * length, but the constraint isn't checked. The given bit buffer is moved and + * stored. + */ +public: + QrSegment(const Mode &md, int numCh, std::vector &&dt); + + /*---- Methods ----*/ + + /* + * Returns the mode field of this segment. + */ +public: + const Mode &getMode() const; + + /* + * Returns the character count field of this segment. + */ +public: + int getNumChars() const; + + /* + * Returns the data bits of this segment. + */ +public: + const std::vector &getData() const; + + // (Package-private) Calculates the number of bits needed to encode the given + // segments at the given version. Returns a non-negative number if successful. + // Otherwise returns -1 if a segment has too many characters to fit its length + // field, or the total bits exceeds INT_MAX. +public: + static int getTotalBits(const std::vector &segs, int version); + + /*---- Private constant ----*/ + + /* The set of all legal characters in alphanumeric mode, where + * each character value maps to the index in the string. */ +private: + static const char *ALPHANUMERIC_CHARSET; +}; + +/* + * A QR Code symbol, which is a type of two-dimension barcode. + * Invented by Denso Wave and described in the ISO/IEC 18004 standard. + * Instances of this class represent an immutable square grid of dark and light + * cells. The class provides static factory functions to create a QR Code from + * text or binary data. The class covers the QR Code Model 2 specification, + * supporting all versions (sizes) from 1 to 40, all 4 error correction levels, + * and 4 character encoding modes. + * + * Ways to create a QR Code object: + * - High level: Take the payload data and call QrCode::encodeText() or + * QrCode::encodeBinary(). + * - Mid level: Custom-make the list of segments and call + * QrCode::encodeSegments(). + * - Low level: Custom-make the array of data codeword bytes (including + * segment headers and final padding, excluding error correction codewords), + * supply the appropriate version number, and call the QrCode() constructor. + * (Note that all ways require supplying the desired error correction level.) + */ +class QrCode final { + + /*---- Public helper enumeration ----*/ + + /* + * The error correction level in a QR Code symbol. + */ +public: + enum class Ecc { + LOW = 0, // The QR Code can tolerate about 7% erroneous codewords + MEDIUM, // The QR Code can tolerate about 15% erroneous codewords + QUARTILE, // The QR Code can tolerate about 25% erroneous codewords + HIGH, // The QR Code can tolerate about 30% erroneous codewords + }; + + // Returns a value in the range 0 to 3 (unsigned 2-bit integer). +private: + static int getFormatBits(Ecc ecl); + + /*---- Static factory functions (high level) ----*/ + + /* + * Returns a QR Code representing the given Unicode text string at the given + * error correction level. As a conservative upper bound, this function is + * guaranteed to succeed for strings that have 2953 or fewer UTF-8 code units + * (not Unicode code points) if the low error correction level is used. The + * smallest possible QR Code version is automatically chosen for the output. + * The ECC level of the result may be higher than the ecl argument if it can + * be done without increasing the version. + */ +public: + static QrCode encodeText(const char *text, Ecc ecl); + + /* + * Returns a QR Code representing the given binary data at the given error + * correction level. This function always encodes using the binary segment + * mode, not any text mode. The maximum number of bytes allowed is 2953. The + * smallest possible QR Code version is automatically chosen for the output. + * The ECC level of the result may be higher than the ecl argument if it can + * be done without increasing the version. + */ +public: + static QrCode encodeBinary(const std::vector &data, Ecc ecl); + + /*---- Static factory functions (mid level) ----*/ + + /* + * Returns a QR Code representing the given segments with the given encoding + * parameters. The smallest possible QR Code version within the given range is + * automatically chosen for the output. Iff boostEcl is true, then the ECC + * level of the result may be higher than the ecl argument if it can be done + * without increasing the version. The mask number is either between 0 to 7 + * (inclusive) to force that mask, or -1 to automatically choose an + * appropriate mask (which may be slow). This function allows the user to + * create a custom sequence of segments that switches between modes (such as + * alphanumeric and byte) to encode text in less space. This is a mid-level + * API; the high-level API is encodeText() and encodeBinary(). + */ +public: + static QrCode encodeSegments(const std::vector &segs, Ecc ecl, + int minVersion = 1, int maxVersion = 40, + int mask = -1, + bool boostEcl = true); // All optional parameters + + /*---- Instance fields ----*/ + + // Immutable scalar parameters: + + /* The version number of this QR Code, which is between 1 and 40 (inclusive). + * This determines the size of this barcode. */ +private: + int version; + + /* The width and height of this QR Code, measured in modules, between + * 21 and 177 (inclusive). This is equal to version * 4 + 17. */ +private: + int size; + + /* The error correction level used in this QR Code. */ +private: + Ecc errorCorrectionLevel; + + /* The index of the mask pattern used in this QR Code, which is between 0 and + * 7 (inclusive). Even if a QR Code is created with automatic masking + * requested (mask = -1), the resulting object still has a mask value between + * 0 and 7. */ +private: + int mask; + + // Private grids of modules/pixels, with dimensions of size*size: + + // The modules of this QR Code (false = light, true = dark). + // Immutable after constructor finishes. Accessed through getModule(). +private: + std::vector> modules; + + // Indicates function modules that are not subjected to masking. Discarded + // when constructor finishes. +private: + std::vector> isFunction; + + /*---- Constructor (low level) ----*/ + + /* + * Creates a new QR Code with the given version number, + * error correction level, data codeword bytes, and mask number. + * This is a low-level API that most users should not use directly. + * A mid-level API is the encodeSegments() function. + */ +public: + QrCode(int ver, Ecc ecl, const std::vector &dataCodewords, + int msk); + + /*---- Public instance methods ----*/ + + /* + * Returns this QR Code's version, in the range [1, 40]. + */ +public: + int getVersion() const; + + /* + * Returns this QR Code's size, in the range [21, 177]. + */ +public: + int getSize() const; + + /* + * Returns this QR Code's error correction level. + */ +public: + Ecc getErrorCorrectionLevel() const; + + /* + * Returns this QR Code's mask, in the range [0, 7]. + */ +public: + int getMask() const; + + /* + * Returns the color of the module (pixel) at the given coordinates, which is + * false for light or true for dark. The top left corner has the coordinates + * (x=0, y=0). If the given coordinates are out of bounds, then false (light) + * is returned. + */ +public: + bool getModule(int x, int y) const; + + /*---- Private helper methods for constructor: Drawing function modules ----*/ + + // Reads this object's version field, and draws and marks all function + // modules. +private: + void drawFunctionPatterns(); + + // Draws two copies of the format bits (with its own error correction code) + // based on the given mask and this object's error correction level field. +private: + void drawFormatBits(int msk); + + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field, iff 7 <= version <= 40. +private: + void drawVersion(); + + // Draws a 9*9 finder pattern including the border separator, + // with the center module at (x, y). Modules can be out of bounds. +private: + void drawFinderPattern(int x, int y); + + // Draws a 5*5 alignment pattern, with the center module + // at (x, y). All modules must be in bounds. +private: + void drawAlignmentPattern(int x, int y); + + // Sets the color of a module and marks it as a function module. + // Only used by the constructor. Coordinates must be in bounds. +private: + void setFunctionModule(int x, int y, bool isDark); + + // Returns the color of the module at the given coordinates, which must be in + // range. +private: + bool module(int x, int y) const; + + /*---- Private helper methods for constructor: Codewords and masking ----*/ + + // Returns a new byte string representing the given data with the appropriate + // error correction codewords appended to it, based on this object's version + // and error correction level. +private: + std::vector + addEccAndInterleave(const std::vector &data) const; + + // Draws the given sequence of 8-bit codewords (data and error correction) + // onto the entire data area of this QR Code. Function modules need to be + // marked off before this is called. +private: + void drawCodewords(const std::vector &data); + + // XORs the codeword modules in this QR Code with the given mask pattern. + // The function modules must be marked and the codeword bits must be drawn + // before masking. Due to the arithmetic of XOR, calling applyMask() with + // the same mask value a second time will undo the mask. A final well-formed + // QR Code needs exactly one (not zero, two, etc.) mask applied. +private: + void applyMask(int msk); + + // Calculates and returns the penalty score based on state of this QR Code's + // current modules. This is used by the automatic mask choice algorithm to + // find the mask pattern that yields the lowest score. +private: + long getPenaltyScore() const; + + /*---- Private helper functions ----*/ + + // Returns an ascending list of positions of alignment patterns for this + // version number. Each position is in the range [0,177), and are used on both + // the x and y axes. This could be implemented as lookup table of 40 + // variable-length lists of unsigned bytes. +private: + std::vector getAlignmentPatternPositions() const; + + // Returns the number of data bits that can be stored in a QR Code of the + // given version number, after all function modules are excluded. This + // includes remainder bits, so it might not be a multiple of 8. The result is + // in the range [208, 29648]. This could be implemented as a 40-entry lookup + // table. +private: + static int getNumRawDataModules(int ver); + + // Returns the number of 8-bit data (i.e. not error correction) codewords + // contained in any QR Code of the given version number and error correction + // level, with remainder bits discarded. This stateless pure function could be + // implemented as a (40*4)-cell lookup table. +private: + static int getNumDataCodewords(int ver, Ecc ecl); + + // Returns a Reed-Solomon ECC generator polynomial for the given degree. This + // could be implemented as a lookup table over all possible parameter values, + // instead of as an algorithm. +private: + static std::vector reedSolomonComputeDivisor(int degree); + + // Returns the Reed-Solomon error correction codeword for the given data and + // divisor polynomials. +private: + static std::vector + reedSolomonComputeRemainder(const std::vector &data, + const std::vector &divisor); + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). + // All inputs are valid. This could be implemented as a 256*256 lookup table. +private: + static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y); + + // Can only be called immediately after a light run is added, and + // returns either 0, 1, or 2. A helper function for getPenaltyScore(). +private: + int finderPenaltyCountPatterns(const std::array &runHistory) const; + + // Must be called at the end of a line (row or column) of modules. A helper + // function for getPenaltyScore(). +private: + int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, + std::array &runHistory) const; + + // Pushes the given value to the front and drops the last value. A helper + // function for getPenaltyScore(). +private: + void finderPenaltyAddHistory(int currentRunLength, + std::array &runHistory) const; + + // Returns true iff the i'th bit of x is set to 1. +private: + static bool getBit(long x, int i); + + /*---- Constants and tables ----*/ + + // The minimum version number supported in the QR Code Model 2 standard. +public: + static constexpr int MIN_VERSION = 1; + + // The maximum version number supported in the QR Code Model 2 standard. +public: + static constexpr int MAX_VERSION = 40; + + // For use in getPenaltyScore(), when evaluating which mask is best. +private: + static const int PENALTY_N1; + +private: + static const int PENALTY_N2; + +private: + static const int PENALTY_N3; + +private: + static const int PENALTY_N4; + +private: + static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41]; + +private: + static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; +}; + +/*---- Public exception class ----*/ + +/* + * Thrown when the supplied data does not fit any QR Code version. Ways to + * handle this exception include: + * - Decrease the error correction level if it was greater than Ecc::LOW. + * - If the encodeSegments() function was called with a maxVersion argument, + * then increase it if it was less than QrCode::MAX_VERSION. (This advice does + * not apply to the other factory functions because they search all versions up + * to QrCode::MAX_VERSION.) + * - Split the text data into better or optimal segments in order to reduce the + * number of bits required. + * - Change the text or binary data to be shorter. + * - Change the text to fit the character set of a particular segment mode (e.g. + * alphanumeric). + * - Propagate the error upward to the caller/user. + */ +class data_too_long : public std::length_error { + +public: + explicit data_too_long(const std::string &msg); +}; + +/* + * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment. + */ +class BitBuffer final : public std::vector { + + /*---- Constructor ----*/ + + // Creates an empty bit buffer (length 0). +public: + BitBuffer(); + + /*---- Method ----*/ + + // Appends the given number of low-order bits of the given value + // to this buffer. Requires 0 <= len <= 31 and val < 2^len. +public: + void appendBits(std::uint32_t val, int len); +}; +/*---- utils functions ----*/ + +/* + * @param the qr code object, the border, and the fill color. + * @return the svg string of the qr. + * + */ +std::string toSvgString(const QrCode &qr, std::string fill); + +} // namespace Esterv::Utils::QrGen diff --git a/QrGen/include/qrcodegen.hpp b/QrGen/include/qrcodegen.hpp deleted file mode 100644 index 55e5c05..0000000 --- a/QrGen/include/qrcodegen.hpp +++ /dev/null @@ -1,560 +0,0 @@ -/* - * QR Code generator library (C++) - * - * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/page/qr-code-generator-library - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising from, - * out of or in connection with the Software or the use or other dealings in the - * Software. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace qrcodegen { - - /* - * A segment of character/binary/control data in a QR Code symbol. - * Instances of this class are immutable. - * The mid-level way to create a segment is to take the payload data - * and call a static factory function such as QrSegment::makeNumeric(). - * The low-level way to create a segment is to custom-make the bit buffer - * and call the QrSegment() constructor with appropriate values. - * This segment class imposes no length restrictions, but QR Codes have restrictions. - * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. - * Any segment longer than this is meaningless for the purpose of generating QR Codes. - */ - - class QrSegment final { - - /*---- Public helper enumeration ----*/ - - /* - * Describes how a segment's data bits are interpreted. Immutable. - */ - public: class Mode final { - - /*-- Constants --*/ - - public: static const Mode NUMERIC; - public: static const Mode ALPHANUMERIC; - public: static const Mode BYTE; - public: static const Mode KANJI; - public: static const Mode ECI; - - - /*-- Fields --*/ - - // The mode indicator bits, which is a uint4 value (range 0 to 15). - private: int modeBits; - - // Number of character count bits for three different version ranges. - private: int numBitsCharCount[3]; - - - /*-- Constructor --*/ - - private: Mode(int mode, int cc0, int cc1, int cc2); - - - /*-- Methods --*/ - - /* - * (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15). - */ - public: int getModeBits() const; - - /* - * (Package-private) Returns the bit width of the character count field for a segment in - * this mode in a QR Code at the given version number. The result is in the range [0, 16]. - */ - public: int numCharCountBits(int ver) const; - - }; - - - - /*---- Static factory functions (mid level) ----*/ - - /* - * Returns a segment representing the given binary data encoded in - * byte mode. All input byte vectors are acceptable. Any text string - * can be converted to UTF-8 bytes and encoded as a byte mode segment. - */ - public: static QrSegment makeBytes(const std::vector &data); - - - /* - * Returns a segment representing the given string of decimal digits encoded in numeric mode. - */ - public: static QrSegment makeNumeric(const char *digits); - - - /* - * Returns a segment representing the given text string encoded in alphanumeric mode. - * The characters allowed are: 0 to 9, A to Z (uppercase only), space, - * dollar, percent, asterisk, plus, hyphen, period, slash, colon. - */ - public: static QrSegment makeAlphanumeric(const char *text); - - - /* - * Returns a list of zero or more segments to represent the given text string. The result - * may use various segment modes and switch modes to optimize the length of the bit stream. - */ - public: static std::vector makeSegments(const char *text); - - - /* - * Returns a segment representing an Extended Channel Interpretation - * (ECI) designator with the given assignment value. - */ - public: static QrSegment makeEci(long assignVal); - - - /*---- Public static helper functions ----*/ - - /* - * Tests whether the given string can be encoded as a segment in numeric mode. - * A string is encodable iff each character is in the range 0 to 9. - */ - public: static bool isNumeric(const char *text); - - - /* - * Tests whether the given string can be encoded as a segment in alphanumeric mode. - * A string is encodable iff each character is in the following set: 0 to 9, A to Z - * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. - */ - public: static bool isAlphanumeric(const char *text); - - - - /*---- Instance fields ----*/ - - /* The mode indicator of this segment. Accessed through getMode(). */ - private: const Mode *mode; - - /* The length of this segment's unencoded data. Measured in characters for - * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. - * Always zero or positive. Not the same as the data's bit length. - * Accessed through getNumChars(). */ - private: int numChars; - - /* The data bits of this segment. Accessed through getData(). */ - private: std::vector data; - - - /*---- Constructors (low level) ----*/ - - /* - * Creates a new QR Code segment with the given attributes and data. - * The character count (numCh) must agree with the mode and the bit buffer length, - * but the constraint isn't checked. The given bit buffer is copied and stored. - */ - public: QrSegment(const Mode &md, int numCh, const std::vector &dt); - - - /* - * Creates a new QR Code segment with the given parameters and data. - * The character count (numCh) must agree with the mode and the bit buffer length, - * but the constraint isn't checked. The given bit buffer is moved and stored. - */ - public: QrSegment(const Mode &md, int numCh, std::vector &&dt); - - - /*---- Methods ----*/ - - /* - * Returns the mode field of this segment. - */ - public: const Mode &getMode() const; - - - /* - * Returns the character count field of this segment. - */ - public: int getNumChars() const; - - - /* - * Returns the data bits of this segment. - */ - public: const std::vector &getData() const; - - - // (Package-private) Calculates the number of bits needed to encode the given segments at - // the given version. Returns a non-negative number if successful. Otherwise returns -1 if a - // segment has too many characters to fit its length field, or the total bits exceeds INT_MAX. - public: static int getTotalBits(const std::vector &segs, int version); - - - /*---- Private constant ----*/ - - /* The set of all legal characters in alphanumeric mode, where - * each character value maps to the index in the string. */ - private: static const char *ALPHANUMERIC_CHARSET; - - }; - - - - /* - * A QR Code symbol, which is a type of two-dimension barcode. - * Invented by Denso Wave and described in the ISO/IEC 18004 standard. - * Instances of this class represent an immutable square grid of dark and light cells. - * The class provides static factory functions to create a QR Code from text or binary data. - * The class covers the QR Code Model 2 specification, supporting all versions (sizes) - * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. - * - * Ways to create a QR Code object: - * - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary(). - * - Mid level: Custom-make the list of segments and call QrCode::encodeSegments(). - * - Low level: Custom-make the array of data codeword bytes (including - * segment headers and final padding, excluding error correction codewords), - * supply the appropriate version number, and call the QrCode() constructor. - * (Note that all ways require supplying the desired error correction level.) - */ - class QrCode final { - - /*---- Public helper enumeration ----*/ - - /* - * The error correction level in a QR Code symbol. - */ - public: enum class Ecc { - LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords - MEDIUM , // The QR Code can tolerate about 15% erroneous codewords - QUARTILE, // The QR Code can tolerate about 25% erroneous codewords - HIGH , // The QR Code can tolerate about 30% erroneous codewords - }; - - - // Returns a value in the range 0 to 3 (unsigned 2-bit integer). - private: static int getFormatBits(Ecc ecl); - - - - /*---- Static factory functions (high level) ----*/ - - /* - * Returns a QR Code representing the given Unicode text string at the given error correction level. - * As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer - * UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible - * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than - * the ecl argument if it can be done without increasing the version. - */ - public: static QrCode encodeText(const char *text, Ecc ecl); - - - /* - * Returns a QR Code representing the given binary data at the given error correction level. - * This function always encodes using the binary segment mode, not any text mode. The maximum number of - * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. - * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. - */ - public: static QrCode encodeBinary(const std::vector &data, Ecc ecl); - - - /*---- Static factory functions (mid level) ----*/ - - /* - * Returns a QR Code representing the given segments with the given encoding parameters. - * The smallest possible QR Code version within the given range is automatically - * chosen for the output. Iff boostEcl is true, then the ECC level of the result - * may be higher than the ecl argument if it can be done without increasing the - * version. The mask number is either between 0 to 7 (inclusive) to force that - * mask, or -1 to automatically choose an appropriate mask (which may be slow). - * This function allows the user to create a custom sequence of segments that switches - * between modes (such as alphanumeric and byte) to encode text in less space. - * This is a mid-level API; the high-level API is encodeText() and encodeBinary(). - */ - public: static QrCode encodeSegments(const std::vector &segs, Ecc ecl, - int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters - - - - /*---- Instance fields ----*/ - - // Immutable scalar parameters: - - /* The version number of this QR Code, which is between 1 and 40 (inclusive). - * This determines the size of this barcode. */ - private: int version; - - /* The width and height of this QR Code, measured in modules, between - * 21 and 177 (inclusive). This is equal to version * 4 + 17. */ - private: int size; - - /* The error correction level used in this QR Code. */ - private: Ecc errorCorrectionLevel; - - /* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). - * Even if a QR Code is created with automatic masking requested (mask = -1), - * the resulting object still has a mask value between 0 and 7. */ - private: int mask; - - // Private grids of modules/pixels, with dimensions of size*size: - - // The modules of this QR Code (false = light, true = dark). - // Immutable after constructor finishes. Accessed through getModule(). - private: std::vector > modules; - - // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. - private: std::vector > isFunction; - - - - /*---- Constructor (low level) ----*/ - - /* - * Creates a new QR Code with the given version number, - * error correction level, data codeword bytes, and mask number. - * This is a low-level API that most users should not use directly. - * A mid-level API is the encodeSegments() function. - */ - public: QrCode(int ver, Ecc ecl, const std::vector &dataCodewords, int msk); - - - - /*---- Public instance methods ----*/ - - /* - * Returns this QR Code's version, in the range [1, 40]. - */ - public: int getVersion() const; - - - /* - * Returns this QR Code's size, in the range [21, 177]. - */ - public: int getSize() const; - - - /* - * Returns this QR Code's error correction level. - */ - public: Ecc getErrorCorrectionLevel() const; - - - /* - * Returns this QR Code's mask, in the range [0, 7]. - */ - public: int getMask() const; - - - /* - * Returns the color of the module (pixel) at the given coordinates, which is false - * for light or true for dark. The top left corner has the coordinates (x=0, y=0). - * If the given coordinates are out of bounds, then false (light) is returned. - */ - public: bool getModule(int x, int y) const; - - - - /*---- Private helper methods for constructor: Drawing function modules ----*/ - - // Reads this object's version field, and draws and marks all function modules. - private: void drawFunctionPatterns(); - - - // Draws two copies of the format bits (with its own error correction code) - // based on the given mask and this object's error correction level field. - private: void drawFormatBits(int msk); - - - // Draws two copies of the version bits (with its own error correction code), - // based on this object's version field, iff 7 <= version <= 40. - private: void drawVersion(); - - - // Draws a 9*9 finder pattern including the border separator, - // with the center module at (x, y). Modules can be out of bounds. - private: void drawFinderPattern(int x, int y); - - - // Draws a 5*5 alignment pattern, with the center module - // at (x, y). All modules must be in bounds. - private: void drawAlignmentPattern(int x, int y); - - - // Sets the color of a module and marks it as a function module. - // Only used by the constructor. Coordinates must be in bounds. - private: void setFunctionModule(int x, int y, bool isDark); - - - // Returns the color of the module at the given coordinates, which must be in range. - private: bool module(int x, int y) const; - - - /*---- Private helper methods for constructor: Codewords and masking ----*/ - - // Returns a new byte string representing the given data with the appropriate error correction - // codewords appended to it, based on this object's version and error correction level. - private: std::vector addEccAndInterleave(const std::vector &data) const; - - - // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire - // data area of this QR Code. Function modules need to be marked off before this is called. - private: void drawCodewords(const std::vector &data); - - - // XORs the codeword modules in this QR Code with the given mask pattern. - // The function modules must be marked and the codeword bits must be drawn - // before masking. Due to the arithmetic of XOR, calling applyMask() with - // the same mask value a second time will undo the mask. A final well-formed - // QR Code needs exactly one (not zero, two, etc.) mask applied. - private: void applyMask(int msk); - - - // Calculates and returns the penalty score based on state of this QR Code's current modules. - // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. - private: long getPenaltyScore() const; - - - - /*---- Private helper functions ----*/ - - // Returns an ascending list of positions of alignment patterns for this version number. - // Each position is in the range [0,177), and are used on both the x and y axes. - // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. - private: std::vector getAlignmentPatternPositions() const; - - - // Returns the number of data bits that can be stored in a QR Code of the given version number, after - // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. - // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. - private: static int getNumRawDataModules(int ver); - - - // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any - // QR Code of the given version number and error correction level, with remainder bits discarded. - // This stateless pure function could be implemented as a (40*4)-cell lookup table. - private: static int getNumDataCodewords(int ver, Ecc ecl); - - - // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be - // implemented as a lookup table over all possible parameter values, instead of as an algorithm. - private: static std::vector reedSolomonComputeDivisor(int degree); - - - // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. - private: static std::vector reedSolomonComputeRemainder(const std::vector &data, const std::vector &divisor); - - - // Returns the product of the two given field elements modulo GF(2^8/0x11D). - // All inputs are valid. This could be implemented as a 256*256 lookup table. - private: static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y); - - - // Can only be called immediately after a light run is added, and - // returns either 0, 1, or 2. A helper function for getPenaltyScore(). - private: int finderPenaltyCountPatterns(const std::array &runHistory) const; - - - // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). - private: int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const; - - - // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). - private: void finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const; - - - // Returns true iff the i'th bit of x is set to 1. - private: static bool getBit(long x, int i); - - - /*---- Constants and tables ----*/ - - // The minimum version number supported in the QR Code Model 2 standard. - public: static constexpr int MIN_VERSION = 1; - - // The maximum version number supported in the QR Code Model 2 standard. - public: static constexpr int MAX_VERSION = 40; - - - // For use in getPenaltyScore(), when evaluating which mask is best. - private: static const int PENALTY_N1; - private: static const int PENALTY_N2; - private: static const int PENALTY_N3; - private: static const int PENALTY_N4; - - - private: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41]; - private: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; - - }; - - - - /*---- Public exception class ----*/ - - /* - * Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include: - * - Decrease the error correction level if it was greater than Ecc::LOW. - * - If the encodeSegments() function was called with a maxVersion argument, then increase - * it if it was less than QrCode::MAX_VERSION. (This advice does not apply to the other - * factory functions because they search all versions up to QrCode::MAX_VERSION.) - * - Split the text data into better or optimal segments in order to reduce the number of bits required. - * - Change the text or binary data to be shorter. - * - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). - * - Propagate the error upward to the caller/user. - */ - class data_too_long : public std::length_error { - - public: explicit data_too_long(const std::string &msg); - - }; - - - - /* - * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment. - */ - class BitBuffer final : public std::vector { - - /*---- Constructor ----*/ - - // Creates an empty bit buffer (length 0). - public: BitBuffer(); - - - - /*---- Method ----*/ - - // Appends the given number of low-order bits of the given value - // to this buffer. Requires 0 <= len <= 31 and val < 2^len. - public: void appendBits(std::uint32_t val, int len); - - }; - /*---- utils functions ----*/ - - /* - * @param the qr code object, the border, and the fill color. - * @return the svg string of the qr. - * - */ - std::string toSvgString(const QrCode &qr, std::string fill); - - -} diff --git a/QrGen/src/qrcode_gen.cpp b/QrGen/src/qrcode_gen.cpp new file mode 100644 index 0000000..a531ffd --- /dev/null +++ b/QrGen/src/qrcode_gen.cpp @@ -0,0 +1,844 @@ +/* + * QR Code generator library (C++) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising + * from, out of or in connection with the Software or the use or other dealings + * in the Software. + */ + +#include "esterv/utils/qrcode_gen.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using std::int8_t; +using std::size_t; +using std::uint8_t; +using std::vector; + +namespace Esterv::Utils::QrGen { + +/*---- Class QrSegment ----*/ + +QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) : modeBits(mode) { + numBitsCharCount[0] = cc0; + numBitsCharCount[1] = cc1; + numBitsCharCount[2] = cc2; +} + +int QrSegment::Mode::getModeBits() const { return modeBits; } + +int QrSegment::Mode::numCharCountBits(int ver) const { + return numBitsCharCount[(ver + 7) / 17]; +} + +const QrSegment::Mode QrSegment::Mode::NUMERIC(0x1, 10, 12, 14); +const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13); +const QrSegment::Mode QrSegment::Mode::BYTE(0x4, 8, 16, 16); +const QrSegment::Mode QrSegment::Mode::KANJI(0x8, 8, 10, 12); +const QrSegment::Mode QrSegment::Mode::ECI(0x7, 0, 0, 0); + +QrSegment QrSegment::makeBytes(const vector &data) { + if (data.size() > static_cast(INT_MAX)) + throw std::length_error("Data too long"); + BitBuffer bb; + for (uint8_t b : data) + bb.appendBits(b, 8); + return QrSegment(Mode::BYTE, static_cast(data.size()), std::move(bb)); +} + +QrSegment QrSegment::makeNumeric(const char *digits) { + BitBuffer bb; + int accumData = 0; + int accumCount = 0; + int charCount = 0; + for (; *digits != '\0'; digits++, charCount++) { + char c = *digits; + if (c < '0' || c > '9') + throw std::domain_error("String contains non-numeric characters"); + accumData = accumData * 10 + (c - '0'); + accumCount++; + if (accumCount == 3) { + bb.appendBits(static_cast(accumData), 10); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 or 2 digits remaining + bb.appendBits(static_cast(accumData), accumCount * 3 + 1); + return QrSegment(Mode::NUMERIC, charCount, std::move(bb)); +} + +QrSegment QrSegment::makeAlphanumeric(const char *text) { + BitBuffer bb; + int accumData = 0; + int accumCount = 0; + int charCount = 0; + for (; *text != '\0'; text++, charCount++) { + const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text); + if (temp == nullptr) + throw std::domain_error( + "String contains unencodable characters in alphanumeric mode"); + accumData = accumData * 45 + static_cast(temp - ALPHANUMERIC_CHARSET); + accumCount++; + if (accumCount == 2) { + bb.appendBits(static_cast(accumData), 11); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 character remaining + bb.appendBits(static_cast(accumData), 6); + return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb)); +} + +vector QrSegment::makeSegments(const char *text) { + // Select the most efficient segment encoding automatically + vector result; + if (*text == '\0') + ; // Leave result empty + else if (isNumeric(text)) + result.push_back(makeNumeric(text)); + else if (isAlphanumeric(text)) + result.push_back(makeAlphanumeric(text)); + else { + vector bytes; + for (; *text != '\0'; text++) + bytes.push_back(static_cast(*text)); + result.push_back(makeBytes(bytes)); + } + return result; +} + +QrSegment QrSegment::makeEci(long assignVal) { + BitBuffer bb; + if (assignVal < 0) + throw std::domain_error("ECI assignment value out of range"); + else if (assignVal < (1 << 7)) + bb.appendBits(static_cast(assignVal), 8); + else if (assignVal < (1 << 14)) { + bb.appendBits(2, 2); + bb.appendBits(static_cast(assignVal), 14); + } else if (assignVal < 1000000L) { + bb.appendBits(6, 3); + bb.appendBits(static_cast(assignVal), 21); + } else + throw std::domain_error("ECI assignment value out of range"); + return QrSegment(Mode::ECI, 0, std::move(bb)); +} + +QrSegment::QrSegment(const Mode &md, int numCh, const std::vector &dt) + : mode(&md), numChars(numCh), data(dt) { + if (numCh < 0) + throw std::domain_error("Invalid value"); +} + +QrSegment::QrSegment(const Mode &md, int numCh, std::vector &&dt) + : mode(&md), numChars(numCh), data(std::move(dt)) { + if (numCh < 0) + throw std::domain_error("Invalid value"); +} + +int QrSegment::getTotalBits(const vector &segs, int version) { + int result = 0; + for (const QrSegment &seg : segs) { + int ccbits = seg.mode->numCharCountBits(version); + if (seg.numChars >= (1L << ccbits)) + return -1; // The segment's length doesn't fit the field's bit width + if (4 + ccbits > INT_MAX - result) + return -1; // The sum will overflow an int type + result += 4 + ccbits; + if (seg.data.size() > static_cast(INT_MAX - result)) + return -1; // The sum will overflow an int type + result += static_cast(seg.data.size()); + } + return result; +} + +bool QrSegment::isNumeric(const char *text) { + for (; *text != '\0'; text++) { + char c = *text; + if (c < '0' || c > '9') + return false; + } + return true; +} + +bool QrSegment::isAlphanumeric(const char *text) { + for (; *text != '\0'; text++) { + if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr) + return false; + } + return true; +} + +const QrSegment::Mode &QrSegment::getMode() const { return *mode; } + +int QrSegment::getNumChars() const { return numChars; } + +const std::vector &QrSegment::getData() const { return data; } + +const char *QrSegment::ALPHANUMERIC_CHARSET = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + +/*---- Class QrCode ----*/ + +int QrCode::getFormatBits(Ecc ecl) { + switch (ecl) { + case Ecc::LOW: + return 1; + case Ecc::MEDIUM: + return 0; + case Ecc::QUARTILE: + return 3; + case Ecc::HIGH: + return 2; + default: + throw std::logic_error("Unreachable"); + } +} + +QrCode QrCode::encodeText(const char *text, Ecc ecl) { + + vector segs = QrSegment::makeSegments(text); + return encodeSegments(segs, ecl); +} + +QrCode QrCode::encodeBinary(const vector &data, Ecc ecl) { + vector segs{QrSegment::makeBytes(data)}; + return encodeSegments(segs, ecl); +} + +QrCode QrCode::encodeSegments(const vector &segs, Ecc ecl, + int minVersion, int maxVersion, int mask, + bool boostEcl) { + if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && + maxVersion <= MAX_VERSION) || + mask < -1 || mask > 7) + throw std::invalid_argument("Invalid value"); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion;; version++) { + int dataCapacityBits = + getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = QrSegment::getTotalBits(segs, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= + maxVersion) { // All versions in the range could not fit the given data + std::ostringstream sb; + if (dataUsedBits == -1) + sb << "Segment too long"; + else { + sb << "Data length = " << dataUsedBits << " bits, "; + sb << "Max capacity = " << dataCapacityBits << " bits"; + } + throw data_too_long(sb.str()); + } + } + assert(dataUsedBits != -1); + + // Increase the error correction level while the data still fits in the + // current version number + for (Ecc newEcl : + {Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) + ecl = newEcl; + } + + // Concatenate all segments to create the data bit string + BitBuffer bb; + for (const QrSegment &seg : segs) { + bb.appendBits(static_cast(seg.getMode().getModeBits()), 4); + bb.appendBits(static_cast(seg.getNumChars()), + seg.getMode().numCharCountBits(version)); + bb.insert(bb.end(), seg.getData().begin(), seg.getData().end()); + } + assert(bb.size() == static_cast(dataUsedBits)); + + // Add terminator and pad up to a byte if applicable + size_t dataCapacityBits = + static_cast(getNumDataCodewords(version, ecl)) * 8; + assert(bb.size() <= dataCapacityBits); + bb.appendBits(0, std::min(4, static_cast(dataCapacityBits - bb.size()))); + bb.appendBits(0, (8 - static_cast(bb.size() % 8)) % 8); + assert(bb.size() % 8 == 0); + + // Pad with alternating bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; + padByte ^= 0xEC ^ 0x11) + bb.appendBits(padByte, 8); + + // Pack bits into bytes in big endian + vector dataCodewords(bb.size() / 8); + for (size_t i = 0; i < bb.size(); i++) + dataCodewords.at(i >> 3) |= (bb.at(i) ? 1 : 0) << (7 - (i & 7)); + + // Create the QR Code object + return QrCode(version, ecl, dataCodewords, mask); +} + +QrCode::QrCode(int ver, Ecc ecl, const vector &dataCodewords, int msk) + : // Initialize fields and check arguments + version(ver), errorCorrectionLevel(ecl) { + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw std::domain_error("Version value out of range"); + if (msk < -1 || msk > 7) + throw std::domain_error("Mask value out of range"); + size = ver * 4 + 17; + size_t sz = static_cast(size); + modules = vector>(sz, vector(sz)); // Initially all light + isFunction = vector>(sz, vector(sz)); + + // Compute ECC, draw modules + drawFunctionPatterns(); + const vector allCodewords = addEccAndInterleave(dataCodewords); + drawCodewords(allCodewords); + + // Do masking + if (msk == -1) { // Automatically choose best mask + long minPenalty = LONG_MAX; + for (int i = 0; i < 8; i++) { + applyMask(i); + drawFormatBits(i); + long penalty = getPenaltyScore(); + if (penalty < minPenalty) { + msk = i; + minPenalty = penalty; + } + applyMask(i); // Undoes the mask due to XOR + } + } + assert(0 <= msk && msk <= 7); + mask = msk; + applyMask(msk); // Apply the final choice of mask + drawFormatBits(msk); // Overwrite old format bits + + isFunction.clear(); + isFunction.shrink_to_fit(); +} + +int QrCode::getVersion() const { return version; } + +int QrCode::getSize() const { return size; } + +QrCode::Ecc QrCode::getErrorCorrectionLevel() const { + return errorCorrectionLevel; +} + +int QrCode::getMask() const { return mask; } + +bool QrCode::getModule(int x, int y) const { + return 0 <= x && x < size && 0 <= y && y < size && module(x, y); +} + +void QrCode::drawFunctionPatterns() { + // Draw horizontal and vertical timing patterns + for (int i = 0; i < size; i++) { + setFunctionModule(6, i, i % 2 == 0); + setFunctionModule(i, 6, i % 2 == 0); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some + // timing modules) + drawFinderPattern(3, 3); + drawFinderPattern(size - 4, 3); + drawFinderPattern(3, size - 4); + + // Draw numerous alignment patterns + const vector alignPatPos = getAlignmentPatternPositions(); + size_t numAlign = alignPatPos.size(); + for (size_t i = 0; i < numAlign; i++) { + for (size_t j = 0; j < numAlign; j++) { + // Don't draw on the three finder corners + if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || + (i == numAlign - 1 && j == 0))) + drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j)); + } + } + + // Draw configuration data + drawFormatBits(0); // Dummy mask value; overwritten later in the constructor + drawVersion(); +} + +void QrCode::drawFormatBits(int msk) { + // Calculate error correction code and pack bits + int data = getFormatBits(errorCorrectionLevel) << 3 | + msk; // errCorrLvl is uint2, msk is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + int bits = (data << 10 | rem) ^ 0x5412; // uint15 + assert(bits >> 15 == 0); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setFunctionModule(8, i, getBit(bits, i)); + setFunctionModule(8, 7, getBit(bits, 6)); + setFunctionModule(8, 8, getBit(bits, 7)); + setFunctionModule(7, 8, getBit(bits, 8)); + for (int i = 9; i < 15; i++) + setFunctionModule(14 - i, 8, getBit(bits, i)); + + // Draw second copy + for (int i = 0; i < 8; i++) + setFunctionModule(size - 1 - i, 8, getBit(bits, i)); + for (int i = 8; i < 15; i++) + setFunctionModule(8, size - 15 + i, getBit(bits, i)); + setFunctionModule(8, size - 8, true); // Always dark +} + +void QrCode::drawVersion() { + if (version < 7) + return; + + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + long bits = static_cast(version) << 12 | rem; // uint18 + assert(bits >> 18 == 0); + + // Draw two copies + for (int i = 0; i < 18; i++) { + bool bit = getBit(bits, i); + int a = size - 11 + i % 3; + int b = i / 3; + setFunctionModule(a, b, bit); + setFunctionModule(b, a, bit); + } +} + +void QrCode::drawFinderPattern(int x, int y) { + for (int dy = -4; dy <= 4; dy++) { + for (int dx = -4; dx <= 4; dx++) { + int dist = + std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm + int xx = x + dx, yy = y + dy; + if (0 <= xx && xx < size && 0 <= yy && yy < size) + setFunctionModule(xx, yy, dist != 2 && dist != 4); + } + } +} + +void QrCode::drawAlignmentPattern(int x, int y) { + for (int dy = -2; dy <= 2; dy++) { + for (int dx = -2; dx <= 2; dx++) + setFunctionModule(x + dx, y + dy, + std::max(std::abs(dx), std::abs(dy)) != 1); + } +} + +void QrCode::setFunctionModule(int x, int y, bool isDark) { + size_t ux = static_cast(x); + size_t uy = static_cast(y); + modules.at(uy).at(ux) = isDark; + isFunction.at(uy).at(ux) = true; +} + +bool QrCode::module(int x, int y) const { + return modules.at(static_cast(y)).at(static_cast(x)); +} + +vector QrCode::addEccAndInterleave(const vector &data) const { + if (data.size() != static_cast( + getNumDataCodewords(version, errorCorrectionLevel))) + throw std::invalid_argument("Invalid argument"); + + // Calculate parameter numbers + int numBlocks = + NUM_ERROR_CORRECTION_BLOCKS[static_cast(errorCorrectionLevel)] + [version]; + int blockEccLen = + ECC_CODEWORDS_PER_BLOCK[static_cast(errorCorrectionLevel)][version]; + int rawCodewords = getNumRawDataModules(version) / 8; + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockLen = rawCodewords / numBlocks; + + // Split data into blocks and append ECC to each block + vector> blocks; + const vector rsDiv = reedSolomonComputeDivisor(blockEccLen); + for (int i = 0, k = 0; i < numBlocks; i++) { + vector dat(data.cbegin() + k, + data.cbegin() + (k + shortBlockLen - blockEccLen + + (i < numShortBlocks ? 0 : 1))); + k += static_cast(dat.size()); + const vector ecc = reedSolomonComputeRemainder(dat, rsDiv); + if (i < numShortBlocks) + dat.push_back(0); + dat.insert(dat.end(), ecc.cbegin(), ecc.cend()); + blocks.push_back(std::move(dat)); + } + + // Interleave (not concatenate) the bytes from every block into a single + // sequence + vector result; + for (size_t i = 0; i < blocks.at(0).size(); i++) { + for (size_t j = 0; j < blocks.size(); j++) { + // Skip the padding byte in short blocks + if (i != static_cast(shortBlockLen - blockEccLen) || + j >= static_cast(numShortBlocks)) + result.push_back(blocks.at(j).at(i)); + } + } + assert(result.size() == static_cast(rawCodewords)); + return result; +} + +void QrCode::drawCodewords(const vector &data) { + if (data.size() != + static_cast(getNumRawDataModules(version) / 8)) + throw std::invalid_argument("Invalid argument"); + + size_t i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = size - 1; right >= 1; + right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < size; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + size_t x = static_cast(right - j); // Actual x coordinate + bool upward = ((right + 1) & 2) == 0; + size_t y = static_cast(upward ? size - 1 - vert + : vert); // Actual y coordinate + if (!isFunction.at(y).at(x) && i < data.size() * 8) { + modules.at(y).at(x) = + getBit(data.at(i >> 3), 7 - static_cast(i & 7)); + i++; + } + // If this QR Code has any remainder bits (0 to 7), they were assigned + // as 0/false/light by the constructor and are left unchanged by this + // method + } + } + } + assert(i == data.size() * 8); +} + +void QrCode::applyMask(int msk) { + if (msk < 0 || msk > 7) + throw std::domain_error("Mask value out of range"); + size_t sz = static_cast(size); + for (size_t y = 0; y < sz; y++) { + for (size_t x = 0; x < sz; x++) { + bool invert; + switch (msk) { + case 0: + invert = (x + y) % 2 == 0; + break; + case 1: + invert = y % 2 == 0; + break; + case 2: + invert = x % 3 == 0; + break; + case 3: + invert = (x + y) % 3 == 0; + break; + case 4: + invert = (x / 3 + y / 2) % 2 == 0; + break; + case 5: + invert = x * y % 2 + x * y % 3 == 0; + break; + case 6: + invert = (x * y % 2 + x * y % 3) % 2 == 0; + break; + case 7: + invert = ((x + y) % 2 + x * y % 3) % 2 == 0; + break; + default: + throw std::logic_error("Unreachable"); + } + modules.at(y).at(x) = + modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x)); + } + } +} + +long QrCode::getPenaltyScore() const { + long result = 0; + + // Adjacent modules in row having same color, and finder-like patterns + for (int y = 0; y < size; y++) { + bool runColor = false; + int runX = 0; + std::array runHistory = {}; + for (int x = 0; x < size; x++) { + if (module(x, y) == runColor) { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } else { + finderPenaltyAddHistory(runX, runHistory); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; + runColor = module(x, y); + runX = 1; + } + } + result += + finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3; + } + // Adjacent modules in column having same color, and finder-like patterns + for (int x = 0; x < size; x++) { + bool runColor = false; + int runY = 0; + std::array runHistory = {}; + for (int y = 0; y < size; y++) { + if (module(x, y) == runColor) { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } else { + finderPenaltyAddHistory(runY, runHistory); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; + runColor = module(x, y); + runY = 1; + } + } + result += + finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < size - 1; y++) { + for (int x = 0; x < size - 1; x++) { + bool color = module(x, y); + if (color == module(x + 1, y) && color == module(x, y + 1) && + color == module(x + 1, y + 1)) + result += PENALTY_N2; + } + } + + // Balance of dark and light modules + int dark = 0; + for (const vector &row : modules) { + for (bool color : row) { + if (color) + dark++; + } + } + int total = size * size; // Note that size is odd, so dark/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= + // (55+5k)% + int k = static_cast((std::abs(dark * 20L - total * 10L) + total - 1) / + total) - + 1; + assert(0 <= k && k <= 9); + result += k * PENALTY_N4; + assert(0 <= result && + result <= 2568888L); // Non-tight upper bound based on default values + // of PENALTY_N1, ..., N4 + return result; +} + +vector QrCode::getAlignmentPatternPositions() const { + if (version == 1) + return vector(); + else { + int numAlign = version / 7 + 2; + int step = (version == 32) + ? 26 + : (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; + vector result; + for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step) + result.insert(result.begin(), pos); + result.insert(result.begin(), 6); + return result; + } +} + +int QrCode::getNumRawDataModules(int ver) { + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw std::domain_error("Version number out of range"); + int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 36; + } + assert(208 <= result && result <= 29648); + return result; +} + +int QrCode::getNumDataCodewords(int ver, Ecc ecl) { + return getNumRawDataModules(ver) / 8 - + ECC_CODEWORDS_PER_BLOCK[static_cast(ecl)][ver] * + NUM_ERROR_CORRECTION_BLOCKS[static_cast(ecl)][ver]; +} + +vector QrCode::reedSolomonComputeDivisor(int degree) { + if (degree < 1 || degree > 255) + throw std::domain_error("Degree out of range"); + // Polynomial coefficients are stored from highest to lowest power, excluding + // the leading term which is always 1. For example the polynomial x^3 + 255x^2 + // + 8x + 93 is stored as the uint8 array {255, 8, 93}. + vector result(static_cast(degree)); + result.at(result.size() - 1) = 1; // Start off with the monomial x^0 + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x + // - r^{degree-1}), and drop the highest monomial term which is always + // 1x^degree. Note that r = 0x02, which is a generator element of this field + // GF(2^8/0x11D). + uint8_t root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (size_t j = 0; j < result.size(); j++) { + result.at(j) = reedSolomonMultiply(result.at(j), root); + if (j + 1 < result.size()) + result.at(j) ^= result.at(j + 1); + } + root = reedSolomonMultiply(root, 0x02); + } + return result; +} + +vector +QrCode::reedSolomonComputeRemainder(const vector &data, + const vector &divisor) { + vector result(divisor.size()); + for (uint8_t b : data) { // Polynomial division + uint8_t factor = b ^ result.at(0); + result.erase(result.begin()); + result.push_back(0); + for (size_t i = 0; i < result.size(); i++) + result.at(i) ^= reedSolomonMultiply(divisor.at(i), factor); + } + return result; +} + +uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) { + // Russian peasant multiplication + int z = 0; + for (int i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + assert(z >> 8 == 0); + return static_cast(z); +} + +int QrCode::finderPenaltyCountPatterns( + const std::array &runHistory) const { + int n = runHistory.at(1); + assert(n <= size * 3); + bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && + runHistory.at(4) == n && runHistory.at(5) == n; + return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0) + + (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0); +} + +int QrCode::finderPenaltyTerminateAndCount( + bool currentRunColor, int currentRunLength, + std::array &runHistory) const { + if (currentRunColor) { // Terminate dark run + finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += size; // Add light border to final run + finderPenaltyAddHistory(currentRunLength, runHistory); + return finderPenaltyCountPatterns(runHistory); +} + +void QrCode::finderPenaltyAddHistory(int currentRunLength, + std::array &runHistory) const { + if (runHistory.at(0) == 0) + currentRunLength += size; // Add light border to initial run + std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, + runHistory.end()); + runHistory.at(0) = currentRunLength; +} + +bool QrCode::getBit(long x, int i) { return ((x >> i) & 1) != 0; } + +/*---- Tables of constants ----*/ + +const int QrCode::PENALTY_N1 = 3; +const int QrCode::PENALTY_N2 = 3; +const int QrCode::PENALTY_N3 = 40; +const int QrCode::PENALTY_N4 = 10; + +const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal + // value) + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + // 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + // 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, + 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, + 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, + 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, + 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High +}; + +const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal + // value) + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + // 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + // 39, 40 Error correction level + {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, + 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, + 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low + {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, + 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, + 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium + {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, + 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, + 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile + {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, + 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, + 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High +}; + +data_too_long::data_too_long(const std::string &msg) : std::length_error(msg) {} + +/*---- Class BitBuffer ----*/ + +BitBuffer::BitBuffer() : std::vector() {} + +void BitBuffer::appendBits(std::uint32_t val, int len) { + if (len < 0 || len > 31 || val >> len != 0) + throw std::domain_error("Value out of range"); + for (int i = len - 1; i >= 0; i--) // Append bit by bit + this->push_back(((val >> i) & 1) != 0); +} + +} // namespace Esterv::Utils::QrGen diff --git a/QrGen/src/qrcodegen.cpp b/QrGen/src/qrcodegen.cpp deleted file mode 100644 index db0acdc..0000000 --- a/QrGen/src/qrcodegen.cpp +++ /dev/null @@ -1,831 +0,0 @@ -/* - * QR Code generator library (C++) - * - * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/page/qr-code-generator-library - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising from, - * out of or in connection with the Software or the use or other dealings in the - * Software. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "qrcodegen.hpp" - -using std::int8_t; -using std::uint8_t; -using std::size_t; -using std::vector; - - -namespace qrcodegen { - -/*---- Class QrSegment ----*/ - -QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) : - modeBits(mode) { - numBitsCharCount[0] = cc0; - numBitsCharCount[1] = cc1; - numBitsCharCount[2] = cc2; -} - - -int QrSegment::Mode::getModeBits() const { - return modeBits; -} - - -int QrSegment::Mode::numCharCountBits(int ver) const { - return numBitsCharCount[(ver + 7) / 17]; -} - - -const QrSegment::Mode QrSegment::Mode::NUMERIC (0x1, 10, 12, 14); -const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13); -const QrSegment::Mode QrSegment::Mode::BYTE (0x4, 8, 16, 16); -const QrSegment::Mode QrSegment::Mode::KANJI (0x8, 8, 10, 12); -const QrSegment::Mode QrSegment::Mode::ECI (0x7, 0, 0, 0); - - -QrSegment QrSegment::makeBytes(const vector &data) { - if (data.size() > static_cast(INT_MAX)) - throw std::length_error("Data too long"); - BitBuffer bb; - for (uint8_t b : data) - bb.appendBits(b, 8); - return QrSegment(Mode::BYTE, static_cast(data.size()), std::move(bb)); -} - - -QrSegment QrSegment::makeNumeric(const char *digits) { - BitBuffer bb; - int accumData = 0; - int accumCount = 0; - int charCount = 0; - for (; *digits != '\0'; digits++, charCount++) { - char c = *digits; - if (c < '0' || c > '9') - throw std::domain_error("String contains non-numeric characters"); - accumData = accumData * 10 + (c - '0'); - accumCount++; - if (accumCount == 3) { - bb.appendBits(static_cast(accumData), 10); - accumData = 0; - accumCount = 0; - } - } - if (accumCount > 0) // 1 or 2 digits remaining - bb.appendBits(static_cast(accumData), accumCount * 3 + 1); - return QrSegment(Mode::NUMERIC, charCount, std::move(bb)); -} - - -QrSegment QrSegment::makeAlphanumeric(const char *text) { - BitBuffer bb; - int accumData = 0; - int accumCount = 0; - int charCount = 0; - for (; *text != '\0'; text++, charCount++) { - const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text); - if (temp == nullptr) - throw std::domain_error("String contains unencodable characters in alphanumeric mode"); - accumData = accumData * 45 + static_cast(temp - ALPHANUMERIC_CHARSET); - accumCount++; - if (accumCount == 2) { - bb.appendBits(static_cast(accumData), 11); - accumData = 0; - accumCount = 0; - } - } - if (accumCount > 0) // 1 character remaining - bb.appendBits(static_cast(accumData), 6); - return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb)); -} - - -vector QrSegment::makeSegments(const char *text) { - // Select the most efficient segment encoding automatically - vector result; - if (*text == '\0'); // Leave result empty - else if (isNumeric(text)) - result.push_back(makeNumeric(text)); - else if (isAlphanumeric(text)) - result.push_back(makeAlphanumeric(text)); - else { - vector bytes; - for (; *text != '\0'; text++) - bytes.push_back(static_cast(*text)); - result.push_back(makeBytes(bytes)); - } - return result; -} - - -QrSegment QrSegment::makeEci(long assignVal) { - BitBuffer bb; - if (assignVal < 0) - throw std::domain_error("ECI assignment value out of range"); - else if (assignVal < (1 << 7)) - bb.appendBits(static_cast(assignVal), 8); - else if (assignVal < (1 << 14)) { - bb.appendBits(2, 2); - bb.appendBits(static_cast(assignVal), 14); - } else if (assignVal < 1000000L) { - bb.appendBits(6, 3); - bb.appendBits(static_cast(assignVal), 21); - } else - throw std::domain_error("ECI assignment value out of range"); - return QrSegment(Mode::ECI, 0, std::move(bb)); -} - - -QrSegment::QrSegment(const Mode &md, int numCh, const std::vector &dt) : - mode(&md), - numChars(numCh), - data(dt) { - if (numCh < 0) - throw std::domain_error("Invalid value"); -} - - -QrSegment::QrSegment(const Mode &md, int numCh, std::vector &&dt) : - mode(&md), - numChars(numCh), - data(std::move(dt)) { - if (numCh < 0) - throw std::domain_error("Invalid value"); -} - - -int QrSegment::getTotalBits(const vector &segs, int version) { - int result = 0; - for (const QrSegment &seg : segs) { - int ccbits = seg.mode->numCharCountBits(version); - if (seg.numChars >= (1L << ccbits)) - return -1; // The segment's length doesn't fit the field's bit width - if (4 + ccbits > INT_MAX - result) - return -1; // The sum will overflow an int type - result += 4 + ccbits; - if (seg.data.size() > static_cast(INT_MAX - result)) - return -1; // The sum will overflow an int type - result += static_cast(seg.data.size()); - } - return result; -} - - -bool QrSegment::isNumeric(const char *text) { - for (; *text != '\0'; text++) { - char c = *text; - if (c < '0' || c > '9') - return false; - } - return true; -} - - -bool QrSegment::isAlphanumeric(const char *text) { - for (; *text != '\0'; text++) { - if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr) - return false; - } - return true; -} - - -const QrSegment::Mode &QrSegment::getMode() const { - return *mode; -} - - -int QrSegment::getNumChars() const { - return numChars; -} - - -const std::vector &QrSegment::getData() const { - return data; -} - - -const char *QrSegment::ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; - - - -/*---- Class QrCode ----*/ - -int QrCode::getFormatBits(Ecc ecl) { - switch (ecl) { - case Ecc::LOW : return 1; - case Ecc::MEDIUM : return 0; - case Ecc::QUARTILE: return 3; - case Ecc::HIGH : return 2; - default: throw std::logic_error("Unreachable"); - } -} - - -QrCode QrCode::encodeText(const char *text, Ecc ecl) { - - vector segs = QrSegment::makeSegments(text); - return encodeSegments(segs, ecl); -} - - -QrCode QrCode::encodeBinary(const vector &data, Ecc ecl) { - vector segs{QrSegment::makeBytes(data)}; - return encodeSegments(segs, ecl); -} - - -QrCode QrCode::encodeSegments(const vector &segs, Ecc ecl, - int minVersion, int maxVersion, int mask, bool boostEcl) { - if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) - throw std::invalid_argument("Invalid value"); - - // Find the minimal version number to use - int version, dataUsedBits; - for (version = minVersion; ; version++) { - int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available - dataUsedBits = QrSegment::getTotalBits(segs, version); - if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) - break; // This version number is found to be suitable - if (version >= maxVersion) { // All versions in the range could not fit the given data - std::ostringstream sb; - if (dataUsedBits == -1) - sb << "Segment too long"; - else { - sb << "Data length = " << dataUsedBits << " bits, "; - sb << "Max capacity = " << dataCapacityBits << " bits"; - } - throw data_too_long(sb.str()); - } - } - assert(dataUsedBits != -1); - - // Increase the error correction level while the data still fits in the current version number - for (Ecc newEcl : {Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high - if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) - ecl = newEcl; - } - - // Concatenate all segments to create the data bit string - BitBuffer bb; - for (const QrSegment &seg : segs) { - bb.appendBits(static_cast(seg.getMode().getModeBits()), 4); - bb.appendBits(static_cast(seg.getNumChars()), seg.getMode().numCharCountBits(version)); - bb.insert(bb.end(), seg.getData().begin(), seg.getData().end()); - } - assert(bb.size() == static_cast(dataUsedBits)); - - // Add terminator and pad up to a byte if applicable - size_t dataCapacityBits = static_cast(getNumDataCodewords(version, ecl)) * 8; - assert(bb.size() <= dataCapacityBits); - bb.appendBits(0, std::min(4, static_cast(dataCapacityBits - bb.size()))); - bb.appendBits(0, (8 - static_cast(bb.size() % 8)) % 8); - assert(bb.size() % 8 == 0); - - // Pad with alternating bytes until data capacity is reached - for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) - bb.appendBits(padByte, 8); - - // Pack bits into bytes in big endian - vector dataCodewords(bb.size() / 8); - for (size_t i = 0; i < bb.size(); i++) - dataCodewords.at(i >> 3) |= (bb.at(i) ? 1 : 0) << (7 - (i & 7)); - - // Create the QR Code object - return QrCode(version, ecl, dataCodewords, mask); -} - - -QrCode::QrCode(int ver, Ecc ecl, const vector &dataCodewords, int msk) : - // Initialize fields and check arguments - version(ver), - errorCorrectionLevel(ecl) { - if (ver < MIN_VERSION || ver > MAX_VERSION) - throw std::domain_error("Version value out of range"); - if (msk < -1 || msk > 7) - throw std::domain_error("Mask value out of range"); - size = ver * 4 + 17; - size_t sz = static_cast(size); - modules = vector >(sz, vector(sz)); // Initially all light - isFunction = vector >(sz, vector(sz)); - - // Compute ECC, draw modules - drawFunctionPatterns(); - const vector allCodewords = addEccAndInterleave(dataCodewords); - drawCodewords(allCodewords); - - // Do masking - if (msk == -1) { // Automatically choose best mask - long minPenalty = LONG_MAX; - for (int i = 0; i < 8; i++) { - applyMask(i); - drawFormatBits(i); - long penalty = getPenaltyScore(); - if (penalty < minPenalty) { - msk = i; - minPenalty = penalty; - } - applyMask(i); // Undoes the mask due to XOR - } - } - assert(0 <= msk && msk <= 7); - mask = msk; - applyMask(msk); // Apply the final choice of mask - drawFormatBits(msk); // Overwrite old format bits - - isFunction.clear(); - isFunction.shrink_to_fit(); -} - - -int QrCode::getVersion() const { - return version; -} - - -int QrCode::getSize() const { - return size; -} - - -QrCode::Ecc QrCode::getErrorCorrectionLevel() const { - return errorCorrectionLevel; -} - - -int QrCode::getMask() const { - return mask; -} - - -bool QrCode::getModule(int x, int y) const { - return 0 <= x && x < size && 0 <= y && y < size && module(x, y); -} - - -void QrCode::drawFunctionPatterns() { - // Draw horizontal and vertical timing patterns - for (int i = 0; i < size; i++) { - setFunctionModule(6, i, i % 2 == 0); - setFunctionModule(i, 6, i % 2 == 0); - } - - // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) - drawFinderPattern(3, 3); - drawFinderPattern(size - 4, 3); - drawFinderPattern(3, size - 4); - - // Draw numerous alignment patterns - const vector alignPatPos = getAlignmentPatternPositions(); - size_t numAlign = alignPatPos.size(); - for (size_t i = 0; i < numAlign; i++) { - for (size_t j = 0; j < numAlign; j++) { - // Don't draw on the three finder corners - if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) - drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j)); - } - } - - // Draw configuration data - drawFormatBits(0); // Dummy mask value; overwritten later in the constructor - drawVersion(); -} - - -void QrCode::drawFormatBits(int msk) { - // Calculate error correction code and pack bits - int data = getFormatBits(errorCorrectionLevel) << 3 | msk; // errCorrLvl is uint2, msk is uint3 - int rem = data; - for (int i = 0; i < 10; i++) - rem = (rem << 1) ^ ((rem >> 9) * 0x537); - int bits = (data << 10 | rem) ^ 0x5412; // uint15 - assert(bits >> 15 == 0); - - // Draw first copy - for (int i = 0; i <= 5; i++) - setFunctionModule(8, i, getBit(bits, i)); - setFunctionModule(8, 7, getBit(bits, 6)); - setFunctionModule(8, 8, getBit(bits, 7)); - setFunctionModule(7, 8, getBit(bits, 8)); - for (int i = 9; i < 15; i++) - setFunctionModule(14 - i, 8, getBit(bits, i)); - - // Draw second copy - for (int i = 0; i < 8; i++) - setFunctionModule(size - 1 - i, 8, getBit(bits, i)); - for (int i = 8; i < 15; i++) - setFunctionModule(8, size - 15 + i, getBit(bits, i)); - setFunctionModule(8, size - 8, true); // Always dark -} - - -void QrCode::drawVersion() { - if (version < 7) - return; - - // Calculate error correction code and pack bits - int rem = version; // version is uint6, in the range [7, 40] - for (int i = 0; i < 12; i++) - rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); - long bits = static_cast(version) << 12 | rem; // uint18 - assert(bits >> 18 == 0); - - // Draw two copies - for (int i = 0; i < 18; i++) { - bool bit = getBit(bits, i); - int a = size - 11 + i % 3; - int b = i / 3; - setFunctionModule(a, b, bit); - setFunctionModule(b, a, bit); - } -} - - -void QrCode::drawFinderPattern(int x, int y) { - for (int dy = -4; dy <= 4; dy++) { - for (int dx = -4; dx <= 4; dx++) { - int dist = std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm - int xx = x + dx, yy = y + dy; - if (0 <= xx && xx < size && 0 <= yy && yy < size) - setFunctionModule(xx, yy, dist != 2 && dist != 4); - } - } -} - - -void QrCode::drawAlignmentPattern(int x, int y) { - for (int dy = -2; dy <= 2; dy++) { - for (int dx = -2; dx <= 2; dx++) - setFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1); - } -} - - -void QrCode::setFunctionModule(int x, int y, bool isDark) { - size_t ux = static_cast(x); - size_t uy = static_cast(y); - modules .at(uy).at(ux) = isDark; - isFunction.at(uy).at(ux) = true; -} - - -bool QrCode::module(int x, int y) const { - return modules.at(static_cast(y)).at(static_cast(x)); -} - - -vector QrCode::addEccAndInterleave(const vector &data) const { - if (data.size() != static_cast(getNumDataCodewords(version, errorCorrectionLevel))) - throw std::invalid_argument("Invalid argument"); - - // Calculate parameter numbers - int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast(errorCorrectionLevel)][version]; - int blockEccLen = ECC_CODEWORDS_PER_BLOCK [static_cast(errorCorrectionLevel)][version]; - int rawCodewords = getNumRawDataModules(version) / 8; - int numShortBlocks = numBlocks - rawCodewords % numBlocks; - int shortBlockLen = rawCodewords / numBlocks; - - // Split data into blocks and append ECC to each block - vector > blocks; - const vector rsDiv = reedSolomonComputeDivisor(blockEccLen); - for (int i = 0, k = 0; i < numBlocks; i++) { - vector dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1))); - k += static_cast(dat.size()); - const vector ecc = reedSolomonComputeRemainder(dat, rsDiv); - if (i < numShortBlocks) - dat.push_back(0); - dat.insert(dat.end(), ecc.cbegin(), ecc.cend()); - blocks.push_back(std::move(dat)); - } - - // Interleave (not concatenate) the bytes from every block into a single sequence - vector result; - for (size_t i = 0; i < blocks.at(0).size(); i++) { - for (size_t j = 0; j < blocks.size(); j++) { - // Skip the padding byte in short blocks - if (i != static_cast(shortBlockLen - blockEccLen) || j >= static_cast(numShortBlocks)) - result.push_back(blocks.at(j).at(i)); - } - } - assert(result.size() == static_cast(rawCodewords)); - return result; -} - - -void QrCode::drawCodewords(const vector &data) { - if (data.size() != static_cast(getNumRawDataModules(version) / 8)) - throw std::invalid_argument("Invalid argument"); - - size_t i = 0; // Bit index into the data - // Do the funny zigzag scan - for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair - if (right == 6) - right = 5; - for (int vert = 0; vert < size; vert++) { // Vertical counter - for (int j = 0; j < 2; j++) { - size_t x = static_cast(right - j); // Actual x coordinate - bool upward = ((right + 1) & 2) == 0; - size_t y = static_cast(upward ? size - 1 - vert : vert); // Actual y coordinate - if (!isFunction.at(y).at(x) && i < data.size() * 8) { - modules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast(i & 7)); - i++; - } - // If this QR Code has any remainder bits (0 to 7), they were assigned as - // 0/false/light by the constructor and are left unchanged by this method - } - } - } - assert(i == data.size() * 8); -} - - -void QrCode::applyMask(int msk) { - if (msk < 0 || msk > 7) - throw std::domain_error("Mask value out of range"); - size_t sz = static_cast(size); - for (size_t y = 0; y < sz; y++) { - for (size_t x = 0; x < sz; x++) { - bool invert; - switch (msk) { - case 0: invert = (x + y) % 2 == 0; break; - case 1: invert = y % 2 == 0; break; - case 2: invert = x % 3 == 0; break; - case 3: invert = (x + y) % 3 == 0; break; - case 4: invert = (x / 3 + y / 2) % 2 == 0; break; - case 5: invert = x * y % 2 + x * y % 3 == 0; break; - case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; - case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; - default: throw std::logic_error("Unreachable"); - } - modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x)); - } - } -} - - -long QrCode::getPenaltyScore() const { - long result = 0; - - // Adjacent modules in row having same color, and finder-like patterns - for (int y = 0; y < size; y++) { - bool runColor = false; - int runX = 0; - std::array runHistory = {}; - for (int x = 0; x < size; x++) { - if (module(x, y) == runColor) { - runX++; - if (runX == 5) - result += PENALTY_N1; - else if (runX > 5) - result++; - } else { - finderPenaltyAddHistory(runX, runHistory); - if (!runColor) - result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; - runColor = module(x, y); - runX = 1; - } - } - result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3; - } - // Adjacent modules in column having same color, and finder-like patterns - for (int x = 0; x < size; x++) { - bool runColor = false; - int runY = 0; - std::array runHistory = {}; - for (int y = 0; y < size; y++) { - if (module(x, y) == runColor) { - runY++; - if (runY == 5) - result += PENALTY_N1; - else if (runY > 5) - result++; - } else { - finderPenaltyAddHistory(runY, runHistory); - if (!runColor) - result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; - runColor = module(x, y); - runY = 1; - } - } - result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; - } - - // 2*2 blocks of modules having same color - for (int y = 0; y < size - 1; y++) { - for (int x = 0; x < size - 1; x++) { - bool color = module(x, y); - if ( color == module(x + 1, y) && - color == module(x, y + 1) && - color == module(x + 1, y + 1)) - result += PENALTY_N2; - } - } - - // Balance of dark and light modules - int dark = 0; - for (const vector &row : modules) { - for (bool color : row) { - if (color) - dark++; - } - } - int total = size * size; // Note that size is odd, so dark/total != 1/2 - // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% - int k = static_cast((std::abs(dark * 20L - total * 10L) + total - 1) / total) - 1; - assert(0 <= k && k <= 9); - result += k * PENALTY_N4; - assert(0 <= result && result <= 2568888L); // Non-tight upper bound based on default values of PENALTY_N1, ..., N4 - return result; -} - - -vector QrCode::getAlignmentPatternPositions() const { - if (version == 1) - return vector(); - else { - int numAlign = version / 7 + 2; - int step = (version == 32) ? 26 : - (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; - vector result; - for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step) - result.insert(result.begin(), pos); - result.insert(result.begin(), 6); - return result; - } -} - - -int QrCode::getNumRawDataModules(int ver) { - if (ver < MIN_VERSION || ver > MAX_VERSION) - throw std::domain_error("Version number out of range"); - int result = (16 * ver + 128) * ver + 64; - if (ver >= 2) { - int numAlign = ver / 7 + 2; - result -= (25 * numAlign - 10) * numAlign - 55; - if (ver >= 7) - result -= 36; - } - assert(208 <= result && result <= 29648); - return result; -} - - -int QrCode::getNumDataCodewords(int ver, Ecc ecl) { - return getNumRawDataModules(ver) / 8 - - ECC_CODEWORDS_PER_BLOCK [static_cast(ecl)][ver] - * NUM_ERROR_CORRECTION_BLOCKS[static_cast(ecl)][ver]; -} - - -vector QrCode::reedSolomonComputeDivisor(int degree) { - if (degree < 1 || degree > 255) - throw std::domain_error("Degree out of range"); - // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. - // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. - vector result(static_cast(degree)); - result.at(result.size() - 1) = 1; // Start off with the monomial x^0 - - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), - // and drop the highest monomial term which is always 1x^degree. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint8_t root = 1; - for (int i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for (size_t j = 0; j < result.size(); j++) { - result.at(j) = reedSolomonMultiply(result.at(j), root); - if (j + 1 < result.size()) - result.at(j) ^= result.at(j + 1); - } - root = reedSolomonMultiply(root, 0x02); - } - return result; -} - - -vector QrCode::reedSolomonComputeRemainder(const vector &data, const vector &divisor) { - vector result(divisor.size()); - for (uint8_t b : data) { // Polynomial division - uint8_t factor = b ^ result.at(0); - result.erase(result.begin()); - result.push_back(0); - for (size_t i = 0; i < result.size(); i++) - result.at(i) ^= reedSolomonMultiply(divisor.at(i), factor); - } - return result; -} - - -uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) { - // Russian peasant multiplication - int z = 0; - for (int i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - assert(z >> 8 == 0); - return static_cast(z); -} - - -int QrCode::finderPenaltyCountPatterns(const std::array &runHistory) const { - int n = runHistory.at(1); - assert(n <= size * 3); - bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n && runHistory.at(5) == n; - return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0) - + (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0); -} - - -int QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const { - if (currentRunColor) { // Terminate dark run - finderPenaltyAddHistory(currentRunLength, runHistory); - currentRunLength = 0; - } - currentRunLength += size; // Add light border to final run - finderPenaltyAddHistory(currentRunLength, runHistory); - return finderPenaltyCountPatterns(runHistory); -} - - -void QrCode::finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const { - if (runHistory.at(0) == 0) - currentRunLength += size; // Add light border to initial run - std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end()); - runHistory.at(0) = currentRunLength; -} - - -bool QrCode::getBit(long x, int i) { - return ((x >> i) & 1) != 0; -} - - -/*---- Tables of constants ----*/ - -const int QrCode::PENALTY_N1 = 3; -const int QrCode::PENALTY_N2 = 3; -const int QrCode::PENALTY_N3 = 40; -const int QrCode::PENALTY_N4 = 10; - - -const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low - {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium - {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile - {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High -}; - -const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low - {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium - {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile - {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High -}; - - -data_too_long::data_too_long(const std::string &msg) : - std::length_error(msg) {} - - - -/*---- Class BitBuffer ----*/ - -BitBuffer::BitBuffer() - : std::vector() {} - - -void BitBuffer::appendBits(std::uint32_t val, int len) { - if (len < 0 || len > 31 || val >> len != 0) - throw std::domain_error("Value out of range"); - for (int i = len - 1; i >= 0; i--) // Append bit by bit - this->push_back(((val >> i) & 1) != 0); -} - -} diff --git a/QrGen/src/utils.cpp b/QrGen/src/utils.cpp index 4cb5957..6b443ca 100644 --- a/QrGen/src/utils.cpp +++ b/QrGen/src/utils.cpp @@ -1,26 +1,28 @@ -#include "qrcodegen.hpp" -namespace qrcodegen { - std::string toSvgString(const QrCode &qr, std::string fill) { +#include "esterv/utils/qrcode_gen.hpp" +namespace Esterv::Utils::QrGen { +std::string toSvgString(const QrCode &qr, std::string fill) { - std::ostringstream sb; - sb << "\n"; - sb << "\n"; - sb << "\n"; - sb << "\t\n"; - sb << "\t\n"; - sb << "\n"; - return sb.str(); - } + std::ostringstream sb; + sb << "\n"; + sb << "\n"; + sb << "\n"; + sb << "\t\n"; + sb << "\t\n"; + sb << "\n"; + return sb.str(); } +} // namespace Esterv::Utils::QrGen diff --git a/QtQrDec/CMakeLists.txt b/QtQrDec/CMakeLists.txt index 553116d..865d4a2 100644 --- a/QtQrDec/CMakeLists.txt +++ b/QtQrDec/CMakeLists.txt @@ -1,90 +1,112 @@ - if(EMSCRIPTEN) - find_package(Qt6 COMPONENTS Core Gui Qml Quick ShaderTools) + find_package(Qt6 COMPONENTS Core Gui Qml Quick ShaderTools) else() - find_package(Qt6 COMPONENTS Core Gui Qml Quick ShaderTools Multimedia) + find_package(Qt6 COMPONENTS Core Gui Qml Quick ShaderTools Multimedia) endif(EMSCRIPTEN) +if(Qt6_FOUND AND TARGET QrDec) + FetchContent_Declare( + EstervDesigns + GIT_REPOSITORY https://github.com/EddyTheCo/Esterv.Designs.git + GIT_TAG v2.0.0 + FIND_PACKAGE_ARGS 2 COMPONENTS SimpleStyle CustomControls CONFIG) + FetchContent_MakeAvailable(EstervDesigns) + qt_standard_project_setup() + qt6_add_qml_module( + QtQrDec + URI + Esterv.CustomControls.QrDec + VERSION + 1.0 + SOURCES + src/qr_image_decoder.cpp + include/esterv/utils/qr_image_decoder.hpp + QML_FILES + "qml/QrCam.qml" + "qml/QrDecPop.qml" + "qml/QrTextField.qml" + RESOURCE_PREFIX + "/esterVtech.com/imports" + OUTPUT_TARGETS + out_targets_var + OUTPUT_DIRECTORY + ${CMAKE_BINARY_DIR}/Esterv/CustomControls/QrDec + IMPORT_PATH + ${CMAKE_BINARY_DIR}) + add_library(Esterv::QtQrDec ALIAS QtQrDec) + add_library(Esterv::QtQrDecplugin ALIAS QtQrDecplugin) + target_compile_definitions( + QtQrDec + PUBLIC + $,$,SHARED_LIBRARY>>,QTQRDEC_SHARED,> + ) + target_compile_definitions(QtQrDec PRIVATE WINDOWS_EXPORT) + set_target_properties(QtQrDec PROPERTIES VERSION ${VERSION} SOVERSION + ${VERSION_MAJOR}) -if (Qt6_FOUND AND TARGET QrDec) - FetchContent_Declare( - EstervDesigns - GIT_REPOSITORY https://github.com/EddyTheCo/MyDesigns.git - GIT_TAG v1.2.0 - FIND_PACKAGE_ARGS 1.2 COMPONENTS SimpleStyle CustomControls CONFIG - ) - FetchContent_MakeAvailable(EstervDesigns) - qt_standard_project_setup() - qt6_add_qml_module(QtQrDec - URI Esterv.CustomControls.QrDec - VERSION 1.0 - SOURCES Qrimagedecoder.cpp include/Qrimagedecoder.hpp - QML_FILES - "qml/QrCam.qml" - "qml/QrDecPop.qml" - "qml/QrTextField.qml" - RESOURCE_PREFIX - "/esterVtech.com/imports" - OUTPUT_TARGETS out_targets_var - OUTPUT_DIRECTORY - ${CMAKE_BINARY_DIR}/Esterv/CustomControls/QrDec - IMPORT_PATH ${CMAKE_BINARY_DIR} - ) - - add_library(${PROJECT_NAME}::QtQrDec ALIAS QtQrDec) - add_library(${PROJECT_NAME}::QtQrDecplugin ALIAS QtQrDecplugin) - target_compile_definitions(QtQrDec PRIVATE WINDOWS_DEC) - set_target_properties(QtQrDec PROPERTIES VERSION ${VERSION} SOVERSION ${VERSION_MAJOR}) - - qt6_add_shaders(QtQrDec "esterVtech.com.imports.QtQrDec.shaders" - BATCHABLE - PRECOMPILE - OPTIMIZED - OUTPUT_TARGETS out_targets_var2 - PREFIX - "/esterVtech.com/imports/Designs" - FILES - "frag/qrscanner.frag" - ) - target_include_directories(QtQrDec PUBLIC $ - "$") - if(EMSCRIPTEN) - target_compile_definitions(QtQrDec PRIVATE USE_EMSCRIPTEN) - else() - target_link_libraries(QtQrDec PUBLIC Qt6::Multimedia) - endif(EMSCRIPTEN) - target_link_libraries(QtQrDec PUBLIC Qt6::Gui Qt6::Quick QrDec - EstervDesigns::SimpleStyle EstervDesigns::CustomControls - $<$,STATIC_LIBRARY>:EstervDesigns::SimpleStyleplugin> - $<$,STATIC_LIBRARY>:EstervDesigns::CustomControlsplugin>) + qt6_add_shaders( + QtQrDec + "esterVtech.com.imports.QtQrDec.shaders" + BATCHABLE + PRECOMPILE + OPTIMIZED + OUTPUT_TARGETS + out_targets_var2 + PREFIX + "/esterVtech.com/imports/Designs" + FILES + "frag/qrscanner.frag") + target_include_directories( + QtQrDec PUBLIC $ + "$") + target_include_directories( + QtQrDec PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/esterv/utils) + if(EMSCRIPTEN) + target_compile_definitions(QtQrDec PUBLIC USE_EMSCRIPTEN) + else() + target_link_libraries(QtQrDec PUBLIC Qt6::Multimedia) + endif(EMSCRIPTEN) + target_link_libraries( + QtQrDec + PUBLIC + Qt6::Gui + Qt6::Quick + QrDec + Esterv::SimpleStyle + Esterv::CustomControls + $<$,STATIC_LIBRARY>:Esterv::SimpleStyleplugin> + $<$,STATIC_LIBRARY>:Esterv::CustomControlsplugin> + ) - install(TARGETS QtQrDec ${out_targets_var} ${out_targets_var2} - EXPORT ${PROJECT_NAME}-config - DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT QtQrDec - ) - install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/Esterv - COMPONENT QtQrDec - ) - install(DIRECTORY ${CMAKE_BINARY_DIR}/Esterv/CustomControls/QrDec - DESTINATION ${CMAKE_INSTALL_LIBDIR}/Esterv/CustomControls - COMPONENT QtQrDec - ) + install( + TARGETS QtQrDec ${out_targets_var} ${out_targets_var2} + EXPORT ${PROJECT_NAME}-config + COMPONENT EstervQtQrDec + ARCHIVE COMPONENT ${PROJECT_NAME}-dev) + install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT ${PROJECT_NAME}-dev) + install( + DIRECTORY ${CMAKE_BINARY_DIR}/Esterv/CustomControls/QrDec + DESTINATION ${CMAKE_INSTALL_LIBDIR}/Esterv/CustomControls + COMPONENT EstervQtQrDec-qml) - install(TARGETS QtQrDecplugin - EXPORT ${PROJECT_NAME}-config - DESTINATION ${CMAKE_INSTALL_LIBDIR}/Esterv/CustomControls/QrDec - COMPONENT QtQrDec - ) + install( + TARGETS QtQrDecplugin + EXPORT ${PROJECT_NAME}-config + DESTINATION ${CMAKE_INSTALL_LIBDIR}/Esterv/CustomControls/QrDec + COMPONENT EstervQtQrDec-qml + ARCHIVE COMPONENT ${PROJECT_NAME}-dev) - if(EMSCRIPTEN) - target_compile_definitions(QtQrDec PRIVATE USE_EMSCRIPTEN) - endif(EMSCRIPTEN) - if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - add_subdirectory(examples) - endif(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + if(EMSCRIPTEN) + target_compile_definitions(QtQrDec PRIVATE USE_EMSCRIPTEN) + endif(EMSCRIPTEN) + if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + add_subdirectory(examples) + endif(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) else(Qt6_FOUND AND TARGET QrDec) - message(STATUS "The QML Module for QRCODE decoding will not be built") + message( + STATUS "The QML Module/Qt methods for QRCODE decoding will not be built") endif(Qt6_FOUND AND TARGET QrDec) diff --git a/QtQrDec/Qrimagedecoder.cpp b/QtQrDec/Qrimagedecoder.cpp deleted file mode 100644 index e514591..0000000 --- a/QtQrDec/Qrimagedecoder.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include "Qrimagedecoder.hpp" -#include -#include -#include -#include - - -QRImageDecoder* QRImageDecoder::m_instance=nullptr; - -#ifdef USE_EMSCRIPTEN - -#include -#include - - -EMSCRIPTEN_BINDINGS(qrdecoder) { - emscripten::class_("QRImageDecoder") - .function("reload", &QRImageDecoder::reload,emscripten::allow_raw_pointers()) - .class_function("instance", &QRImageDecoder::instance, emscripten::allow_raw_pointers()); -} - -EM_JS(void, js_start, (), { - - if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) { - stream = navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, audio: false }).then((stream) => { - const settings = stream.getVideoTracks()[0].getSettings(); - const width = settings.width; - const height = settings.height; - - if(document.querySelector("#qrvideo")=== null) - { - let elemDiv = document.createElement('div'); - elemDiv.style.cssText = 'display:none; position:absolute;width:100%;height:100%;'; - elemDiv.innerHTML += ''; - document.body.appendChild(elemDiv); - } - const video = document.querySelector("#qrvideo"); - video.srcObject = stream; - window.localStream = stream; - - let canvas = document.querySelector("#qrcanvas"); - let ctx=canvas.getContext("2d"); - const processFrame = function () { - ctx.drawImage(video, 0, 0, canvas.width, canvas.height); - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const sourceBuffer = imageData.data; - const buffer = _malloc(sourceBuffer.byteLength); - HEAPU8.set(sourceBuffer, buffer); - Module.QRImageDecoder.instance().reload(buffer,video.width,video.height); - _free(buffer); - if(window.localStream.active) - { - requestAnimationFrame(processFrame); - } - else - { - ctx.clearRect(0, 0, canvas.width, canvas.height); - } - }; - processFrame(); - - - }).catch(alert); - - - } - -}); - -EM_JS(void, js_stop, (), { - if(window.localStream)window.localStream.getVideoTracks()[0].stop(); -}); -#else -#if QT_CONFIG(permissions) -#include -#endif - -void QRImageDecoder::getCamera(void) -{ - const QList cameras = QMediaDevices::videoInputs(); - if(cameras.size()) - { - QCameraDevice best=cameras.front(); - for (const QCameraDevice &cameraDevice : cameras) { - - if (cameraDevice.position() == QCameraDevice::BackFace) - { - best=cameraDevice; - } - } - m_camera=new QCamera(best,this); - auto bvF=best.videoFormats().at(0); - for (const QCameraFormat &format : best.videoFormats()) - { - if(abs(format.resolution().width()*1.0-format.resolution().height())setCameraFormat(bvF); - - } - -} - -#endif -QRImageDecoder* QRImageDecoder::instance() -{ - if (!m_instance) m_instance=new QRImageDecoder(); - return m_instance; -} -QRImageDecoder::QRImageDecoder(QObject *parent):QObject(parent),m_useTorch(false),m_hasTorch(false), -#ifndef USE_EMSCRIPTEN - m_camera(nullptr),captureSession(new QMediaCaptureSession(this)),videoSink(new QVideoSink(this)), -#endif - m_state(Ready) -{ -#ifndef USE_EMSCRIPTEN - captureSession->setVideoOutput(videoSink); - QObject::connect(videoSink,&QVideoSink::videoFrameChanged,this,[=](const QVideoFrame & Vframe) - { - - if(m_camera&&m_camera->isActive()&&Vframe.isValid()){ - auto picture=Vframe.toImage(); - WasmImageProvider::img=picture; - setid(); - if(m_state) - { - auto var = std::thread(&QRImageDecoder::decodePicture, this,picture); - var.detach(); - } - } - }); - connect(this,&QRImageDecoder::useTorchChanged,this,[=](){ - if(m_camera->isActive()&&m_useTorch) - m_camera->setTorchMode(QCamera::TorchOn); - else - m_camera->setTorchMode(QCamera::TorchOff); - }); -#endif - - -}; -void QRImageDecoder::stop(){ -#ifdef USE_EMSCRIPTEN - js_stop(); -#else - if(m_camera)m_camera->stop(); -#endif -}; -void QRImageDecoder::start() -{ -#ifdef USE_EMSCRIPTEN - clear(); - js_start(); -#elif QT_CONFIG(permissions) - QCameraPermission cPermission; - switch (qApp->checkPermission(cPermission)) { - case Qt::PermissionStatus::Undetermined: - qApp->requestPermission(cPermission, this, - &QRImageDecoder::start); - return; - case Qt::PermissionStatus::Denied: - return; - case Qt::PermissionStatus::Granted: - if(!m_camera) - { - getCamera(); - if(m_camera) - { - captureSession->setCamera(m_camera); - QObject::connect(m_camera,&QCamera::activeChanged,[=](bool var) - { - - if(var&&m_camera->isTorchModeSupported(QCamera::TorchOn)) - { - m_hasTorch=true; - emit hasTorchChanged(); - - } - - }); - - QObject::connect(m_camera,&QCamera::errorOccurred,[](QCamera::Error error, const QString &errorString) - { - qDebug()<<"Camera Error:"<start(); - } - - return; - } - -#endif -} - -void QRImageDecoder::decodePicture(QImage picture) -{ - m_state=QRImageDecoder::Decoding; - picture.convertTo(QImage::Format_Grayscale8); - const auto str = detector.decode_grey(picture.bits(), picture.height(),picture.bytesPerLine()); - const auto qstr=QString::fromStdString(str); - if(qstr!="") - { - emit decodedQR(qstr); - } - m_state=QRImageDecoder::Ready; -} - -QImage WasmImageProvider::img=QImage(); -QImage WasmImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) -{ - return img; -} -void QRImageDecoder::clear(void) -{ - WasmImageProvider::restart(); - setid(); -} -void WasmImageProvider::restart(void) -{ - WasmImageProvider::img=QImage(QSize(200,150),QImage::Format_RGBA8888); - WasmImageProvider::img.fill("black"); -} -void QRImageDecoder::reload(int offset, int width, int height) -{ - auto imgarr = reinterpret_cast(offset); - WasmImageProvider::img=QImage(imgarr,width,height,QImage::Format_RGBA8888); - setid(); - if(m_state)decodePicture(WasmImageProvider::img); -} -void QRImageDecoder::setid() -{ - static quint8 index=0; - m_source="qrimage"+QString::number(index); - emit sourceChanged(); - index++; -} - diff --git a/QtQrDec/README.md b/QtQrDec/README.md index 01dfb94..7afcc8e 100644 --- a/QtQrDec/README.md +++ b/QtQrDec/README.md @@ -1,10 +1,10 @@ -# QtQrDec +# Esterv.CustomControls.QrDec [TOC] This repo produce a QML Module with custom types that can detect and decode QRCODEs. -The types should be style independent, but the colors used relies on the [EstervDesigns](https://github.com/EddyTheCo/MyDesigns) +The types should be style independent, but the colors used relies on the [EstervDesigns](https://github.com/EddyTheCo/Esterv.Designs) Simple style. If you want to change the colors in your top qml file one can do ``` @@ -13,14 +13,14 @@ import Esterv.Styles.Simple Component.onCompleted: { -Style.frontColor1= (Style.theme)?LightThemeColor:DarkThemeColor//Like control.palette.text +Style.frontColor1= (Style.theme)?LightThemeColor:DarkThemeColor //Like control.palette.text -Style.frontColor2= ... -Style.frontColor3= ... +Style.frontColor2= ... +Style.frontColor3= ... -Style.backColor1= ... -Style.backColor2= ... -Style.backColor3= ... +Style.backColor1= ... +Style.backColor2= ... +Style.backColor3= ... } ``` @@ -33,18 +33,20 @@ If compiling for wasm the library creates a custom ImageProvider that communicat You can play with the decoder on [this page](https://eddytheco.github.io/qmlonline/?example_url=qt_qr_dec) -## Adding the module to your CMake project +## Adding the module to your CMake project + ``` include(FetchContent) FetchContent_Declare( - qrCode - GIT_REPOSITORY https://github.com/EddyTheCo/qrCode.git - GIT_TAG vMAJOR.MINOR.PATCH - FIND_PACKAGE_ARGS MAJOR.MINOR COMPONENTS QtQrDec CONFIG + EstervQrCode + GIT_REPOSITORY https://github.com/EddyTheCo/Esterv.Utils.QrCode.git + GIT_TAG vMAJOR.MINOR.PATCH + FIND_PACKAGE_ARGS MAJOR.MINOR COMPONENTS QtQrDec CONFIG ) -FetchContent_MakeAvailable(qrCode) +FetchContent_MakeAvailable(EstervQrCode) -target_link_libraries( qrCode::QtQrDec) +target_link_libraries( Esterv::QtQrDec) +target_link_libraries( $<$,STATIC_LIBRARY>:Esterv::QtQrDecplugin>) ``` diff --git a/QtQrDec/examples/CMakeLists.txt b/QtQrDec/examples/CMakeLists.txt index 927ef93..b32435b 100644 --- a/QtQrDec/examples/CMakeLists.txt +++ b/QtQrDec/examples/CMakeLists.txt @@ -1,68 +1,93 @@ if(BUILD_EXAMPLES) - if(EMSCRIPTEN OR ANDROID) - find_package(Qt6 REQUIRED COMPONENTS QuickControls2) - find_package(EstervDesigns 0.4 REQUIRED COMPONENTS FlatControl CONFIG ) - endif(EMSCRIPTEN OR ANDROID) + include(InstallRequiredSystemLibraries) + if(EMSCRIPTEN OR ANDROID) + find_package(Qt6 REQUIRED COMPONENTS QuickControls2) + find_package(EstervDesigns 2.0 REQUIRED COMPONENTS FlatControl CONFIG) + endif(EMSCRIPTEN OR ANDROID) - foreach(example "qrcam" "qrtext" - ) - qt_add_executable(${example} ${example}.cpp ) + foreach(example "Qrcam" "Qrtext") + qt_add_executable(${example} ${example}.cpp) - qt6_add_qml_module(${example} - URI E${example} - VERSION 1.0 - QML_FILES - "qml/${example}.qml" - RESOURCE_PREFIX - "/esterVtech.com/imports" - IMPORT_PATH ${CMAKE_BINARY_DIR} - ) - if(NOT EMSCRIPTEN) - target_link_libraries(${example} PRIVATE Qt6::Multimedia) - endif(NOT EMSCRIPTEN) - target_link_libraries(${example} PRIVATE Qt::Gui Qt::Qml Qt::Quick $ QtQrDec - $<$,STATIC_LIBRARY>:QtQrDecplugin> - ) + qt6_add_qml_module( + ${example} + URI + Examples${example} + VERSION + 1.0 + QML_FILES + "qml/${example}.qml" + RESOURCE_PREFIX + "/esterVtech.com/imports" + IMPORT_PATH + ${CMAKE_BINARY_DIR}) + if(NOT EMSCRIPTEN) + target_link_libraries(${example} PRIVATE Qt6::Multimedia) + endif(NOT EMSCRIPTEN) + target_link_libraries( + ${example} + PRIVATE + Qt::Gui + Qt::Qml + Qt::Quick + QtQrDec + $<$,STATIC_LIBRARY>:QtQrDecplugin> + ) - set_target_properties(${example} PROPERTIES - WIN32_EXECUTABLE ON - MACOSX_BUNDLE ON - ) - install(TARGETS ${example} COMPONENT examples - BUNDLE DESTINATION . COMPONENT examples - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT examples - ) - if(QTDEPLOY) - qt_generate_deploy_qml_app_script( - TARGET ${example} - OUTPUT_SCRIPT deploy_script - ) - install(SCRIPT ${deploy_script} COMPONENT examples) - endif(QTDEPLOY) - if(ANDROID) - set_property(TARGET ${example} APPEND PROPERTY QT_ANDROID_MIN_SDK_VERSION 30) - set_property(TARGET ${example} APPEND PROPERTY QT_ANDROID_TARGET_SDK_VERSION 34) - set_property(TARGET ${example} APPEND PROPERTY QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0) + set_target_properties(${example} PROPERTIES WIN32_EXECUTABLE ON + MACOSX_BUNDLE ON) + install( + TARGETS ${example} + COMPONENT ${PROJECT_NAME}-examples + BUNDLE DESTINATION . COMPONENT ${PROJECT_NAME}-examples + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT ${PROJECT_NAME}-examples) + if(QTDEPLOY) + if(WIN32) + set(deploy_tools_options -multimedia) + endif(WIN32) + qt_generate_deploy_qml_app_script( + TARGET ${example} OUTPUT_SCRIPT deploy_script DEPLOY_TOOL_OPTIONS + ${deploy_tools_options} # Qt> 6.7.0 only for windows + ) + install(SCRIPT ${deploy_script} COMPONENT ${PROJECT_NAME}-examples) + endif(QTDEPLOY) + if(ANDROID) + set_property( + TARGET ${example} + APPEND + PROPERTY QT_ANDROID_MIN_SDK_VERSION 30) + set_property( + TARGET ${example} + APPEND + PROPERTY QT_ANDROID_TARGET_SDK_VERSION 34) + set_property( + TARGET ${example} + APPEND + PROPERTY QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0) - FetchContent_Declare( - android_openssl - DOWNLOAD_EXTRACT_TIMESTAMP true - URL https://github.com/KDAB/android_openssl/archive/refs/heads/master.zip - ) - FetchContent_GetProperties(android_openssl) - if(NOT android_openssl_POPULATED) - FetchContent_Populate(android_openssl) - include(${android_openssl_SOURCE_DIR}/android_openssl.cmake) - add_android_openssl_libraries(${example}) - endif(NOT android_openssl_POPULATED) + FetchContent_Declare( + android_openssl + DOWNLOAD_EXTRACT_TIMESTAMP true + URL https://github.com/KDAB/android_openssl/archive/refs/heads/master.zip + ) + FetchContent_GetProperties(android_openssl) + if(NOT android_openssl_POPULATED) + FetchContent_Populate(android_openssl) + include(${android_openssl_SOURCE_DIR}/android_openssl.cmake) + add_android_openssl_libraries(${example}) + endif(NOT android_openssl_POPULATED) - endif(ANDROID) - if(EMSCRIPTEN OR ANDROID) - target_link_libraries(${example} PRIVATE EstervDesigns::FlatControl Qt6::QuickControls2 - $<$,STATIC_LIBRARY>:EstervDesigns::FlatControlplugin> - ) - target_compile_definitions(${example} PRIVATE FORCE_STYLE="Esterv.Controls.Flat") - endif(EMSCRIPTEN OR ANDROID) - endforeach() + endif(ANDROID) + if(EMSCRIPTEN OR ANDROID) + target_link_libraries( + ${example} + PRIVATE + Esterv::FlatControl + Qt6::QuickControls2 + $<$,STATIC_LIBRARY>:Esterv::FlatControlplugin> + ) + target_compile_definitions(${example} + PRIVATE FORCE_STYLE="Esterv.Controls.Flat") + endif(EMSCRIPTEN OR ANDROID) + endforeach() endif(BUILD_EXAMPLES) diff --git a/QtQrDec/examples/Qrcam.cpp b/QtQrDec/examples/Qrcam.cpp new file mode 100644 index 0000000..7150716 --- /dev/null +++ b/QtQrDec/examples/Qrcam.cpp @@ -0,0 +1,23 @@ +#include "esterv/utils/qr_image_decoder.hpp" +#include +#include + +#if defined(FORCE_STYLE) +#include +#endif + +using namespace Esterv::Utils::QrDec; + +int main(int argc, char *argv[]) { + QGuiApplication app(argc, argv); + +#if defined(FORCE_STYLE) + QQuickStyle::setStyle(FORCE_STYLE); +#endif + QQmlApplicationEngine engine; + engine.addImageProvider(QLatin1String("wasm"), new WasmImageProvider()); + engine.addImportPath("qrc:/esterVtech.com/imports"); + engine.loadFromModule("ExamplesQrcam", "Qrcam"); + + return app.exec(); +} diff --git a/QtQrDec/examples/Qrtext.cpp b/QtQrDec/examples/Qrtext.cpp new file mode 100644 index 0000000..f2142c5 --- /dev/null +++ b/QtQrDec/examples/Qrtext.cpp @@ -0,0 +1,23 @@ +#include "esterv/utils/qr_image_decoder.hpp" +#include +#include + +#if defined(FORCE_STYLE) +#include +#endif + +using namespace Esterv::Utils::QrDec; + +int main(int argc, char *argv[]) { + QGuiApplication app(argc, argv); + +#if defined(FORCE_STYLE) + QQuickStyle::setStyle(FORCE_STYLE); +#endif + QQmlApplicationEngine engine; + engine.addImageProvider(QLatin1String("wasm"), new WasmImageProvider()); + engine.addImportPath("qrc:/esterVtech.com/imports"); + + engine.loadFromModule("ExamplesQrtext", "Qrtext"); + return app.exec(); +} diff --git a/QtQrDec/examples/qml/qrcam.qml b/QtQrDec/examples/qml/Qrcam.qml similarity index 59% rename from QtQrDec/examples/qml/qrcam.qml rename to QtQrDec/examples/qml/Qrcam.qml index 387667c..dae295f 100644 --- a/QtQrDec/examples/qml/qrcam.qml +++ b/QtQrDec/examples/qml/Qrcam.qml @@ -1,57 +1,49 @@ - import QtQuick import QtQuick.Controls import QtQuick.Layouts import Esterv.Styles.Simple import Esterv.CustomControls.QrDec import Esterv.CustomControls + ApplicationWindow { + id: window visible: true - id:window - background:Rectangle - { - color:Style.backColor1 + background: Rectangle { + color: Style.backColor1 } - ColumnLayout - { + ColumnLayout { anchors.fill: parent - RowLayout - { - id:rowlayout + RowLayout { + id: rowlayout Layout.fillHeight: true Layout.fillWidth: true Layout.minimumHeight: 70 Layout.minimumWidth: 300 - ThemeSwitch - { - id:themeswitch + ThemeSwitch { + id: themeswitch } - Label - { - id:label + Label { + id: label Layout.fillHeight: true Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignBottom text: "" - elide:Text.ElideRight - color:Style.frontColor2 + elide: Text.ElideRight + color: Style.frontColor2 } - Switch - { - id:onoff + Switch { + id: onoff - onCheckedChanged: - { - (onoff.checked)?QRImageDecoder.start():QRImageDecoder.stop(); + onCheckedChanged: { + (onoff.checked) ? QRImageDecoder.start() : QRImageDecoder.stop(); } } } - QrCam - { + QrCam { Layout.fillHeight: true Layout.fillWidth: true Layout.margins: 20 @@ -59,14 +51,8 @@ ApplicationWindow { Connections { target: QRImageDecoder function onDecodedQR(data) { - label.text=data; + label.text = data; } } } - - - - - - } diff --git a/QtQrDec/examples/qml/Qrtext.qml b/QtQrDec/examples/qml/Qrtext.qml new file mode 100644 index 0000000..6660588 --- /dev/null +++ b/QtQrDec/examples/qml/Qrtext.qml @@ -0,0 +1,28 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Esterv.Styles.Simple +import Esterv.CustomControls.QrDec +import Esterv.CustomControls + +ApplicationWindow { + id: window + visible: true + + background: Rectangle { + color: Style.backColor1 + } + ThemeSwitch { + id: themeswitch + width: 45 + height: width + } + QrTextField { + anchors.top: themeswitch.bottom + anchors.horizontalCenter: parent.horizontalCenter + width: 300 + height: 60 + popWidth: Math.max(parent.width * 0.5, 300) + popHeight: Math.max(parent.height * 0.5, 500) + } +} diff --git a/QtQrDec/examples/qml/qrtext.qml b/QtQrDec/examples/qml/qrtext.qml deleted file mode 100644 index 268ba0a..0000000 --- a/QtQrDec/examples/qml/qrtext.qml +++ /dev/null @@ -1,37 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Esterv.Styles.Simple -import Esterv.CustomControls.QrDec -import Esterv.CustomControls - -ApplicationWindow { - visible: true - id:window - - background:Rectangle - { - color:Style.backColor1 - } - ThemeSwitch - { - id:themeswitch - width:45 - height:width - } - QrTextField - { - anchors.top:themeswitch.bottom - anchors.horizontalCenter:parent.horizontalCenter - width:300 - height:60 - popWidth:Math.max(parent.width*0.5,300) - popHeight:Math.max(parent.height*0.5,500) - } - - - - - - -} diff --git a/QtQrDec/examples/qrcam.cpp b/QtQrDec/examples/qrcam.cpp deleted file mode 100644 index 505e8cf..0000000 --- a/QtQrDec/examples/qrcam.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include "Qrimagedecoder.hpp" - -#if defined(FORCE_STYLE) -#include -#endif -int main(int argc, char *argv[]) -{ - QGuiApplication app(argc, argv); - -#if defined(FORCE_STYLE) - QQuickStyle::setStyle(FORCE_STYLE); -#endif - QQmlApplicationEngine engine; - engine.addImageProvider(QLatin1String("wasm"), new WasmImageProvider()); - engine.addImportPath("qrc:/esterVtech.com/imports"); - const QUrl url=QUrl("qrc:/esterVtech.com/imports/Eqrcam/qml/qrcam.qml"); - - engine.load(url); - - return app.exec(); -} - diff --git a/QtQrDec/examples/qrtext.cpp b/QtQrDec/examples/qrtext.cpp deleted file mode 100644 index 08c613b..0000000 --- a/QtQrDec/examples/qrtext.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include "Qrimagedecoder.hpp" - -#if defined(FORCE_STYLE) -#include -#endif -int main(int argc, char *argv[]) -{ - QGuiApplication app(argc, argv); - -#if defined(FORCE_STYLE) - QQuickStyle::setStyle(FORCE_STYLE); -#endif - QQmlApplicationEngine engine; - engine.addImageProvider(QLatin1String("wasm"), new WasmImageProvider()); - engine.addImportPath("qrc:/esterVtech.com/imports"); - const QUrl url=QUrl("qrc:/esterVtech.com/imports/Eqrtext/qml/qrtext.qml"); - - engine.load(url); - - return app.exec(); -} - diff --git a/QtQrDec/include/Qrimagedecoder.hpp b/QtQrDec/include/Qrimagedecoder.hpp deleted file mode 100644 index 58af70a..0000000 --- a/QtQrDec/include/Qrimagedecoder.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#ifndef USE_EMSCRIPTEN -#include -#include -#include -#include -#include -#include -#endif - -#include - - -#include -#if defined(WINDOWS_DEC) -# define DEC_EXPORT Q_DECL_EXPORT -#else -#define DEC_EXPORT Q_DECL_IMPORT -#endif - - -class DEC_EXPORT QRImageDecoder : public QObject -{ - Q_OBJECT - Q_PROPERTY(QString source READ get_source NOTIFY sourceChanged) - Q_PROPERTY(bool useTorch MEMBER m_useTorch NOTIFY useTorchChanged) - Q_PROPERTY(bool hasTorch MEMBER m_hasTorch NOTIFY hasTorchChanged) - QML_ELEMENT - QML_SINGLETON - - QRImageDecoder(QObject *parent = nullptr); -public: - static QRImageDecoder* instance(); - static QRImageDecoder *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) - { - return instance(); - } - enum State { - Decoding = 0, - Ready - }; - Q_INVOKABLE void start(); - Q_INVOKABLE void stop(); - Q_INVOKABLE void clear(); - QString get_source(void)const{return m_source;} - - - void reload(int offset, int width, int height); -signals: - void decodedQR(QString); - void sourceChanged(); - void hasTorchChanged(); - void useTorchChanged(); -private: - State m_state; -#ifndef USE_EMSCRIPTEN - QCamera* m_camera; - QMediaCaptureSession* captureSession; - QVideoSink* videoSink; - void getCamera(void); -#endif - void setid(); - void decodePicture(QImage picture); - QString m_source; - QRDecoder detector; - bool m_useTorch,m_hasTorch; - static QRImageDecoder* m_instance; -}; - - -class DEC_EXPORT WasmImageProvider : public QQuickImageProvider -{ -public: - WasmImageProvider():QQuickImageProvider(QQuickImageProvider::Image) - { - restart(); - } - QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; - static void restart(void); - static QImage img; -}; - diff --git a/QtQrDec/include/esterv/utils/qr_image_decoder.hpp b/QtQrDec/include/esterv/utils/qr_image_decoder.hpp new file mode 100644 index 0000000..1f03d1b --- /dev/null +++ b/QtQrDec/include/esterv/utils/qr_image_decoder.hpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include + +#include + +#ifndef USE_EMSCRIPTEN +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#if defined(QTQRDEC_SHARED) +#include +#ifdef WINDOWS_EXPORT +#define DEC_EXPORT Q_DECL_EXPORT +#else +#define DEC_EXPORT Q_DECL_IMPORT +#endif +#else +#define DEC_EXPORT +#endif + +namespace Esterv::Utils::QrDec { +class DEC_EXPORT QRImageDecoder : public QObject { + Q_OBJECT + Q_PROPERTY(QString source READ get_source NOTIFY sourceChanged) + Q_PROPERTY(bool useTorch MEMBER m_useTorch NOTIFY useTorchChanged) + Q_PROPERTY(bool hasTorch MEMBER m_hasTorch NOTIFY hasTorchChanged) + QML_ELEMENT + QML_SINGLETON + + QRImageDecoder(QObject *parent = nullptr); + +public: + ~QRImageDecoder() override { + { + std::lock_guard lk(m_decoding_mutex); + m_decode_running = false; + } + m_decoding_variable.notify_one(); + } + static QRImageDecoder *instance(); + static QRImageDecoder *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) { + return instance(); + } + enum State { Decoding = 0, Ready }; + Q_INVOKABLE void start(); + Q_INVOKABLE void stop(); + Q_INVOKABLE void clear(); + QString get_source(void) const { return m_source; } + + void reload(int offset, int width, int height); +signals: + void decodedQR(QString); + void sourceChanged(); + void hasTorchChanged(); + void useTorchChanged(); + +private: + State m_state{Ready}; + std::mutex m_decoding_mutex; + std::condition_variable m_decoding_variable; + bool m_decode_running{true}; +#ifndef USE_EMSCRIPTEN + QCamera *m_camera{nullptr}; + QMediaCaptureSession *captureSession; + QVideoSink *videoSink; + void getCamera(void); +#endif + void setid(); + void decodePicture(); + QString m_source; + QRDecoder detector; + bool m_useTorch{false}, m_hasTorch{false}; +}; + +class DEC_EXPORT WasmImageProvider : public QQuickImageProvider { +public: + WasmImageProvider() : QQuickImageProvider(QQuickImageProvider::Image) { + restart(); + } + QImage requestImage(const QString &id, QSize *size, + const QSize &requestedSize) override; + static void restart(void); + static QImage img; +}; +} // namespace Esterv::Utils::QrDec diff --git a/QtQrDec/qml/QrCam.qml b/QtQrDec/qml/QrCam.qml index 038582f..8f8ed02 100644 --- a/QtQrDec/qml/QrCam.qml +++ b/QtQrDec/qml/QrCam.qml @@ -1,26 +1,23 @@ import QtQuick 2.0 import QtQuick.Controls import Esterv.CustomControls.QrDec -Image -{ - id:control +Image { + id: control - cache : false - source: "image://wasm/"+QRImageDecoder.source + cache: false + source: "image://wasm/" + QRImageDecoder.source Switch { - id:useTorch + id: useTorch opacity: checked ? 0.75 : 0.25 anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter - visible:QRImageDecoder.hasTorch - onCheckedChanged: - { - QRImageDecoder.useTorch=useTorch.checked; + visible: QRImageDecoder.hasTorch + onCheckedChanged: { + QRImageDecoder.useTorch = useTorch.checked; } - onVisibleChanged: useTorch.checked=false; - text:qsTr("Torch") + onVisibleChanged: useTorch.checked = false + text: qsTr("Torch") } - } diff --git a/QtQrDec/qml/QrDecPop.qml b/QtQrDec/qml/QrDecPop.qml index 14b1329..a846767 100644 --- a/QtQrDec/qml/QrDecPop.qml +++ b/QtQrDec/qml/QrDecPop.qml @@ -3,38 +3,33 @@ import QtQuick.Controls import Esterv.CustomControls.QrDec import Esterv.CustomControls -Popup -{ - id:control - property bool showClose:true; - signal gotData(string data); +Popup { + id: control + property bool showClose: true + signal gotData(string data) onOpened: QRImageDecoder.start() onClosed: QRImageDecoder.stop() Connections { target: QRImageDecoder function onDecodedQR(data) { - if(control.enabled&&control.visible) - { + if (control.enabled && control.visible) { control.gotData(data); - control.visible=false; + control.visible = false; } } } - QrCam - { + QrCam { anchors.fill: parent } - CloseButton - { - id:cbutton + CloseButton { + id: cbutton anchors.right: parent.right anchors.top: parent.top visible: control.showClose - radius:width - flat:true - onClicked: - { - control.visible=false; + radius: width + flat: true + onClicked: { + control.visible = false; } } } diff --git a/QtQrDec/qml/QrTextField.qml b/QtQrDec/qml/QrTextField.qml index 0edc1f9..c776a86 100644 --- a/QtQrDec/qml/QrTextField.qml +++ b/QtQrDec/qml/QrTextField.qml @@ -3,37 +3,36 @@ import QtQuick.Controls import Esterv.Styles.Simple import Esterv.CustomControls.QrDec -TextField -{ - id:control - property int popWidth:300 - property int popHeight:500 - rightPadding: control.height*0.9 - QrDecPop - { - id:qrpop - visible:false +TextField { + id: control + property int popWidth: 300 + property int popHeight: 500 + rightPadding: control.height * 0.9 + QrDecPop { + id: qrpop + visible: false closePolicy: Popup.CloseOnPressOutside anchors.centerIn: Overlay.overlay - width:control.popWidth - height:control.popHeight - onGotData: (data) =>{control.text=data;} - + width: control.popWidth + height: control.popHeight + onGotData: data => { + control.text = data; + } } Rectangle { - id:qricon - height:Math.min(parent.height,control.font.pixelSize) - width:height + id: qricon + height: Math.min(parent.height, control.font.pixelSize) + width: height anchors.right: parent.right anchors.verticalCenter: control.verticalCenter color: "transparent" - visible: !control.text&&control.enabled + visible: !control.text && control.enabled ShaderEffect { id: shader - property var src: qricon; - property color fcolor:Style.frontColor2 - property var pixelStep: Qt.vector2d(1/src.width, 1/src.height) + property var src: qricon + property color fcolor: Style.frontColor2 + property var pixelStep: Qt.vector2d(1 / src.width, 1 / src.height) fragmentShader: "qrc:/esterVtech.com/imports/Designs/frag/qrscanner.frag.qsb" anchors.fill: parent } @@ -44,6 +43,4 @@ TextField } } } - - } diff --git a/QtQrDec/src/qr_image_decoder.cpp b/QtQrDec/src/qr_image_decoder.cpp new file mode 100644 index 0000000..f06f5e3 --- /dev/null +++ b/QtQrDec/src/qr_image_decoder.cpp @@ -0,0 +1,228 @@ +#include "esterv/utils/qr_image_decoder.hpp" +#include +#include +#include + +#ifdef USE_EMSCRIPTEN + +#include +#include + +EMSCRIPTEN_BINDINGS(qrdecoder) { + emscripten::class_("QRImageDecoder") + .function("reload", &Esterv::Utils::QrDec::QRImageDecoder::reload, + emscripten::allow_raw_pointers()) + .class_function("instance", + &Esterv::Utils::QrDec::QRImageDecoder::instance, + emscripten::allow_raw_pointers()); +} +// clang-format off +EM_JS(void, js_start, (), { + if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) { + stream = navigator.mediaDevices.getUserMedia({video : {facingMode : 'environment'}, audio : false}).then((stream) => { + const settings = stream.getVideoTracks()[0].getSettings(); + const width = settings.width; + const height = settings.height; + + if (document.querySelector("#qrvideo") === null) { + let elemDiv = document.createElement('div'); + elemDiv.style.cssText = 'display:none; position:absolute;width:100%;height:100%;'; + elemDiv.innerHTML += ''; + document.body.appendChild(elemDiv); + } + const video = document.querySelector("#qrvideo"); + video.srcObject = stream; + window.localStream = stream; + + let canvas = document.querySelector("#qrcanvas"); + let ctx = canvas.getContext("2d", { willReadFrequently: true }); + const processFrame = function() { + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const sourceBuffer = imageData.data; + const buffer = _malloc(sourceBuffer.byteLength); + HEAPU8.set(sourceBuffer, buffer); + Module.QRImageDecoder.instance().reload(buffer, video.width, video.height); + _free(buffer); + if (window.localStream.active) { + requestAnimationFrame(processFrame); + } else { + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + }; + processFrame(); + }) + .catch(alert); + } +}); + +EM_JS(void, js_stop, (), { + if (window.localStream) + window.localStream.getVideoTracks()[0].stop(); +}); +// clang-format on +namespace Esterv::Utils::QrDec { +#else +#include +#if QT_CONFIG(permissions) +#include +#endif + +namespace Esterv::Utils::QrDec { + +void QRImageDecoder::getCamera(void) { + const QList cameras = QMediaDevices::videoInputs(); + if (cameras.size()) { + QCameraDevice best = cameras.front(); + for (const QCameraDevice &cameraDevice : cameras) { + + if (cameraDevice.position() == QCameraDevice::BackFace) { + best = cameraDevice; + } + } + m_camera = new QCamera(best, this); + } +} + +#endif +QRImageDecoder *QRImageDecoder::instance() { + static QRImageDecoder *instance = new QRImageDecoder(); + return instance; +} +QRImageDecoder::QRImageDecoder(QObject *parent) + : QObject(parent) + +#ifndef USE_EMSCRIPTEN + , + captureSession{new QMediaCaptureSession(this)}, + videoSink{new QVideoSink(this)} +#endif +{ +#ifndef USE_EMSCRIPTEN + std::thread decoding_thread([this]() { + std::unique_lock lk(m_decoding_mutex); + while (m_decode_running) { + m_decoding_variable.wait(lk); + decodePicture(); + } + }); + decoding_thread.detach(); + captureSession->setVideoOutput(videoSink); + QObject::connect(videoSink, &QVideoSink::videoFrameChanged, this, + [=](const QVideoFrame &Vframe) { + if (m_camera && m_camera->isActive() && Vframe.isValid()) { + auto picture = Vframe.toImage(); + WasmImageProvider::img = picture; + setid(); + if (m_state == Ready) { + { + std::lock_guard lk(m_decoding_mutex); + m_state = Decoding; + } + m_decoding_variable.notify_one(); + } + } + }); + connect(this, &QRImageDecoder::useTorchChanged, this, [=]() { + if (m_camera->isActive() && m_useTorch) + m_camera->setTorchMode(QCamera::TorchOn); + else + m_camera->setTorchMode(QCamera::TorchOff); + }); +#endif +}; +void QRImageDecoder::stop() { +#ifdef USE_EMSCRIPTEN + js_stop(); +#else + if (m_camera) + m_camera->stop(); +#endif +}; +void QRImageDecoder::start() { +#ifdef USE_EMSCRIPTEN + clear(); + js_start(); +#elif QT_CONFIG(permissions) + QCameraPermission cPermission; + switch (qApp->checkPermission(cPermission)) { + case Qt::PermissionStatus::Undetermined: + qApp->requestPermission(cPermission, this, &QRImageDecoder::start); + return; + case Qt::PermissionStatus::Denied: + return; + case Qt::PermissionStatus::Granted: + if (!m_camera) { + getCamera(); + if (m_camera) { + captureSession->setCamera(m_camera); + QObject::connect(m_camera, &QCamera::activeChanged, [=](bool var) { + if (var && m_camera->isTorchModeSupported(QCamera::TorchOn)) { + m_hasTorch = true; + emit hasTorchChanged(); + } + }); + + QObject::connect(m_camera, &QCamera::errorOccurred, + [](QCamera::Error error, const QString &errorString) { + qDebug() << "Camera Error:" << errorString; + }); + } + } + if (m_camera) { + clear(); + m_camera->start(); + } + + return; + } + +#endif +} + +void QRImageDecoder::decodePicture() { + QImage picture = WasmImageProvider::img; + picture.convertTo(QImage::Format_Grayscale8); + const auto str = detector.decode_grey(picture.bits(), picture.height(), + picture.bytesPerLine()); + const auto qstr = QString::fromStdString(str); + if (qstr != "") { + emit decodedQR(qstr); + } + m_state = QRImageDecoder::Ready; +} + +QImage WasmImageProvider::img = QImage(); +QImage WasmImageProvider::requestImage(const QString &id, QSize *size, + const QSize &requestedSize) { + return img; +} +void QRImageDecoder::clear(void) { + WasmImageProvider::restart(); + setid(); +} +void WasmImageProvider::restart(void) { + WasmImageProvider::img = QImage(QSize(200, 150), QImage::Format_RGBA8888); + WasmImageProvider::img.fill("black"); +} +void QRImageDecoder::reload(int offset, int width, int height) { + auto imgarr = reinterpret_cast(offset); + WasmImageProvider::img = + QImage(imgarr, width, height, QImage::Format_RGBA8888); + setid(); + if (m_state == Ready) { + m_state = Decoding; + decodePicture(); + } +} +void QRImageDecoder::setid() { + static quint8 index = 0; + m_source = "qrimage" + QString::number(index); + emit sourceChanged(); + index++; +} +} diff --git a/QtQrGen/CMakeLists.txt b/QtQrGen/CMakeLists.txt index bbc88e2..e4c93a7 100644 --- a/QtQrGen/CMakeLists.txt +++ b/QtQrGen/CMakeLists.txt @@ -1,86 +1,111 @@ -find_package(Qt6 COMPONENTS Core Gui Qml Quick OPTIONAL_COMPONENTS Svg) +find_package( + Qt6 + COMPONENTS Core Gui Qml Quick + OPTIONAL_COMPONENTS Svg) -if (Qt6_FOUND AND TARGET QrGen) - FetchContent_Declare( - EstervDesigns - GIT_REPOSITORY https://github.com/EddyTheCo/MyDesigns.git - GIT_TAG v1.2.0 - FIND_PACKAGE_ARGS 1.2 COMPONENTS SimpleStyle CustomControls CONFIG - ) - FetchContent_MakeAvailable(EstervDesigns) - qt_standard_project_setup() - qt6_add_qml_module(QtQrGen - URI Esterv.CustomControls.QrGen - VERSION ${VERSION} - SOURCES Qrimageprovider.cpp include/Qrimageprovider.hpp - QML_FILES - "qml/QrGenImage.qml" - "qml/QrGenPop.qml" - "qml/QrText.qml" - RESOURCE_PREFIX - "/esterVtech.com/imports" - OUTPUT_TARGETS out_targets_var - OUTPUT_DIRECTORY - ${CMAKE_BINARY_DIR}/Esterv/CustomControls/QrGen - IMPORT_PATH ${CMAKE_BINARY_DIR} - ) +if(Qt6_FOUND AND TARGET QrGen) + FetchContent_Declare( + EstervDesigns + GIT_REPOSITORY https://github.com/EddyTheCo/Esterv.Designs.git + GIT_TAG v2.0.0 + FIND_PACKAGE_ARGS 2 COMPONENTS CustomControls SimpleStyle CONFIG) + FetchContent_MakeAvailable(EstervDesigns) + qt_standard_project_setup() + qt6_add_qml_module( + QtQrGen + URI + Esterv.CustomControls.QrGen + VERSION + ${VERSION} + SOURCES + src/qr_image_provider.cpp + include/esterv/utils/qr_image_provider.hpp + QML_FILES + "qml/QrGenImage.qml" + "qml/QrGenPop.qml" + "qml/QrText.qml" + RESOURCE_PREFIX + "/esterVtech.com/imports" + OUTPUT_TARGETS + out_targets_var + OUTPUT_DIRECTORY + ${CMAKE_BINARY_DIR}/Esterv/CustomControls/QrGen + IMPORT_PATH + ${CMAKE_BINARY_DIR}) - add_library(${PROJECT_NAME}::QtQrGen ALIAS QtQrGen) - add_library(${PROJECT_NAME}::QtQrGenplugin ALIAS QtQrGenplugin) - set_target_properties(QtQrGen PROPERTIES VERSION ${VERSION} SOVERSION ${VERSION_MAJOR}) - target_compile_definitions(QtQrGen PRIVATE WINDOWS_GEN) + add_library(Esterv::QtQrGen ALIAS QtQrGen) + add_library(Esterv::QtQrGenplugin ALIAS QtQrGenplugin) + target_compile_definitions( + QtQrGen + PUBLIC + $,$,SHARED_LIBRARY>>,QTQRGEN_SHARED,> + ) + target_compile_definitions(QtQrGen PRIVATE WINDOWS_EXPORT) + set_target_properties(QtQrGen PROPERTIES VERSION ${VERSION} SOVERSION + ${VERSION_MAJOR}) - qt6_add_shaders(QtQrGen "esterVtech.com.imports.QtQrGen.shaders" - BATCHABLE - PRECOMPILE - OPTIMIZED - OUTPUT_TARGETS out_targets_var2 - PREFIX - "/esterVtech.com/imports/Designs" - FILES - "frag/qrscanner.frag" - ) + qt6_add_shaders( + QtQrGen + "esterVtech.com.imports.QtQrGen.shaders" + BATCHABLE + PRECOMPILE + OPTIMIZED + OUTPUT_TARGETS + out_targets_var2 + PREFIX + "/esterVtech.com/imports/Designs" + FILES + "frag/qrscanner.frag") - target_include_directories(QtQrGen PUBLIC $ - "$") - if(NOT TARGET Qt6::Svg) - FetchContent_Declare( - qtsvg - GIT_REPOSITORY https://github.com/qt/qtsvg.git - GIT_TAG 6.6.0 - ) - FetchContent_MakeAvailable(qtsvg) - endif() + target_include_directories( + QtQrGen PUBLIC $ + "$") + target_include_directories( + QtQrGen PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/esterv/utils) + if(NOT TARGET Qt6::Svg) + FetchContent_Declare( + qtsvg + GIT_REPOSITORY https://github.com/qt/qtsvg.git + GIT_TAG 6.7.0) + FetchContent_MakeAvailable(qtsvg) + endif() + target_link_libraries(QtQrGen PUBLIC Qt6::Quick) + target_link_libraries( + QtQrGen + PRIVATE + QrGen + Qt6::Core + Qt6::Gui + Qt6::Qml + Qt6::Svg + Esterv::SimpleStyle + Esterv::CustomControls + $<$,STATIC_LIBRARY>:Esterv::SimpleStyleplugin> + $<$,STATIC_LIBRARY>:Esterv::CustomControlsplugin> + ) - target_link_libraries(QtQrGen PUBLIC - Qt6::Quick - ) - target_link_libraries(QtQrGen PRIVATE QrGen Qt6::Core Qt6::Gui Qt6::Qml Qt6::Svg - EstervDesigns::SimpleStyle EstervDesigns::CustomControls - $<$,STATIC_LIBRARY>:EstervDesigns::SimpleStyleplugin> - $<$,STATIC_LIBRARY>:EstervDesigns::CustomControlsplugin>) + install( + TARGETS QtQrGen ${out_targets_var} ${out_targets_var2} + EXPORT ${PROJECT_NAME}-config + COMPONENT EstervQtQrGen + ARCHIVE COMPONENT ${PROJECT_NAME}-dev) + install( + DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT ${PROJECT_NAME}-dev) + install( + DIRECTORY ${CMAKE_BINARY_DIR}/Esterv/CustomControls/QrGen + DESTINATION ${CMAKE_INSTALL_LIBDIR}/Esterv/CustomControls + COMPONENT EstervQtQrGen-qml) - install(TARGETS QtQrGen ${out_targets_var} ${out_targets_var2} - EXPORT ${PROJECT_NAME}-config - DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT QtQrGen - ) - install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/Esterv - COMPONENT QtQrGen - ) - install(DIRECTORY ${CMAKE_BINARY_DIR}/Esterv/CustomControls/QrGen - DESTINATION ${CMAKE_INSTALL_LIBDIR}/Esterv/CustomControls - COMPONENT QtQrGen - ) - - install(TARGETS QtQrGenplugin - EXPORT ${PROJECT_NAME}-config - DESTINATION ${CMAKE_INSTALL_LIBDIR}/Esterv/CustomControls/QrGen - COMPONENT QtQrGen - ) - if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - add_subdirectory(examples) - endif(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + install( + TARGETS QtQrGenplugin + EXPORT ${PROJECT_NAME}-config + DESTINATION ${CMAKE_INSTALL_LIBDIR}/Esterv/CustomControls/QrGen + COMPONENT EstervQtQrGen-qml + ARCHIVE COMPONENT ${PROJECT_NAME}-dev) + if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + add_subdirectory(examples) + endif(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) endif(Qt6_FOUND AND TARGET QrGen) diff --git a/QtQrGen/Qrimageprovider.cpp b/QtQrGen/Qrimageprovider.cpp deleted file mode 100644 index 1664c9e..0000000 --- a/QtQrGen/Qrimageprovider.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include -#include "qrcodegen.hpp" -#include "Qrimageprovider.hpp" -#include - -using namespace qrcodegen; - -QPixmap QRImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) -{ - const int width = 100; - QStringList strlist=id.split('/'); - const auto color=strlist.front(); - strlist.removeFirst(); - const auto data=strlist.join('/'); - const auto max=(requestedSize.width()>requestedSize.height())?requestedSize.width():requestedSize.height(); - - QPixmap pixmap(max > 0 ? max : width, - max> 0 ? max : width); - pixmap.fill( Qt::transparent ); - *size = pixmap.size(); - const QrCode qr = QrCode::encodeText(data.toStdString().c_str(), static_cast(errC)); - const auto qrSVGstr=toSvgString(qr, color.toStdString()); - - auto qrImage=QSvgRenderer(QByteArray::fromStdString(qrSVGstr)); - QPainter Painter; - - Painter.begin(&pixmap); - qrImage.render(&Painter); - Painter.end(); - return pixmap; -} - diff --git a/QtQrGen/README.md b/QtQrGen/README.md index 658dbe3..3cf2e41 100644 --- a/QtQrGen/README.md +++ b/QtQrGen/README.md @@ -1,4 +1,4 @@ -# QtQrGen +# Esterv.CustomControls.QrGen [TOC] @@ -7,8 +7,9 @@ The custom types are related to the generation and showing of QRCODEs. The types should be style independent, but the colors used relies on the [EstervDesigns](https://github.com/EddyTheCo/MyDesigns) -Simple style. +Simple style. If you want to change the colors in your top qml file one can do + ``` import Esterv.Styles.Simple ... @@ -17,12 +18,12 @@ Component.onCompleted: { Style.frontColor1= (Style.theme)?LightThemeColor:DarkThemeColor//Like control.palette.text -Style.frontColor2= ... -Style.frontColor3= ... +Style.frontColor2= ... +Style.frontColor3= ... -Style.backColor1= ... -Style.backColor2= ... -Style.backColor3= ... +Style.backColor1= ... +Style.backColor2= ... +Style.backColor3= ... } ``` @@ -30,19 +31,22 @@ Style.backColor3= ... You can play with the ImageProvider on [this page](https://eddytheco.github.io/qmlonline/?example_url=qt_qr_gen). -## Adding the module to your CMake project +## Adding the module to your CMake project + ``` include(FetchContent) FetchContent_Declare( - qrCode - GIT_REPOSITORY https://github.com/EddyTheCo/qrCode.git + EstervQrCode + GIT_REPOSITORY https://github.com/EddyTheCo/Esterv.Utils.QrCode.git GIT_TAG vMAJOR.MINOR.PATCH - FIND_PACKAGE_ARGS MAJOR.MINOR COMPONENTS QtQrGen CONFIG + FIND_PACKAGE_ARGS MAJOR.MINOR COMPONENTS QtQrGen CONFIG ) -FetchContent_MakeAvailable(qrCode) +FetchContent_MakeAvailable(EstervQrCode) -target_link_libraries( qrCode::QtQrGen) +target_link_libraries( Esterv::QtQrGen) +target_link_libraries( $<$,STATIC_LIBRARY>:Esterv::QtQrGenplugin>) ``` + ## Examples The [examples](examples) folder shows the use of the different custom types provided by the QML module. diff --git a/QtQrGen/examples/CMakeLists.txt b/QtQrGen/examples/CMakeLists.txt index 14ce0ba..eedcd18 100644 --- a/QtQrGen/examples/CMakeLists.txt +++ b/QtQrGen/examples/CMakeLists.txt @@ -1,63 +1,84 @@ if(BUILD_EXAMPLES) - if(EMSCRIPTEN OR ANDROID) - find_package(Qt6 REQUIRED COMPONENTS QuickControls2) - find_package(EstervDesigns 0.4 REQUIRED COMPONENTS FlatControl CONFIG ) - endif(EMSCRIPTEN OR ANDROID) + include(InstallRequiredSystemLibraries) + if(EMSCRIPTEN OR ANDROID) + find_package(Qt6 REQUIRED COMPONENTS QuickControls2) + find_package(EstervDesigns 1.2 REQUIRED COMPONENTS FlatControl CONFIG) + endif(EMSCRIPTEN OR ANDROID) - foreach(example "image" "text" - ) - qt_add_executable(${example} ${example}.cpp ) + foreach(example "Image" "Text") + qt_add_executable(${example} ${example}.cpp) - qt6_add_qml_module(${example} - URI E${example} - VERSION 1.0 - QML_FILES - "qml/${example}.qml" - RESOURCE_PREFIX - "/esterVtech.com/imports" - IMPORT_PATH ${CMAKE_BINARY_DIR} - ) - target_link_libraries(${example} PRIVATE Qt::Gui Qt::Qml Qt::Quick QtQrGen - $<$,STATIC_LIBRARY>:QtQrGenplugin> - ) + qt6_add_qml_module( + ${example} + URI + Examples${example} + VERSION + 1.0 + QML_FILES + "qml/${example}.qml" + RESOURCE_PREFIX + "/esterVtech.com/imports" + IMPORT_PATH + ${CMAKE_BINARY_DIR}) + target_link_libraries( + ${example} + PRIVATE + Qt::Gui + Qt::Qml + Qt::Quick + QtQrGen + $<$,STATIC_LIBRARY>:QtQrGenplugin> + ) - set_target_properties(${example} PROPERTIES - WIN32_EXECUTABLE ON - MACOSX_BUNDLE ON - ) - install(TARGETS ${example} COMPONENT examples - BUNDLE DESTINATION . COMPONENT examples - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) - if(QTDEPLOY) - qt_generate_deploy_qml_app_script( - TARGET ${example} - OUTPUT_SCRIPT deploy_script - ) - install(SCRIPT ${deploy_script} COMPONENT examples) - endif(QTDEPLOY) - if(ANDROID) - set_property(TARGET ${example} APPEND PROPERTY QT_ANDROID_MIN_SDK_VERSION 30) - set_property(TARGET ${example} APPEND PROPERTY QT_ANDROID_TARGET_SDK_VERSION 34) - set_property(TARGET ${example} APPEND PROPERTY QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0) + set_target_properties(${example} PROPERTIES WIN32_EXECUTABLE ON + MACOSX_BUNDLE ON) + install( + TARGETS ${example} + COMPONENT ${PROJECT_NAME}-examples + BUNDLE DESTINATION . COMPONENT ${PROJECT_NAME}-examples + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT ${PROJECT_NAME}-examples) + if(QTDEPLOY) + qt_generate_deploy_qml_app_script(TARGET ${example} OUTPUT_SCRIPT + deploy_script) + install(SCRIPT ${deploy_script} COMPONENT ${PROJECT_NAME}-examples) + endif(QTDEPLOY) + if(ANDROID) + set_property( + TARGET ${example} + APPEND + PROPERTY QT_ANDROID_MIN_SDK_VERSION 30) + set_property( + TARGET ${example} + APPEND + PROPERTY QT_ANDROID_TARGET_SDK_VERSION 34) + set_property( + TARGET ${example} + APPEND + PROPERTY QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0) - FetchContent_Declare( - android_openssl - DOWNLOAD_EXTRACT_TIMESTAMP true - URL https://github.com/KDAB/android_openssl/archive/refs/heads/master.zip - ) - FetchContent_GetProperties(android_openssl) - if(NOT android_openssl_POPULATED) - FetchContent_Populate(android_openssl) - include(${android_openssl_SOURCE_DIR}/android_openssl.cmake) - add_android_openssl_libraries(${example}) - endif(NOT android_openssl_POPULATED) - endif(ANDROID) - if(EMSCRIPTEN OR ANDROID) - target_link_libraries(${example} PRIVATE EstervDesigns::FlatControl Qt6::QuickControls2 - $<$,STATIC_LIBRARY>:EstervDesigns::FlatControlplugin> - ) - target_compile_definitions(${example} PRIVATE FORCE_STYLE="Esterv.Controls.Flat") - endif(EMSCRIPTEN OR ANDROID) - endforeach() + FetchContent_Declare( + android_openssl + DOWNLOAD_EXTRACT_TIMESTAMP true + URL https://github.com/KDAB/android_openssl/archive/refs/heads/master.zip + ) + FetchContent_GetProperties(android_openssl) + if(NOT android_openssl_POPULATED) + FetchContent_Populate(android_openssl) + include(${android_openssl_SOURCE_DIR}/android_openssl.cmake) + add_android_openssl_libraries(${example}) + endif(NOT android_openssl_POPULATED) + endif(ANDROID) + if(EMSCRIPTEN OR ANDROID) + target_link_libraries( + ${example} + PRIVATE + Esterv::FlatControl + Qt6::QuickControls2 + $<$,STATIC_LIBRARY>:Esterv::FlatControlplugin> + ) + target_compile_definitions(${example} + PRIVATE FORCE_STYLE="Esterv.Controls.Flat") + endif(EMSCRIPTEN OR ANDROID) + endforeach() endif(BUILD_EXAMPLES) diff --git a/QtQrGen/examples/Image.cpp b/QtQrGen/examples/Image.cpp new file mode 100644 index 0000000..226d2a7 --- /dev/null +++ b/QtQrGen/examples/Image.cpp @@ -0,0 +1,23 @@ +#include "esterv/utils/qr_image_provider.hpp" +#include +#include + +#if defined(FORCE_STYLE) +#include +#endif + +using namespace Esterv::Utils::QrGen; + +int main(int argc, char *argv[]) { + QGuiApplication app(argc, argv); + +#if defined(FORCE_STYLE) + QQuickStyle::setStyle(FORCE_STYLE); +#endif + QQmlApplicationEngine engine; + engine.addImportPath("qrc:/esterVtech.com/imports"); + engine.addImageProvider(QLatin1String("qrcode"), new QRImageProvider(1)); + + engine.loadFromModule("ExamplesImage", "Image"); + return app.exec(); +} diff --git a/QtQrGen/examples/Text.cpp b/QtQrGen/examples/Text.cpp new file mode 100644 index 0000000..a1fac50 --- /dev/null +++ b/QtQrGen/examples/Text.cpp @@ -0,0 +1,22 @@ +#include "esterv/utils/qr_image_provider.hpp" +#include +#include +#if defined(FORCE_STYLE) +#include +#endif + +using namespace Esterv::Utils::QrGen; + +int main(int argc, char *argv[]) { + QGuiApplication app(argc, argv); + +#if defined(FORCE_STYLE) + QQuickStyle::setStyle(FORCE_STYLE); +#endif + QQmlApplicationEngine engine; + engine.addImportPath("qrc:/esterVtech.com/imports"); + engine.addImageProvider(QLatin1String("qrcode"), new QRImageProvider(1)); + + engine.loadFromModule("ExamplesText", "Text"); + return app.exec(); +} diff --git a/QtQrGen/examples/image.cpp b/QtQrGen/examples/image.cpp deleted file mode 100644 index ac60319..0000000 --- a/QtQrGen/examples/image.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include -#include "Qrimageprovider.hpp" -#if defined(FORCE_STYLE) -#include -#endif -int main(int argc, char *argv[]) -{ - QGuiApplication app(argc, argv); - -#if defined(FORCE_STYLE) - QQuickStyle::setStyle(FORCE_STYLE); -#endif - QQmlApplicationEngine engine; - engine.addImportPath("qrc:/esterVtech.com/imports"); - engine.addImageProvider(QLatin1String("qrcode"), new QRImageProvider(1)); - const QUrl url=QUrl("qrc:/esterVtech.com/imports/Eimage/qml/image.qml"); - - engine.load(url); - - return app.exec(); -} - diff --git a/QtQrGen/examples/qml/Image.qml b/QtQrGen/examples/qml/Image.qml new file mode 100644 index 0000000..4df3b42 --- /dev/null +++ b/QtQrGen/examples/qml/Image.qml @@ -0,0 +1,27 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Esterv.Styles.Simple +import Esterv.CustomControls.QrGen +import Esterv.CustomControls + +ApplicationWindow { + id: window + visible: true + + background: Rectangle { + color: Style.backColor1 + } + + ThemeSwitch { + id: themeswitch + } + + QrGenImage { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: themeswitch.bottom + width: parent.width * 0.5 + height: parent.height * 0.5 + textData: "https://eddytheco.github.io/" + } +} diff --git a/QtQrGen/examples/qml/Text.qml b/QtQrGen/examples/qml/Text.qml new file mode 100644 index 0000000..68e2ee5 --- /dev/null +++ b/QtQrGen/examples/qml/Text.qml @@ -0,0 +1,27 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Esterv.Styles.Simple +import Esterv.CustomControls.QrGen +import Esterv.CustomControls + +ApplicationWindow { + id: window + visible: true + + background: Rectangle { + color: Style.backColor1 + } + + ThemeSwitch { + id: themeswitch + } + + QrText { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: themeswitch.bottom + text: "smr1qp9rtwlc00ksp0mvet8ugwvqu03ygzr8s3x77w3df9qw9srm3hwk2l0v9kf" + width: parent.width * 0.5 + font.pixelSize: 30 + } +} diff --git a/QtQrGen/examples/qml/image.qml b/QtQrGen/examples/qml/image.qml deleted file mode 100644 index 052414d..0000000 --- a/QtQrGen/examples/qml/image.qml +++ /dev/null @@ -1,40 +0,0 @@ - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Esterv.Styles.Simple -import Esterv.CustomControls.QrGen -import Esterv.CustomControls - -ApplicationWindow { - visible: true - id:window - - background:Rectangle - { - color:Style.backColor1 - } - - ThemeSwitch - { - id:themeswitch - } - - QrGenImage - { - anchors.horizontalCenter:parent.horizontalCenter - anchors.top:themeswitch.bottom - width:parent.width*0.5 - height:parent.height*0.5 - textData:"https://eddytheco.github.io/" - } - - - - - - - - - -} diff --git a/QtQrGen/examples/qml/text.qml b/QtQrGen/examples/qml/text.qml deleted file mode 100644 index 1b5b649..0000000 --- a/QtQrGen/examples/qml/text.qml +++ /dev/null @@ -1,40 +0,0 @@ - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Esterv.Styles.Simple -import Esterv.CustomControls.QrGen -import Esterv.CustomControls - -ApplicationWindow { - visible: true - id:window - - background:Rectangle - { - color:Style.backColor1 - } - - ThemeSwitch - { - id:themeswitch - } - - QrText - { - anchors.horizontalCenter:parent.horizontalCenter - anchors.top:themeswitch.bottom - text:"smr1qp9rtwlc00ksp0mvet8ugwvqu03ygzr8s3x77w3df9qw9srm3hwk2l0v9kf" - width:parent.width*0.5 - font.pixelSize: 30 - } - - - - - - - - - -} diff --git a/QtQrGen/examples/text.cpp b/QtQrGen/examples/text.cpp deleted file mode 100644 index 02dee2a..0000000 --- a/QtQrGen/examples/text.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include -#include "Qrimageprovider.hpp" -#if defined(FORCE_STYLE) -#include -#endif -int main(int argc, char *argv[]) -{ - QGuiApplication app(argc, argv); - -#if defined(FORCE_STYLE) - QQuickStyle::setStyle(FORCE_STYLE); -#endif - QQmlApplicationEngine engine; - engine.addImportPath("qrc:/esterVtech.com/imports"); - engine.addImageProvider(QLatin1String("qrcode"), new QRImageProvider(1)); - const QUrl url=QUrl("qrc:/esterVtech.com/imports/Etext/qml/text.qml"); - - engine.load(url); - - return app.exec(); -} - diff --git a/QtQrGen/include/Qrimageprovider.hpp b/QtQrGen/include/Qrimageprovider.hpp deleted file mode 100644 index df949d0..0000000 --- a/QtQrGen/include/Qrimageprovider.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#include - -#include -#if defined(WINDOWS_GEN) -# define GEN_EXPORT Q_DECL_EXPORT -#else -#define GEN_EXPORT Q_DECL_IMPORT -#endif - -class GEN_EXPORT QRImageProvider : public QQuickImageProvider -{ -public: - QRImageProvider(int erc=0) - : QQuickImageProvider(QQuickImageProvider::Pixmap),errC(erc) - { - - } - - QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; -private: - const int errC; -}; - - - diff --git a/QtQrGen/include/esterv/utils/qr_image_provider.hpp b/QtQrGen/include/esterv/utils/qr_image_provider.hpp new file mode 100644 index 0000000..ea19d38 --- /dev/null +++ b/QtQrGen/include/esterv/utils/qr_image_provider.hpp @@ -0,0 +1,28 @@ +#include + +#if defined(QTQRGEN_SHARED) +#include +#ifdef WINDOWS_EXPORT +#define GEN_EXPORT Q_DECL_EXPORT +#else +#define GEN_EXPORT Q_DECL_IMPORT +#endif +#else +#define GEN_EXPORT +#endif + +namespace Esterv::Utils::QrGen { + +class GEN_EXPORT QRImageProvider : public QQuickImageProvider { +public: + QRImageProvider(int erc = 0) + : QQuickImageProvider(QQuickImageProvider::Pixmap), errC(erc) {} + + QPixmap requestPixmap(const QString &id, QSize *size, + const QSize &requestedSize) override; + +private: + const int errC; +}; + +} // namespace Esterv::Utils::QrGen diff --git a/QtQrGen/qml/QrGenImage.qml b/QtQrGen/qml/QrGenImage.qml index 8155abd..eb63101 100644 --- a/QtQrGen/qml/QrGenImage.qml +++ b/QtQrGen/qml/QrGenImage.qml @@ -3,18 +3,16 @@ import QtQuick import QtQml import Esterv.Styles.Simple -Control -{ - id:control - required property string textData - implicitWidth: 100 - implicitHeight: 100 +Control { + id: control + required property string textData + implicitWidth: 100 + implicitHeight: 100 - - Image { - id:img - anchors.centerIn:parent - sourceSize.width: Math.min(control.width,control.height)-30 - source: "image://qrcode/"+Style.frontColor1+"/"+control.textData - } + Image { + id: img + anchors.centerIn: parent + sourceSize.width: Math.min(control.width, control.height) - 30 + source: "image://qrcode/" + Style.frontColor1 + "/" + control.textData + } } diff --git a/QtQrGen/qml/QrGenPop.qml b/QtQrGen/qml/QrGenPop.qml index efe6f21..cf7baa9 100644 --- a/QtQrGen/qml/QrGenPop.qml +++ b/QtQrGen/qml/QrGenPop.qml @@ -6,71 +6,57 @@ import Esterv.Styles.Simple Popup { id: control - required property string textData; - property bool showClose:false; + required property string textData + property bool showClose: false - ColumnLayout - { - anchors.fill:parent - Item - { + ColumnLayout { + anchors.fill: parent + Item { Layout.fillHeight: true Layout.fillWidth: true Layout.minimumHeight: 50 Layout.maximumHeight: 100 - Switch - { - id:showdata + Switch { + id: showdata text: qsTr("Show text") } - CloseButton - { - id:cbutton + CloseButton { + id: cbutton anchors.top: parent.top - radius:width + radius: width anchors.right: parent.right visible: control.showClose - flat:true - onClicked: - { - control.visible=false; + flat: true + onClicked: { + control.visible = false; } } } - Item - { + Item { Layout.minimumHeight: 100 Layout.minimumWidth: 200 Layout.fillWidth: true Layout.fillHeight: true ScrollView { - anchors.fill:parent - TextArea - { - id:tex + anchors.fill: parent + TextArea { + id: tex text: control.textData readOnly: true wrapMode: Text.Wrap - } } - visible:showdata.checked + visible: showdata.checked } - QrGenImage - { - id:qrgenimage - textData:control.textData - visible:!tex.visible + QrGenImage { + id: qrgenimage + textData: control.textData + visible: !tex.visible Layout.fillHeight: true Layout.fillWidth: true Layout.margins: 0 } - } - - - } - diff --git a/QtQrGen/qml/QrText.qml b/QtQrGen/qml/QrText.qml index 85eccbb..92cbe83 100644 --- a/QtQrGen/qml/QrText.qml +++ b/QtQrGen/qml/QrText.qml @@ -1,45 +1,40 @@ import QtQuick.Controls import QtQuick - import Esterv.Styles.Simple - -Text -{ - id:control - property int popWidth:300 - property int popHeight:500 - QrGenPop - { - id:qrgenpop - textData:control.text - visible:false +Text { + id: control + property int popWidth: 300 + property int popHeight: 500 + QrGenPop { + id: qrgenpop + textData: control.text + visible: false closePolicy: Popup.CloseOnPressOutside anchors.centerIn: Overlay.overlay - width:control.popWidth - height:control.popHeight + width: control.popWidth + height: control.popHeight } - color:Style.frontColor1 - elide:Text.ElideRight + color: Style.frontColor1 + elide: Text.ElideRight horizontalAlignment: TextEdit.AlignLeft - rightPadding: control.height*0.9 + rightPadding: control.height * 0.9 Rectangle { - id:qricon - height:Math.min(parent.height,control.font.pixelSize) - width:height + id: qricon + height: Math.min(parent.height, control.font.pixelSize) + width: height x: parent.contentWidth color: "transparent" ShaderEffect { id: shader - property var src: qricon; - property color fcolor:Style.frontColor2 - property var pixelStep: Qt.vector2d(1/src.width, 1/src.height) + property var src: qricon + property color fcolor: Style.frontColor2 + property var pixelStep: Qt.vector2d(1 / src.width, 1 / src.height) fragmentShader: "qrc:/esterVtech.com/imports/Designs/frag/qrscanner.frag.qsb" anchors.fill: parent } - } MouseArea { anchors.fill: parent diff --git a/QtQrGen/src/qr_image_provider.cpp b/QtQrGen/src/qr_image_provider.cpp new file mode 100644 index 0000000..f630395 --- /dev/null +++ b/QtQrGen/src/qr_image_provider.cpp @@ -0,0 +1,33 @@ +#include "esterv/utils/qr_image_provider.hpp" +#include "esterv/utils/qrcode_gen.hpp" +#include +#include + +namespace Esterv::Utils::QrGen { +QPixmap QRImageProvider::requestPixmap(const QString &id, QSize *size, + const QSize &requestedSize) { + const int width = 100; + QStringList strlist = id.split('/'); + const auto color = strlist.front(); + strlist.removeFirst(); + const auto data = strlist.join('/'); + const auto max = (requestedSize.width() > requestedSize.height()) + ? requestedSize.width() + : requestedSize.height(); + + QPixmap pixmap(max > 0 ? max : width, max > 0 ? max : width); + pixmap.fill(Qt::transparent); + *size = pixmap.size(); + const QrCode qr = QrCode::encodeText(data.toStdString().c_str(), + static_cast(errC)); + const auto qrSVGstr = toSvgString(qr, color.toStdString()); + + auto qrImage = QSvgRenderer(QByteArray::fromStdString(qrSVGstr)); + QPainter Painter; + + Painter.begin(&pixmap); + qrImage.render(&Painter); + Painter.end(); + return pixmap; +} +} // namespace Esterv::Utils::QrGen diff --git a/README.md b/README.md index 94efc35..52d3f48 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# qrCode +# Esterv.Utils.QrCode [TOC] @@ -8,19 +8,25 @@ The GUI part will be based on Qt libraries and QML. Examples of this library com - [QtQrGen](https://eddytheco.github.io/qmlonline/?example_url=qt_qr_gen) - [QtQrDec](https://eddytheco.github.io/qmlonline/?example_url=qt_qr_dec) -## Adding the libraries to your CMake project +## Configure, build, test, package ... + +The project uses [CMake presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) as a way to share CMake configurations. +Refer to [cmake](https://cmake.org/cmake/help/latest/manual/cmake.1.html), [ctest](https://cmake.org/cmake/help/latest/manual/ctest.1.html) and [cpack](https://cmake.org/cmake/help/latest/manual/cpack.1.html) documentation for more information on the use of presets. + + +## Adding the libraries to your CMake project ```CMake include(FetchContent) FetchContent_Declare( - qrCode - GIT_REPOSITORY https://github.com/EddyTheCo/qrCode.git + EstervQrCode + GIT_REPOSITORY https://github.com/EddyTheCo/Esterv.Utils.QrCode.git GIT_TAG vMAJOR.MINOR.PATCH - FIND_PACKAGE_ARGS MAJOR.MINOR CONFIG + FIND_PACKAGE_ARGS MAJOR.MINOR COMPONENTS QrDec QrGen QtQrDec QtQrGen CONFIG ) -FetchContent_MakeAvailable(qrCode) +FetchContent_MakeAvailable(EstervQrCode) -target_link_libraries( qrCode::QrGen qrCode::QtQrGen qrCode::QrDec qrCode::QtQrDec) +target_link_libraries( Esterv::QrGen Esterv::QtQrGen Esterv::QrDec Esterv::QtQrDec) ``` For more information check @@ -31,10 +37,9 @@ For more information check ## API reference -You can read the [API reference](https://eddytheco.github.io/qrCode/) here, or generate it yourself like +You can read the [API reference](https://eddytheco.github.io/Esterv.Utils.QrCode/), or generate it yourself like ``` -cmake -DBUILD_DOCS=ON ../ -cmake --build . --target doxygen_docs +cmake --workflow --preset default-documentation ``` ## Contributing