diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index ef202db43d..0000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,55 +0,0 @@ -version: '{branch}.{build}' -branches: - only: - - /v\d*\.\d*\.\d*/ -skip_non_tags: true -image: Visual Studio 2017 -build_script: -- cmd: >- - set CMAKE_EXE="cmake.exe" - set DESTINATION="wrappers\winrt\UAP\v0.8.0.0\ExtensionSDKs\ZXingWinRT\1.0.0.0" - set BASE_DIR="%CD%" - - cd %DESTINATION% - set DESTINATION=%CD% - - cd %BASE_DIR% - set BUILD_LOC=build_uwp_x86 - md %BUILD_LOC% - cd %BUILD_LOC% - %CMAKE_EXE% -G "Visual Studio 15 2017" -A Win32 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DEXTENSION_SDK_OUTPUT="%DESTINATION%" ..\wrappers\winrt - %CMAKE_EXE% --build . --config Release - - cd %BASE_DIR% - set BUILD_LOC=build_uwp_x64 - md %BUILD_LOC% - cd %BUILD_LOC% - %CMAKE_EXE% -G "Visual Studio 15 2017" -A x64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DEXTENSION_SDK_OUTPUT="%DESTINATION%" ..\wrappers\winrt - %CMAKE_EXE% --build . --config Release - - cd %BASE_DIR% - set BUILD_LOC=build_uwp_arm - md %BUILD_LOC% - cd %BUILD_LOC% - %CMAKE_EXE% -G "Visual Studio 15 2017" -A ARM -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DEXTENSION_SDK_OUTPUT="%DESTINATION%" ..\wrappers\winrt - %CMAKE_EXE% --build . --config Release - - cd %BASE_DIR% - set BUILD_LOC=build_uwp_arm64 - md %BUILD_LOC% - cd %BUILD_LOC% - %CMAKE_EXE% -G "Visual Studio 15 2017" -A ARM64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DEXTENSION_SDK_OUTPUT="%DESTINATION%" ..\wrappers\winrt - %CMAKE_EXE% --build . --config Release - - cd %BASE_DIR% - for /f "tokens=1,2,3 delims=. " %%a in ("%APPVEYOR_BUILD_VERSION%") do set major=%%a&set minor=%%b&set patch=%%c&set - nuget pack -Version %major%.%minor%.%patch% wrappers\winrt\nuget\ZXingWinRT.nuspec -artifacts: -- path: '*.nupkg' -deploy: -- provider: NuGet - api_key: - secure: OMvgm0WQ+yV7E+yxtmrOn6c/uO/V1fsbBERdz9aQUicqHGImzWqWoSBAankUtxdEwDZx0XrJ5hlUsIuL5tdQD+C8ZKrI1L3FukbXdbqVdKM= - artifact: /.*\.nupkg/ - on: - APPVEYOR_REPO_TAG: true diff --git a/.clang-format b/.clang-format index 51ba5e80f4..5058084794 100644 --- a/.clang-format +++ b/.clang-format @@ -24,7 +24,14 @@ AllowAllParametersOfDeclarationOnNextLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakTemplateDeclarations: Yes -BreakBeforeBraces: Mozilla +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterEnum: true + AfterStruct: true + AfterUnion: true + AfterFunction: true + SplitEmptyFunction: false BreakBeforeBinaryOperators: NonAssignment BreakBeforeTernaryOperators: true ConstructorInitializerAllOnOneLineOrOnePerLine: true diff --git a/.editorconfig b/.editorconfig index d3cd97c9dd..c643beb33d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 -[*.{cpp,h,html,py}] +[*.{cpp,c,h,html,py}] indent_style = tab indent_size = 4 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..7be904885c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: axxel +custom: 'www.paypal.com/donate/?hosted_button_id=8LDF4NNQF8Z3L' diff --git a/.github/workflows/build-winrt.yml b/.github/workflows/build-winrt.yml index 7bdce312aa..d4d8832d0e 100644 --- a/.github/workflows/build-winrt.yml +++ b/.github/workflows/build-winrt.yml @@ -2,7 +2,7 @@ name: build-winrt on: release: types: [published] - + workflow_dispatch: inputs: publish: @@ -20,7 +20,7 @@ jobs: target: [Win32, x64, ARM, ARM64] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Create build environment shell: cmd @@ -39,7 +39,7 @@ jobs: working-directory: ${{runner.workspace}}/build run: cmake --build . -j8 --config Release - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: winrt-${{matrix.target}}-artifacts path: ${{runner.workspace}}/build/dist @@ -49,16 +49,16 @@ jobs: runs-on: windows-latest if: ${{ github.event_name == 'release' || github.event.inputs.publish == 'y' }} steps: - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: winrt-Win32-artifacts - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: winrt-x64-artifacts - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: winrt-ARM-artifacts - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: winrt-ARM64-artifacts @@ -78,8 +78,8 @@ jobs: shell: cmd run: nuget push huycn.zxingcpp.winrt.nupkg -ApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: nuget-package path: huycn.zxingcpp.winrt.nupkg - + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee91c7886d..c26a4243f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,9 +24,9 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Python 3 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Create Build Environment @@ -39,7 +39,7 @@ jobs: # Use a bash shell so we can use the same syntax for environment variable # access regardless of the host operating system shell: bash - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=ON + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=ON -DBUILD_C_API=ON - name: Build working-directory: ${{runner.workspace}}/build @@ -64,7 +64,7 @@ jobs: run: cmake -E make_directory ${{runner.workspace}}/build - name: Configure - run: cmake -S $GITHUB_WORKSPACE -B ${{runner.workspace}}/build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=OFF -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-march=native -fsanitize=address,undefined -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer" + run: cmake -S $GITHUB_WORKSPACE -B ${{runner.workspace}}/build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=OFF -DBUILD_C_API=ON -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-march=native -fsanitize=address,undefined -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer" - name: Build run: cmake --build ${{runner.workspace}}/build -j8 @@ -79,18 +79,18 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{github.ref}} - + - name: Build the ZXingCpp.xcframework shell: sh working-directory: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios run: ./build-release.sh - + - name: Upload .xcframework uses: actions/upload-artifact@v3 with: name: ios-artifacts path: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios/ZXingCpp.xcframework - + - name: Build the demo app shell: sh working-directory: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios/demo @@ -99,13 +99,13 @@ jobs: build-android: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build the lib/app working-directory: wrappers/android run: ./gradlew assembleDebug # build only the debug version of the aar (faster build) - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: android-artifacts path: "wrappers/android/zxingcpp/build/outputs/aar/zxingcpp-debug.aar" @@ -113,8 +113,8 @@ jobs: build-wasm: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: mymindstorm/setup-emsdk@v7 + - uses: actions/checkout@v3 + - uses: mymindstorm/setup-emsdk@v12 - name: Configure run: emcmake cmake -Swrappers/wasm -Bbuild @@ -125,10 +125,12 @@ jobs: # - name: Test # run: node build/EmGlueTests.js - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: wasm-artifacts - path: "build/zxing*" + path: | + build/zxing* + build/demo* build-python: runs-on: ${{ matrix.os }} @@ -138,10 +140,10 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3acd79f7dd..3937ed1f5f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,11 +37,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -52,7 +52,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -66,4 +66,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000000..df51ce0721 --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,56 @@ +name: gh-pages + +on: + # Runs on pushes targeting the default branch +# push: +# branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup EMSDK + uses: mymindstorm/setup-emsdk@v12 + + - name: Configure + run: emcmake cmake -Swrappers/wasm -Bbuild + + - name: Build + run: cmake --build build -j4 + + - name: Prepare Archive + shell: sh + run: | + mkdir pages + mv build/zxing_* build/*.html pages + + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: 'pages' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.github/workflows/msvc-analysis.yml b/.github/workflows/msvc-analysis.yml new file mode 100644 index 0000000000..269f231f7e --- /dev/null +++ b/.github/workflows/msvc-analysis.yml @@ -0,0 +1,71 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# Find more information at: +# https://github.com/microsoft/msvc-code-analysis-action + +name: Microsoft C++ Code Analysis + +on: workflow_dispatch + +#on: +# push: +# branches: [ "master" ] +# pull_request: +# branches: [ "master" ] +# schedule: +# - cron: '20 18 * * 1' + +env: + # Path to the CMake build directory. + build: '${{ github.workspace }}/build' + config: 'Debug' + +permissions: + contents: read + +jobs: + analyze: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + name: Analyze + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Configure CMake + run: cmake -B ${{ env.build }} -DCMAKE_BUILD_TYPE=${{ env.config }} + + # Build is not required unless generated source files are used + # - name: Build CMake + # run: cmake --build ${{ env.build }} + + - name: Initialize MSVC Code Analysis + uses: microsoft/msvc-code-analysis-action@v0.1.1 + # Provide a unique ID to access the sarif output path + id: run-analysis + with: + cmakeBuildDirectory: ${{ env.build }} + buildConfiguration: ${{ env.config }} + # Ruleset file that will determine what checks will be run + ruleset: NativeRecommendedRules.ruleset + additionalArgs: /wd26451 # Suppress C26451, apparently bogous in VS2019 + + # Upload SARIF file to GitHub Code Scanning Alerts + - name: Upload SARIF to GitHub + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: ${{ steps.run-analysis.outputs.sarif }} + + # Upload SARIF file as an Artifact to download and view + # - name: Upload SARIF as an Artifact + # uses: actions/upload-artifact@v3 + # with: + # name: sarif-file + # path: ${{ steps.run-analysis.outputs.sarif }} diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index b342a99689..607d6b4ccb 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -3,7 +3,7 @@ name: build-python-dist on: release: types: [published] - + workflow_dispatch: inputs: publish: @@ -25,21 +25,25 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 + with: + python-version: '3.9' - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.11.2 + run: python3 -m pip install cibuildwheel==2.12.1 - name: Build wheels - run: python -m cibuildwheel --output-dir wheelhouse wrappers/python + run: python3 -m cibuildwheel --output-dir wheelhouse wrappers/python env: CIBW_BUILD: cp39-* cp310-* cp311-* CIBW_SKIP: "*musllinux*" + CIBW_ARCHS_MACOS: universal2 + CIBW_ENVIRONMENT_MACOS: CMAKE_OSX_ARCHITECTURES="arm64;x86_64" - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: path: ./wheelhouse/*.whl @@ -47,18 +51,20 @@ jobs: name: Build source distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 + with: + python-version: '3.9' - name: Build sdist working-directory: wrappers/python - run: python setup.py sdist + run: python3 setup.py sdist - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: path: wrappers/python/dist/*.tar.gz @@ -70,12 +76,12 @@ jobs: # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') if: github.event_name == 'release' || github.event.inputs.publish == 'y' steps: - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: artifact path: dist - - uses: pypa/gh-action-pypi-publish@master + - uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index a4223377f5..9992cb687a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ CMakeLists.txt.user *.lib *.d *.a - +compile_commands.json diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 616d962292..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -arch: - - amd64 - - ppc64le -language: cpp -dist: focal -os: - - linux - -script: - - mkdir build && cd build - - cmake -DBUILD_UNIT_TESTS=ON -DBUILD_BLACKBOX_TESTS=ON .. - - make -j10 - - ./test/unit/UnitTest - - ./test/blackbox/ReaderTest ../test/samples - - ./test/blackbox/WriterTest - - ./test/blackbox/ReaderTest *.png - - ./example/ZXingWriter QR_CODE QRCodeTestText test.png - - ./example/ZXingReader test.png diff --git a/CMakeLists.txt b/CMakeLists.txt index ab3dcafca2..87785add27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.14) +cmake_minimum_required(VERSION 3.15) project(ZXing) @@ -8,6 +8,8 @@ option (BUILD_EXAMPLES "Build the example barcode reader/writer applications" ON option (BUILD_BLACKBOX_TESTS "Build the black box reader/writer tests" OFF) option (BUILD_UNIT_TESTS "Build the unit tests (don't enable for production builds)" OFF) option (BUILD_PYTHON_MODULE "Build the python module" OFF) +option (BUILD_C_API "Build the C-API" OFF) +option (BUILD_EXPERIMENTAL_API "Build with experimental API" OFF) set(BUILD_DEPENDENCIES "AUTO" CACHE STRING "Fetch from github or use locally installed (AUTO/GITHUB/LOCAL)") if (WIN32) @@ -17,17 +19,10 @@ else() endif() if (MSVC) - option (LINK_CPP_STATICALLY "MSVC only, link standard library statically (/MT and /MTd)" OFF) - add_definitions (-DUNICODE -D_UNICODE -D_CRT_SECURE_NO_WARNINGS) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") - set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GS-") - set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GS-") + option (LINK_CPP_STATICALLY "MSVC only, link standard library statically (/MT and /MTd)" OFF) if (LINK_CPP_STATICALLY) - set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT") - set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") - set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd") - set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() endif() @@ -42,8 +37,12 @@ if (BUILD_SHARED_LIBS) set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) endif() -set (CMAKE_CXX_STANDARD 17) -set (CMAKE_CXX_EXTENSIONS OFF) +if (NOT CMAKE_CXX_STANDARD) + set (CMAKE_CXX_STANDARD 17) +endif() +if (NOT CMAKE_CXX_EXTENSIONS) + set (CMAKE_CXX_EXTENSIONS OFF) +endif() if (NOT (BUILD_READERS OR BUILD_WRITERS)) message(FATAL_ERROR "At least one of BUILD_READERS/BUILD_WRITERS must be enabled.") @@ -79,3 +78,6 @@ endif() if (BUILD_PYTHON_MODULE) add_subdirectory (wrappers/python) endif() +if (BUILD_C_API) + add_subdirectory (wrappers/c) +endif() diff --git a/README.md b/README.md index 4c98dc4c70..f47b910504 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,18 @@ ZXing-C++ ("zebra crossing") is an open-source, multi-format linear/matrix barco It was originally ported from the Java [ZXing Library](https://github.com/zxing/zxing) but has been developed further and now includes many improvements in terms of quality and performance. It can both read and write barcodes in a number of formats. +## Sponsors + +You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/axxel). + +Named Sponsors: +* [Liftric GmbH](https://github.com/Liftric) +* [KURZ Digital Solutions GmbH & Co. KG](https://github.com/kurzdigital) +* [Useful Sensors Inc](https://github.com/usefulsensors) +* [Sergio Olivo](https://github.com/sergio-) + +Thanks a lot for your contribution! + ## Features * Written in pure C++17 (/C++20), no third-party dependencies (for the library itself) @@ -50,14 +62,14 @@ As an example, have a look at [`ZXingWriter.cpp`](example/ZXingWriter.cpp). ## Web Demos - [Read barcodes](https://zxing-cpp.github.io/zxing-cpp/demo_reader.html) - [Write barcodes](https://zxing-cpp.github.io/zxing-cpp/demo_writer.html) -- [Scan with camera](https://zxing-cpp.github.io/zxing-cpp/zxing_viddemo.html) +- [Read barcodes from camera](https://zxing-cpp.github.io/zxing-cpp/demo_cam_reader.html) [Note: those live demos are not necessarily fully up-to-date at all times.] ## Build Instructions These are the generic instructions to build the library on Windows/macOS/Linux. For details on how to build the individual wrappers, follow the links above. -1. Make sure [CMake](https://cmake.org) version 3.14 or newer is installed. +1. Make sure [CMake](https://cmake.org) version 3.15 or newer is installed. 2. Make sure a C++17 compliant compiler is installed (minimum VS 2019 16.8 / gcc 7 / clang 5). 3. See the cmake `BUILD_...` options to enable the testing code, python wrapper, etc. diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index afdadbcfe8..24cebd93ab 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,6 +1,6 @@ -cmake_minimum_required (VERSION 3.14) +cmake_minimum_required(VERSION 3.15) -project (ZXing VERSION "2.0.0") +project (ZXing VERSION "2.1.0") set (ZXING_SONAME 3) # see https://github.com/zxing-cpp/zxing-cpp/issues/333 include(../zxing.cmake) @@ -27,6 +27,7 @@ set (ZXING_CORE_LOCAL_DEFINES $<$:-DZXING_BUILD_READERS> $<$:-DZXING_BUILD_WRITERS> $<$:-DZXING_BUILD_FOR_TEST> + $<$:-DZXING_BUILD_EXPERIMENTAL_API> ) if (MSVC) set (ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} @@ -34,6 +35,7 @@ if (MSVC) -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS -DNOMINMAX + /Zc:__cplusplus ) else() set (ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} @@ -330,8 +332,8 @@ if (BUILD_READERS) src/pdf417/PDFCodeword.h src/pdf417/PDFCodewordDecoder.h src/pdf417/PDFCodewordDecoder.cpp - src/pdf417/PDFDecodedBitStreamParser.h - src/pdf417/PDFDecodedBitStreamParser.cpp + src/pdf417/PDFDecoder.h + src/pdf417/PDFDecoder.cpp src/pdf417/PDFDecoderResultExtra.h src/pdf417/PDFDetectionResult.h src/pdf417/PDFDetectionResult.cpp @@ -439,7 +441,7 @@ add_library (ZXing ) target_include_directories (ZXing - PUBLIC "$" + PUBLIC "$" "$" INTERFACE "$" ) @@ -515,7 +517,7 @@ install ( DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ZXing" ) -if(MSVC) +if (MSVC) set_target_properties(ZXing PROPERTIES COMPILE_PDB_NAME ZXing COMPILE_PDB_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/core/ZXVersion.h.in b/core/ZXVersion.h.in index 78fde4106a..41dd401145 100644 --- a/core/ZXVersion.h.in +++ b/core/ZXVersion.h.in @@ -1,5 +1,6 @@ /* * Copyright 2019 Nu-book Inc. +* Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 @@ -9,3 +10,9 @@ #define ZXING_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ #define ZXING_VERSION_MINOR @PROJECT_VERSION_MINOR@ #define ZXING_VERSION_PATCH @PROJECT_VERSION_PATCH@ + +namespace ZXing { + +constexpr const char* ZXING_VERSION_STR = "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@"; + +} diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index 2952f2181c..4cbe5d5ef2 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -17,6 +17,36 @@ struct BinaryBitmap::Cache std::shared_ptr matrix; }; +BitMatrix BinaryBitmap::binarize(const uint8_t threshold) const +{ + BitMatrix res(width(), height()); + + if (_buffer.pixStride() == 1 && _buffer.rowStride() == _buffer.width()) { + // Specialize for a packed buffer with pixStride 1 to support auto vectorization (16x speedup on AVX2) + auto dst = res.row(0).begin(); + for (auto src = _buffer.data(0, 0), end = _buffer.data(0, height()); src != end; ++src, ++dst) + *dst = (*src <= threshold) * BitMatrix::SET_V; + } else { + auto processLine = [&res, threshold](int y, const auto* src, const int stride) { + for (auto& dst : res.row(y)) { + dst = (*src <= threshold) * BitMatrix::SET_V; + src += stride; + } + }; + for (int y = 0; y < res.height(); ++y) { + auto src = _buffer.data(0, y) + GreenIndex(_buffer.format()); + // Specialize the inner loop for strides 1 and 4 to support auto vectorization + switch (_buffer.pixStride()) { + case 1: processLine(y, src, 1); break; + case 4: processLine(y, src, 4); break; + default: processLine(y, src, _buffer.pixStride()); break; + } + } + } + + return res; +} + BinaryBitmap::BinaryBitmap(const ImageView& buffer) : _cache(new Cache), _buffer(buffer) {} BinaryBitmap::~BinaryBitmap() = default; @@ -36,4 +66,34 @@ void BinaryBitmap::invert() _inverted = true; } +template +void SumFilter(const BitMatrix& in, BitMatrix& out, F func) +{ + const auto* in0 = in.row(0).begin(); + const auto* in1 = in.row(1).begin(); + const auto* in2 = in.row(2).begin(); + + for (auto *out1 = out.row(1).begin() + 1, *end = out.row(out.height() - 1).begin() - 1; out1 != end; ++in0, ++in1, ++in2, ++out1) { + int sum = 0; + for (int j = 0; j < 3; ++j) + sum += in0[j] + in1[j] + in2[j]; + + *out1 = func(sum); + } +} + +void BinaryBitmap::close() +{ + if (_cache->matrix) { + auto& matrix = *const_cast(_cache->matrix.get()); + BitMatrix tmp(matrix.width(), matrix.height()); + + // dilate + SumFilter(matrix, tmp, [](int sum) { return (sum > 0 * BitMatrix::SET_V) * BitMatrix::SET_V; }); + // erode + SumFilter(tmp, matrix, [](int sum) { return (sum == 9 * BitMatrix::SET_V) * BitMatrix::SET_V; }); + } + _closed = true; +} + } // ZXing diff --git a/core/src/BinaryBitmap.h b/core/src/BinaryBitmap.h index 292b866910..1a44be09ef 100644 --- a/core/src/BinaryBitmap.h +++ b/core/src/BinaryBitmap.h @@ -28,6 +28,7 @@ class BinaryBitmap struct Cache; std::unique_ptr _cache; bool _inverted = false; + bool _closed = false; protected: const ImageView _buffer; @@ -39,6 +40,8 @@ class BinaryBitmap */ virtual std::shared_ptr getBlackMatrix() const = 0; + BitMatrix binarize(const uint8_t threshold) const; + public: BinaryBitmap(const ImageView& buffer); virtual ~BinaryBitmap(); @@ -55,6 +58,9 @@ class BinaryBitmap void invert(); bool inverted() const { return _inverted; } + + void close(); + bool closed() const { return _closed; } }; } // ZXing diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index f21a15460e..d943a1269a 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -10,8 +10,18 @@ #include #include +#if __has_include() && __cplusplus > 201703L // MSVC has the header but then warns about including it +#include +#if __cplusplus > 201703L && defined(__ANDROID__) // NDK 25.1.8937393 has the implementation but fails to advertise it +#define __cpp_lib_bitops 201907L +#endif +#endif + #if defined(__clang__) || defined(__GNUC__) #define ZX_HAS_GCC_BUILTINS +#elif defined(_MSC_VER) && !defined(_M_ARM) && !defined(_M_ARM64) +#include +#define ZX_HAS_MSC_BUILTINS #endif namespace ZXing::BitHacks { @@ -24,41 +34,98 @@ namespace ZXing::BitHacks { /// /// Compute the number of zero bits on the left. /// -inline int NumberOfLeadingZeros(uint32_t x) +template>> +inline int NumberOfLeadingZeros(T x) { - if (x == 0) - return 32; +#ifdef __cpp_lib_bitops + return std::countl_zero(static_cast>(x)); +#else + if constexpr (sizeof(x) <= 4) { + if (x == 0) + return 32; +#ifdef ZX_HAS_GCC_BUILTINS + return __builtin_clz(x); +#elif defined(ZX_HAS_MSC_BUILTINS) + return __lzcnt(x); +#else + int n = 0; + if ((x & 0xFFFF0000) == 0) { n = n + 16; x = x << 16; } + if ((x & 0xFF000000) == 0) { n = n + 8; x = x << 8; } + if ((x & 0xF0000000) == 0) { n = n + 4; x = x << 4; } + if ((x & 0xC0000000) == 0) { n = n + 2; x = x << 2; } + if ((x & 0x80000000) == 0) { n = n + 1; } + return n; +#endif + } else { + if (x == 0) + return 64; #ifdef ZX_HAS_GCC_BUILTINS - return __builtin_clz(x); + return __builtin_clzll(x); +#elif defined(ZX_HAS_MSC_BUILTINS) + return __lzcnt64(x); #else - int n = 0; - if ((x & 0xFFFF0000) == 0) { n = n + 16; x = x << 16; } - if ((x & 0xFF000000) == 0) { n = n + 8; x = x << 8; } - if ((x & 0xF0000000) == 0) { n = n + 4; x = x << 4; } - if ((x & 0xC0000000) == 0) { n = n + 2; x = x << 2; } - if ((x & 0x80000000) == 0) { n = n + 1; } - return n; + int n = NumberOfLeadingZeros(static_cast(x >> 32)); + if (n == 32) + n += NumberOfLeadingZeros(static_cast(x)); + return n; +#endif + } #endif } /// /// Compute the number of zero bits on the right. /// -inline int NumberOfTrailingZeros(uint32_t v) +template>> +inline int NumberOfTrailingZeros(T v) { -#ifdef ZX_HAS_GCC_BUILTINS assert(v != 0); - return __builtin_ctz(v); +#ifdef __cpp_lib_bitops + return std::countr_zero(static_cast>(v)); +#else + if constexpr (sizeof(v) <= 4) { +#ifdef ZX_HAS_GCC_BUILTINS + return __builtin_ctz(v); +#elif defined(ZX_HAS_MSC_BUILTINS) + unsigned long where; + if (_BitScanForward(&where, v)) + return static_cast(where); + return 32; +#else + int c = 32; + v &= -int32_t(v); + if (v) c--; + if (v & 0x0000FFFF) c -= 16; + if (v & 0x00FF00FF) c -= 8; + if (v & 0x0F0F0F0F) c -= 4; + if (v & 0x33333333) c -= 2; + if (v & 0x55555555) c -= 1; + return c; +#endif + } else { +#ifdef ZX_HAS_GCC_BUILTINS + return __builtin_ctzll(v); +#elif defined(ZX_HAS_MSC_BUILTINS) + unsigned long where; + #if defined(_WIN64) + if (_BitScanForward64(&where, v)) + return static_cast(where); + #elif defined(_WIN32) + if (_BitScanForward(&where, static_cast(v))) + return static_cast(where); + if (_BitScanForward(&where, static_cast(v >> 32))) + return static_cast(where + 32); + #else + #error "Implementation of __builtin_ctzll required" + #endif + return 64; #else - int c = 32; - v &= -int32_t(v); - if (v) c--; - if (v & 0x0000FFFF) c -= 16; - if (v & 0x00FF00FF) c -= 8; - if (v & 0x0F0F0F0F) c -= 4; - if (v & 0x33333333) c -= 2; - if (v & 0x55555555) c -= 1; - return c; + int n = NumberOfTrailingZeros(static_cast(v)); + if (n == 32) + n += NumberOfTrailingZeros(static_cast(v >> 32)); + return n; +#endif + } #endif } @@ -82,7 +149,9 @@ inline uint32_t Reverse(uint32_t v) inline int CountBitsSet(uint32_t v) { -#ifdef ZX_HAS_GCC_BUILTINS +#ifdef __cpp_lib_bitops + return std::popcount(v); +#elif defined(ZX_HAS_GCC_BUILTINS) return __builtin_popcount(v); #else v = v - ((v >> 1) & 0x55555555); // reuse input as temporary @@ -134,4 +203,14 @@ void Reverse(std::vector& bits, std::size_t padding) ShiftRight(bits, padding); } +// use to avoid "load of misaligned address" when using a simple type cast +template +T LoadU(const void* ptr) +{ + static_assert(std::is_integral::value, "T must be an integer"); + T res; + memcpy(&res, ptr, sizeof(T)); + return res; +} + } // namespace ZXing::BitHacks diff --git a/core/src/BitMatrix.cpp b/core/src/BitMatrix.cpp index 018bec31cc..729a5752b3 100644 --- a/core/src/BitMatrix.cpp +++ b/core/src/BitMatrix.cpp @@ -6,7 +6,6 @@ #include "BitMatrix.h" -#include "BitArray.h" #include "Pattern.h" #include @@ -30,7 +29,7 @@ BitMatrix::setRegion(int left, int top, int width, int height) throw std::invalid_argument("BitMatrix::setRegion(): The region must fit inside the matrix"); } for (int y = top; y < bottom; y++) { - size_t offset = y * _width; + auto offset = y * _width; for (int x = left; x < right; x++) { _bits[offset + x] = SET_V; } diff --git a/core/src/BitMatrix.h b/core/src/BitMatrix.h index 128f3495fc..c063d0e0ac 100644 --- a/core/src/BitMatrix.h +++ b/core/src/BitMatrix.h @@ -30,9 +30,6 @@ class BitMatrix int _width = 0; int _height = 0; using data_t = uint8_t; - static constexpr data_t SET_V = 0xff; // allows playing with SIMD binarization - static constexpr data_t UNSET_V = 0; - static_assert(bool(SET_V) && !bool(UNSET_V), "SET_V needs to evaluate to true, UNSET_V to false, see iterator usage"); std::vector _bits; // There is nothing wrong to support this but disable to make it explicit since we may copy something very big here. @@ -55,9 +52,13 @@ class BitMatrix bool getBottomRightOnBit(int &right, int& bottom) const; public: + static constexpr data_t SET_V = 0xff; // allows playing with SIMD binarization + static constexpr data_t UNSET_V = 0; + static_assert(bool(SET_V) && !bool(UNSET_V), "SET_V needs to evaluate to true, UNSET_V to false, see iterator usage"); + BitMatrix() = default; -#ifdef __GNUC__ +#if defined(__llvm__) || (defined(__GNUC__) && (__GNUC__ > 7)) __attribute__((no_sanitize("signed-integer-overflow"))) #endif BitMatrix(int width, int height) : _width(width), _height(height), _bits(width * height, UNSET_V) @@ -76,7 +77,10 @@ class BitMatrix Range row(int y) { return {_bits.data() + y * _width, _bits.data() + (y + 1) * _width}; } Range row(int y) const { return {_bits.data() + y * _width, _bits.data() + (y + 1) * _width}; } - Range> col(int x) const { return {{_bits.data() + x, _width}, {_bits.data() + x + _height * _width, _width}}; } + Range> col(int x) const + { + return {{_bits.data() + x + (_height - 1) * _width, -_width}, {_bits.data() + x - _width, -_width}}; + } bool get(int x, int y) const { return get(y * _width + x); } void set(int x, int y, bool val = true) { get(y * _width + x) = val * SET_V; } @@ -96,7 +100,7 @@ class BitMatrix void flipAll() { for (auto& i : _bits) - i = !i; + i = !i * SET_V; } /** diff --git a/core/src/BitMatrixCursor.h b/core/src/BitMatrixCursor.h index 7e1f16c440..bdfc523ed6 100644 --- a/core/src/BitMatrixCursor.h +++ b/core/src/BitMatrixCursor.h @@ -8,6 +8,7 @@ #include "BitMatrix.h" #include +#include namespace ZXing { @@ -27,6 +28,8 @@ inline Direction opposite(Direction dir) noexcept template class BitMatrixCursor { + using this_t = BitMatrixCursor; + public: const BitMatrix* img; @@ -89,8 +92,8 @@ class BitMatrixCursor Value edgeAtRight() const noexcept { return edgeAt(right()); } Value edgeAt(Direction dir) const noexcept { return edgeAt(direction(dir)); } - void setDirection(PointF dir) { d = bresenhamDirection(dir); } - void setDirection(PointI dir) { d = dir; } + this_t& setDirection(PointF dir) { return d = bresenhamDirection(dir), *this; } + this_t& setDirection(PointI dir) { return d = dir, *this; } bool step(typename POINT::value_t s = 1) { @@ -98,12 +101,8 @@ class BitMatrixCursor return isIn(p); } - BitMatrixCursor movedBy(POINT d) const - { - auto res = *this; - res.p += d; - return res; - } + this_t movedBy(POINT o) const noexcept { return {*img, p + o, d}; } + this_t turnedBack() const noexcept { return {*img, p, back()}; } /** * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge. @@ -114,7 +113,6 @@ class BitMatrixCursor */ int stepToEdge(int nth = 1, int range = 0, bool backup = false) { - // TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt() int steps = 0; auto lv = testAt(p); @@ -155,11 +153,11 @@ class BitMatrixCursor return ret; } - int countEdges(int range = 0) + int countEdges(int range) { int res = 0; - while (int steps = stepToEdge(1, range)) { + while (int steps = range ? stepToEdge(1, range) : 0) { range -= steps; ++res; } @@ -171,8 +169,13 @@ class BitMatrixCursor ARRAY readPattern(int range = 0) { ARRAY res; - for (auto& i : res) + for (auto& i : res) { i = stepToEdge(1, range); + if (!i) + return res; + if (range) + range -= i; + } return res; } @@ -188,4 +191,41 @@ class BitMatrixCursor using BitMatrixCursorF = BitMatrixCursor; using BitMatrixCursorI = BitMatrixCursor; +class FastEdgeToEdgeCounter +{ + const uint8_t* p = nullptr; + int stride = 0; + int stepsToBorder = 0; + +public: + FastEdgeToEdgeCounter(const BitMatrixCursorI& cur) + { + stride = cur.d.y * cur.img->width() + cur.d.x; + p = cur.img->row(cur.p.y).begin() + cur.p.x; + + int maxStepsX = cur.d.x ? (cur.d.x > 0 ? cur.img->width() - 1 - cur.p.x : cur.p.x) : INT_MAX; + int maxStepsY = cur.d.y ? (cur.d.y > 0 ? cur.img->height() - 1 - cur.p.y : cur.p.y) : INT_MAX; + stepsToBorder = std::min(maxStepsX, maxStepsY); + } + + int stepToNextEdge(int range) + { + int maxSteps = std::min(stepsToBorder, range); + int steps = 0; + do { + if (++steps > maxSteps) { + if (maxSteps == stepsToBorder) + break; + else + return 0; + } + } while (p[steps * stride] == p[0]); + + p += steps * stride; + stepsToBorder -= steps; + + return steps; + } +}; + } // ZXing diff --git a/core/src/BitMatrixIO.cpp b/core/src/BitMatrixIO.cpp index 36d8d83eb4..685e9cf236 100644 --- a/core/src/BitMatrixIO.cpp +++ b/core/src/BitMatrixIO.cpp @@ -6,8 +6,6 @@ #include "BitMatrixIO.h" -#include "BitArray.h" - #include #include diff --git a/core/src/ByteMatrix.h b/core/src/ByteMatrix.h index 09dc1eac32..e94f589c7e 100644 --- a/core/src/ByteMatrix.h +++ b/core/src/ByteMatrix.h @@ -20,8 +20,8 @@ class ByteMatrix : public Matrix public: ByteMatrix() = default; ByteMatrix(int width, int height, int8_t val = 0) : Matrix(width, height, val) {} - ByteMatrix(ByteMatrix&&) = default; - ByteMatrix& operator=(ByteMatrix&&) = default; + ByteMatrix(ByteMatrix&&) noexcept = default; + ByteMatrix& operator=(ByteMatrix&&) noexcept = default; }; } // ZXing diff --git a/core/src/CharacterSet.cpp b/core/src/CharacterSet.cpp index fb84199f9f..8676ad0950 100644 --- a/core/src/CharacterSet.cpp +++ b/core/src/CharacterSet.cpp @@ -9,6 +9,7 @@ #include "ZXAlgorithms.h" #include +#include namespace ZXing { diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index c08b570dfd..c21a6455e3 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -7,6 +7,7 @@ #include "LogMatrix.h" #include "RegressionLine.h" +#include "ZXAlgorithms.h" namespace ZXing { @@ -38,9 +39,17 @@ std::optional CenterOfDoubleCross(const BitMatrix& image, PointI center, std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle) { + // range is the approximate width/height of the nth ring, if nth>1 then it would be plausible to limit the search radius + // to approximately range / 2 * sqrt(2) == range * 0.75 but it turned out to be too limiting with realworld/noisy data. + int radius = range; + bool inner = nth < 0; + nth = std::abs(nth); + log(center, 3); BitMatrixCursorI cur(image, center, {0, 1}); - cur.stepToEdge(nth, range); - cur.turnRight(); // move clock wise and keep edge on the right + if (!cur.stepToEdge(nth, radius, inner)) + return {}; + cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup + const auto edgeDir = inner ? Direction::LEFT : Direction::RIGHT; uint32_t neighbourMask = 0; auto start = cur.p; @@ -54,11 +63,11 @@ std::optional CenterOfRing(const BitMatrix& image, PointI center, int ra // find out if we come full circle around the center. 8 bits have to be set in the end. neighbourMask |= (1 << (4 + dot(bresenhamDirection(cur.p - center), PointI(1, 3)))); - if (!cur.stepAlongEdge(Direction::RIGHT)) + if (!cur.stepAlongEdge(edgeDir)) return {}; // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate - if (maxAbsComponent(cur.p - center) > range || center == cur.p || n > 4 * 2 * range) + if (maxAbsComponent(cur.p - center) > radius || center == cur.p || n > 4 * 2 * range) return {}; } while (cur.p != start); @@ -68,43 +77,34 @@ std::optional CenterOfRing(const BitMatrix& image, PointI center, int ra return sum / n; } -std::optional CenterOfRings(const BitMatrix& image, PointI center, int range, int numOfRings) +std::optional CenterOfRings(const BitMatrix& image, PointF center, int range, int numOfRings) { - PointF sum = {}; - int n = 0; - for (int i = 0; i < numOfRings; ++i) { - auto c = CenterOfRing(image, center, range, i + 1); - if (!c) + int n = 1; + PointF sum = center; + for (int i = 2; i < numOfRings + 1; ++i) { + auto c = CenterOfRing(image, PointI(center), range, i); + if (!c) { + if (n == 1) + return {}; + else + return sum / n; + } else if (distance(*c, center) > range / numOfRings / 2) { return {}; - // TODO: decide whether this wheighting depending on distance to the center is worth it - int weight = numOfRings - i; - sum += weight * *c; - n += weight; + } + + sum += *c; + n++; } return sum / n; } -std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize) -{ - // make sure we have at least one path of white around the center - if (!CenterOfRing(image, PointI(center), range, 1)) - return {}; - - auto res = CenterOfRings(image, PointI(center), range, finderPatternSize / 2); - if (!res || !image.get(*res)) - res = CenterOfDoubleCross(image, PointI(center), range, finderPatternSize / 2 + 1); - if (!res || !image.get(*res)) - res = center; - if (!res || !image.get(*res)) - return {}; - return res; -} - static std::vector CollectRingPoints(const BitMatrix& image, PointF center, int range, int edgeIndex, bool backup) { PointI centerI(center); + int radius = range; BitMatrixCursorI cur(image, centerI, {0, 1}); - cur.stepToEdge(edgeIndex, range, backup); + if (!cur.stepToEdge(edgeIndex, radius, backup)) + return {}; cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup const auto edgeDir = backup ? Direction::LEFT : Direction::RIGHT; @@ -124,7 +124,7 @@ static std::vector CollectRingPoints(const BitMatrix& image, PointF cent return {}; // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate - if (maxAbsComponent(cur.p - center) > range || centerI == cur.p || Size(points) > 4 * 2 * range) + if (maxAbsComponent(cur.p - centerI) > radius || centerI == cur.p || Size(points) > 4 * 2 * range) return {}; } while (cur.p != start); @@ -157,6 +157,21 @@ static std::optional FitQadrilateralToPoints(PointF center, std: if (std::any_of(lines.begin(), lines.end(), [](auto line) { return !line.isValid(); })) return {}; + std::array beg = {corners[0] + 1, corners[1] + 1, corners[2] + 1, corners[3] + 1}; + std::array end = {corners[1], corners[2], corners[3], &points.back() + 1}; + + // check if all points belonging to each line segment are sufficiently close to that line + for (int i = 0; i < 4; ++i) + for (const PointF* p = beg[i]; p != end[i]; ++p) { + auto len = std::distance(beg[i], end[i]); + if (len > 3 && lines[i].distance(*p) > std::max(1., std::min(8., len / 8.))) { +#ifdef PRINT_DEBUG + printf("%d: %.2f > %.2f @ %.fx%.f\n", i, lines[i].distance(*p), std::distance(beg[i], end[i]) / 1., p->x, p->y); +#endif + return {}; + } + } + QuadrilateralF res; for (int i = 0; i < 4; ++i) res[i] = intersect(lines[i], lines[(i + 1) % 4]); @@ -168,46 +183,41 @@ static bool QuadrilateralIsPlausibleSquare(const QuadrilateralF q, int lineIndex { double m, M; m = M = distance(q[0], q[3]); - for (int i = 1; i < 4; ++i) { - double d = distance(q[i - 1], q[i]); - m = std::min(m, d); - M = std::max(M, d); - } + for (int i = 1; i < 4; ++i) + UpdateMinMax(m, M, distance(q[i - 1], q[i])); return m >= lineIndex * 2 && m > M / 3; } -std::optional FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int lineIndex) +static std::optional FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup) { - auto innerPoints = CollectRingPoints(image, center, range, lineIndex, false); - auto outerPoints = CollectRingPoints(image, center, range, lineIndex + 1, true); - - if (innerPoints.empty() || outerPoints.empty()) + auto points = CollectRingPoints(image, center, range, lineIndex, backup); + if (points.empty()) return {}; - auto oInnerCorners = FitQadrilateralToPoints(center, innerPoints); - if (!oInnerCorners || !QuadrilateralIsPlausibleSquare(*oInnerCorners, lineIndex)) + auto res = FitQadrilateralToPoints(center, points); + if (!res || !QuadrilateralIsPlausibleSquare(*res, lineIndex - backup)) return {}; - auto oOuterCorners = FitQadrilateralToPoints(center, outerPoints); - if (!oOuterCorners || !QuadrilateralIsPlausibleSquare(*oOuterCorners, lineIndex)) - return {}; + return res; +} - auto& innerCorners = *oInnerCorners; - auto& outerCorners = *oOuterCorners; +std::optional FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int lineIndex) +{ + auto innerCorners = FitSquareToPoints(image, center, range, lineIndex, false); + if (!innerCorners) + return {}; - auto dist2First = [c = innerCorners[0]](auto a, auto b) { return distance(a, c) < distance(b, c); }; - // rotate points such that the the two topLeft points are closest to each other - std::rotate(outerCorners.begin(), std::min_element(outerCorners.begin(), outerCorners.end(), dist2First), outerCorners.end()); + auto outerCorners = FitSquareToPoints(image, center, range, lineIndex + 1, true); + if (!outerCorners) + return {}; - QuadrilateralF res; - for (int i=0; i<4; ++i) - res[i] = (innerCorners[i] + outerCorners[i]) / 2; + auto res = Blend(*innerCorners, *outerCorners); - for (auto p : innerCorners) + for (auto p : *innerCorners) log(p, 3); - for (auto p : outerCorners) + for (auto p : *outerCorners) log(p, 3); for (auto p : res) @@ -216,4 +226,21 @@ std::optional FindConcentricPatternCorners(const BitMatrix& imag return res; } +std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize) +{ + // make sure we have at least one path of white around the center + if (auto res1 = CenterOfRing(image, PointI(center), range, 1); res1 && image.get(*res1)) { + // and then either at least one more ring around that + if (auto res2 = CenterOfRings(image, *res1, range, finderPatternSize / 2); res2 && image.get(*res2)) + return res2; + // or the center can be approximated by a square + if (FitSquareToPoints(image, *res1, range, 1, false)) + return res1; + // TODO: this is currently only keeping #258 alive, evaluate if still worth it + if (auto res2 = CenterOfDoubleCross(image, PointI(*res1), range, finderPatternSize / 2 + 1); res2 && image.get(*res2)) + return res2; + } + return {}; +} + } // ZXing diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index e9be41b5b8..02c72c171c 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -27,43 +27,74 @@ static float CenterFromEnd(const std::array& pattern, float end) float b = (pattern[2] + pattern[1] + pattern[0]) / 2.f; return end - (2 * a + b) / 3; } else { // aztec - auto a = std::accumulate(&pattern[N/2 + 1], &pattern[N], pattern[N/2] / 2.f); + auto a = std::accumulate(pattern.begin() + (N/2 + 1), pattern.end(), pattern[N/2] / 2.f); return end - a; } } -template -std::optional ReadSymmetricPattern(Cursor& cur, int range) +template +std::optional> ReadSymmetricPattern(Cursor& cur, int range) { - if (!cur.stepToEdge(std::tuple_size::value / 2 + 1, range)) - return std::nullopt; - - cur.turnBack(); - cur.step(); + static_assert(N % 2 == 1); + assert(range > 0); + Pattern res = {}; + auto constexpr s_2 = Size(res)/2; + auto cuo = cur.turnedBack(); + + auto next = [&](auto& cur, int i) { + auto v = cur.stepToEdge(1, range); + res[s_2 + i] += v; + if (range) + range -= v; + return v; + }; + + for (int i = 0; i <= s_2; ++i) { + if (!next(cur, i) || !next(cuo, -i)) + return {}; + } + res[s_2]--; // the starting pixel has been counted twice, fix this - auto pattern = cur.template readPattern(range); - if (pattern.back() == 0) - return std::nullopt; - return pattern; + return res; } -template -int CheckDirection(BitMatrixCursorF& cur, PointF dir, FinderPattern finderPattern, int range, bool updatePosition) +template +int CheckSymmetricPattern(BitMatrixCursorI& cur, PATTERN pattern, int range, bool updatePosition) { - using Pattern = std::array; + FastEdgeToEdgeCounter curFwd(cur), curBwd(cur.turnedBack()); + + int centerFwd = curFwd.stepToNextEdge(range); + if (!centerFwd) + return 0; + int centerBwd = curBwd.stepToNextEdge(range); + if (!centerBwd) + return 0; + + assert(range > 0); + Pattern res = {}; + auto constexpr s_2 = Size(res)/2; + res[s_2] = centerFwd + centerBwd - 1; // -1 because the starting pixel is counted twice + range -= res[s_2]; + + auto next = [&](auto& cur, int i) { + auto v = cur.stepToNextEdge(range); + res[s_2 + i] = v; + range -= v; + return v; + }; + + for (int i = 1; i <= s_2; ++i) { + if (!next(curFwd, i) || !next(curBwd, -i)) + return 0; + } - auto pOri = cur.p; - cur.setDirection(dir); - auto pattern = ReadSymmetricPattern(cur, range); - if (!pattern || !IsPattern(*pattern, finderPattern)) + if (!IsPattern(res, pattern)) return 0; if (updatePosition) - cur.step(CenterFromEnd(*pattern, 0.5) - 1); - else - cur.p = pOri; + cur.step(res[s_2] / 2 - (centerBwd - 1)); - return Reduce(*pattern); + return Reduce(res); } std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle = true); @@ -77,33 +108,36 @@ struct ConcentricPattern : public PointF int size = 0; }; -template -std::optional LocateConcentricPattern(const BitMatrix& image, FINDER_PATTERN finderPattern, PointF center, int range) +template +std::optional LocateConcentricPattern(const BitMatrix& image, PATTERN pattern, PointF center, int range) { - auto cur = BitMatrixCursorF(image, center, {}); + auto cur = BitMatrixCursor(image, PointI(center), {}); int minSpread = image.width(), maxSpread = 0; - for (auto d : {PointF{0, 1}, {1, 0}}) { - int spread = CheckDirection(cur, d, finderPattern, range, !RELAXED_THRESHOLD); - if (!spread) + // TODO: setting maxError to 1 can subtantially help with detecting symbols with low print quality resulting in damaged + // finder patterns, but it sutantially increases the runtime (approx. 20% slower for the falsepositive images). + int maxError = 0; + for (auto d : {PointI{0, 1}, {1, 0}}) { + int spread = CheckSymmetricPattern(cur.setDirection(d), pattern, range, true); + if (spread) + UpdateMinMax(minSpread, maxSpread, spread); + else if (--maxError < 0) return {}; - minSpread = std::min(spread, minSpread); - maxSpread = std::max(spread, maxSpread); } #if 1 - for (auto d : {PointF{1, 1}, {1, -1}}) { - int spread = CheckDirection(cur, d, finderPattern, range, false); - if (!spread) + for (auto d : {PointI{1, 1}, {1, -1}}) { + int spread = CheckSymmetricPattern(cur.setDirection(d), pattern, range * 2, false); + if (spread) + UpdateMinMax(minSpread, maxSpread, spread); + else if (--maxError < 0) return {}; - minSpread = std::min(spread, minSpread); - maxSpread = std::max(spread, maxSpread); } #endif if (maxSpread > 5 * minSpread) return {}; - auto newCenter = FinetuneConcentricPatternCenter(image, cur.p, range, finderPattern.size()); + auto newCenter = FinetuneConcentricPatternCenter(image, PointF(cur.p), range, pattern.size()); if (!newCenter) return {}; diff --git a/core/src/Content.cpp b/core/src/Content.cpp index 71d87b5c38..4d3c44657c 100644 --- a/core/src/Content.cpp +++ b/core/src/Content.cpp @@ -12,6 +12,8 @@ #include "Utf.h" #include "ZXAlgorithms.h" +#include + namespace ZXing { std::string ToString(ContentType type) diff --git a/core/src/DecodeHints.h b/core/src/DecodeHints.h index 97069d6a8c..facee2d7ef 100644 --- a/core/src/DecodeHints.h +++ b/core/src/DecodeHints.h @@ -63,6 +63,9 @@ class DecodeHints Binarizer _binarizer : 2; TextMode _textMode : 3; CharacterSet _characterSet : 6; +#ifdef BUILD_EXPERIMENTAL_API + bool _tryDenoise : 1; +#endif uint8_t _minLineCount = 2; uint8_t _maxNumberOfSymbols = 0xff; @@ -87,6 +90,10 @@ class DecodeHints _binarizer(Binarizer::LocalAverage), _textMode(TextMode::HRI), _characterSet(CharacterSet::Unknown) +#ifdef BUILD_EXPERIMENTAL_API + , + _tryDenoise(0) +#endif {} #define ZX_PROPERTY(TYPE, GETTER, SETTER) \ @@ -109,6 +116,11 @@ class DecodeHints /// Also try detecting code in downscaled images (depending on image size). ZX_PROPERTY(bool, tryDownscale, setTryDownscale) +#ifdef BUILD_EXPERIMENTAL_API + /// Also try detecting code after denoising (currently morphological closing filter for 2D symbologies only). + ZX_PROPERTY(bool, tryDenoise, setTryDenoise) +#endif + /// Binarizer to use internally when using the ReadBarcode function ZX_PROPERTY(Binarizer, binarizer, setBinarizer) diff --git a/core/src/DecoderResult.h b/core/src/DecoderResult.h index cf3492ebe4..02b2850841 100644 --- a/core/src/DecoderResult.h +++ b/core/src/DecoderResult.h @@ -6,11 +6,9 @@ #pragma once -#include "ByteArray.h" #include "Content.h" #include "Error.h" #include "StructuredAppend.h" -#include "ZXAlgorithms.h" #include #include @@ -41,7 +39,7 @@ class DecoderResult DecoderResult(Content&& bytes) : _content(std::move(bytes)) {} DecoderResult(DecoderResult&&) noexcept = default; - DecoderResult& operator=(DecoderResult&&) = default; + DecoderResult& operator=(DecoderResult&&) noexcept = default; bool isValid(bool includeErrors = false) const { diff --git a/core/src/DetectorResult.h b/core/src/DetectorResult.h index dbeffe0b02..fe15d04ec3 100644 --- a/core/src/DetectorResult.h +++ b/core/src/DetectorResult.h @@ -28,8 +28,8 @@ class DetectorResult public: DetectorResult() = default; - DetectorResult(DetectorResult&&) = default; - DetectorResult& operator=(DetectorResult&&) = default; + DetectorResult(DetectorResult&&) noexcept = default; + DetectorResult& operator=(DetectorResult&&) noexcept = default; DetectorResult(BitMatrix&& bits, QuadrilateralI&& position) : _bits(std::move(bits)), _position(std::move(position)) {} diff --git a/core/src/GenericGFPoly.cpp b/core/src/GenericGFPoly.cpp index a2634f82d1..1ec9f84fd3 100644 --- a/core/src/GenericGFPoly.cpp +++ b/core/src/GenericGFPoly.cpp @@ -8,7 +8,6 @@ #include "GenericGFPoly.h" #include "GenericGF.h" -#include "ZXConfig.h" #include "ZXAlgorithms.h" #include diff --git a/core/src/GenericGFPoly.h b/core/src/GenericGFPoly.h index 70ce6f1a70..331cf5bcd7 100644 --- a/core/src/GenericGFPoly.h +++ b/core/src/GenericGFPoly.h @@ -68,8 +68,8 @@ class GenericGFPoly } GenericGFPoly(const GenericGF& field, const std::vector& coefficients) : GenericGFPoly(field, std::vector(coefficients)) {} - GenericGFPoly& operator=(GenericGFPoly&& other) = default; - GenericGFPoly(GenericGFPoly&& other) = default; + GenericGFPoly& operator=(GenericGFPoly&& other) noexcept = default; + GenericGFPoly(GenericGFPoly&& other) noexcept = default; GenericGFPoly& operator=(const GenericGFPoly& other) { assert(_field == other._field); diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index f09d040d22..44ca15810c 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -7,7 +7,7 @@ #include "GlobalHistogramBinarizer.h" #include "BitMatrix.h" -#include "ByteArray.h" +#include "Pattern.h" #include #include @@ -17,16 +17,45 @@ namespace ZXing { -static const int LUMINANCE_BITS = 5; -static const int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; -static const int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; +static constexpr int LUMINANCE_BITS = 5; +static constexpr int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; +static constexpr int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; + +using Histogram = std::array; GlobalHistogramBinarizer::GlobalHistogramBinarizer(const ImageView& buffer) : BinaryBitmap(buffer) {} GlobalHistogramBinarizer::~GlobalHistogramBinarizer() = default; +using ImageLineView = Range>; + +inline ImageLineView RowView(const ImageView& iv, int row) +{ + return {{iv.data(0, row), iv.pixStride()}, {iv.data(iv.width(), row), iv.pixStride()}}; +} + +static void ThresholdSharpened(const ImageLineView in, int threshold, std::vector& out) +{ + out.resize(in.size()); + auto i = in.begin(); + auto o = out.begin(); + + *o++ = (*i++ <= threshold) * BitMatrix::SET_V; + for (auto end = in.end() - 1; i != end; ++i) + *o++ = ((-i[-1] + (int(i[0]) * 4) - i[1]) / 2 <= threshold) * BitMatrix::SET_V; + *o++ = (*i++ <= threshold) * BitMatrix::SET_V; +} + +static auto GenHistogram(const ImageLineView line) +{ + Histogram res = {}; + for (auto pix : line) + res[pix >> LUMINANCE_SHIFT]++; + return res; +} + // Return -1 on error -static int EstimateBlackPoint(const std::array& buckets) +static int EstimateBlackPoint(const Histogram& buckets) { // Find the tallest peak in the histogram. auto firstPeakPos = std::max_element(buckets.begin(), buckets.end()); @@ -37,7 +66,7 @@ static int EstimateBlackPoint(const std::array& buckets) // Find the second-tallest peak which is somewhat far from the tallest peak. int secondPeak = 0; int secondPeakScore = 0; - for (int x = 0; x < LUMINANCE_BUCKETS; x++) { + for (int x = 0; x < Size(buckets); x++) { int distanceToBiggest = x - firstPeak; // Encourage more distant second peaks by multiplying by square of distance. int score = buckets[x] * distanceToBiggest * distanceToBiggest; @@ -76,48 +105,35 @@ static int EstimateBlackPoint(const std::array& buckets) bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const { auto buffer = _buffer.rotated(rotation); + auto lineView = RowView(buffer, row); if (buffer.width() < 3) return false; // special casing the code below for a width < 3 makes no sense - res.clear(); - - const uint8_t* luminances = buffer.data(0, row); - const int pixStride = buffer.pixStride(); - std::array buckets = {}; - for (int x = 0; x < buffer.width(); x++) - buckets[luminances[x * pixStride] >> LUMINANCE_SHIFT]++; +#ifdef __AVX__ + // If we are extracting a column (instead of a row), we run into cache misses on every pixel access both + // during the histogram caluculation and during the sharpen+threshold operation. Additionally, if we + // perform the ThresholdSharpened function on pixStride==1 data, the auto-vectorizer makes that part + // 8x faster on an AVX2 cpu which easily recovers the extra cost that we pay for the copying. + thread_local std::vector line; + if (std::abs(buffer.pixStride()) > 4) { + line.resize(lineView.size()); + std::copy(lineView.begin(), lineView.end(), line.begin()); + lineView = {{line.data(), 1}, {line.data() + line.size(), 1}}; + } +#endif - int blackPoint = EstimateBlackPoint(buckets); - if (blackPoint <= 0) + auto threshold = EstimateBlackPoint(GenHistogram(lineView)) - 1; + if (threshold <= 0) return false; - auto* lastPos = luminances; - bool lastVal = luminances[0] < blackPoint; - if (lastVal) - res.push_back(0); // first value is number of white pixels, here 0 - - auto process = [&](bool val, const uint8_t* p) { - if (val != lastVal) { - res.push_back(narrow_cast((p - lastPos) / pixStride)); - lastVal = val; - lastPos = p; - } - }; - - for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p < e; p += pixStride) - process((-*(p - pixStride) + (int(*p) * 4) - *(p + pixStride)) / 2 < blackPoint, p); - - auto* backPos = buffer.data(buffer.width() - 1, row); - bool backVal = *backPos < blackPoint; - process(backVal, backPos); - - res.push_back(narrow_cast((backPos - lastPos) / pixStride + 1)); - - if (backVal) - res.push_back(0); // last value is number of white pixels, here 0 - - assert(res.size() % 2 == 1); + thread_local std::vector binarized; + // the optimizer can generate a specialized version for pixStride==1 (non-rotated input) that is about 8x faster on AVX2 hardware + if (lineView.begin().stride == 1) + ThresholdSharpened(lineView, threshold, binarized); + else + ThresholdSharpened(lineView, threshold, binarized); + GetPatternRow(Range(binarized), res); return true; } @@ -128,7 +144,7 @@ GlobalHistogramBinarizer::getBlackMatrix() const { // Quickly calculates the histogram by sampling four rows from the image. This proved to be // more robust on the blackbox tests than sampling a diagonal as we used to do. - std::array localBuckets = {}; + Histogram localBuckets = {}; { for (int y = 1; y < 5; y++) { int row = height() * y / 5; @@ -143,15 +159,9 @@ GlobalHistogramBinarizer::getBlackMatrix() const if (blackPoint <= 0) return {}; - // We delay reading the entire image luminance until the black point estimation succeeds. - // Although we end up reading four rows twice, it is consistent with our motto of - // "fail quickly" which is necessary for continuous scanning. - auto matrix = std::make_shared(width(), height()); - for(int y = 0; y < height(); ++y) - for(int x = 0; x < width(); ++x) - matrix->set(x, y, *_buffer.data(x, y) < blackPoint); - return matrix; + + return std::make_shared(binarize(blackPoint)); } } // ZXing diff --git a/core/src/GridSampler.cpp b/core/src/GridSampler.cpp index 9bc3490212..8b5cf9b336 100644 --- a/core/src/GridSampler.cpp +++ b/core/src/GridSampler.cpp @@ -19,46 +19,63 @@ LogMatrix log; #endif DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix) +{ + return SampleGrid(image, width, height, {ROI{0, width, 0, height, mod2Pix}}); +} + +DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const ROIs& rois) { #ifdef PRINT_DEBUG LogMatrix log; static int i = 0; LogMatrixWriter lmw(log, image, 5, "grid" + std::to_string(i++) + ".pnm"); #endif - if (width <= 0 || height <= 0 || !mod2Pix.isValid()) + if (width <= 0 || height <= 0) return {}; - // To deal with remaining examples (see #251 and #267) of "numercial instabilities" that have not been - // prevented with the Quadrilateral.h:IsConvex() check, we check for all boundary points of the grid to - // be inside. - auto isInside = [&](PointI p) { return image.isIn(mod2Pix(centered(p))); }; - for (int y = 0; y < height; ++y) - if (!isInside({0, y}) || !isInside({width - 1, y})) - return {}; - for (int x = 1; x < width - 1; ++x) - if (!isInside({x, 0}) || !isInside({x, height - 1})) + for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) { + // Precheck the corners of every roi to bail out early if the grid is "obviously" not completely inside the image + auto isInside = [&mod2Pix = mod2Pix, &image](int x, int y) { return image.isIn(mod2Pix(centered(PointI(x, y)))); }; + if (!mod2Pix.isValid() || !isInside(x0, y0) || !isInside(x1 - 1, y0) || !isInside(x1 - 1, y1 - 1) || !isInside(x0, y1 - 1)) return {}; + } BitMatrix res(width, height); - for (int y = 0; y < height; ++y) - for (int x = 0; x < width; ++x) { - auto p = mod2Pix(centered(PointI{x, y})); + for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) { + for (int y = y0; y < y1; ++y) + for (int x = x0; x < x1; ++x) { + auto p = mod2Pix(centered(PointI{x, y})); + // Due to a "numerical instability" in the PerspectiveTransform generation/application it has been observed + // that even though all boundary grid points get projected inside the image, it can still happen that an + // inner grid points is not. See #563. A true perspective transformation cannot have this property. + // The following check takes 100% care of the issue and turned out to be less of a performance impact than feared. + // TODO: Check some mathematical/numercial property of mod2Pix to determine if it is a perspective transforation. + if (!image.isIn(p)) + return {}; + #ifdef PRINT_DEBUG - log(p, 3); + log(p, 3); #endif - if (image.get(p)) - res.set(x, y); - } + if (image.get(p)) + res.set(x, y); + } + } #ifdef PRINT_DEBUG printf("width: %d, height: %d\n", width, height); - printf("%s", ToString(res).c_str()); +// printf("%s", ToString(res).c_str()); #endif - auto projectCorner = [&](PointI p) { return PointI(mod2Pix(PointF(p)) + PointF(0.5, 0.5)); }; - return { - std::move(res), - {projectCorner({0, 0}), projectCorner({width, 0}), projectCorner({width, height}), projectCorner({0, height})}}; -} + auto projectCorner = [&](PointI p) { + for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) + if (x0 <= p.x && p.x <= x1 && y0 <= p.y && p.y <= y1) + return PointI(mod2Pix(PointF(p)) + PointF(0.5, 0.5)); + + return PointI(); + }; + + return {std::move(res), + {projectCorner({0, 0}), projectCorner({width, 0}), projectCorner({width, height}), projectCorner({0, height})}}; + } } // ZXing diff --git a/core/src/GridSampler.h b/core/src/GridSampler.h index 8fb4eea6a9..654b866024 100644 --- a/core/src/GridSampler.h +++ b/core/src/GridSampler.h @@ -38,4 +38,21 @@ namespace ZXing { */ DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix); +template +Quadrilateral Rectangle(int x0, int x1, int y0, int y1, typename PointT::value_t o = 0.5) +{ + return {PointT{x0 + o, y0 + o}, {x1 + o, y0 + o}, {x1 + o, y1 + o}, {x0 + o, y1 + o}}; +} + +class ROI +{ +public: + int x0, x1, y0, y1; + PerspectiveTransform mod2Pix; +}; + +using ROIs = std::vector; + +DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const ROIs& rois); + } // ZXing diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index 8ccc2aa800..4aaec4ae85 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -7,10 +7,7 @@ #include "HybridBinarizer.h" #include "BitMatrix.h" -#include "BitMatrixIO.h" -#include "ByteArray.h" #include "Matrix.h" -#include "ZXAlgorithms.h" #include #include @@ -29,12 +26,30 @@ HybridBinarizer::HybridBinarizer(const ImageView& iv) : GlobalHistogramBinarizer HybridBinarizer::~HybridBinarizer() = default; +bool HybridBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const +{ +#if 1 + // This is the original "hybrid" behavior: use GlobalHistogram for the 1D case + return GlobalHistogramBinarizer::getPatternRow(row, rotation, res); +#else + // This is an alternative that can be faster in general and perform better in unevenly lit sitations like + // https://github.com/zxing-cpp/zxing-cpp/blob/master/test/samples/ean13-2/21.png. That said, it fairs + // worse in borderline low resolution situations. With the current black box sample set we'd loose 94 + // test cases while gaining 53 others. + auto bits = getBitMatrix(); + if (bits) + GetPatternRow(*bits, row, res, rotation % 180 != 0); + return bits != nullptr; +#endif +} + /** * Calculates a single black point for each block of pixels and saves it away. * See the following thread for a discussion of this algorithm: * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 */ -static Matrix CalculateBlackPoints(const uint8_t* luminances, int subWidth, int subHeight, int width, int height, int rowStride) +static Matrix CalculateBlackPoints(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height, + int rowStride) { Matrix blackPoints(subWidth, subHeight); @@ -51,7 +66,7 @@ static Matrix CalculateBlackPoints(const uint8_t* luminances, int subWidth, sum += pixel; if (pixel < min) min = pixel; - else if (pixel > max) + if (pixel > max) max = pixel; } // short-circuit min/max tests once dynamic range is met @@ -101,13 +116,15 @@ static Matrix CalculateBlackPoints(const uint8_t* luminances, int subWidth, /** * Applies a single threshold to a block of pixels. */ -static void ThresholdBlock(const uint8_t* luminances, int xoffset, int yoffset, int threshold, int rowStride, BitMatrix& matrix) +static void ThresholdBlock(const uint8_t* __restrict luminances, int xoffset, int yoffset, int threshold, int rowStride, + BitMatrix& matrix) { for (int y = yoffset; y < yoffset + BLOCK_SIZE; ++y) { auto* src = luminances + y * rowStride + xoffset; auto* const dstBegin = matrix.row(y).begin() + xoffset; + // TODO: fix pixelStride > 1 case for (auto* dst = dstBegin; dst < dstBegin + BLOCK_SIZE; ++dst, ++src) - *dst = *src <= threshold; + *dst = (*src <= threshold) * BitMatrix::SET_V; } } @@ -116,7 +133,7 @@ static void ThresholdBlock(const uint8_t* luminances, int xoffset, int yoffset, * of the blocks around it. Also handles the corner cases (fractional blocks are computed based * on the last pixels in the row/column which are also used in the previous block). */ -static std::shared_ptr CalculateMatrix(const uint8_t* luminances, int subWidth, int subHeight, int width, +static std::shared_ptr CalculateMatrix(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height, int rowStride, const Matrix& blackPoints) { auto matrix = std::make_shared(width, height); diff --git a/core/src/HybridBinarizer.h b/core/src/HybridBinarizer.h index 6e0a8b42d0..3175fa63c9 100644 --- a/core/src/HybridBinarizer.h +++ b/core/src/HybridBinarizer.h @@ -33,6 +33,7 @@ class HybridBinarizer : public GlobalHistogramBinarizer explicit HybridBinarizer(const ImageView& iv); ~HybridBinarizer() override; + bool getPatternRow(int row, int rotation, PatternRow &res) const override; std::shared_ptr getBlackMatrix() const override; }; diff --git a/core/src/Matrix.h b/core/src/Matrix.h index 8b04bb1af1..36d4a0efe2 100644 --- a/core/src/Matrix.h +++ b/core/src/Matrix.h @@ -9,6 +9,7 @@ #include "Point.h" #include "ZXAlgorithms.h" +#include #include #include #include @@ -34,7 +35,7 @@ class Matrix public: Matrix() = default; -#ifdef __GNUC__ +#if defined(__llvm__) || (defined(__GNUC__) && (__GNUC__ > 7)) __attribute__((no_sanitize("signed-integer-overflow"))) #endif Matrix(int width, int height, value_t val = {}) : _width(width), _height(height), _data(_width * _height, val) { @@ -42,8 +43,8 @@ class Matrix throw std::invalid_argument("invalid size: width * height is too big"); } - Matrix(Matrix&&) = default; - Matrix& operator=(Matrix&&) = default; + Matrix(Matrix&&) noexcept = default; + Matrix& operator=(Matrix&&) noexcept = default; Matrix copy() const { return *this; @@ -77,16 +78,16 @@ class Matrix return operator()(x, y); } - void set(int x, int y, value_t value) { - operator()(x, y) = value; + value_t& set(int x, int y, value_t value) { + return operator()(x, y) = value; } const value_t& get(PointI p) const { return operator()(p.x, p.y); } - void set(PointI p, value_t value) { - operator()(p.x, p.y) = value; + value_t& set(PointI p, value_t value) { + return operator()(p.x, p.y) = value; } const value_t* data() const { diff --git a/core/src/MultiFormatReader.h b/core/src/MultiFormatReader.h index a9022f246c..4765b1574a 100644 --- a/core/src/MultiFormatReader.h +++ b/core/src/MultiFormatReader.h @@ -30,7 +30,8 @@ class MultiFormatReader { public: explicit MultiFormatReader(const DecodeHints& hints); - ~MultiFormatReader(); + explicit MultiFormatReader(DecodeHints&& hints) = delete; + ~MultiFormatReader(); Result read(const BinaryBitmap& image) const; diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 88c9adacfd..3d4cbc3ac9 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -5,6 +5,7 @@ #pragma once +#include "BitHacks.h" #include "Range.h" #include "ZXAlgorithms.h" @@ -19,7 +20,9 @@ namespace ZXing { -using PatternRow = std::vector; +using PatternType = uint16_t; +template using Pattern = std::array; +using PatternRow = std::vector; class PatternView { @@ -43,7 +46,7 @@ class PatternView PatternView(Iterator data, int size, Iterator base, Iterator end) : _data(data), _size(size), _base(base), _end(end) {} template - PatternView(const std::array& row) : _data(row.data()), _size(N) + PatternView(const Pattern& row) : _data(row.data()), _size(N) {} Iterator data() const { return _data; } @@ -129,12 +132,21 @@ struct BarAndSpace using value_type = T; T bar = {}, space = {}; // even index -> bar, odd index -> space - T& operator[](int i) { return reinterpret_cast(this)[i & 1]; } - T operator[](int i) const { return reinterpret_cast(this)[i & 1]; } + constexpr T& operator[](int i) noexcept { return reinterpret_cast(this)[i & 1]; } + constexpr T operator[](int i) const noexcept { return reinterpret_cast(this)[i & 1]; } bool isValid() const { return bar != T{} && space != T{}; } }; -using BarAndSpaceI = BarAndSpace; +using BarAndSpaceI = BarAndSpace; + +template +constexpr auto BarAndSpaceSum(const T* view) noexcept +{ + BarAndSpace res; + for (int i = 0; i < LEN; ++i) + res[i] += view[i]; + return res; +} /** * @brief FixedPattern describes a compile-time constant (start/stop) pattern. @@ -151,17 +163,42 @@ struct FixedPattern constexpr value_type operator[](int i) const noexcept { return _data[i]; } constexpr const value_type* data() const noexcept { return _data; } constexpr int size() const noexcept { return N; } + constexpr BarAndSpace sums() const noexcept { return BarAndSpaceSum(_data); } }; template using FixedSparcePattern = FixedPattern; -template -float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, +template +float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, float minQuietZone = 0, float moduleSizeRef = 0) { - int width = view.sum(N); - if (SUM > N && width < SUM) + if constexpr (E2E) { + using float_t = double; + + auto widths = BarAndSpaceSum(view.data()); + auto sums = pattern.sums(); + BarAndSpace modSize = {widths[0] / sums[0], widths[1] / sums[1]}; + + auto [m, M] = std::minmax(modSize[0], modSize[1]); + if (M > 4 * m) // make sure module sizes of bars and spaces are not too far away from each other + return 0; + + if (minQuietZone && spaceInPixel < minQuietZone * modSize.space) + return 0; + + const BarAndSpace thr = {modSize[0] * .75 + .5, modSize[1] / (2 + (LEN < 6)) + .5}; + + for (int x = 0; x < LEN; ++x) + if (std::abs(view[x] - pattern[x] * modSize[x]) > thr[x]) + return 0; + + const float_t moduleSize = (modSize[0] + modSize[1]) / 2; + return moduleSize; + } + + int width = view.sum(LEN); + if (SUM > LEN && width < SUM) return 0; const float moduleSize = (float)width / SUM; @@ -174,9 +211,9 @@ float IsPattern(const PatternView& view, const FixedPattern& patt // the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes. // TODO: review once we have upsampling in the binarizer in place. - const float threshold = moduleSizeRef * (0.5f + RELAXED_THRESHOLD * 0.25f) + 0.5f; + const float threshold = moduleSizeRef * (0.5f + E2E * 0.25f) + 0.5f; - for (int x = 0; x < N; ++x) + for (int x = 0; x < LEN; ++x) if (std::abs(view[x] - pattern[x] * moduleSizeRef) > threshold) return 0; @@ -314,12 +351,36 @@ void GetPatternRow(Range b_row, PatternRow& p_row) std::fill(p_row.begin(), p_row.end(), 0); auto bitPos = b_row.begin(); + const auto bitPosEnd = b_row.end(); auto intPos = p_row.data(); if (*bitPos) intPos++; // first value is number of white pixels, here 0 - while (++bitPos < b_row.end()) { + // The following code as been observed to cause a speedup of up to 30% on large images on an AVX cpu + // and on an a Google Pixel 3 Android phone. Your mileage may vary. + if constexpr (std::is_pointer_v && sizeof(I) == 8 && sizeof(std::remove_pointer_t) == 1) { + using simd_t = uint64_t; + while (bitPos < bitPosEnd - sizeof(simd_t)) { + auto asSimd0 = BitHacks::LoadU(bitPos); + auto asSimd1 = BitHacks::LoadU(bitPos + 1); + auto z = asSimd0 ^ asSimd1; + if (z) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + int step = BitHacks::NumberOfTrailingZeros(z) / 8 + 1; +#else + int step = BitHacks::NumberOfLeadingZeros(z) / 8 + 1; +#endif + (*intPos++) += step; + bitPos += step; + } else { + (*intPos) += sizeof(simd_t); + bitPos += sizeof(simd_t); + } + } + } + + while (++bitPos != bitPosEnd) { ++(*intPos); intPos += bitPos[0] != bitPos[-1]; } diff --git a/core/src/PerspectiveTransform.h b/core/src/PerspectiveTransform.h index c2f5043e18..cc85fcb20c 100644 --- a/core/src/PerspectiveTransform.h +++ b/core/src/PerspectiveTransform.h @@ -20,12 +20,11 @@ namespace ZXing { class PerspectiveTransform { using value_t = PointF::value_t; - value_t a11, a12, a13, a21, a22, a23, a31, a32, a33; - bool _isValid = false; + value_t a11, a12, a13, a21, a22, a23, a31, a32, a33 = NAN; PerspectiveTransform(value_t a11, value_t a21, value_t a31, value_t a12, value_t a22, value_t a32, value_t a13, value_t a23, value_t a33) - : a11(a11), a12(a12), a13(a13), a21(a21), a22(a22), a23(a23), a31(a31), a32(a32), a33(a33), _isValid(true) + : a11(a11), a12(a12), a13(a13), a21(a21), a22(a22), a23(a23), a31(a31), a32(a32), a33(a33) {} PerspectiveTransform inverse() const; @@ -40,7 +39,7 @@ class PerspectiveTransform /// Project from the destination space (grid of modules) into the image space (bit matrix) PointF operator()(PointF p) const; - bool isValid() const { return _isValid; } + bool isValid() const { return !std::isnan(a33); } }; } // ZXing diff --git a/core/src/Quadrilateral.h b/core/src/Quadrilateral.h index ce5d47bec5..3e339bb61c 100644 --- a/core/src/Quadrilateral.h +++ b/core/src/Quadrilateral.h @@ -79,8 +79,9 @@ bool IsConvex(const Quadrilateral& poly) auto d2 = poly[i] - poly[(i + 1) % N]; auto cp = cross(d1, d2); - m = std::min(std::fabs(m), cp); - M = std::max(std::fabs(M), cp); + // TODO: see if the isInside check for all boundary points in GridSampler is still required after fixing the wrong fabs() + // application in the following line + UpdateMinMax(m, M, std::fabs(cp)); if (i == 0) sign = cp > 0; @@ -130,14 +131,37 @@ bool IsInside(const PointT& p, const Quadrilateral& q) return pos == 0 || neg == 0; } +template +Quadrilateral BoundingBox(const Quadrilateral& q) +{ + auto [minX, maxX] = std::minmax({q[0].x, q[1].x, q[2].x, q[3].x}); + auto [minY, maxY] = std::minmax({q[0].y, q[1].y, q[2].y, q[3].y}); + return {PointT{minX, minY}, {maxX, minY}, {maxX, maxY}, {minX, maxY}}; +} + template bool HaveIntersectingBoundingBoxes(const Quadrilateral& a, const Quadrilateral& b) { - // TODO: this is only a quick and dirty approximation that works for the trivial standard cases - bool x = b.topRight().x < a.topLeft().x || b.topLeft().x > a.topRight().x; - bool y = b.bottomLeft().y < a.topLeft().y || b.topLeft().y > a.bottomLeft().y; + auto bba = BoundingBox(a), bbb = BoundingBox(b); + + bool x = bbb.topRight().x < bba.topLeft().x || bbb.topLeft().x > bba.topRight().x; + bool y = bbb.bottomLeft().y < bba.topLeft().y || bbb.topLeft().y > bba.bottomLeft().y; return !(x || y); } +template +Quadrilateral Blend(const Quadrilateral& a, const Quadrilateral& b) +{ + auto dist2First = [c = a[0]](auto a, auto b) { return distance(a, c) < distance(b, c); }; + // rotate points such that the the two topLeft points are closest to each other + auto offset = std::min_element(b.begin(), b.end(), dist2First) - b.begin(); + + Quadrilateral res; + for (int i = 0; i < 4; ++i) + res[i] = (a[i] + b[(i + offset) % 4]) / 2; + + return res; +} + } // ZXing diff --git a/core/src/Range.h b/core/src/Range.h index d1d94faaaa..5040157807 100644 --- a/core/src/Range.h +++ b/core/src/Range.h @@ -7,6 +7,8 @@ #include "ZXAlgorithms.h" +#include + namespace ZXing { template @@ -15,23 +17,43 @@ struct StrideIter Iterator pos; int stride; + using iterator_category = std::random_access_iterator_tag; + using difference_type = typename std::iterator_traits::difference_type; + using value_type = typename std::iterator_traits::value_type; + using pointer = Iterator; + using reference = typename std::iterator_traits::reference; + auto operator*() const { return *pos; } auto operator[](int i) const { return *(pos + i * stride); } StrideIter& operator++() { return pos += stride, *this; } - bool operator<(const StrideIter& rhs) const { return pos < rhs.pos; } + StrideIter operator++(int) { auto temp = *this; ++*this; return temp; } + bool operator!=(const StrideIter& rhs) const { return pos != rhs.pos; } StrideIter operator+(int i) const { return {pos + i * stride, stride}; } + StrideIter operator-(int i) const { return {pos - i * stride, stride}; } int operator-(const StrideIter& rhs) const { return narrow_cast((pos - rhs.pos) / stride); } }; +template +StrideIter(const Iterator&, int) -> StrideIter; + + template struct Range { Iterator _begin, _end; + Range(Iterator b, Iterator e) : _begin(b), _end(e) {} + + template + Range(const C& c) : _begin(std::begin(c)), _end(std::end(c)) {} + Iterator begin() const noexcept { return _begin; } Iterator end() const noexcept { return _end; } explicit operator bool() const { return begin() < end(); } int size() const { return narrow_cast(end() - begin()); } }; +template +Range(const C&) -> Range; + } // namespace ZXing diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 905dd191cd..f814add08b 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -9,8 +9,10 @@ #include "GlobalHistogramBinarizer.h" #include "HybridBinarizer.h" #include "MultiFormatReader.h" +#include "Pattern.h" #include "ThresholdBinarizer.h" +#include #include #include @@ -45,9 +47,9 @@ static LumImage ExtractLum(const ImageView& iv, P projection) class LumImagePyramid { - int N = 3; std::vector buffers; + template void addLayer() { auto siv = layers.back(); @@ -66,18 +68,27 @@ class LumImagePyramid } } + void addLayer(int factor) + { + // help the compiler's auto-vectorizer by hard-coding the scale factor + switch (factor) { + case 2: addLayer<2>(); break; + case 3: addLayer<3>(); break; + case 4: addLayer<4>(); break; + default: throw std::invalid_argument("Invalid DecodeHints::downscaleFactor"); break; + } + } + public: std::vector layers; - LumImagePyramid(const ImageView& iv, int threshold, int factor) : N(factor) + LumImagePyramid(const ImageView& iv, int threshold, int factor) { - if (factor < 2) - throw std::invalid_argument("Invalid DecodeHints::downscaleFactor"); - layers.push_back(iv); // TODO: if only matrix codes were considered, then using std::min would be sufficient (see #425) - while (threshold > 0 && std::max(layers.back().width(), layers.back().height()) > threshold) - addLayer(); + while (threshold > 0 && std::max(layers.back().width(), layers.back().height()) > threshold && + std::min(layers.back().width(), layers.back().height()) >= factor) + addLayer(factor); #if 0 // Reversing the layers means we'd start with the smallest. that can make sense if we are only looking for a // single symbol. If we start with the higher resolution, we get better (high res) position information. @@ -124,6 +135,9 @@ Result ReadBarcode(const ImageView& _iv, const DecodeHints& hints) Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) { + if (sizeof(PatternType) < 4 && hints.hasFormat(BarcodeFormat::LinearCodes) && (_iv.width() > 0xffff || _iv.height() > 0xffff)) + throw std::invalid_argument("maximum image width/height is 65535"); + LumImage lum; ImageView iv = SetupLumImageView(_iv, lum, hints); MultiFormatReader reader(hints); @@ -131,28 +145,43 @@ Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) if (hints.isPure()) return {reader.read(*CreateBitmap(hints.binarizer(), iv))}; + std::unique_ptr closedReader; +#ifdef BUILD_EXPERIMENTAL_API + auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode; + if (hints.tryDenoise() && hints.hasFormat(formatsBenefittingFromClosing)) { + DecodeHints closedHints = hints; + closedHints.setFormats((hints.formats().empty() ? BarcodeFormat::Any : hints.formats()) & formatsBenefittingFromClosing); + closedReader = std::make_unique(closedHints); + } +#endif LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor()); Results results; - int maxSymbols = hints.maxNumberOfSymbols(); + int maxSymbols = hints.maxNumberOfSymbols() ? hints.maxNumberOfSymbols() : INT_MAX; for (auto&& iv : pyramid.layers) { auto bitmap = CreateBitmap(hints.binarizer(), iv); - for (int invert = 0; invert <= static_cast(hints.tryInvert()); ++invert) { - if (invert) - bitmap->invert(); - auto rs = reader.readMultiple(*bitmap, maxSymbols); - for (auto& r : rs) { - if (iv.width() != _iv.width()) - r.setPosition(Scale(r.position(), _iv.width() / iv.width())); - if (!Contains(results, r)) { - r.setDecodeHints(hints); - r.setIsInverted(bitmap->inverted()); - results.push_back(std::move(r)); - --maxSymbols; + for (int close = 0; close <= (closedReader ? 1 : 0); ++close) { + if (close) + bitmap->close(); + + // TODO: check if closing after invert would be beneficial + for (int invert = 0; invert <= static_cast(hints.tryInvert() && !close); ++invert) { + if (invert) + bitmap->invert(); + auto rs = (close ? *closedReader : reader).readMultiple(*bitmap, maxSymbols); + for (auto& r : rs) { + if (iv.width() != _iv.width()) + r.setPosition(Scale(r.position(), _iv.width() / iv.width())); + if (!Contains(results, r)) { + r.setDecodeHints(hints); + r.setIsInverted(bitmap->inverted()); + results.push_back(std::move(r)); + --maxSymbols; + } } + if (maxSymbols <= 0) + return results; } - if (maxSymbols <= 0) - return results; } } diff --git a/core/src/RegressionLine.h b/core/src/RegressionLine.h index 489e9d503f..eb7ecd1d4f 100644 --- a/core/src/RegressionLine.h +++ b/core/src/RegressionLine.h @@ -6,6 +6,7 @@ #pragma once #include "Point.h" +#include "ZXAlgorithms.h" #include #include @@ -94,6 +95,11 @@ class RegressionLine } void pop_back() { _points.pop_back(); } + void pop_front() + { + std::rotate(_points.begin(), _points.begin() + 1, _points.end()); + _points.pop_back(); + } void setDirectionInward(PointF d) { _directionInward = normalized(d); } @@ -107,7 +113,7 @@ class RegressionLine // remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist auto end = std::remove_if(points.begin(), points.end(), [this, maxSignedDist](auto p) { auto sd = this->signedDistance(p); - return sd > maxSignedDist || sd < -2 * maxSignedDist; + return sd > maxSignedDist || sd < -2 * maxSignedDist; }); points.erase(end, points.end()); if (old_points_size == points.size()) @@ -128,10 +134,8 @@ class RegressionLine { PointF min = _points.front(), max = _points.front(); for (auto p : _points) { - min.x = std::min(min.x, p.x); - min.y = std::min(min.y, p.y); - max.x = std::max(max.x, p.x); - max.y = std::max(max.y, p.y); + UpdateMinMax(min.x, max.x, p.x); + UpdateMinMax(min.y, max.y, p.y); } auto diff = max - min; auto len = maxAbsComponent(diff); diff --git a/core/src/Result.cpp b/core/src/Result.cpp index af77bad917..837a561a43 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -123,17 +123,23 @@ Result& Result::setDecodeHints(DecodeHints hints) bool Result::operator==(const Result& o) const { - if (format() != o.format() || bytes() != o.bytes() || error() != o.error()) - return false; + // handle case where both are MatrixCodes first + if (!BarcodeFormats(BarcodeFormat::LinearCodes).testFlags(format() | o.format())) { + if (format() != o.format() || (bytes() != o.bytes() && isValid() && o.isValid())) + return false; - if (BarcodeFormats(BarcodeFormat::MatrixCodes).testFlag(format())) + // check for equal position if both are valid with equal bytes or at least one is in error return IsInside(Center(o.position()), position()); + } + + if (format() != o.format() || bytes() != o.bytes() || error() != o.error()) + return false; if (orientation() != o.orientation()) return false; if (lineCount() > 1 && o.lineCount() > 1) - return IsInside(Center(o.position()), position()); + return HaveIntersectingBoundingBoxes(o.position(), position()); // the following code is only meant for this->lineCount == 1 assert(lineCount() == 1); diff --git a/core/src/TextUtfEncoding.cpp b/core/src/TextUtfEncoding.cpp index 7c366371ad..084ec76b40 100644 --- a/core/src/TextUtfEncoding.cpp +++ b/core/src/TextUtfEncoding.cpp @@ -17,7 +17,7 @@ std::string ToUtf8(std::wstring_view str) // Same as `ToUtf8()` above, except if angleEscape set, places non-graphical characters in angle brackets with text name std::string ToUtf8(std::wstring_view str, const bool angleEscape) { - return ZXing::ToUtf8(angleEscape ? EscapeNonGraphical(str) : str); + return angleEscape ? ZXing::ToUtf8(EscapeNonGraphical(str)) : ZXing::ToUtf8(str); } std::wstring FromUtf8(std::string_view utf8) diff --git a/core/src/ThresholdBinarizer.h b/core/src/ThresholdBinarizer.h index 75ad2a898d..c24636aa9b 100644 --- a/core/src/ThresholdBinarizer.h +++ b/core/src/ThresholdBinarizer.h @@ -17,7 +17,7 @@ class ThresholdBinarizer : public BinaryBitmap const uint8_t _threshold = 0; public: - ThresholdBinarizer(const ImageView& buffer, uint8_t threshold = 1) : BinaryBitmap(buffer), _threshold(threshold) {} + ThresholdBinarizer(const ImageView& buffer, uint8_t threshold = 128) : BinaryBitmap(buffer), _threshold(threshold) {} bool getPatternRow(int row, int rotation, PatternRow& res) const override { @@ -32,7 +32,7 @@ class ThresholdBinarizer : public BinaryBitmap res.clear(); - for (const uint8_t* p = begin; p < end; p += stride) { + for (const uint8_t* p = begin; p != end; p += stride) { bool val = *p <= _threshold; if (val != lastVal) { res.push_back(narrow_cast((p - lastPos) / stride)); @@ -51,32 +51,7 @@ class ThresholdBinarizer : public BinaryBitmap std::shared_ptr getBlackMatrix() const override { - BitMatrix res(width(), height()); - - if (_buffer.pixStride() == 1 && _buffer.rowStride() == _buffer.width()) { - // Specialize for a packed buffer with pixStride 1 to support auto vectorization (16x speedup on AVX2) - auto dst = res.row(0).begin(); - for (auto src = _buffer.data(0, 0), end = _buffer.data(0, height()); src != end; ++src, ++dst) - *dst = *src <= _threshold; - } else { - auto processLine = [this, &res](int y, const auto* src, const int stride) { - for (auto& dst : res.row(y)) { - dst = *src <= _threshold; - src += stride; - } - }; - for (int y = 0; y < res.height(); ++y) { - auto src = _buffer.data(0, y) + GreenIndex(_buffer.format()); - // Specialize the inner loop for strides 1 and 4 to support auto vectorization - switch (_buffer.pixStride()) { - case 1: processLine(y, src, 1); break; - case 4: processLine(y, src, 4); break; - default: processLine(y, src, _buffer.pixStride()); break; - } - } - } - - return std::make_shared(std::move(res)); + return std::make_shared(binarize(_threshold)); } }; diff --git a/core/src/ZXAlgorithms.h b/core/src/ZXAlgorithms.h index 50f054c868..3965ab0746 100644 --- a/core/src/ZXAlgorithms.h +++ b/core/src/ZXAlgorithms.h @@ -107,4 +107,30 @@ std::string ToString(T val, int len) return result; } +template +void UpdateMin(T& min, T val) +{ + min = std::min(min, val); +} + +template +void UpdateMax(T& max, T val) +{ + max = std::max(max, val); +} + +template +void UpdateMinMax(T& min, T& max, T val) +{ + min = std::min(min, val); + max = std::max(max, val); + + // Note: the above code is not equivalent to + // if (val < min) min = val; + // else if (val > max) max = val; + // It is basically the same but without the 'else'. For the 'else'-variant to work, + // both min and max have to be initialized with a value that is part of the sequence. + // Also it turns out clang and gcc can vectorize the code above but not the code below. +} + } // ZXing diff --git a/core/src/ZXNullable.h b/core/src/ZXNullable.h index 8cd6bd040f..8d3a69ffa7 100644 --- a/core/src/ZXNullable.h +++ b/core/src/ZXNullable.h @@ -19,7 +19,7 @@ class Nullable final public: Nullable() = default; Nullable(const T &value) : m_hasValue(true), m_value(value) {} - Nullable(T &&value) : m_hasValue(true), m_value(std::move(value)) {} + Nullable(T &&value) noexcept : m_hasValue(true), m_value(std::move(value)) {} Nullable(std::nullptr_t) {} Nullable & operator=(const T &value) { @@ -28,7 +28,7 @@ class Nullable final return *this; } - Nullable & operator=(T &&value) { + Nullable & operator=(T &&value) noexcept { m_hasValue = true; m_value = std::move(value); return *this; diff --git a/core/src/aztec/AZDecoder.cpp b/core/src/aztec/AZDecoder.cpp index 88953f61e4..f87e652c3e 100644 --- a/core/src/aztec/AZDecoder.cpp +++ b/core/src/aztec/AZDecoder.cpp @@ -16,6 +16,7 @@ #include "ReedSolomonDecoder.h" #include "ZXTestSupport.h" +#include #include #include #include diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index 759245e4e9..87915060aa 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -26,21 +26,16 @@ namespace ZXing::Aztec { -static bool IsAztectCenterPattern(const PatternView& view) +static bool IsAztecCenterPattern(const PatternView& view) { // find min/max of all subsequent black/white pairs and check that they 'close together' auto m = view[0] + view[1]; auto M = m; for (int i = 1; i < Size(view) - 1; ++i) { int v = view[i] + view[i + 1]; - if (v < m) - m = v; - else if (v > M) - M = v; - if (M > m * 4 / 3) - return false; + UpdateMinMax(m, M, v); } - return view[-1] >= view[Size(view) / 2] - 2 && view[Size(view)] >= view[Size(view) / 2] - 2; + return M <= m * 4 / 3 + 1 && view[-1] >= view[Size(view) / 2] - 2 && view[Size(view)] >= view[Size(view) / 2] - 2; }; // specialized version of FindLeftGuard to find the '1,1,1,1,1,1,1' pattern of a compact Aztec center pattern @@ -49,72 +44,67 @@ static PatternView FindAztecCenterPattern(const PatternView& view) constexpr int minSize = 8; // Aztec runes auto window = view.subView(0, 7); for (auto end = view.end() - minSize; window.data() < end; window.skipPair()) - if (IsAztectCenterPattern(window)) + if (IsAztecCenterPattern(window)) return window; return {}; }; -static int CheckDirection(BitMatrixCursorF& cur, PointF dir, int range, bool updatePosition) +static int CheckSymmetricAztecCenterPattern(BitMatrixCursorI& cur, int range, bool updatePosition) { - range = range * 2 / 7; // TODO: tune - auto pOri = cur.p; - auto cuo = cur; - cur.setDirection(dir); - cuo.setDirection(-dir); - - int centerUp = cur.stepToEdge(1, range); - if (!centerUp) + range *= 2; // tilted symbols may have a larger vertical than horizontal range + + FastEdgeToEdgeCounter curFwd(cur), curBwd(cur.turnedBack()); + + int centerFwd = curFwd.stepToNextEdge(range / 7); + if (!centerFwd) return 0; - int centerDown = cuo.stepToEdge(1, range); - if (!centerDown) + int centerBwd = curBwd.stepToNextEdge(range / 7); + if (!centerBwd) return 0; - int center = centerUp + centerDown - 1; // -1 because the starting pixel is counted twice - if (center > range || center < range / 6) + int center = centerFwd + centerBwd - 1; // -1 because the starting pixel is counted twice + if (center > range / 7 || center < range / (4 * 7)) return 0; - if (updatePosition) - pOri = (cur.p + cuo.p) / 2; - int spread = center; int m = 0; int M = 0; - for (auto c : {&cur, &cuo}) { + for (auto c : {&curFwd, &curBwd}) { int lastS = center; for (int i = 0; i < 3; ++i) { - int s = c->stepToEdge(1, M); + int s = c->stepToNextEdge(range - spread); + if (s == 0) + return 0; int v = s + lastS; if (m == 0) m = M = v; - else if (v < m) - m = v; - else if (v > M) - M = v; - if (M > m * 4 / 3) + else + UpdateMinMax(m, M, v); + if (M > m * 4 / 3 + 1) return 0; spread += s; lastS = s; } } - cur.p = pOri; + if (updatePosition) + cur.step(centerFwd - centerBwd); return spread; } static std::optional LocateAztecCenter(const BitMatrix& image, PointF center, int spreadH) { - auto cur = BitMatrixCursorF(image, center, {}); - int minSpread = spreadH, maxSpread = spreadH; - for (auto d : {PointF{0, 1}, {1, 0}, {1, 1}, {1, -1}}) { - int spread = CheckDirection(cur, d, spreadH, d.x == 0); + auto cur = BitMatrixCursor(image, PointI(center), {}); + int minSpread = spreadH, maxSpread = 0; + for (auto d : {PointI{0, 1}, {1, 0}, {1, 1}, {1, -1}}) { + int spread = CheckSymmetricAztecCenterPattern(cur.setDirection(d), spreadH, d.x == 0); if (!spread) return {}; - minSpread = std::min(spread, minSpread); - maxSpread = std::max(spread, maxSpread); + UpdateMinMax(minSpread, maxSpread, spread); } - return ConcentricPattern{cur.p, (maxSpread + minSpread) / 2}; + return ConcentricPattern{centered(cur.p), (maxSpread + minSpread) / 2}; } static std::vector FindPureFinderPattern(const BitMatrix& image) @@ -125,7 +115,7 @@ static std::vector FindPureFinderPattern(const BitMatrix& ima PointF p(left + width / 2, top + height / 2); constexpr auto PATTERN = FixedPattern<7, 7>{1, 1, 1, 1, 1, 1, 1}; - if (auto pattern = LocateConcentricPattern(image, PATTERN, p, width / 3)) + if (auto pattern = LocateConcentricPattern(image, PATTERN, p, width / 2)) return {*pattern}; else return {}; @@ -188,9 +178,10 @@ static std::vector FindFinderPatterns(const BitMatrix& image, int skip = tryHarder ? 1 : std::clamp(image.height() / 2 / 100, 1, 5); int margin = tryHarder ? 5 : image.height() / 4; + PatternRow row; + for (int y = margin; y < image.height() - margin; y += skip) { - PatternRow row; GetPatternRow(image, y, row, false); PatternView next = row; next.shift(1); // the center pattern we are looking for starts with white and is 7 wide (compact code) @@ -346,7 +337,7 @@ DetectorResults Detect(const BitMatrix& image, bool isPure, bool tryHarder, int DetectorResults res; auto fps = isPure ? FindPureFinderPattern(image) : FindFinderPatterns(image, tryHarder); - for (auto fp : fps) { + for (const auto& fp : fps) { auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 3); if (!fpQuad) continue; diff --git a/core/src/aztec/AZDetectorResult.h b/core/src/aztec/AZDetectorResult.h index 0ed182ae62..cd65e9daab 100644 --- a/core/src/aztec/AZDetectorResult.h +++ b/core/src/aztec/AZDetectorResult.h @@ -25,8 +25,8 @@ class DetectorResult : public ZXing::DetectorResult public: DetectorResult() = default; - DetectorResult(DetectorResult&&) = default; - DetectorResult& operator=(DetectorResult&&) = default; + DetectorResult(DetectorResult&&) noexcept = default; + DetectorResult& operator=(DetectorResult&&) noexcept = default; DetectorResult(ZXing::DetectorResult&& result, bool isCompact, int nbDatablocks, int nbLayers, bool readerInit, bool isMirrored) : ZXing::DetectorResult{std::move(result)}, diff --git a/core/src/datamatrix/DMDataBlock.cpp b/core/src/datamatrix/DMDataBlock.cpp index 710e67d087..ea1c872d7c 100644 --- a/core/src/datamatrix/DMDataBlock.cpp +++ b/core/src/datamatrix/DMDataBlock.cpp @@ -1,6 +1,7 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 @@ -13,12 +14,12 @@ namespace ZXing::DataMatrix { -std::vector GetDataBlocks(const ByteArray& rawCodewords, const Version& version) +std::vector GetDataBlocks(const ByteArray& rawCodewords, const Version& version, bool fix259) { // First count the total number of data blocks // Now establish DataBlocks of the appropriate size and number of data codewords auto& ecBlocks = version.ecBlocks; - int numResultBlocks = ecBlocks.numBlocks(); + const int numResultBlocks = ecBlocks.numBlocks(); std::vector result; result.reserve(numResultBlocks); for (auto& ecBlock : ecBlocks.blocks) @@ -28,29 +29,27 @@ std::vector GetDataBlocks(const ByteArray& rawCodewords, const Versio // All blocks have the same amount of data, except that the last n // (where n may be 0) have 1 less byte. Figure out where these start. // TODO(bbrown): There is only one case where there is a difference for Data Matrix for size 144 - int longerBlocksTotalCodewords = Size(result[0].codewords); - int longerBlocksNumDataCodewords = longerBlocksTotalCodewords - ecBlocks.codewordsPerBlock; - int shorterBlocksNumDataCodewords = longerBlocksNumDataCodewords - 1; + const int numCodewords = Size(result[0].codewords); + const int numDataCodewords = numCodewords - ecBlocks.codewordsPerBlock; // The last elements of result may be 1 element shorter for 144 matrix // first fill out as many elements as all of them have minus 1 int rawCodewordsOffset = 0; - for (int i = 0; i < shorterBlocksNumDataCodewords; i++) + for (int i = 0; i < numDataCodewords - 1; i++) for (int j = 0; j < numResultBlocks; j++) result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; // Fill out the last data block in the longer ones - bool specialVersion = version.versionNumber == 24; - int numLongerBlocks = specialVersion ? 8 : numResultBlocks; + const bool size144x144 = version.symbolHeight == 144; + const int numLongerBlocks = size144x144 ? 8 : numResultBlocks; for (int j = 0; j < numLongerBlocks; j++) - result[j].codewords[longerBlocksNumDataCodewords - 1] = rawCodewords[rawCodewordsOffset++]; + result[j].codewords[numDataCodewords - 1] = rawCodewords[rawCodewordsOffset++]; // Now add in error correction blocks - int max = Size(result[0].codewords); - for (int i = longerBlocksNumDataCodewords; i < max; i++) { + for (int i = numDataCodewords; i < numCodewords; i++) { for (int j = 0; j < numResultBlocks; j++) { - int jOffset = specialVersion ? (j + 8) % numResultBlocks : j; - int iOffset = specialVersion && jOffset > 7 ? i - 1 : i; + int jOffset = size144x144 && fix259 ? (j + 8) % numResultBlocks : j; + int iOffset = size144x144 && jOffset > 7 ? i - 1 : i; result[jOffset].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; } } diff --git a/core/src/datamatrix/DMDataBlock.h b/core/src/datamatrix/DMDataBlock.h index 54dc7fade7..bc2a8b86a2 100644 --- a/core/src/datamatrix/DMDataBlock.h +++ b/core/src/datamatrix/DMDataBlock.h @@ -32,9 +32,10 @@ struct DataBlock * * @param rawCodewords bytes as read directly from the Data Matrix Code * @param version version of the Data Matrix Code + * @param fix259 see https://github.com/zxing-cpp/zxing-cpp/issues/259 * @return DataBlocks containing original bytes, "de-interleaved" from representation in the * Data Matrix Code */ -std::vector GetDataBlocks(const ByteArray& rawCodewords, const Version& version); +std::vector GetDataBlocks(const ByteArray& rawCodewords, const Version& version, bool fix259 = false); } // namespace ZXing::DataMatrix diff --git a/core/src/datamatrix/DMDecoder.cpp b/core/src/datamatrix/DMDecoder.cpp index c79515e0f5..af3b9c5018 100644 --- a/core/src/datamatrix/DMDecoder.cpp +++ b/core/src/datamatrix/DMDecoder.cpp @@ -397,8 +397,10 @@ static DecoderResult DoDecode(const BitMatrix& bits) if (codewords.empty()) return FormatError("Invalid number of code words"); + bool fix259 = false; // see https://github.com/zxing-cpp/zxing-cpp/issues/259 +retry: // Separate into data blocks - std::vector dataBlocks = GetDataBlocks(codewords, *version); + std::vector dataBlocks = GetDataBlocks(codewords, *version, fix259); if (dataBlocks.empty()) return FormatError("Invalid number of data blocks"); @@ -408,17 +410,24 @@ static DecoderResult DoDecode(const BitMatrix& bits) // Error-correct and copy data blocks together into a stream of bytes const int dataBlocksCount = Size(dataBlocks); for (int j = 0; j < dataBlocksCount; j++) { - auto& dataBlock = dataBlocks[j]; - ByteArray& codewordBytes = dataBlock.codewords; - int numDataCodewords = dataBlock.numDataCodewords; - if (!CorrectErrors(codewordBytes, numDataCodewords)) + auto& [numDataCodewords, codewords] = dataBlocks[j]; + if (!CorrectErrors(codewords, numDataCodewords)) { + if(version->versionNumber == 24 && !fix259) { + fix259 = true; + goto retry; + } return ChecksumError(); + } for (int i = 0; i < numDataCodewords; i++) { // De-interlace data blocks. - resultBytes[i * dataBlocksCount + j] = codewordBytes[i]; + resultBytes[i * dataBlocksCount + j] = codewords[i]; } } +#ifdef PRINT_DEBUG + if (fix259) + printf("-> needed retry with fix259 for 144x144 symbol\n"); +#endif // Decode the contents of that stream of bytes return DecodedBitStreamParser::Decode(std::move(resultBytes), version->isDMRE()) diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index 381ada961a..b263b26adf 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -29,6 +29,16 @@ #include #include +#ifndef PRINT_DEBUG +#define printf(...){} +#define printv(...){} +#else +#define printv(fmt, vec) \ +for (auto v : vec) \ + printf(fmt, v); \ +printf("\n"); +#endif + namespace ZXing::DataMatrix { /** @@ -426,7 +436,7 @@ class DMRegressionLine : public RegressionLine assert(_points.size() > 3); // re-evaluate and filter out all points too far away. required for the gapSizes calculation. - evaluate(1.0, true); + evaluate(1.2, true); std::vector gapSizes, modSizes; gapSizes.reserve(_points.size()); @@ -449,19 +459,32 @@ class DMRegressionLine : public RegressionLine if (dist > 1.9 * unitPixelDist) modSizes.push_back(std::exchange(sumFront, 0.0)); } + if (modSizes.empty()) + return 0; modSizes.push_back(sumFront + distance(end, project(_points.back()))); modSizes.front() = 0; // the first element is an invalid sumBack value, would be pop_front() if vector supported this auto lineLength = distance(beg, end) - unitPixelDist; - auto meanModSize = average(modSizes, [](double){ return true; }); -#ifdef PRINT_DEBUG + auto [iMin, iMax] = std::minmax_element(modSizes.begin() + 1, modSizes.end()); + auto meanModSize = average(modSizes, [](double dist){ return dist > 0; }); + printf("unit pixel dist: %.1f\n", unitPixelDist); - printf("lineLength: %.1f, meanModSize: %.1f, gaps: %lu\n", lineLength, meanModSize, modSizes.size()); -#endif - for (int i = 0; i < 2; ++i) - meanModSize = average(modSizes, [=](double dist) { return std::abs(dist - meanModSize) < meanModSize / (2 + i); }); -#ifdef PRINT_DEBUG + printf("lineLength: %.1f, meanModSize: %.1f (min: %.1f, max: %.1f), gaps: %lu\n", lineLength, meanModSize, *iMin, *iMax, + modSizes.size()); + printv("%.1f ", modSizes); + + if (*iMax > 2 * *iMin) { + for (int i = 1; i < Size(modSizes) - 2; ++i) { + if (modSizes[i] > 0 && modSizes[i] + modSizes[i + 2] < meanModSize * 1.4) + modSizes[i] += std::exchange(modSizes[i + 2], 0); + else if (modSizes[i] > meanModSize * 1.6) + modSizes[i] = 0; + } + printv("%.1f ", modSizes); + + meanModSize = average(modSizes, [](double dist) { return dist > 0; }); + } printf("post filter meanModSize: %.1f\n", meanModSize); -#endif + return lineLength / meanModSize; } }; @@ -547,14 +570,15 @@ class EdgeTracer : public BitMatrixCursorF } while (true); } - bool traceGaps(PointF dEdge, RegressionLine& line, int maxStepSize, const RegressionLine& finishLine = {}) + bool traceGaps(PointF dEdge, RegressionLine& line, int maxStepSize, const RegressionLine& finishLine = {}, double minDist = 0) { line.setDirectionInward(dEdge); - int gaps = 0; + int gaps = 0, steps = 0, maxStepsPerGap = maxStepSize; + PointF lastP; do { // detect an endless loop (lack of progress). if encountered, please report. - assert(line.points().empty() || p != line.points().back()); - if (!line.points().empty() && p == line.points().back()) + // this fixes a deadlock in falsepositives-1/#570.png and the regression in #574 + if (p == std::exchange(lastP, p) || steps++ > (gaps == 0 ? 2 : gaps + 1) * maxStepsPerGap) return false; log(p); @@ -571,6 +595,10 @@ class EdgeTracer : public BitMatrixCursorF if (std::abs(dot(normalized(d), line.normal())) > 0.7) // thresh is approx. sin(45 deg) return false; + // re-evaluate line with all the points up to here before projecting + if (!line.evaluate(1.5)) + return false; + auto np = line.project(p); // make sure we are making progress even when back-projecting: // consider a 90deg corner, rotated 45deg. we step away perpendicular from the line and get @@ -583,10 +611,11 @@ class EdgeTracer : public BitMatrixCursorF p = centered(np); } else { - auto stepLengthInMainDir = line.points().empty() ? 0.0 : dot(mainDirection(d), (p - line.points().back())); + auto curStep = line.points().empty() ? PointF() : p - line.points().back(); + auto stepLengthInMainDir = line.points().empty() ? 0.0 : dot(mainDirection(d), curStep); line.add(p); - if (stepLengthInMainDir > 1) { + if (stepLengthInMainDir > 1 || maxAbsComponent(curStep) >= 2) { ++gaps; if (gaps >= 2 || line.points().size() > 5) { if (!line.evaluate(1.5)) @@ -595,21 +624,20 @@ class EdgeTracer : public BitMatrixCursorF return false; // check if the first half of the top-line trace is complete. // the minimum code size is 10x10 -> every code has at least 4 gaps - //TODO: maybe switch to termination condition based on bottom line length to get a better - // finishLine for the right line trace - if (!finishLine.isValid() && gaps == 4) { + if (minDist && gaps >= 4 && distance(p, line.points().front()) > minDist) { // undo the last insert, it will be inserted again after the restart line.pop_back(); --gaps; return true; } } - } else if (gaps == 0 && line.points().size() >= static_cast(2 * maxStepSize)) + } else if (gaps == 0 && Size(line.points()) >= 2 * maxStepSize) { return false; // no point in following a line that has no gaps + } } if (finishLine.isValid()) - maxStepSize = std::min(maxStepSize, static_cast(finishLine.signedDistance(p))); + UpdateMin(maxStepSize, static_cast(finishLine.signedDistance(p))); auto stepResult = traceStep(dEdge, maxStepSize, line.isValid()); @@ -627,22 +655,35 @@ class EdgeTracer : public BitMatrixCursorF corner = p; std::swap(d, dir); traceStep(-1 * dir, 2, false); -#ifdef PRINT_DEBUG printf("turn: %.0f x %.0f -> %.2f, %.2f\n", p.x, p.y, d.x, d.y); -#endif + return isIn(corner) && isIn(p); } + + bool moveToNextWhiteAfterBlack() + { + assert(std::abs(d.x + d.y) == 1); + + FastEdgeToEdgeCounter e2e(BitMatrixCursorI(*img, PointI(p), PointI(d))); + int steps = e2e.stepToNextEdge(INT_MAX); + if (!steps) + return false; + step(steps); + if(isWhite()) + return true; + + steps = e2e.stepToNextEdge(INT_MAX); + if (!steps) + return false; + return step(steps); + } }; static DetectorResult Scan(EdgeTracer& startTracer, std::array& lines) { - while (startTracer.step()) { + while (startTracer.moveToNextWhiteAfterBlack()) { log(startTracer.p); - // continue until we cross from black into white - if (!startTracer.edgeAtBack().isWhite()) - continue; - PointF tl, bl, br, tr; auto& [lineL, lineB, lineR, lineT] = lines; @@ -694,9 +735,9 @@ static DetectorResult Scan(EdgeTracer& startTracer, std::array(lenB / 5 + 1); // datamatrix bottom dim is at least 10 // at this point we found a plausible L-shape and are now looking for the b/w pattern at the top and right: - // follow top row right 'half way' (4 gaps), see traceGaps break condition with 'invalid' line + // follow top row right 'half way' (at least 4 gaps), see traceGaps tlTracer.setDirection(right); - CHECK(tlTracer.traceGaps(tlTracer.right(), lineT, maxStepSize)); + CHECK(tlTracer.traceGaps(tlTracer.right(), lineT, maxStepSize, {}, lenB / 2)); maxStepSize = std::min(lineT.length() / 3, static_cast(lenL / 5)) * 2; @@ -715,10 +756,8 @@ static DetectorResult Scan(EdgeTracer& startTracer, std::array %.1f, %.1f (%d : %d : %d : %d)\n", bl.x, bl.y, tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, (int)lenL, (int)lenB, (int)lenT, (int)lenR); -#endif for (auto* l : {&lineL, &lineB, &lineT, &lineR}) l->evaluate(1.0); @@ -738,26 +777,23 @@ static DetectorResult Scan(EdgeTracer& startTracer, std::array %.1f, %.1f ^> %.1f, %.1f\n", bl.x, bl.y, tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, tr.x, tr.y); printf("dim: %d x %d\n", dimT, dimR); -#endif // if we have an almost square (invalid rectangular) data matrix dimension, we try to parse it by assuming a // square. we use the dimension that is closer to an integral value. all valid rectangular symbols differ in - // their dimension by at least 10 (here 5, see doubling below). Note: this is currently not required for the - // black-box tests to complete. - if (std::abs(dimT - dimR) < 5) + // their dimension by at least 10. Note: this is currently not required for the black-box tests to complete. + if (std::abs(dimT - dimR) < 10) dimT = dimR = fracR < fracT ? dimR : dimT; - // the dimension is 2x the number of black/white transitions - dimT *= 2; - dimR *= 2; - CHECK(dimT >= 10 && dimT <= 144 && dimR >= 8 && dimR <= 144); - auto movedTowardsBy = [](PointF& a, PointF b1, PointF b2, auto d) { + auto movedTowardsBy = [](PointF a, PointF b1, PointF b2, auto d) { return a + d * normalized(normalized(b1 - a) + normalized(b2 - a)); }; @@ -803,8 +839,8 @@ static DetectorResults DetectNew(const BitMatrix& image, bool tryHarder, bool tr constexpr int minSymbolSize = 8 * 2; // minimum realistic size in pixel: 8 modules x 2 pixels per module - for (auto dir : {PointF(-1, 0), PointF(1, 0), PointF(0, -1), PointF(0, 1)}) { - auto center = PointF(image.width() / 2, image.height() / 2); + for (auto dir : {PointF{-1, 0}, {1, 0}, {0, -1}, {0, 1}}) { + auto center = PointI(image.width() / 2, image.height() / 2); auto startPos = centered(center - center * dir + minSymbolSize / 2 * dir); history.clear(); diff --git a/core/src/datamatrix/DMECEncoder.cpp b/core/src/datamatrix/DMECEncoder.cpp index 484a1d6e5a..22af05fcb9 100644 --- a/core/src/datamatrix/DMECEncoder.cpp +++ b/core/src/datamatrix/DMECEncoder.cpp @@ -48,7 +48,7 @@ static const std::array FACTORS = {{ /*set 16*/ {220, 228, 173, 89, 251, 149, 159, 56, 89, 33, 147, 244, 154, 36, 73, 127, 213, 136, 248, 180, 234, 197, 158, 177, 68, 122, 93, 213, 15, 160, 227, 236, 66, 139, 153, 185, 202, 167, 179, 25, 220, 232, 96, 210, 231, 136, 223, 239, 181, 241, 59, 52, 172, 25, 49, 232, 211, 189, 64, 54, - 108, 153, 132, 63, 96, 103, 82, 186}, + 108, 153, 132, 63, 96, 103, 82, 186}, }}; static const uint8_t LOG[] = { diff --git a/core/src/libzueci/zueci.c b/core/src/libzueci/zueci.c index 156521ef98..26fd7ff9e3 100644 --- a/core/src/libzueci/zueci.c +++ b/core/src/libzueci/zueci.c @@ -1500,7 +1500,7 @@ ZUECI_EXTERN int zueci_eci_to_utf8(const int eci, const unsigned char src[], con zueci_u32 u; int src_incr; unsigned char replacement[5]; - int replacement_len; + int replacement_len = 0; /* g++ complains with "-Wmaybe-uninitialized" if this isn't set */ int ret = 0; if (!zueci_is_valid_eci(eci)) { diff --git a/core/src/oned/ODCode128Reader.cpp b/core/src/oned/ODCode128Reader.cpp index e48b3c2e18..9d0927eab6 100644 --- a/core/src/oned/ODCode128Reader.cpp +++ b/core/src/oned/ODCode128Reader.cpp @@ -180,10 +180,10 @@ static auto E2E_PATTERNS = [] { Result Code128Reader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const { int minCharCount = 4; // start + payload + checksum + stop - auto decodePattern = [](const PatternView& view) { + auto decodePattern = [](const PatternView& view, bool start = false) { // This is basically the reference algorithm from the specification int code = IndexOf(E2E_PATTERNS, ToInt(NormalizedE2EPattern(view))); - if (code == -1) // if the reference algo fails, give the original upstream version a try (required to decode a few samples) + if (code == -1 && !start) // if the reference algo fails, give the original upstream version a try (required to decode a few samples) code = DecodeDigit(view, Code128::CODE_PATTERNS, MAX_AVG_VARIANCE, MAX_INDIVIDUAL_VARIANCE); return code; }; @@ -193,7 +193,7 @@ Result Code128Reader::decodePattern(int rowNumber, PatternView& next, std::uniqu return {}; next = next.subView(0, CHAR_LEN); - int startCode = decodePattern(next); + int startCode = decodePattern(next, true); if (!(CODE_START_A <= startCode && startCode <= CODE_START_C)) return {}; diff --git a/core/src/oned/ODDataBarCommon.cpp b/core/src/oned/ODDataBarCommon.cpp index 3a7f592431..937f350005 100644 --- a/core/src/oned/ODDataBarCommon.cpp +++ b/core/src/oned/ODDataBarCommon.cpp @@ -145,10 +145,16 @@ bool ReadDataCharacterRaw(const PatternView& view, int numModules, bool reversed #endif } +static bool IsStacked(const Pair& first, const Pair& last) +{ + // check if we see two halfes that are far away from each other in y or overlapping in x + return std::abs(first.y - last.y) > (first.xStop - first.xStart) || last.xStart < (first.xStart + first.xStop) / 2; +} + Position EstimatePosition(const Pair& first, const Pair& last) { - if (first.y == last.y) - return Line(first.y, first.xStart, last.xStop); + if (!IsStacked(first, last)) + return Line((first.y + last.y) / 2, first.xStart, last.xStop); else return Position{{first.xStart, first.y}, {first.xStop, first.y}, {last.xStop, last.y}, {last.xStart, last.y}}; } @@ -156,7 +162,7 @@ Position EstimatePosition(const Pair& first, const Pair& last) int EstimateLineCount(const Pair& first, const Pair& last) { // see incrementLineCount() in ODReader.cpp for the -1 here - return std::min(first.count, last.count) * ((first.y == last.y) ? 1 : 2) - 1; + return std::min(first.count, last.count) - 1 + IsStacked(first, last); } } // namespace ZXing::OneD::DataBar diff --git a/core/src/oned/ODMultiUPCEANReader.cpp b/core/src/oned/ODMultiUPCEANReader.cpp index 8afd1e6ad3..aa88369c8c 100644 --- a/core/src/oned/ODMultiUPCEANReader.cpp +++ b/core/src/oned/ODMultiUPCEANReader.cpp @@ -34,7 +34,8 @@ static const int FIRST_DIGIT_ENCODINGS[] = {0x00, 0x0B, 0x0D, 0x0E, 0x13, 0x19, // QZ R: 7 | 7 | 9 | 7 | 5 | 5 constexpr float QUIET_ZONE_LEFT = 6; -constexpr float QUIET_ZONE_RIGHT = 6; +constexpr float QUIET_ZONE_RIGHT_EAN = 3; // used to be 6, see #526 and #558 +constexpr float QUIET_ZONE_RIGHT_UPC = 6; constexpr float QUIET_ZONE_ADDON = 3; // There is a single sample (ean13-1/12.png) that fails to decode with these (new) settings because @@ -131,7 +132,7 @@ static bool EAN13(PartialResult& res, PatternView begin) auto mid = begin.subView(27, MID_PATTERN.size()); auto end = begin.subView(56, END_PATTERN.size()); - CHECK(end.isValid() && IsRightGuard(end, END_PATTERN, QUIET_ZONE_RIGHT) && IsPattern(mid, MID_PATTERN)); + CHECK(end.isValid() && IsRightGuard(end, END_PATTERN, QUIET_ZONE_RIGHT_EAN) && IsPattern(mid, MID_PATTERN)); auto next = begin.subView(END_PATTERN.size(), CHAR_LEN); res.txt = " "; // make space for lgPattern character @@ -163,7 +164,7 @@ static bool EAN8(PartialResult& res, PatternView begin) auto mid = begin.subView(19, MID_PATTERN.size()); auto end = begin.subView(40, END_PATTERN.size()); - CHECK(end.isValid() && IsRightGuard(end, END_PATTERN, QUIET_ZONE_RIGHT) && IsPattern(mid, MID_PATTERN)); + CHECK(end.isValid() && IsRightGuard(end, END_PATTERN, QUIET_ZONE_RIGHT_EAN) && IsPattern(mid, MID_PATTERN)); // additional plausibility check for the module size: it has to be about the same for both // the guard patterns and the payload/data part. @@ -190,7 +191,7 @@ static bool UPCE(PartialResult& res, PatternView begin) { auto end = begin.subView(27, UPCE_END_PATTERN.size()); - CHECK(end.isValid() && IsRightGuard(end, UPCE_END_PATTERN, QUIET_ZONE_RIGHT)); + CHECK(end.isValid() && IsRightGuard(end, UPCE_END_PATTERN, QUIET_ZONE_RIGHT_UPC)); // additional plausibility check for the module size: it has to be about the same for both // the guard patterns and the payload/data part. This speeds up the falsepositives use case @@ -289,30 +290,34 @@ Result MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::u res.format = BarcodeFormat::UPCA; } + // if we explicitly requested UPCA but not EAN13, don't return an EAN13 symbol + if (res.format == BarcodeFormat::EAN13 && ! _hints.hasFormat(BarcodeFormat::EAN13)) + return {}; + // Symbology identifier modifiers ISO/IEC 15420:2009 Annex B Table B.1 // ISO/IEC 15420:2009 (& GS1 General Specifications 5.1.3) states that the content for "]E0" should be 13 digits, // i.e. converted to EAN-13 if UPC-A/E, but not doing this here to maintain backward compatibility SymbologyIdentifier symbologyIdentifier = {'E', res.format == BarcodeFormat::EAN8 ? '4' : '0'}; + next = res.end; + auto ext = res.end; PartialResult addOnRes; if (_hints.eanAddOnSymbol() != EanAddOnSymbol::Ignore && ext.skipSymbol() && ext.skipSingle(static_cast(begin.sum() * 3.5)) && (AddOn(addOnRes, ext, 5) || AddOn(addOnRes, ext, 2))) { // ISO/IEC 15420:2009 states that the content for "]E3" should be 15 or 18 digits, i.e. converted to EAN-13 // and extended with no separator, and that the content for "]E4" should be 8 digits, i.e. no add-on - //TODO: extend position in include extension res.txt += " " + addOnRes.txt; + next = addOnRes.end; if (res.format != BarcodeFormat::EAN8) // Keeping EAN-8 with add-on as "]E4" symbologyIdentifier.modifier = '3'; // Combined packet, EAN-13, UPC-A, UPC-E, with add-on } - next = res.end; - if (_hints.eanAddOnSymbol() == EanAddOnSymbol::Require && !addOnRes.isValid()) return {}; - return Result(res.txt, rowNumber, begin.pixelsInFront(), res.end.pixelsTillEnd(), res.format, symbologyIdentifier, error); + return Result(res.txt, rowNumber, begin.pixelsInFront(), next.pixelsTillEnd(), res.format, symbologyIdentifier, error); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODReader.cpp b/core/src/oned/ODReader.cpp index 52e376562f..e928d04f77 100644 --- a/core/src/oned/ODReader.cpp +++ b/core/src/oned/ODReader.cpp @@ -22,6 +22,11 @@ #include #include +#ifdef PRINT_DEBUG +#include "BitMatrix.h" +#include "BitMatrixIO.h" +#endif + namespace ZXing { void IncrementLineCount(Result& r) @@ -96,6 +101,10 @@ static Results DoDecode(const std::vector>& readers, PatternRow bars; bars.reserve(128); // e.g. EAN-13 has 59 bars/spaces +#ifdef PRINT_DEBUG + BitMatrix dbg(width, height); +#endif + for (int i = 0; i < maxLines; i++) { // Scanning from the middle out. Determine which row we're looking at next: @@ -118,9 +127,19 @@ static Results DoDecode(const std::vector>& readers, continue; } - if (!image.getPatternRow(rowNumber, rotate ? 270 : 0, bars)) + if (!image.getPatternRow(rowNumber, rotate ? 90 : 0, bars)) continue; +#ifdef PRINT_DEBUG + bool val = false; + int x = 0; + for (auto b : bars) { + for(int j = 0; j < b; ++j) + dbg.set(x++, rowNumber, val); + val = !val; + } +#endif + // While we have the image data in a PatternRow, it's fairly cheap to reverse it in place to // handle decoding upside down barcodes. // TODO: the DataBarExpanded (stacked) decoder depends on seeing each line from both directions. This @@ -156,7 +175,7 @@ static Results DoDecode(const std::vector>& readers, if (rotate) { auto points = result.position(); for (auto& p : points) { - p = {height - p.y - 1, p.x}; + p = {p.y, width - p.x - 1}; } result.setPosition(std::move(points)); } @@ -225,6 +244,10 @@ static Results DoDecode(const std::vector>& readers, it = std::remove_if(res.begin(), res.end(), [](auto&& r) { return r.format() == BarcodeFormat::None; }); res.erase(it, res.end()); +#ifdef PRINT_DEBUG + SaveAsPBM(dbg, rotate ? "od-log-r.pnm" : "od-log.pnm"); +#endif + return res; } diff --git a/core/src/oned/ODReader.h b/core/src/oned/ODReader.h index 5ccf2b43a0..ab2b5866a8 100644 --- a/core/src/oned/ODReader.h +++ b/core/src/oned/ODReader.h @@ -23,7 +23,7 @@ class Reader : public ZXing::Reader { public: explicit Reader(const DecodeHints& hints); - ~Reader() override; + ~Reader() override; Result decode(const BinaryBitmap& image) const override; Results decode(const BinaryBitmap& image, int maxSymbols) const override; diff --git a/core/src/oned/ODRowReader.h b/core/src/oned/ODRowReader.h index 762a0af353..837037528a 100644 --- a/core/src/oned/ODRowReader.h +++ b/core/src/oned/ODRowReader.h @@ -10,6 +10,7 @@ #include "BitArray.h" #include "Pattern.h" #include "Result.h" +#include "ZXAlgorithms.h" #include #include @@ -146,13 +147,10 @@ class RowReader */ static BarAndSpaceI NarrowWideThreshold(const PatternView& view) { - BarAndSpaceI m = {std::numeric_limits::max(), - std::numeric_limits::max()}; - BarAndSpaceI M = {0, 0}; - for (int i = 0; i < view.size(); ++i) { - m[i] = std::min(m[i], view[i]); - M[i] = std::max(M[i], view[i]); - } + BarAndSpaceI m = {view[0], view[1]}; + BarAndSpaceI M = m; + for (int i = 2; i < view.size(); ++i) + UpdateMinMax(m[i], M[i], view[i]); BarAndSpaceI res; for (int i = 0; i < 2; ++i) { diff --git a/core/src/pdf417/PDFDecodedBitStreamParser.h b/core/src/pdf417/PDFDecodedBitStreamParser.h deleted file mode 100644 index 103db7c5b0..0000000000 --- a/core/src/pdf417/PDFDecodedBitStreamParser.h +++ /dev/null @@ -1,31 +0,0 @@ -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include -#include - -namespace ZXing { - -class DecoderResult; - -namespace Pdf417 { - -/** -*

This class contains the methods for decoding the PDF417 codewords.

-* -* @author SITA Lab (kevin.osullivan@sita.aero) -* @author Guenther Grau -*/ -class DecodedBitStreamParser -{ -public: - static DecoderResult Decode(const std::vector& codewords, int ecLevel); -}; - -} // Pdf417 -} // ZXing diff --git a/core/src/pdf417/PDFDecodedBitStreamParser.cpp b/core/src/pdf417/PDFDecoder.cpp similarity index 78% rename from core/src/pdf417/PDFDecodedBitStreamParser.cpp rename to core/src/pdf417/PDFDecoder.cpp index 4a2574535c..1c6c89633b 100644 --- a/core/src/pdf417/PDFDecodedBitStreamParser.cpp +++ b/core/src/pdf417/PDFDecoder.cpp @@ -4,18 +4,17 @@ */ // SPDX-License-Identifier: Apache-2.0 -#include "PDFDecodedBitStreamParser.h" +#include "PDFDecoder.h" -#include "ByteArray.h" #include "CharacterSet.h" #include "DecoderResult.h" #include "PDFDecoderResultExtra.h" +#include "ZXAlgorithms.h" #include "ZXBigInteger.h" #include "ZXTestSupport.h" #include #include -#include #include #include @@ -31,38 +30,38 @@ enum class Mode PUNCT_SHIFT }; -static const int TEXT_COMPACTION_MODE_LATCH = 900; -static const int BYTE_COMPACTION_MODE_LATCH = 901; -static const int NUMERIC_COMPACTION_MODE_LATCH = 902; +constexpr int TEXT_COMPACTION_MODE_LATCH = 900; +constexpr int BYTE_COMPACTION_MODE_LATCH = 901; +constexpr int NUMERIC_COMPACTION_MODE_LATCH = 902; // 903-912 reserved -static const int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913; +constexpr int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913; // 914-917 reserved -static const int LINKAGE_OTHER = 918; +constexpr int LINKAGE_OTHER = 918; // 919 reserved -static const int LINKAGE_EANUCC = 920; // GS1 Composite -static const int READER_INIT = 921; // Reader Initialisation/Programming -static const int MACRO_PDF417_TERMINATOR = 922; -static const int BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923; -static const int BYTE_COMPACTION_MODE_LATCH_6 = 924; -static const int ECI_USER_DEFINED = 925; // 810900-811799 (1 codeword) -static const int ECI_GENERAL_PURPOSE = 926; // 900-810899 (2 codewords) -static const int ECI_CHARSET = 927; // 0-899 (1 codeword) -static const int BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928; - -static const int MAX_NUMERIC_CODEWORDS = 15; - -static const int MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME = 0; -static const int MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT = 1; -static const int MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP = 2; -static const int MACRO_PDF417_OPTIONAL_FIELD_SENDER = 3; -static const int MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE = 4; -static const int MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE = 5; -static const int MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM = 6; +constexpr int LINKAGE_EANUCC = 920; // GS1 Composite +constexpr int READER_INIT = 921; // Reader Initialisation/Programming +constexpr int MACRO_PDF417_TERMINATOR = 922; +constexpr int BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923; +constexpr int BYTE_COMPACTION_MODE_LATCH_6 = 924; +constexpr int ECI_USER_DEFINED = 925; // 810900-811799 (1 codeword) +constexpr int ECI_GENERAL_PURPOSE = 926; // 900-810899 (2 codewords) +constexpr int ECI_CHARSET = 927; // 0-899 (1 codeword) +constexpr int BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928; + +constexpr int MAX_NUMERIC_CODEWORDS = 15; + +constexpr int MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME = 0; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT = 1; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP = 2; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_SENDER = 3; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE = 4; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE = 5; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM = 6; static const char* PUNCT_CHARS = ";<>@[\\]_`~!\r\t,:\n-.$/\"|*()?{}'"; static const char* MIXED_CHARS = "0123456789&\r\t,:#-.$/+%*=^"; -static const int NUMBER_OF_SEQUENCE_CODEWORDS = 2; +constexpr int NUMBER_OF_SEQUENCE_CODEWORDS = 2; inline bool IsECI(int code) { @@ -449,7 +448,7 @@ Decode the above codewords involves Remove leading 1 => Result is 000213298174000 */ -static std::string DecodeBase900toBase10(const std::vector& codewords, int count) +static std::string DecodeBase900toBase10(const std::vector& codewords, int endIndex, int count) { // Table containing values for the exponent of 900. static const auto EXP900 = []() { @@ -463,7 +462,7 @@ static std::string DecodeBase900toBase10(const std::vector& codewords, int BigInteger result; for (int i = 0; i < count; i++) - result += EXP900[count - i - 1] * codewords[i]; + result += EXP900[count - i - 1] * codewords[endIndex - count + i]; std::string resultString = result.toString(); if (!resultString.empty() && resultString.front() == '1') @@ -486,44 +485,27 @@ static std::string DecodeBase900toBase10(const std::vector& codewords, int static int NumericCompaction(const std::vector& codewords, int codeIndex, Content& result) { int count = 0; - bool end = false; - std::vector numericCodewords(MAX_NUMERIC_CODEWORDS); + while (codeIndex < codewords[0]) { + int code = codewords[codeIndex]; + if (code < TEXT_COMPACTION_MODE_LATCH) { + count++; + codeIndex++; + } + if (count > 0 && (count == MAX_NUMERIC_CODEWORDS || codeIndex == codewords[0] || code >= TEXT_COMPACTION_MODE_LATCH)) { + result += DecodeBase900toBase10(codewords, codeIndex, count); + count = 0; + } - while (codeIndex < codewords[0] && !end) { - int code = codewords[codeIndex++]; if (code >= TEXT_COMPACTION_MODE_LATCH) { if (IsECI(code)) { // As operating in Basic Channel Mode (i.e. not embedding backslashed ECIs and doubling backslashes) // allow ECIs anywhere in Numeric Compaction (i.e. ISO/IEC 15438:2015 5.5.3.4 doesn't apply). - if (count > 0) { - result += DecodeBase900toBase10(numericCodewords, count); - count = 0; - } - codeIndex = ProcessECI(codewords, codeIndex, codewords[0], code, result); - continue; - } - if (!TerminatesCompaction(code)) + codeIndex = ProcessECI(codewords, codeIndex + 1, codewords[0], code, result); + } else if (TerminatesCompaction(code)) { + break; + } else { throw FormatError(); - - codeIndex--; - end = true; - } - if (codeIndex == codewords[0]) { - end = true; - } - if (code < TEXT_COMPACTION_MODE_LATCH) { - numericCodewords[count] = code; - count++; - } - if (count % MAX_NUMERIC_CODEWORDS == 0 || code == NUMERIC_COMPACTION_MODE_LATCH || end) { - // Re-invoking Numeric Compaction mode (by using codeword 902 - // while in Numeric Compaction mode) serves to terminate the - // current Numeric Compaction mode grouping as described in 5.4.4.2, - // and then to start a new one grouping. - if (count > 0) { - result += DecodeBase900toBase10(numericCodewords, count); - count = 0; } } } @@ -568,15 +550,11 @@ static int DecodeMacroOptionalNumericField(const std::vector& codewords, in ZXING_EXPORT_TEST_ONLY int DecodeMacroBlock(const std::vector& codewords, int codeIndex, DecoderResultExtra& resultMetadata) { - // we must have at least two bytes left for the segment index + // we must have at least two codewords left for the segment index if (codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0]) throw FormatError(); - std::vector segmentIndexArray(NUMBER_OF_SEQUENCE_CODEWORDS); - for (int i = 0; i < NUMBER_OF_SEQUENCE_CODEWORDS; i++, codeIndex++) - segmentIndexArray[i] = codewords[codeIndex]; - - std::string strBuf = DecodeBase900toBase10(segmentIndexArray, NUMBER_OF_SEQUENCE_CODEWORDS); + std::string strBuf = DecodeBase900toBase10(codewords, codeIndex += NUMBER_OF_SEQUENCE_CODEWORDS, NUMBER_OF_SEQUENCE_CODEWORDS); resultMetadata.setSegmentIndex(std::stoi(strBuf)); @@ -584,11 +562,10 @@ int DecodeMacroBlock(const std::vector& codewords, int codeIndex, DecoderRe // (See ISO/IEC 15438:2015 Annex H.6) and preserves all info, but some generators (e.g. TEC-IT) write // the fileId using text compaction, so in those cases the fileId will appear mangled. std::ostringstream fileId; - fileId.fill('0'); for (; codeIndex < codewords[0] && codewords[codeIndex] != MACRO_PDF417_TERMINATOR && codewords[codeIndex] != BEGIN_MACRO_PDF417_OPTIONAL_FIELD; codeIndex++) { - fileId << std::setw(3) << codewords[codeIndex]; + fileId << ToString(codewords[codeIndex], 3); } resultMetadata.setFileId(fileId.str()); @@ -664,79 +641,69 @@ int DecodeMacroBlock(const std::vector& codewords, int codeIndex, DecoderRe if (resultMetadata.isLastSegment()) optionalFieldsLength--; // do not include terminator - resultMetadata.setOptionalData(std::vector(codewords.begin() + optionalFieldsStart, - codewords.begin() + optionalFieldsStart + optionalFieldsLength)); + resultMetadata.setOptionalData( + std::vector(codewords.begin() + optionalFieldsStart, codewords.begin() + optionalFieldsStart + optionalFieldsLength)); } return codeIndex; } -DecoderResult -DecodedBitStreamParser::Decode(const std::vector& codewords, int ecLevel) +DecoderResult Decode(const std::vector& codewords) { Content result; - result.symbology = { 'L', '2', char(-1) }; + result.symbology = {'L', '2', char(-1)}; bool readerInit = false; auto resultMetadata = std::make_shared(); - int codeIndex = 1; - while (codeIndex < codewords[0]) { - int code = codewords[codeIndex++]; - switch (code) { - case TEXT_COMPACTION_MODE_LATCH: - codeIndex = TextCompaction(codewords, codeIndex, result); - break; - case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: - // This should only be encountered once in this loop, when default Text Compaction mode applies - // (see default case below) - codeIndex = TextCompaction(codewords, codeIndex - 1, result); - break; - case BYTE_COMPACTION_MODE_LATCH: - case BYTE_COMPACTION_MODE_LATCH_6: - codeIndex = ByteCompaction(code, codewords, codeIndex, result); - break; - case NUMERIC_COMPACTION_MODE_LATCH: - codeIndex = NumericCompaction(codewords, codeIndex, result); - break; - case ECI_CHARSET: - case ECI_GENERAL_PURPOSE: - case ECI_USER_DEFINED: - codeIndex = ProcessECI(codewords, codeIndex, codewords[0], code, result); - break; - case BEGIN_MACRO_PDF417_CONTROL_BLOCK: - codeIndex = DecodeMacroBlock(codewords, codeIndex, *resultMetadata); - break; - case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: - case MACRO_PDF417_TERMINATOR: - // Should not see these outside a macro block - throw FormatError(); - break; - case READER_INIT: - if (codeIndex != 2) // Must be first codeword after symbol length (ISO/IEC 15438:2015 5.4.1.4) - throw FormatError(); - else - readerInit = true; - break; - case LINKAGE_EANUCC: - if (codeIndex != 2) // Must be first codeword after symbol length (GS1 Composite ISO/IEC 24723:2010 4.3) + try { + for (int codeIndex = 1; codeIndex < codewords[0];) { + int code = codewords[codeIndex++]; + switch (code) { + case TEXT_COMPACTION_MODE_LATCH: codeIndex = TextCompaction(codewords, codeIndex, result); break; + // This should only be encountered once in this loop, when default Text Compaction mode applies + // (see default case below) + case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: codeIndex = TextCompaction(codewords, codeIndex - 1, result); break; + case BYTE_COMPACTION_MODE_LATCH: + case BYTE_COMPACTION_MODE_LATCH_6: codeIndex = ByteCompaction(code, codewords, codeIndex, result); break; + case NUMERIC_COMPACTION_MODE_LATCH: codeIndex = NumericCompaction(codewords, codeIndex, result); break; + case ECI_CHARSET: + case ECI_GENERAL_PURPOSE: + case ECI_USER_DEFINED: codeIndex = ProcessECI(codewords, codeIndex, codewords[0], code, result); break; + case BEGIN_MACRO_PDF417_CONTROL_BLOCK: codeIndex = DecodeMacroBlock(codewords, codeIndex, *resultMetadata); break; + case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: + case MACRO_PDF417_TERMINATOR: + // Should not see these outside a macro block throw FormatError(); - // TODO: handle else case - break; - case LINKAGE_OTHER: - // Allowed to treat as invalid by ISO/IEC 24723:2010 5.4.1.5 and 5.4.6.1 when in Basic Channel Mode - throw UnsupportedError("LINKAGE_OTHER, see ISO/IEC 24723:2010 5.4.1.5"); - break; - default: - if (code >= TEXT_COMPACTION_MODE_LATCH) { // Reserved codewords (all others in switch) - // Allowed to treat as invalid by ISO/IEC 24723:2010 5.4.6.1 when in Basic Channel Mode - throw UnsupportedError("TEXT_COMPACTION_MODE_LATCH, see ISO/IEC 24723:2010 5.4.6.1"); - } else { - // Default mode is Text Compaction mode Alpha sub-mode (ISO/IEC 15438:2015 5.4.2.1) - codeIndex = TextCompaction(codewords, codeIndex - 1, result); + break; + case READER_INIT: + if (codeIndex != 2) // Must be first codeword after symbol length (ISO/IEC 15438:2015 5.4.1.4) + throw FormatError(); + else + readerInit = true; + break; + case LINKAGE_EANUCC: + if (codeIndex != 2) // Must be first codeword after symbol length (GS1 Composite ISO/IEC 24723:2010 4.3) + throw FormatError(); + // TODO: handle else case + break; + case LINKAGE_OTHER: + // Allowed to treat as invalid by ISO/IEC 24723:2010 5.4.1.5 and 5.4.6.1 when in Basic Channel Mode + throw UnsupportedError("LINKAGE_OTHER, see ISO/IEC 15438:2015 5.4.1.5"); + break; + default: + if (code >= TEXT_COMPACTION_MODE_LATCH) { // Reserved codewords (all others in switch) + // Allowed to treat as invalid by ISO/IEC 24723:2010 5.4.6.1 when in Basic Channel Mode + throw UnsupportedError("Reserved codeword, see ISO/IEC 15438:2015 5.4.6.1"); + } else { + // Default mode is Text Compaction mode Alpha sub-mode (ISO/IEC 15438:2015 5.4.2.1) + codeIndex = TextCompaction(codewords, codeIndex - 1, result); + } + break; } - break; } + } catch (Error e) { + return e; } if (result.empty() && resultMetadata->segmentIndex() == -1) @@ -752,7 +719,6 @@ DecodedBitStreamParser::Decode(const std::vector& codewords, int ecLevel) } return DecoderResult(std::move(result)) - .setEcLevel(std::to_string(ecLevel)) .setStructuredAppend(sai) .setReaderInit(readerInit) .setExtra(resultMetadata); diff --git a/core/src/pdf417/PDFDecoder.h b/core/src/pdf417/PDFDecoder.h new file mode 100644 index 0000000000..d37d4b9f20 --- /dev/null +++ b/core/src/pdf417/PDFDecoder.h @@ -0,0 +1,20 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace ZXing { + +class DecoderResult; + +namespace Pdf417 { + +DecoderResult Decode(const std::vector& codewords); + +} // namespace Pdf417 +} // namespace ZXing diff --git a/core/src/pdf417/PDFDetector.h b/core/src/pdf417/PDFDetector.h index 1316c0a52c..81d22c34c1 100644 --- a/core/src/pdf417/PDFDetector.h +++ b/core/src/pdf417/PDFDetector.h @@ -35,7 +35,7 @@ class Detector { std::shared_ptr bits; std::list, 8>> points; - int rotation; + int rotation = -1; }; static Result Detect(const BinaryBitmap& image, bool multiple, bool tryRotate); diff --git a/core/src/pdf417/PDFEncoder.cpp b/core/src/pdf417/PDFEncoder.cpp index bafa090de3..2e33035925 100644 --- a/core/src/pdf417/PDFEncoder.cpp +++ b/core/src/pdf417/PDFEncoder.cpp @@ -287,8 +287,8 @@ static const short EC_COEFFICIENTS_L8[] = { 352, 77, 373, 504, 35, 599, 428, 2 63, 310, 863, 251, 366, 304, 282, 738, 675, 410, 389, 244, 31, 121, 303, 263, }; static const short* EC_COEFFICIENTS[] = {EC_COEFFICIENTS_L0, EC_COEFFICIENTS_L1, EC_COEFFICIENTS_L2, - EC_COEFFICIENTS_L3, EC_COEFFICIENTS_L4, EC_COEFFICIENTS_L5, - EC_COEFFICIENTS_L6, EC_COEFFICIENTS_L7, EC_COEFFICIENTS_L8}; + EC_COEFFICIENTS_L3, EC_COEFFICIENTS_L4, EC_COEFFICIENTS_L5, + EC_COEFFICIENTS_L6, EC_COEFFICIENTS_L7, EC_COEFFICIENTS_L8}; /** * Determines the number of error correction codewords for a specified error correction diff --git a/core/src/pdf417/PDFReader.cpp b/core/src/pdf417/PDFReader.cpp index d901ce0631..dd77c7cc1f 100644 --- a/core/src/pdf417/PDFReader.cpp +++ b/core/src/pdf417/PDFReader.cpp @@ -25,8 +25,6 @@ #include #ifdef PRINT_DEBUG -#include "PDFDecoderResultExtra.h" -#include "PDFDecodedBitStreamParser.h" #include "BitMatrixIO.h" #include #endif @@ -256,9 +254,6 @@ std::vector ReadCodeWords(BitMatrixCursor topCur, SymbolInfo info) return codeWords; } -// forward declaration from PDFScanningDecoder.cpp -DecoderResult DecodeCodewords(std::vector& codewords, int ecLevel, const std::vector& erasures); - static Result DecodePure(const BinaryBitmap& image_) { auto pimage = image_.getBitMatrix(); @@ -295,14 +290,7 @@ static Result DecodePure(const BinaryBitmap& image_) auto codeWords = ReadCodeWords(cur, info); - std::vector erasures; - for (int i = 0; i < Size(codeWords); ++i) - if (codeWords[i] == -1) { - codeWords[i] = 0; - erasures.push_back(i); - } - - auto res = DecodeCodewords(codeWords, info.ecLevel, erasures); + auto res = DecodeCodewords(codeWords, NumECCodeWords(info.ecLevel)); return Result(std::move(res), {{left, top}, {right, top}, {right, bottom}, {left, bottom}}, BarcodeFormat::PDF417); } diff --git a/core/src/pdf417/PDFScanningDecoder.cpp b/core/src/pdf417/PDFScanningDecoder.cpp index f3840748c7..bd5eac8d9c 100644 --- a/core/src/pdf417/PDFScanningDecoder.cpp +++ b/core/src/pdf417/PDFScanningDecoder.cpp @@ -12,8 +12,9 @@ #include "PDFBarcodeValue.h" #include "PDFCodewordDecoder.h" #include "PDFDetectionResult.h" -#include "PDFDecodedBitStreamParser.h" +#include "PDFDecoder.h" #include "PDFModulusGF.h" +#include "ZXAlgorithms.h" #include "ZXTestSupport.h" #include @@ -75,8 +76,8 @@ static bool CheckCodewordSkew(int codewordSize, int minCodewordWidth, int maxCod static ModuleBitCountType GetBitCountForCodeword(int codeword) { - ModuleBitCountType result; - result.fill(0); + ModuleBitCountType result; + result.fill(0); int previousValue = 0; int i = Size(result) - 1; while (true) { @@ -340,13 +341,14 @@ static bool AdjustCodewordCount(const DetectionResult& detectionResult, std::vec { auto numberOfCodewords = barcodeMatrix[0][1].value(); int calculatedNumberOfCodewords = detectionResult.barcodeColumnCount() * detectionResult.barcodeRowCount() - GetNumberOfECCodeWords(detectionResult.barcodeECLevel()); + if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > CodewordDecoder::MAX_CODEWORDS_IN_BARCODE) + calculatedNumberOfCodewords = 0; if (numberOfCodewords.empty()) { - if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > CodewordDecoder::MAX_CODEWORDS_IN_BARCODE) { + if (!calculatedNumberOfCodewords) return false; - } barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords); } - else if (numberOfCodewords[0] != calculatedNumberOfCodewords) { + else if (calculatedNumberOfCodewords && numberOfCodewords[0] != calculatedNumberOfCodewords) { // The calculated one is more reliable as it is derived from the row indicator columns barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords); } @@ -455,7 +457,7 @@ static std::vector FindErrorMagnitudes(const ModulusPoly& errorEvaluator, c * @return false if errors cannot be corrected, maybe because of too many errors */ ZXING_EXPORT_TEST_ONLY -bool DecodeErrorCorrection(std::vector& received, int numECCodewords, const std::vector& erasures, int& nbErrors) +bool DecodeErrorCorrection(std::vector& received, int numECCodewords, const std::vector& erasures [[maybe_unused]], int& nbErrors) { const ModulusGF& field = GetModulusGF(); ModulusPoly poly(field, received); @@ -474,23 +476,23 @@ bool DecodeErrorCorrection(std::vector& received, int numECCodewords, const return true; } - ModulusPoly knownErrors = field.one(); - for (int erasure : erasures) { - int b = field.exp(Size(received) - 1 - erasure); - // Add (1 - bx) term: - ModulusPoly term(field, { field.subtract(0, b), 1 }); - knownErrors = knownErrors.multiply(term); - } +// ModulusPoly knownErrors = field.one(); +// for (int erasure : erasures) { +// int b = field.exp(Size(received) - 1 - erasure); +// // Add (1 - bx) term: +// ModulusPoly term(field, { field.subtract(0, b), 1 }); +// knownErrors = knownErrors.multiply(term); +// } ModulusPoly syndrome(field, S); - //syndrome = syndrome.multiply(knownErrors); +// syndrome = syndrome.multiply(knownErrors); ModulusPoly sigma, omega; if (!RunEuclideanAlgorithm(field.buildMonomial(numECCodewords, 1), syndrome, numECCodewords, sigma, omega)) { return false; } - //sigma = sigma.multiply(knownErrors); +// sigma = sigma.multiply(knownErrors); std::vector errorLocations; if (!FindErrorLocations(sigma, errorLocations)) { @@ -564,12 +566,11 @@ static bool VerifyCodewordCount(std::vector& codewords, int numECCodewords) return true; } -DecoderResult DecodeCodewords(std::vector& codewords, int ecLevel, const std::vector& erasures) +static DecoderResult DecodeCodewords(std::vector& codewords, int numECCodewords, const std::vector& erasures) { if (codewords.empty()) return FormatError(); - int numECCodewords = 1 << (ecLevel + 1); int correctedErrorsCount = 0; if (!CorrectErrors(codewords, erasures, numECCodewords, correctedErrorsCount)) return ChecksumError(); @@ -578,11 +579,16 @@ DecoderResult DecodeCodewords(std::vector& codewords, int ecLevel, const st return FormatError(); // Decode the codewords - try { - return DecodedBitStreamParser::Decode(codewords, ecLevel); - } catch (Error e) { - return e; - } + return Decode(codewords).setEcLevel(std::to_string(numECCodewords * 100 / Size(codewords)) + "%"); +} + +DecoderResult DecodeCodewords(std::vector& codewords, int numECCodeWords) +{ + for (auto& cw : codewords) + cw = std::clamp(cw, 0, CodewordDecoder::MAX_CODEWORDS_IN_BARCODE); + + // erasures array has never been actually used inside the error correction code + return DecodeCodewords(codewords, numECCodeWords, {}); } @@ -610,7 +616,7 @@ static DecoderResult CreateDecoderResultFromAmbiguousValues(int ecLevel, std::ve for (size_t i = 0; i < ambiguousIndexCount.size(); i++) { codewords[ambiguousIndexes[i]] = ambiguousIndexValues[i][ambiguousIndexCount[i]]; } - auto result = DecodeCodewords(codewords, ecLevel, erasureArray); + auto result = DecodeCodewords(codewords, NumECCodeWords(ecLevel), erasureArray); if (result.error() != Error::Checksum) { return result; } @@ -729,8 +735,7 @@ ScanningDecoder::Decode(const BitMatrix& image, const Nullable& ima if (codeword != nullptr) { detectionResult.column(barcodeColumn).value().setCodeword(imageRow, codeword); previousStartColumn = startColumn; - minCodewordWidth = std::min(minCodewordWidth, codeword.value().width()); - maxCodewordWidth = std::max(maxCodewordWidth, codeword.value().width()); + UpdateMinMax(minCodewordWidth, maxCodewordWidth, codeword.value().width()); } } } diff --git a/core/src/pdf417/PDFScanningDecoder.h b/core/src/pdf417/PDFScanningDecoder.h index 8516699bf7..9e3460ef20 100644 --- a/core/src/pdf417/PDFScanningDecoder.h +++ b/core/src/pdf417/PDFScanningDecoder.h @@ -6,6 +6,8 @@ #pragma once +#include + namespace ZXing { class BitMatrix; @@ -27,5 +29,12 @@ class ScanningDecoder int minCodewordWidth, int maxCodewordWidth); }; +inline int NumECCodeWords(int ecLevel) +{ + return 1 << (ecLevel + 1); +} + +DecoderResult DecodeCodewords(std::vector& codewords, int numECCodeWords); + } // Pdf417 } // ZXing diff --git a/core/src/qrcode/QRBitMatrixParser.cpp b/core/src/qrcode/QRBitMatrixParser.cpp index e693857e1f..b970266c30 100644 --- a/core/src/qrcode/QRBitMatrixParser.cpp +++ b/core/src/qrcode/QRBitMatrixParser.cpp @@ -34,15 +34,11 @@ static bool hasValidDimension(const BitMatrix& bitMatrix, bool isMicro) const Version* ReadVersion(const BitMatrix& bitMatrix) { int dimension = bitMatrix.height(); - bool isMicro = dimension < 21; - if (!hasValidDimension(bitMatrix, isMicro)) - return nullptr; - - int provisionalVersion = (dimension - Version::DimensionOffset(isMicro)) / Version::DimensionStep(isMicro); + const Version* version = Version::FromDimension(dimension); - if (provisionalVersion <= 6) - return Version::VersionForNumber(provisionalVersion, isMicro); + if (!version || version->versionNumber() < 7) + return version; for (bool mirror : {false, true}) { // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) @@ -51,10 +47,9 @@ const Version* ReadVersion(const BitMatrix& bitMatrix) for (int x = dimension - 9; x >= dimension - 11; --x) AppendBit(versionBits, getBit(bitMatrix, x, y, mirror)); - auto theParsedVersion = Version::DecodeVersionInformation(versionBits); - // TODO: why care for the contents of the version bits if we know the dimension already? - if (theParsedVersion != nullptr && theParsedVersion->dimensionForVersion() == dimension) - return theParsedVersion; + version = Version::DecodeVersionInformation(versionBits); + if (version && version->dimension() == dimension) + return version; } return nullptr; diff --git a/core/src/qrcode/QRCodecMode.cpp b/core/src/qrcode/QRCodecMode.cpp index 9d018d64bd..3d9f435996 100644 --- a/core/src/qrcode/QRCodecMode.cpp +++ b/core/src/qrcode/QRCodecMode.cpp @@ -6,6 +6,7 @@ #include "QRCodecMode.h" +#include "Error.h" #include "QRVersion.h" #include "ZXAlgorithms.h" @@ -14,9 +15,9 @@ namespace ZXing::QRCode { -CodecMode CodecModeForBits(int bits, bool isMirco) +CodecMode CodecModeForBits(int bits, bool isMicro) { - if (!isMirco) { + if (!isMicro) { if ((bits >= 0x00 && bits <= 0x05) || (bits >= 0x07 && bits <= 0x09) || bits == 0x0d) return static_cast(bits); } else { @@ -25,7 +26,7 @@ CodecMode CodecModeForBits(int bits, bool isMirco) return Bits2Mode[bits]; } - throw std::invalid_argument("Invalid mode"); + throw FormatError("Invalid codec mode"); } int CharacterCountBits(CodecMode mode, const Version& version) diff --git a/core/src/qrcode/QRCodecMode.h b/core/src/qrcode/QRCodecMode.h index 54c5dbe622..2c4f9d3551 100644 --- a/core/src/qrcode/QRCodecMode.h +++ b/core/src/qrcode/QRCodecMode.h @@ -32,9 +32,9 @@ enum class CodecMode * @param bits variable number of bits encoding a QR Code data mode * @param isMicro is this a MicroQRCode * @return Mode encoded by these bits - * @throws std::invalid_argument if bits do not correspond to a known mode + * @throws FormatError if bits do not correspond to a known mode */ -CodecMode CodecModeForBits(int bits, bool isMirco = false); +CodecMode CodecModeForBits(int bits, bool isMicro = false); /** * @param version version in question diff --git a/core/src/qrcode/QRDecoder.cpp b/core/src/qrcode/QRDecoder.cpp index 967fdd7688..905f0a5c8d 100644 --- a/core/src/qrcode/QRDecoder.cpp +++ b/core/src/qrcode/QRDecoder.cpp @@ -209,7 +209,7 @@ static ECI ParseECIValue(BitSource& bits) * a terminator code. If true, then the decoding can finish. If false, then the decoding * can read off the next mode code. * - * See ISO 18004:2006, 6.4.1 Table 2 + * See ISO 18004:2015, 7.4.1 Table 2 * * @param bits the stream of bits that might have a terminator code * @param version the QR or micro QR code version diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index a4d4351090..7b6c16d461 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -19,8 +19,6 @@ #include "Quadrilateral.h" #include "RegressionLine.h" -#include "BitMatrixIO.h" - #include #include #include @@ -29,9 +27,26 @@ #include #include +#ifdef PRINT_DEBUG +#include "BitMatrixIO.h" +#else +#define printf(...){} +#endif + namespace ZXing::QRCode { constexpr auto PATTERN = FixedPattern<5, 7>{1, 1, 3, 1, 1}; +constexpr bool E2E = true; + +PatternView FindPattern(const PatternView& view) +{ + return FindLeftGuard(view, PATTERN.size(), [](const PatternView& view, int spaceInPixel) { + // perform a fast plausability test for 1:1:3:1:1 pattern + if (view[2] < 2 * std::max(view[0], view[4]) || view[2] < std::max(view[1], view[3])) + return 0.f; + return IsPattern(view, PATTERN, spaceInPixel, 0.1); // the requires 4, here we accept almost 0 + }); +} std::vector FindFinderPatterns(const BitMatrix& image, bool tryHarder) { @@ -48,21 +63,28 @@ std::vector FindFinderPatterns(const BitMatrix& image, bool t skip = MIN_SKIP; std::vector res; + [[maybe_unused]] int N = 0; + PatternRow row; for (int y = skip - 1; y < height; y += skip) { - PatternRow row; GetPatternRow(image, y, row, false); PatternView next = row; - while (next = FindLeftGuard(next, 0, PATTERN, 0.5), next.isValid()) { + while (next = FindPattern(next), next.isValid()) { PointF p(next.pixelsInFront() + next[0] + next[1] + next[2] / 2.0, y + 0.5); // make sure p is not 'inside' an already found pattern area if (FindIf(res, [p](const auto& old) { return distance(p, old) < old.size / 2; }) == res.end()) { - auto pattern = LocateConcentricPattern(image, PATTERN, p, - Reduce(next) * 3 / 2); // 1.5 for very skewed samples + log(p); + N++; + auto pattern = LocateConcentricPattern(image, PATTERN, p, + Reduce(next) * 3); // 3 for very skewed samples if (pattern) { log(*pattern, 3); + log(*pattern + PointF(.2, 0), 3); + log(*pattern - PointF(.2, 0), 3); + log(*pattern + PointF(0, .2), 3); + log(*pattern - PointF(0, .2), 3); assert(image.get(pattern->x, pattern->y)); res.push_back(*pattern); } @@ -74,6 +96,8 @@ std::vector FindFinderPatterns(const BitMatrix& image, bool t } } + printf("FPs? : %d\n", N); + return res; } @@ -127,9 +151,14 @@ FinderPatternSets GenerateFinderPatternSets(FinderPatterns& patterns) auto distAB = std::sqrt(distAB2); auto distBC = std::sqrt(distBC2); + // Make sure distAB and distBC don't differ more than reasonable + // TODO: make sure the constant 2 is not to conservative for reasonably tilted symbols + if (distAB > 2 * distBC || distBC > 2 * distAB) + continue; + // Estimate the module count and ignore this set if it can not result in a valid decoding if (auto moduleCount = (distAB + distBC) / (2 * (a->size + b->size + c->size) / (3 * 7.f)) + 7; - moduleCount < 21 * 0.9 || moduleCount > 177 * 1.05) + moduleCount < 21 * 0.9 || moduleCount > 177 * 1.5) // moduleCount may be overestimated, see above continue; // Make sure the angle between AB and BC does not deviate from 90° by more than 45° @@ -167,34 +196,32 @@ FinderPatternSets GenerateFinderPatternSets(FinderPatterns& patterns) res.reserve(sets.size()); for (auto& [d, s] : sets) res.push_back(s); + + printf("FPSets: %d\n", Size(res)); + return res; } -static double EstimateModuleSize(const BitMatrix& image, PointF a, PointF b) +static double EstimateModuleSize(const BitMatrix& image, ConcentricPattern a, ConcentricPattern b) { BitMatrixCursorF cur(image, a, b - a); assert(cur.isBlack()); - if (!cur.stepToEdge(3, static_cast(distance(a, b) / 3), true)) + auto pattern = ReadSymmetricPattern<5>(cur, a.size * 2); + if (!pattern || !IsPattern(*pattern, PATTERN)) return -1; - assert(cur.isBlack()); - cur.turnBack(); - - - auto pattern = cur.readPattern>(); - - return (2 * Reduce(pattern) - pattern[0] - pattern[4]) / 12.0 * length(cur.d); + return (2 * Reduce(*pattern) - (*pattern)[0] - (*pattern)[4]) / 12.0 * length(cur.d); } struct DimensionEstimate { int dim = 0; double ms = 0; - int err = 0; + int err = 4; }; -static DimensionEstimate EstimateDimension(const BitMatrix& image, PointF a, PointF b) +static DimensionEstimate EstimateDimension(const BitMatrix& image, ConcentricPattern a, ConcentricPattern b) { auto ms_a = EstimateModuleSize(image, a, b); auto ms_b = EstimateModuleSize(image, b, a); @@ -257,25 +284,78 @@ static double EstimateTilt(const FinderPatternSet& fp) return double(max) / min; } +static PerspectiveTransform Mod2Pix(int dimension, PointF brOffset, QuadrilateralF pix) +{ + auto quad = Rectangle(dimension, dimension, 3.5); + quad[2] = quad[2] - brOffset; + return {quad, pix}; +} + +static std::optional LocateAlignmentPattern(const BitMatrix& image, int moduleSize, PointF estimate) +{ + log(estimate, 4); + + for (auto d : {PointF{0, 0}, {0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, +#if 1 + }) { +#else + {0, -2}, {0, 2}, {-2, 0}, {2, 0}, {-1, -2}, {1, -2}, {-1, 2}, {1, 2}, {-2, -1}, {-2, 1}, {2, -1}, {2, 1}}) { +#endif + auto cor = CenterOfRing(image, PointI(estimate + moduleSize * 2.25 * d), moduleSize * 3, 1, false); + + // if we did not land on a black pixel the concentric pattern finder will fail + if (!cor || !image.get(*cor)) + continue; + + if (auto cor1 = CenterOfRing(image, PointI(*cor), moduleSize, 1)) + if (auto cor2 = CenterOfRing(image, PointI(*cor), moduleSize * 3, -2)) + if (distance(*cor1, *cor2) < moduleSize / 2) { + auto res = (*cor1 + *cor2) / 2; + log(res, 3); + return res; + } + } + + return {}; +} + +static const Version* ReadVersion(const BitMatrix& image, int dimension, const PerspectiveTransform& mod2Pix) +{ + int bits[2] = {}; + + for (bool mirror : {false, true}) { + // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) + int versionBits = 0; + for (int y = 5; y >= 0; --y) + for (int x = dimension - 9; x >= dimension - 11; --x) { + auto mod = mirror ? PointI{y, x} : PointI{x, y}; + auto pix = mod2Pix(centered(mod)); + if (!image.isIn(pix)) + versionBits = -1; + else + AppendBit(versionBits, image.get(pix)); + log(pix, 3); + } + bits[static_cast(mirror)] = versionBits; + } + + return Version::DecodeVersionInformation(bits[0], bits[1]); +} + DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) { auto top = EstimateDimension(image, fp.tl, fp.tr); auto left = EstimateDimension(image, fp.tl, fp.bl); - if (!top.dim || !left.dim) + if (!top.dim && !left.dim) return {}; - auto best = top.err < left.err ? top : left; + auto best = top.err == left.err ? (top.dim > left.dim ? top : left) : (top.err < left.err ? top : left); int dimension = best.dim; int moduleSize = static_cast(best.ms + 1); - auto quad = Rectangle(dimension, dimension, 3.5); - - auto sample = [&](PointF br, PointF quad2) { - log(br, 3); - quad[2] = quad2; - return SampleGrid(image, dimension, dimension, {quad, {fp.tl, fp.tr, br, fp.bl}}); - }; + auto br = PointF{-1, -1}; + auto brOffset = PointF{3, 3}; // Everything except version 1 (21 modules) has an alignment pattern. Estimate the center of that by intersecting // line extensions of the 1 module wide square around the finder patterns. This could also help with detecting @@ -293,29 +373,137 @@ DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) auto brInter = (intersect(bl2, tr2) + intersect(bl3, tr3)) / 2; log(brInter, 3); - // check that the estimated alignment pattern position is inside of the image - if (image.isIn(PointI(brInter), 3 * moduleSize)) { - if (dimension > 21) { - // just in case we landed outside of the central black module of the alignment pattern, use the center - // of the next best circle (either outer or inner edge of the white part of the alignment pattern) - auto brCoR = CenterOfRing(image, PointI(brInter), moduleSize * 4, 1, false).value_or(brInter); - // if we did not land on a black pixel or the concentric pattern finder fails, - // leave the intersection of the lines as the best guess - if (image.get(brCoR)) { - if (auto brCP = LocateConcentricPattern(image, FixedPattern<3, 3>{1, 1, 1}, brCoR, moduleSize * 3)) - return sample(*brCP, quad[2] - PointF(3, 3)); + if (dimension > 21) + if (auto brCP = LocateAlignmentPattern(image, moduleSize, brInter)) + br = *brCP; + + // if the symbol is tilted or the resolution of the RegressionLines is sufficient, use their intersection + // as the best estimate (see discussion in #199 and test image estimate-tilt.jpg ) + if (!image.isIn(br) && (EstimateTilt(fp) > 1.1 || (bl2.isHighRes() && bl3.isHighRes() && tr2.isHighRes() && tr3.isHighRes()))) + br = brInter; + } + + // otherwise the simple estimation used by upstream is used as a best guess fallback + if (!image.isIn(br)) { + br = fp.tr - fp.tl + fp.bl; + brOffset = PointF(0, 0); + } + + log(br, 3); + auto mod2Pix = Mod2Pix(dimension, brOffset, {fp.tl, fp.tr, br, fp.bl}); + + if( dimension >= Version::DimensionOfVersion(7, false)) { + auto version = ReadVersion(image, dimension, mod2Pix); + + // if the version bits are garbage -> discard the detection + if (!version || std::abs(version->dimension() - dimension) > 8) + return DetectorResult(); + if (version->dimension() != dimension) { + printf("update dimension: %d -> %d\n", dimension, version->dimension()); + dimension = version->dimension(); + mod2Pix = Mod2Pix(dimension, brOffset, {fp.tl, fp.tr, br, fp.bl}); + } +#if 1 + auto& apM = version->alignmentPatternCenters(); // alignment pattern positions in modules + auto apP = Matrix>(Size(apM), Size(apM)); // found/guessed alignment pattern positions in pixels + const int N = Size(apM) - 1; + + // project the alignment pattern at module coordinates x/y to pixel coordinate based on current mod2Pix + auto projectM2P = [&mod2Pix, &apM](int x, int y) { return mod2Pix(centered(PointI(apM[x], apM[y]))); }; + + auto findInnerCornerOfConcentricPattern = [&image, &apP, &projectM2P](int x, int y, const ConcentricPattern& fp) { + auto pc = *apP.set(x, y, projectM2P(x, y)); + if (auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 2)) + for (auto c : *fpQuad) + if (distance(c, pc) < fp.size / 2) + apP.set(x, y, c); + }; + + findInnerCornerOfConcentricPattern(0, 0, fp.tl); + findInnerCornerOfConcentricPattern(0, N, fp.bl); + findInnerCornerOfConcentricPattern(N, 0, fp.tr); + + auto bestGuessAPP = [&](int x, int y){ + if (auto p = apP(x, y)) + return *p; + return projectM2P(x, y); + }; + + for (int y = 0; y <= N; ++y) + for (int x = 0; x <= N; ++x) { + if (apP(x, y)) + continue; + + PointF guessed = + x * y == 0 ? bestGuessAPP(x, y) : bestGuessAPP(x - 1, y) + bestGuessAPP(x, y - 1) - bestGuessAPP(x - 1, y - 1); + if (auto found = LocateAlignmentPattern(image, moduleSize, guessed)) + apP.set(x, y, *found); + } + + // go over the whole set of alignment patters again and try to fill any remaining gap by using available neighbors as guides + for (int y = 0; y <= N; ++y) + for (int x = 0; x <= N; ++x) { + if (apP(x, y)) + continue; + + // find the two closest valid alignment pattern pixel positions both horizontally and vertically + std::vector hori, verti; + for (int i = 2; i < 2 * N + 2 && Size(hori) < 2; ++i) { + int xi = x + i / 2 * (i%2 ? 1 : -1); + if (0 <= xi && xi <= N && apP(xi, y)) + hori.push_back(*apP(xi, y)); + } + for (int i = 2; i < 2 * N + 2 && Size(verti) < 2; ++i) { + int yi = y + i / 2 * (i%2 ? 1 : -1); + if (0 <= yi && yi <= N && apP(x, yi)) + verti.push_back(*apP(x, yi)); + } + + // if we found 2 each, intersect the two lines that are formed by connecting the point pairs + if (Size(hori) == 2 && Size(verti) == 2) { + auto guessed = intersect(RegressionLine(hori[0], hori[1]), RegressionLine(verti[0], verti[1])); + auto found = LocateAlignmentPattern(image, moduleSize, guessed); + // search again near that intersection and if the search fails, use the intersection + if (!found) printf("location guessed at %dx%d\n", x, y); + apP.set(x, y, found ? *found : guessed); } } - // if the symbol is tilted or the resolution of the RegressionLines is sufficient, use their intersection - // as the best estimate (see discussion in #199 and test image estimate-tilt.jpg ) - if (EstimateTilt(fp) > 1.1 || (bl2.isHighRes() && bl3.isHighRes() && tr2.isHighRes() && tr3.isHighRes())) - return sample(brInter, quad[2] - PointF(3, 3)); - } + if (auto c = apP.get(N, N)) + mod2Pix = Mod2Pix(dimension, PointF(3, 3), {fp.tl, fp.tr, *c, fp.bl}); + + // go over the whole set of alignment patters again and fill any remaining gaps by a projection based on an updated mod2Pix + // projection. This works if the symbol is flat, wich is a reasonable fall-back assumption. + for (int y = 0; y <= N; ++y) + for (int x = 0; x <= N; ++x) { + if (apP(x, y)) + continue; + + printf("locate failed at %dx%d\n", x, y); + apP.set(x, y, projectM2P(x, y)); + } + +#ifdef PRINT_DEBUG + for (int y = 0; y <= N; ++y) + for (int x = 0; x <= N; ++x) + log(*apP(x, y), 2); +#endif + + // assemble a list of region-of-interests based on the found alignment pattern pixel positions + ROIs rois; + for (int y = 0; y < N; ++y) + for (int x = 0; x < N; ++x) { + int x0 = apM[x], x1 = apM[x + 1], y0 = apM[y], y1 = apM[y + 1]; + rois.push_back({x0 - (x == 0) * 6, x1 + (x == N - 1) * 7, y0 - (y == 0) * 6, y1 + (y == N - 1) * 7, + PerspectiveTransform{Rectangle(x0, x1, y0, y1), + {*apP(x, y), *apP(x + 1, y), *apP(x + 1, y + 1), *apP(x, y + 1)}}}); + } + + return SampleGrid(image, dimension, dimension, rois); +#endif } - // otherwise the simple estimation used by upstream is used as a best guess fallback - return sample(fp.tr - fp.tl + fp.bl, quad[2]); + return SampleGrid(image, dimension, dimension, mod2Pix); } /** @@ -345,13 +533,14 @@ DetectorResult DetectPureQR(const BitMatrix& image) Pattern diagonal; // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts for (auto [p, d] : {std::pair(tl, PointI{1, 1}), {tr, {-1, 1}}, {bl, {1, -1}}}) { - diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3); + diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3 + 1); if (!IsPattern(diagonal, PATTERN)) return {}; } auto fpWidth = Reduce(diagonal); - auto dimension = EstimateDimension(image, tl + fpWidth / 2 * PointF(1, 1), tr + fpWidth / 2 * PointF(-1, 1)).dim; + auto dimension = + EstimateDimension(image, {tl + fpWidth / 2 * PointF(1, 1), fpWidth}, {tr + fpWidth / 2 * PointF(-1, 1), fpWidth}).dim; float moduleSize = float(width) / dimension; if (dimension < MIN_MODULES || dimension > MAX_MODULES || diff --git a/core/src/qrcode/QREncoder.cpp b/core/src/qrcode/QREncoder.cpp index 206e4cd70a..cc3f71d8d3 100644 --- a/core/src/qrcode/QREncoder.cpp +++ b/core/src/qrcode/QREncoder.cpp @@ -256,7 +256,7 @@ static bool WillFit(int numInputBits, const Version& version, ErrorCorrectionLev // numBytes = 196 int numBytes = version.totalCodewords(); // getNumECBytes = 130 - auto ecBlocks = version.ecBlocksForLevel(ecLevel); + auto& ecBlocks = version.ecBlocksForLevel(ecLevel); int numEcBytes = ecBlocks.totalCodewords(); // getNumDataBytes = 196 - 130 = 66 int numDataBytes = numBytes - numEcBytes; @@ -267,7 +267,7 @@ static bool WillFit(int numInputBits, const Version& version, ErrorCorrectionLev static const Version& ChooseVersion(int numInputBits, ErrorCorrectionLevel ecLevel) { for (int versionNum = 1; versionNum <= 40; versionNum++) { - const Version* version = Version::VersionForNumber(versionNum); + const Version* version = Version::FromNumber(versionNum); if (WillFit(numInputBits, *version, ecLevel)) { return *version; } @@ -472,7 +472,7 @@ static const Version& RecommendVersion(ErrorCorrectionLevel ecLevel, CodecMode m // Hard part: need to know version to know how many bits length takes. But need to know how many // bits it takes to know version. First we take a guess at version by assuming version will be // the minimum, 1: - int provisionalBitsNeeded = CalculateBitsNeeded(mode, headerBits, dataBits, *Version::VersionForNumber(1)); + int provisionalBitsNeeded = CalculateBitsNeeded(mode, headerBits, dataBits, *Version::FromNumber(1)); const Version& provisionalVersion = ChooseVersion(provisionalBitsNeeded, ecLevel); // Use that guess to calculate the right version. I am still not sure this works in 100% of cases. @@ -517,7 +517,7 @@ EncodeResult Encode(const std::wstring& content, ErrorCorrectionLevel ecLevel, C const Version* version; if (versionNumber > 0) { - version = Version::VersionForNumber(versionNumber); + version = Version::FromNumber(versionNumber); if (version != nullptr) { int bitsNeeded = CalculateBitsNeeded(mode, headerBits, dataBits, *version); if (!WillFit(bitsNeeded, *version, ecLevel)) { @@ -556,7 +556,7 @@ EncodeResult Encode(const std::wstring& content, ErrorCorrectionLevel ecLevel, C output.version = version; // Choose the mask pattern and set to "qrCode". - int dimension = version->dimensionForVersion(); + int dimension = version->dimension(); TritMatrix matrix(dimension, dimension); output.maskPattern = maskPattern != -1 ? maskPattern : ChooseMaskPattern(finalBits, ecLevel, *version, matrix); diff --git a/core/src/qrcode/QRFormatInformation.cpp b/core/src/qrcode/QRFormatInformation.cpp index 87e4be2ffe..9195a7fa65 100644 --- a/core/src/qrcode/QRFormatInformation.cpp +++ b/core/src/qrcode/QRFormatInformation.cpp @@ -7,6 +7,7 @@ #include "QRFormatInformation.h" #include "BitHacks.h" +#include "ZXAlgorithms.h" #include @@ -99,7 +100,7 @@ static FormatInformation FindBestFormatInfo(int mask, const std::array usedFPs; Results results; if (_hints.hasFormat(BarcodeFormat::QRCode)) { auto allFPSets = GenerateFinderPatternSets(allFPs); - for (auto& fpSet : allFPSets) { + for (const auto& fpSet : allFPSets) { if (Contains(usedFPs, fpSet.bl) || Contains(usedFPs, fpSet.tl) || Contains(usedFPs, fpSet.tr)) continue; + logFPSet(fpSet); + auto detectorResult = SampleQR(*binImg, fpSet); if (detectorResult.isValid()) { auto decoderResult = Decode(detectorResult.bits()); @@ -88,7 +110,7 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const } if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { - for (auto fp : allFPs) { + for (const auto& fp : allFPs) { if (Contains(usedFPs, fp)) continue; diff --git a/core/src/qrcode/QRVersion.cpp b/core/src/qrcode/QRVersion.cpp index 537bc91fc7..2f202b654d 100644 --- a/core/src/qrcode/QRVersion.cpp +++ b/core/src/qrcode/QRVersion.cpp @@ -304,7 +304,7 @@ Version::Version(int versionNumber, const std::array& ecBlocks) _totalCodewords = ecBlocks[0].totalDataCodewords(); } -const Version* Version::VersionForNumber(int versionNumber, bool isMicro) +const Version* Version::FromNumber(int versionNumber, bool isMicro) { if (versionNumber < 1 || versionNumber > (isMicro ? 4 : 40)) { //throw std::invalid_argument("Version should be in range [1-40]."); @@ -313,38 +313,41 @@ const Version* Version::VersionForNumber(int versionNumber, bool isMicro) return &(isMicro ? AllMicroVersions() : AllVersions())[versionNumber - 1]; } -const Version* Version::ProvisionalVersionForDimension(int dimension, bool isMicro) +const Version* Version::FromDimension(int dimension) { + bool isMicro = dimension < 21; if (dimension % DimensionStep(isMicro) != 1) { //throw std::invalid_argument("Unexpected dimension"); return nullptr; } - return VersionForNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro); + return FromNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro); } -const Version* Version::DecodeVersionInformation(int versionBits) +const Version* Version::DecodeVersionInformation(int versionBitsA, int versionBitsB) { int bestDifference = std::numeric_limits::max(); int bestVersion = 0; int i = 0; for (int targetVersion : VERSION_DECODE_INFO) { // Do the version info bits match exactly? done. - if (targetVersion == versionBits) { - return VersionForNumber(i + 7); + if (targetVersion == versionBitsA || targetVersion == versionBitsB) { + return FromNumber(i + 7); } // Otherwise see if this is the closest to a real version info bit string // we have seen so far - int bitsDifference = BitHacks::CountBitsSet(versionBits ^ targetVersion); - if (bitsDifference < bestDifference) { - bestVersion = i + 7; - bestDifference = bitsDifference; + for (int bits : {versionBitsA, versionBitsB}) { + int bitsDifference = BitHacks::CountBitsSet(bits ^ targetVersion); + if (bitsDifference < bestDifference) { + bestVersion = i + 7; + bestDifference = bitsDifference; + } } ++i; } // We can tolerate up to 3 bits of error since no two version info codewords will // differ in less than 8 bits. if (bestDifference <= 3) { - return VersionForNumber(bestVersion); + return FromNumber(bestVersion); } // If we didn't find a close enough match, fail return nullptr; @@ -355,7 +358,7 @@ const Version* Version::DecodeVersionInformation(int versionBits) */ BitMatrix Version::buildFunctionPattern() const { - int dimension = dimensionForVersion(); + int dimension = this->dimension(); BitMatrix bitMatrix(dimension, dimension); // Top left finder pattern + separator + format diff --git a/core/src/qrcode/QRVersion.h b/core/src/qrcode/QRVersion.h index 024f1a7387..0b03270011 100644 --- a/core/src/qrcode/QRVersion.h +++ b/core/src/qrcode/QRVersion.h @@ -33,7 +33,7 @@ class Version int totalCodewords() const { return _totalCodewords; } - int dimensionForVersion() const { return DimensionOfVersion(_versionNumber, _isMicro); } + int dimension() const { return DimensionOfVersion(_versionNumber, _isMicro); } const ECBlocks& ecBlocksForLevel(ErrorCorrectionLevel ecLevel) const { return _ecBlocks[(int)ecLevel]; } @@ -54,11 +54,12 @@ class Version * @param dimension dimension in modules * @return Version for a QR Code of that dimension */ - static const Version* ProvisionalVersionForDimension(int dimension, bool isMicro = false); + static const Version* FromDimension(int dimension); - static const Version* VersionForNumber(int versionNumber, bool isMicro = false); + static const Version* FromNumber(int versionNumber, bool isMicro = false); + + static const Version* DecodeVersionInformation(int versionBitsA, int versionBitsB = 0); - static const Version* DecodeVersionInformation(int versionBits); private: int _versionNumber; std::vector _alignmentPatternCenters; diff --git a/example/ZXingQtCamReader.cpp b/example/ZXingQtCamReader.cpp index 3815bfe43b..940e474e4a 100644 --- a/example/ZXingQtCamReader.cpp +++ b/example/ZXingQtCamReader.cpp @@ -11,21 +11,21 @@ int main(int argc, char *argv[]) { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif ZXingQt::registerQmlAndMetaTypes(); - QGuiApplication app(argc, argv); - app.setApplicationName("ZXingQtCamReader"); - QQmlApplicationEngine engine; + QGuiApplication app(argc, argv); + app.setApplicationName("ZXingQtCamReader"); + QQmlApplicationEngine engine; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - engine.load(QUrl(QStringLiteral("qrc:/ZXingQt5CamReader.qml"))); + engine.load(QUrl(QStringLiteral("qrc:/ZXingQt5CamReader.qml"))); #else - engine.load(QUrl(QStringLiteral("qrc:/ZXingQt6CamReader.qml"))); + engine.load(QUrl(QStringLiteral("qrc:/ZXingQt6CamReader.qml"))); #endif - if (engine.rootObjects().isEmpty()) - return -1; + if (engine.rootObjects().isEmpty()) + return -1; - return app.exec(); + return app.exec(); } diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index 7636c8b765..b857b2505d 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -6,6 +6,7 @@ #include "ReadBarcode.h" #include "GTIN.h" +#include "ZXVersion.h" #include #include @@ -40,7 +41,8 @@ static void PrintUsage(const char* exePath) << " -bytes Write (only) the bytes content of the symbol(s) to stdout\n" << " -pngout \n" << " Write a copy of the input image with barcodes outlined by a green line\n" - << " -help Print usage information and exit\n" + << " -help Print usage information\n" + << " -version Print version information\n" << "\n" << "Supported formats are:\n"; for (auto f : BarcodeFormats::all()) { @@ -52,10 +54,17 @@ static void PrintUsage(const char* exePath) static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLine, bool& bytesOnly, std::vector& filePaths, std::string& outPath) { +#ifdef BUILD_EXPERIMENTAL_API + hints.setTryDenoise(true); +#endif + for (int i = 1; i < argc; ++i) { auto is = [&](const char* str) { return strncmp(argv[i], str, strlen(argv[i])) == 0; }; if (is("-fast")) { hints.setTryHarder(false); +#ifdef BUILD_EXPERIMENTAL_API + hints.setTryDenoise(false); +#endif } else if (is("-norotate")) { hints.setTryRotate(false); } else if (is("-noinvert")) { @@ -100,6 +109,9 @@ static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLi } else if (is("-help") || is("--help")) { PrintUsage(argv[0]); exit(0); + } else if (is("-version") || is("--version")) { + std::cout << "ZXingReader " << ZXING_VERSION_STR << "\n"; + exit(0); } else { filePaths.push_back(argv[i]); } @@ -277,7 +289,7 @@ int main(int argc, char* argv[]) if (blockSize < 1000 && duration < std::chrono::milliseconds(100)) blockSize *= 10; } while (duration < std::chrono::seconds(1)); - printf("time: %5.1f ms per frame\n", double(std::chrono::duration_cast(duration).count()) / N); + printf("time: %5.2f ms per frame\n", double(std::chrono::duration_cast(duration).count()) / N); } #endif } diff --git a/example/ZXingWriter.cpp b/example/ZXingWriter.cpp index 4f5c11531f..8258094b0a 100644 --- a/example/ZXingWriter.cpp +++ b/example/ZXingWriter.cpp @@ -8,6 +8,7 @@ #include "BitMatrixIO.h" #include "CharacterSet.h" #include "MultiFormatWriter.h" +#include "ZXVersion.h" #include #include @@ -23,13 +24,16 @@ using namespace ZXing; static void PrintUsage(const char* exePath) { - std::cout << "Usage: " << exePath << " [-size x] [-margin ] [-encoding ] [-ecc ] \n" - << " -size Size of generated image\n" - << " -margin Margin around barcode\n" - << " -encoding Encoding used to encode input text\n" - << " -ecc Error correction level, [0-8]\n" - << " -help Print usage information and exit\n" - << "\n" + std::cout << "Usage: " << exePath + << " [-size x] [-margin ] [-encoding ] [-ecc ] \n" + << " -size Size of generated image\n" + << " -margin Margin around barcode\n" + << " -encoding Encoding used to encode input text\n" + << " -ecc Error correction level, [0-8]\n" + << " -binary Interpret as a file name containing binary data\n" + << " -help Print usage information\n" + << " -version Print version information\n" + << "\n" << "Supported formats are:\n"; for (auto f : BarcodeFormatsFromString("Aztec Codabar Code39 Code93 Code128 DataMatrix EAN8 EAN13 ITF PDF417 QRCode UPCA UPCE")) std::cout << " " << ToString(f) << "\n"; @@ -51,32 +55,38 @@ static bool ParseSize(std::string str, int* width, int* height) } static bool ParseOptions(int argc, char* argv[], int* width, int* height, int* margin, CharacterSet* encoding, - int* eccLevel, BarcodeFormat* format, std::string* text, std::string* filePath) + int* eccLevel, BarcodeFormat* format, std::string* text, std::string* filePath, bool* inputIsFile) { int nonOptArgCount = 0; for (int i = 1; i < argc; ++i) { - if (strcmp(argv[i], "-size") == 0) { + auto is = [&](const char* str) { return strncmp(argv[i], str, strlen(argv[i])) == 0; }; + if (is("-size")) { if (++i == argc) return false; if (!ParseSize(argv[i], width, height)) { std::cerr << "Invalid size specification: " << argv[i] << std::endl; return false; } - } else if (strcmp(argv[i], "-margin") == 0) { + } else if (is("-margin")) { if (++i == argc) return false; *margin = std::stoi(argv[i]); - } else if (strcmp(argv[i], "-ecc") == 0) { + } else if (is("-ecc")) { if (++i == argc) return false; *eccLevel = std::stoi(argv[i]); - } else if (strcmp(argv[i], "-encoding") == 0) { + } else if (is("-encoding")) { if (++i == argc) return false; *encoding = CharacterSetFromString(argv[i]); - } else if (strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) { + } else if (is("-binary")) { + *inputIsFile = true; + } else if (is("-help") || is("--help")) { PrintUsage(argv[0]); exit(0); + } else if (is("-version") || is("--version")) { + std::cout << "ZXingWriter " << ZXING_VERSION_STR << "\n"; + exit(0); } else if (nonOptArgCount == 0) { *format = BarcodeFormatFromString(argv[i]); if (*format == BarcodeFormat::None) { @@ -108,23 +118,41 @@ static std::string GetExtension(const std::string& path) return ext; } +static std::string ReadFile(const std::string& fn) +{ + std::ifstream ifs(fn, std::ios::binary); + return ifs ? std::string(std::istreambuf_iterator(ifs), std::istreambuf_iterator()) : std::string(); +}; + int main(int argc, char* argv[]) { int width = 100, height = 100; int margin = 10; int eccLevel = -1; + bool inputIsFile = false; CharacterSet encoding = CharacterSet::Unknown; - std::string text, filePath; + std::string input, filePath; BarcodeFormat format; - if (!ParseOptions(argc, argv, &width, &height, &margin, &encoding, &eccLevel, &format, &text, &filePath)) { + if (!ParseOptions(argc, argv, &width, &height, &margin, &encoding, &eccLevel, &format, &input, &filePath, &inputIsFile)) { PrintUsage(argv[0]); return -1; } try { auto writer = MultiFormatWriter(format).setMargin(margin).setEncoding(encoding).setEccLevel(eccLevel); - auto matrix = writer.encode(text, width, height); + + BitMatrix matrix; + if (inputIsFile) { + auto file = ReadFile(input); + std::wstring bytes; + for (uint8_t c : file) + bytes.push_back(c); + writer.setEncoding(CharacterSet::BINARY); + matrix = writer.encode(bytes, width, height); + } else { + matrix = writer.encode(input, width, height); + } auto bitmap = ToMatrix(matrix); auto ext = GetExtension(filePath); diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 1402cc7a44..af2e247d80 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -6,14 +6,10 @@ #include "BlackboxTestRunner.h" -#include "DecoderResult.h" #include "ImageLoader.h" #include "ReadBarcode.h" -#include "ThresholdBinarizer.h" #include "Utf.h" #include "ZXAlgorithms.h" -#include "pdf417/PDFReader.h" -#include "qrcode/QRReader.h" #include #include @@ -351,12 +347,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 21, 21, 270 }, }); - runTests("datamatrix-1", "DataMatrix", 27, { - { 26, 27, 0 }, - { 0, 26, 90 }, - { 0, 26, 180 }, - { 0, 26, 270 }, - { 26, 0, pure }, + runTests("datamatrix-1", "DataMatrix", 29, { + { 27, 29, 0 }, + { 0, 27, 90 }, + { 0, 27, 180 }, + { 0, 27, 270 }, + { 28, 0, pure }, }); runTests("datamatrix-2", "DataMatrix", 13, { @@ -432,9 +428,9 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 7, 0, pure }, }); - runTests("ean13-1", "EAN-13", 31, { - { 24, 29, 0 }, - { 23, 29, 180 }, + runTests("ean13-1", "EAN-13", 32, { + { 26, 30, 0 }, + { 25, 30, 180 }, }); runTests("ean13-2", "EAN-13", 24, { @@ -549,8 +545,8 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("rssexpandedstacked-1", "DataBarExpanded", 65, { - { 60, 65, 0 }, - { 60, 65, 180 }, + { 55, 65, 0 }, + { 55, 65, 180 }, { 60, 0, pure }, }); @@ -566,19 +562,19 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 16, 16, 270 }, }); - runTests("qrcode-2", "QRCode", 48, { - { 44, 46, 0 }, - { 44, 46, 90 }, - { 44, 46, 180 }, - { 44, 45, 270 }, + runTests("qrcode-2", "QRCode", 49, { + { 45, 47, 0 }, + { 45, 47, 90 }, + { 45, 47, 180 }, + { 45, 47, 270 }, { 21, 1, pure }, // the misread is the 'outer' symbol in 16.png }); runTests("qrcode-3", "QRCode", 28, { - { 25, 25, 0 }, - { 25, 25, 90 }, - { 25, 25, 180 }, - { 24, 24, 270 }, + { 28, 28, 0 }, + { 28, 28, 90 }, + { 28, 28, 180 }, + { 27, 27, 270 }, }); runTests("qrcode-4", "QRCode", 41, { @@ -611,7 +607,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 15, 15, 0 }, { 15, 15, 90 }, { 15, 15, 180 }, - { 14, 14, 270 }, + { 15, 15, 270 }, { 9, 0, pure }, }); @@ -620,7 +616,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 1, 17, 90 }, { 16, 17, 180 }, { 1, 17, 270 }, - { 17, 0, pure }, + { 16, 0, pure }, }); runTests("pdf417-2", "PDF417", 25, { @@ -642,7 +638,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 3, 3, 0 }, }); - runTests("falsepositives-1", "None", 26, { + runTests("falsepositives-1", "None", 27, { { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 90 }, { 0, 0, 0, 0, 180 }, diff --git a/test/blackbox/CMakeLists.txt b/test/blackbox/CMakeLists.txt index fa4b984e27..b4badb6319 100644 --- a/test/blackbox/CMakeLists.txt +++ b/test/blackbox/CMakeLists.txt @@ -1,5 +1,5 @@ zxing_add_package_stb() -zxing_add_package(fmt fmtlib https://github.com/fmtlib/fmt.git 9.1.0) +zxing_add_package(fmt fmtlib https://github.com/fmtlib/fmt.git 10.0.0) if (BUILD_READERS) add_executable (ReaderTest diff --git a/test/samples/datamatrix-1/144x144_wrong.png b/test/samples/datamatrix-1/144x144_wrong.png new file mode 100644 index 0000000000..d298c06388 Binary files /dev/null and b/test/samples/datamatrix-1/144x144_wrong.png differ diff --git a/test/samples/datamatrix-1/144x144_wrong.txt b/test/samples/datamatrix-1/144x144_wrong.txt new file mode 100644 index 0000000000..102e452641 --- /dev/null +++ b/test/samples/datamatrix-1/144x144_wrong.txt @@ -0,0 +1,5 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse - there is still some space, so I'm filling it with letters. And I don't speak latin.... - there is still some space, so I'm filling it with letters. And I don't speak latin.... - there is still some space, so I'm filling it with letters. And I don't speak latin.... - there is still some space, so I'm filling it with letters. And I don't speak latin.... \ No newline at end of file diff --git a/test/samples/datamatrix-1/mod-size-1.png b/test/samples/datamatrix-1/mod-size-1.png new file mode 100644 index 0000000000..91c5565f68 Binary files /dev/null and b/test/samples/datamatrix-1/mod-size-1.png differ diff --git a/test/samples/datamatrix-1/mod-size-1.txt b/test/samples/datamatrix-1/mod-size-1.txt new file mode 100644 index 0000000000..5cce967199 --- /dev/null +++ b/test/samples/datamatrix-1/mod-size-1.txt @@ -0,0 +1 @@ +c++ \ No newline at end of file diff --git a/test/samples/ean13-1/#526.jpg b/test/samples/ean13-1/#526.jpg new file mode 100644 index 0000000000..2f1fa82342 Binary files /dev/null and b/test/samples/ean13-1/#526.jpg differ diff --git a/test/samples/ean13-1/#526.txt b/test/samples/ean13-1/#526.txt new file mode 100644 index 0000000000..0101635f8b --- /dev/null +++ b/test/samples/ean13-1/#526.txt @@ -0,0 +1 @@ +8886316200561 \ No newline at end of file diff --git a/test/samples/falsepositives-1/#570.png b/test/samples/falsepositives-1/#570.png new file mode 100644 index 0000000000..6dae963515 Binary files /dev/null and b/test/samples/falsepositives-1/#570.png differ diff --git a/test/samples/qrcode-2/13.txt b/test/samples/qrcode-2/13.txt index 7729d6463d..6c62da9ba6 100644 --- a/test/samples/qrcode-2/13.txt +++ b/test/samples/qrcode-2/13.txt @@ -1 +1 @@ -The 2005 USGS aerial photograph of the Washington Monument is censored. \ No newline at end of file +The 2005 USGS aerial photography of the Washington Monument is censored. \ No newline at end of file diff --git a/test/samples/qrcode-2/high-res-1.jpg b/test/samples/qrcode-2/high-res-1.jpg new file mode 100644 index 0000000000..69efb1f5e8 Binary files /dev/null and b/test/samples/qrcode-2/high-res-1.jpg differ diff --git a/test/samples/qrcode-2/high-res-1.txt b/test/samples/qrcode-2/high-res-1.txt new file mode 100644 index 0000000000..5071ea84cc --- /dev/null +++ b/test/samples/qrcode-2/high-res-1.txt @@ -0,0 +1,2 @@ +QR code (abbreviated from Quick Response Code) is the trademark for a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan.[1] A barcode is a machine-readable optical label that contains information about the item to which it is attached. In practice, QR codes often contain data for a locator, identifier, or tracker that points to a website or application. A QR code uses four standardized encoding modes (numeric, alphanumeric, byte/binary, and kanji) to store data efficiently; extensions may also be used.[2]sdfgksjdflkgjsdkfgiotmbx,cmvbofghjoaasdfaERYYKLLDFGSDFFFFFFFFFFFFFFFFFFFFFFFFDDDDDDDDDSVFB094856JLKSJFGS0DBIUZKL;KSFDF09846JLKSDNFGBLDKSFBHJ0SP98ASDFKthat contains information about the item to which it is attached. In practice, QR codes often contain data for a locator, identifier, or trac +QR code (abbreviated from Quick Response Code) is the trademark for a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan.[1] A barcode is a machine-readable optical label that contains information about the item to which it is attached. In practice, QR codes often contain data for a locator, identifier, or tracker that points to a website or application. A QR code uses four standardized encoding modes (numeric, alphanumeric, byte/binary, and kanji) to store data efficiently; extensions may also be used.[2]sdfgksjdflkgjsdkfgiotmbx,cmvbofghjoaasdfaERYYKLLDFGSDFFFFFFFFFFFFFFFFFFFFFFFFDDDDDDDDDSVFB094856JLKSJFGS0DBIUZKL;KSFDF09846JLKSDNFGBLDKSFBHJ0SP98ASDFKthat contains information about the item to which it is attached. In practice, QR codes often contain data for a locator, identifier, or trac \ No newline at end of file diff --git a/test/unit/BitArrayUtility.h b/test/unit/BitArrayUtility.h index 848e1a058b..6c56b03344 100644 --- a/test/unit/BitArrayUtility.h +++ b/test/unit/BitArrayUtility.h @@ -8,7 +8,7 @@ namespace ZXing { class BitArray; namespace Utility { - + std::string ToString(const BitArray& arr, char one, char zero); std::string ToString(const BitArray& BitArray); BitArray ParseBitArray(std::string_view str, char one = 'X'); diff --git a/test/unit/CharacterSetECITest.cpp b/test/unit/CharacterSetECITest.cpp index e34dd9640f..39888622a4 100644 --- a/test/unit/CharacterSetECITest.cpp +++ b/test/unit/CharacterSetECITest.cpp @@ -7,7 +7,6 @@ #include "ECI.h" #include "gtest/gtest.h" -#include "gmock/gmock.h" using namespace ZXing; using namespace testing; diff --git a/test/unit/ContentTest.cpp b/test/unit/ContentTest.cpp index 982c3d6ffd..1bdb9a8d3b 100644 --- a/test/unit/ContentTest.cpp +++ b/test/unit/ContentTest.cpp @@ -7,7 +7,6 @@ #include "ECI.h" #include "gtest/gtest.h" -#include "gmock/gmock.h" using namespace ZXing; using namespace testing; diff --git a/test/unit/PatternTest.cpp b/test/unit/PatternTest.cpp index f3cdc58de0..dde3a58841 100644 --- a/test/unit/PatternTest.cpp +++ b/test/unit/PatternTest.cpp @@ -11,12 +11,13 @@ using namespace ZXing; constexpr int N = 33; +PatternRow pr; + TEST(PatternTest, AllWhite) { for (int s = 1; s <= N; ++s) { std::vector in(s, 0); - PatternRow pr; - GetPatternRow(Range{in.data(), in.data() + in.size()}, pr); + GetPatternRow(Range{in}, pr); EXPECT_EQ(pr.size(), 1); EXPECT_EQ(pr[0], s); @@ -27,8 +28,7 @@ TEST(PatternTest, AllBlack) { for (int s = 1; s <= N; ++s) { std::vector in(s, 0xff); - PatternRow pr; - GetPatternRow(Range{in.data(), in.data() + in.size()}, pr); + GetPatternRow(Range{in}, pr); EXPECT_EQ(pr.size(), 3); EXPECT_EQ(pr[0], 0); @@ -42,8 +42,7 @@ TEST(PatternTest, BlackWhite) for (int s = 1; s <= N; ++s) { std::vector in(N, 0); std::fill_n(in.data(), s, 0xff); - PatternRow pr; - GetPatternRow(Range{in.data(), in.data() + in.size()}, pr); + GetPatternRow(Range{in}, pr); EXPECT_EQ(pr.size(), 3); EXPECT_EQ(pr[0], 0); @@ -57,8 +56,7 @@ TEST(PatternTest, WhiteBlack) for (int s = 0; s < N; ++s) { std::vector in(N, 0xff); std::fill_n(in.data(), s, 0); - PatternRow pr; - GetPatternRow(Range{in.data(), in.data() + in.size()}, pr); + GetPatternRow(Range{in}, pr); EXPECT_EQ(pr.size(), 3); EXPECT_EQ(pr[0], s); diff --git a/test/unit/ThresholdBinarizerTest.cpp b/test/unit/ThresholdBinarizerTest.cpp index 13ff0e5823..d49e51d9cf 100644 --- a/test/unit/ThresholdBinarizerTest.cpp +++ b/test/unit/ThresholdBinarizerTest.cpp @@ -95,6 +95,7 @@ TEST(ThresholdBinarizerTest, PatternRowClear) bits = ParseBitMatrix(bitstream, 53 /*width*/); hints.setFormats(BarcodeFormat::DataBarExpanded); + hints.setMinLineCount(1); OneD::Reader reader(hints); Result result = reader.decode(ThresholdBinarizer(getImageView(buf, bits), 0x7F)); diff --git a/test/unit/ZXAlgorithmsTest.cpp b/test/unit/ZXAlgorithmsTest.cpp index 3531e88b48..7f73d7d7f5 100644 --- a/test/unit/ZXAlgorithmsTest.cpp +++ b/test/unit/ZXAlgorithmsTest.cpp @@ -28,3 +28,24 @@ TEST(ZXAlgorithmsTest, ToString) EXPECT_THROW(ToString(-1, 2), Error); EXPECT_THROW(ToString(111, 2), Error); } + +TEST(ZXAlgorithmsTest, UpdateMinMax) +{ + int m = 10, M = 0; + UpdateMinMax(m, M, 5); + EXPECT_EQ(m, 5); + EXPECT_EQ(M, 5); + + UpdateMinMax(m, M, 2); + EXPECT_EQ(m, 2); + EXPECT_EQ(M, 5); + + m = 1, M = 1; + UpdateMinMax(m, M, 0); + EXPECT_EQ(m, 0); + EXPECT_EQ(M, 1); + + UpdateMinMax(m, M, 2); + EXPECT_EQ(m, 0); + EXPECT_EQ(M, 2); +} diff --git a/test/unit/datamatrix/DMHighLevelEncodeTest.cpp b/test/unit/datamatrix/DMHighLevelEncodeTest.cpp index 75eebe3d0b..cf03ef4e62 100644 --- a/test/unit/datamatrix/DMHighLevelEncodeTest.cpp +++ b/test/unit/datamatrix/DMHighLevelEncodeTest.cpp @@ -77,13 +77,13 @@ namespace { TEST(DMHighLevelEncodeTest, ASCIIEncodation) { - std::string visualized = Encode(L"123456"); - EXPECT_EQ(visualized, "142 164 186"); + std::string visualized = Encode(L"123456"); + EXPECT_EQ(visualized, "142 164 186"); - visualized = Encode(L"123456\xA3"); + visualized = Encode(L"123456\xA3"); EXPECT_EQ(visualized, "142 164 186 235 36"); - visualized = Encode(L"30Q324343430794 2, rest = 2 --> unlatch and encode as ASCII + EXPECT_EQ(visualized, "230 91 11 91 11 91 11 91 11 91 11 91 11 254 66 74"); + //available > 2, rest = 2 --> unlatch and encode as ASCII } TEST(DMHighLevelEncodeTest, TextEncodation) { std::string visualized = Encode(L"aimaimaim"); - EXPECT_EQ(visualized, "239 91 11 91 11 91 11 254"); - //239 shifts to Text encodation, 254 unlatches + EXPECT_EQ(visualized, "239 91 11 91 11 91 11 254"); + //239 shifts to Text encodation, 254 unlatches - visualized = Encode(L"aimaimaim'"); - EXPECT_EQ(visualized, "239 91 11 91 11 91 11 254 40 129"); - //EXPECT_EQ(visualized, "239 91 11 91 11 91 11 7 49 254"); - //This is an alternative, but doesn't strictly follow the rules in the spec. + visualized = Encode(L"aimaimaim'"); + EXPECT_EQ(visualized, "239 91 11 91 11 91 11 254 40 129"); + // EXPECT_EQ(visualized, "239 91 11 91 11 91 11 7 49 254"); + // This is an alternative, but doesn't strictly follow the rules in the spec. - visualized = Encode(L"aimaimaIm"); - EXPECT_EQ(visualized, "239 91 11 91 11 87 218 110"); + visualized = Encode(L"aimaimaIm"); + EXPECT_EQ(visualized, "239 91 11 91 11 87 218 110"); - visualized = Encode(L"aimaimaimB"); - EXPECT_EQ(visualized, "239 91 11 91 11 91 11 254 67 129"); + visualized = Encode(L"aimaimaimB"); + EXPECT_EQ(visualized, "239 91 11 91 11 91 11 254 67 129"); - visualized = Encode(L"aimaimaim{txt}\x04"); - EXPECT_EQ(visualized, "239 91 11 91 11 91 11 16 218 236 107 181 69 254 129 237"); + visualized = Encode(L"aimaimaim{txt}\x04"); + EXPECT_EQ(visualized, "239 91 11 91 11 91 11 16 218 236 107 181 69 254 129 237"); } TEST(DMHighLevelEncodeTest, X12Encodation) { - //238 shifts to X12 encodation, 254 unlatches + // 238 shifts to X12 encodation, 254 unlatches std::string visualized = Encode(L"ABC>ABC123>AB"); - EXPECT_EQ(visualized, "238 89 233 14 192 100 207 44 31 67"); + EXPECT_EQ(visualized, "238 89 233 14 192 100 207 44 31 67"); - visualized = Encode(L"ABC>ABC123>ABC"); - EXPECT_EQ(visualized, "238 89 233 14 192 100 207 44 31 254 67 68"); + visualized = Encode(L"ABC>ABC123>ABC"); + EXPECT_EQ(visualized, "238 89 233 14 192 100 207 44 31 254 67 68"); - visualized = Encode(L"ABC>ABC123>ABCD"); - EXPECT_EQ(visualized, "238 89 233 14 192 100 207 44 31 96 82 254"); + visualized = Encode(L"ABC>ABC123>ABCD"); + EXPECT_EQ(visualized, "238 89 233 14 192 100 207 44 31 96 82 254"); - visualized = Encode(L"ABC>ABC123>ABCDE"); - EXPECT_EQ(visualized, "238 89 233 14 192 100 207 44 31 96 82 70"); - - visualized = Encode(L"ABC>ABC123>ABCDEF"); - EXPECT_EQ(visualized, "238 89 233 14 192 100 207 44 31 96 82 254 70 71 129 237"); + visualized = Encode(L"ABC>ABC123>ABCDE"); + EXPECT_EQ(visualized, "238 89 233 14 192 100 207 44 31 96 82 70"); + visualized = Encode(L"ABC>ABC123>ABCDEF"); + EXPECT_EQ(visualized, "238 89 233 14 192 100 207 44 31 96 82 254 70 71 129 237"); } TEST(DMHighLevelEncodeTest, EDIFACTEncodation) { - //240 shifts to EDIFACT encodation + // 240 shifts to EDIFACT encodation std::string visualized = Encode(L".A.C1.3.DATA.123DATA.123DATA"); - EXPECT_EQ(visualized, "240 184 27 131 198 236 238 16 21 1 187 28 179 16 21 1 187 28 179 16 21 1"); + EXPECT_EQ(visualized, "240 184 27 131 198 236 238 16 21 1 187 28 179 16 21 1 187 28 179 16 21 1"); - visualized = Encode(L".A.C1.3.X.X2.."); - EXPECT_EQ(visualized, "240 184 27 131 198 236 238 98 230 50 47 47"); + visualized = Encode(L".A.C1.3.X.X2.."); + EXPECT_EQ(visualized, "240 184 27 131 198 236 238 98 230 50 47 47"); - visualized = Encode(L".A.C1.3.X.X2."); - EXPECT_EQ(visualized, "240 184 27 131 198 236 238 98 230 50 47 129"); + visualized = Encode(L".A.C1.3.X.X2."); + EXPECT_EQ(visualized, "240 184 27 131 198 236 238 98 230 50 47 129"); - visualized = Encode(L".A.C1.3.X.X2"); - EXPECT_EQ(visualized, "240 184 27 131 198 236 238 98 230 50"); + visualized = Encode(L".A.C1.3.X.X2"); + EXPECT_EQ(visualized, "240 184 27 131 198 236 238 98 230 50"); - visualized = Encode(L".A.C1.3.X.X"); - EXPECT_EQ(visualized, "240 184 27 131 198 236 238 98 230 31"); + visualized = Encode(L".A.C1.3.X.X"); + EXPECT_EQ(visualized, "240 184 27 131 198 236 238 98 230 31"); - visualized = Encode(L".A.C1.3.X."); - EXPECT_EQ(visualized, "240 184 27 131 198 236 238 98 231 192"); + visualized = Encode(L".A.C1.3.X."); + EXPECT_EQ(visualized, "240 184 27 131 198 236 238 98 231 192"); - visualized = Encode(L".A.C1.3.X"); - EXPECT_EQ(visualized, "240 184 27 131 198 236 238 89"); + visualized = Encode(L".A.C1.3.X"); + EXPECT_EQ(visualized, "240 184 27 131 198 236 238 89"); - //Checking temporary unlatch from EDIFACT - visualized = Encode(L".XXX.XXX.XXX.XXX.XXX.XXX.\xFCXX.XXX.XXX.XXX.XXX.XXX.XXX"); - EXPECT_EQ(visualized, "240 185 134 24 185 134 24 185 134 24 185 134 24 185 134 24 185 134 24" - " 124 47 235 125 240" //<-- this is the temporary unlatch - " 97 139 152 97 139 152 97 139 152 97 139 152 97 139 152 97 139 152 89 89"); + // Checking temporary unlatch from EDIFACT + visualized = Encode(L".XXX.XXX.XXX.XXX.XXX.XXX.\xFCXX.XXX.XXX.XXX.XXX.XXX.XXX"); + EXPECT_EQ(visualized, "240 185 134 24 185 134 24 185 134 24 185 134 24 185 134 24 185 134 24" + " 124 47 235 125 240" //<-- this is the temporary unlatch + " 97 139 152 97 139 152 97 139 152 97 139 152 97 139 152 97 139 152 89 89"); } TEST(DMHighLevelEncodeTest, Base256Encodation) { - //231 shifts to Base256 encodation + // 231 shifts to Base256 encodation std::string visualized = Encode(L"\xAB\xE4\xF6\xFC\xE9\xBB"); EXPECT_EQ(visualized, "231 44 108 59 226 126 1 104"); - visualized = Encode(L"\xAB\xE4\xF6\xFC\xE9\xE0\xBB"); - EXPECT_EQ(visualized, "231 51 108 59 226 126 1 141 254 129"); - visualized = Encode(L"\xAB\xE4\xF6\xFC\xE9\xE0\xE1\xBB"); - EXPECT_EQ(visualized, "231 44 108 59 226 126 1 141 36 147"); + visualized = Encode(L"\xAB\xE4\xF6\xFC\xE9\xE0\xBB"); + EXPECT_EQ(visualized, "231 51 108 59 226 126 1 141 254 129"); + visualized = Encode(L"\xAB\xE4\xF6\xFC\xE9\xE0\xE1\xBB"); + EXPECT_EQ(visualized, "231 44 108 59 226 126 1 141 36 147"); - visualized = Encode(L" 23\xA3"); //ASCII only (for reference) - EXPECT_EQ(visualized, "33 153 235 36 129"); + visualized = Encode(L" 23\xA3"); // ASCII only (for reference) + EXPECT_EQ(visualized, "33 153 235 36 129"); - visualized = Encode(L"\xAB\xE4\xF6\xFC\xE9\xBB 234"); //Mixed Base256 + ASCII - EXPECT_EQ(visualized, "231 51 108 59 226 126 1 104 99 153 53 129"); + visualized = Encode(L"\xAB\xE4\xF6\xFC\xE9\xBB 234"); // Mixed Base256 + ASCII + EXPECT_EQ(visualized, "231 51 108 59 226 126 1 104 99 153 53 129"); - visualized = Encode(L"\xAB\xE4\xF6\xFC\xE9\xBB 23\xA3 1234567890123456789"); - EXPECT_EQ(visualized, "231 55 108 59 226 126 1 104 99 10 161 167 185 142 164 186 208" - " 220 142 164 186 208 58 129 59 209 104 254 150 45"); + visualized = Encode(L"\xAB\xE4\xF6\xFC\xE9\xBB 23\xA3 1234567890123456789"); + EXPECT_EQ(visualized, "231 55 108 59 226 126 1 104 99 10 161 167 185 142 164 186 208" + " 220 142 164 186 208 58 129 59 209 104 254 150 45"); - visualized = Encode(CreateBinaryMessage(20)); - EXPECT_EQ(visualized, "231 44 108 59 226 126 1 141 36 5 37 187 80 230 123 17 166 60 210 103 253 150"); - visualized = Encode(CreateBinaryMessage(19)); //padding necessary at the end - EXPECT_EQ(visualized, "231 63 108 59 226 126 1 141 36 5 37 187 80 230 123 17 166 60 210 103 1 129"); + visualized = Encode(CreateBinaryMessage(20)); + EXPECT_EQ(visualized, "231 44 108 59 226 126 1 141 36 5 37 187 80 230 123 17 166 60 210 103 253 150"); + visualized = Encode(CreateBinaryMessage(19)); // padding necessary at the end + EXPECT_EQ(visualized, "231 63 108 59 226 126 1 141 36 5 37 187 80 230 123 17 166 60 210 103 1 129"); - visualized = Encode(CreateBinaryMessage(276)); + visualized = Encode(CreateBinaryMessage(276)); std::string expectedStart = "231 38 219 2 208 120 20 150 35"; std::string epxectedEnd = "146 40 194 129"; EXPECT_EQ(visualized.substr(0, expectedStart.length()), expectedStart); EXPECT_EQ(visualized.substr(visualized.length() - epxectedEnd.length()), epxectedEnd); - visualized = Encode(CreateBinaryMessage(277)); + visualized = Encode(CreateBinaryMessage(277)); expectedStart = "231 38 220 2 208 120 20 150 35"; epxectedEnd = "146 40 190 87"; EXPECT_EQ(visualized.substr(0, expectedStart.length()), expectedStart); @@ -280,7 +279,7 @@ TEST(DMHighLevelEncodeTest, Base256Encodation) TEST(DMHighLevelEncodeTest, UnlatchingFromC40) { - std::string visualized = Encode(L"AIMAIMAIMAIMaimaimaim"); + std::string visualized = Encode(L"AIMAIMAIMAIMaimaimaim"); EXPECT_EQ(visualized, "230 91 11 91 11 91 11 254 66 74 78 239 91 11 91 11 91 11"); } @@ -298,16 +297,16 @@ TEST(DMHighLevelEncodeTest, tHelloWorld) TEST(DMHighLevelEncodeTest, Bug1664266) { - //There was an exception and the encoder did not handle the unlatching from - //EDIFACT encoding correctly + // There was an exception and the encoder did not handle the unlatching from + // EDIFACT encoding correctly std::string visualized = Encode(L"CREX-TAN:h"); EXPECT_EQ(visualized, "240 13 33 88 181 64 78 124 59 105"); - visualized = Encode(L"CREX-TAN:hh"); + visualized = Encode(L"CREX-TAN:hh"); EXPECT_EQ(visualized, "240 13 33 88 181 64 78 124 59 105 105 129"); - visualized = Encode(L"CREX-TAN:hhh"); + visualized = Encode(L"CREX-TAN:hhh"); EXPECT_EQ(visualized, "240 13 33 88 181 64 78 124 59 105 105 105"); } @@ -325,8 +324,8 @@ TEST(DMHighLevelEncodeTest, X12Unlatch2) TEST(DMHighLevelEncodeTest, Bug3048549) { - //There was an IllegalArgumentException for an illegal character here because - //of an encoding problem of the character 0x0060 in Java source code. + // There was an IllegalArgumentException for an illegal character here because + // of an encoding problem of the character 0x0060 in Java source code. std::string visualized = Encode(L"fiykmj*Rh2`,e6"); EXPECT_EQ(visualized, "239 122 87 154 40 7 171 115 207 12 130 71 155 254 129 237"); } @@ -334,22 +333,22 @@ TEST(DMHighLevelEncodeTest, Bug3048549) TEST(DMHighLevelEncodeTest, MacroCharacters) { std::string visualized = Encode(L"[)>\x1E""05\x1D""5555\x1C""6666\x1E\x04"); - //EXPECT_EQ(visualized, "92 42 63 31 135 30 185 185 29 196 196 31 5 129 87 237"); + //EXPECT_EQ(visualized, "92 42 63 31 135 30 185 185 29 196 196 31 5 129 87 237"); EXPECT_EQ(visualized, "236 185 185 29 196 196 129 56"); } TEST(DMHighLevelEncodeTest, EncodingWithStartAsX12AndLatchToEDIFACTInTheMiddle) { - std::string visualized = Encode(L"*MEMANT-1F-MESTECH"); - EXPECT_EQ(visualized, "238 10 99 164 204 254 240 82 220 70 180 209 83 80 80 200"); + std::string visualized = Encode(L"*MEMANT-1F-MESTECH"); + EXPECT_EQ(visualized, "238 10 99 164 204 254 240 82 220 70 180 209 83 80 80 200"); } TEST(DMHighLevelEncodeTest, EDIFACTWithEODBug) { std::string visualized = Visualize( DataMatrix::Encode(L"abc<->ABCDE", DataMatrix::SymbolShape::SQUARE, -1, -1, -1, -1)); - // switch to EDIFACT on '<', uses 10 code words + 2 padding. Buggy code introduced invalid 254 after the 5 - EXPECT_EQ(visualized, "98 99 100 240 242 223 129 8 49 5 129 147"); + // switch to EDIFACT on '<', uses 10 code words + 2 padding. Buggy code introduced invalid 254 after the 5 + EXPECT_EQ(visualized, "98 99 100 240 242 223 129 8 49 5 129 147"); } // @Ignore diff --git a/test/unit/datamatrix/DMPlacementTest.cpp b/test/unit/datamatrix/DMPlacementTest.cpp index 091d365fbf..c9eec63718 100644 --- a/test/unit/datamatrix/DMPlacementTest.cpp +++ b/test/unit/datamatrix/DMPlacementTest.cpp @@ -28,20 +28,20 @@ namespace { TEST(DMPlacementTest, Placement) { - auto codewords = Unvisualize("66 74 78 66 74 78 129 56 35 102 192 96 226 100 156 1 107 221"); //"AIMAIM" encoded - auto matrix = BitMatrixFromCodewords(codewords, 12, 12); - std::string expected = - "011100001111\n" - "001010101000\n" - "010001010100\n" - "001010100010\n" - "000111000100\n" - "011000010100\n" - "000100001101\n" - "011000010000\n" - "001100001101\n" - "100010010111\n" - "011101011010\n" - "001011001010\n"; - EXPECT_EQ(expected, ToString(matrix, '1', '0', false)); - } + auto codewords = Unvisualize("66 74 78 66 74 78 129 56 35 102 192 96 226 100 156 1 107 221"); //"AIMAIM" encoded + auto matrix = BitMatrixFromCodewords(codewords, 12, 12); + std::string expected = + "011100001111\n" + "001010101000\n" + "010001010100\n" + "001010100010\n" + "000111000100\n" + "011000010100\n" + "000100001101\n" + "011000010000\n" + "001100001101\n" + "100010010111\n" + "011101011010\n" + "001011001010\n"; + EXPECT_EQ(expected, ToString(matrix, '1', '0', false)); +} diff --git a/test/unit/datamatrix/DMSymbolInfoTest.cpp b/test/unit/datamatrix/DMSymbolInfoTest.cpp index 1d9268f135..c950088254 100644 --- a/test/unit/datamatrix/DMSymbolInfoTest.cpp +++ b/test/unit/datamatrix/DMSymbolInfoTest.cpp @@ -12,41 +12,41 @@ using namespace ZXing::DataMatrix; TEST(DMSymbolInfoTest, SymbolInfo) { - auto info = SymbolInfo::Lookup(3); - EXPECT_EQ(5, info->errorCodewords()); + auto info = SymbolInfo::Lookup(3); + EXPECT_EQ(5, info->errorCodewords()); EXPECT_EQ(8, info->matrixWidth()); EXPECT_EQ(8, info->matrixHeight()); EXPECT_EQ(10, info->symbolWidth()); EXPECT_EQ(10, info->symbolHeight()); - info = SymbolInfo::Lookup(3, SymbolShape::RECTANGLE); - EXPECT_EQ(7, info->errorCodewords()); - EXPECT_EQ(16, info->matrixWidth()); - EXPECT_EQ(6, info->matrixHeight()); - EXPECT_EQ(18, info->symbolWidth()); - EXPECT_EQ(8, info->symbolHeight()); - - info = SymbolInfo::Lookup(9); - EXPECT_EQ(11, info->errorCodewords()); - EXPECT_EQ(14, info->matrixWidth()); - EXPECT_EQ(6, info->matrixHeight()); - EXPECT_EQ(32, info->symbolWidth()); - EXPECT_EQ(8, info->symbolHeight()); - - info = SymbolInfo::Lookup(9, SymbolShape::SQUARE); - EXPECT_EQ(12, info->errorCodewords()); - EXPECT_EQ(14, info->matrixWidth()); - EXPECT_EQ(14, info->matrixHeight()); - EXPECT_EQ(16, info->symbolWidth()); - EXPECT_EQ(16, info->symbolHeight()); - - info = SymbolInfo::Lookup(1559); + info = SymbolInfo::Lookup(3, SymbolShape::RECTANGLE); + EXPECT_EQ(7, info->errorCodewords()); + EXPECT_EQ(16, info->matrixWidth()); + EXPECT_EQ(6, info->matrixHeight()); + EXPECT_EQ(18, info->symbolWidth()); + EXPECT_EQ(8, info->symbolHeight()); + + info = SymbolInfo::Lookup(9); + EXPECT_EQ(11, info->errorCodewords()); + EXPECT_EQ(14, info->matrixWidth()); + EXPECT_EQ(6, info->matrixHeight()); + EXPECT_EQ(32, info->symbolWidth()); + EXPECT_EQ(8, info->symbolHeight()); + + info = SymbolInfo::Lookup(9, SymbolShape::SQUARE); + EXPECT_EQ(12, info->errorCodewords()); + EXPECT_EQ(14, info->matrixWidth()); + EXPECT_EQ(14, info->matrixHeight()); + EXPECT_EQ(16, info->symbolWidth()); + EXPECT_EQ(16, info->symbolHeight()); + + info = SymbolInfo::Lookup(1559); EXPECT_EQ(nullptr, info) << "There's no rectangular symbol for more than 1558 data codewords"; info = SymbolInfo::Lookup(50, SymbolShape::RECTANGLE); EXPECT_EQ(nullptr, info) << "There's no rectangular symbol for 50 data codewords"; - info = SymbolInfo::Lookup(35); + info = SymbolInfo::Lookup(35); EXPECT_EQ(24, info->symbolWidth()); EXPECT_EQ(24, info->symbolHeight()); @@ -54,25 +54,25 @@ TEST(DMSymbolInfoTest, SymbolInfo) int minHeight = 26; int maxWidth = 26; int maxHeight = 26; - + info = SymbolInfo::Lookup(35, SymbolShape::NONE, minWidth, minHeight, maxWidth, maxHeight); EXPECT_EQ(26, info->symbolWidth()); EXPECT_EQ(26, info->symbolHeight()); - info = SymbolInfo::Lookup(45, SymbolShape::NONE, minWidth, minHeight, maxWidth, maxHeight); - EXPECT_EQ(nullptr, info); + info = SymbolInfo::Lookup(45, SymbolShape::NONE, minWidth, minHeight, maxWidth, maxHeight); + EXPECT_EQ(nullptr, info); maxWidth = 32; maxHeight = 32; - info = SymbolInfo::Lookup(35, SymbolShape::NONE, minWidth, minHeight, maxWidth, maxHeight); + info = SymbolInfo::Lookup(35, SymbolShape::NONE, minWidth, minHeight, maxWidth, maxHeight); EXPECT_EQ(26, info->symbolWidth()); EXPECT_EQ(26, info->symbolHeight()); info = SymbolInfo::Lookup(40, SymbolShape::NONE, minWidth, minHeight, maxWidth, maxHeight); EXPECT_EQ(26, info->symbolWidth()); EXPECT_EQ(26, info->symbolHeight()); - + info = SymbolInfo::Lookup(45, SymbolShape::NONE, minWidth, minHeight, maxWidth, maxHeight); EXPECT_EQ(32, info->symbolWidth()); EXPECT_EQ(32, info->symbolHeight()); diff --git a/test/unit/datamatrix/DMWriterTest.cpp b/test/unit/datamatrix/DMWriterTest.cpp index a47f2b743b..9b2eaa3a32 100644 --- a/test/unit/datamatrix/DMWriterTest.cpp +++ b/test/unit/datamatrix/DMWriterTest.cpp @@ -14,11 +14,11 @@ using namespace ZXing::DataMatrix; TEST(DMWriterTest, ImageWriter) { - int bigEnough = 64; + int bigEnough = 64; Writer writer; writer.setMargin(0).setShapeHint(SymbolShape::SQUARE); auto matrix = writer.encode(L"Hello Google", bigEnough, bigEnough); - EXPECT_LE(matrix.width(), bigEnough); + EXPECT_LE(matrix.width(), bigEnough); EXPECT_LE(matrix.height(), bigEnough); } @@ -34,8 +34,8 @@ TEST(DMWriterTest, Writer2) TEST(DMWriterTest, TooSmallSize) { - // The DataMatrix will not fit in this size, so the matrix should come back bigger - int tooSmall = 8; + // The DataMatrix will not fit in this size, so the matrix should come back bigger + int tooSmall = 8; Writer writer; writer.setMargin(0); auto matrix = writer.encode(L"http://www.google.com/", tooSmall, tooSmall); diff --git a/test/unit/oned/ODCodaBarWriterTest.cpp b/test/unit/oned/ODCodaBarWriterTest.cpp index 9023ffc787..471ac52473 100644 --- a/test/unit/oned/ODCodaBarWriterTest.cpp +++ b/test/unit/oned/ODCodaBarWriterTest.cpp @@ -5,7 +5,6 @@ // SPDX-License-Identifier: Apache-2.0 #include "oned/ODCodabarWriter.h" -#include "BitArray.h" #include "BitMatrixIO.h" #include "DecodeHints.h" #include "Result.h" @@ -28,18 +27,18 @@ namespace { TEST(ODCodaBarWriterTest, Encode) { EXPECT_EQ(Encode("B515-3/B"), - "00000" - "1001001011" "0110101001" "0101011001" "0110101001" "0101001101" - "0110010101" "01101101011" "01001001011" - "00000"); + "00000" + "1001001011" "0110101001" "0101011001" "0110101001" "0101001101" + "0110010101" "01101101011" "01001001011" + "00000"); } TEST(ODCodaBarWriterTest, Encode2) { EXPECT_EQ(Encode("T123T"), - "00000" - "1011001001" "0101011001" "0101001011" "0110010101" "01011001001" - "00000"); + "00000" + "1011001001" "0101011001" "0101001011" "0110010101" "01011001001" + "00000"); } TEST(ODCodaBarWriterTest, AltStartEnd) diff --git a/test/unit/oned/ODCode93WriterTest.cpp b/test/unit/oned/ODCode93WriterTest.cpp index beaf6965b5..6bc1cf15fd 100644 --- a/test/unit/oned/ODCode93WriterTest.cpp +++ b/test/unit/oned/ODCode93WriterTest.cpp @@ -26,12 +26,12 @@ namespace { TEST(ODCode93WriterTest, Encode) { - EXPECT_EQ(Encode(L"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), - "000001010111101101010001101001001101000101100101001100100101100010101011010001011001" - "001011000101001101001000110101010110001010011001010001101001011001000101101101101001" - "101100101101011001101001101100101101100110101011011001011001101001101101001110101000" - "101001010010001010001001010000101001010001001001001001000101010100001000100101000010" - "10100111010101000010101011110100000"); + EXPECT_EQ(Encode(L"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), + "000001010111101101010001101001001101000101100101001100100101100010101011010001011001" + "001011000101001101001000110101010110001010011001010001101001011001000101101101101001" + "101100101101011001101001101100101101100110101011011001011001101001101101001110101000" + "101001010010001010001001010000101001010001001001001001000101010100001000100101000010" + "10100111010101000010101011110100000"); } TEST(ODCode93WriterTest, EncodeExtended) diff --git a/test/unit/oned/ODEAN8WriterTest.cpp b/test/unit/oned/ODEAN8WriterTest.cpp index 6df37af6c9..e235d14391 100644 --- a/test/unit/oned/ODEAN8WriterTest.cpp +++ b/test/unit/oned/ODEAN8WriterTest.cpp @@ -30,7 +30,7 @@ TEST(ODEAN8WriterTest, Encode1) TEST(ODEAN8WriterTest, AddChecksumAndEncode) { std::string toEncode = "9638507"; - std::string expected = "0000101000101101011110111101011011101010100111011100101000100101110010100000"; + std::string expected = "0000101000101101011110111101011011101010100111011100101000100101110010100000"; EXPECT_EQ(Encode(toEncode), expected); } diff --git a/test/unit/oned/ODITFWriterTest.cpp b/test/unit/oned/ODITFWriterTest.cpp index 34a9d0392a..078d882ac9 100644 --- a/test/unit/oned/ODITFWriterTest.cpp +++ b/test/unit/oned/ODITFWriterTest.cpp @@ -23,9 +23,9 @@ namespace { TEST(ODITFWriterTest, Encode) { - EXPECT_EQ(Encode(L"00123456789012"), - "0000010101010111000111000101110100010101110001110111010001010001110100011" - "100010101000101011100011101011101000111000101110100010101110001110100000"); + EXPECT_EQ(Encode(L"00123456789012"), + "0000010101010111000111000101110100010101110001110111010001010001110100011" + "100010101000101011100011101011101000111000101110100010101110001110100000"); } TEST(ODITFWriterTest, EncodeIllegalCharacters) diff --git a/test/unit/pdf417/PDF417DecoderTest.cpp b/test/unit/pdf417/PDF417DecoderTest.cpp index 2ec267c14b..cbee272e11 100644 --- a/test/unit/pdf417/PDF417DecoderTest.cpp +++ b/test/unit/pdf417/PDF417DecoderTest.cpp @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "DecoderResult.h" -#include "pdf417/PDFDecodedBitStreamParser.h" +#include "pdf417/PDFDecoder.h" #include "pdf417/PDFDecoderResultExtra.h" #include "gtest/gtest.h" @@ -17,16 +17,6 @@ int DecodeMacroBlock(const std::vector& codewords, int codeIndex, DecoderRe using namespace ZXing; using namespace ZXing::Pdf417; -// Shorthand for Decode() -static DecoderResult parse(const std::vector& codewords, int ecLevel = 0) -{ - try { - return DecodedBitStreamParser::Decode(codewords, ecLevel); - } catch (Error e) { - return e; - } -} - /** * Tests the first sample given in ISO/IEC 15438:2015(E) - Annex H.4 */ @@ -50,7 +40,7 @@ TEST(PDF417DecoderTest, StandardSample1) EXPECT_EQ(1, optionalData.front()) << "first element of optional array should be the first field identifier"; EXPECT_EQ(67, optionalData.back()) << "last element of optional array should be the last codeword of the last field"; - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(0, result.structuredAppend().index); EXPECT_EQ("017053", result.structuredAppend().id); @@ -80,7 +70,7 @@ TEST(PDF417DecoderTest, StandardSample2) EXPECT_EQ(1, optionalData.front()) << "first element of optional array should be the first field identifier"; EXPECT_EQ(104, optionalData.back()) << "last element of optional array should be the last codeword of the last field"; - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(3, result.structuredAppend().index); EXPECT_EQ("017053", result.structuredAppend().id); @@ -101,7 +91,7 @@ TEST(PDF417DecoderTest, StandardSample3) EXPECT_EQ("100200300", resultMetadata.fileId()); EXPECT_EQ(-1, resultMetadata.segmentCount()); - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(0, result.structuredAppend().index); EXPECT_EQ("100200300", result.structuredAppend().id); @@ -125,7 +115,7 @@ TEST(PDF417DecoderTest, SampleWithFilename) EXPECT_EQ("", resultMetadata.addressee()); EXPECT_EQ("filename.txt", resultMetadata.fileName()); - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(0, result.structuredAppend().index); EXPECT_EQ("000252021086", result.structuredAppend().id); @@ -149,7 +139,7 @@ TEST(PDF417DecoderTest, SampleWithNumericValues) EXPECT_EQ(260013, resultMetadata.checksum()); EXPECT_EQ(-1, resultMetadata.segmentCount()); - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(0, result.structuredAppend().index); EXPECT_EQ("000252021086", result.structuredAppend().id); @@ -168,7 +158,7 @@ TEST(PDF417DecoderTest, SampleWithMacroTerminatorOnly) EXPECT_EQ(true, resultMetadata.isLastSegment()); EXPECT_EQ(-1, resultMetadata.segmentCount()); - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(99998, result.structuredAppend().index); EXPECT_EQ("000", result.structuredAppend().id); @@ -178,13 +168,13 @@ TEST(PDF417DecoderTest, SampleWithMacroTerminatorOnly) // Shorthand to decode and return text static std::wstring decode(const std::vector& codewords) { - return parse(codewords).text(); + return Decode(codewords).text(); } // Shorthand to decode and return isValid static bool valid(const std::vector& codewords) { - return parse(codewords).isValid(); + return Decode(codewords).isValid(); } TEST(PDF417DecoderTest, TextCompactionSimple) @@ -500,7 +490,7 @@ TEST(PDF417DecoderTest, ECIMultipleNumeric) TEST(PDF417DecoderTest, ECIInvalid) { EXPECT_EQ(decode({ 4, 927, 901, 0 }), L""); // non-charset ECI > 899 -> empty text result - EXPECT_EQ(parse({4, 927, 901, 0}).content().bytes, ByteArray("AA")); // non-charset ECI > 899 -> ignored in binary result + EXPECT_EQ(Decode({4, 927, 901, 0}).content().bytes, ByteArray("AA")); // non-charset ECI > 899 -> ignored in binary result EXPECT_EQ(decode({ 3, 0, 927 }), L"AA"); // Malformed ECI at end silently ignored } @@ -542,21 +532,21 @@ TEST(PDF417DecoderTest, ECIUserDefined) TEST(PDF417DecoderTest, ReaderInit) { // Null - EXPECT_FALSE(parse({ 2, 0 }).readerInit()); + EXPECT_FALSE(Decode({2, 0}).readerInit()); EXPECT_EQ(decode({ 2, 0 }), L"AA"); // Set - EXPECT_TRUE(parse({ 3, 921, 0 }).readerInit()); + EXPECT_TRUE(Decode({3, 921, 0}).readerInit()); EXPECT_EQ(decode({ 3, 921, 0 }), L"AA"); // Must be first - EXPECT_FALSE(parse({ 3, 0, 921 }).readerInit()); + EXPECT_FALSE(Decode({3, 0, 921}).readerInit()); EXPECT_FALSE(valid({ 3, 0, 921 })); - EXPECT_FALSE(parse({ 4, 901, 65, 921 }).readerInit()); + EXPECT_FALSE(Decode({4, 901, 65, 921}).readerInit()); EXPECT_FALSE(valid({ 4, 901, 65, 921 })); - EXPECT_FALSE(parse({ 4, 901, 921, 65 }).readerInit()); + EXPECT_FALSE(Decode({4, 901, 921, 65}).readerInit()); EXPECT_FALSE(valid({ 4, 901, 921, 65 })); } diff --git a/test/unit/pdf417/PDF417ErrorCorrectionTest.cpp b/test/unit/pdf417/PDF417ErrorCorrectionTest.cpp index b7def50056..a4aeec2506 100644 --- a/test/unit/pdf417/PDF417ErrorCorrectionTest.cpp +++ b/test/unit/pdf417/PDF417ErrorCorrectionTest.cpp @@ -68,28 +68,28 @@ static void Corrupt(std::vector& received, int howMany, PseudoRandom& rando TEST(PDF417ErrorCorrectionTest, NoError) { std::vector received(PDF417_TEST_WITH_EC, PDF417_TEST_WITH_EC + Size(PDF417_TEST_WITH_EC)); - // no errors + // no errors CheckDecode(received); } TEST(PDF417ErrorCorrectionTest, OneError) { PseudoRandom random(0x12345678); - for (int i = 0; i < Size(PDF417_TEST_WITH_EC); i++) { + for (int i = 0; i < Size(PDF417_TEST_WITH_EC); i++) { std::vector received(PDF417_TEST_WITH_EC, PDF417_TEST_WITH_EC + Size(PDF417_TEST_WITH_EC)); received[i] = random.next(0, 255); CheckDecode(received); - } + } } TEST(PDF417ErrorCorrectionTest, MaxErrors) { PseudoRandom random(0x12345678); - for (int testIterations = 0; testIterations < 100; testIterations++) { // # iterations is kind of arbitrary + for (int testIterations = 0; testIterations < 100; testIterations++) { // # iterations is kind of arbitrary std::vector received(PDF417_TEST_WITH_EC, PDF417_TEST_WITH_EC + Size(PDF417_TEST_WITH_EC)); Corrupt(received, MAX_ERRORS, random, 929); CheckDecode(received); - } + } } TEST(PDF417ErrorCorrectionTest, TooManyErrors) diff --git a/test/unit/pdf417/PDF417HighLevelEncoderTest.cpp b/test/unit/pdf417/PDF417HighLevelEncoderTest.cpp index bbcc492d03..3d586c2f46 100644 --- a/test/unit/pdf417/PDF417HighLevelEncoderTest.cpp +++ b/test/unit/pdf417/PDF417HighLevelEncoderTest.cpp @@ -24,7 +24,7 @@ TEST(PDF417HighLevelEncoderTest, EncodeAutoWithSpecialChars) //Just check if this does not throw an exception HighLevelEncoder::EncodeHighLevel(L"1%\xA7""s ?aG$", Compaction::AUTO, CharacterSet::UTF8); } - + TEST(PDF417HighLevelEncoderTest, EncodeIso88591WithSpecialChars) { // Just check if this does not throw an exception @@ -33,7 +33,7 @@ TEST(PDF417HighLevelEncoderTest, EncodeIso88591WithSpecialChars) TEST(PDF417HighLevelEncoderTest, EncodeText) { - auto encoded = HighLevelEncoder::EncodeHighLevel(L"ABCD", Compaction::TEXT, CharacterSet::UTF8); + auto encoded = HighLevelEncoder::EncodeHighLevel(L"ABCD", Compaction::TEXT, CharacterSet::UTF8); EXPECT_EQ(encoded, std::vector({ 0x39f, 0x1a, 1, '?' })); } diff --git a/test/unit/pdf417/PDF417ScanningDecoderTest.cpp b/test/unit/pdf417/PDF417ScanningDecoderTest.cpp index cd7143a42f..f4822d8881 100644 --- a/test/unit/pdf417/PDF417ScanningDecoderTest.cpp +++ b/test/unit/pdf417/PDF417ScanningDecoderTest.cpp @@ -8,18 +8,14 @@ #include "gtest/gtest.h" -namespace ZXing::Pdf417 { - DecoderResult DecodeCodewords(std::vector& codewords, int ecLevel, const std::vector& erasures); -} - using namespace ZXing; using namespace ZXing::Pdf417; // Shorthand for DecodeCodewords() -static DecoderResult decode(std::vector& codewords, int ecLevel = 0) +static DecoderResult decode(std::vector& codewords) { std::vector erasures; - auto result = DecodeCodewords(codewords, ecLevel, erasures); + auto result = DecodeCodewords(codewords, NumECCodeWords(0)); return result; } diff --git a/test/unit/pdf417/PDF417WriterTest.cpp b/test/unit/pdf417/PDF417WriterTest.cpp index 747d2c5524..e4285cac6a 100644 --- a/test/unit/pdf417/PDF417WriterTest.cpp +++ b/test/unit/pdf417/PDF417WriterTest.cpp @@ -16,8 +16,8 @@ TEST(PDF417WriterTest, DataMatrixImageWriter) { Writer writer; writer.setMargin(0); - int size = 64; - BitMatrix matrix = writer.encode(L"Hello Google", size, size); + int size = 64; + BitMatrix matrix = writer.encode(L"Hello Google", size, size); auto actual = ToString(matrix, 'X', ' ', true); EXPECT_EQ(actual, "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" diff --git a/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp b/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp index 0d8c1b0def..b979e73855 100644 --- a/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp +++ b/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp @@ -25,51 +25,51 @@ using namespace ZXing::QRCode; TEST(QRDecodedBitStreamParserTest, SimpleByteMode) { - BitArray ba; - ba.appendBits(0x04, 4); // Byte mode - ba.appendBits(0x03, 8); // 3 bytes - ba.appendBits(0xF1, 8); - ba.appendBits(0xF2, 8); - ba.appendBits(0xF3, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium).text(); - EXPECT_EQ(L"\xF1\xF2\xF3", result); + BitArray ba; + ba.appendBits(0x04, 4); // Byte mode + ba.appendBits(0x03, 8); // 3 bytes + ba.appendBits(0xF1, 8); + ba.appendBits(0xF2, 8); + ba.appendBits(0xF3, 8); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + EXPECT_EQ(L"\xF1\xF2\xF3", result); } TEST(QRDecodedBitStreamParserTest, SimpleSJIS) { - BitArray ba; - ba.appendBits(0x04, 4); // Byte mode - ba.appendBits(0x04, 8); // 4 bytes - ba.appendBits(0xA1, 8); - ba.appendBits(0xA2, 8); - ba.appendBits(0xA3, 8); - ba.appendBits(0xD0, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium).text(); + BitArray ba; + ba.appendBits(0x04, 4); // Byte mode + ba.appendBits(0x04, 8); // 4 bytes + ba.appendBits(0xA1, 8); + ba.appendBits(0xA2, 8); + ba.appendBits(0xA3, 8); + ba.appendBits(0xD0, 8); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\uff61\uff62\uff63\uff90", result); } TEST(QRDecodedBitStreamParserTest, ECI) { - BitArray ba; - ba.appendBits(0x07, 4); // ECI mode - ba.appendBits(0x02, 8); // ECI 2 = CP437 encoding - ba.appendBits(0x04, 4); // Byte mode - ba.appendBits(0x03, 8); // 3 bytes - ba.appendBits(0xA1, 8); - ba.appendBits(0xA2, 8); - ba.appendBits(0xA3, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium).text(); + BitArray ba; + ba.appendBits(0x07, 4); // ECI mode + ba.appendBits(0x02, 8); // ECI 2 = CP437 encoding + ba.appendBits(0x04, 4); // Byte mode + ba.appendBits(0x03, 8); // 3 bytes + ba.appendBits(0xA1, 8); + ba.appendBits(0xA2, 8); + ba.appendBits(0xA3, 8); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\xED\xF3\xFA", result); } TEST(QRDecodedBitStreamParserTest, Hanzi) { - BitArray ba; - ba.appendBits(0x0D, 4); // Hanzi mode - ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding - ba.appendBits(0x01, 8); // 1 characters - ba.appendBits(0x03C1, 13); - auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium).text(); + BitArray ba; + ba.appendBits(0x0D, 4); // Hanzi mode + ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding + ba.appendBits(0x01, 8); // 1 characters + ba.appendBits(0x03C1, 13); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\u963f", result); } @@ -82,13 +82,13 @@ TEST(QRDecodedBitStreamParserTest, HanziLevel1) // A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181 ba.appendBits(0x0181, 13); - auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\u30a2", result); } TEST(QRDecodedBitStreamParserTest, SymbologyIdentifier) { - const Version& version = *Version::VersionForNumber(1); + const Version& version = *Version::FromNumber(1); const ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Medium; DecoderResult result; diff --git a/test/unit/qrcode/QREncoderTest.cpp b/test/unit/qrcode/QREncoderTest.cpp index 6a31177836..8e28abc515 100644 --- a/test/unit/qrcode/QREncoderTest.cpp +++ b/test/unit/qrcode/QREncoderTest.cpp @@ -363,22 +363,22 @@ TEST(QREncoderTest, AppendLengthInfo) { BitArray bits; AppendLengthInfo(1, // 1 letter (1/1). - *Version::VersionForNumber(1), CodecMode::NUMERIC, bits); + *Version::FromNumber(1), CodecMode::NUMERIC, bits); EXPECT_EQ(ToString(bits), RemoveSpace("........ .X")); // 10 bits. bits = BitArray(); AppendLengthInfo(2, // 2 letters (2/1). - *Version::VersionForNumber(10), CodecMode::ALPHANUMERIC, bits); + *Version::FromNumber(10), CodecMode::ALPHANUMERIC, bits); EXPECT_EQ(ToString(bits), RemoveSpace("........ .X.")); // 11 bits. bits = BitArray(); AppendLengthInfo(255, // 255 letter (255/1). - *Version::VersionForNumber(27), CodecMode::BYTE, bits); + *Version::FromNumber(27), CodecMode::BYTE, bits); EXPECT_EQ(ToString(bits), RemoveSpace("........ XXXXXXXX")); // 16 bits. bits = BitArray(); AppendLengthInfo(512, // 512 letters (1024/2). - *Version::VersionForNumber(40), CodecMode::KANJI, bits); + *Version::FromNumber(40), CodecMode::KANJI, bits); EXPECT_EQ(ToString(bits), RemoveSpace("..X..... ....")); // 12 bits. } diff --git a/test/unit/qrcode/QRErrorCorrectionLevelTest.cpp b/test/unit/qrcode/QRErrorCorrectionLevelTest.cpp index 77c103023e..cea739a638 100644 --- a/test/unit/qrcode/QRErrorCorrectionLevelTest.cpp +++ b/test/unit/qrcode/QRErrorCorrectionLevelTest.cpp @@ -13,7 +13,7 @@ using namespace ZXing::QRCode; TEST(QRErrorCorrectionLevelTest, ForBits) { - EXPECT_EQ(ErrorCorrectionLevel::Medium, ECLevelFromBits(0)); + EXPECT_EQ(ErrorCorrectionLevel::Medium, ECLevelFromBits(0)); EXPECT_EQ(ErrorCorrectionLevel::Low, ECLevelFromBits(1)); EXPECT_EQ(ErrorCorrectionLevel::High, ECLevelFromBits(2)); EXPECT_EQ(ErrorCorrectionLevel::Quality, ECLevelFromBits(3)); diff --git a/test/unit/qrcode/QRModeTest.cpp b/test/unit/qrcode/QRModeTest.cpp index 94ab70243f..28411f1cf5 100644 --- a/test/unit/qrcode/QRModeTest.cpp +++ b/test/unit/qrcode/QRModeTest.cpp @@ -6,7 +6,9 @@ #include "qrcode/QRCodecMode.h" #include "qrcode/QRVersion.h" +#include "Error.h" +#include #include "gtest/gtest.h" using namespace ZXing; @@ -14,23 +16,23 @@ using namespace ZXing::QRCode; TEST(QRModeTest, ForBits) { - ASSERT_EQ(CodecMode::TERMINATOR, CodecModeForBits(0x00)); - ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x01)); - ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x02)); - ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x04)); - ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x08)); - ASSERT_THROW(CodecModeForBits(0x10), std::invalid_argument); + ASSERT_EQ(CodecMode::TERMINATOR, CodecModeForBits(0x00)); + ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x01)); + ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x02)); + ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x04)); + ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x08)); + ASSERT_THROW(CodecModeForBits(0x10), Error); } TEST(QRModeTest, CharacterCount) { - // Spot check a few values - ASSERT_EQ(10, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(5))); - ASSERT_EQ(12, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(26))); - ASSERT_EQ(14, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(40))); - ASSERT_EQ(9, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::VersionForNumber(6))); - ASSERT_EQ(8, CharacterCountBits(CodecMode::BYTE, *Version::VersionForNumber(7))); - ASSERT_EQ(8, CharacterCountBits(CodecMode::KANJI, *Version::VersionForNumber(8))); + // Spot check a few values + ASSERT_EQ(10, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(5))); + ASSERT_EQ(12, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(26))); + ASSERT_EQ(14, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(40))); + ASSERT_EQ(9, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::FromNumber(6))); + ASSERT_EQ(8, CharacterCountBits(CodecMode::BYTE, *Version::FromNumber(7))); + ASSERT_EQ(8, CharacterCountBits(CodecMode::KANJI, *Version::FromNumber(8))); } TEST(QRModeTest, MicroForBits) @@ -51,16 +53,16 @@ TEST(QRModeTest, MicroForBits) ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x02, true)); ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x03, true)); - ASSERT_THROW(CodecModeForBits(0x04, true), std::invalid_argument); + ASSERT_THROW(CodecModeForBits(0x04, true), Error); } TEST(QRModeTest, MicroCharacterCount) { // Spot check a few values - ASSERT_EQ(3, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(1, true))); - ASSERT_EQ(4, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(2, true))); - ASSERT_EQ(6, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(4, true))); - ASSERT_EQ(3, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::VersionForNumber(2, true))); - ASSERT_EQ(4, CharacterCountBits(CodecMode::BYTE, *Version::VersionForNumber(3, true))); - ASSERT_EQ(4, CharacterCountBits(CodecMode::KANJI, *Version::VersionForNumber(4, true))); + ASSERT_EQ(3, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(1, true))); + ASSERT_EQ(4, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(2, true))); + ASSERT_EQ(6, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(4, true))); + ASSERT_EQ(3, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::FromNumber(2, true))); + ASSERT_EQ(4, CharacterCountBits(CodecMode::BYTE, *Version::FromNumber(3, true))); + ASSERT_EQ(4, CharacterCountBits(CodecMode::KANJI, *Version::FromNumber(4, true))); } diff --git a/test/unit/qrcode/QRVersionTest.cpp b/test/unit/qrcode/QRVersionTest.cpp index bf4fbe377b..ff2bea8b4b 100644 --- a/test/unit/qrcode/QRVersionTest.cpp +++ b/test/unit/qrcode/QRVersionTest.cpp @@ -21,7 +21,7 @@ namespace { if (number > 1 && !version->isMicroQRCode()) { EXPECT_FALSE(version->alignmentPatternCenters().empty()); } - EXPECT_EQ(dimension, version->dimensionForVersion()); + EXPECT_EQ(dimension, version->dimension()); } void DoTestVersion(int expectedVersion, int mask) { @@ -34,49 +34,49 @@ namespace { TEST(QRVersionTest, VersionForNumber) { - auto version = Version::VersionForNumber(0); - EXPECT_EQ(version, nullptr) << "There is version with number 0"; + auto version = Version::FromNumber(0); + EXPECT_EQ(version, nullptr) << "There is version with number 0"; for (int i = 1; i <= 40; i++) { - CheckVersion(Version::VersionForNumber(i), i, 4*i + 17); - } + CheckVersion(Version::FromNumber(i), i, 4*i + 17); + } } TEST(QRVersionTest, GetProvisionalVersionForDimension) { - for (int i = 1; i <= 40; i++) { - auto prov = Version::ProvisionalVersionForDimension(4 * i + 17); + for (int i = 1; i <= 40; i++) { + auto prov = Version::FromDimension(4 * i + 17); ASSERT_NE(prov, nullptr); EXPECT_EQ(i, prov->versionNumber()); - } + } } TEST(QRVersionTest, DecodeVersionInformation) { - // Spot check - DoTestVersion(7, 0x07C94); - DoTestVersion(12, 0x0C762); - DoTestVersion(17, 0x1145D); - DoTestVersion(22, 0x168C9); - DoTestVersion(27, 0x1B08E); - DoTestVersion(32, 0x209D5); + // Spot check + DoTestVersion(7, 0x07C94); + DoTestVersion(12, 0x0C762); + DoTestVersion(17, 0x1145D); + DoTestVersion(22, 0x168C9); + DoTestVersion(27, 0x1B08E); + DoTestVersion(32, 0x209D5); } - + TEST(QRVersionTest, MicroVersionForNumber) { - auto version = Version::VersionForNumber(0, true); + auto version = Version::FromNumber(0, true); EXPECT_EQ(version, nullptr) << "There is version with number 0"; for (int i = 1; i <= 4; i++) { - CheckVersion(Version::VersionForNumber(i, true), i, 2 * i + 9); + CheckVersion(Version::FromNumber(i, true), i, 2 * i + 9); } } TEST(QRVersionTest, GetProvisionalMicroVersionForDimension) { for (int i = 1; i <= 4; i++) { - auto prov = Version::ProvisionalVersionForDimension(2 * i + 9, true); + auto prov = Version::FromDimension(2 * i + 9); ASSERT_NE(prov, nullptr); EXPECT_EQ(i, prov->versionNumber()); } @@ -90,12 +90,12 @@ TEST(QRVersionTest, FunctionPattern) EXPECT_TRUE(bitMatrix.get(col, row)); }; for (int i = 1; i <= 4; i++) { - const auto version = Version::VersionForNumber(i, true); + const auto version = Version::FromNumber(i, true); const auto functionPattern = version->buildFunctionPattern(); testFinderPatternRegion(functionPattern); // Check timing pattern areas. - const auto dimension = version->dimensionForVersion(); + const auto dimension = version->dimension(); for (int row = dimension; row < functionPattern.height(); row++) EXPECT_TRUE(functionPattern.get(0, row)); for (int col = dimension; col < functionPattern.width(); col++) diff --git a/test/unit/qrcode/QRWriterTest.cpp b/test/unit/qrcode/QRWriterTest.cpp index f96a24e2f6..bb33cf4d11 100644 --- a/test/unit/qrcode/QRWriterTest.cpp +++ b/test/unit/qrcode/QRWriterTest.cpp @@ -9,6 +9,7 @@ #include "qrcode/QRErrorCorrectionLevel.h" #include "gtest/gtest.h" +#include using namespace ZXing; using namespace ZXing::QRCode; diff --git a/wrappers/android/.gitignore b/wrappers/android/.gitignore index 643d213c62..d699f8732b 100644 --- a/wrappers/android/.gitignore +++ b/wrappers/android/.gitignore @@ -1,15 +1,22 @@ -*.iml +# Gradle files .gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures +.gradle/ +build/ + +# Android Studio +.idea/ +/**/build/ +/**/local.properties +/**/out +/**/production +captures/ +.navigation/ .externalNativeBuild +*.ipr +*~ +*.swp +*.iml + +.DS_Store .cxx -local.properties + diff --git a/wrappers/android/.idea/.gitignore b/wrappers/android/.idea/.gitignore deleted file mode 100644 index eaf91e2ac6..0000000000 --- a/wrappers/android/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/wrappers/android/.idea/.name b/wrappers/android/.idea/.name deleted file mode 100644 index e0f5cac5d4..0000000000 --- a/wrappers/android/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -ZXingCpp \ No newline at end of file diff --git a/wrappers/android/.idea/codeStyles/Project.xml b/wrappers/android/.idea/codeStyles/Project.xml deleted file mode 100644 index c4a490d90b..0000000000 --- a/wrappers/android/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/codeStyles/codeStyleConfig.xml b/wrappers/android/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c2b..0000000000 --- a/wrappers/android/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/compiler.xml b/wrappers/android/.idea/compiler.xml deleted file mode 100644 index 7d7ec2eaff..0000000000 --- a/wrappers/android/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/gradle.xml b/wrappers/android/.idea/gradle.xml deleted file mode 100644 index 64ed26b8ed..0000000000 --- a/wrappers/android/.idea/gradle.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/jarRepositories.xml b/wrappers/android/.idea/jarRepositories.xml deleted file mode 100644 index 52a77b6c6e..0000000000 --- a/wrappers/android/.idea/jarRepositories.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/misc.xml b/wrappers/android/.idea/misc.xml deleted file mode 100644 index 59135fb6a1..0000000000 --- a/wrappers/android/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/vcs.xml b/wrappers/android/.idea/vcs.xml deleted file mode 100644 index b2bdec2d71..0000000000 --- a/wrappers/android/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/wrappers/android/app/build.gradle b/wrappers/android/app/build.gradle index 2e3b969e3c..14437f1648 100644 --- a/wrappers/android/app/build.gradle +++ b/wrappers/android/app/build.gradle @@ -41,12 +41,12 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.appcompat:appcompat:1.5.1' - implementation 'com.google.android.material:material:1.7.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.8.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' // CameraX - def camerax_version = "1.0.2" + def camerax_version = "1.2.1" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" implementation "androidx.camera:camera-lifecycle:${camerax_version}" diff --git a/wrappers/android/app/src/main/AndroidManifest.xml b/wrappers/android/app/src/main/AndroidManifest.xml index d411996a69..4127b366bf 100644 --- a/wrappers/android/app/src/main/AndroidManifest.xml +++ b/wrappers/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + + out.write(image.toJpeg()) + } + MediaActionSound().play(MediaActionSound.SHUTTER_CLICK) + } catch (e: Exception) { + beeper.startTone(ToneGenerator.TONE_CDMA_SOFT_ERROR_LITE) //Fail Tone + } + } + private fun bindCameraUseCases() = binding.viewFinder.post { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) @@ -96,6 +137,28 @@ class MainActivity : AppCompatActivity() { val readerJava = MultiFormatReader() val readerCpp = BarcodeReader() + // Create a new camera selector each time, enforcing lens facing + val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() + + // Camera provider is now guaranteed to be available + val cameraProvider = cameraProviderFuture.get() + + // Apply declared configs to CameraX using the same lifecycle owner + cameraProvider.unbindAll() + val camera = cameraProvider.bindToLifecycle( + this as LifecycleOwner, cameraSelector, preview, imageAnalysis + ) + + // Reduce exposure time to decrease effect of motion blur + val camera2 = Camera2CameraControl.from(camera.cameraControl) + camera2.captureRequestOptions = CaptureRequestOptions.Builder() + .setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, 1600) + .setCaptureRequestOption(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, -8) + .build() + + // Use the camera object to link our preview use case with the view + preview.setSurfaceProvider(binding.viewFinder.surfaceProvider) + imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { image -> // Early exit: image analysis is in paused state if (binding.pause.isChecked) { @@ -103,6 +166,11 @@ class MainActivity : AppCompatActivity() { return@Analyzer } + if (doSaveImage) { + doSaveImage = false + saveImage(image) + } + val cropSize = image.height / 3 * 2 val cropRect = if (binding.crop.isChecked) Rect( @@ -190,31 +258,11 @@ class MainActivity : AppCompatActivity() { runtime2 = 0 } + camera.cameraControl.enableTorch(binding.torch.isChecked) + showResult(resultText, infoText, resultPoints, image) }) - // Create a new camera selector each time, enforcing lens facing - val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() - - // Camera provider is now guaranteed to be available - val cameraProvider = cameraProviderFuture.get() - - // Apply declared configs to CameraX using the same lifecycle owner - cameraProvider.unbindAll() - val camera = cameraProvider.bindToLifecycle( - this as LifecycleOwner, cameraSelector, preview, imageAnalysis - ) - - // Reduce exposure time to decrease effect of motion blur - val camera2 = Camera2CameraControl.from(camera.cameraControl) - camera2.captureRequestOptions = CaptureRequestOptions.Builder() - .setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, 1600) - .setCaptureRequestOption(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, -8) - .build() - - // Use the camera object to link our preview use case with the view - preview.setSurfaceProvider(binding.viewFinder.surfaceProvider) - }, ContextCompat.getMainExecutor(this)) } @@ -262,4 +310,4 @@ class MainActivity : AppCompatActivity() { private fun hasPermissions(context: Context) = permissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED } -} +} \ No newline at end of file diff --git a/wrappers/android/app/src/main/res/layout-land/activity_camera.xml b/wrappers/android/app/src/main/res/layout-land/activity_camera.xml index b2726a2600..cf3dca0bde 100644 --- a/wrappers/android/app/src/main/res/layout-land/activity_camera.xml +++ b/wrappers/android/app/src/main/res/layout-land/activity_camera.xml @@ -56,7 +56,7 @@ android:background="@drawable/ic_shutter" android:contentDescription="@string/capture_button_alt" android:scaleType="fitCenter" - android:visibility="invisible" + android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -104,6 +104,11 @@ style="@style/Chip" android:text="crop" /> + + - + \ No newline at end of file diff --git a/wrappers/android/app/src/main/res/layout/activity_camera.xml b/wrappers/android/app/src/main/res/layout/activity_camera.xml index 66ccae4775..4b5d3f4eb4 100644 --- a/wrappers/android/app/src/main/res/layout/activity_camera.xml +++ b/wrappers/android/app/src/main/res/layout/activity_camera.xml @@ -58,7 +58,7 @@ android:background="@drawable/ic_shutter" android:contentDescription="@string/capture_button_alt" android:scaleType="fitCenter" - android:visibility="invisible" + android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> @@ -107,6 +107,11 @@ style="@style/ChipR" android:text="crop" /> + + - + \ No newline at end of file diff --git a/wrappers/android/build.gradle b/wrappers/android/build.gradle index b0ff60f443..b46a4947a6 100644 --- a/wrappers/android/build.gradle +++ b/wrappers/android/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.3.1' + classpath 'com.android.tools.build:gradle:7.4.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/wrappers/android/gradle.properties b/wrappers/android/gradle.properties index 5b0930c074..95845120f6 100644 --- a/wrappers/android/gradle.properties +++ b/wrappers/android/gradle.properties @@ -16,7 +16,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true +android.enableJetifier=false # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official # org.gradle.warning.mode=all \ No newline at end of file diff --git a/wrappers/android/gradle/wrapper/gradle-wrapper.properties b/wrappers/android/gradle/wrapper/gradle-wrapper.properties index 45357a59b1..cfb22fcb3f 100644 --- a/wrappers/android/gradle/wrapper/gradle-wrapper.properties +++ b/wrappers/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip diff --git a/wrappers/android/zxingcpp/build.gradle b/wrappers/android/zxingcpp/build.gradle index 56981b9ea6..2b3a4ed505 100644 --- a/wrappers/android/zxingcpp/build.gradle +++ b/wrappers/android/zxingcpp/build.gradle @@ -47,7 +47,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.core:core-ktx:1.9.0' - def camerax_version = "1.0.2" + def camerax_version = "1.2.1" implementation "androidx.camera:camera-core:${camerax_version}" } diff --git a/wrappers/c/CMakeLists.txt b/wrappers/c/CMakeLists.txt new file mode 100644 index 0000000000..38a817e79e --- /dev/null +++ b/wrappers/c/CMakeLists.txt @@ -0,0 +1,7 @@ +zxing_add_package_stb() + +if (BUILD_READERS) + add_executable (zxing-c-test zxing-c.cpp zxing-c-test.c) + target_link_libraries (zxing-c-test ZXing::ZXing stb::stb) + add_test(NAME zxing-c-test COMMAND zxing-c-test ${CMAKE_SOURCE_DIR}/test/samples/qrcode-1/1.png) +endif() diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c new file mode 100644 index 0000000000..24d4457d66 --- /dev/null +++ b/wrappers/c/zxing-c-test.c @@ -0,0 +1,93 @@ +/* +* Copyright 2023 siiky +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "zxing-c.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +int usage(char* pname) +{ + fprintf(stderr, "Usage: %s FILE [FORMATS]\n", pname); + return 1; +} + +bool parse_args(int argc, char** argv, char** filename, zxing_BarcodeFormats* formats) +{ + if (argc < 2) + return false; + *filename = argv[1]; + if (argc >= 3) { + *formats = zxing_BarcodeFormatsFromString(argv[2]); + if (*formats == zxing_BarcodeFormat_Invalid) { + fprintf(stderr, "Invalid barcode formats string '%s'\n", argv[2]); + return false; + } + } + return true; +} + +void printF(const char* fmt, char* text) +{ + if (!text) + return; + if (*text) + printf(fmt, text); + free(text); +} + +int main(int argc, char** argv) +{ + char* filename = NULL; + zxing_BarcodeFormats formats = zxing_BarcodeFormat_None; + + if (!parse_args(argc, argv, &filename, &formats)) + return usage(argv[0]); + + int width = 0; + int height = 0; + int channels = 0; + stbi_uc* data = stbi_load(filename, &width, &height, &channels, STBI_grey); + if (!data) + return 2; + + zxing_DecodeHints* hints = zxing_DecodeHints_new(); + zxing_DecodeHints_setTextMode(hints, zxing_TextMode_HRI); + zxing_DecodeHints_setEanAddOnSymbol(hints, zxing_EanAddOnSymbol_Ignore); + zxing_DecodeHints_setFormats(hints, formats); + zxing_DecodeHints_setReturnErrors(hints, true); + + zxing_ImageView* iv = zxing_ImageView_new(data, width, height, zxing_ImageFormat_Lum, 0, 0); + + zxing_Results* results = zxing_ReadBarcodes(iv, hints); + + if (results) { + for (int i = 0, n = zxing_Results_size(results); i < n; ++i) { + const zxing_Result* result = zxing_Results_at(results, i); + + printF("Text : %s\n", zxing_Result_text(result)); + printF("Format : %s\n", zxing_BarcodeFormatToString(zxing_Result_format(result))); + printF("Content : %s\n", zxing_ContentTypeToString(zxing_Result_contentType(result))); + printF("Identifier : %s\n", zxing_Result_symbologyIdentifier(result)); + printF("EC Level : %s\n", zxing_Result_ecLevel(result)); + printF("Error : %s\n", zxing_Result_errorMsg(result)); + printf("Rotation : %d\n", zxing_Result_orientation(result)); + + if (i < n-1) + printf("\n"); + } + + zxing_Results_delete(results); + } else { + printf("No barcode found\n"); + } + + zxing_ImageView_delete(iv); + zxing_DecodeHints_delete(hints); + stbi_image_free(data); + + return 0; +} diff --git a/wrappers/c/zxing-c.cpp b/wrappers/c/zxing-c.cpp new file mode 100644 index 0000000000..f40916027a --- /dev/null +++ b/wrappers/c/zxing-c.cpp @@ -0,0 +1,241 @@ +/* +* Copyright 2023 siiky +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "zxing-c.h" + +#include "ReadBarcode.h" + +using namespace ZXing; + +char* copy(std::string_view sv) +{ + auto ret = (char*)malloc(sv.size() + 1); + if (ret) { + strncpy(ret, sv.data(), sv.size()); + ret[sv.size()] = '\0'; + } + return ret; +} + +extern "C" +{ + /* + * ZXing/ImageView.h + */ + + zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, + int pixStride) + { + ImageFormat cppformat = static_cast(format); + return new ImageView(data, width, height, cppformat, rowStride, pixStride); + } + + void zxing_ImageView_delete(zxing_ImageView* iv) + { + delete iv; + } + + /* + * ZXing/BarcodeFormat.h + */ + + zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str) + { + if (!str) + return {}; + try { + auto format = BarcodeFormatsFromString(str); + return static_cast(*reinterpret_cast(&format)); + } catch (...) { + return zxing_BarcodeFormat_Invalid; + } + } + + zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str) + { + zxing_BarcodeFormat res = zxing_BarcodeFormatsFromString(str); + return BitHacks::CountBitsSet(res) == 1 ? res : zxing_BarcodeFormat_Invalid; + } + + char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format) + { + return copy(ToString(static_cast(format))); + } + + /* + * ZXing/DecodeHints.h + */ + + zxing_DecodeHints* zxing_DecodeHints_new() + { + return new DecodeHints(); + } + + void zxing_DecodeHints_delete(zxing_DecodeHints* hints) + { + delete hints; + } + + void zxing_DecodeHints_setTryHarder(zxing_DecodeHints* hints, bool tryHarder) + { + hints->setTryHarder(tryHarder); + } + + void zxing_DecodeHints_setTryRotate(zxing_DecodeHints* hints, bool tryRotate) + { + hints->setTryRotate(tryRotate); + } + + void zxing_DecodeHints_setTryInvert(zxing_DecodeHints* hints, bool tryInvert) + { + hints->setTryInvert(tryInvert); + } + + void zxing_DecodeHints_setTryDownscale(zxing_DecodeHints* hints, bool tryDownscale) + { + hints->setTryDownscale(tryDownscale); + } + + void zxing_DecodeHints_setIsPure(zxing_DecodeHints* hints, bool isPure) + { + hints->setIsPure(isPure); + } + + void zxing_DecodeHints_setReturnErrors(zxing_DecodeHints* hints, bool returnErrors) + { + hints->setReturnErrors(returnErrors); + } + + void zxing_DecodeHints_setFormats(zxing_DecodeHints* hints, zxing_BarcodeFormats formats) + { + hints->setFormats(static_cast(formats)); + } + + void zxing_DecodeHints_setBinarizer(zxing_DecodeHints* hints, zxing_Binarizer binarizer) + { + hints->setBinarizer(static_cast(binarizer)); + } + + void zxing_DecodeHints_setEanAddOnSymbol(zxing_DecodeHints* hints, zxing_EanAddOnSymbol eanAddOnSymbol) + { + hints->setEanAddOnSymbol(static_cast(eanAddOnSymbol)); + } + + void zxing_DecodeHints_setTextMode(zxing_DecodeHints* hints, zxing_TextMode textMode) + { + hints->setTextMode(static_cast(textMode)); + } + + /* + * ZXing/Result.h + */ + + char* zxing_ContentTypeToString(zxing_ContentType type) + { + return copy(ToString(static_cast(type))); + } + + bool zxing_Result_isValid(const zxing_Result* result) + { + return result != NULL && result->isValid(); + } + + char* zxing_Result_errorMsg(const zxing_Result* result) + { + return copy(ToString(result->error())); + } + + zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result) + { + return static_cast(result->format()); + } + + zxing_ContentType zxing_Result_contentType(const zxing_Result* result) + { + return static_cast(result->contentType()); + } + + uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len) + { + *len = Size(result->bytes()); + + auto ret = (uint8_t*)malloc(*len + 1); + if (ret) + memcpy(ret, result->bytes().data(), *len); + else + *len = 0; + + return ret; + } + + char* zxing_Result_text(const zxing_Result* result) + { + return copy(result->text()); + } + + char* zxing_Result_ecLevel(const zxing_Result* result) + { + return copy(result->ecLevel()); + } + + char* zxing_Result_symbologyIdentifier(const zxing_Result* result) + { + return copy(result->symbologyIdentifier()); + } + + int zxing_Result_orientation(const zxing_Result* result) + { + return result->orientation(); + } + + bool zxing_Result_isInverted(const zxing_Result* result) + { + return result->isInverted(); + } + + bool zxing_Result_isMirrored(const zxing_Result* result) + { + return result->isMirrored(); + } + + /* + * ZXing/ReadBarcode.h + */ + + zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_DecodeHints* hints) + { + auto res = ReadBarcode(*iv, *hints); + return res.format() != BarcodeFormat::None ? new Result(std::move(res)) : NULL; + } + + zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_DecodeHints* hints) + { + auto res = ReadBarcodes(*iv, *hints); + return !res.empty() ? new Results(std::move(res)) : NULL; + } + + void zxing_Result_delete(zxing_Result* result) + { + delete result; + } + + void zxing_Results_delete(zxing_Results* results) + { + delete results; + } + + int zxing_Results_size(const zxing_Results* results) + { + return results ? Size(*results) : 0; + } + + const zxing_Result* zxing_Results_at(const zxing_Results* results, int i) + { + if (!results || i < 0 || i >= Size(*results)) + return NULL; + return &(*results)[i]; + } +} diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h new file mode 100644 index 0000000000..daded89240 --- /dev/null +++ b/wrappers/c/zxing-c.h @@ -0,0 +1,184 @@ +/* +* Copyright 2023 siiky +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#ifndef _ZXING_C_H +#define _ZXING_C_H + +#include +#include + +#ifdef __cplusplus + +#include "DecodeHints.h" +#include "ImageView.h" +#include "Result.h" + +typedef ZXing::ImageView zxing_ImageView; +typedef ZXing::DecodeHints zxing_DecodeHints; +typedef ZXing::Result zxing_Result; +typedef ZXing::Results zxing_Results; + +extern "C" +{ +#else + +typedef struct zxing_ImageView zxing_ImageView; +typedef struct zxing_DecodeHints zxing_DecodeHints; +typedef struct zxing_Result zxing_Result; +typedef struct zxing_Results zxing_Results; + +#endif + + /* + * ZXing/ImageView.h + */ + + typedef enum + { + zxing_ImageFormat_None = 0, + zxing_ImageFormat_Lum = 0x01000000, + zxing_ImageFormat_RGB = 0x03000102, + zxing_ImageFormat_BGR = 0x03020100, + zxing_ImageFormat_RGBX = 0x04000102, + zxing_ImageFormat_XRGB = 0x04010203, + zxing_ImageFormat_BGRX = 0x04020100, + zxing_ImageFormat_XBGR = 0x04030201, + } zxing_ImageFormat; + + zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, + int pixStride); + void zxing_ImageView_delete(zxing_ImageView* iv); + + /* + * ZXing/BarcodeFormat.h + */ + + typedef enum + { + zxing_BarcodeFormat_None = 0, + zxing_BarcodeFormat_Aztec = (1 << 0), + zxing_BarcodeFormat_Codabar = (1 << 1), + zxing_BarcodeFormat_Code39 = (1 << 2), + zxing_BarcodeFormat_Code93 = (1 << 3), + zxing_BarcodeFormat_Code128 = (1 << 4), + zxing_BarcodeFormat_DataBar = (1 << 5), + zxing_BarcodeFormat_DataBarExpanded = (1 << 6), + zxing_BarcodeFormat_DataMatrix = (1 << 7), + zxing_BarcodeFormat_EAN8 = (1 << 8), + zxing_BarcodeFormat_EAN13 = (1 << 9), + zxing_BarcodeFormat_ITF = (1 << 10), + zxing_BarcodeFormat_MaxiCode = (1 << 11), + zxing_BarcodeFormat_PDF417 = (1 << 12), + zxing_BarcodeFormat_QRCode = (1 << 13), + zxing_BarcodeFormat_UPCA = (1 << 14), + zxing_BarcodeFormat_UPCE = (1 << 15), + zxing_BarcodeFormat_MicroQRCode = (1 << 16), + + zxing_BarcodeFormat_LinearCodes = zxing_BarcodeFormat_Codabar | zxing_BarcodeFormat_Code39 | zxing_BarcodeFormat_Code93 + | zxing_BarcodeFormat_Code128 | zxing_BarcodeFormat_EAN8 | zxing_BarcodeFormat_EAN13 + | zxing_BarcodeFormat_ITF | zxing_BarcodeFormat_DataBar | zxing_BarcodeFormat_DataBarExpanded + | zxing_BarcodeFormat_UPCA | zxing_BarcodeFormat_UPCE, + zxing_BarcodeFormat_MatrixCodes = zxing_BarcodeFormat_Aztec | zxing_BarcodeFormat_DataMatrix | zxing_BarcodeFormat_MaxiCode + | zxing_BarcodeFormat_PDF417 | zxing_BarcodeFormat_QRCode | zxing_BarcodeFormat_MicroQRCode, + zxing_BarcodeFormat_Any = zxing_BarcodeFormat_LinearCodes | zxing_BarcodeFormat_MatrixCodes, + + zxing_BarcodeFormat_Invalid = -1 /* return value when BarcodeFormatsFromString() throws */ + } zxing_BarcodeFormat; + + typedef zxing_BarcodeFormat zxing_BarcodeFormats; + + zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str); + zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str); + char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format); + + /* + * ZXing/DecodeHints.h + */ + + typedef enum + { + zxing_Binarizer_LocalAverage, + zxing_Binarizer_GlobalHistogram, + zxing_Binarizer_FixedThreshold, + zxing_Binarizer_BoolCast, + } zxing_Binarizer; + + typedef enum + { + zxing_EanAddOnSymbol_Ignore, + zxing_EanAddOnSymbol_Read, + zxing_EanAddOnSymbol_Require, + } zxing_EanAddOnSymbol; + + typedef enum + { + zxing_TextMode_Plain, + zxing_TextMode_ECI, + zxing_TextMode_HRI, + zxing_TextMode_Hex, + zxing_TextMode_Escaped, + } zxing_TextMode; + + zxing_DecodeHints* zxing_DecodeHints_new(); + void zxing_DecodeHints_delete(zxing_DecodeHints* hints); + + void zxing_DecodeHints_setTryHarder(zxing_DecodeHints* hints, bool tryHarder); + void zxing_DecodeHints_setTryRotate(zxing_DecodeHints* hints, bool tryRotate); + void zxing_DecodeHints_setTryInvert(zxing_DecodeHints* hints, bool tryInvert); + void zxing_DecodeHints_setTryDownscale(zxing_DecodeHints* hints, bool tryDownscale); + void zxing_DecodeHints_setIsPure(zxing_DecodeHints* hints, bool isPure); + void zxing_DecodeHints_setReturnErrors(zxing_DecodeHints* hints, bool returnErrors); + void zxing_DecodeHints_setFormats(zxing_DecodeHints* hints, zxing_BarcodeFormats formats); + void zxing_DecodeHints_setBinarizer(zxing_DecodeHints* hints, zxing_Binarizer binarizer); + void zxing_DecodeHints_setEanAddOnSymbol(zxing_DecodeHints* hints, zxing_EanAddOnSymbol eanAddOnSymbol); + void zxing_DecodeHints_setTextMode(zxing_DecodeHints* hints, zxing_TextMode textMode); + + /* + * ZXing/Result.h + */ + + typedef enum + { + zxing_ContentType_Text, + zxing_ContentType_Binary, + zxing_ContentType_Mixed, + zxing_ContentType_GS1, + zxing_ContentType_ISO15434, + zxing_ContentType_UnknownECI + } zxing_ContentType; + + char* zxing_ContentTypeToString(zxing_ContentType type); + + bool zxing_Result_isValid(const zxing_Result* result); + char* zxing_Result_errorMsg(const zxing_Result* result); + zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result); + zxing_ContentType zxing_Result_contentType(const zxing_Result* result); + uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len); + char* zxing_Result_text(const zxing_Result* result); + char* zxing_Result_ecLevel(const zxing_Result* result); + char* zxing_Result_symbologyIdentifier(const zxing_Result* result); + int zxing_Result_orientation(const zxing_Result* result); + bool zxing_Result_isInverted(const zxing_Result* result); + bool zxing_Result_isMirrored(const zxing_Result* result); + + /* + * ZXing/ReadBarcode.h + */ + + zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_DecodeHints* hints); + zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_DecodeHints* hints); + + void zxing_Result_delete(zxing_Result* result); + void zxing_Results_delete(zxing_Results* results); + + int zxing_Results_size(const zxing_Results* results); + const zxing_Result* zxing_Results_at(const zxing_Results* results, int i); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZXING_C_H */ diff --git a/wrappers/ios/Package.swift b/wrappers/ios/Package.swift index b0dbe39bb3..7e3352acd7 100644 --- a/wrappers/ios/Package.swift +++ b/wrappers/ios/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "ZXingCppWrapper", platforms: [ - .iOS(.v13) + .iOS(.v11) ], products: [ .library( diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm index 622b585aa9..9a50235c0d 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm @@ -44,6 +44,10 @@ - (instancetype)initWithTryHarder:(BOOL)tryHarder return self; } +-(void)setMaxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { + self.zxingHints.setMaxNumberOfSymbols(maxNumberOfSymbols); +} + -(void)setTryHarder:(BOOL)tryHarder { self.zxingHints.setTryHarder(tryHarder); } @@ -80,6 +84,10 @@ -(void)setDownscalethreshold:(uint16_t)downscaleThreshold { self.zxingHints.setDownscaleThreshold(downscaleThreshold); } +- (NSInteger)maxNumberOfSymbols { + return self.zxingHints.maxNumberOfSymbols(); +} + -(BOOL)tryHarder { return self.zxingHints.tryHarder(); } diff --git a/wrappers/ios/build-release.sh b/wrappers/ios/build-release.sh index 9a4256ca09..7c1f4b4d5e 100755 --- a/wrappers/ios/build-release.sh +++ b/wrappers/ios/build-release.sh @@ -6,7 +6,7 @@ echo ========= Create project structure cmake -S../../ -B_builds -GXcode \ -DCMAKE_SYSTEM_NAME=iOS \ "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ -DCMAKE_INSTALL_PREFIX=`pwd`/_install \ -DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \ -DBUILD_UNIT_TESTS=NO \ diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index 5eb7a618ab..5b2a41581f 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -1,63 +1,31 @@ -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.15) project(ZXingPython) set (pybind11_git_repo https://github.com/pybind/pybind11.git) -set (pybind11_git_rev v2.10.2) +set (pybind11_git_rev v2.10.4) # check if we are called from the top-level ZXing project get_directory_property(hasParent PARENT_DIRECTORY) if (NOT hasParent) - # Force to build by C++17. The zxing-cpp require C++17 to build - set(CMAKE_CXX_STANDARD 17) - set(CMAKE_CXX_STANDARD_REQUIRED ON) + # Build with C++20 by default (which enables position independent DataMatrix detection). + set(CMAKE_CXX_STANDARD 20) + # Allow the fallback to earlier versions of the compiler does not support it. + set(CMAKE_CXX_STANDARD_REQUIRED OFF) option (BUILD_SHARED_LIBS "Link python module to shared lib" OFF) option (BUILD_WRITERS "Build with writer support (encoders)" ON) option (BUILD_READERS "Build with reader support (decoders)" ON) - - # build the main library - if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../../core) - # In development mode, when the whole zxing-cpp directory is checked out, build against head code. - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../core ZXing EXCLUDE_FROM_ALL) - - include(${CMAKE_CURRENT_SOURCE_DIR}/../../zxing.cmake) - zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) + set(BUILD_DEPENDENCIES "AUTO") + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/core ZXing EXCLUDE_FROM_ALL) + include(${CMAKE_CURRENT_SOURCE_DIR}/zxing.cmake) else() - # we don't have access to the top-level cmake helpers -> simply fetch it unconditional - include(FetchContent) - FetchContent_Declare (pybind11 - GIT_REPOSITORY ${pybind11_git_repo} - GIT_TAG ${pybind11_git_rev}) - FetchContent_MakeAvailable (pybind11) - - # Building from python source distribution (which does not include the whole repository but only python part) - # so we need to get c++ source git to build the python extension. The python distribution version (given in - # VERSION_INFO), matches the c++ version the distribution must build against ; hence the checkout of - # related tag. - find_package (Git REQUIRED) - execute_process( - COMMAND ${GIT_EXECUTABLE} ls-remote https://github.com/zxing-cpp/zxing-cpp.git tags/* - RESULT_VARIABLE RESULT - OUTPUT_VARIABLE OUTPUT) - string (FIND "${OUTPUT}" "refs/tags/v${VERSION_INFO}\n" FOUND_IDX) - if (FOUND_IDX LESS 0) - # Version not found in tags (suppose we are preparing future version), use master branch - FetchContent_Declare(zxing-cpp - GIT_REPOSITORY https://github.com/zxing-cpp/zxing-cpp.git) - else() - FetchContent_Declare(zxing-cpp - GIT_REPOSITORY https://github.com/zxing-cpp/zxing-cpp.git - GIT_TAG v${VERSION_INFO}) - endif() - if(NOT zxing-cpp_POPULATED) - FetchContent_Populate(zxing-cpp) - add_subdirectory(${zxing-cpp_SOURCE_DIR}/core ZXing EXCLUDE_FROM_ALL) - endif() + message(FATAL_ERROR "Unable to locate zxing source code") endif() -else() - zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) endif() +zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) + # build the python module pybind11_add_module(zxingcpp zxing.cpp) target_link_libraries(zxingcpp PRIVATE ZXing::ZXing) diff --git a/wrappers/python/MANIFEST.in b/wrappers/python/MANIFEST.in index e4f71fae29..b77220579c 100644 --- a/wrappers/python/MANIFEST.in +++ b/wrappers/python/MANIFEST.in @@ -1,2 +1,4 @@ -include CMakeLists.txt -include zxing.cpp \ No newline at end of file +global-include CMakeLists.txt +include zxing.cpp +include zxing.cmake +graft core/ diff --git a/wrappers/python/README.md b/wrappers/python/README.md index 015f90eefe..52b23f9b14 100644 --- a/wrappers/python/README.md +++ b/wrappers/python/README.md @@ -14,7 +14,7 @@ or python setup.py install ``` -Note: To install via `setup.py`, you need a suitable [build environment](https://github.com/zxing-cpp/zxing-cpp#build-instructions) including a c++ compiler. +[Note: To install via `setup.py` (or via `pip install` in case there is no pre-build wheel available for your platfor or python version), you need a suitable [build environment](https://github.com/zxing-cpp/zxing-cpp#build-instructions) including a c++ compiler.] ## Usage @@ -30,3 +30,5 @@ for result in results: if len(results) == 0: print("Could not find any barcode.") ``` + +To get a full list of available parameters for `read_barcodes` and `write_barcode` as well as the properties of the result objects, have a look at the `PYBIND11_MODULE` definition in [this c++ source file](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/python/zxing.cpp). diff --git a/wrappers/python/core b/wrappers/python/core new file mode 120000 index 0000000000..3d25ddeb2c --- /dev/null +++ b/wrappers/python/core @@ -0,0 +1 @@ +../../core \ No newline at end of file diff --git a/wrappers/python/pyproject.toml b/wrappers/python/pyproject.toml index dbeaae43ca..16484fe3ee 100644 --- a/wrappers/python/pyproject.toml +++ b/wrappers/python/pyproject.toml @@ -3,6 +3,7 @@ requires = [ "setuptools>=42", "setuptools_scm", "wheel", - "cmake>=3.14", + "cmake>=3.15", + "pybind11[global]", ] build-backend = "setuptools.build_meta" diff --git a/wrappers/python/setup.py b/wrappers/python/setup.py index abb78d31e0..232b5a731f 100644 --- a/wrappers/python/setup.py +++ b/wrappers/python/setup.py @@ -59,7 +59,7 @@ def build_extension(self, ext): # "local_scheme": "no-local-version", # "tag_regex": "v?([0-9]+.[0-9]+.[0-9]+)", # }, - version='2.0.0', + version='2.1.0', description='Python bindings for the zxing-cpp barcode library', long_description=long_description, long_description_content_type="text/markdown", diff --git a/wrappers/python/test.py b/wrappers/python/test.py index ea979f9ee7..5d7f8cf30e 100644 --- a/wrappers/python/test.py +++ b/wrappers/python/test.py @@ -84,13 +84,13 @@ def test_write_read_cycle_pil(self): def test_read_invalid_type(self): self.assertRaisesRegex( - TypeError, "Unsupported type . Expect a PIL Image or numpy array", zxingcpp.read_barcode, "foo" + TypeError, "Could not convert to numpy array.", zxingcpp.read_barcode, "foo" ) def test_read_invalid_numpy_array_channels(self): import numpy as np self.assertRaisesRegex( - TypeError, "Unsupported number of channels for numpy array: 4", zxingcpp.read_barcode, + ValueError, "Unsupported number of channels for numpy array: 4", zxingcpp.read_barcode, np.zeros((100, 100, 4), np.uint8) ) diff --git a/wrappers/python/zxing.cmake b/wrappers/python/zxing.cmake new file mode 120000 index 0000000000..e0240de99c --- /dev/null +++ b/wrappers/python/zxing.cmake @@ -0,0 +1 @@ +../../zxing.cmake \ No newline at end of file diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index 14dd5c7e7b..79db3c480d 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -50,38 +50,43 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t .setEanAddOnSymbol(ean_add_on_symbol); const auto _type = std::string(py::str(py::type::of(_image))); Image image; + ImageFormat imgfmt = ImageFormat::None; try { + if (_type.find("PIL.") != std::string::npos) { + _image.attr("load")(); + const auto mode = _image.attr("mode").cast(); + if (mode == "L") + imgfmt = ImageFormat::Lum; + else if (mode == "RGB") + imgfmt = ImageFormat::RGB; + else if (mode == "RGBA") + imgfmt = ImageFormat::RGBX; + else { + // Unsupported mode in ImageFormat. Let's do conversion to L mode with PIL. + _image = _image.attr("convert")("L"); + imgfmt = ImageFormat::Lum; + } + } image = _image.cast(); - } - catch(...) { - throw py::type_error("Unsupported type " + _type + ". Expect a PIL Image or numpy array"); +#if PYBIND11_VERSION_HEX > 0x02080000 // py::raise_from is available starting from 2.8.0 + } catch (py::error_already_set &e) { + py::raise_from(e, PyExc_TypeError, ("Could not convert " + _type + " to numpy array of dtype 'uint8'.").c_str()); + throw py::error_already_set(); +#endif + } catch (...) { + throw py::type_error("Could not convert " + _type + " to numpy array. Expecting a PIL Image or numpy array."); } const auto height = narrow_cast(image.shape(0)); const auto width = narrow_cast(image.shape(1)); - auto channels = image.ndim() == 2 ? 1 : narrow_cast(image.shape(2)); - ImageFormat imgfmt; - if (_type.find("PIL.") != std::string::npos) { - const auto mode = _image.attr("mode").cast(); - if (mode == "L") - imgfmt = ImageFormat::Lum; - else if (mode == "RGB") - imgfmt = ImageFormat::RGB; - else if (mode == "RGBA") - imgfmt = ImageFormat::RGBX; - else { - // Unsupported mode in ImageFormat. Let's do conversion to L mode with PIL - image = _image.attr("convert")("L").cast(); - imgfmt = ImageFormat::Lum; - channels = 1; - } - } else { + const auto channels = image.ndim() == 2 ? 1 : narrow_cast(image.shape(2)); + if (imgfmt == ImageFormat::None) { // Assume grayscale or BGR image depending on channels number if (channels == 1) imgfmt = ImageFormat::Lum; else if (channels == 3) imgfmt = ImageFormat::BGR; else - throw py::type_error("Unsupported number of channels for numpy array: " + std::to_string(channels)); + throw py::value_error("Unsupported number of channels for numpy array: " + std::to_string(channels)); } const auto bytes = image.data(); @@ -136,7 +141,7 @@ PYBIND11_MODULE(zxingcpp, m) .value("MaxiCode", BarcodeFormat::MaxiCode) .value("PDF417", BarcodeFormat::PDF417) .value("QRCode", BarcodeFormat::QRCode) - .value("MircoQRCode", BarcodeFormat::MicroQRCode) + .value("MicroQRCode", BarcodeFormat::MicroQRCode) .value("DataBar", BarcodeFormat::DataBar) .value("DataBarExpanded", BarcodeFormat::DataBarExpanded) .value("UPCA", BarcodeFormat::UPCA) diff --git a/wrappers/wasm/BarcodeReader.cpp b/wrappers/wasm/BarcodeReader.cpp index 4f5d99f973..e56762e490 100644 --- a/wrappers/wasm/BarcodeReader.cpp +++ b/wrappers/wasm/BarcodeReader.cpp @@ -1,29 +1,32 @@ /* -* Copyright 2016 Nu-book Inc. -*/ + * Copyright 2016 Nu-book Inc. + * Copyright 2023 Axel Waggershauser + */ // SPDX-License-Identifier: Apache-2.0 #include "ReadBarcode.h" -#include +#include #include #include -#include +#include #define STB_IMAGE_IMPLEMENTATION #include +using namespace ZXing; + struct ReadResult { - std::string format; - std::string text; - std::string error; - ZXing::Position position; + std::string format{}; + std::string text{}; + std::string error{}; + Position position{}; + std::string symbologyIdentifier{}; }; -ReadResult readBarcodeFromImageView(ZXing::ImageView iv, bool tryHarder, const std::string& format) +std::vector readBarcodes(ImageView iv, bool tryHarder, const std::string& format, int maxSymbols) { - using namespace ZXing; try { DecodeHints hints; hints.setTryHarder(tryHarder); @@ -31,42 +34,52 @@ ReadResult readBarcodeFromImageView(ZXing::ImageView iv, bool tryHarder, const s hints.setTryInvert(tryHarder); hints.setTryDownscale(tryHarder); hints.setFormats(BarcodeFormatsFromString(format)); - hints.setMaxNumberOfSymbols(1); + hints.setMaxNumberOfSymbols(maxSymbols); +// hints.setReturnErrors(maxSymbols > 1); auto results = ReadBarcodes(iv, hints); - if (!results.empty()) { - auto& result = results.front(); - return { ToString(result.format()), result.text(), "", result.position() }; + + std::vector readResults{}; + readResults.reserve(results.size()); + + for (auto& result : results) { + readResults.push_back({ToString(result.format()), result.text(), ToString(result.error()), result.position(), result.symbologyIdentifier()}); } - } - catch (const std::exception& e) { - return { "", "", e.what() }; - } - catch (...) { - return { "", "", "Unknown error" }; + + return readResults; + } catch (const std::exception& e) { + return {{"", "", e.what()}}; + } catch (...) { + return {{"", "", "Unknown error"}}; } return {}; } -ReadResult readBarcodeFromImage(int bufferPtr, int bufferLength, bool tryHarder, std::string format) +std::vector readBarcodesFromImage(int bufferPtr, int bufferLength, bool tryHarder, std::string format, int maxSymbols) { - using namespace ZXing; - int width, height, channels; std::unique_ptr buffer( - stbi_load_from_memory(reinterpret_cast(bufferPtr), bufferLength, &width, &height, &channels, 4), + stbi_load_from_memory(reinterpret_cast(bufferPtr), bufferLength, &width, &height, &channels, 1), stbi_image_free); - if (buffer == nullptr) { - return {"", "", "Error loading image"}; - } + if (buffer == nullptr) + return {{"", "", "Error loading image"}}; + + return readBarcodes({buffer.get(), width, height, ImageFormat::Lum}, tryHarder, format, maxSymbols); +} + +ReadResult readBarcodeFromImage(int bufferPtr, int bufferLength, bool tryHarder, std::string format) +{ + return FirstOrDefault(readBarcodesFromImage(bufferPtr, bufferLength, tryHarder, format, 1)); +} - return readBarcodeFromImageView({buffer.get(), width, height, ImageFormat::RGBX}, tryHarder, format); +std::vector readBarcodesFromPixmap(int bufferPtr, int imgWidth, int imgHeight, bool tryHarder, std::string format, int maxSymbols) +{ + return readBarcodes({reinterpret_cast(bufferPtr), imgWidth, imgHeight, ImageFormat::RGBX}, tryHarder, format, maxSymbols); } ReadResult readBarcodeFromPixmap(int bufferPtr, int imgWidth, int imgHeight, bool tryHarder, std::string format) { - using namespace ZXing; - return readBarcodeFromImageView({reinterpret_cast(bufferPtr), imgWidth, imgHeight, ImageFormat::RGBX}, tryHarder, format); + return FirstOrDefault(readBarcodesFromPixmap(bufferPtr, imgWidth, imgHeight, tryHarder, format, 1)); } EMSCRIPTEN_BINDINGS(BarcodeReader) @@ -74,24 +87,25 @@ EMSCRIPTEN_BINDINGS(BarcodeReader) using namespace emscripten; value_object("ReadResult") - .field("format", &ReadResult::format) - .field("text", &ReadResult::text) - .field("error", &ReadResult::error) - .field("position", &ReadResult::position) - ; + .field("format", &ReadResult::format) + .field("text", &ReadResult::text) + .field("error", &ReadResult::error) + .field("position", &ReadResult::position) + .field("symbologyIdentifier", &ReadResult::symbologyIdentifier); - value_object("Point") - .field("x", &ZXing::PointI::x) - .field("y", &ZXing::PointI::y) - ; + value_object("Point").field("x", &ZXing::PointI::x).field("y", &ZXing::PointI::y); value_object("Position") - .field("topLeft", emscripten::index<0>()) - .field("topRight", emscripten::index<1>()) - .field("bottomRight", emscripten::index<2>()) - .field("bottomLeft", emscripten::index<3>()) - ; + .field("topLeft", emscripten::index<0>()) + .field("topRight", emscripten::index<1>()) + .field("bottomRight", emscripten::index<2>()) + .field("bottomLeft", emscripten::index<3>()); + + register_vector("vector"); function("readBarcodeFromImage", &readBarcodeFromImage); function("readBarcodeFromPixmap", &readBarcodeFromPixmap); -} + + function("readBarcodesFromImage", &readBarcodesFromImage); + function("readBarcodesFromPixmap", &readBarcodesFromPixmap); +}; diff --git a/wrappers/wasm/CMakeLists.txt b/wrappers/wasm/CMakeLists.txt index 75453a2bc7..0bc692da05 100644 --- a/wrappers/wasm/CMakeLists.txt +++ b/wrappers/wasm/CMakeLists.txt @@ -5,7 +5,7 @@ project (ZXingWasm) include (${CMAKE_CURRENT_SOURCE_DIR}/../../zxing.cmake) zxing_add_package_stb() -set (CMAKE_CXX_STANDARD 17) +set (CMAKE_CXX_STANDARD 20) option (BUILD_WRITERS "Build with writer support (encoders)" ON) option (BUILD_READERS "Build with reader support (decoders)" ON) @@ -13,14 +13,14 @@ option (BUILD_READERS "Build with reader support (decoders)" ON) set(BUILD_EMSCRIPTEN_ENVIRONMENT "web" CACHE STRING "Optimize build for given emscripten runtime environment (web/node/shell/worker)") if (NOT CMAKE_BUILD_TYPE) - set (CMAKE_BUILD_TYPE "MinSizeRel" CACHE STRING "Choose the type of build." FORCE) + set (CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) endif() add_definitions ("-s DISABLE_EXCEPTION_CATCHING=0") add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR}/../../core ${CMAKE_BINARY_DIR}/ZXing) -set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --bind -s ENVIRONMENT=${BUILD_EMSCRIPTEN_ENVIRONMENT} -s DISABLE_EXCEPTION_CATCHING=0 -s FILESYSTEM=0 -s MODULARIZE=1 -s EXPORT_NAME=ZXing -s ALLOW_MEMORY_GROWTH=1") +set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --bind -s ENVIRONMENT=${BUILD_EMSCRIPTEN_ENVIRONMENT} -s DISABLE_EXCEPTION_CATCHING=0 -s FILESYSTEM=0 -s MODULARIZE=1 -s EXPORT_NAME=ZXing -s EXPORTED_FUNCTIONS=\"['_malloc', '_free']\" -s ALLOW_MEMORY_GROWTH=1") if (BUILD_READERS AND BUILD_WRITERS) add_executable (zxing BarcodeReader.cpp BarcodeWriter.cpp) @@ -28,11 +28,14 @@ if (BUILD_READERS AND BUILD_WRITERS) endif() if (BUILD_READERS) + configure_file(demo_cam_reader.html demo_cam_reader.html COPYONLY) + configure_file(demo_reader.html demo_reader.html COPYONLY) add_executable (zxing_reader BarcodeReader.cpp) target_link_libraries (zxing_reader ZXing::ZXing stb::stb) endif() if (BUILD_WRITERS) + configure_file(demo_writer.html demo_writer.html COPYONLY) add_executable (zxing_writer BarcodeWriter.cpp ) target_link_libraries (zxing_writer ZXing::ZXing stb::stb) endif() diff --git a/wrappers/wasm/README.md b/wrappers/wasm/README.md index 52c9b69d49..b5dd82d3a8 100644 --- a/wrappers/wasm/README.md +++ b/wrappers/wasm/README.md @@ -5,7 +5,24 @@ 1. [Install Emscripten](https://kripken.github.io/emscripten-site/docs/getting_started/) if not done already. 2. In an empty build folder, invoke `emcmake cmake `. 3. Invoke `cmake --build .` to create `zxing.js` and `zxing.wasm` (and `_reader`/`_writer` versions). -4. To see how to include these into a working HTML page, have a look at the [reader](demo_reader.html) and [writer](demo_writer.html) demos. +4. To see how to include these into a working HTML page, have a look at the [reader](demo_reader.html), [writer](demo_writer.html) and [cam reader](demo_cam_reader.html) demos. 5. To quickly test your build, copy those demo files into your build directory and run e.g. `emrun --serve_after_close demo_reader.html`. -You can also download the latest build output from the continuous integration system from the [Actions](https://github.com/zxing-cpp/zxing-cpp/actions) tab. Look for 'wasm-artifacts'. Also check out the [live demos](https://zxing-cpp.github.io/zxing-cpp/). +You can also download the latest build output from the continuous integration system from the [Actions](https://github.com/zxing-cpp/zxing-cpp/actions) tab. Look for 'wasm-artifacts'. Also check out the [live demos](https://github.com/zxing-cpp/zxing-cpp#web-demos). + +## Performance + +It turns out that compiling the library with the `-Os` (`MinSizeRel`) flag causes a noticible performance penalty. Here are some measurements from the demo_cam_reader (performed on Chromium 109 running on a Core i9-9980HK): + +| | `-Os` | `-Os -flto` | `-O3` | `-O3 -flto` | _Build system_ | +|---------|-------|-------------|--------|-------------|-| +| size | 790kB | 950kb | 940kb | 1000kB | _All_ | +| runtime | 320ms | 30ms | 8ms | 8ms | C++17, emsdk 3.1.9 | +| runtime | 13ms | 30ms | 8ms | 8ms | C++17, emsdk 3.1.31 | +| runtime | 46ms | 46ms | 11ms | 11ms | C++20, emsdk 3.1.31 | + +Conclusions: + * saving 15% of download size for the price of a 2x-4x slowdown seems like a hard sale (let alone the 40x one)... + * building in C++-20 mode brings position independent DataMatrix detection but costs 35% more time + * link time optimization (`-flto`) is not worth it and potentially even counter productive + diff --git a/wrappers/wasm/base64ArrayBuffer.js b/wrappers/wasm/base64ArrayBuffer.js deleted file mode 100644 index fde0dd04cb..0000000000 --- a/wrappers/wasm/base64ArrayBuffer.js +++ /dev/null @@ -1,65 +0,0 @@ -// From: https://gist.github.com/jonleighton/958841 - -/* -MIT LICENSE - -Copyright 2011 Jon Leighton - -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. -*/ - -function base64ArrayBuffer(arrayBuffer) { - var base64 = '' - var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - - var bytes = new Uint8Array(arrayBuffer) - var byteLength = bytes.byteLength - var byteRemainder = byteLength % 3 - var mainLength = byteLength - byteRemainder - - var a, b, c, d - var chunk - - // Main loop deals with bytes in chunks of 3 - for (var i = 0; i < mainLength; i = i + 3) { - // Combine the three bytes into a single integer - chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2] - - // Use bitmasks to extract 6-bit segments from the triplet - a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18 - b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12 - c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6 - d = chunk & 63 // 63 = 2^6 - 1 - - // Convert the raw binary segments to the appropriate ASCII encoding - base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d] - } - - // Deal with the remaining bytes and padding - if (byteRemainder == 1) { - chunk = bytes[mainLength] - - a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2 - - // Set the 4 least significant bits to zero - b = (chunk & 3) << 4 // 3 = 2^2 - 1 - - base64 += encodings[a] + encodings[b] + '==' - } else if (byteRemainder == 2) { - chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1] - - a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10 - b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4 - - // Set the 2 least significant bits to zero - c = (chunk & 15) << 2 // 15 = 2^4 - 1 - - base64 += encodings[a] + encodings[b] + encodings[c] + '=' - } - - return base64 -} diff --git a/wrappers/wasm/demo_cam_reader.html b/wrappers/wasm/demo_cam_reader.html new file mode 100644 index 0000000000..1791d8cf66 --- /dev/null +++ b/wrappers/wasm/demo_cam_reader.html @@ -0,0 +1,149 @@ + + + + + zxing-cpp/wasm live demo + + + + + +

zxing-cpp/wasm live demo

+

+ This is a simple demo of the wasm wrapper of zxing-cpp + scanning for barcodes in a live video stream. +

+ + Camera: + +    + + Format: + +    + + Mode: + +

+ + +

+ +
+ + + + + diff --git a/wrappers/wasm/demo_reader.html b/wrappers/wasm/demo_reader.html index 51461d496c..cbcb7cfcca 100644 --- a/wrappers/wasm/demo_reader.html +++ b/wrappers/wasm/demo_reader.html @@ -1,154 +1,101 @@ + - ZXing in Javascript demo + zxing-cpp/wasm reader demo + - - - -

Read barcodes

+ +

zxing-cpp/wasm reader demo

- This is a simple demo of WebAssembly build (using Emcripten) of zxing-cpp + This is a simple demo of the wasm wrapper of zxing-cpp + scanning for barcodes in image files.

Scan Format: - @@ -156,21 +103,32 @@

Read barcodes

+ + + + + -
-
- Drag your image here... -
- Or -
-
+ +    + + Image File: + +

+ + + +

+ +
    +
    diff --git a/wrappers/wasm/demo_writer.html b/wrappers/wasm/demo_writer.html index 9c3aad01c9..16b90d411f 100644 --- a/wrappers/wasm/demo_writer.html +++ b/wrappers/wasm/demo_writer.html @@ -1,11 +1,12 @@ + - ZXing in Javascript demo + zxing-cpp/wasm writer demo + - @@ -65,9 +66,10 @@ -

    Generate barcodes

    +

    zxing-cpp/wasm writer demo

    - This is a simple demo of WebAssembly build (using Emcripten) of zxing-cpp + This is a simple demo of the wasm wrapper of zxing-cpp + for generating barcodes.

    diff --git a/wrappers/winrt/nuget/ZXingWinRT.nuspec b/wrappers/winrt/nuget/ZXingWinRT.nuspec index ebf397c09b..f6b61e8bf6 100644 --- a/wrappers/winrt/nuget/ZXingWinRT.nuspec +++ b/wrappers/winrt/nuget/ZXingWinRT.nuspec @@ -6,8 +6,8 @@ ZXingWinRT Nu-book Inc. Nu-book Inc. - https://github.com/nu-book/zxing-cpp/blob/master/LICENSE - https://github.com/nu-book/zxing-cpp + https://github.com/zxing-cpp/zxing-cpp/blob/master/LICENSE + https://github.com/zxing-cpp/zxing-cpp true C++ port of ZXing barcode scanner library Bug fixes and improvements for many readers and decoders pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy