diff --git a/.editorconfig b/.editorconfig index c643beb33d..f8db50e18e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,9 +5,10 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 -[*.{cpp,c,h,html,py}] +[*.{cpp,c,h,html,py,kt,cs,qlm}] indent_style = tab indent_size = 4 +max_line_length = 135 [CMakeLists.txt] indent_style = space diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c26a4243f4..4986ba1f08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,140 +17,208 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-latest, ubuntu-latest, macOS-latest] + os: [windows-latest, ubuntu-latest, macos-13] # don't cancel all jobs just because one of them failed fail-fast: false # 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@v3 + - uses: actions/checkout@v4 + - name: Setup Python 3 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - working-directory: ${{runner.workspace}}/build - # 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 -DBUILD_C_API=ON + + - name: Configure + run: > + cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_SHARED_LIBS=ON + -DZXING_READERS=ON -DZXING_WRITERS=ON + -DZXING_BLACKBOX_TESTS=ON -DZXING_UNIT_TESTS=ON -DZXING_PYTHON_MODULE=ON -DZXING_C_API=ON + + - name: Build + run: cmake --build build -j8 --config ${{env.BUILD_TYPE}} + + # - name: Set PATH for Tests + # shell: bash # to make the $GITHUB_PATH update work + # if: runner.os == 'Windows' + # run: | + # echo "${GITHUB_WORKSPACE}/build/core/${BUILD_TYPE}" >> $GITHUB_PATH + # echo "${GITHUB_WORKSPACE}/build/lib/${BUILD_TYPE}" >> $GITHUB_PATH + + - name: Test + if: runner.os != 'Windows' # need to disable ctest on Windows when build as shared library for now + run: ctest --test-dir build -V -C ${{env.BUILD_TYPE}} + + - name: Install + run: | + cmake -E make_directory install + cmake --install build --config ${{env.BUILD_TYPE}} --prefix ${{github.workspace}}/install + + - uses: actions/upload-artifact@v4 + with: + name: ${{matrix.os}}-artifacts + path: install + + build-experimental: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-13] + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Configure + run: > + cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_SHARED_LIBS=ON + -DZXING_READERS=ON -DZXING_WRITERS=NEW -DZXING_EXPERIMENTAL_API=ON + -DZXING_BLACKBOX_TESTS=ON -DZXING_UNIT_TESTS=OFF -DZXING_PYTHON_MODULE=OFF -DZXING_C_API=ON - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . -j8 --config $BUILD_TYPE + run: cmake --build build -j8 --config ${{env.BUILD_TYPE}} - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -V -C $BUILD_TYPE + if: runner.os != 'Windows' # need to disable ctest on Windows when build as shared library for now + run: ctest --test-dir build -V -C ${{env.BUILD_TYPE}} + + - name: Install + run: | + cmake -E make_directory install + cmake --install build --config ${{env.BUILD_TYPE}} --prefix ${{github.workspace}}/install + + - uses: actions/upload-artifact@v4 + with: + name: ${{matrix.os}}-exp-artifacts + path: install build-ubuntu-sanitize: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - with: - ref: ${{github.ref}} - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - uses: actions/checkout@v4 - 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 -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" + run: > + cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DZXING_READERS=ON -DZXING_WRITERS=ON + -DZXING_BLACKBOX_TESTS=ON -DZXING_UNIT_TESTS=ON -DZXING_PYTHON_MODULE=OFF -DZXING_C_API=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" + -DCMAKE_C_COMPILER=clang + -DCMAKE_C_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 + run: cmake --build build -j8 - name: Test - working-directory: ${{runner.workspace}}/build - run: ctest -V + run: ctest -V --test-dir build build-ios: - runs-on: macos-latest + runs-on: macos-13 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 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 + submodules: true - - 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 swift package + run: swift build - name: Build the demo app - shell: sh - working-directory: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios/demo + working-directory: wrappers/ios/demo run: xcodebuild build -scheme demo -sdk "iphonesimulator" + - name: Validate the Pod + run: pod lib lint --allow-warnings + build-android: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' - name: Build the lib/app working-directory: wrappers/android - run: ./gradlew assembleDebug # build only the debug version of the aar (faster build) + run: ./gradlew assembleRelease - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: android-artifacts - path: "wrappers/android/zxingcpp/build/outputs/aar/zxingcpp-debug.aar" + path: "wrappers/android/zxingcpp/build/outputs/aar/zxingcpp-release.aar" - build-wasm: + - name: Publish Library Snapshot + if: github.repository == 'zxing-cpp/zxing-cpp' && github.event_name != 'pull_request' + working-directory: wrappers/android + env: + ORG_GRADLE_PROJECT_publishSnapshot: true + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} + ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }} + ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }} + run: ./gradlew publishReleasePublicationToSonatypeRepository + + build-kn: runs-on: ubuntu-latest + defaults: + run: + working-directory: wrappers/kn steps: - - uses: actions/checkout@v3 - - uses: mymindstorm/setup-emsdk@v12 + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true - - name: Configure - run: emcmake cmake -Swrappers/wasm -Bbuild + - name: Checkout toolchain initializer repository + uses: actions/checkout@v4 + with: + repository: ISNing/kn-toolchain-initializer + path: wrappers/kn/.kn-toolchain-initializer - - name: Build - run: cmake --build build -j4 + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin -# - name: Test -# run: node build/EmGlueTests.js + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 - - uses: actions/upload-artifact@v3 - with: - name: wasm-artifacts - path: | - build/zxing* - build/demo* + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v2 + + - name: Initialize Kotlin/Native toolchain + working-directory: wrappers/kn/.kn-toolchain-initializer + run: ./gradlew build -DkotlinVersion=1.9.22 + + - name: Run test for linuxX64 target + run: | + echo -e "konan.dir=$HOME/.konan/kotlin-native-prebuilt-linux-x86_64-1.9.22" > local.properties + ./gradlew linuxX64Test build-python: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.9', '3.10', '3.11'] - os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.12'] + os: [ubuntu-latest, macos-13, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + submodules: true - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies - working-directory: wrappers/python run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip setuptools python -m pip install numpy pillow - name: Build module @@ -160,3 +228,83 @@ jobs: - name: Test module working-directory: wrappers/python run: python -m unittest -v + + - uses: actions/upload-artifact@v4 + with: + name: ${{matrix.os}}-python-artifacts + path: wrappers/python/zxingcpp.* + + build-rust: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-13, windows-latest] + defaults: + run: + working-directory: wrappers/rust + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Lint + run: | + cargo fmt --check + cargo clippy -- -Dwarnings + + - name: Build + run: cargo build --release --verbose --all-features --examples + + - name: Test + run: cargo test --release --all-features + + # disable for now, as this started to cause random failures like "error: attempt to get status of nonexistent file 'zint/backend/rss.c'" + # this is apparently related with the used symlinks + # - name: Package + # # --allow-dirty is required on the windows build (but not the ubuntu build?!) + # run: cargo package --verbose --allow-dirty --all-features + + build-wasm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mymindstorm/setup-emsdk@v14 + + - name: Configure + run: emcmake cmake -Swrappers/wasm -Bbuild + + - name: Build + run: cmake --build build -j4 + +# - name: Test +# run: node build/EmGlueTests.js + + - uses: actions/upload-artifact@v4 + with: + name: wasm-artifacts + path: | + build/zxing* + build/demo* + + build-winrt: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Configure + shell: cmd # powershell messes up the arguments containing a '.' ?!? + run: > + cmake -S wrappers/winrt -B build -A ARM64 + -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release + -DBUILD_WINRT_LIB=ON -DZXING_EXAMPLES=OFF -DZXING_BLACKBOX_TESTS=OFF -DZXING_C_API=OFF + -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0 + + - name: Build + run: cmake --build build -j8 --config Release + + - uses: actions/upload-artifact@v4 + with: + name: winrt-ARM64-artifacts + path: build/dist diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3937ed1f5f..12934f156b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,23 +25,23 @@ on: workflow_dispatch jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} strategy: fail-fast: false matrix: - language: [ 'cpp' ] + language: [ 'c-cpp', 'python' ] # , 'java-kotlin', 'swift' currently fail with autobuild # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 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@v2 + uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -66,4 +66,6 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index df51ce0721..acaf23ff26 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -29,10 +29,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup EMSDK - uses: mymindstorm/setup-emsdk@v12 + uses: mymindstorm/setup-emsdk@v14 - name: Configure run: emcmake cmake -Swrappers/wasm -Bbuild @@ -47,10 +47,10 @@ jobs: mv build/zxing_* build/*.html pages - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: 'pages' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/msvc-analysis.yml b/.github/workflows/msvc-analysis.yml index 269f231f7e..4659e4efcf 100644 --- a/.github/workflows/msvc-analysis.yml +++ b/.github/workflows/msvc-analysis.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure CMake run: cmake -B ${{ env.build }} -DCMAKE_BUILD_TYPE=${{ env.config }} @@ -55,17 +55,18 @@ jobs: buildConfiguration: ${{ env.config }} # Ruleset file that will determine what checks will be run ruleset: NativeRecommendedRules.ruleset - additionalArgs: /wd26451 # Suppress C26451, apparently bogous in VS2019 + # additionalArgs: /wd26451 # Suppress C26451, apparently bogous in VS2019 + # still fails, see https://github.com/microsoft/msvc-code-analysis-action/issues/31 # Upload SARIF file to GitHub Code Scanning Alerts - name: Upload SARIF to GitHub - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 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 + # uses: actions/upload-artifact@v4 # with: # name: sarif-file # path: ${{ steps.run-analysis.outputs.sarif }} diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml new file mode 100644 index 0000000000..24ab171b60 --- /dev/null +++ b/.github/workflows/publish-android.yml @@ -0,0 +1,40 @@ +name: publish-android + +on: +# release: +# types: [published] + + workflow_dispatch: + inputs: + publish: + description: 'Publish package (y/n)' + default: 'n' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' +# working-directory: wrappers/android +# run: ./gradlew versionDisplay + + - name: Build Library + working-directory: wrappers/android + run: ./gradlew assembleRelease + + - name: Publish Library + if: ${{ github.event.inputs.publish == 'y' }} + working-directory: wrappers/android + env: + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} + ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }} + ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }} + run: ./gradlew publishReleasePublicationToSonatypeRepository + + +# To actually publish the the blob, login to https://s01.oss.sonatype.org/#stagingRepositories, then press "Close", then "Release". diff --git a/.github/workflows/publish-kn.yml b/.github/workflows/publish-kn.yml new file mode 100644 index 0000000000..436b32678a --- /dev/null +++ b/.github/workflows/publish-kn.yml @@ -0,0 +1,77 @@ +name: publish-kn + +on: + workflow_dispatch: + inputs: + publish: + description: 'Publish package (y/n)' + default: 'n' + +jobs: + publish: + name: Library Publish + + runs-on: macos-13 # at least macos-13 is required to enable c++20 support + + defaults: + run: + working-directory: ./wrappers/kn + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + + - name: Checkout toolchain initializer repository + uses: actions/checkout@v4 + with: + repository: ISNing/kn-toolchain-initializer + path: wrappers/kn/.kn-toolchain-initializer + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v2 + + - name: Initialize Kotlin/Native toolchain + working-directory: wrappers/kn/.kn-toolchain-initializer + run: ./gradlew build -DkotlinVersion=1.9.22 + + - name: Export Toolchain properties + run: | + echo -e "konan.dir=$HOME/.konan/kotlin-native-prebuilt-macos-x86_64-1.9.22" > local.properties + + - name: Build Library + run: | + export PATH="$PATH:$(brew --prefix llvm@15)/bin" + ./gradlew assemble + + - name: Run All Library Tests + run: | + export PATH="$PATH:$(brew --prefix llvm@15)/bin" + ./gradlew allTests + + - name: Upload Library Test Reports + uses: actions/upload-artifact@v4 + with: + name: allTests-reports + path: wrappers/kn/build/reports/tests/allTests + + - name: Publish Library + if: ${{ github.event.inputs.publish == 'y' }} + env: + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} + ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }} + ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }} + run: | + export PATH="$PATH:$(brew --prefix llvm@15)/bin" + ./gradlew publishAllPublicationsToSonatypeRepository diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml new file mode 100644 index 0000000000..d00b3f141b --- /dev/null +++ b/.github/workflows/publish-python.yml @@ -0,0 +1,113 @@ +name: publish-python + +on: + release: + types: [published] + + workflow_dispatch: + inputs: + publish: + description: 'Publish package (y/n)' + default: 'n' +# push: +# branches: [master] +# tags: ["v*.*.*"] +# pull_request: +# branches: [master] + +jobs: + build-wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-13, windows-latest] # at least macos-13 is required to enable c++20 support + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install cibuildwheel + run: python3 -m pip install cibuildwheel==2.22.0 + + - name: Build wheels + run: python3 -m cibuildwheel --output-dir wheelhouse wrappers/python + env: + # TODO: setup a "BEFORE" cmake build and link the python module to the prebuild libZXing.a + # see https://github.com/YannickJadoul/Parselmouth/blob/523c117aa780184345121f6ff8315670bc7d4d94/.github/workflows/wheels.yml#L120 + CIBW_BUILD: cp39-* cp310-* cp311-* cp312-* cp313-* + CIBW_SKIP: "*musllinux*" + # the default maylinux2014 image does not contain a c++20 compiler, see https://github.com/pypa/manylinux + CIBW_MANYLINUX_X86_64_IMAGE: quay.io/pypa/manylinux_2_28_x86_64 + # CIBW_ARCHS_MACOS: "x86_64 arm64" + CIBW_ARCHS_MACOS: universal2 + CIBW_ENVIRONMENT_MACOS: CMAKE_OSX_ARCHITECTURES="arm64;x86_64" + # the default macOS target version is 10.9. it might be required to increase that to support c++20 or later features. + # MACOSX_DEPLOYMENT_TARGET: "10.15" + CIBW_BUILD_VERBOSITY: 1 + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }} + path: ./wheelhouse/*.whl + + build-sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Install dependencies + working-directory: wrappers/python + run: python -m pip install --upgrade pip setuptools + + - name: Build sdist + working-directory: wrappers/python + run: python3 setup.py sdist + + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: wrappers/python/dist/*.tar.gz + + upload-pypi: + name: Upload to PyPI + needs: [build-wheels, build-sdist] + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/zxing-cpp + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + # only run if the commit is tagged... +# 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@v4 + with: + pattern: cibw-* + path: dist + merge-multiple: true + + - name: List wheels + run: ls dist + + - uses: pypa/gh-action-pypi-publish@release/v1 +# with: +# repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/build-winrt.yml b/.github/workflows/publish-winrt.yml similarity index 81% rename from .github/workflows/build-winrt.yml rename to .github/workflows/publish-winrt.yml index d4d8832d0e..880ad39a69 100644 --- a/.github/workflows/build-winrt.yml +++ b/.github/workflows/publish-winrt.yml @@ -1,4 +1,4 @@ -name: build-winrt +name: publish-winrt on: release: types: [published] @@ -17,10 +17,10 @@ jobs: runs-on: windows-latest strategy: matrix: - target: [Win32, x64, ARM, ARM64] + target: [Win32, x64, ARM64] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Create build environment shell: cmd @@ -32,14 +32,14 @@ jobs: - name: Configure CMake shell: cmd working-directory: ${{runner.workspace}}/build - run: cmake ${{github.workspace}}/wrappers/winrt -A ${{matrix.target}} -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release -DBUILD_WINRT_LIB=ON -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0 + run: cmake ${{github.workspace}}/wrappers/winrt -A ${{matrix.target}} -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release -DBUILD_WINRT_LIB=ON -DZXING_EXAMPLES=OFF -DZXING_BLACKBOX_TESTS=OFF -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0 - name: Build shell: cmd working-directory: ${{runner.workspace}}/build run: cmake --build . -j8 --config Release - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: winrt-${{matrix.target}}-artifacts path: ${{runner.workspace}}/build/dist @@ -49,16 +49,13 @@ jobs: runs-on: windows-latest if: ${{ github.event_name == 'release' || github.event.inputs.publish == 'y' }} steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: winrt-Win32-artifacts - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: winrt-x64-artifacts - - uses: actions/download-artifact@v3 - with: - name: winrt-ARM-artifacts - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: winrt-ARM64-artifacts @@ -78,7 +75,7 @@ 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@v3 + - uses: actions/upload-artifact@v4 with: name: nuget-package path: huycn.zxingcpp.winrt.nupkg diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml deleted file mode 100644 index 607d6b4ccb..0000000000 --- a/.github/workflows/python-build.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: build-python-dist - -on: - release: - types: [published] - - workflow_dispatch: - inputs: - publish: - description: 'Publish package (y/n)' - default: 'n' -# push: -# branches: [master] -# tags: ["v*.*.*"] -# pull_request: -# branches: [master] - -jobs: - build-wheels: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - - steps: - - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - - name: Install cibuildwheel - run: python3 -m pip install cibuildwheel==2.12.1 - - - name: Build wheels - 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@v3 - with: - path: ./wheelhouse/*.whl - - build-sdist: - name: Build source distribution - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - - name: Build sdist - working-directory: wrappers/python - run: python3 setup.py sdist - - - uses: actions/upload-artifact@v3 - with: - path: wrappers/python/dist/*.tar.gz - - upload-pypi: - name: Deploy - needs: [build-wheels, build-sdist] - runs-on: ubuntu-latest - # only run if the commit is tagged... -# 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@v3 - with: - name: artifact - path: dist - - - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_TOKEN }} - # To test: repository_url: https://test.pypi.org/legacy/ diff --git a/.gitignore b/.gitignore index 9992cb687a..f24ac8e73d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,26 @@ -CMakeLists.txt.user *.o *.so *.lib *.d *.a compile_commands.json + +# QtCreator +CMakeLists.txt.user + +# Vim +*.swp + +# XCode/Swift +.swiftpm +.build + +# JetBrain (AndroidStudio, clion) +.idea + +# Visual Studio +/.vs +/CMakeUserPresets.json + +# Visual Studio Code +/.vscode diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..23eb394603 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "zint"] + path = zint + url = https://github.com/zint/zint.git + shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt index 87785add27..b48b2d09e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,17 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.16) project(ZXing) -option (BUILD_WRITERS "Build with writer support (encoders)" ON) -option (BUILD_READERS "Build with reader support (decoders)" ON) -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)") +option (ZXING_READERS "Build with reader support (decoders)" ON) +set (ZXING_WRITERS "ON" CACHE STRING "Build with old and/or new writer (encoder) backend (OFF/ON/OLD/NEW/BOTH)") +option (ZXING_USE_BUNDLED_ZINT "Use the bundled libzint for barcode creation/writing" ON) +option (ZXING_C_API "Build the C-API" OFF) +option (ZXING_EXPERIMENTAL_API "Build with experimental API" OFF) +option (ZXING_EXAMPLES "Build the example barcode reader/writer applications" ON) +option (ZXING_BLACKBOX_TESTS "Build the black box reader/writer tests" OFF) +option (ZXING_UNIT_TESTS "Build the unit tests (don't enable for production builds)" OFF) +option (ZXING_PYTHON_MODULE "Build the python module" OFF) +set (ZXING_DEPENDENCIES "AUTO" CACHE STRING "Fetch from github or use locally installed (AUTO/GITHUB/LOCAL)") if (WIN32) option (BUILD_SHARED_LIBS "Build and link as shared library" OFF) @@ -20,8 +21,8 @@ endif() if (MSVC) add_definitions (-DUNICODE -D_UNICODE -D_CRT_SECURE_NO_WARNINGS) - option (LINK_CPP_STATICALLY "MSVC only, link standard library statically (/MT and /MTd)" OFF) - if (LINK_CPP_STATICALLY) + option (ZXING_LINK_CPP_STATICALLY "MSVC only, link standard library statically (/MT and /MTd)" OFF) + if (LINK_CPP_STATICALLY OR ZXING_LINK_CPP_STATICALLY) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() endif() @@ -33,31 +34,49 @@ if (NOT CMAKE_BUILD_TYPE) set_property (CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() -if (BUILD_SHARED_LIBS) - set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +# provide backward compatibility for deprecated BUILD_... options +if (DEFINED BUILD_READERS) + set (ZXING_READERS ${BUILD_READERS}) endif() - -if (NOT CMAKE_CXX_STANDARD) - set (CMAKE_CXX_STANDARD 17) +if (DEFINED BUILD_WRITERS) + set (ZXING_WRITERS ${BUILD_WRITERS}) endif() -if (NOT CMAKE_CXX_EXTENSIONS) - set (CMAKE_CXX_EXTENSIONS OFF) +if (DEFINED BUILD_EXAMPLES) + message (WARNING "zxing-cpp cmake options BUILD_... are deprecated, please switch to ZXING_... variant") + set (ZXING_EXAMPLES ${BUILD_EXAMPLES}) +endif() +if (DEFINED BUILD_PYTHON_MODULE) + message (WARNING "zxing-cpp cmake options BUILD_... are deprecated, please switch to ZXING_... variant") + set (ZXING_PYTHON_MODULE ${BUILD_PYTHON_MODULE}) +endif() +if (DEFINED BUILD_DEPENDENCIES) + message (WARNING "zxing-cpp cmake options BUILD_... are deprecated, please switch to ZXING_... variant") + set (ZXING_DEPENDENCIES ${BUILD_DEPENDENCIES}) endif() -if (NOT (BUILD_READERS OR BUILD_WRITERS)) - message(FATAL_ERROR "At least one of BUILD_READERS/BUILD_WRITERS must be enabled.") +if (NOT (ZXING_READERS OR ZXING_WRITERS)) + message(FATAL_ERROR "At least one of ZXING_READERS/ZXING_WRITERS must be enabled.") endif() -if (BUILD_UNIT_TESTS AND (NOT BUILD_WRITERS OR NOT BUILD_READERS)) - message("Note: To build with unit tests, the library will be build with READERS and WRITERS.") - set (BUILD_WRITERS ON) - set (BUILD_READERS ON) +set(ZXING_WRITERS_LIST OFF ON OLD NEW BOTH) +set_property(CACHE ZXING_WRITERS PROPERTY STRINGS ${ZXING_WRITERS_LIST}) +if(NOT ZXING_WRITERS IN_LIST ZXING_WRITERS_LIST) + message(FATAL_ERROR "ZXING_WRITERS must be one of ${ZXING_WRITERS_LIST}") endif() -set(BUILD_DEPENDENCIES_LIST AUTO GITHUB LOCAL) -set_property(CACHE BUILD_DEPENDENCIES PROPERTY STRINGS ${BUILD_DEPENDENCIES_LIST}) -if(NOT BUILD_DEPENDENCIES IN_LIST BUILD_DEPENDENCIES_LIST) - message(FATAL_ERROR "BUILD_DEPENDENCIES must be one of ${BUILD_DEPENDENCIES_LIST}") +set(ZXING_DEPENDENCIES_LIST AUTO GITHUB LOCAL) +set_property(CACHE ZXING_DEPENDENCIES PROPERTY STRINGS ${ZXING_DEPENDENCIES_LIST}) +if(NOT ZXING_DEPENDENCIES IN_LIST ZXING_DEPENDENCIES_LIST) + message(FATAL_ERROR "ZXING_DEPENDENCIES must be one of ${ZXING_DEPENDENCIES_LIST}") +endif() + +if (NOT DEFINED CMAKE_CXX_STANDARD) + set (CMAKE_CXX_STANDARD 20) + # Allow the fallback to earlier versions if the compiler does not support it. + set(CMAKE_CXX_STANDARD_REQUIRED OFF) +endif() +if (NOT DEFINED CMAKE_CXX_EXTENSIONS) + set (CMAKE_CXX_EXTENSIONS OFF) endif() add_subdirectory (core) @@ -66,18 +85,18 @@ enable_testing() include(zxing.cmake) -if (BUILD_EXAMPLES) +if (ZXING_EXAMPLES) add_subdirectory (example) endif() -if (BUILD_BLACKBOX_TESTS) +if (ZXING_BLACKBOX_TESTS) add_subdirectory (test/blackbox) endif() -if (BUILD_UNIT_TESTS) +if (ZXING_UNIT_TESTS) add_subdirectory (test/unit) endif() -if (BUILD_PYTHON_MODULE) +if (ZXING_PYTHON_MODULE) add_subdirectory (wrappers/python) endif() -if (BUILD_C_API) +if (ZXING_C_API) add_subdirectory (wrappers/c) endif() diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000000..5faf571d10 --- /dev/null +++ b/Package.swift @@ -0,0 +1,37 @@ +// swift-tools-version:5.7.1 +import PackageDescription + +let package = Package( + name: "ZXingCpp", + platforms: [ + .iOS(.v11) + ], + products: [ + .library( + name: "ZXingCpp", + targets: ["ZXingCpp"]) + ], + targets: [ + .target( + name: "ZXingCppCore", + path: "core/src", + exclude: ["libzint", "ZXingC.cpp", "ZXingCpp.cpp"], + publicHeadersPath: ".", + cxxSettings: [ + .define("ZXING_READERS") + ] + ), + .target( + name: "ZXingCpp", + dependencies: ["ZXingCppCore"], + path: "wrappers/ios/Sources/Wrapper", + publicHeadersPath: ".", + linkerSettings: [ + .linkedFramework("CoreGraphics"), + .linkedFramework("CoreImage"), + .linkedFramework("CoreVideo") + ] + ) + ], + cxxLanguageStandard: CXXLanguageStandard.gnucxx20 +) diff --git a/README.md b/README.md index f47b910504..9dc75d7f5b 100644 --- a/README.md +++ b/README.md @@ -4,61 +4,90 @@ ZXing-C++ ("zebra crossing") is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. -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. +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 runtime and detection 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-) +* [synedra](https://synedra.com/) Thanks a lot for your contribution! ## Features -* Written in pure C++17 (/C++20), no third-party dependencies (for the library itself) +* Written in pure C++20 (/C++17), no third-party dependencies (for the library itself) * Thread safe * Wrappers/Bindings for: * [Android](wrappers/android/README.md) + * [C](wrappers/c/README.md) * [iOS](wrappers/ios/README.md) + * [Kotlin/Native](wrappers/kn/README.md) + * [.NET](wrappers/dotnet/README.md) * [Python](wrappers/python/README.md) + * [Rust](wrappers/rust/README.md) * [WebAssembly](wrappers/wasm/README.md) * [WinRT](wrappers/winrt/README.md) * [Flutter](https://pub.dev/packages/flutter_zxing) (external project) ## Supported Formats -| Linear product | Linear industrial | Matrix | -|----------------|-------------------|--------------------| -| UPC-A | Code 39 | QR Code | -| UPC-E | Code 93 | Micro QR Code | -| EAN-8 | Code 128 | Aztec | -| EAN-13 | Codabar | DataMatrix | -| DataBar | DataBar Expanded | PDF417 | -| | ITF | MaxiCode (partial) | +| Linear product | Linear industrial | Matrix | +|-----------------|-------------------|--------------------| +| UPC-A | Code 39 | QR Code | +| UPC-E | Code 93 | Micro QR Code | +| EAN-8 | Code 128 | rMQR Code | +| EAN-13 | Codabar | Aztec | +| DataBar | DataBar Expanded | DataMatrix | +| DataBar Limited | DX Film Edge | PDF417 | +| | ITF | MaxiCode (partial) | [Note:] * DataBar used to be called RSS. - * DataBar, MaxiCode and Micro QR Code are not supported for writing. - * Building with C++20 (see [CMakeLists.txt](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/CMakeLists.txt#L45)) changes the behaviour of the library: it then supports multi-symbol and position independent detection for DataMatrix. This comes at a noticable performace cost. To enable this in the Android library, one needs to use at least NDK [version 25](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/wrappers/android/zxingcpp/build.gradle#L9). + * DataBar, DX Film Edge, MaxiCode, Micro QR Code and rMQR Code are not supported for writing (unless the library is configured `ZXING_WRITERS=NEW` and `ZXING_EXPERIMENTAL_API=ON`). + * Building with only C++17 (see [CMakeLists.txt](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/CMakeLists.txt#L45)) changes the behavior of the library: it then lacks support for DataBarLimited and multi-symbol and position independent detection for DataMatrix. ## Getting Started ### To read barcodes: -As an example, have a look at [`ZXingReader.cpp`](example/ZXingReader.cpp). 1. Load your image into memory (3rd-party library required). -2. Call `ReadBarcodes()` from [`ReadBarcode.h`](core/src/ReadBarcode.h), the simplest API to get a list of `Result` objects. +2. Call `ReadBarcodes()` from [`ReadBarcode.h`](core/src/ReadBarcode.h), the simplest API to get a list of `Barcode` objects. + +A very simple example looks like this: +```c++ +#include "ZXing/ReadBarcode.h" +#include + +int main(int argc, char** argv) +{ + int width, height; + unsigned char* data; + // load your image data from somewhere. ImageFormat::Lum assumes grey scale image data. + + auto image = ZXing::ImageView(data, width, height, ZXing::ImageFormat::Lum); + auto options = ZXing::ReaderOptions().setFormats(ZXing::BarcodeFormat::Any); + auto barcodes = ZXing::ReadBarcodes(image, options); + + for (const auto& b : barcodes) + std::cout << ZXing::ToString(b.format()) << ": " << b.text() << "\n"; + + return 0; +} +``` +To see the full capability of the API, have a look at [`ZXingReader.cpp`](example/ZXingReader.cpp). + +[Note: At least C++17 is required on the client side to use the API.] ### To write barcodes: -As an example, have a look at [`ZXingWriter.cpp`](example/ZXingWriter.cpp). 1. Create a [`MultiFormatWriter`](core/src/MultiFormatWriter.h) instance with the format you want to generate. Set encoding and margins if needed. 2. Call `encode()` with text content and the image size. This returns a [`BitMatrix`](core/src/BitMatrix.h) which is a binary image of the barcode where `true` == visual black and `false` == visual white. 3. Convert the bit matrix to your native image format. See also the `ToMatrix(BitMatrix&)` helper function. +As an example, have a look at [`ZXingWriter.cpp`](example/ZXingWriter.cpp). That file also contains example code showing the new `ZXING_EXPERIMENTAL_API` for writing barcodes. + ## 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) @@ -69,18 +98,18 @@ As an example, have a look at [`ZXingWriter.cpp`](example/ZXingWriter.cpp). ## 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.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. +1. Make sure [CMake](https://cmake.org) version 3.16 or newer is installed. The python module requires 3.18 or higher. +2. Make sure a sufficiently C++20 compliant compiler is installed (minimum VS 2019 16.10? / gcc 11 / clang 12?). +3. See the cmake `ZXING_...` options to enable the testing code, python wrapper, etc. ``` -git clone https://github.com/zxing-cpp/zxing-cpp.git --single-branch --depth 1 +git clone https://github.com/zxing-cpp/zxing-cpp.git --recursive --single-branch --depth 1 cmake -S zxing-cpp -B zxing-cpp.release -DCMAKE_BUILD_TYPE=Release -cmake --build zxing-cpp.release -j8 +cmake --build zxing-cpp.release -j8 --config Release ``` [Note: binary packages are available for/as [vcpkg](https://github.com/Microsoft/vcpkg/tree/master/ports/nu-book-zxing-cpp), [conan](https://github.com/conan-io/conan-center-index/tree/master/recipes/zxing-cpp), [mingw](https://github.com/msys2/MINGW-packages/tree/master/mingw-w64-zxing-cpp) and a bunch of -[linux distributions](https://repology.org/project/zxing-cpp-nu-book/versions).] +[linux distributions](https://repology.org/project/zxing-cpp/versions).] diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 24cebd93ab..8ded55ef5e 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,142 +1,202 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.16) -project (ZXing VERSION "2.1.0") +project (ZXing VERSION "2.3.0") set (ZXING_SONAME 3) # see https://github.com/zxing-cpp/zxing-cpp/issues/333 -include(../zxing.cmake) if (BUILD_SHARED_LIBS) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() -if (NOT DEFINED BUILD_WRITERS) - set (BUILD_WRITERS OFF) +if (DEFINED BUILD_READERS) + set (ZXING_READERS ${BUILD_READERS}) + message (WARNING "zxing-cpp cmake options BUILD_... are deprecated, please switch to ZXING_... variant") +endif() +if (DEFINED BUILD_WRITERS) + set (ZXING_WRITERS ${BUILD_WRITERS}) + message (WARNING "zxing-cpp cmake options BUILD_... are deprecated, please switch to ZXING_... variant") +endif() + +if (NOT DEFINED ZXING_WRITERS) + set (ZXING_WRITERS OFF) +endif() + +if (NOT DEFINED ZXING_READERS) + set (ZXING_READERS ON) endif() -if (NOT DEFINED BUILD_READERS) - set (BUILD_READERS ON) +set (ZXING_WRITERS_NEW OFF) +set (ZXING_WRITERS_OLD OFF) +if (ZXING_WRITERS MATCHES "OLD|ON") + set (ZXING_WRITERS ON) + set (ZXING_WRITERS_OLD ON) +elseif (ZXING_WRITERS MATCHES "NEW") + set (ZXING_WRITERS ON) + set (ZXING_WRITERS_NEW ON) +elseif (ZXING_WRITERS MATCHES "BOTH") + set (ZXING_WRITERS ON) + set (ZXING_WRITERS_NEW ON) + set (ZXING_WRITERS_OLD ON) endif() -set (ZXING_CORE_DEFINES) +if (BUILD_SHARED_LIBS) + set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +endif() + +set (ZXING_PUBLIC_FLAGS + $<$:-DZXING_EXPERIMENTAL_API> +) if (WINRT) - set (ZXING_CORE_DEFINES ${ZXING_CORE_DEFINES} + set (ZXING_PUBLIC_FLAGS ${ZXING_PUBLIC_FLAGS} -DWINRT ) endif() +if (MSVC) + set (ZXING_PUBLIC_FLAGS ${ZXING_PUBLIC_FLAGS} + $<$:/Zc:__cplusplus> + ) +endif() -set (ZXING_CORE_LOCAL_DEFINES - $<$:-DZXING_BUILD_READERS> - $<$:-DZXING_BUILD_WRITERS> - $<$:-DZXING_BUILD_FOR_TEST> - $<$:-DZXING_BUILD_EXPERIMENTAL_API> +set (ZXING_PRIVATE_FLAGS + $<$:-DZXING_USE_ZINT> + $<$:-DZXING_BUILD_FOR_TEST> ) if (MSVC) - set (ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} + set (ZXING_PRIVATE_FLAGS ${ZXING_PRIVATE_FLAGS} -D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS -DNOMINMAX - /Zc:__cplusplus + /utf-8 # see https://github.com/zxing-cpp/zxing-cpp/issues/757 ) else() - set (ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} + set (ZXING_PRIVATE_FLAGS ${ZXING_PRIVATE_FLAGS} -Wall -Wextra -Wno-missing-braces -Werror=undef -Werror=return-type) endif() +include (CheckCXXCompilerFlag) + +# This is needed for reproducible builds across different build directories. +# Without this, the usage of the __FILE__ macro leaves the build directory in +# the binary. When building the Python extension with build isolation enabled +# this would lead to random paths in the binary. +set(FILE_PREFIX_ARG "-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}/=") +check_cxx_compiler_flag("${FILE_PREFIX_ARG}" HAS_FILE_PREFIX_ARG) +if(HAS_FILE_PREFIX_ARG) + set(ZXING_PRIVATE_FLAGS ${ZXING_PRIVATE_FLAGS} "${FILE_PREFIX_ARG}") +endif() ################# Source files set (COMMON_FILES + src/Barcode.h + src/Barcode.cpp src/BarcodeFormat.h src/BarcodeFormat.cpp - src/BitArray.h - src/BitArray.cpp src/BitHacks.h src/BitMatrix.h src/BitMatrix.cpp - src/BitMatrixCursor.h src/BitMatrixIO.h src/BitMatrixIO.cpp src/ByteArray.h src/ByteMatrix.h src/CharacterSet.h src/CharacterSet.cpp - src/ConcentricFinder.h - src/ConcentricFinder.cpp - src/CustomData.h + src/Content.h + src/Content.cpp + src/DecoderResult.h + src/DetectorResult.h src/ECI.h src/ECI.cpp + src/Error.h + src/Error.cpp src/Flags.h - src/Generator.h - src/GenericGF.h - src/GenericGF.cpp - src/GenericGFPoly.h - src/GenericGFPoly.cpp src/GTIN.h src/GTIN.cpp - src/LogMatrix.h + src/ImageView.h + src/JSON.h + src/JSON.cpp src/Matrix.h - src/Pattern.h src/Point.h src/Quadrilateral.h src/Range.h - src/RegressionLine.h - src/Scope.h - src/TextUtfEncoding.h # [[deprecated]] - src/TextUtfEncoding.cpp # [[deprecated]] - src/TritMatrix.h + src/ReaderOptions.h + src/ReadBarcode.h + src/ReadBarcode.cpp src/Utf.h src/Utf.cpp + src/WriteBarcode.h + src/WriteBarcode.cpp + src/ZXingCpp.h + src/ZXingCpp.cpp src/ZXAlgorithms.h - src/ZXBigInteger.h - src/ZXBigInteger.cpp src/ZXConfig.h - src/ZXNullable.h src/ZXTestSupport.h + src/ZXVersion.h # [[deprecated]] + $<$:src/ZXingC.h> + $<$:src/ZXingC.cpp> ) -if (BUILD_READERS) +if (ZXING_READERS OR ZXING_WRITERS_OLD) + set (COMMON_FILES ${COMMON_FILES} + src/BitArray.h + src/BitArray.cpp + src/Generator.h + src/GenericGF.h + src/GenericGF.cpp + src/GenericGFPoly.h + src/GenericGFPoly.cpp + src/TextUtfEncoding.h # [[deprecated]] + src/TextUtfEncoding.cpp # [[deprecated]] + src/Scope.h + ) +endif() +if (ZXING_READERS) set (COMMON_FILES ${COMMON_FILES} src/BinaryBitmap.h src/BinaryBitmap.cpp + src/BitMatrixCursor.h src/BitSource.h src/BitSource.cpp - src/Content.h - src/Content.cpp + src/ConcentricFinder.h + src/ConcentricFinder.cpp src/DecodeHints.h - src/DecodeHints.cpp - src/DecoderResult.h - src/DetectorResult.h - src/Error.h + $<$:src/DecodeHints.cpp> # [[deprecated]] src/GlobalHistogramBinarizer.h src/GlobalHistogramBinarizer.cpp src/GridSampler.h src/GridSampler.cpp + src/LogMatrix.h src/HRI.h src/HRI.cpp src/HybridBinarizer.h src/HybridBinarizer.cpp - src/ImageView.h src/MultiFormatReader.h src/MultiFormatReader.cpp + src/Pattern.h src/PerspectiveTransform.h src/PerspectiveTransform.cpp src/Reader.h - src/ReadBarcode.h - src/ReadBarcode.cpp src/ReedSolomonDecoder.h src/ReedSolomonDecoder.cpp - src/Result.h - src/Result.cpp + src/RegressionLine.h + src/Result.h # [[deprecated]] src/ResultPoint.h src/ResultPoint.cpp src/StructuredAppend.h src/TextDecoder.h src/TextDecoder.cpp src/ThresholdBinarizer.h + src/TritMatrix.h # QRCode src/WhiteRectDetector.h src/WhiteRectDetector.cpp ) endif() -if (BUILD_WRITERS) + +if (ZXING_WRITERS) + set (COMMON_FILES ${COMMON_FILES} + ) +endif() + +if (ZXING_WRITERS_OLD) set (COMMON_FILES ${COMMON_FILES} src/ByteMatrix.h src/ReedSolomonEncoder.h @@ -150,43 +210,46 @@ endif() # define subset of public headers that get distributed with the binaries set (PUBLIC_HEADERS + src/Barcode.h src/BarcodeFormat.h src/BitHacks.h src/ByteArray.h src/CharacterSet.h + src/Content.h + src/Error.h src/Flags.h src/GTIN.h + src/ImageView.h + src/Point.h + src/Quadrilateral.h + src/Range.h # re-evaluate for 3.0 + src/ReadBarcode.h + src/ReaderOptions.h + src/StructuredAppend.h src/TextUtfEncoding.h # [[deprecated]] + src/ZXingCpp.h src/ZXAlgorithms.h - src/ZXConfig.h + src/ZXVersion.h # [[deprecated]] + $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/ZXingC.h> + $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/WriteBarcode.h> ) -if (BUILD_READERS) +if (ZXING_READERS) set (PUBLIC_HEADERS ${PUBLIC_HEADERS} - src/Content.h - src/DecodeHints.h - src/Error.h - src/ImageView.h - src/Point.h - src/Quadrilateral.h - src/ReadBarcode.h - src/Result.h - src/StructuredAppend.h + src/DecodeHints.h # [[deprecated]] + src/Result.h # [[deprecated]] ) endif() -if (BUILD_WRITERS) +if (ZXING_WRITERS_OLD) set (PUBLIC_HEADERS ${PUBLIC_HEADERS} src/BitMatrix.h src/BitMatrixIO.h src/Matrix.h src/MultiFormatWriter.h - src/Range.h ) endif() # end of public header set -set (AZTEC_FILES -) -if (BUILD_READERS) +if (ZXING_READERS) set (AZTEC_FILES ${AZTEC_FILES} src/aztec/AZDecoder.h src/aztec/AZDecoder.cpp @@ -197,7 +260,7 @@ if (BUILD_READERS) src/aztec/AZReader.cpp ) endif() -if (BUILD_WRITERS) +if (ZXING_WRITERS_OLD) set (AZTEC_FILES ${AZTEC_FILES} src/aztec/AZEncodingState.h src/aztec/AZEncoder.h @@ -212,13 +275,15 @@ if (BUILD_WRITERS) endif() -set (DATAMATRIX_FILES - src/datamatrix/DMBitLayout.h - src/datamatrix/DMBitLayout.cpp - src/datamatrix/DMVersion.h - src/datamatrix/DMVersion.cpp -) -if (BUILD_READERS) +if (ZXING_READERS OR ZXING_WRITERS_OLD) + set (DATAMATRIX_FILES + src/datamatrix/DMBitLayout.h + src/datamatrix/DMBitLayout.cpp + src/datamatrix/DMVersion.h + src/datamatrix/DMVersion.cpp + ) +endif() +if (ZXING_READERS) set (DATAMATRIX_FILES ${DATAMATRIX_FILES} src/datamatrix/DMDataBlock.h src/datamatrix/DMDataBlock.cpp @@ -230,7 +295,7 @@ if (BUILD_READERS) src/datamatrix/DMReader.cpp ) endif() -if (BUILD_WRITERS) +if (ZXING_WRITERS_OLD) set (DATAMATRIX_FILES ${DATAMATRIX_FILES} src/datamatrix/DMECEncoder.h src/datamatrix/DMECEncoder.cpp @@ -246,9 +311,7 @@ if (BUILD_WRITERS) endif() -set (MAXICODE_FILES -) -if (BUILD_READERS) +if (ZXING_READERS) set (MAXICODE_FILES ${MAXICODE_FILES} src/maxicode/MCBitMatrixParser.h src/maxicode/MCBitMatrixParser.cpp @@ -260,13 +323,15 @@ if (BUILD_READERS) endif() -set (ONED_FILES - src/oned/ODUPCEANCommon.h - src/oned/ODUPCEANCommon.cpp - src/oned/ODCode128Patterns.h - src/oned/ODCode128Patterns.cpp -) -if (BUILD_READERS) +if (ZXING_READERS OR ZXING_WRITERS_OLD) + set (ONED_FILES + src/oned/ODUPCEANCommon.h + src/oned/ODUPCEANCommon.cpp + src/oned/ODCode128Patterns.h + src/oned/ODCode128Patterns.cpp + ) +endif() +if (ZXING_READERS) set (ONED_FILES ${ONED_FILES} src/oned/ODCodabarReader.h src/oned/ODCodabarReader.cpp @@ -284,6 +349,10 @@ if (BUILD_READERS) src/oned/ODDataBarExpandedBitDecoder.cpp src/oned/ODDataBarExpandedReader.h src/oned/ODDataBarExpandedReader.cpp + src/oned/ODDataBarLimitedReader.h + src/oned/ODDataBarLimitedReader.cpp + src/oned/ODDXFilmEdgeReader.h + src/oned/ODDXFilmEdgeReader.cpp src/oned/ODITFReader.h src/oned/ODITFReader.cpp src/oned/ODMultiUPCEANReader.h @@ -291,10 +360,9 @@ if (BUILD_READERS) src/oned/ODReader.h src/oned/ODReader.cpp src/oned/ODRowReader.h - src/oned/ODRowReader.cpp ) endif() -if (BUILD_WRITERS) +if (ZXING_WRITERS_OLD) set (ONED_FILES ${ONED_FILES} src/oned/ODCodabarWriter.h src/oned/ODCodabarWriter.cpp @@ -320,9 +388,13 @@ if (BUILD_WRITERS) endif() -set (PDF417_FILES -) -if (BUILD_READERS) +if (ZXING_READERS OR ZXING_WRITERS_OLD) + set (PDF417_FILES + src/pdf417/ZXBigInteger.h + src/pdf417/ZXBigInteger.cpp + ) +endif() +if (ZXING_READERS) set (PDF417_FILES ${PDF417_FILES} src/pdf417/PDFBarcodeMetadata.h src/pdf417/PDFBarcodeValue.h @@ -332,9 +404,9 @@ if (BUILD_READERS) src/pdf417/PDFCodeword.h src/pdf417/PDFCodewordDecoder.h src/pdf417/PDFCodewordDecoder.cpp + src/pdf417/PDFCustomData.h src/pdf417/PDFDecoder.h src/pdf417/PDFDecoder.cpp - src/pdf417/PDFDecoderResultExtra.h src/pdf417/PDFDetectionResult.h src/pdf417/PDFDetectionResult.cpp src/pdf417/PDFDetectionResultColumn.h @@ -349,9 +421,10 @@ if (BUILD_READERS) src/pdf417/PDFReader.cpp src/pdf417/PDFScanningDecoder.h src/pdf417/PDFScanningDecoder.cpp + src/pdf417/ZXNullable.h ) endif() -if (BUILD_WRITERS) +if (ZXING_WRITERS_OLD) set (PDF417_FILES ${PDF417_FILES} src/pdf417/PDFCompaction.h src/pdf417/PDFEncoder.h @@ -364,15 +437,17 @@ if (BUILD_WRITERS) endif() -set (QRCODE_FILES - src/qrcode/QRCodecMode.h - src/qrcode/QRCodecMode.cpp - src/qrcode/QRErrorCorrectionLevel.h - src/qrcode/QRErrorCorrectionLevel.cpp - src/qrcode/QRVersion.h - src/qrcode/QRVersion.cpp -) -if (BUILD_READERS) +if (ZXING_READERS OR ZXING_WRITERS_OLD) + set (QRCODE_FILES + src/qrcode/QRCodecMode.h + src/qrcode/QRCodecMode.cpp + src/qrcode/QRErrorCorrectionLevel.h + src/qrcode/QRErrorCorrectionLevel.cpp + src/qrcode/QRVersion.h + src/qrcode/QRVersion.cpp + ) +endif() +if (ZXING_READERS) set (QRCODE_FILES ${QRCODE_FILES} src/qrcode/QRBitMatrixParser.h src/qrcode/QRBitMatrixParser.cpp @@ -390,7 +465,7 @@ if (BUILD_READERS) src/qrcode/QRReader.cpp ) endif() -if (BUILD_WRITERS) +if (ZXING_WRITERS_OLD) set (QRCODE_FILES ${QRCODE_FILES} src/qrcode/QREncoder.h src/qrcode/QREncoder.cpp @@ -404,18 +479,6 @@ if (BUILD_WRITERS) ) endif() -set (LIBZUECI_FILES - src/libzueci/zueci.c - src/libzueci/zueci.h -) - -if (NOT BUILD_READERS) - set_source_files_properties(src/libzueci/zueci.c PROPERTIES COMPILE_FLAGS -DZUECI_EMBED_NO_TO_UTF) -endif() -if (NOT BUILD_WRITERS) - set_source_files_properties(src/libzueci/zueci.c PROPERTIES COMPILE_FLAGS -DZUECI_EMBED_NO_TO_ECI) -endif() - source_group (Sources FILES ${COMMON_FILES}) source_group (Sources\\aztec FILES ${AZTEC_FILES}) source_group (Sources\\datamatrix FILES ${DATAMATRIX_FILES}) @@ -423,7 +486,6 @@ source_group (Sources\\maxicode FILES ${MAXICODE_FILES}) source_group (Sources\\oned FILES ${ONED_FILES}) source_group (Sources\\pdf417 FILES ${PDF417_FILES}) source_group (Sources\\qrcode FILES ${QRCODE_FILES}) -source_group (Sources\\libzueci FILES ${LIBZUECI_FILES}) set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) @@ -437,7 +499,6 @@ add_library (ZXing ${ONED_FILES} ${PDF417_FILES} ${QRCODE_FILES} - ${LIBZUECI_FILES} ) target_include_directories (ZXing @@ -446,25 +507,43 @@ target_include_directories (ZXing ) target_compile_options (ZXing - PUBLIC ${ZXING_CORE_DEFINES} - PRIVATE ${ZXING_CORE_LOCAL_DEFINES} + PUBLIC ${ZXING_PUBLIC_FLAGS} + PRIVATE ${ZXING_PRIVATE_FLAGS} ) -include (CheckCXXCompilerFlag) +target_compile_features(ZXing PUBLIC cxx_std_17) -CHECK_CXX_COMPILER_FLAG ("-ffloat-store" COMPILER_NEEDS_FLOAT_STORE) -if (COMPILER_NEEDS_FLOAT_STORE) - target_compile_options(ZXing PRIVATE - -ffloat-store # same floating point precision in all optimization levels +target_link_libraries (ZXing PRIVATE Threads::Threads) + +if (ZXING_READERS OR ZXING_WRITERS_OLD) + set (LIBZUECI_FILES + src/libzueci/zueci.c + src/libzueci/zueci.h ) + set_source_files_properties(${LIBZUECI_FILES} PROPERTIES + COMPILE_FLAGS "$<$>:-DZUECI_EMBED_NO_TO_UTF> $<$>:-DZUECI_EMBED_NO_TO_ECI>" + SKIP_PRECOMPILE_HEADERS ON + ) + target_sources(ZXing PRIVATE ${LIBZUECI_FILES}) + source_group (Sources\\libzueci FILES ${LIBZUECI_FILES}) endif() -target_compile_features(ZXing PUBLIC cxx_std_17) - -target_link_libraries (ZXing PRIVATE Threads::Threads) +if (ZXING_WRITERS_NEW) + if (ZXING_USE_BUNDLED_ZINT) + aux_source_directory(src/libzint LIBZINT_FILES) # manually re-run cmake after adding a new file/symlink + set_source_files_properties(${LIBZINT_FILES} PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + target_sources(ZXing PRIVATE ${LIBZINT_FILES}) + source_group (Sources\\libzint FILES ${LIBZINT_FILES}) + target_include_directories (ZXing PRIVATE "$") + else() + include(../zxing.cmake) + zxing_add_package(zint zint https://github.com/zint/zint.git 7a9fdd6cd00cd5bfd0082705d934c13ef84f25e1) + target_link_libraries (ZXing PRIVATE zint) + endif() +endif() add_library(ZXing::ZXing ALIAS ZXing) -# add the old alias as well, to keep old clients compiling +# add the old alias as well, to keep old clients compiling [[deprecated]] # note: this only affects client code that includes ZXing via sub_directory. # for clients using the exported target, see ZXingConfig.cmake.in add_library(ZXing::Core ALIAS ZXing) @@ -479,21 +558,47 @@ endif() set_target_properties(ZXing PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") -if (APPLE AND BUILD_APPLE_FRAMEWORK) - set_target_properties(ZXing PROPERTIES - FRAMEWORK TRUE - FRAMEWORK_VERSION "C" - XCODE_ATTRIBUTE_DEFINES_MODULE YES - XCODE_ATTRIBUTE_BUILD_LIBRARY_FOR_DISTRIBUTION YES - XCODE_ATTRIBUTE_MODULEMAP_FILE "wrappers/ios/Sources/Wrapper/module.modulemap" - XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES - MACOSX_FRAMEWORK_IDENTIFIER "com.zxing-cpp.ios" - CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO - #MACOSX_FRAMEWORK_INFO_PLIST Info.plist - PUBLIC_HEADER "${PUBLIC_HEADERS}" - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer" - XCODE_ATTRIBUTE_ENABLE_BITCODE "NO" - ) +set(PRECOMPILE_HEADERS ${PUBLIC_HEADERS}) +list(REMOVE_ITEM PRECOMPILE_HEADERS "$<$:${CMAKE_CURRENT_SOURCE_DIR}/src/ZXingC.h>") +list(REMOVE_ITEM PRECOMPILE_HEADERS src/DecodeHints.h) # [[deprecated]] +list(REMOVE_ITEM PRECOMPILE_HEADERS src/Result.h) # [[deprecated]] +list(REMOVE_ITEM PRECOMPILE_HEADERS src/ZXVersion.h) # [[deprecated]] +target_precompile_headers(ZXing PRIVATE ${PRECOMPILE_HEADERS}) +set_source_files_properties(src/DecodeHints.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + +if(CMAKE_BUILD_TYPE MATCHES "Release|RelWithDebInfo") + # The following is a list of translation units that fulfill two criteria regarding the use of -Os vs -O3: + # 1. their binary size decreases significantly + # 2. the runtime of ReaderTest is not (measurably) affected + # Compiling them with -Os saves about 40kB (3%) with clang and 190kB (12%) with gcc. + check_cxx_compiler_flag("-Os" COMPILER_KNOWS_Os) + if(COMPILER_KNOWS_Os) + set_source_files_properties( + src/Barcode.cpp + src/BarcodeFormat.cpp + src/BitMatrixIO.cpp + src/Error.cpp + src/GTIN.cpp + src/HRI.cpp + src/MultiFormatReader.cpp + src/WriteBarcode.cpp + src/ZXingC.cpp + src/ZXingCpp.cpp + src/aztec/AZHighLevelEncoder.cpp + src/datamatrix/DMDataBlock.cpp + src/datamatrix/DMHighLevelEncoder.cpp + src/oned/ODDataBarExpandedBitDecoder.cpp + src/pdf417/PDFHighLevelEncoder.cpp + src/qrcode/QRBitMatrixParser.cpp + src/qrcode/QRDataBlock.cpp + src/qrcode/QRDecoder.cpp + src/qrcode/QREncoder.cpp + src/qrcode/QRMaskUtil.cpp + src/qrcode/QRReader.cpp + src/qrcode/QRVersion.cpp + ${LIBZINT_FILES} + PROPERTIES SKIP_PRECOMPILE_HEADERS ON COMPILE_FLAGS -Os) + endif() endif() include (GNUInstallDirs) @@ -510,10 +615,10 @@ install ( PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ZXing" ) -configure_file (ZXVersion.h.in ZXVersion.h) +configure_file (Version.h.in Version.h) install ( - FILES "${CMAKE_CURRENT_BINARY_DIR}/ZXVersion.h" + FILES "${CMAKE_CURRENT_BINARY_DIR}/Version.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ZXing" ) @@ -523,7 +628,7 @@ if (MSVC) COMPILE_PDB_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ZXing.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} - CONFIGURATIONS Debug RelWithDebInfo + CONFIGURATIONS Debug RelWithDebInfo OPTIONAL) endif() @@ -535,6 +640,9 @@ install ( ) IF (NOT WIN32 OR MINGW) + if (${ZXING_EXPERIMENTAL_API}) + set(COMPILE_FLAGS "-DZXING_EXPERIMENTAL_API") + endif() configure_file(zxing.pc.in zxing.pc @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/zxing.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) ENDIF() diff --git a/core/ZXVersion.h.in b/core/Version.h.in similarity index 66% rename from core/ZXVersion.h.in rename to core/Version.h.in index 41dd401145..09105fca9c 100644 --- a/core/ZXVersion.h.in +++ b/core/Version.h.in @@ -6,13 +6,12 @@ #pragma once +#cmakedefine ZXING_READERS +#cmakedefine ZXING_WRITERS + // Version numbering #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@"; - -} +#define ZXING_VERSION_STR "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@" diff --git a/core/src/Barcode.cpp b/core/src/Barcode.cpp new file mode 100644 index 0000000000..5454c85588 --- /dev/null +++ b/core/src/Barcode.cpp @@ -0,0 +1,269 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "Barcode.h" + +#include "DecoderResult.h" +#include "DetectorResult.h" +#include "JSON.h" +#include "ZXAlgorithms.h" + +#ifdef ZXING_EXPERIMENTAL_API +#include "BitMatrix.h" + +#ifdef ZXING_USE_ZINT +#include +void zint_symbol_deleter::operator()(zint_symbol* p) const noexcept +{ + ZBarcode_Delete(p); +} +#else +struct zint_symbol {}; +void zint_symbol_deleter::operator()(zint_symbol*) const noexcept {} +#endif + +#endif + +#include +#include +#include +#include + +namespace ZXing { + +Result::Result(const std::string& text, int y, int xStart, int xStop, BarcodeFormat format, SymbologyIdentifier si, Error error, bool readerInit) + : _content({ByteArray(text)}, si), + _error(error), + _position(Line(y, xStart, xStop)), + _format(format), + _readerInit(readerInit) +{} + +Result::Result(DecoderResult&& decodeResult, DetectorResult&& detectorResult, BarcodeFormat format) + : _content(std::move(decodeResult).content()), + _error(std::move(decodeResult).error()), + _position(std::move(detectorResult).position()), + _sai(decodeResult.structuredAppend()), + _format(format), + _lineCount(decodeResult.lineCount()), + _isMirrored(decodeResult.isMirrored()), + _readerInit(decodeResult.readerInit()) +#ifdef ZXING_EXPERIMENTAL_API + , _symbol(std::make_shared(std::move(detectorResult).bits())) + , _json(std::move(decodeResult).json()) +#endif +{ + if (decodeResult.versionNumber()) + snprintf(_version, 4, "%d", decodeResult.versionNumber()); + snprintf(_ecLevel, 4, "%s", decodeResult.ecLevel().data()); + + // TODO: add type opaque and code specific 'extra data'? (see DecoderResult::extra()) +} + +Result::Result(DecoderResult&& decodeResult, Position&& position, BarcodeFormat format) + : Result(std::move(decodeResult), {{}, std::move(position)}, format) +{} + +bool Result::isValid() const +{ + return format() != BarcodeFormat::None && !_content.bytes.empty() && !error(); +} + +const ByteArray& Result::bytes() const +{ + return _content.bytes; +} + +ByteArray Result::bytesECI() const +{ + return _content.bytesECI(); +} + +std::string Result::text(TextMode mode) const +{ + return _content.text(mode); +} + +std::string Result::text() const +{ + return text(_readerOpts.textMode()); +} + +std::string Result::ecLevel() const +{ + return _ecLevel; +} + +ContentType Result::contentType() const +{ + return _content.type(); +} + +bool Result::hasECI() const +{ + return _content.hasECI; +} + +int Result::orientation() const +{ + constexpr auto std_numbers_pi_v = 3.14159265358979323846; // TODO: c++20 + return narrow_cast(std::lround(_position.orientation() * 180 / std_numbers_pi_v)); +} + +std::string Result::symbologyIdentifier() const +{ + return _content.symbology.toString(); +} + +int Result::sequenceSize() const +{ + return _sai.count; +} + +int Result::sequenceIndex() const +{ + return _sai.index; +} + +std::string Result::sequenceId() const +{ + return _sai.id; +} + +std::string Result::version() const +{ + return _version; +} + +Result& Result::setReaderOptions(const ReaderOptions& opts) +{ + if (opts.characterSet() != CharacterSet::Unknown) + _content.defaultCharset = opts.characterSet(); + _readerOpts = opts; + return *this; +} + +#ifdef ZXING_EXPERIMENTAL_API +void Result::symbol(BitMatrix&& bits) +{ + bits.flipAll(); + _symbol = std::make_shared(std::move(bits)); +} + +ImageView Result::symbol() const +{ + return _symbol && !_symbol->empty() ? ImageView{_symbol->row(0).begin(), _symbol->width(), _symbol->height(), ImageFormat::Lum} + : ImageView{}; +} + +void Result::zint(unique_zint_symbol&& z) +{ + _zint = std::shared_ptr(std::move(z)); +} + +std::string Result::extra(std::string_view key) const +{ + if (key == "ALL") { + if (format() == BarcodeFormat::None) + return {}; + auto res = + StrCat("{", JsonProp("Text", text(TextMode::Plain)), JsonProp("HRI", text(TextMode::HRI)), + JsonProp("TextECI", text(TextMode::ECI)), JsonProp("Bytes", text(TextMode::Hex)), + JsonProp("Identifier", symbologyIdentifier()), JsonProp("Format", ToString(format())), + JsonProp("ContentType", isValid() ? ToString(contentType()) : ""), JsonProp("Position", ToString(position())), + JsonProp("HasECI", hasECI()), JsonProp("IsMirrored", isMirrored()), JsonProp("IsInverted", isInverted()), _json, + JsonProp("Error", ToString(error()))); + res.back() = '}'; + return res; + } + return _json.empty() ? "" : key.empty() ? StrCat("{", _json.substr(0, _json.size() - 1), "}") : std::string(JsonGetStr(_json, key)); +} +#endif + +bool Result::operator==(const Result& o) const +{ + if (format() != o.format()) + return false; + + // handle MatrixCodes first + if (!IsLinearBarcode(format())) { + if (bytes() != o.bytes() && isValid() && o.isValid()) + return false; + + // 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 (bytes() != o.bytes() || error() != o.error() || orientation() != o.orientation()) + return false; + + if (lineCount() > 1 && o.lineCount() > 1) + return HaveIntersectingBoundingBoxes(o.position(), position()); + + // the following code is only meant for this or other lineCount == 1 + assert(lineCount() == 1 || o.lineCount() == 1); + + // sl == single line, ml = multi line + const auto& sl = lineCount() == 1 ? *this : o; + const auto& ml = lineCount() == 1 ? o : *this; + + // If one line is less than half the length of the other away from the + // latter, we consider it to belong to the same symbol. + // Additionally, both need to have roughly the same length (see #367). + auto dTop = maxAbsComponent(ml.position().topLeft() - sl.position().topLeft()); + auto dBot = maxAbsComponent(ml.position().bottomLeft() - sl.position().topLeft()); + auto slLength = maxAbsComponent(sl.position().topLeft() - sl.position().bottomRight()); + bool isHorizontal = sl.position().topLeft().y == sl.position().bottomRight().y; + // Measure the multi line length in the same direction as the single line one (not diagonaly) + // to make sure overly tall symbols don't get segmented (see #769). + auto mlLength = isHorizontal ? std::abs(ml.position().topLeft().x - ml.position().bottomRight().x) + : std::abs(ml.position().topLeft().y - ml.position().bottomRight().y); + + return std::min(dTop, dBot) < slLength / 2 && std::abs(slLength - mlLength) < slLength / 5; +} + +Barcode MergeStructuredAppendSequence(const Barcodes& barcodes) +{ + if (barcodes.empty()) + return {}; + + std::list allBarcodes(barcodes.begin(), barcodes.end()); + allBarcodes.sort([](const Barcode& r1, const Barcode& r2) { return r1.sequenceIndex() < r2.sequenceIndex(); }); + + Barcode res = allBarcodes.front(); + for (auto i = std::next(allBarcodes.begin()); i != allBarcodes.end(); ++i) + res._content.append(i->_content); + + res._position = {}; + res._sai.index = -1; + + if (allBarcodes.back().sequenceSize() != Size(allBarcodes) || + !std::all_of(allBarcodes.begin(), allBarcodes.end(), + [&](Barcode& it) { return it.sequenceId() == allBarcodes.front().sequenceId(); })) + res._error = FormatError("sequenceIDs not matching during structured append sequence merging"); + + return res; +} + +Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes) +{ + std::map sas; + for (auto& barcode : barcodes) { + if (barcode.isPartOfSequence()) + sas[barcode.sequenceId()].push_back(barcode); + } + + Barcodes res; + for (auto& [id, seq] : sas) { + auto barcode = MergeStructuredAppendSequence(seq); + if (barcode.isValid()) + res.push_back(std::move(barcode)); + } + + return res; +} + +} // namespace ZXing diff --git a/core/src/Barcode.h b/core/src/Barcode.h new file mode 100644 index 0000000000..cc13023130 --- /dev/null +++ b/core/src/Barcode.h @@ -0,0 +1,230 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BarcodeFormat.h" +#include "ByteArray.h" +#include "Content.h" +#include "ReaderOptions.h" +#include "Error.h" +#include "ImageView.h" +#include "Quadrilateral.h" +#include "StructuredAppend.h" + +#ifdef ZXING_EXPERIMENTAL_API +#include +namespace ZXing { +class BitMatrix; + +namespace BarcodeExtra { + #define ZX_EXTRA(NAME) static constexpr auto NAME = #NAME + ZX_EXTRA(DataMask); // QRCodes + ZX_EXTRA(Version); + ZX_EXTRA(EanAddOn); // EAN/UPC + ZX_EXTRA(UPCE); + #undef ZX_EXTRA +} // namespace BarcodeExtra + +} // namespace ZXing + +extern "C" struct zint_symbol; +struct zint_symbol_deleter +{ + void operator()(zint_symbol* p) const noexcept; +}; +using unique_zint_symbol = std::unique_ptr; +#endif + +#include +#include + +namespace ZXing { + +class DecoderResult; +class DetectorResult; +class WriterOptions; +class Result; // TODO: 3.0 replace deprected symbol name + +using Position = QuadrilateralI; +using Barcode = Result; +using Barcodes = std::vector; +using Results = std::vector; + +/** + * @brief The Barcode class encapsulates the result of decoding a barcode within an image. + */ +class Result +{ + void setIsInverted(bool v) { _isInverted = v; } + Result& setReaderOptions(const ReaderOptions& opts); + + friend Barcode MergeStructuredAppendSequence(const Barcodes&); + friend Barcodes ReadBarcodes(const ImageView&, const ReaderOptions&); + friend Image WriteBarcodeToImage(const Barcode&, const WriterOptions&); + friend void IncrementLineCount(Barcode&); + +public: + Result() = default; + + // linear symbology convenience constructor + Result(const std::string& text, int y, int xStart, int xStop, BarcodeFormat format, SymbologyIdentifier si, Error error = {}, + bool readerInit = false); + + Result(DecoderResult&& decodeResult, DetectorResult&& detectorResult, BarcodeFormat format); + + [[deprecated]] Result(DecoderResult&& decodeResult, Position&& position, BarcodeFormat format); + + bool isValid() const; + + const Error& error() const { return _error; } + + BarcodeFormat format() const { return _format; } + + /** + * @brief bytes is the raw / standard content without any modifications like character set conversions + */ + const ByteArray& bytes() const; // TODO 3.0: replace ByteArray with std::vector + + /** + * @brief bytesECI is the raw / standard content following the ECI protocol + */ + ByteArray bytesECI() const; + + /** + * @brief text returns the bytes() content rendered to unicode/utf8 text accoring to specified TextMode + */ + std::string text(TextMode mode) const; + + /** + * @brief text returns the bytes() content rendered to unicode/utf8 text accoring to the TextMode set in the ReaderOptions + */ + std::string text() const; + + /** + * @brief ecLevel returns the error correction level of the symbol (empty string if not applicable) + */ + std::string ecLevel() const; + + /** + * @brief contentType gives a hint to the type of content found (Text/Binary/GS1/etc.) + */ + ContentType contentType() const; + + /** + * @brief hasECI specifies wheter or not an ECI tag was found + */ + bool hasECI() const; + + const Position& position() const { return _position; } + void setPosition(Position pos) { _position = pos; } + + /** + * @brief orientation of barcode in degree, see also Position::orientation() + */ + int orientation() const; + + /** + * @brief isMirrored is the symbol mirrored (currently only supported by QRCode and DataMatrix) + */ + bool isMirrored() const { return _isMirrored; } + + /** + * @brief isInverted is the symbol inverted / has reveresed reflectance (see ReaderOptions::tryInvert) + */ + bool isInverted() const { return _isInverted; } + + /** + * @brief symbologyIdentifier Symbology identifier "]cm" where "c" is symbology code character, "m" the modifier. + */ + std::string symbologyIdentifier() const; + + /** + * @brief sequenceSize number of symbols in a structured append sequence. + * + * If this is not part of a structured append sequence, the returned value is -1. + * If it is a structured append symbol but the total number of symbols is unknown, the + * returned value is 0 (see PDF417 if optional "Segment Count" not given). + */ + int sequenceSize() const; + + /** + * @brief sequenceIndex the 0-based index of this symbol in a structured append sequence. + */ + int sequenceIndex() const; + + /** + * @brief sequenceId id to check if a set of symbols belongs to the same structured append sequence. + * + * If the symbology does not support this feature, the returned value is empty (see MaxiCode). + * For QR Code, this is the parity integer converted to a string. + * For PDF417 and DataMatrix, this is the "fileId". + */ + std::string sequenceId() const; + + bool isLastInSequence() const { return sequenceSize() == sequenceIndex() + 1; } + bool isPartOfSequence() const { return sequenceSize() > -1 && sequenceIndex() > -1; } + + /** + * @brief readerInit Set if Reader Initialisation/Programming symbol. + */ + bool readerInit() const { return _readerInit; } + + /** + * @brief lineCount How many lines have been detected with this code (applies only to linear symbologies) + */ + int lineCount() const { return _lineCount; } + + /** + * @brief version QRCode / DataMatrix / Aztec version or size. + */ + std::string version() const; + +#ifdef ZXING_EXPERIMENTAL_API + void symbol(BitMatrix&& bits); + ImageView symbol() const; + void zint(unique_zint_symbol&& z); + zint_symbol* zint() const { return _zint.get(); } + Result&& addExtra(std::string&& json) { _json += std::move(json); return std::move(*this); } + // template + // Result&& addExtra(std::string_view key, T val, T ignore = {}) { _json += JsonProp(key, val, ignore); return std::move(*this); } + std::string extra(std::string_view key = "") const; +#endif + + bool operator==(const Result& o) const; + +private: + Content _content; + Error _error; + Position _position; + ReaderOptions _readerOpts; // TODO: 3.0 switch order to prevent 4 padding bytes + StructuredAppendInfo _sai; + BarcodeFormat _format = BarcodeFormat::None; + char _ecLevel[4] = {}; + char _version[4] = {}; + int _lineCount = 0; + bool _isMirrored = false; + bool _isInverted = false; + bool _readerInit = false; +#ifdef ZXING_EXPERIMENTAL_API + std::shared_ptr _symbol; + std::shared_ptr _zint; + std::string _json; +#endif +}; + +/** + * @brief Merge a list of Barcodes from one Structured Append sequence to a single barcode + */ +Barcode MergeStructuredAppendSequence(const Barcodes& results); + +/** + * @brief Automatically merge all Structured Append sequences found in the given list of barcodes + */ +Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes); + +} // ZXing diff --git a/core/src/BarcodeFormat.cpp b/core/src/BarcodeFormat.cpp index 58db3decce..ba9a2937cf 100644 --- a/core/src/BarcodeFormat.cpp +++ b/core/src/BarcodeFormat.cpp @@ -31,7 +31,9 @@ static BarcodeFormatName NAMES[] = { {BarcodeFormat::Code128, "Code128"}, {BarcodeFormat::DataBar, "DataBar"}, {BarcodeFormat::DataBarExpanded, "DataBarExpanded"}, + {BarcodeFormat::DataBarLimited, "DataBarLimited"}, {BarcodeFormat::DataMatrix, "DataMatrix"}, + {BarcodeFormat::DXFilmEdge, "DXFilmEdge"}, {BarcodeFormat::EAN8, "EAN-8"}, {BarcodeFormat::EAN13, "EAN-13"}, {BarcodeFormat::ITF, "ITF"}, @@ -39,6 +41,7 @@ static BarcodeFormatName NAMES[] = { {BarcodeFormat::MicroQRCode, "MicroQRCode"}, {BarcodeFormat::PDF417, "PDF417"}, {BarcodeFormat::QRCode, "QRCode"}, + {BarcodeFormat::RMQRCode, "rMQRCode"}, {BarcodeFormat::UPCA, "UPC-A"}, {BarcodeFormat::UPCE, "UPC-E"}, {BarcodeFormat::LinearCodes, "Linear-Codes"}, @@ -65,7 +68,11 @@ static std::string NormalizeFormatString(std::string_view sv) { std::string str(sv); std::transform(str.begin(), str.end(), str.begin(), [](char c) { return (char)std::tolower(c); }); +#ifdef __cpp_lib_erase_if + std::erase_if(str, [](char c) { return Contains("_-[]", c); }); +#else str.erase(std::remove_if(str.begin(), str.end(), [](char c) { return Contains("_-[]", c); }), str.end()); +#endif return str; } diff --git a/core/src/BarcodeFormat.h b/core/src/BarcodeFormat.h index 3d3a4c326e..f8b7bc0e9c 100644 --- a/core/src/BarcodeFormat.h +++ b/core/src/BarcodeFormat.h @@ -39,16 +39,25 @@ enum class BarcodeFormat UPCA = (1 << 14), ///< UPC-A UPCE = (1 << 15), ///< UPC-E MicroQRCode = (1 << 16), ///< Micro QR Code + RMQRCode = (1 << 17), ///< Rectangular Micro QR Code + DXFilmEdge = (1 << 18), ///< DX Film Edge Barcode + DataBarLimited = (1 << 19), ///< GS1 DataBar Limited - LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE, - MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode, + LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DataBarLimited + | DXFilmEdge | UPCA | UPCE, + MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode, Any = LinearCodes | MatrixCodes, - _max = MicroQRCode, ///> implementation detail, don't use + _max = DataBarLimited, ///> implementation detail, don't use }; ZX_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat) +inline constexpr bool IsLinearBarcode(BarcodeFormat format) +{ + return BarcodeFormats(BarcodeFormat::LinearCodes).testFlag(format); +} + std::string ToString(BarcodeFormat format); std::string ToString(BarcodeFormats formats); diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index 4cbe5d5ef2..9cbb592ee7 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -63,12 +63,14 @@ void BinaryBitmap::invert() auto matrix = const_cast(_cache->matrix.get()); matrix->flipAll(); } - _inverted = true; + _inverted = !_inverted; } template void SumFilter(const BitMatrix& in, BitMatrix& out, F func) { + assert(in.height() >= 3); + const auto* in0 = in.row(0).begin(); const auto* in1 = in.row(1).begin(); const auto* in2 = in.row(2).begin(); diff --git a/core/src/BitArray.h b/core/src/BitArray.h index 8e814b5a19..c9e38c87eb 100644 --- a/core/src/BitArray.h +++ b/core/src/BitArray.h @@ -13,10 +13,8 @@ #include #include #include -#include #include #include -#include #include namespace ZXing { @@ -158,7 +156,7 @@ class BitArrayView BitArrayView& skipBits(int n) { - if (n > bits.size()) + if (cur + n > bits.end()) throw std::out_of_range("BitArrayView::skipBits() out of range."); cur += n; return *this; @@ -167,7 +165,7 @@ class BitArrayView int peakBits(int n) const { assert(n <= 32); - if (n > bits.size()) + if (cur + n > bits.end()) throw std::out_of_range("BitArrayView::peakBits() out of range."); int res = 0; for (auto i = cur; n > 0; --n, i++) diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index d943a1269a..20b4231513 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -8,13 +8,19 @@ #include #include +#include #include -#if __has_include() && __cplusplus > 201703L // MSVC has the header but then warns about including it +// MSVC has the header but then warns about including it. +// We check for _MSVC_LANG here as well, so client code is depending on /Zc:__cplusplus +#if __has_include() && (__cplusplus > 201703L || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L)) #include #if __cplusplus > 201703L && defined(__ANDROID__) // NDK 25.1.8937393 has the implementation but fails to advertise it #define __cpp_lib_bitops 201907L #endif +#elif defined(_MSC_VER) +// accoring to #863 MSVC defines __cpp_lib_bitops even when it not included and bitops are not available +#undef __cpp_lib_bitops #endif #if defined(__clang__) || defined(__GNUC__) @@ -41,12 +47,16 @@ inline int NumberOfLeadingZeros(T x) return std::countl_zero(static_cast>(x)); #else if constexpr (sizeof(x) <= 4) { + static_assert(sizeof(x) == 4, "NumberOfLeadingZeros not implemented for 8 and 16 bit ints."); if (x == 0) return 32; #ifdef ZX_HAS_GCC_BUILTINS return __builtin_clz(x); #elif defined(ZX_HAS_MSC_BUILTINS) - return __lzcnt(x); + unsigned long where; + if (_BitScanReverse(&where, x)) + return 31 - static_cast(where); + return 32; #else int n = 0; if ((x & 0xFFFF0000) == 0) { n = n + 16; x = x << 16; } @@ -61,9 +71,7 @@ inline int NumberOfLeadingZeros(T x) return 64; #ifdef ZX_HAS_GCC_BUILTINS return __builtin_clzll(x); -#elif defined(ZX_HAS_MSC_BUILTINS) - return __lzcnt64(x); -#else +#else // including ZX_HAS_MSC_BUILTINS int n = NumberOfLeadingZeros(static_cast(x >> 32)); if (n == 32) n += NumberOfLeadingZeros(static_cast(x)); @@ -79,13 +87,13 @@ inline int NumberOfLeadingZeros(T x) template>> inline int NumberOfTrailingZeros(T v) { - assert(v != 0); #ifdef __cpp_lib_bitops return std::countr_zero(static_cast>(v)); #else if constexpr (sizeof(v) <= 4) { + static_assert(sizeof(v) == 4, "NumberOfTrailingZeros not implemented for 8 and 16 bit ints."); #ifdef ZX_HAS_GCC_BUILTINS - return __builtin_ctz(v); + return v == 0 ? 32 : __builtin_ctz(v); #elif defined(ZX_HAS_MSC_BUILTINS) unsigned long where; if (_BitScanForward(&where, v)) @@ -104,22 +112,8 @@ inline int NumberOfTrailingZeros(T v) #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 + return v == 0 ? 64 : __builtin_ctzll(v); +#else // including ZX_HAS_MSC_BUILTINS int n = NumberOfTrailingZeros(static_cast(v)); if (n == 32) n += NumberOfTrailingZeros(static_cast(v >> 32)); diff --git a/core/src/BitMatrix.cpp b/core/src/BitMatrix.cpp index 729a5752b3..f03cde7471 100644 --- a/core/src/BitMatrix.cpp +++ b/core/src/BitMatrix.cpp @@ -173,4 +173,3 @@ BitMatrix Deflate(const BitMatrix& input, int width, int height, float top, floa } } // ZXing - diff --git a/core/src/BitMatrix.h b/core/src/BitMatrix.h index c063d0e0ac..821a035e79 100644 --- a/core/src/BitMatrix.h +++ b/core/src/BitMatrix.h @@ -11,10 +11,8 @@ #include "Point.h" #include "Range.h" -#include #include #include -#include #include namespace ZXing { @@ -64,7 +62,7 @@ class BitMatrix BitMatrix(int width, int height) : _width(width), _height(height), _bits(width * height, UNSET_V) { if (width != 0 && Size(_bits) / width != height) - throw std::invalid_argument("invalid size: width * height is too big"); + throw std::invalid_argument("Invalid size: width * height is too big"); } explicit BitMatrix(int dimension) : BitMatrix(dimension, dimension) {} // Construct a square matrix. @@ -153,7 +151,7 @@ void GetPatternRow(const BitMatrix& matrix, int r, std::vector& pr, bo /** * @brief Inflate scales a BitMatrix up and adds a quiet Zone plus padding - * @param matrix input to be expanded + * @param input matrix to be expanded * @param width new width in bits (pixel) * @param height new height in bits (pixel) * @param quietZone size of quiet zone to add in modules @@ -163,7 +161,7 @@ BitMatrix Inflate(BitMatrix&& input, int width, int height, int quietZone); /** * @brief Deflate (crop + subsample) a bit matrix - * @param matrix + * @param input matrix to be shrinked * @param width new width * @param height new height * @param top cropping starts at top row @@ -171,7 +169,7 @@ BitMatrix Inflate(BitMatrix&& input, int width, int height, int quietZone); * @param subSampling typically the module size * @return deflated input */ -BitMatrix Deflate(const BitMatrix& matrix, int width, int height, float top, float left, float subSampling); +BitMatrix Deflate(const BitMatrix& input, int width, int height, float top, float left, float subSampling); template BitMatrix ToBitMatrix(const Matrix& in, T trueValue = {true}) diff --git a/core/src/BitMatrixCursor.h b/core/src/BitMatrixCursor.h index bdfc523ed6..a7fae30dc9 100644 --- a/core/src/BitMatrixCursor.h +++ b/core/src/BitMatrixCursor.h @@ -7,7 +7,6 @@ #include "BitMatrix.h" -#include #include namespace ZXing { @@ -168,7 +167,7 @@ class BitMatrixCursor template ARRAY readPattern(int range = 0) { - ARRAY res; + ARRAY res = {}; for (auto& i : res) { i = stepToEdge(1, range); if (!i) diff --git a/core/src/BitMatrixIO.cpp b/core/src/BitMatrixIO.cpp index 685e9cf236..08678084af 100644 --- a/core/src/BitMatrixIO.cpp +++ b/core/src/BitMatrixIO.cpp @@ -6,6 +6,7 @@ #include "BitMatrixIO.h" +#include #include #include @@ -30,6 +31,23 @@ std::string ToString(const BitMatrix& matrix, char one, char zero, bool addSpace return result; } +std::string ToString(const BitMatrix& matrix, bool inverted) +{ + constexpr auto map = std::array{" ", "▀", "▄", "█"}; + std::string res; + + for (int y = 0; y < matrix.height(); y += 2) { + for (int x = 0; x < matrix.width(); ++x) { + int tp = matrix.get(x, y) ^ inverted; + int bt = (matrix.height() == 1 && tp) || (y + 1 < matrix.height() && (matrix.get(x, y + 1) ^ inverted)); + res += map[tp | (bt << 1)]; + } + res.push_back('\n'); + } + + return res; +} + std::string ToSVG(const BitMatrix& matrix) { // see https://stackoverflow.com/questions/10789059/create-qr-code-in-vector-image/60638350#60638350 diff --git a/core/src/BitMatrixIO.h b/core/src/BitMatrixIO.h index 022b48ec35..d4b695c7d5 100644 --- a/core/src/BitMatrixIO.h +++ b/core/src/BitMatrixIO.h @@ -12,7 +12,8 @@ namespace ZXing { -std::string ToString(const BitMatrix& matrix, char one = 'X', char zero = ' ', bool addSpace = true, bool printAsCString = false); +std::string ToString(const BitMatrix& matrix, bool inverted = false); +std::string ToString(const BitMatrix& matrix, char one, char zero = ' ', bool addSpace = true, bool printAsCString = false); std::string ToSVG(const BitMatrix& matrix); BitMatrix ParseBitMatrix(const std::string& str, char one = 'X', bool expectSpace = true); void SaveAsPBM(const BitMatrix& matrix, const std::string filename, int quietZone = 0); diff --git a/core/src/ByteArray.h b/core/src/ByteArray.h index 6cb603e7b4..f652b1ba2d 100644 --- a/core/src/ByteArray.h +++ b/core/src/ByteArray.h @@ -6,6 +6,8 @@ #pragma once +#include "Range.h" + #include #include #include @@ -15,7 +17,7 @@ namespace ZXing { /** - ByteArray is an extension of std::vector. + ByteArray is an extension of std::vector. */ class ByteArray : public std::vector { @@ -25,15 +27,21 @@ class ByteArray : public std::vector explicit ByteArray(int len) : std::vector(len, 0) {} explicit ByteArray(const std::string& str) : std::vector(str.begin(), str.end()) {} - void append(const ByteArray& other) { insert(end(), other.begin(), other.end()); } + void append(ByteView other) { insert(end(), other.begin(), other.end()); } + void append(std::string_view other) { insert(end(), other.begin(), other.end()); } std::string_view asString(size_t pos = 0, size_t len = std::string_view::npos) const { return std::string_view(reinterpret_cast(data()), size()).substr(pos, len); } + + ByteView asView(size_t pos = 0, size_t len = size_t(-1)) const + { + return ByteView(*this).subview(pos, len); + } }; -inline std::string ToHex(const ByteArray& bytes) +inline std::string ToHex(ByteView bytes) { std::string res(bytes.size() * 3, ' '); diff --git a/core/src/ByteMatrix.h b/core/src/ByteMatrix.h index e94f589c7e..a7fde0f3e8 100644 --- a/core/src/ByteMatrix.h +++ b/core/src/ByteMatrix.h @@ -14,7 +14,7 @@ namespace ZXing { // TODO: If kept at all, this should be replaced by `using ByteMatrix = Matrix;` to be consistent with ByteArray // This non-template class is kept for now to stay source-compatible with older versions of the library. - +// [[deprecated]] class ByteMatrix : public Matrix { public: diff --git a/core/src/CharacterSet.cpp b/core/src/CharacterSet.cpp index 8676ad0950..fd778a5461 100644 --- a/core/src/CharacterSet.cpp +++ b/core/src/CharacterSet.cpp @@ -68,7 +68,11 @@ static std::string NormalizeName(std::string_view sv) { std::string str(sv); std::transform(str.begin(), str.end(), str.begin(), [](char c) { return (char)std::tolower(c); }); +#ifdef __cpp_lib_erase_if + std::erase_if(str, [](char c) { return Contains("_-[] ", c); }); +#else str.erase(std::remove_if(str.begin(), str.end(), [](char c) { return Contains("_-[] ", c); }), str.end()); +#endif return str; } diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index c21a6455e3..e43dd38b79 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -39,6 +39,24 @@ std::optional CenterOfDoubleCross(const BitMatrix& image, PointI center, std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle) { +#if 0 + if (requireCircle) { + // alternative implementation with the aim of discarding closed loops that are not all circle like (M > 5*m) + auto points = CollectRingPoints(image, center, range, std::abs(nth), nth < 0); + if (points.empty()) + return {}; + auto res = Reduce(points, PointF{}, std::plus{}) / Size(points); + + double m = range, M = 0; + for (auto p : points) + UpdateMinMax(m, M, maxAbsComponent(p - res)); + + if (M > 5 * m) + return {}; + + return res; + } +#endif // 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; @@ -138,8 +156,14 @@ static std::vector CollectRingPoints(const BitMatrix& image, PointF cent static std::optional FitQadrilateralToPoints(PointF center, std::vector& points) { auto dist2Center = [c = center](auto a, auto b) { return distance(a, c) < distance(b, c); }; + auto [minDistElem, maxDistElem] = std::minmax_element(points.begin(), points.end(), dist2Center); + + // check if points are on a circle: for a square the min/max ratio is 0.7, for a circle it is 1 + if (distance(center, *minDistElem) / distance(center, *maxDistElem) > 0.85) + return {}; + // rotate points such that the first one is the furthest away from the center (hence, a corner) - std::rotate(points.begin(), std::max_element(points.begin(), points.end(), dist2Center), points.end()); + std::rotate(points.begin(), maxDistElem, points.end()); std::array corners; corners[0] = &points[0]; @@ -189,7 +213,7 @@ static bool QuadrilateralIsPlausibleSquare(const QuadrilateralF q, int lineIndex return m >= lineIndex * 2 && m > M / 3; } -static std::optional FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup) +std::optional FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup) { auto points = CollectRingPoints(image, center, range, lineIndex, backup); if (points.empty()) diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index 02c72c171c..f8490e0c21 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -27,7 +27,7 @@ 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.begin() + (N/2 + 1), pattern.end(), pattern[N/2] / 2.f); + auto a = Reduce(pattern.begin() + (N/2 + 1), pattern.end(), pattern[N/2] / 2.f); return end - a; } } @@ -101,6 +101,8 @@ std::optional CenterOfRing(const BitMatrix& image, PointI center, int ra std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize); +std::optional FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup); + std::optional FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int ringIndex); struct ConcentricPattern : public PointF diff --git a/core/src/Content.cpp b/core/src/Content.cpp index 4d3c44657c..6f18ad8581 100644 --- a/core/src/Content.cpp +++ b/core/src/Content.cpp @@ -12,6 +12,10 @@ #include "Utf.h" #include "ZXAlgorithms.h" +#if !defined(ZXING_READERS) && !defined(ZXING_WRITERS) +#include "Version.h" +#endif + #include namespace ZXing { @@ -77,15 +81,15 @@ void Content::erase(int pos, int n) bytes.erase(bytes.begin() + pos, bytes.begin() + pos + n); for (auto& e : encodings) if (e.pos > pos) - pos -= n; + e.pos -= n; } -void Content::insert(int pos, const std::string& str) +void Content::insert(int pos, std::string_view str) { bytes.insert(bytes.begin() + pos, str.begin(), str.end()); for (auto& e : encodings) if (e.pos > pos) - pos += Size(str); + e.pos += Size(str); } bool Content::canProcess() const @@ -98,44 +102,46 @@ std::string Content::render(bool withECI) const if (empty() || !canProcess()) return {}; +#ifdef ZXING_READERS std::string res; + res.reserve(bytes.size() * 2); if (withECI) - res = symbology.toString(true); + res += symbology.toString(true); ECI lastECI = ECI::Unknown; auto fallbackCS = defaultCharset; if (!hasECI && fallbackCS == CharacterSet::Unknown) fallbackCS = guessEncoding(); ForEachECIBlock([&](ECI eci, int begin, int end) { - // first determine how to decode the content (choose character set) - // * eci == ECI::Unknown implies !hasECI and we guess - // * if !IsText(eci) the ToCharcterSet(eci) will return Unknown and we decode as binary - CharacterSet cs = eci == ECI::Unknown ? fallbackCS : ToCharacterSet(eci); - + // basic idea: if IsText(eci), we transcode it to UTF8, otherwise we treat it as binary but + // transcoded it to valid UTF8 bytes seqences representing the code points 0-255. The eci we report + // back to the caller by inserting their "\XXXXXX" ECI designator is UTF8 for text and + // the original ECI for everything else. + // first determine how to decode the content (use fallback if unknown) + auto inEci = IsText(eci) ? eci : eci == ECI::Unknown ? ToECI(fallbackCS) : ECI::Binary; if (withECI) { // then find the eci to report back in the ECI designator - if (IsText(ToECI(cs))) // everything decoded as text is reported as utf8 - eci = ECI::UTF8; - else if (eci == ECI::Unknown) // implies !hasECI and fallbackCS is Unknown or Binary - eci = ECI::Binary; - - if (lastECI != eci) - res += ToString(eci); - lastECI = eci; - - std::string tmp; - TextDecoder::Append(tmp, bytes.data() + begin, end - begin, cs); - for (auto c : tmp) { + auto outEci = IsText(inEci) ? ECI::UTF8 : eci; + + if (lastECI != outEci) + res += ToString(outEci); + lastECI = outEci; + + for (auto c : BytesToUtf8(bytes.asView(begin, end - begin), inEci)) { res += c; - if (c == '\\') // in the ECI protocol a '\' has to be doubled + if (c == '\\') // in the ECI protocol a '\' (0x5c) has to be doubled, works only because 0x5c can only mean `\` res += c; } } else { - TextDecoder::Append(res, bytes.data() + begin, end - begin, cs); + res += BytesToUtf8(bytes.asView(begin, end - begin), inEci); } }); return res; +#else + //TODO: replace by proper construction from encoded data from within zint + return std::string(bytes.asString()); +#endif } std::string Content::text(TextMode mode) const @@ -145,6 +151,7 @@ std::string Content::text(TextMode mode) const case TextMode::ECI: return render(true); case TextMode::HRI: switch (type()) { +#ifdef ZXING_READERS case ContentType::GS1: { auto plain = render(false); auto hri = HRIFromGS1(plain); @@ -152,6 +159,7 @@ std::string Content::text(TextMode mode) const } case ContentType::ISO15434: return HRIFromISO15434(render(false)); case ContentType::Text: return render(false); +#endif default: return text(TextMode::Escaped); } case TextMode::Hex: return ToHex(bytes); @@ -171,40 +179,52 @@ ByteArray Content::bytesECI() const if (empty()) return {}; - std::string res = symbology.toString(true); + ByteArray res; + res.reserve(3 + bytes.size() + hasECI * encodings.size() * 7); - ForEachECIBlock([&](ECI eci, int begin, int end) { - if (hasECI) - res += ToString(eci); + // report ECI protocol only if actually found ECI data in the barode bit stream + // see also https://github.com/zxing-cpp/zxing-cpp/issues/936 + res.append(symbology.toString(hasECI)); - for (int i = begin; i != end; ++i) { - char c = static_cast(bytes[i]); - res += c; - if (c == '\\') // in the ECI protocol a '\' has to be doubled - res += c; - } - }); + if (hasECI) + ForEachECIBlock([&](ECI eci, int begin, int end) { + if (hasECI) + res.append(ToString(eci)); - return ByteArray(res); + for (auto b : bytes.asView(begin, end - begin)) { + res.push_back(b); + if (b == '\\') // in the ECI protocol a '\' has to be doubled + res.push_back(b); + } + }); + else + res.append(bytes); + + return res; } CharacterSet Content::guessEncoding() const { +#ifdef ZXING_READERS // assemble all blocks with unknown encoding ByteArray input; ForEachECIBlock([&](ECI eci, int begin, int end) { if (eci == ECI::Unknown) - input.insert(input.end(), bytes.begin() + begin, bytes.begin() + end); + input.append(bytes.asView(begin, end - begin)); }); if (input.empty()) return CharacterSet::Unknown; - return TextDecoder::GuessEncoding(input.data(), input.size(), CharacterSet::ISO8859_1); + return GuessTextEncoding(input); +#else + return CharacterSet::ISO8859_1; +#endif } ContentType Content::type() const { +#if 1 //def ZXING_READERS if (empty()) return ContentType::Text; @@ -235,6 +255,10 @@ ContentType Content::type() const return ContentType::Binary; return ContentType::Mixed; +#else + //TODO: replace by proper construction from encoded data from within zint + return ContentType::Text; +#endif } } // namespace ZXing diff --git a/core/src/Content.h b/core/src/Content.h index 501f89d5b7..17fe1413df 100644 --- a/core/src/Content.h +++ b/core/src/Content.h @@ -7,9 +7,11 @@ #include "ByteArray.h" #include "CharacterSet.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" +#include "ZXAlgorithms.h" #include +#include #include namespace ZXing { @@ -28,7 +30,8 @@ struct SymbologyIdentifier std::string toString(bool hasECI = false) const { - return code ? ']' + std::string(1, code) + static_cast(modifier + eciModifierOffset * hasECI) : std::string(); + int modVal = (modifier >= 'A' ? modifier - 'A' + 10 : modifier - '0') + eciModifierOffset * hasECI; + return code ? StrCat(']', code, static_cast((modVal >= 10 ? 'A' - 10 : '0') + modVal)) : std::string(); } }; @@ -62,15 +65,13 @@ class Content void reserve(int count) { bytes.reserve(bytes.size() + count); } void push_back(uint8_t val) { bytes.push_back(val); } - void append(const std::string& str) { bytes.insert(bytes.end(), str.begin(), str.end()); } - void append(const ByteArray& ba) { bytes.insert(bytes.end(), ba.begin(), ba.end()); } + void push_back(int val) { bytes.push_back(narrow_cast(val)); } + void append(std::string_view str) { bytes.insert(bytes.end(), str.begin(), str.end()); } + void append(ByteView bv) { bytes.insert(bytes.end(), bv.begin(), bv.end()); } void append(const Content& other); - void operator+=(char val) { push_back(val); } - void operator+=(const std::string& str) { append(str); } - void erase(int pos, int n); - void insert(int pos, const std::string& str); + void insert(int pos, std::string_view str); bool empty() const { return bytes.empty(); } bool canProcess() const; diff --git a/core/src/CustomData.h b/core/src/CustomData.h deleted file mode 100644 index 6578af5260..0000000000 --- a/core/src/CustomData.h +++ /dev/null @@ -1,20 +0,0 @@ -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -namespace ZXing { - -class CustomData -{ -public: - virtual ~CustomData() = default; - -protected: - CustomData() = default; -}; - -} // ZXing diff --git a/core/src/DecodeHints.cpp b/core/src/DecodeHints.cpp index 72ded6073c..9a54d8d2fb 100644 --- a/core/src/DecodeHints.cpp +++ b/core/src/DecodeHints.cpp @@ -1,11 +1,30 @@ /* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors +* Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 -#include "DecodeHints.h" +#define HIDE_DECODE_HINTS_ALIAS + +#include "ReadBarcode.h" namespace ZXing { +// Provide a struct that is binary compatible with ReaderOptions and is actually called DecodeHints so that +// the compiler generates a correctly mangled pair of ReadBarcode(s) symbols to keep backward ABI compatibility. + +struct DecodeHints +{ + char data[sizeof(ReaderOptions)]; +}; + +Barcode ReadBarcode(const ImageView& image, const DecodeHints& hints = {}) +{ + return ReadBarcode(image, reinterpret_cast(hints)); +} + +Barcodes ReadBarcodes(const ImageView& image, const DecodeHints& hints = {}) +{ + return ReadBarcodes(image, reinterpret_cast(hints)); +} + } // ZXing diff --git a/core/src/DecodeHints.h b/core/src/DecodeHints.h index facee2d7ef..f3276baa0a 100644 --- a/core/src/DecodeHints.h +++ b/core/src/DecodeHints.h @@ -1,175 +1,10 @@ /* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -* Copyright 2020 Axel Waggershauser +* Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 #pragma once -#include "BarcodeFormat.h" -#include "CharacterSet.h" +#include "ReaderOptions.h" -#include -#include - -namespace ZXing { - -/** - * @brief The Binarizer enum - * - * Specify which algorithm to use for the grayscale to binary transformation. - * The difference is how to get to a threshold value T which results in a bit - * value R = L <= T. - */ -enum class Binarizer : unsigned char // needs to be unsigned for the bitfield below to work, uint8_t fails as well -{ - LocalAverage, ///< T = average of neighboring pixels for matrix and GlobalHistogram for linear (HybridBinarizer) - GlobalHistogram, ///< T = valley between the 2 largest peaks in the histogram (per line in linear case) - FixedThreshold, ///< T = 127 - BoolCast, ///< T = 0, fastest possible -}; - -enum class EanAddOnSymbol : unsigned char // see above -{ - Ignore, ///< Ignore any Add-On symbol during read/scan - Read, ///< Read EAN-2/EAN-5 Add-On symbol if found - Require, ///< Require EAN-2/EAN-5 Add-On symbol to be present -}; - -enum class TextMode : unsigned char // see above -{ - Plain, ///< bytes() transcoded to unicode based on ECI info or guessed charset (the default mode prior to 2.0) - ECI, ///< standard content following the ECI protocol with every character set ECI segment transcoded to unicode - HRI, ///< Human Readable Interpretation (dependent on the ContentType) - Hex, ///< bytes() transcoded to ASCII string of HEX values - Escaped, ///< Use the EscapeNonGraphical() function (e.g. ASCII 29 will be transcoded to "") -}; - -class DecodeHints -{ - bool _tryHarder : 1; - bool _tryRotate : 1; - bool _tryInvert : 1; - bool _tryDownscale : 1; - bool _isPure : 1; - bool _tryCode39ExtendedMode : 1; - bool _validateCode39CheckSum : 1; - bool _validateITFCheckSum : 1; - bool _returnCodabarStartEnd : 1; - bool _returnErrors : 1; - uint8_t _downscaleFactor : 3; - EanAddOnSymbol _eanAddOnSymbol : 2; - 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; - uint16_t _downscaleThreshold = 500; - BarcodeFormats _formats = BarcodeFormat::None; - -public: - // bitfields don't get default initialized to 0 before c++20 - DecodeHints() - : _tryHarder(1), - _tryRotate(1), - _tryInvert(1), - _tryDownscale(1), - _isPure(0), - _tryCode39ExtendedMode(0), - _validateCode39CheckSum(0), - _validateITFCheckSum(0), - _returnCodabarStartEnd(0), - _returnErrors(0), - _downscaleFactor(3), - _eanAddOnSymbol(EanAddOnSymbol::Ignore), - _binarizer(Binarizer::LocalAverage), - _textMode(TextMode::HRI), - _characterSet(CharacterSet::Unknown) -#ifdef BUILD_EXPERIMENTAL_API - , - _tryDenoise(0) -#endif - {} - -#define ZX_PROPERTY(TYPE, GETTER, SETTER) \ - TYPE GETTER() const noexcept { return _##GETTER; } \ - DecodeHints& SETTER(TYPE v)& { return _##GETTER = std::move(v), *this; } \ - DecodeHints&& SETTER(TYPE v)&& { return _##GETTER = std::move(v), std::move(*this); } - - /// Specify a set of BarcodeFormats that should be searched for, the default is all supported formats. - ZX_PROPERTY(BarcodeFormats, formats, setFormats) - - /// Spend more time to try to find a barcode; optimize for accuracy, not speed. - ZX_PROPERTY(bool, tryHarder, setTryHarder) - - /// Also try detecting code in 90, 180 and 270 degree rotated images. - ZX_PROPERTY(bool, tryRotate, setTryRotate) - - /// Also try detecting inverted ("reversed reflectance") codes if the format allows for those. - ZX_PROPERTY(bool, tryInvert, setTryInvert) - - /// 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) - - /// Set to true if the input contains nothing but a single perfectly aligned barcode (generated image) - ZX_PROPERTY(bool, isPure, setIsPure) - - /// Image size ( min(width, height) ) threshold at which to start downscaled scanning - // WARNING: this API is experimental and may change/disappear - ZX_PROPERTY(uint16_t, downscaleThreshold, setDownscaleThreshold) - - /// Scale factor used during downscaling, meaningful values are 2, 3 and 4 - // WARNING: this API is experimental and may change/disappear - ZX_PROPERTY(uint8_t, downscaleFactor, setDownscaleFactor) - - /// The number of scan lines in a linear barcode that have to be equal to accept the result, default is 2 - ZX_PROPERTY(uint8_t, minLineCount, setMinLineCount) - - /// The maximum number of symbols (barcodes) to detect / look for in the image with ReadBarcodes - ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) - - /// If true, the Code-39 reader will try to read extended mode. - ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode) - - /// Assume Code-39 codes employ a check digit and validate it. - ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum) - - /// Assume ITF codes employ a GS1 check digit and validate it. - ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum) - - /// If true, return the start and end chars in a Codabar barcode instead of stripping them. - ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd) - - /// If true, return the barcodes with errors as well (e.g. checksum errors, see @Result::error()) - ZX_PROPERTY(bool, returnErrors, setReturnErrors) - - /// Specify whether to ignore, read or require EAN-2/5 add-on symbols while scanning EAN/UPC codes - ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol) - - /// Specifies the TextMode that controls the return of the Result::text() function - ZX_PROPERTY(TextMode, textMode, setTextMode) - - /// Specifies fallback character set to use instead of auto-detecting it (when applicable) - ZX_PROPERTY(CharacterSet, characterSet, setCharacterSet) - DecodeHints& setCharacterSet(std::string_view v)& { return _characterSet = CharacterSetFromString(v), *this; } - DecodeHints&& setCharacterSet(std::string_view v) && { return _characterSet = CharacterSetFromString(v), std::move(*this); } - -#undef ZX_PROPERTY - - bool hasFormat(BarcodeFormats f) const noexcept { return _formats.testFlags(f) || _formats.empty(); } -}; - -} // ZXing +// TODO: remove this backward compatibility header once the deprecated name DecodeHints has been removed (3.0) diff --git a/core/src/DecoderResult.h b/core/src/DecoderResult.h index 02b2850841..66a6731158 100644 --- a/core/src/DecoderResult.h +++ b/core/src/DecoderResult.h @@ -8,6 +8,7 @@ #include "Content.h" #include "Error.h" +#include "JSON.h" #include "StructuredAppend.h" #include @@ -16,7 +17,10 @@ namespace ZXing { -class CustomData; +struct CustomData +{ + virtual ~CustomData() = default; +}; class DecoderResult { @@ -28,7 +32,8 @@ class DecoderResult bool _isMirrored = false; bool _readerInit = false; Error _error; - std::shared_ptr _extra; + std::string _json; + std::shared_ptr _customData; DecoderResult(const DecoderResult &) = delete; DecoderResult& operator=(const DecoderResult &) = delete; @@ -43,7 +48,7 @@ class DecoderResult bool isValid(bool includeErrors = false) const { - return includeErrors || (_content.symbology.code != 0 && !_error); + return (!_content.bytes.empty() && !_error) || (includeErrors && !!_error); } const Content& content() const & { return _content; } @@ -75,9 +80,13 @@ class DecoderResult ZX_PROPERTY(Error, error, setError) ZX_PROPERTY(bool, isMirrored, setIsMirrored) ZX_PROPERTY(bool, readerInit, setReaderInit) - ZX_PROPERTY(std::shared_ptr, extra, setExtra) - + ZX_PROPERTY(std::string, json, setJson) + ZX_PROPERTY(std::shared_ptr, customData, setCustomData) #undef ZX_PROPERTY + + template + DecoderResult&& addExtra(std::string_view key, T val, T ignore = {}) { _json += JsonProp(key, val, ignore); return std::move(*this); } + }; } // ZXing diff --git a/core/src/Error.cpp b/core/src/Error.cpp new file mode 100644 index 0000000000..c46a6c6232 --- /dev/null +++ b/core/src/Error.cpp @@ -0,0 +1,29 @@ +/* + * Copyright 2022 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#include "Error.h" + +namespace ZXing { + +std::string Error::location() const +{ + if (!_file) + return {}; + std::string file(_file); + return file.substr(file.find_last_of("/\\") + 1) + ":" + std::to_string(_line); +} + +std::string ToString(const Error& e) +{ + const char* name[] = {"", "FormatError", "ChecksumError", "Unsupported"}; + std::string ret = name[static_cast(e.type())]; + if (!e.msg().empty()) + ret += " (" + e.msg() + ")"; + if (auto location = e.location(); !location.empty()) + ret += " @ " + e.location(); + return ret; +} + +} diff --git a/core/src/Error.h b/core/src/Error.h index 07e7ff0390..fbccf9faa7 100644 --- a/core/src/Error.h +++ b/core/src/Error.h @@ -10,6 +10,24 @@ namespace ZXing { +/** + * @brief The Error class is a value type for the error() member of @Barcode + * + * The use-case of this class is to communicate whether or not a particular Barcode + * symbol is in error. It is (primarily) not meant to be thrown as an exception and + * therefore not derived from std::exception. The library code may throw (and catch!) + * objects of this class as a convenient means of flow control (c++23's std::expected + * will allow to replace those use-cases with something similarly convenient). In + * those situations, the author is advised to make sure any thrown Error object is + * caught before leaking into user/wrapper code, i.e. the functions of the public + * API should be considered `noexcept` with respect to this class. + * + * Looking at the implementation of std::runtime_exception, it might actually be of + * interest to replace the std::string msg member with a std::runtime_exception base + * class, thereby reducing sizeof(Error) by 16 bytes. This would be a breaking ABI + * change and would therefore have to wait for release 3.0. (TODO) + */ + class Error { public: @@ -18,13 +36,7 @@ class Error const std::string& msg() const noexcept { return _msg; } explicit operator bool() const noexcept { return _type != Type::None; } - std::string location() const - { - if (!_file) - return {}; - std::string file(_file); - return file.substr(file.find_last_of("/\\") + 1) + ":" + std::to_string(_line); - } + std::string location() const; Error() = default; Error(Type type, std::string msg = {}) : _msg(std::move(msg)), _type(type) {} @@ -56,15 +68,6 @@ inline bool operator!=(Error::Type t, const Error& e) noexcept { return !(t == e #define ChecksumError(...) Error(__FILE__, __LINE__, Error::Checksum, std::string(__VA_ARGS__)) #define UnsupportedError(...) Error(__FILE__, __LINE__, Error::Unsupported, std::string(__VA_ARGS__)) -inline std::string ToString(const Error& e) -{ - const char* name[] = {"", "FormatError", "ChecksumError", "Unsupported"}; - std::string ret = name[static_cast(e.type())]; - if (!e.msg().empty()) - ret += " (" + e.msg() + ")"; - if (auto location = e.location(); !location.empty()) - ret += " @ " + e.location(); - return ret; -} +std::string ToString(const Error& e); } diff --git a/core/src/GTIN.cpp b/core/src/GTIN.cpp index 256855a030..1c51bda2da 100644 --- a/core/src/GTIN.cpp +++ b/core/src/GTIN.cpp @@ -6,7 +6,7 @@ #include "GTIN.h" -#include "Result.h" +#include "Barcode.h" #include #include @@ -18,9 +18,9 @@ namespace ZXing::GTIN { struct CountryId { - int first; - int last; - const char *id; + uint16_t first; + uint16_t last; + const char id[3]; }; bool operator<(const CountryId& lhs, const CountryId& rhs) @@ -32,9 +32,9 @@ bool operator<(const CountryId& lhs, const CountryId& rhs) // and https://en.wikipedia.org/wiki/List_of_GS1_country_codes static const CountryId COUNTRIES[] = { // clang-format off - {1, 19, "US/CA"}, + {1, 19, "US"}, {30, 39, "US"}, - {60, 99, "US/CA"}, // Note 99 coupon identification + {60, 99, "US"}, // Note 99 coupon identification {100, 139, "US"}, {300, 379, "FR"}, // France (and Monaco according to Wikipedia) {380, 380, "BG"}, // Bulgaria @@ -73,7 +73,7 @@ static const CountryId COUNTRIES[] = { {531, 531, "MK"}, // North Macedonia {535, 535, "MT"}, // Malta {539, 539, "IE"}, // Ireland - {540, 549, "BE/LU"}, // Belgium & Luxembourg + {540, 549, "BE"}, // Belgium & Luxembourg {560, 560, "PT"}, // Portugal {569, 569, "IS"}, // Iceland {570, 579, "DK"}, // Denmark (and Faroe Islands and Greenland according to Wikipedia) @@ -194,19 +194,16 @@ std::string LookupCountryIdentifier(const std::string& GTIN, const BarcodeFormat if (size == 8 && format == BarcodeFormat::EAN8 && prefix <= 99) // Restricted Circulation Numbers return {}; - const auto it = std::lower_bound(std::begin(COUNTRIES), std::end(COUNTRIES), CountryId{0, prefix, nullptr}); + const auto it = std::lower_bound(std::begin(COUNTRIES), std::end(COUNTRIES), CountryId{0, narrow_cast(prefix), ""}); return it != std::end(COUNTRIES) && prefix >= it->first && prefix <= it->last ? it->id : std::string(); } -std::string EanAddOn(const Result& result) +std::string EanAddOn(const Barcode& barcode) { - if (!(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE | BarcodeFormat::EAN8) - .testFlag(result.format())) + if (barcode.symbologyIdentifier() != "]E3") return {}; - auto txt = result.bytes().asString(); - auto pos = txt.find(' '); - return pos != std::string::npos ? std::string(txt.substr(pos + 1)) : std::string(); + return std::string(barcode.bytes().asString().substr(barcode.format() == BarcodeFormat::EAN8 ? 8 : 13)); } std::string IssueNr(const std::string& ean2AddOn) diff --git a/core/src/GTIN.h b/core/src/GTIN.h index d56b604e5f..74df5e8645 100644 --- a/core/src/GTIN.h +++ b/core/src/GTIN.h @@ -7,16 +7,13 @@ #pragma once +#include "Barcode.h" #include "BarcodeFormat.h" #include "ZXAlgorithms.h" #include -namespace ZXing { - -class Result; - -namespace GTIN { +namespace ZXing::GTIN { template T ComputeCheckDigit(const std::basic_string& digits, bool skipTail = false) @@ -36,6 +33,7 @@ bool IsCheckDigitValid(const std::basic_string& s) return ComputeCheckDigit(s, true) == s.back(); } +//TODO: use std::string_view in 3.0 /** * Evaluate the prefix of the GTIN to estimate the country of origin. See * @@ -47,10 +45,9 @@ bool IsCheckDigitValid(const std::basic_string& s) */ std::string LookupCountryIdentifier(const std::string& GTIN, const BarcodeFormat format = BarcodeFormat::None); -std::string EanAddOn(const Result& result); +std::string EanAddOn(const Barcode& barcode); std::string IssueNr(const std::string& ean2AddOn); std::string Price(const std::string& ean5AddOn); -} // namespace GTIN -} // namespace ZXing +} // namespace ZXing::GTIN diff --git a/core/src/Generator.h b/core/src/Generator.h index 7a1fd179f2..a5083e9d15 100644 --- a/core/src/Generator.h +++ b/core/src/Generator.h @@ -5,8 +5,12 @@ #pragma once -#ifdef __cpp_impl_coroutine #ifdef __ANDROID__ +#include +#endif + +#ifdef __cpp_impl_coroutine +#if defined __ANDROID__ && __NDK_MAJOR__ < 26 // NDK 25.1.8937393 can compile this code with c++20 but needs a few tweaks: #include namespace std { @@ -25,7 +29,7 @@ namespace std { // this code is based on https://en.cppreference.com/w/cpp/coroutine/coroutine_handle#Example // but modified trying to prevent accidental copying of generated objects -#ifdef __ANDROID__ +#if defined __ANDROID__ && __NDK_MAJOR__ < 26 template #else template diff --git a/core/src/GenericGFPoly.cpp b/core/src/GenericGFPoly.cpp index 1ec9f84fd3..a624e487e2 100644 --- a/core/src/GenericGFPoly.cpp +++ b/core/src/GenericGFPoly.cpp @@ -19,18 +19,14 @@ namespace ZXing { int GenericGFPoly::evaluateAt(int a) const { - if (a == 0) - // Just return the x^0 coefficient + if (a == 0) // return the x^0 coefficient return constant(); - if (a == 1) - // Just the sum of the coefficients - return Reduce(_coefficients, 0, [](auto a, auto b) { return a ^ b; }); + if (a == 1) // return the sum of the coefficients + return Reduce(_coefficients, 0, [](auto s, auto c) { return s ^ c; }); - int result = _coefficients[0]; - for (size_t i = 1; i < _coefficients.size(); ++i) - result = _field->multiply(a, result) ^ _coefficients[i]; - return result; + return std::accumulate(_coefficients.begin(), _coefficients.end(), 0, + [this, a](auto s, auto c) { return _field->multiply(a, s) ^ c; }); } GenericGFPoly& GenericGFPoly::addOrSubtract(GenericGFPoly& other) diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index 44ca15810c..2777bb2283 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -8,11 +8,10 @@ #include "BitMatrix.h" #include "Pattern.h" +#include "ZXConfig.h" #include #include -#include -#include #include namespace ZXing { @@ -48,6 +47,8 @@ static void ThresholdSharpened(const ImageLineView in, int threshold, std::vecto static auto GenHistogram(const ImageLineView line) { + // This code causes about 20% of the total runtime on an AVX2 system for a EAN13 search on Lum input data. + // Trying to increase the performance by performing 2 or 4 "parallel" histograms helped nothing. Histogram res = {}; for (auto pix : line) res[pix >> LUMINANCE_SHIFT]++; @@ -110,12 +111,12 @@ bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& if (buffer.width() < 3) return false; // special casing the code below for a width < 3 makes no sense -#ifdef __AVX__ +#if defined(__AVX__) // or defined(__ARM_NEON) // 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 + // during the histogram calculation 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; + ZX_THREAD_LOCAL std::vector line; if (std::abs(buffer.pixStride()) > 4) { line.resize(lineView.size()); std::copy(lineView.begin(), lineView.end(), line.begin()); @@ -127,7 +128,7 @@ bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& if (threshold <= 0) return false; - thread_local std::vector binarized; + ZX_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); diff --git a/core/src/GridSampler.cpp b/core/src/GridSampler.cpp index 8b5cf9b336..626ce025e3 100644 --- a/core/src/GridSampler.cpp +++ b/core/src/GridSampler.cpp @@ -56,7 +56,15 @@ DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const R #ifdef PRINT_DEBUG log(p, 3); #endif +#if 0 + int sum = 0; + for (int dy = -1; dy <= 1; ++dy) + for (int dx = -1; dx <= 1; ++dx) + sum += image.get(p + PointF(dx, dy)); + if (sum >= 5) +#else if (image.get(p)) +#endif res.set(x, y); } } diff --git a/core/src/HRI.cpp b/core/src/HRI.cpp index d3a2687a4a..f264b608f8 100644 --- a/core/src/HRI.cpp +++ b/core/src/HRI.cpp @@ -10,29 +10,31 @@ #include "ZXAlgorithms.h" #include -#include +#include +#include namespace ZXing { struct AiInfo { - std::string_view aiPrefix; - int _fieldSize; // if negative, the length is variable and abs(length) give the max size + const char aiPrefix[5]; + int8_t _fieldSize; // if negative, the length is variable and abs(length) give the max size bool isVariableLength() const noexcept { return _fieldSize < 0; } int fieldSize() const noexcept { return std::abs(_fieldSize); } int aiSize() const { - if ((aiPrefix[0] == '3' && Contains("1234569", aiPrefix[1])) || aiPrefix == "703" || aiPrefix == "723") + using namespace std::literals; + if ((aiPrefix[0] == '3' && Contains("1234569", aiPrefix[1])) || aiPrefix == "703"sv || aiPrefix == "723"sv) return 4; else - return Size(aiPrefix); + return strlen(aiPrefix); } }; -// GS1 General Specifications Release 22.0 (Jan 22, 2022) +// https://github.com/gs1/gs1-syntax-dictionary 2024-06-10 static const AiInfo aiInfos[] = { -// TWO_DIGIT_DATA_LENGTH +//TWO_DIGIT_DATA_LENGTH { "00", 18 }, { "01", 14 }, { "02", 14 }, @@ -52,7 +54,6 @@ static const AiInfo aiInfos[] = { { "30", -8 }, { "37", -8 }, - //internal company codes { "90", -30 }, { "91", -90 }, { "92", -90 }, @@ -177,6 +178,7 @@ static const AiInfo aiInfos[] = { { "4306", -70 }, { "4307", 2 }, { "4308", -30 }, + { "4309", 20 }, { "4310", -35 }, { "4311", -35 }, { "4312", -70 }, @@ -194,6 +196,10 @@ static const AiInfo aiInfos[] = { { "4324", 10 }, { "4325", 10 }, { "4326", 6 }, + { "4330", -7 }, + { "4331", -7 }, + { "4332", -7 }, + { "4333", -7 }, { "7001", 13 }, { "7002", -30 }, @@ -205,12 +211,25 @@ static const AiInfo aiInfos[] = { { "7008", -3 }, { "7009", -10 }, { "7010", -2 }, + { "7011", -10 }, { "7020", -20 }, { "7021", -20 }, { "7022", -20 }, { "7023", -30 }, { "7040", 4 }, { "7240", -20 }, + { "7241", 2 }, + { "7242", -25 }, + { "7250", 8 }, + { "7251", 12 }, + { "7252", 1 }, + { "7253", -40 }, + { "7254", -40 }, + { "7255", -10 }, + { "7256", -90 }, + { "7257", -70 }, + { "7258", 3 }, + { "7259", -40 }, { "8001", 14 }, { "8002", -20 }, @@ -230,6 +249,7 @@ static const AiInfo aiInfos[] = { { "8019", -10 }, { "8020", -25 }, { "8026", 18 }, + { "8030", -90 }, { "8110", -70 }, { "8111", 4 }, { "8112", -70 }, @@ -284,26 +304,20 @@ std::string HRIFromGS1(std::string_view gs1) return res; } -#if __cplusplus > 201703L -std::ostream& operator<<(std::ostream& os, const char8_t* str) -{ - return os << reinterpret_cast(str); -} -#endif - std::string HRIFromISO15434(std::string_view str) { // Use available unicode symbols to simulate sub- and superscript letters as specified in // ISO/IEC 15434:2019(E) 6. Human readable representation - std::ostringstream oss; + std::string res; + res.reserve(str.size()); for (char c : str) { #if 1 if (0 <= c && c <= 0x20) - oss << "\xe2\x90" << char(0x80 + c); // Unicode Block “Control Pictures”: 0x2400 + (res += "\xe2\x90") += char(0x80 + c); // Unicode Block “Control Pictures”: 0x2400 else - oss << c; + res += c; #else switch (c) { case 4: oss << u8"\u1d31\u1d52\u209c"; break; // EOT @@ -316,7 +330,7 @@ std::string HRIFromISO15434(std::string_view str) #endif } - return oss.str(); + return res; } } // namespace ZXing diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index 4aaec4ae85..8f8a6f90cc 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -9,18 +9,20 @@ #include "BitMatrix.h" #include "Matrix.h" +#include #include +#include #include -#include -#include + +#define USE_NEW_ALGORITHM namespace ZXing { // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. // So this is the smallest dimension in each axis we can accept. -static const int BLOCK_SIZE = 8; -static const int MINIMUM_DIMENSION = BLOCK_SIZE * 5; -static const int MIN_DYNAMIC_RANGE = 24; +static constexpr int BLOCK_SIZE = 8; +static constexpr int WINDOW_SIZE = BLOCK_SIZE * (1 + 2 * 2); +static constexpr int MIN_DYNAMIC_RANGE = 24; HybridBinarizer::HybridBinarizer(const ImageView& iv) : GlobalHistogramBinarizer(iv) {} @@ -43,15 +45,34 @@ bool HybridBinarizer::getPatternRow(int row, int rotation, PatternRow& res) cons #endif } +using T_t = uint8_t; + +/** +* Applies a single threshold to a block of pixels. +*/ +static void ThresholdBlock(const uint8_t* __restrict luminances, int xoffset, int yoffset, T_t 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) * BitMatrix::SET_V; + } +} + +#ifndef USE_NEW_ALGORITHM + /** * 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* __restrict luminances, int subWidth, int subHeight, int width, int height, +static Matrix CalculateBlackPoints(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height, int rowStride) { - Matrix blackPoints(subWidth, subHeight); + Matrix blackPoints(subWidth, subHeight); for (int y = 0; y < subHeight; y++) { int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE); @@ -112,32 +133,21 @@ static Matrix CalculateBlackPoints(const uint8_t* __restrict luminances, in return blackPoints; } - -/** -* Applies a single threshold to a block of pixels. -*/ -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) * BitMatrix::SET_V; - } -} - /** * For each block in the image, calculate the average black point using a 5x5 grid * 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* __restrict luminances, int subWidth, int subHeight, int width, - int height, int rowStride, const Matrix& blackPoints) + int height, int rowStride, const Matrix& blackPoints) { auto matrix = std::make_shared(width, height); +#ifdef PRINT_DEBUG + Matrix out(width, height); + Matrix out2(width, height); +#endif + for (int y = 0; y < subHeight; y++) { int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE); for (int x = 0; x < subWidth; x++) { @@ -152,22 +162,148 @@ static std::shared_ptr CalculateMatrix(const uint8_t* __restrict lumi } int average = sum / 25; ThresholdBlock(luminances, xoffset, yoffset, average, rowStride, *matrix); + +#ifdef PRINT_DEBUG + for (int yy = 0; yy < 8; ++yy) + for (int xx = 0; xx < 8; ++xx) { + out.set(xoffset + xx, yoffset + yy, blackPoints(x, y)); + out2.set(xoffset + xx, yoffset + yy, average); + } +#endif + } + } + +#ifdef PRINT_DEBUG + std::ofstream file("thresholds.pnm"); + file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file.write(reinterpret_cast(out.data()), out.size()); + std::ofstream file2("thresholds_avg.pnm"); + file2 << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file2.write(reinterpret_cast(out2.data()), out2.size()); +#endif + + return matrix; +} + +#else + +// Subdivide the image in blocks of BLOCK_SIZE and calculate one treshold value per block as +// (max - min > MIN_DYNAMIC_RANGE) ? (max + min) / 2 : 0 +static Matrix BlockThresholds(const ImageView iv) +{ + int subWidth = (iv.width() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(width/BS) + int subHeight = (iv.height() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(height/BS) + + Matrix thresholds(subWidth, subHeight); + + for (int y = 0; y < subHeight; y++) { + int y0 = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE); + for (int x = 0; x < subWidth; x++) { + int x0 = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE); + uint8_t min = 255; + uint8_t max = 0; + for (int yy = 0; yy < BLOCK_SIZE; yy++) { + auto line = iv.data(x0, y0 + yy); + for (int xx = 0; xx < BLOCK_SIZE; xx++) + UpdateMinMax(min, max, line[xx]); + } + + thresholds(x, y) = (max - min > MIN_DYNAMIC_RANGE) ? (int(max) + min) / 2 : 0; + } + } + + return thresholds; +} + +// Apply gaussian-like smoothing filter over all non-zero thresholds and fill any remainig gaps with nearest neighbor +static Matrix SmoothThresholds(Matrix&& in) +{ + Matrix out(in.width(), in.height()); + + constexpr int R = WINDOW_SIZE / BLOCK_SIZE / 2; + for (int y = 0; y < in.height(); y++) { + for (int x = 0; x < in.width(); x++) { + int left = std::clamp(x, R, in.width() - R - 1); + int top = std::clamp(y, R, in.height() - R - 1); + + int sum = in(x, y) * 2; + int n = (sum > 0) * 2; + auto add = [&](int x, int y) { + int t = in(x, y); + sum += t; + n += t > 0; + }; + + for (int dy = -R; dy <= R; ++dy) + for (int dx = -R; dx <= R; ++dx) + add(left + dx, top + dy); + + out(x, y) = n > 0 ? sum / n : 0; + } + } + + // flood fill any remaing gaps of (very large) no-contrast regions + auto last = out.begin() - 1; + for (auto* i = out.begin(); i != out.end(); ++i) { + if (*i) { + if (last != i - 1) + std::fill(last + 1, i, *i); + last = i; } } + std::fill(last + 1, out.end(), *(std::max(last, out.begin()))); + + return out; +} + +static std::shared_ptr ThresholdImage(const ImageView iv, const Matrix& thresholds) +{ + auto matrix = std::make_shared(iv.width(), iv.height()); + +#ifdef PRINT_DEBUG + Matrix out(iv.width(), iv.height()); +#endif + + for (int y = 0; y < thresholds.height(); y++) { + int yoffset = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE); + for (int x = 0; x < thresholds.width(); x++) { + int xoffset = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE); + ThresholdBlock(iv.data(), xoffset, yoffset, thresholds(x, y), iv.rowStride(), *matrix); + +#ifdef PRINT_DEBUG + for (int yy = 0; yy < 8; ++yy) + for (int xx = 0; xx < 8; ++xx) + out.set(xoffset + xx, yoffset + yy, thresholds(x, y)); +#endif + } + } + +#ifdef PRINT_DEBUG + std::ofstream file("thresholds_new.pnm"); + file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file.write(reinterpret_cast(out.data()), out.size()); +#endif return matrix; } +#endif + std::shared_ptr HybridBinarizer::getBlackMatrix() const { - if (width() >= MINIMUM_DIMENSION && height() >= MINIMUM_DIMENSION) { - const uint8_t* luminances = _buffer.data(0, 0); + if (width() >= WINDOW_SIZE && height() >= WINDOW_SIZE) { +#ifdef USE_NEW_ALGORITHM + auto thrs = SmoothThresholds(BlockThresholds(_buffer)); + return ThresholdImage(_buffer, thrs); +#else + const uint8_t* luminances = _buffer.data(); int subWidth = (width() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(width/BS) int subHeight = (height() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(height/BS) auto blackPoints = CalculateBlackPoints(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride()); return CalculateMatrix(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride(), blackPoints); +#endif } else { // If the image is too small, fall back to the global histogram approach. return GlobalHistogramBinarizer::getBlackMatrix(); diff --git a/core/src/ImageView.h b/core/src/ImageView.h index bee631b67a..4d145eb6b6 100644 --- a/core/src/ImageView.h +++ b/core/src/ImageView.h @@ -7,6 +7,9 @@ #include #include +#include +#include +#include namespace ZXing { @@ -14,12 +17,17 @@ enum class ImageFormat : uint32_t { None = 0, Lum = 0x01000000, + LumA = 0x02000000, RGB = 0x03000102, BGR = 0x03020100, - RGBX = 0x04000102, - XRGB = 0x04010203, - BGRX = 0x04020100, - XBGR = 0x04030201, + RGBA = 0x04000102, + ARGB = 0x04010203, + BGRA = 0x04020100, + ABGR = 0x04030201, + RGBX [[deprecated("use RGBA")]] = RGBA, + XRGB [[deprecated("use ARGB")]] = ARGB, + BGRX [[deprecated("use BGRA")]] = BGRA, + XBGR [[deprecated("use ABGR")]] = ABGR, }; constexpr inline int PixStride(ImageFormat format) { return (static_cast(format) >> 3*8) & 0xFF; } @@ -42,10 +50,14 @@ class ImageView { protected: const uint8_t* _data = nullptr; - ImageFormat _format; + ImageFormat _format = ImageFormat::None; int _width = 0, _height = 0, _pixStride = 0, _rowStride = 0; public: + /** ImageView default constructor creates a 'null' image view + */ + ImageView() = default; + /** * ImageView constructor * @@ -63,7 +75,29 @@ class ImageView _height(height), _pixStride(pixStride ? pixStride : PixStride(format)), _rowStride(rowStride ? rowStride : width * _pixStride) - {} + { + // TODO: [[deprecated]] this check is to prevent exising code from suddenly throwing, remove in 3.0 + if (_data == nullptr && _width == 0 && _height == 0 && rowStride == 0 && pixStride == 0) { + fprintf(stderr, "zxing-cpp deprecation warning: ImageView(nullptr, ...) will throw in the future, use ImageView()\n"); + return; + } + + if (_data == nullptr) + throw std::invalid_argument("Can not construct an ImageView from a NULL pointer"); + + if (_width <= 0 || _height <= 0) + throw std::invalid_argument("Neither width nor height of ImageView can be less or equal to 0"); + } + + /** + * ImageView constructor with bounds checking + */ + ImageView(const uint8_t* data, int size, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) + : ImageView(data, width, height, format, rowStride, pixStride) + { + if (_rowStride < 0 || _pixStride < 0 || size < _height * _rowStride) + throw std::invalid_argument("ImageView parameters are inconsistent (out of bounds)"); + } int width() const { return _width; } int height() const { return _height; } @@ -71,12 +105,13 @@ class ImageView int rowStride() const { return _rowStride; } ImageFormat format() const { return _format; } + const uint8_t* data() const { return _data; } const uint8_t* data(int x, int y) const { return _data + y * _rowStride + x * _pixStride; } ImageView cropped(int left, int top, int width, int height) const { - left = std::max(0, left); - top = std::max(0, top); + left = std::clamp(left, 0, _width - 1); + top = std::clamp(top, 0, _height - 1); width = width <= 0 ? (_width - left) : std::min(_width - left, width); height = height <= 0 ? (_height - top) : std::min(_height - top, height); return {data(left, top), width, height, _format, _rowStride, _pixStride}; @@ -99,5 +134,15 @@ class ImageView }; +class Image : public ImageView +{ + std::unique_ptr _memory; + Image(std::unique_ptr&& data, int w, int h, ImageFormat f) : ImageView(data.get(), w, h, f), _memory(std::move(data)) {} + +public: + Image() = default; + Image(int w, int h, ImageFormat f = ImageFormat::Lum) : Image(std::make_unique(w * h * PixStride(f)), w, h, f) {} +}; + } // ZXing diff --git a/core/src/JSON.cpp b/core/src/JSON.cpp new file mode 100644 index 0000000000..1c65bbdf9f --- /dev/null +++ b/core/src/JSON.cpp @@ -0,0 +1,159 @@ +/* +* Copyright 2025 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "JSON.h" + +#include +#include + +// This code is trying to find the value of a key-value pair in a string of those. +// The input could be valid JSON, like '{"key": "val"}' or a stipped down version like +// 'key:val'. This is also compatible with the string serialization of a python dictionary. +// This could easily be done with the following regex (see below). +// But using std::regex adds 140k+ to the binary size. ctre is _very_ nice to use and has +// the same binary footprint as the hand-roled code below! But I don't want to add +// 5k lines of c++ code for that. + +// #define ZXING_USE_CTRE +#ifdef ZXING_USE_CTRE +#pragma GCC diagnostic ignored "-Wundef" // see https://github.com/hanickadot/compile-time-regular-expressions/issues/340 +#include "ctre.hpp" +#pragma GCC diagnostic error "-Wundef" + +static constexpr auto PATTERN = + ctll::fixed_string{R"(["']?([[:alpha:]][[:alnum:]]*)["']?\s*(?:[:]\s*["']?([[:alnum:]]+)["']?)?(?:,|\}|$))"}; +#else +#include "ZXAlgorithms.h" +#endif + +namespace ZXing { + +// Trim whitespace and quotes/braces from both ends +inline std::string_view Trim(std::string_view sv) +{ + constexpr auto ws = " \t\n\r\"'{}"; + while (sv.size() && Contains(ws, sv.back())) + sv.remove_suffix(1); + while (sv.size() && Contains(ws, sv.front())) + sv.remove_prefix(1); + return sv.empty() ? std::string_view() : sv; +} + +inline bool IsEqualCaseInsensitive(std::string_view sv1, std::string_view sv2) +{ + return sv1.size() == sv2.size() + && std::equal(sv1.begin(), sv1.end(), sv2.begin(), [](uint8_t a, uint8_t b) { return std::tolower(a) == std::tolower(b); }); +} + +std::string_view JsonGetStr(std::string_view json, std::string_view key) +{ +#ifdef ZXING_USE_CTRE + for (auto [ma, mk, mv] : ctre::search_all(json)) + if (IsEqualCaseInsensitive(key, mk)) + return mv.size() ? mv.to_view() : std::string_view(mk.data(), 0); + + return {}; +#else + json = Trim(json); + + while (!json.empty()) { + auto posComma = json.find(','); + auto pair = Trim(json.substr(0, posComma)); + + if (IsEqualCaseInsensitive(pair, key)) + return {pair.data(), 0}; // return non-null data to signal that we found the key + + auto posColon = pair.find(':'); + + if (posColon != std::string_view::npos && IsEqualCaseInsensitive(Trim(pair.substr(0, posColon)), key)) + return Trim(pair.substr(posColon + 1)); + + json = (posComma == std::string_view::npos) ? std::string_view{} : json.substr(posComma + 1); + } + + return {}; +#endif +} + +std::string JsonEscapeStr(std::string_view str) +{ + std::string res; + res.reserve(str.size() + 10); + + for (unsigned char c : str) { + switch (c) { + case '\"': res += "\\\""; break; + case '\\': res += "\\\\"; break; + case '\b': res += "\\b"; break; + case '\f': res += "\\f"; break; + case '\n': res += "\\n"; break; + case '\r': res += "\\r"; break; + case '\t': res += "\\t"; break; + default: + if (c <= 0x1F) { + char buf[7]; + std::snprintf(buf, sizeof(buf), "\\u%04X", c); + res += buf; + } else { + res += c; + } + break; + } + } + + return res; +} + +std::string JsonUnEscapeStr(std::string_view str) +{ + std::string res; + res.reserve(str.size()); + + for (size_t i = 0; i < str.size(); ++i) { + char c = str[i]; + if (c == '\\') { + if (++i >= str.size()) + throw std::runtime_error("Invalid escape sequence"); + + char esc = str[i]; + switch (esc) { + case '"': res += '\"'; break; + case '\\': res += '\\'; break; + case '/': res += '/'; break; + case 'b': res += '\b'; break; + case 'f': res += '\f'; break; + case 'n': res += '\n'; break; + case 'r': res += '\r'; break; + case 't': res += '\t'; break; + case 'u': { + if (i + 4 >= str.size()) + throw std::runtime_error("Incomplete \\u escape"); + + uint32_t code = 0; + auto first = str.data() + i + 1; + auto last = first + 4; + + auto [ptr, ec] = std::from_chars(first, last, code, 16); + if (ec != std::errc() || ptr != last) + throw std::runtime_error("Failed to parse hex code"); + + if (code > 0x1F) + throw std::runtime_error("Unexpected code point in \\u escape"); + + res += static_cast(code); + i += 4; + break; + } + default: throw std::runtime_error("Unknown escape sequence"); + } + } else { + res += c; + } + } + + return res; +} + +} // ZXing diff --git a/core/src/JSON.h b/core/src/JSON.h new file mode 100644 index 0000000000..0124db7251 --- /dev/null +++ b/core/src/JSON.h @@ -0,0 +1,57 @@ +/* +* Copyright 2025 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ZXAlgorithms.h" + +#include +#include +#include +#include + +namespace ZXing { + +std::string JsonEscapeStr(std::string_view str); +std::string JsonUnEscapeStr(std::string_view str); + +template +inline std::string JsonProp(std::string_view key, T val, T ignore = {}) +{ + if (val == ignore) + return {}; + + #define ZX_JSON_KEY_VAL(...) StrCat("\"", key, "\":", __VA_ARGS__, ',') + if constexpr (std::is_same_v) + return val ? ZX_JSON_KEY_VAL("true") : ""; + else if constexpr (std::is_arithmetic_v) + return ZX_JSON_KEY_VAL(std::to_string(val)); + else if constexpr (std::is_convertible_v) + return ZX_JSON_KEY_VAL("\"" , JsonEscapeStr(val), "\""); + else + static_assert("unsupported JSON value type"); + #undef ZX_JSON_KEY_VAL +} + +std::string_view JsonGetStr(std::string_view json, std::string_view key); + +template +inline std::optional JsonGet(std::string_view json, std::string_view key) +{ + auto str = JsonGetStr(json, key); + if (!str.data()) + return std::nullopt; + + if constexpr (std::is_same_v) + return str.empty() || Contains("1tT", str.front()) ? std::optional(true) : std::nullopt; + else if constexpr (std::is_arithmetic_v) + return str.empty() ? std::nullopt : std::optional(FromString(str)); + else if constexpr (std::is_same_v) + return JsonUnEscapeStr(str); + else + static_assert("unsupported JSON value type"); +} + +} // ZXing diff --git a/core/src/Matrix.h b/core/src/Matrix.h index 36d4a0efe2..b6f8bc2150 100644 --- a/core/src/Matrix.h +++ b/core/src/Matrix.h @@ -40,7 +40,7 @@ class Matrix #endif Matrix(int width, int height, value_t val = {}) : _width(width), _height(height), _data(_width * _height, val) { if (width != 0 && Size(_data) / width != height) - throw std::invalid_argument("invalid size: width * height is too big"); + throw std::invalid_argument("Invalid size: width * height is too big"); } Matrix(Matrix&&) noexcept = default; @@ -102,6 +102,14 @@ class Matrix return _data.data() + _width * _height; } + value_t* begin() { + return _data.data(); + } + + value_t* end() { + return _data.data() + _width * _height; + } + void clear(value_t value = {}) { std::fill(_data.begin(), _data.end(), value); } diff --git a/core/src/MultiFormatReader.cpp b/core/src/MultiFormatReader.cpp index 58a78cc0ca..fedc9e3a99 100644 --- a/core/src/MultiFormatReader.cpp +++ b/core/src/MultiFormatReader.cpp @@ -8,7 +8,7 @@ #include "BarcodeFormat.h" #include "BinaryBitmap.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "aztec/AZReader.h" #include "datamatrix/DMReader.h" #include "maxicode/MCReader.h" @@ -20,56 +20,58 @@ namespace ZXing { -MultiFormatReader::MultiFormatReader(const DecodeHints& hints) : _hints(hints) +MultiFormatReader::MultiFormatReader(const ReaderOptions& opts) : _opts(opts) { - auto formats = hints.formats().empty() ? BarcodeFormat::Any : hints.formats(); + auto formats = opts.formats().empty() ? BarcodeFormat::Any : opts.formats(); // Put linear readers upfront in "normal" mode - if (formats.testFlags(BarcodeFormat::LinearCodes) && !hints.tryHarder()) - _readers.emplace_back(new OneD::Reader(hints)); + if (formats.testFlags(BarcodeFormat::LinearCodes) && !opts.tryHarder()) + _readers.emplace_back(new OneD::Reader(opts)); - if (formats.testFlags(BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode)) - _readers.emplace_back(new QRCode::Reader(hints, true)); + if (formats.testFlags(BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode | BarcodeFormat::RMQRCode)) + _readers.emplace_back(new QRCode::Reader(opts, true)); if (formats.testFlag(BarcodeFormat::DataMatrix)) - _readers.emplace_back(new DataMatrix::Reader(hints, true)); + _readers.emplace_back(new DataMatrix::Reader(opts, true)); if (formats.testFlag(BarcodeFormat::Aztec)) - _readers.emplace_back(new Aztec::Reader(hints, true)); + _readers.emplace_back(new Aztec::Reader(opts, true)); if (formats.testFlag(BarcodeFormat::PDF417)) - _readers.emplace_back(new Pdf417::Reader(hints)); + _readers.emplace_back(new Pdf417::Reader(opts)); if (formats.testFlag(BarcodeFormat::MaxiCode)) - _readers.emplace_back(new MaxiCode::Reader(hints)); + _readers.emplace_back(new MaxiCode::Reader(opts)); // At end in "try harder" mode - if (formats.testFlags(BarcodeFormat::LinearCodes) && hints.tryHarder()) - _readers.emplace_back(new OneD::Reader(hints)); + if (formats.testFlags(BarcodeFormat::LinearCodes) && opts.tryHarder()) + _readers.emplace_back(new OneD::Reader(opts)); } MultiFormatReader::~MultiFormatReader() = default; -Result -MultiFormatReader::read(const BinaryBitmap& image) const +Barcode MultiFormatReader::read(const BinaryBitmap& image) const { - Result r; + Barcode r; for (const auto& reader : _readers) { r = reader->decode(image); if (r.isValid()) return r; } - return _hints.returnErrors() ? r : Result(); + return _opts.returnErrors() ? r : Barcode(); } -Results MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbols) const +Barcodes MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbols) const { - std::vector res; + Barcodes res; for (const auto& reader : _readers) { if (image.inverted() && !reader->supportsInversion) continue; auto r = reader->decode(image, maxSymbols); - if (!_hints.returnErrors()) { - //TODO: C++20 res.erase_if() - auto it = std::remove_if(res.begin(), res.end(), [](auto&& r) { return !r.isValid(); }); - res.erase(it, res.end()); + if (!_opts.returnErrors()) { +#ifdef __cpp_lib_erase_if + std::erase_if(r, [](auto&& s) { return !s.isValid(); }); +#else + auto it = std::remove_if(r.begin(), r.end(), [](auto&& s) { return !s.isValid(); }); + r.erase(it, r.end()); +#endif } maxSymbols -= Size(r); res.insert(res.end(), std::move_iterator(r.begin()), std::move_iterator(r.end())); @@ -77,8 +79,8 @@ Results MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbol break; } - // sort results based on their position on the image - std::sort(res.begin(), res.end(), [](const Result& l, const Result& r) { + // sort barcodes based on their position on the image + std::sort(res.begin(), res.end(), [](const Barcode& l, const Barcode& r) { auto lp = l.position().topLeft(); auto rp = r.position().topLeft(); return lp.y < rp.y || (lp.y == rp.y && lp.x < rp.x); diff --git a/core/src/MultiFormatReader.h b/core/src/MultiFormatReader.h index 4765b1574a..17e3229e7a 100644 --- a/core/src/MultiFormatReader.h +++ b/core/src/MultiFormatReader.h @@ -6,41 +6,32 @@ #pragma once -#include "Result.h" +#include "Barcode.h" #include #include namespace ZXing { -class Result; class Reader; class BinaryBitmap; -class DecodeHints; - -/** -* MultiFormatReader is a convenience class and the main entry point into the library for most uses. -* By default it attempts to decode all barcode formats that the library supports. Optionally, you -* can provide a hints object to request different behavior, for example only decoding QR codes. -* -* @author Sean Owen -* @author dswitkin@google.com (Daniel Switkin) -*/ +class ReaderOptions; + class MultiFormatReader { public: - explicit MultiFormatReader(const DecodeHints& hints); - explicit MultiFormatReader(DecodeHints&& hints) = delete; + explicit MultiFormatReader(const ReaderOptions& opts); + explicit MultiFormatReader(ReaderOptions&& opts) = delete; ~MultiFormatReader(); - Result read(const BinaryBitmap& image) const; + Barcode read(const BinaryBitmap& image) const; // WARNING: this API is experimental and may change/disappear - Results readMultiple(const BinaryBitmap& image, int maxSymbols = 0xFF) const; + Barcodes readMultiple(const BinaryBitmap& image, int maxSymbols = 0xFF) const; private: std::vector> _readers; - const DecodeHints& _hints; + const ReaderOptions& _opts; }; } // ZXing diff --git a/core/src/MultiFormatWriter.cpp b/core/src/MultiFormatWriter.cpp index b278d848dd..14124a3e8c 100644 --- a/core/src/MultiFormatWriter.cpp +++ b/core/src/MultiFormatWriter.cpp @@ -49,9 +49,15 @@ MultiFormatWriter::encode(const std::wstring& contents, int width, int height) c return exec0(std::move(writer)); }; + auto exec2 = [&](auto&& writer) { + if (_encoding != CharacterSet::Unknown) + writer.setEncoding(_encoding); + return exec0(std::move(writer)); + }; + switch (_format) { case BarcodeFormat::Aztec: return exec1(Aztec::Writer(), AztecEccLevel); - case BarcodeFormat::DataMatrix: return exec0(DataMatrix::Writer()); + case BarcodeFormat::DataMatrix: return exec2(DataMatrix::Writer()); case BarcodeFormat::PDF417: return exec1(Pdf417::Writer(), Pdf417EccLevel); case BarcodeFormat::QRCode: return exec1(QRCode::Writer(), QRCodeEccLevel); case BarcodeFormat::Codabar: return exec0(OneD::CodabarWriter()); @@ -63,7 +69,7 @@ MultiFormatWriter::encode(const std::wstring& contents, int width, int height) c case BarcodeFormat::ITF: return exec0(OneD::ITFWriter()); case BarcodeFormat::UPCA: return exec0(OneD::UPCAWriter()); case BarcodeFormat::UPCE: return exec0(OneD::UPCEWriter()); - default: throw std::invalid_argument(std::string("Unsupported format: ") + ToString(_format)); + default: throw std::invalid_argument("Unsupported format: " + ToString(_format)); } } diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 3d4cbc3ac9..cf6963c25f 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -15,7 +15,6 @@ #include #include #include -#include #include namespace ZXing { @@ -59,13 +58,13 @@ class PatternView return _data[i]; } - int sum(int n = 0) const { return std::accumulate(_data, _data + (n == 0 ? _size : n), 0); } + int sum(int n = 0) const { return Reduce(_data, _data + (n == 0 ? _size : n)); } int size() const { return _size; } // index is the number of bars and spaces from the first bar to the current position int index() const { return narrow_cast(_data - _base) - 1; } - int pixelsInFront() const { return std::accumulate(_base, _data, 0); } - int pixelsTillEnd() const { return std::accumulate(_base, _data + _size, 0) - 1; } + int pixelsInFront() const { return Reduce(_base, _data); } + int pixelsTillEnd() const { return Reduce(_base, _data + _size) - 1; } bool isAtFirstBar() const { return _data == _base + 1; } bool isAtLastBar() const { return _data + _size == _end - 1; } bool isValid(int n) const { return _data && _data >= _base && _data + n <= _end; } @@ -139,7 +138,7 @@ struct BarAndSpace using BarAndSpaceI = BarAndSpace; -template +template constexpr auto BarAndSpaceSum(const T* view) noexcept { BarAndSpace res; @@ -151,9 +150,9 @@ constexpr auto BarAndSpaceSum(const T* view) noexcept /** * @brief FixedPattern describes a compile-time constant (start/stop) pattern. * - * @param N number of bars/spaces - * @param SUM sum over all N elements (size of pattern in modules) - * @param IS_SPARCE whether or not the pattern contains '0's denoting 'wide' bars/spaces + * N = number of bars/spaces + * SUM = sum over all N elements (size of pattern in modules) + * IS_SPARCE = whether or not the pattern contains '0's denoting 'wide' bars/spaces */ template struct FixedPattern @@ -163,22 +162,20 @@ 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); } + constexpr BarAndSpace sums() const noexcept { return BarAndSpaceSum(_data); } }; template using FixedSparcePattern = FixedPattern; template -float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, - float minQuietZone = 0, float moduleSizeRef = 0) +double IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, + double minQuietZone = 0, double moduleSizeRef = 0) { if constexpr (E2E) { - using float_t = double; - - auto widths = BarAndSpaceSum(view.data()); + auto widths = BarAndSpaceSum(view.data()); auto sums = pattern.sums(); - BarAndSpace modSize = {widths[0] / sums[0], widths[1] / sums[1]}; + 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 @@ -187,21 +184,20 @@ float IsPattern(const PatternView& view, const FixedPattern& pa if (minQuietZone && spaceInPixel < minQuietZone * modSize.space) return 0; - const BarAndSpace thr = {modSize[0] * .75 + .5, modSize[1] / (2 + (LEN < 6)) + .5}; + const BarAndSpace thr = {modSize[0] * .75 + .5, modSize[1] * .5 + .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; + return (modSize[0] + modSize[1]) / 2; } - int width = view.sum(LEN); + double width = view.sum(LEN); if (SUM > LEN && width < SUM) return 0; - const float moduleSize = (float)width / SUM; + const auto moduleSize = width / SUM; if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1) return 0; @@ -211,7 +207,7 @@ float IsPattern(const PatternView& view, const FixedPattern& pa // 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 + E2E * 0.25f) + 0.5f; + const auto threshold = moduleSizeRef * (0.5 + E2E * 0.25) + 0.5; for (int x = 0; x < LEN; ++x) if (std::abs(view[x] - pattern[x] * moduleSizeRef) > threshold) @@ -221,15 +217,15 @@ float IsPattern(const PatternView& view, const FixedPattern& pa } template -float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, - float minQuietZone = 0, float moduleSizeRef = 0) +double IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, + double minQuietZone = 0, double moduleSizeRef = 0) { // pattern contains the indices with the bars/spaces that need to be equally wide - int width = 0; + double width = 0; for (int x = 0; x < SUM; ++x) width += view[pattern[x]]; - const float moduleSize = (float)width / SUM; + const auto moduleSize = width / SUM; if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1) return 0; @@ -239,7 +235,7 @@ float IsPattern(const PatternView& view, const FixedPattern& patte // 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 auto threshold = moduleSizeRef * (0.5 + RELAXED_THRESHOLD * 0.25) + 0.5; for (int x = 0; x < SUM; ++x) if (std::abs(view[pattern[x]] - moduleSizeRef) > threshold) @@ -249,8 +245,8 @@ float IsPattern(const PatternView& view, const FixedPattern& patte } template -bool IsRightGuard(const PatternView& view, const FixedPattern& pattern, float minQuietZone, - float moduleSizeRef = 0.f) +bool IsRightGuard(const PatternView& view, const FixedPattern& pattern, double minQuietZone, + double moduleSizeRef = 0) { int spaceInPixel = view.isAtLastBar() ? std::numeric_limits::max() : *view.end(); return IsPattern(view, pattern, spaceInPixel, minQuietZone, moduleSizeRef) != 0; @@ -274,7 +270,7 @@ PatternView FindLeftGuard(const PatternView& view, int minSize, Pred isGuard) template PatternView FindLeftGuard(const PatternView& view, int minSize, const FixedPattern& pattern, - float minQuietZone) + double minQuietZone) { return FindLeftGuard(view, std::max(minSize, LEN), [&pattern, minQuietZone](const PatternView& window, int spaceInPixel) { @@ -282,15 +278,16 @@ PatternView FindLeftGuard(const PatternView& view, int minSize, const FixedPatte }); } -template -std::array NormalizedE2EPattern(const PatternView& view) +template +std::array NormalizedE2EPattern(const PatternView& view, int mods, bool reverse = false) { - float moduleSize = static_cast(view.sum(LEN)) / SUM; + double moduleSize = static_cast(view.sum(LEN)) / mods; std::array e2e; for (int i = 0; i < LEN - 2; i++) { - float v = (view[i] + view[i + 1]) / moduleSize; - e2e[i] = int(v + .5f); + int i_v = reverse ? LEN - 2 - i : i; + double v = (view[i_v] + view[i_v + 1]) / moduleSize; + e2e[i] = int(v + .5); } return e2e; @@ -299,13 +296,14 @@ std::array NormalizedE2EPattern(const PatternView& view) template std::array NormalizedPattern(const PatternView& view) { - float moduleSize = static_cast(view.sum(LEN)) / SUM; + double moduleSize = static_cast(view.sum(LEN)) / SUM; +#if 1 int err = SUM; std::array is; - std::array rs; + std::array rs; for (int i = 0; i < LEN; i++) { - float v = view[i] / moduleSize; - is[i] = int(v + .5f); + double v = view[i] / moduleSize; + is[i] = int(v + .5); rs[i] = v - is[i]; err -= is[i]; } @@ -319,7 +317,25 @@ std::array NormalizedPattern(const PatternView& view) is[mi] += err; rs[mi] -= err; } +#else + std::array is, e2e; + int min_v = view[0], min_i = 0; + + for (int i = 1; i < LEN; i++) { + double v = (view[i - 1] + view[i]) / moduleSize; + e2e[i] = int(v + .5); + if (view[i] < min_v) { + min_v = view[i]; + min_i = i; + } + } + is[min_i] = 1; + for (int i = min_i + 1; i < LEN; ++i) + is[i] = e2e[i] - is[i - 1]; + for (int i = min_i - 1; i >= 0; --i) + is[i] = e2e[i + 1] - is[i + 1]; +#endif return is; } diff --git a/core/src/PerspectiveTransform.h b/core/src/PerspectiveTransform.h index cc85fcb20c..3ac6699828 100644 --- a/core/src/PerspectiveTransform.h +++ b/core/src/PerspectiveTransform.h @@ -13,9 +13,11 @@ namespace ZXing { /** -*

This class implements a perspective transform in two dimensions. Given four source and four +* This class implements a perspective transform in two dimensions. Given four source and four * destination points, it will compute the transformation implied between them. The code is based -* directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.

+* directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56. +* +* See also e.g. https://math.stackexchange.com/a/339033 */ class PerspectiveTransform { diff --git a/core/src/Point.h b/core/src/Point.h index 19e1366099..731a8c207c 100644 --- a/core/src/Point.h +++ b/core/src/Point.h @@ -6,8 +6,8 @@ #pragma once #include -#include #include +#include namespace ZXing { @@ -154,6 +154,11 @@ PointT mainDirection(PointT d) return std::abs(d.x) > std::abs(d.y) ? PointT(d.x, 0) : PointT(0, d.y); } +template +std::string ToString(const PointT& p, bool swap = false, char delim = 'x') +{ + return std::to_string(swap ? p.y : p.x) + delim + std::to_string(swap ? p.x : p.y); +} } // ZXing diff --git a/core/src/Quadrilateral.h b/core/src/Quadrilateral.h index 3e339bb61c..7f557daf17 100644 --- a/core/src/Quadrilateral.h +++ b/core/src/Quadrilateral.h @@ -10,6 +10,7 @@ #include #include +#include namespace ZXing { @@ -152,7 +153,7 @@ bool HaveIntersectingBoundingBoxes(const Quadrilateral& a, const Quadril 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); }; + auto dist2First = [r = a[0]](auto s, auto t) { return distance(s, r) < distance(t, r); }; // 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(); @@ -163,5 +164,14 @@ Quadrilateral Blend(const Quadrilateral& a, const Quadrilateral< return res; } +template +std::string ToString(const Quadrilateral>& points) +{ + std::string res; + for (const auto& p : points) + res += std::to_string(p.x) + "x" + std::to_string(p.y) + (&p == &points.back() ? "" : " "); + return res; +} + } // ZXing diff --git a/core/src/Range.h b/core/src/Range.h index 5040157807..e3fbbc3064 100644 --- a/core/src/Range.h +++ b/core/src/Range.h @@ -7,6 +7,7 @@ #include "ZXAlgorithms.h" +#include #include namespace ZXing { @@ -56,4 +57,56 @@ struct Range template Range(const C&) -> Range; +/** + * ArrayView is a lightweight, non-owning, non-mutable view over a contiguous sequence of elements. + * Similar to std::span. See also Range template for general iterator use case. + */ +template +class ArrayView +{ + const T* _data = nullptr; + std::size_t _size = 0; + +public: + using value_type = T; + using pointer = const value_type*; + using const_pointer = const value_type*; + using reference = const value_type&; + using const_reference = const value_type&; + using size_type = std::size_t; + + constexpr ArrayView() noexcept = default; + + constexpr ArrayView(pointer data, size_type size) noexcept : _data(data), _size(size) {} + + template >>>>> + constexpr ArrayView(P data, size_type size) noexcept : _data(reinterpret_cast(data)), _size(size) + {} + + template ())), const_pointer>>> + constexpr ArrayView(const Container& c) noexcept : _data(std::data(c)), _size(std::size(c)) + {} + + constexpr pointer data() const noexcept { return _data; } + constexpr size_type size() const noexcept { return _size; } + constexpr bool empty() const noexcept { return _size == 0; } + + constexpr const_reference operator[](size_type index) const noexcept { return _data[index]; } + + constexpr pointer begin() const noexcept { return _data; } + constexpr pointer end() const noexcept { return _data + _size; } + + constexpr ArrayView subview(size_type pos, size_type len = size_type(-1)) const noexcept + { + if (pos > _size) + return {}; + return {_data + pos, std::min(len, _size - pos)}; + } +}; + +using ByteView = ArrayView; + } // namespace ZXing diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index f814add08b..8a38791628 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -5,12 +5,17 @@ #include "ReadBarcode.h" -#include "DecodeHints.h" +#if !defined(ZXING_READERS) && !defined(ZXING_WRITERS) +#include "Version.h" +#endif + +#ifdef ZXING_READERS #include "GlobalHistogramBinarizer.h" #include "HybridBinarizer.h" #include "MultiFormatReader.h" #include "Pattern.h" #include "ThresholdBinarizer.h" +#endif #include #include @@ -18,18 +23,14 @@ namespace ZXing { -class LumImage : public ImageView -{ - std::unique_ptr _memory; - LumImage(std::unique_ptr&& data, int w, int h) - : ImageView(data.get(), w, h, ImageFormat::Lum), _memory(std::move(data)) - {} +#ifdef ZXING_READERS +class LumImage : public Image +{ public: - LumImage() : ImageView(nullptr, 0, 0, ImageFormat::Lum) {} - LumImage(int w, int h) : LumImage(std::make_unique(w * h), w, h) {} + using Image::Image; - uint8_t* data() { return _memory.get(); } + uint8_t* data() { return const_cast(Image::data()); } }; template @@ -75,7 +76,7 @@ class LumImagePyramid case 2: addLayer<2>(); break; case 3: addLayer<3>(); break; case 4: addLayer<4>(); break; - default: throw std::invalid_argument("Invalid DecodeHints::downscaleFactor"); break; + default: throw std::invalid_argument("Invalid ReaderOptions::downscaleFactor"); break; } } @@ -84,6 +85,9 @@ class LumImagePyramid LumImagePyramid(const ImageView& iv, int threshold, int factor) { + if (factor < 2) + throw std::invalid_argument("Invalid ReaderOptions::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 && @@ -98,13 +102,20 @@ class LumImagePyramid } }; -ImageView SetupLumImageView(ImageView iv, LumImage& lum, const DecodeHints& hints) +ImageView SetupLumImageView(ImageView iv, LumImage& lum, const ReaderOptions& opts) { if (iv.format() == ImageFormat::None) throw std::invalid_argument("Invalid image format"); - if (hints.binarizer() == Binarizer::GlobalHistogram || hints.binarizer() == Binarizer::LocalAverage) { - if (iv.format() != ImageFormat::Lum) { + if (opts.binarizer() == Binarizer::GlobalHistogram || opts.binarizer() == Binarizer::LocalAverage) { + // manually spell out the 3 most common pixel formats to get at least gcc to vectorize the code + if (iv.format() == ImageFormat::RGB && iv.pixStride() == 3) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); }); + } else if (iv.format() == ImageFormat::RGBA && iv.pixStride() == 4) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); }); + } else if (iv.format() == ImageFormat::BGR && iv.pixStride() == 3) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[2], src[1], src[0]); }); + } else if (iv.format() != ImageFormat::Lum) { lum = ExtractLum(iv, [r = RedIndex(iv.format()), g = GreenIndex(iv.format()), b = BlueIndex(iv.format())]( const uint8_t* src) { return RGBToLum(src[r], src[g], src[b]); }); } else if (iv.pixStride() != 1) { @@ -128,64 +139,85 @@ std::unique_ptr CreateBitmap(ZXing::Binarizer binarizer, const Ima return {}; // silence gcc warning } -Result ReadBarcode(const ImageView& _iv, const DecodeHints& hints) +Barcode ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) { - return FirstOrDefault(ReadBarcodes(_iv, DecodeHints(hints).setMaxNumberOfSymbols(1))); + return FirstOrDefault(ReadBarcodes(_iv, ReaderOptions(opts).setMaxNumberOfSymbols(1))); } -Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) +Barcodes ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) { - if (sizeof(PatternType) < 4 && hints.hasFormat(BarcodeFormat::LinearCodes) && (_iv.width() > 0xffff || _iv.height() > 0xffff)) - throw std::invalid_argument("maximum image width/height is 65535"); + if (sizeof(PatternType) < 4 && (_iv.width() > 0xffff || _iv.height() > 0xffff)) + throw std::invalid_argument("Maximum image width/height is 65535"); + + if (!_iv.data() || _iv.width() * _iv.height() == 0) + throw std::invalid_argument("ImageView is null/empty"); LumImage lum; - ImageView iv = SetupLumImageView(_iv, lum, hints); - MultiFormatReader reader(hints); + ImageView iv = SetupLumImageView(_iv, lum, opts); + MultiFormatReader reader(opts); - if (hints.isPure()) - return {reader.read(*CreateBitmap(hints.binarizer(), iv))}; + if (opts.isPure()) + return {reader.read(*CreateBitmap(opts.binarizer(), iv)).setReaderOptions(opts)}; std::unique_ptr closedReader; -#ifdef BUILD_EXPERIMENTAL_API +#ifdef ZXING_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); + ReaderOptions closedOptions = opts; + if (opts.tryDenoise() && opts.hasFormat(formatsBenefittingFromClosing) && _iv.height() >= 3) { + closedOptions.setFormats((opts.formats().empty() ? BarcodeFormat::Any : opts.formats()) & formatsBenefittingFromClosing); + closedReader = std::make_unique(closedOptions); } #endif - LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor()); + LumImagePyramid pyramid(iv, opts.downscaleThreshold() * opts.tryDownscale(), opts.downscaleFactor()); - Results results; - int maxSymbols = hints.maxNumberOfSymbols() ? hints.maxNumberOfSymbols() : INT_MAX; + Barcodes res; + int maxSymbols = opts.maxNumberOfSymbols() ? opts.maxNumberOfSymbols() : INT_MAX; for (auto&& iv : pyramid.layers) { - auto bitmap = CreateBitmap(hints.binarizer(), iv); + auto bitmap = CreateBitmap(opts.binarizer(), iv); for (int close = 0; close <= (closedReader ? 1 : 0); ++close) { - if (close) + if (close) { + // if we already inverted the image in the first round, we need to undo that first + if (bitmap->inverted()) + bitmap->invert(); bitmap->close(); + } // TODO: check if closing after invert would be beneficial - for (int invert = 0; invert <= static_cast(hints.tryInvert() && !close); ++invert) { + for (int invert = 0; invert <= static_cast(opts.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); + if (!Contains(res, r)) { + r.setReaderOptions(opts); r.setIsInverted(bitmap->inverted()); - results.push_back(std::move(r)); + res.push_back(std::move(r)); --maxSymbols; } } if (maxSymbols <= 0) - return results; + return res; } } } - return results; + return res; +} + +#else // ZXING_READERS + +Barcode ReadBarcode(const ImageView&, const ReaderOptions&) +{ + throw std::runtime_error("This build of zxing-cpp does not support reading barcodes."); } +Barcodes ReadBarcodes(const ImageView&, const ReaderOptions&) +{ + throw std::runtime_error("This build of zxing-cpp does not support reading barcodes."); +} + +#endif // ZXING_READERS + } // ZXing diff --git a/core/src/ReadBarcode.h b/core/src/ReadBarcode.h index 5b73d16913..d932398ad7 100644 --- a/core/src/ReadBarcode.h +++ b/core/src/ReadBarcode.h @@ -5,29 +5,29 @@ #pragma once -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "ImageView.h" -#include "Result.h" +#include "Barcode.h" namespace ZXing { /** * Read barcode from an ImageView * - * @param buffer view of the image data including layout and format - * @param hints optional DecodeHints to parameterize / speed up decoding - * @return #Result structure + * @param image view of the image data including layout and format + * @param options optional ReaderOptions to parameterize / speed up detection + * @return #Barcode structure */ -Result ReadBarcode(const ImageView& buffer, const DecodeHints& hints = {}); +Barcode ReadBarcode(const ImageView& image, const ReaderOptions& options = {}); /** * Read barcodes from an ImageView * - * @param buffer view of the image data including layout and format - * @param hints optional DecodeHints to parameterize / speed up decoding - * @return #Results list of results found, may be empty + * @param image view of the image data including layout and format + * @param options optional ReaderOptions to parameterize / speed up detection + * @return #Barcodes list of barcodes found, may be empty */ -Results ReadBarcodes(const ImageView& buffer, const DecodeHints& hints = {}); +Barcodes ReadBarcodes(const ImageView& image, const ReaderOptions& options = {}); } // ZXing diff --git a/core/src/Reader.h b/core/src/Reader.h index bc2d69cdc2..fdac2d02cf 100644 --- a/core/src/Reader.h +++ b/core/src/Reader.h @@ -6,32 +6,32 @@ #pragma once -#include "DecodeHints.h" -#include "Result.h" +#include "ReaderOptions.h" +#include "Barcode.h" namespace ZXing { class BinaryBitmap; -class DecodeHints; +class ReaderOptions; class Reader { protected: - const DecodeHints& _hints; + const ReaderOptions& _opts; public: const bool supportsInversion; - explicit Reader(const DecodeHints& hints, bool supportsInversion = false) : _hints(hints), supportsInversion(supportsInversion) {} - explicit Reader(DecodeHints&& hints) = delete; + explicit Reader(const ReaderOptions& opts, bool supportsInversion = false) : _opts(opts), supportsInversion(supportsInversion) {} + explicit Reader(ReaderOptions&& opts) = delete; virtual ~Reader() = default; - virtual Result decode(const BinaryBitmap& image) const = 0; + virtual Barcode decode(const BinaryBitmap& image) const = 0; // WARNING: this API is experimental and may change/disappear - virtual Results decode(const BinaryBitmap& image, [[maybe_unused]] int maxSymbols) const { + virtual Barcodes decode(const BinaryBitmap& image, [[maybe_unused]] int maxSymbols) const { auto res = decode(image); - return res.isValid() || (_hints.returnErrors() && res.format() != BarcodeFormat::None) ? Results{std::move(res)} : Results{}; + return res.isValid() || (_opts.returnErrors() && res.format() != BarcodeFormat::None) ? Barcodes{std::move(res)} : Barcodes{}; } }; diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h new file mode 100644 index 0000000000..97c1bb0b9e --- /dev/null +++ b/core/src/ReaderOptions.h @@ -0,0 +1,179 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BarcodeFormat.h" +#include "CharacterSet.h" + +#include +#include + +namespace ZXing { + +/** + * @brief The Binarizer enum + * + * Specify which algorithm to use for the grayscale to binary transformation. + * The difference is how to get to a threshold value T which results in a bit + * value R = L <= T. + */ +enum class Binarizer : unsigned char // needs to be unsigned for the bitfield below to work, uint8_t fails as well +{ + LocalAverage, ///< T = average of neighboring pixels for matrix and GlobalHistogram for linear (HybridBinarizer) + GlobalHistogram, ///< T = valley between the 2 largest peaks in the histogram (per line in linear case) + FixedThreshold, ///< T = 127 + BoolCast, ///< T = 0, fastest possible +}; + +enum class EanAddOnSymbol : unsigned char // see above +{ + Ignore, ///< Ignore any Add-On symbol during read/scan + Read, ///< Read EAN-2/EAN-5 Add-On symbol if found + Require, ///< Require EAN-2/EAN-5 Add-On symbol to be present +}; + +enum class TextMode : unsigned char // see above +{ + Plain, ///< bytes() transcoded to unicode based on ECI info or guessed charset (the default mode prior to 2.0) + ECI, ///< standard content following the ECI protocol with every character set ECI segment transcoded to unicode + HRI, ///< Human Readable Interpretation (dependent on the ContentType) + Hex, ///< bytes() transcoded to ASCII string of HEX values + Escaped, ///< Use the EscapeNonGraphical() function (e.g. ASCII 29 will be transcoded to "") +}; + +class ReaderOptions +{ + bool _tryHarder : 1; + bool _tryRotate : 1; + bool _tryInvert : 1; + bool _tryDownscale : 1; + bool _isPure : 1; + bool _tryCode39ExtendedMode : 1; + bool _validateCode39CheckSum : 1; + bool _validateITFCheckSum : 1; + bool _returnCodabarStartEnd : 1; + bool _returnErrors : 1; + uint8_t _downscaleFactor : 3; + EanAddOnSymbol _eanAddOnSymbol : 2; + Binarizer _binarizer : 2; + TextMode _textMode : 3; + CharacterSet _characterSet : 6; +#ifdef ZXING_EXPERIMENTAL_API + bool _tryDenoise : 1; +#endif + + uint8_t _minLineCount = 2; + uint8_t _maxNumberOfSymbols = 0xff; + uint16_t _downscaleThreshold = 500; + BarcodeFormats _formats = BarcodeFormat::None; + +public: + // bitfields don't get default initialized to 0 before c++20 + ReaderOptions() + : _tryHarder(1), + _tryRotate(1), + _tryInvert(1), + _tryDownscale(1), + _isPure(0), + _tryCode39ExtendedMode(1), + _validateCode39CheckSum(0), + _validateITFCheckSum(0), + _returnCodabarStartEnd(1), + _returnErrors(0), + _downscaleFactor(3), + _eanAddOnSymbol(EanAddOnSymbol::Ignore), + _binarizer(Binarizer::LocalAverage), + _textMode(TextMode::HRI), + _characterSet(CharacterSet::Unknown) +#ifdef ZXING_EXPERIMENTAL_API + , + _tryDenoise(0) +#endif + {} + +#define ZX_PROPERTY(TYPE, GETTER, SETTER, ...) \ + TYPE GETTER() const noexcept { return _##GETTER; } \ + __VA_ARGS__ ReaderOptions& SETTER(TYPE v)& { return (void)(_##GETTER = std::move(v)), *this; } \ + __VA_ARGS__ ReaderOptions&& SETTER(TYPE v)&& { return (void)(_##GETTER = std::move(v)), std::move(*this); } + + /// Specify a set of BarcodeFormats that should be searched for, the default is all supported formats. + ZX_PROPERTY(BarcodeFormats, formats, setFormats) + + /// Spend more time to try to find a barcode; optimize for accuracy, not speed. + ZX_PROPERTY(bool, tryHarder, setTryHarder) + + /// Also try detecting code in 90, 180 and 270 degree rotated images. + ZX_PROPERTY(bool, tryRotate, setTryRotate) + + /// Also try detecting inverted ("reversed reflectance") codes if the format allows for those. + ZX_PROPERTY(bool, tryInvert, setTryInvert) + + /// Also try detecting code in downscaled images (depending on image size). + ZX_PROPERTY(bool, tryDownscale, setTryDownscale) + +#ifdef ZXING_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) + + /// Set to true if the input contains nothing but a single perfectly aligned barcode (generated image) + ZX_PROPERTY(bool, isPure, setIsPure) + + /// Image size ( min(width, height) ) threshold at which to start downscaled scanning + // WARNING: this API is experimental and may change/disappear + ZX_PROPERTY(uint16_t, downscaleThreshold, setDownscaleThreshold) + + /// Scale factor used during downscaling, meaningful values are 2, 3 and 4 + // WARNING: this API is experimental and may change/disappear + ZX_PROPERTY(uint8_t, downscaleFactor, setDownscaleFactor) + + /// The number of scan lines in a linear barcode that have to be equal to accept the result, default is 2 + ZX_PROPERTY(uint8_t, minLineCount, setMinLineCount) + + /// The maximum number of symbols (barcodes) to detect / look for in the image with ReadBarcodes + ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) + + /// Enable the heuristic to detect and decode "full ASCII"/extended Code39 symbols + ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode) + + /// Deprecated / does nothing. The Code39 symbol has a valid checksum iff symbologyIdentifier()[2] is an odd digit + ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum, [[deprecated]]) + + /// Deprecated / does nothing. The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'. + ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum, [[deprecated]]) + + /// Deprecated / does nothing. Codabar start/stop characters are always returned. + ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd, [[deprecated]]) + + /// If true, return the barcodes with errors as well (e.g. checksum errors, see @Barcode::error()) + ZX_PROPERTY(bool, returnErrors, setReturnErrors) + + /// Specify whether to ignore, read or require EAN-2/5 add-on symbols while scanning EAN/UPC codes + ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol) + + /// Specifies the TextMode that controls the return of the Barcode::text() function + ZX_PROPERTY(TextMode, textMode, setTextMode) + + /// Specifies fallback character set to use instead of auto-detecting it (when applicable) + ZX_PROPERTY(CharacterSet, characterSet, setCharacterSet) + ReaderOptions& setCharacterSet(std::string_view v)& { return (void)(_characterSet = CharacterSetFromString(v)), *this; } + ReaderOptions&& setCharacterSet(std::string_view v) && { return (void)(_characterSet = CharacterSetFromString(v)), std::move(*this); } + +#undef ZX_PROPERTY + + bool hasFormat(BarcodeFormats f) const noexcept { return _formats.testFlags(f) || _formats.empty(); } +}; + +#ifndef HIDE_DECODE_HINTS_ALIAS +using DecodeHints [[deprecated]] = ReaderOptions; +#endif + +} // ZXing diff --git a/core/src/ReedSolomonDecoder.cpp b/core/src/ReedSolomonDecoder.cpp index e521f4cb7e..f2a49df10d 100644 --- a/core/src/ReedSolomonDecoder.cpp +++ b/core/src/ReedSolomonDecoder.cpp @@ -71,7 +71,7 @@ RunEuclideanAlgorithm(const GenericGF& field, std::vector&& rCoefs, Generic static std::vector FindErrorLocations(const GenericGF& field, const GenericGFPoly& errorLocator) { - // This is a direct application of Chien's search + // This is a brute force search for roots of errorLocator (not Chien's search) int numErrors = errorLocator.degree(); std::vector res; res.reserve(numErrors); @@ -80,9 +80,6 @@ FindErrorLocations(const GenericGF& field, const GenericGFPoly& errorLocator) if (errorLocator.evaluateAt(i) == 0) res.push_back(field.inverse(i)); - if (Size(res) != numErrors) - return {}; // Error locator degree does not match number of roots - return res; } @@ -124,8 +121,8 @@ ReedSolomonDecode(const GenericGF& field, std::vector& message, int numECCo return false; auto errorLocations = FindErrorLocations(field, sigma); - if (errorLocations.empty()) - return false; + if (Size(errorLocations) != sigma.degree()) + return false; // Error locator degree does not match number of roots, most likely there are more errors than can be recovered auto errorMagnitudes = FindErrorMagnitudes(field, omega, errorLocations); @@ -137,6 +134,16 @@ ReedSolomonDecode(const GenericGF& field, std::vector& message, int numECCo message[position] ^= errorMagnitudes[i]; } + +#if 1 + // re-evaluate the syndromes of the recovered message to make sure it is a valid (see #940) + poly = GenericGFPoly(field, message); + + for (int i = 0; i < numECCodeWords; i++) + if (poly.evaluateAt(field.exp(i + field.generatorBase())) != 0) + return false; +#endif + return true; } diff --git a/core/src/RegressionLine.h b/core/src/RegressionLine.h index eb7ecd1d4f..13571e1d18 100644 --- a/core/src/RegressionLine.h +++ b/core/src/RegressionLine.h @@ -10,7 +10,6 @@ #include #include -#include #include #ifdef PRINT_DEBUG @@ -30,7 +29,7 @@ class RegressionLine template bool evaluate(const PointT* begin, const PointT* end) { - auto mean = std::accumulate(begin, end, PointF()) / std::distance(begin, end); + auto mean = Reduce(begin, end, PointF()) / std::distance(begin, end); PointF::value_t sumXX = 0, sumYY = 0, sumXY = 0; for (auto p = begin; p != end; ++p) { auto d = *p - mean; @@ -79,6 +78,7 @@ class RegressionLine auto signedDistance(PointF p) const { return dot(normal(), p) - c; } template auto distance(PointT p) const { return std::abs(signedDistance(PointF(p))); } PointF project(PointF p) const { return p - signedDistance(p) * normal(); } + PointF centroid() const { return Reduce(_points) / _points.size(); } void reset() { @@ -111,15 +111,26 @@ class RegressionLine while (true) { auto old_points_size = points.size(); // remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist +#ifdef __cpp_lib_erase_if + std::erase_if(points, [this, maxSignedDist](auto p) { + auto sd = this->signedDistance(p); + return sd > maxSignedDist || sd < -2 * maxSignedDist; + }); +#else auto end = std::remove_if(points.begin(), points.end(), [this, maxSignedDist](auto p) { auto sd = this->signedDistance(p); return sd > maxSignedDist || sd < -2 * maxSignedDist; }); points.erase(end, points.end()); +#endif + // if we threw away too many points, something is off with the line to begin with + if (points.size() < old_points_size / 2 || points.size() < 2) + return false; if (old_points_size == points.size()) break; #ifdef PRINT_DEBUG - printf("removed %zu points\n", old_points_size - points.size()); + printf("removed %zu points -> %zu remaining\n", old_points_size - points.size(), points.size()); + fflush(stdout); #endif ret = evaluate(points); } diff --git a/core/src/Result.cpp b/core/src/Result.cpp deleted file mode 100644 index 837a561a43..0000000000 --- a/core/src/Result.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "Result.h" - -#include "DecoderResult.h" -#include "TextDecoder.h" -#include "ZXAlgorithms.h" - -#include -#include -#include -#include - -namespace ZXing { - -Result::Result(const std::string& text, int y, int xStart, int xStop, BarcodeFormat format, SymbologyIdentifier si, Error error, bool readerInit) - : _content({ByteArray(text)}, si), - _error(error), - _position(Line(y, xStart, xStop)), - _format(format), - _readerInit(readerInit) -{} - -Result::Result(DecoderResult&& decodeResult, Position&& position, BarcodeFormat format) - : _content(std::move(decodeResult).content()), - _error(std::move(decodeResult).error()), - _position(std::move(position)), - _sai(decodeResult.structuredAppend()), - _format(format), - _lineCount(decodeResult.lineCount()), - _isMirrored(decodeResult.isMirrored()), - _readerInit(decodeResult.readerInit()) -{ - if (decodeResult.versionNumber()) - snprintf(_version, 4, "%d", decodeResult.versionNumber()); - snprintf(_ecLevel, 4, "%s", decodeResult.ecLevel().data()); - - // TODO: add type opaque and code specific 'extra data'? (see DecoderResult::extra()) -} - -bool Result::isValid() const -{ - return format() != BarcodeFormat::None && _content.symbology.code != 0 && !error(); -} - -const ByteArray& Result::bytes() const -{ - return _content.bytes; -} - -ByteArray Result::bytesECI() const -{ - return _content.bytesECI(); -} - -std::string Result::text(TextMode mode) const -{ - return _content.text(mode); -} - -std::string Result::text() const -{ - return text(_decodeHints.textMode()); -} - -std::string Result::ecLevel() const -{ - return _ecLevel; -} - -ContentType Result::contentType() const -{ - return _content.type(); -} - -bool Result::hasECI() const -{ - return _content.hasECI; -} - -int Result::orientation() const -{ - constexpr auto std_numbers_pi_v = 3.14159265358979323846; // TODO: c++20 - return narrow_cast(std::lround(_position.orientation() * 180 / std_numbers_pi_v)); -} - -std::string Result::symbologyIdentifier() const -{ - return _content.symbology.toString(); -} - -int Result::sequenceSize() const -{ - return _sai.count; -} - -int Result::sequenceIndex() const -{ - return _sai.index; -} - -std::string Result::sequenceId() const -{ - return _sai.id; -} - -std::string Result::version() const -{ - return _version; -} - -Result& Result::setDecodeHints(DecodeHints hints) -{ - if (hints.characterSet() != CharacterSet::Unknown) - _content.defaultCharset = hints.characterSet(); - _decodeHints = hints; - return *this; -} - -bool Result::operator==(const Result& o) const -{ - // 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; - - // 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 HaveIntersectingBoundingBoxes(o.position(), position()); - - // the following code is only meant for this->lineCount == 1 - assert(lineCount() == 1); - - // if one line is less than half the length of the other away from the - // latter, we consider it to belong to the same symbol. additionally, both need to have - // roughly the same length (see #367) - auto dTop = maxAbsComponent(o.position().topLeft() - position().topLeft()); - auto dBot = maxAbsComponent(o.position().bottomLeft() - position().topLeft()); - auto length = maxAbsComponent(position().topLeft() - position().bottomRight()); - auto dLength = std::abs(length - maxAbsComponent(o.position().topLeft() - o.position().bottomRight())); - - return std::min(dTop, dBot) < length / 2 && dLength < length / 5; -} - -Result MergeStructuredAppendSequence(const Results& results) -{ - if (results.empty()) - return {}; - - std::list allResults(results.begin(), results.end()); - allResults.sort([](const Result& r1, const Result& r2) { return r1.sequenceIndex() < r2.sequenceIndex(); }); - - Result res = allResults.front(); - for (auto i = std::next(allResults.begin()); i != allResults.end(); ++i) - res._content.append(i->_content); - - res._position = {}; - res._sai.index = -1; - - if (allResults.back().sequenceSize() != Size(allResults) || - !std::all_of(allResults.begin(), allResults.end(), - [&](Result& it) { return it.sequenceId() == allResults.front().sequenceId(); })) - res._error = FormatError("sequenceIDs not matching during structured append sequence merging"); - - return res; -} - -Results MergeStructuredAppendSequences(const Results& results) -{ - std::map sas; - for (auto& res : results) { - if (res.isPartOfSequence()) - sas[res.sequenceId()].push_back(res); - } - - Results saiResults; - for (auto& [id, seq] : sas) { - auto res = MergeStructuredAppendSequence(seq); - if (res.isValid()) - saiResults.push_back(std::move(res)); - } - - return saiResults; -} - -} // ZXing diff --git a/core/src/Result.h b/core/src/Result.h index 9defbc5033..6022ec72a8 100644 --- a/core/src/Result.h +++ b/core/src/Result.h @@ -1,183 +1,10 @@ /* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -* Copyright 2020 Axel Waggershauser +* Copyright 2024 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 #pragma once -#include "BarcodeFormat.h" -#include "ByteArray.h" -#include "Content.h" -#include "DecodeHints.h" -#include "Error.h" -#include "Quadrilateral.h" -#include "StructuredAppend.h" +#include "Barcode.h" -#include -#include - -namespace ZXing { - -class DecoderResult; -class ImageView; - -using Position = QuadrilateralI; - -/** - * @brief The Result class encapsulates the result of decoding a barcode within an image. - */ -class Result -{ - void setIsInverted(bool v) { _isInverted = v; } - Result& setDecodeHints(DecodeHints hints); - - friend Result MergeStructuredAppendSequence(const std::vector& results); - friend std::vector ReadBarcodes(const ImageView&, const DecodeHints&); - friend void IncrementLineCount(Result&); - -public: - Result() = default; - - // linear symbology convenience constructor - Result(const std::string& text, int y, int xStart, int xStop, BarcodeFormat format, SymbologyIdentifier si, Error error = {}, - bool readerInit = false); - - Result(DecoderResult&& decodeResult, Position&& position, BarcodeFormat format); - - bool isValid() const; - - const Error& error() const { return _error; } - - BarcodeFormat format() const { return _format; } - - /** - * @brief bytes is the raw / standard content without any modifications like character set conversions - */ - const ByteArray& bytes() const; - - /** - * @brief bytesECI is the raw / standard content following the ECI protocol - */ - ByteArray bytesECI() const; - - /** - * @brief text returns the bytes() content rendered to unicode/utf8 text accoring to specified TextMode - */ - std::string text(TextMode mode) const; - - /** - * @brief text returns the bytes() content rendered to unicode/utf8 text accoring to the TextMode set in the DecodingHints - */ - std::string text() const; - - /** - * @brief ecLevel returns the error correction level of the symbol (empty string if not applicable) - */ - std::string ecLevel() const; - - /** - * @brief contentType gives a hint to the type of content found (Text/Binary/GS1/etc.) - */ - ContentType contentType() const; - - /** - * @brief hasECI specifies wheter or not an ECI tag was found - */ - bool hasECI() const; - - const Position& position() const { return _position; } - void setPosition(Position pos) { _position = pos; } - - /** - * @brief orientation of barcode in degree, see also Position::orientation() - */ - int orientation() const; - - /** - * @brief isMirrored is the symbol mirrored (currently only supported by QRCode and DataMatrix) - */ - bool isMirrored() const { return _isMirrored; } - - /** - * @brief isInverted is the symbol inverted / has reveresed reflectance (see DecodeHints::tryInvert) - */ - bool isInverted() const { return _isInverted; } - - /** - * @brief symbologyIdentifier Symbology identifier "]cm" where "c" is symbology code character, "m" the modifier. - */ - std::string symbologyIdentifier() const; - - /** - * @brief sequenceSize number of symbols in a structured append sequence. - * - * If this is not part of a structured append sequence, the returned value is -1. - * If it is a structured append symbol but the total number of symbols is unknown, the - * returned value is 0 (see PDF417 if optional "Segment Count" not given). - */ - int sequenceSize() const; - - /** - * @brief sequenceIndex the 0-based index of this symbol in a structured append sequence. - */ - int sequenceIndex() const; - - /** - * @brief sequenceId id to check if a set of symbols belongs to the same structured append sequence. - * - * If the symbology does not support this feature, the returned value is empty (see MaxiCode). - * For QR Code, this is the parity integer converted to a string. - * For PDF417 and DataMatrix, this is the "fileId". - */ - std::string sequenceId() const; - - bool isLastInSequence() const { return sequenceSize() == sequenceIndex() + 1; } - bool isPartOfSequence() const { return sequenceSize() > -1 && sequenceIndex() > -1; } - - /** - * @brief readerInit Set if Reader Initialisation/Programming symbol. - */ - bool readerInit() const { return _readerInit; } - - /** - * @brief lineCount How many lines have been detected with this code (applies only to linear symbologies) - */ - int lineCount() const { return _lineCount; } - - /** - * @brief version QRCode / DataMatrix / Aztec version or size. - */ - std::string version() const; - - bool operator==(const Result& o) const; - -private: - Content _content; - Error _error; - Position _position; - DecodeHints _decodeHints; - StructuredAppendInfo _sai; - BarcodeFormat _format = BarcodeFormat::None; - char _ecLevel[4] = {}; - char _version[4] = {}; - int _lineCount = 0; - bool _isMirrored = false; - bool _isInverted = false; - bool _readerInit = false; -}; - -using Results = std::vector; - -/** - * @brief Merge a list of Results from one Structured Append sequence to a single result - */ -Result MergeStructuredAppendSequence(const Results& results); - -/** - * @brief Automatically merge all Structured Append sequences found in the given results - */ -Results MergeStructuredAppendSequences(const Results& results); - -} // ZXing +#pragma message("Header `Result.h` is deprecated, please include `Barcode.h` and use the new name `Barcode`.") diff --git a/core/src/TextDecoder.cpp b/core/src/TextDecoder.cpp index bca4617616..faa5fd5330 100644 --- a/core/src/TextDecoder.cpp +++ b/core/src/TextDecoder.cpp @@ -1,14 +1,12 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2022 gitlost +* Copyright 2025 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 #include "TextDecoder.h" -#include "CharacterSet.h" -#include "ECI.h" -#include "Utf.h" #include "ZXAlgorithms.h" #include "libzueci/zueci.h" @@ -17,49 +15,38 @@ namespace ZXing { -void TextDecoder::Append(std::string& str, const uint8_t* bytes, size_t length, CharacterSet charset, bool sjisASCII) +std::string BytesToUtf8(ByteView bytes, ECI eci) { - int eci = ToInt(ToECI(charset)); - const size_t str_len = str.length(); - const int bytes_len = narrow_cast(length); constexpr unsigned int replacement = 0xFFFD; - const unsigned int flags = ZUECI_FLAG_SB_STRAIGHT_THRU | (sjisASCII ? ZUECI_FLAG_SJIS_STRAIGHT_THRU : 0); + constexpr unsigned int flags = ZUECI_FLAG_SB_STRAIGHT_THRU | ZUECI_FLAG_SJIS_STRAIGHT_THRU; int utf8_len; - if (eci == -1) - eci = 899; // Binary + if (eci == ECI::Unknown) + eci = ECI::Binary; - int error_number = zueci_dest_len_utf8(eci, bytes, bytes_len, replacement, flags, &utf8_len); + int error_number = zueci_dest_len_utf8(ToInt(eci), bytes.data(), bytes.size(), replacement, flags, &utf8_len); if (error_number >= ZUECI_ERROR) throw std::runtime_error("zueci_dest_len_utf8 failed"); - str.resize(str_len + utf8_len); // Precise length - unsigned char *utf8_buf = reinterpret_cast(str.data()) + str_len; + std::string utf8(utf8_len, 0); - error_number = zueci_eci_to_utf8(eci, bytes, bytes_len, replacement, flags, utf8_buf, &utf8_len); - if (error_number >= ZUECI_ERROR) { - str.resize(str_len); + error_number = zueci_eci_to_utf8(ToInt(eci), bytes.data(), bytes.size(), replacement, flags, + reinterpret_cast(utf8.data()), &utf8_len); + if (error_number >= ZUECI_ERROR) throw std::runtime_error("zueci_eci_to_utf8 failed"); - } - assert(str.length() == str_len + utf8_len); -} -void TextDecoder::Append(std::wstring& str, const uint8_t* bytes, size_t length, CharacterSet charset) -{ - std::string u8str; - Append(u8str, bytes, length, charset); - str.append(FromUtf8(u8str)); + assert(Size(utf8) == utf8_len); + + return utf8; } /** * @param bytes bytes encoding a string, whose encoding should be guessed -* @param hints decode hints if applicable * @return name of guessed encoding; at the moment will only guess one of: * {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform * default encoding if none of these can possibly be correct */ -CharacterSet -TextDecoder::GuessEncoding(const uint8_t* bytes, size_t length, CharacterSet fallback) +CharacterSet GuessTextEncoding(ByteView bytes, CharacterSet fallback) { // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS, // which should be by far the most common encodings. @@ -83,11 +70,12 @@ TextDecoder::GuessEncoding(const uint8_t* bytes, size_t length, CharacterSet fal //int isoHighChars = 0; int isoHighOther = 0; - bool utf8bom = length > 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; + bool utf8bom = bytes.size() > 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; - for (size_t i = 0; i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8); ++i) + for (int value : bytes) { - int value = bytes[i]; + if(!(canBeISO88591 || canBeShiftJIS || canBeUTF8)) + break; // UTF-8 stuff if (canBeUTF8) { @@ -209,7 +197,7 @@ TextDecoder::GuessEncoding(const uint8_t* bytes, size_t length, CharacterSet fal // - at least 10% of bytes that could be "upper" not-alphanumeric Latin1, // - then we conclude Shift_JIS, else ISO-8859-1 if (canBeISO88591 && canBeShiftJIS) { - return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= (int)length + return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= Size(bytes) ? CharacterSet::Shift_JIS : CharacterSet::ISO8859_1; } @@ -227,10 +215,4 @@ TextDecoder::GuessEncoding(const uint8_t* bytes, size_t length, CharacterSet fal return fallback; } -CharacterSet -TextDecoder::DefaultEncoding() -{ - return CharacterSet::ISO8859_1; -} - } // ZXing diff --git a/core/src/TextDecoder.h b/core/src/TextDecoder.h index 531614a779..7ada47ec0a 100644 --- a/core/src/TextDecoder.h +++ b/core/src/TextDecoder.h @@ -1,39 +1,25 @@ /* -* Copyright 2016 Nu-book Inc. +* Copyright 2025 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 #pragma once #include "CharacterSet.h" +#include "ECI.h" +#include "Range.h" -#include -#include #include namespace ZXing { -class TextDecoder +std::string BytesToUtf8(ByteView bytes, ECI eci); + +inline std::string BytesToUtf8(ByteView bytes, CharacterSet cs) { -public: - static CharacterSet DefaultEncoding(); - static CharacterSet GuessEncoding(const uint8_t* bytes, size_t length, CharacterSet fallback = DefaultEncoding()); - - // If `sjisASCII` set then for Shift_JIS maps ASCII directly (straight-thru), i.e. does not map ASCII backslash & tilde - // to Yen sign & overline resp. (JIS X 0201 Roman) - static void Append(std::string& str, const uint8_t* bytes, size_t length, CharacterSet charset, bool sjisASCII = true); - - static void Append(std::wstring& str, const uint8_t* bytes, size_t length, CharacterSet charset); - - static void AppendLatin1(std::wstring& str, const std::string& latin1) { - auto ptr = (const uint8_t*)latin1.data(); - str.append(ptr, ptr + latin1.length()); - } - - static std::wstring FromLatin1(const std::string& latin1) { - auto ptr = (const uint8_t*)latin1.data(); - return std::wstring(ptr, ptr + latin1.length()); - } -}; + return BytesToUtf8(bytes, ToECI(cs)); +} + +CharacterSet GuessTextEncoding(ByteView bytes, CharacterSet fallback = CharacterSet::ISO8859_1); } // ZXing diff --git a/core/src/TextUtfEncoding.h b/core/src/TextUtfEncoding.h index a1940f047b..4eeff813cb 100644 --- a/core/src/TextUtfEncoding.h +++ b/core/src/TextUtfEncoding.h @@ -10,7 +10,7 @@ namespace ZXing::TextUtfEncoding { -// The following functions are not required anymore after Result::text() now returns utf8 natively and the encoders accept utf8 as well. +// The following functions are not required anymore after Barcode::text() now returns utf8 natively and the encoders accept utf8 as well. [[deprecated]] std::string ToUtf8(std::wstring_view str); [[deprecated]] std::string ToUtf8(std::wstring_view str, const bool angleEscape); diff --git a/core/src/Utf.cpp b/core/src/Utf.cpp index ff112e65c8..59194b6862 100644 --- a/core/src/Utf.cpp +++ b/core/src/Utf.cpp @@ -14,13 +14,16 @@ #include #include -namespace ZXing { - -// TODO: c++20 has char8_t #if __cplusplus <= 201703L +#include "Range.h" using char8_t = uint8_t; +using utf8_t = ZXing::ArrayView; +#else +#include +using utf8_t = std::u8string_view; #endif -using utf8_t = std::basic_string_view; + +namespace ZXing { using state_t = uint8_t; constexpr state_t kAccepted = 0; @@ -247,8 +250,10 @@ std::wstring EscapeNonGraphical(std::wstring_view str) ws << "<" << ascii_nongraphs[wc == 127 ? 32 : wc] << ">"; else if (wc < 128) // ASCII ws << wc; - else if (IsUtf16SurrogatePair(str)) - ws.write(str.data(), 2), str.remove_prefix(1); + else if (IsUtf16SurrogatePair(str)) { + ws.write(str.data(), 2); + str.remove_prefix(1); + } // Exclude unpaired surrogates and NO-BREAK spaces NBSP and NUMSP else if ((wc < 0xd800 || wc >= 0xe000) && (iswgraph(wc) && wc != 0xA0 && wc != 0x2007 && wc != 0x2000 && wc != 0xfffd)) ws << wc; diff --git a/core/src/WhiteRectDetector.cpp b/core/src/WhiteRectDetector.cpp index 87cfca924e..9644a271a9 100644 --- a/core/src/WhiteRectDetector.cpp +++ b/core/src/WhiteRectDetector.cpp @@ -76,15 +76,16 @@ static bool GetBlackPointOnSegment(const BitMatrix& image, int aX, int aY, int b /** * recenters the points of a constant distance towards the center * +* p0 to p3 describing the corners of the rectangular +* region. The first and last points are opposed on the diagonal, as +* are the second and third. The first point will be the topmost +* point and the last, the bottommost. The second point will be +* leftmost and the third, the rightmost +* * @param y bottom most point * @param z left most point * @param x right most point * @param t top most point -* @return {@link ResultPoint}[] describing the corners of the rectangular -* region. The first and last points are opposed on the diagonal, as -* are the second and third. The first point will be the topmost -* point and the last, the bottommost. The second point will be -* leftmost and the third, the rightmost */ static void CenterEdges(const ResultPoint& y, const ResultPoint& z, const ResultPoint& x, const ResultPoint& t, int width, ResultPoint& p0, ResultPoint& p1, ResultPoint& p2, ResultPoint& p3) { diff --git a/core/src/WriteBarcode.cpp b/core/src/WriteBarcode.cpp new file mode 100644 index 0000000000..c54a28563d --- /dev/null +++ b/core/src/WriteBarcode.cpp @@ -0,0 +1,661 @@ +/* +* Copyright 2024 Axel Waggershauser +* Copyright 2025 gitlost +*/ +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ZXING_EXPERIMENTAL_API + +#include "WriteBarcode.h" +#include "BitMatrix.h" +#include "JSON.h" + +#if !defined(ZXING_READERS) && !defined(ZXING_WRITERS) +#include "Version.h" +#endif + +#include + +#ifdef ZXING_USE_ZINT + +#include "DecoderResult.h" +#include "DetectorResult.h" +#include + +#else + +struct zint_symbol {}; + +#endif // ZXING_USE_ZINT + +namespace ZXing { + +struct CreatorOptions::Data +{ + BarcodeFormat format; + std::string options; + bool readerInit = false; + bool forceSquareDataMatrix = false; + std::string ecLevel; + + // symbol size (qrcode, datamatrix, etc), map from I, 'WxH' + // structured_append (idx, cnt, ID) + + mutable unique_zint_symbol zint; + +#ifndef __cpp_aggregate_paren_init + Data(BarcodeFormat f, std::string o) : format(f), options(std::move(o)) {} +#endif +}; + +#define ZX_PROPERTY(TYPE, NAME) \ + const TYPE& CreatorOptions::NAME() const noexcept { return d->NAME; } \ + CreatorOptions& CreatorOptions::NAME(TYPE v)& { return d->NAME = std::move(v), *this; } \ + CreatorOptions&& CreatorOptions::NAME(TYPE v)&& { return d->NAME = std::move(v), std::move(*this); } + +ZX_PROPERTY(BarcodeFormat, format) +ZX_PROPERTY(bool, readerInit) +ZX_PROPERTY(bool, forceSquareDataMatrix) +ZX_PROPERTY(std::string, ecLevel) +ZX_PROPERTY(std::string, options) + +#undef ZX_PROPERTY + +#define ZX_RO_PROPERTY(TYPE, NAME) \ + std::optional CreatorOptions::NAME() const noexcept { return JsonGet(d->options, #NAME); } + +ZX_RO_PROPERTY(bool, gs1); +ZX_RO_PROPERTY(bool, stacked); +ZX_RO_PROPERTY(int, version); +ZX_RO_PROPERTY(int, dataMask); + +#undef ZX_RO_PROPERTY + +CreatorOptions::CreatorOptions(BarcodeFormat format, std::string options) : d(std::make_unique(format, std::move(options))) {} +CreatorOptions::~CreatorOptions() = default; +CreatorOptions::CreatorOptions(CreatorOptions&&) = default; +CreatorOptions& CreatorOptions::operator=(CreatorOptions&&) = default; + +struct WriterOptions::Data +{ + int scale = 0; + int sizeHint = 0; + int rotate = 0; + bool withHRT = false; + bool withQuietZones = true; +}; + +#define ZX_PROPERTY(TYPE, NAME) \ + TYPE WriterOptions::NAME() const noexcept { return d->NAME; } \ + WriterOptions& WriterOptions::NAME(TYPE v)& { return d->NAME = std::move(v), *this; } \ + WriterOptions&& WriterOptions::NAME(TYPE v)&& { return d->NAME = std::move(v), std::move(*this); } + +ZX_PROPERTY(int, scale) +ZX_PROPERTY(int, sizeHint) +ZX_PROPERTY(int, rotate) +ZX_PROPERTY(bool, withHRT) +ZX_PROPERTY(bool, withQuietZones) + +#undef ZX_PROPERTY + +WriterOptions::WriterOptions() : d(std::make_unique()) {} +WriterOptions::~WriterOptions() = default; +WriterOptions::WriterOptions(WriterOptions&&) = default; +WriterOptions& WriterOptions::operator=(WriterOptions&&) = default; + +static bool SupportsGS1(BarcodeFormat format) +{ + return (BarcodeFormat::Aztec | BarcodeFormat::Code128 | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode + | BarcodeFormat::RMQRCode | BarcodeFormat::DataBarExpanded) + .testFlag(format); +} + +static std::string ToSVG(ImageView iv) +{ + if (!iv.data()) + return {}; + + // see https://stackoverflow.com/questions/10789059/create-qr-code-in-vector-image/60638350#60638350 + + std::ostringstream res; + + res << "\n" + << "\n" + << "\n"; + + return res.str(); +} + +static Image ToImage(BitMatrix bits, bool isLinearCode, const WriterOptions& opts) +{ + bits.flipAll(); + auto symbol = Inflate(std::move(bits), opts.sizeHint(), + isLinearCode ? std::clamp(opts.sizeHint() / 2, 50, 300) : opts.sizeHint(), + opts.withQuietZones() ? 10 : 0); + auto bitmap = ToMatrix(symbol); + auto iv = Image(symbol.width(), symbol.height()); + std::memcpy(const_cast(iv.data()), bitmap.data(), iv.width() * iv.height()); + return iv; +} + +} // namespace ZXing + + +#ifdef ZXING_WRITERS + +#ifdef ZXING_USE_ZINT +#include "ECI.h" + +#ifdef ZXING_READERS +#include "ReadBarcode.h" +#endif + +#include +#include + +namespace ZXing { + +struct BarcodeFormatZXing2Zint +{ + BarcodeFormat zxing; + int zint; +}; + +static constexpr BarcodeFormatZXing2Zint barcodeFormatZXing2Zint[] = { + {BarcodeFormat::Aztec, BARCODE_AZTEC}, + {BarcodeFormat::Codabar, BARCODE_CODABAR}, + {BarcodeFormat::Code39, BARCODE_CODE39}, + {BarcodeFormat::Code93, BARCODE_CODE93}, + {BarcodeFormat::Code128, BARCODE_CODE128}, + {BarcodeFormat::DataBar, BARCODE_DBAR_OMN}, + {BarcodeFormat::DataBarExpanded, BARCODE_DBAR_EXP}, + {BarcodeFormat::DataBarLimited, BARCODE_DBAR_LTD}, + {BarcodeFormat::DataMatrix, BARCODE_DATAMATRIX}, + {BarcodeFormat::DXFilmEdge, BARCODE_DXFILMEDGE}, + {BarcodeFormat::EAN8, BARCODE_EAN8}, + {BarcodeFormat::EAN13, BARCODE_EAN13}, + {BarcodeFormat::ITF, BARCODE_C25INTER}, + {BarcodeFormat::MaxiCode, BARCODE_MAXICODE}, + {BarcodeFormat::MicroQRCode, BARCODE_MICROQR}, + {BarcodeFormat::PDF417, BARCODE_PDF417}, + {BarcodeFormat::QRCode, BARCODE_QRCODE}, + {BarcodeFormat::RMQRCode, BARCODE_RMQR}, + {BarcodeFormat::UPCA, BARCODE_UPCA}, + {BarcodeFormat::UPCE, BARCODE_UPCE}, +}; + +struct String2Int +{ + const char* str; + int val; +}; + +static int ParseECLevel(int symbology, std::string_view s) +{ + constexpr std::string_view EC_LABELS_QR[4] = {"L", "M", "Q", "H"}; + int res = 0; + + // Convert L/M/Q/H to Zint 1-4 + if (Contains({BARCODE_QRCODE, BARCODE_MICROQR, BARCODE_RMQR}, symbology)) + if ((res = IndexOf(EC_LABELS_QR, s)) != -1) + return res + 1; + + if (std::from_chars(s.data(), s.data() + s.size() - (s.back() == '%'), res).ec != std::errc{}) + throw std::invalid_argument(StrCat("Invalid ecLevel: '", s, "'")); + + auto findClosestECLevel = [](const std::vector& list, int val) { + int mIdx = -2, mAbs = 100; + for (int i = 0; i < Size(list); ++i) + if (int abs = std::abs(val - list[i]); abs < mAbs) { + mIdx = i; + mAbs = abs; + } + return mIdx + 1; + }; + + // Convert percentage to Zint + if (s.back() == '%') { + switch (symbology) { + case BARCODE_QRCODE: return findClosestECLevel({20, 37, 55, 65}, res); + case BARCODE_MICROQR: return findClosestECLevel({20, 37, 55}, res); + case BARCODE_RMQR: return res <= 46 ? 2 : 4; + case BARCODE_AZTEC: return findClosestECLevel({10, 23, 36, 50}, res); + case BARCODE_PDF417: + // TODO: do something sensible with PDF417? + default: + return -1; + } + } + + return res; +}; + +static constexpr struct { BarcodeFormat format; SymbologyIdentifier si; } barcodeFormat2SymbologyIdentifier[] = { + {BarcodeFormat::Aztec, {'z', '0', 3}}, // '1' GS1, '2' AIM + {BarcodeFormat::Codabar, {'F', '0'}}, // if checksum processing were implemented and checksum present and stripped then modifier would be 4 + // {BarcodeFormat::CodablockF, {'O', '4'}}, // '5' GS1 + {BarcodeFormat::Code128, {'C', '0'}}, // '1' GS1, '2' AIM + // {BarcodeFormat::Code16K, {'K', '0'}}, // '1' GS1, '2' AIM, '4' D1 PAD + {BarcodeFormat::Code39, {'A', '0'}}, // '3' checksum, '4' extended, '7' checksum,extended + {BarcodeFormat::Code93, {'G', '0'}}, // no modifiers + {BarcodeFormat::DataBar, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataBarExpanded, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataBarLimited, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataMatrix, {'d', '1', 3}}, // '2' GS1, '3' AIM + // {BarcodeFormat::DotCode, {'J', '0', 3}}, // '1' GS1, '2' AIM + {BarcodeFormat::DXFilmEdge, {}}, + {BarcodeFormat::EAN8, {'E', '4'}}, + {BarcodeFormat::EAN13, {'E', '0'}}, + // {BarcodeFormat::HanXin, {'h', '0', 1}}, // '2' GS1 + {BarcodeFormat::ITF, {'I', '0'}}, // '1' check digit + {BarcodeFormat::MaxiCode, {'U', '0', 2}}, // '1' mode 2 or 3 + // {BarcodeFormat::MicroPDF417, {'L', '2', char(-1)}}, + {BarcodeFormat::MicroQRCode, {'Q', '1', 1}}, + {BarcodeFormat::PDF417, {'L', '2', char(-1)}}, + {BarcodeFormat::QRCode, {'Q', '1', 1}}, // '3' GS1, '5' AIM + {BarcodeFormat::RMQRCode, {'Q', '1', 1}}, // '3' GS1, '5' AIM + {BarcodeFormat::UPCA, {'E', '0'}}, + {BarcodeFormat::UPCE, {'E', '0'}}, +}; + +static SymbologyIdentifier SymbologyIdentifierZint2ZXing(const CreatorOptions& opts, const ByteArray& ba) +{ + const BarcodeFormat format = opts.format(); + + auto i = FindIf(barcodeFormat2SymbologyIdentifier, [format](auto& v) { return v.format == format; }); + assert(i != std::end(barcodeFormat2SymbologyIdentifier)); + SymbologyIdentifier ret = i->si; + + if ((BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE).testFlag(format)) { + if (ba.size() > 13) // Have EAN-2/5 add-on? + ret.modifier = '3'; // Combined packet, EAN-13, UPC-A, UPC-E, with add-on + } else if (format == BarcodeFormat::Code39) { + if (FindIf(ba, iscntrl) != ba.end()) // Extended Code 39? + ret.modifier = static_cast(ret.modifier + 4); + } else if (opts.gs1() && SupportsGS1(format)) { + if ((BarcodeFormat::Aztec | BarcodeFormat::Code128).testFlag(format)) + ret.modifier = '1'; + else if (format == BarcodeFormat::DataMatrix) + ret.modifier = '2'; + else if ((BarcodeFormat::QRCode | BarcodeFormat::RMQRCode).testFlag(format)) + ret.modifier = '3'; + ret.aiFlag = AIFlag::GS1; + } + + return ret; +} + +static std::string ECLevelZint2ZXing(const zint_symbol* zint) +{ + constexpr char EC_LABELS_QR[4] = {'L', 'M', 'Q', 'H'}; + + const int symbology = zint->symbology; + const int option_1 = zint->option_1; + + switch (symbology) { + case BARCODE_AZTEC: + if ((option_1 >> 8) >= 0 && (option_1 >> 8) <= 99) + return std::to_string(option_1 >> 8) + "%"; + break; + case BARCODE_MAXICODE: + // Mode + if (option_1 >= 2 && option_1 <= 6) + return std::to_string(option_1); + break; + case BARCODE_PDF417: + case BARCODE_PDF417COMP: + // Convert to percentage + if (option_1 >= 0 && option_1 <= 8) { + int overhead = symbology == BARCODE_PDF417COMP ? 35 : 69; + int cols = (zint->width - overhead) / 17; + int tot_cws = zint->rows * cols; + assert(tot_cws); + return std::to_string((2 << option_1) * 100 / tot_cws) + "%"; + } + break; + // case BARCODE_MICROPDF417: + // if ((option_1 >> 8) >= 0 && (option_1 >> 8) <= 99) + // return std::to_string(option_1 >> 8) + "%"; + // break; + case BARCODE_QRCODE: + case BARCODE_MICROQR: + case BARCODE_RMQR: + // Convert to L/M/Q/H + if (option_1 >= 1 && option_1 <= 4) + return {EC_LABELS_QR[option_1 - 1]}; + break; + // case BARCODE_HANXIN: + // if (option_1 >= 1 && option_1 <= 4) + // return "L" + std::to_string(option_1); + // break; + default: + break; + } + + return {}; +} + +zint_symbol* CreatorOptions::zint() const +{ + auto& zint = d->zint; + + if (!zint) { +#ifdef PRINT_DEBUG +// printf("zint version: %d, sizeof(zint_symbol): %ld, options: %s\n", ZBarcode_Version(), sizeof(zint_symbol), options().c_str()); +#endif + zint.reset(ZBarcode_Create()); + + auto i = FindIf(barcodeFormatZXing2Zint, [zxing = format()](auto& v) { return v.zxing == zxing; }); + if (i == std::end(barcodeFormatZXing2Zint)) + throw std::invalid_argument("unsupported barcode format: " + ToString(format())); + + if (format() == BarcodeFormat::Code128 && gs1()) + zint->symbology = BARCODE_GS1_128; + else if (format() == BarcodeFormat::DataBar && stacked()) + zint->symbology = BARCODE_DBAR_OMNSTK; + else if (format() == BarcodeFormat::DataBarExpanded && stacked()) + zint->symbology = BARCODE_DBAR_EXPSTK; + else + zint->symbology = i->zint; + + zint->scale = 0.5f; + + if (!ecLevel().empty()) + zint->option_1 = ParseECLevel(zint->symbology, ecLevel()); + + if (auto val = version(); val && !IsLinearBarcode(format())) + zint->option_2 = *val; + + if (auto val = dataMask(); val && (BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode).testFlag(format())) + zint->option_3 = (zint->option_3 & 0xFF) | (*val + 1) << 8; + } + + return zint.get(); +} + +#define CHECK(ZINT_CALL) \ + if (int err = (ZINT_CALL); err >= ZINT_ERROR) \ + throw std::invalid_argument(StrCat(zint->errtxt, " (retval: ", std::to_string(err), ")")); + +Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& opts) +{ + auto zint = opts.zint(); + + zint->input_mode = mode == UNICODE_MODE && opts.gs1() && SupportsGS1(opts.format()) ? GS1_MODE : mode; + if (mode == UNICODE_MODE && static_cast(data)[0] != '[') + zint->input_mode |= GS1PARENS_MODE; + zint->output_options |= OUT_BUFFER_INTERMEDIATE | BARCODE_QUIET_ZONES | BARCODE_RAW_TEXT; + + if (mode == DATA_MODE && ZBarcode_Cap(zint->symbology, ZINT_CAP_ECI)) + zint->eci = static_cast(ECI::Binary); + + CHECK(ZBarcode_Encode_and_Buffer(zint, (uint8_t*)data, size, 0)); + +#ifdef PRINT_DEBUG + printf("create symbol with size: %dx%d\n", zint->width, zint->rows); +#endif + +#if 0 // use ReadBarcode to create Barcode object + auto buffer = std::vector(zint->bitmap_width * zint->bitmap_height); + std::transform(zint->bitmap, zint->bitmap + zint->bitmap_width * zint->bitmap_height, buffer.data(), + [](unsigned char v) { return (v == '0') * 0xff; }); + + auto res = ReadBarcode({buffer.data(), zint->bitmap_width, zint->bitmap_height, ImageFormat::Lum}, + ReaderOptions().setFormats(opts.format()).setIsPure(true).setBinarizer(Binarizer::BoolCast)); +#else + Content content; + +#ifdef ZXING_READERS + for (int i = 0; i < zint->raw_seg_count; ++i) { + const auto& raw_seg = zint->raw_segs[i]; +#ifdef PRINT_DEBUG + printf(" seg %d of %d with eci %d: %.*s\n", i, zint->raw_seg_count, raw_seg.eci, raw_seg.length, (char*)raw_seg.source); +#endif + if (ECI(raw_seg.eci) != ECI::ISO8859_1) + content.switchEncoding(ECI(raw_seg.eci)); + else + content.switchEncoding(CharacterSet::ISO8859_1); // set this as default to prevent guessing without setting "hasECI" + content.append({raw_seg.source, static_cast(raw_seg.length - (opts.format() == BarcodeFormat::Code93 ? 2 : 0))}); + } +#else + if (zint->text_length) { + content.switchEncoding(ECI::UTF8); + content.append({zint->text, static_cast(zint->text_length)}); + } else { + content.switchEncoding(mode == DATA_MODE ? ECI::Binary : ECI::UTF8); + content.append({static_cast(data), static_cast(size)}); + } +#endif + + content.symbology = SymbologyIdentifierZint2ZXing(opts, content.bytes); + + DecoderResult decRes(std::move(content)); + decRes.setEcLevel(ECLevelZint2ZXing(zint)); + DetectorResult detRes; + + auto res = Barcode(std::move(decRes), std::move(detRes), opts.format()); +#endif + + auto bits = BitMatrix(zint->bitmap_width, zint->bitmap_height); + std::transform(zint->bitmap, zint->bitmap + zint->bitmap_width * zint->bitmap_height, bits.row(0).begin(), + [](unsigned char v) { return (v == '1') * BitMatrix::SET_V; }); + res.symbol(std::move(bits)); + res.zint(std::move(opts.d->zint)); + + return res; +} + +Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& opts) +{ + return CreateBarcode(contents.data(), contents.size(), UNICODE_MODE, opts); +} + +#if __cplusplus > 201703L +Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& opts) +{ + return CreateBarcode(contents.data(), contents.size(), UNICODE_MODE, opts); +} +#endif + +Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& opts) +{ + return CreateBarcode(data, size, DATA_MODE, opts); +} + +// Writer ======================================================================== + +struct SetCommonWriterOptions +{ + zint_symbol* zint; + + SetCommonWriterOptions(zint_symbol* zint, const WriterOptions& opts) : zint(zint) + { + zint->show_hrt = opts.withHRT(); + + zint->output_options &= ~OUT_BUFFER_INTERMEDIATE; + zint->output_options |= opts.withQuietZones() ? BARCODE_QUIET_ZONES : BARCODE_NO_QUIET_ZONES; + + if (opts.scale()) + zint->scale = opts.scale() / 2.f; + else if (opts.sizeHint()) { + int size = std::max(zint->width, zint->rows); + zint->scale = std::max(1, int(float(opts.sizeHint()) / size)) / 2.f; + } + } + + // reset the defaults such that consecutive write calls don't influence each other + ~SetCommonWriterOptions() { zint->scale = 0.5f; } +}; + +} // ZXing + +#else // ZXING_USE_ZINT + +#include "MultiFormatWriter.h" +#include "ReadBarcode.h" + +namespace ZXing { + +zint_symbol* CreatorOptions::zint() const { return nullptr; } + +static Barcode CreateBarcode(BitMatrix&& bits, const CreatorOptions& opts) +{ + auto img = ToMatrix(bits); + + auto res = ReadBarcode({img.data(), img.width(), img.height(), ImageFormat::Lum}, + ReaderOptions().setFormats(opts.format()).setIsPure(true).setBinarizer(Binarizer::BoolCast)); + res.symbol(std::move(bits)); + return res; +} + +Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& opts) +{ + auto writer = MultiFormatWriter(opts.format()).setMargin(0); + if (!opts.ecLevel().empty()) + writer.setEccLevel(std::stoi(opts.ecLevel())); + writer.setEncoding(CharacterSet::UTF8); // write UTF8 (ECI value 26) for maximum compatibility + + return CreateBarcode(writer.encode(std::string(contents), 0, IsLinearBarcode(opts.format()) ? 50 : 0), opts); +} + +#if __cplusplus > 201703L +Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& opts) +{ + return CreateBarcodeFromText({reinterpret_cast(contents.data()), contents.size()}, opts); +} +#endif + +Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& opts) +{ + std::wstring bytes; + for (uint8_t c : ByteView(data, size)) + bytes.push_back(c); + + auto writer = MultiFormatWriter(opts.format()).setMargin(0); + if (!opts.ecLevel().empty()) + writer.setEccLevel(std::stoi(opts.ecLevel())); + writer.setEncoding(CharacterSet::BINARY); + + return CreateBarcode(writer.encode(bytes, 0, IsLinearBarcode(opts.format()) ? 50 : 0), opts); +} + +} // namespace ZXing + +#endif // ZXING_USE_ZINT + +#else // ZXING_WRITERS + +namespace ZXing { + +zint_symbol* CreatorOptions::zint() const { return nullptr; } + +Barcode CreateBarcodeFromText(std::string_view, const CreatorOptions&) +{ + throw std::runtime_error("This build of zxing-cpp does not support creating barcodes."); +} + +#if __cplusplus > 201703L +Barcode CreateBarcodeFromText(std::u8string_view, const CreatorOptions&) +{ + throw std::runtime_error("This build of zxing-cpp does not support creating barcodes."); +} +#endif + +Barcode CreateBarcodeFromBytes(const void*, int, const CreatorOptions&) +{ + throw std::runtime_error("This build of zxing-cpp does not support creating barcodes."); +} + +} // namespace ZXing + +#endif // ZXING_WRITERS + +namespace ZXing { + +std::string WriteBarcodeToSVG(const Barcode& barcode, [[maybe_unused]] const WriterOptions& opts) +{ + auto zint = barcode.zint(); + + if (!zint) + return ToSVG(barcode.symbol()); + +#if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT) + auto resetOnExit = SetCommonWriterOptions(zint, opts); + + zint->output_options |= BARCODE_MEMORY_FILE;// | EMBED_VECTOR_FONT; + strcpy(zint->outfile, "null.svg"); + + CHECK(ZBarcode_Print(zint, opts.rotate())); + + return std::string(reinterpret_cast(zint->memfile), zint->memfile_size); +#else + return {}; // unreachable code +#endif +} + +Image WriteBarcodeToImage(const Barcode& barcode, [[maybe_unused]] const WriterOptions& opts) +{ + auto zint = barcode.zint(); + + if (!zint) + return ToImage(barcode._symbol->copy(), IsLinearBarcode(barcode.format()), opts); + +#if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT) + auto resetOnExit = SetCommonWriterOptions(zint, opts); + + CHECK(ZBarcode_Buffer(zint, opts.rotate())); + +#ifdef PRINT_DEBUG + printf("write symbol with size: %dx%d\n", zint->bitmap_width, zint->bitmap_height); +#endif + auto iv = Image(zint->bitmap_width, zint->bitmap_height); + auto* src = zint->bitmap; + auto* dst = const_cast(iv.data()); + for(int y = 0; y < iv.height(); ++y) + for(int x = 0, w = iv.width(); x < w; ++x, src += 3) + *dst++ = RGBToLum(src[0], src[1], src[2]); + + return iv; +#else + return {}; // unreachable code +#endif +} + +std::string WriteBarcodeToUtf8(const Barcode& barcode, [[maybe_unused]] const WriterOptions& options) +{ + auto iv = barcode.symbol(); + if (!iv.data()) + return {}; + + constexpr auto map = std::array{" ", "▀", "▄", "█"}; + std::ostringstream res; + bool inverted = false; // TODO: take from WriterOptions + + for (int y = 0; y < iv.height(); y += 2) { + // for linear barcodes, only print line pairs that are distinct from the previous one + if (IsLinearBarcode(barcode.format()) && y > 1 && y < iv.height() - 1 + && memcmp(iv.data(0, y), iv.data(0, y - 2), 2 * iv.rowStride()) == 0) + continue; + + for (int x = 0; x < iv.width(); ++x) { + int tp = bool(*iv.data(x, y)) ^ inverted; + int bt = (iv.height() == 1 && tp) || (y + 1 < iv.height() && (bool(*iv.data(x, y + 1)) ^ inverted)); + res << map[tp | (bt << 1)]; + } + res << '\n'; + } + + return res.str(); +} + +} // namespace ZXing + +#endif // ZXING_EXPERIMENTAL_API diff --git a/core/src/WriteBarcode.h b/core/src/WriteBarcode.h new file mode 100644 index 0000000000..aa3cf5bec2 --- /dev/null +++ b/core/src/WriteBarcode.h @@ -0,0 +1,155 @@ +/* +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifdef ZXING_EXPERIMENTAL_API + +#include "Barcode.h" +#include "ImageView.h" + +#include +#include +#include + +extern "C" struct zint_symbol; + +namespace ZXing { + +class CreatorOptions +{ + struct Data; + + std::unique_ptr d; + + friend Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& options); + +public: + CreatorOptions(BarcodeFormat format, std::string options = {}); + + ~CreatorOptions(); + CreatorOptions(CreatorOptions&&); + CreatorOptions& operator=(CreatorOptions&&); + + zint_symbol* zint() const; + +#define ZX_PROPERTY(TYPE, NAME) \ + const TYPE& NAME() const noexcept; \ + CreatorOptions& NAME(TYPE v)&; \ + CreatorOptions&& NAME(TYPE v)&&; + + ZX_PROPERTY(BarcodeFormat, format) + ZX_PROPERTY(bool, readerInit) + ZX_PROPERTY(bool, forceSquareDataMatrix) + ZX_PROPERTY(std::string, ecLevel) + ZX_PROPERTY(std::string, options) + +#undef ZX_PROPERTY + +#define ZX_RO_PROPERTY(TYPE, NAME) \ + std::optional NAME() const noexcept; + + ZX_RO_PROPERTY(bool, gs1); + ZX_RO_PROPERTY(bool, stacked); + ZX_RO_PROPERTY(int, version); + ZX_RO_PROPERTY(int, dataMask); +#undef ZX_RO_PROPERTY +}; + +/** + * Generate barcode from unicode text + * + * @param contents UTF-8 string to encode into a barcode + * @param options CreatorOptions (including BarcodeFormat) + * @return #Barcode generated barcode + */ +Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& options); + +/** + * Generate barcode from raw binary data + * + * @param data array of bytes to encode into a barcode + * @param size size of byte array + * @param options CreatorOptions (including BarcodeFormat) + * @return #Barcode generated barcode + */ +Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& options); + +#if __cplusplus > 201703L +Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& options); + +template +requires std::ranges::contiguous_range && std::ranges::sized_range && (sizeof(std::ranges::range_value_t) == 1) +Barcode CreateBarcodeFromBytes(const R& contents, const CreatorOptions& options) +{ + return CreateBarcodeFromBytes(std::ranges::data(contents), std::ranges::size(contents), options); +} +#else +template +Barcode CreateBarcodeFromBytes(const R& contents, const CreatorOptions& options) +{ + return CreateBarcodeFromBytes(std::data(contents), std::size(contents), options); +} +#endif + +// ================================================================================= + +class WriterOptions +{ + struct Data; + + std::unique_ptr d; + +public: + WriterOptions(); + ~WriterOptions(); + WriterOptions(WriterOptions&&); + WriterOptions& operator=(WriterOptions&&); + +#define ZX_PROPERTY(TYPE, NAME) \ + TYPE NAME() const noexcept; \ + WriterOptions& NAME(TYPE v)&; \ + WriterOptions&& NAME(TYPE v)&&; + + ZX_PROPERTY(int, scale) + ZX_PROPERTY(int, sizeHint) + ZX_PROPERTY(int, rotate) + ZX_PROPERTY(bool, withHRT) + ZX_PROPERTY(bool, withQuietZones) + +#undef ZX_PROPERTY +}; + + +/** + * Write barcode symbol to SVG + * + * @param barcode Barcode to write + * @param options WriterOptions to parameterize rendering + * @return std::string SVG representation of barcode symbol + */ +std::string WriteBarcodeToSVG(const Barcode& barcode, const WriterOptions& options = {}); + +/** + * Write barcode symbol to a utf8 string using graphical characters (e.g. '▀') + * + * @param barcode Barcode to write + * @param options WriterOptions to parameterize rendering + * @return std::string Utf8 string representation of barcode symbol + */ +std::string WriteBarcodeToUtf8(const Barcode& barcode, const WriterOptions& options = {}); + +/** + * Write barcode symbol to Image (Bitmap) + * + * @param barcode Barcode to write + * @param options WriterOptions to parameterize rendering + * @return Image Bitmap reprentation of barcode symbol + */ +Image WriteBarcodeToImage(const Barcode& barcode, const WriterOptions& options = {}); + +} // ZXing + +#endif // ZXING_EXPERIMENTAL_API diff --git a/core/src/ZXAlgorithms.h b/core/src/ZXAlgorithms.h index 3965ab0746..487b1cafcd 100644 --- a/core/src/ZXAlgorithms.h +++ b/core/src/ZXAlgorithms.h @@ -8,11 +8,13 @@ #include "Error.h" #include +#include #include #include #include #include #include +#include #include namespace ZXing { @@ -46,15 +48,29 @@ inline bool Contains(const char* str, char c) { return strchr(str, c) != nullptr; } +inline bool Contains(std::string_view str, std::string_view substr) { + return str.find(substr) != std::string_view::npos; +} + template