diff --git a/.editorconfig b/.editorconfig index 50d16bc2ce..f8db50e18e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 -[*.{cpp,c,h,html,py,kt}] +[*.{cpp,c,h,html,py,kt,cs,qlm}] indent_style = tab indent_size = 4 max_line_length = 135 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d476f125a2..4691012156 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,141 +17,206 @@ 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 - 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: 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 - 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}}-artifacts + path: install + + build-experimental: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-13] - build-ubuntu-sanitize: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - 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 + run: cmake --build build -j8 --config ${{env.BUILD_TYPE}} + + - 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: - ref: ${{github.ref}} - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + name: ${{matrix.os}}-exp-artifacts + path: install + + build-ubuntu-sanitize: + runs-on: ubuntu-20.04 # see https://github.com/quantumlib/Stim/issues/717#issuecomment-2002623560 + steps: + - 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}} + submodules: true - name: Build the swift package - shell: sh - working-directory: ${{runner.workspace}}/${{github.event.repository.name}} 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 - shell: sh - working-directory: ${{runner.workspace}}/${{github.event.repository.name}} run: pod lib lint --allow-warnings build-android: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@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.12'] - os: [ubuntu-latest, macos-latest, windows-latest] + 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 setuptools python -m pip install numpy pillow @@ -163,3 +228,76 @@ jobs: - name: Test module working-directory: wrappers/python run: python -m unittest -v + + 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 + + - 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 96f35f4209..12934f156b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,11 +37,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@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,6 +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 4c9342c845..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 }} @@ -60,13 +60,13 @@ jobs: # 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 index eb789c3d26..afb3be26c4 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -1,4 +1,4 @@ -name: publish-adroid +name: publish-android on: # release: @@ -14,14 +14,20 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - 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 }} 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 index 637e91e557..d00b3f141b 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -22,43 +22,55 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-13, windows-latest] # at least macos-13 is required to enable c++20 support steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + submodules: true - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install cibuildwheel - run: python3 -m pip install cibuildwheel==2.16.2 + run: python3 -m pip install cibuildwheel==2.22.0 - name: Build wheels run: python3 -m cibuildwheel --output-dir wheelhouse wrappers/python env: - CIBW_BUILD: cp38-* cp39-* cp310-* cp311-* cp312-* + # 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 - - uses: actions/upload-artifact@v3 + - 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@v3 + - uses: actions/checkout@v4 with: - submodules: recursive + submodules: true - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - name: Install dependencies working-directory: wrappers/python @@ -68,8 +80,10 @@ jobs: working-directory: wrappers/python run: python3 setup.py sdist - - uses: actions/upload-artifact@v3 + - name: Upload sdist + uses: actions/upload-artifact@v4 with: + name: cibw-sdist path: wrappers/python/dist/*.tar.gz upload-pypi: @@ -85,10 +99,14 @@ jobs: # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') if: github.event_name == 'release' || github.event.inputs.publish == 'y' steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: - name: artifact + pattern: cibw-* path: dist + merge-multiple: true + + - name: List wheels + run: ls dist - uses: pypa/gh-action-pypi-publish@release/v1 # with: 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/.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 7c6cc67d41..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,35 +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() -if (BUILD_EXPERIMENTAL_API) - add_definitions (-DZXING_BUILD_EXPERIMENTAL_API) +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() -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}") +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) @@ -70,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 index 27e70a9bc2..5faf571d10 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,11 @@ let package = Package( .target( name: "ZXingCppCore", path: "core/src", - publicHeadersPath: "." + exclude: ["libzint", "ZXingC.cpp", "ZXingCpp.cpp"], + publicHeadersPath: ".", + cxxSettings: [ + .define("ZXING_READERS") + ] ), .target( name: "ZXingCpp", diff --git a/README.md b/README.md index 1054cb2fae..112324a1da 100644 --- a/README.md +++ b/README.md @@ -11,48 +11,50 @@ It was originally ported from the Java [ZXing Library](https://github.com/zxing/ 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-) +* [EUREKAM](https://eurekam.fr/) 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 | rMQR Code | -| EAN-13 | Codabar | Aztec | -| DataBar | DataBar Expanded | DataMatrix | -| | ITF | PDF417 | -| | | 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, Micro QR Code and rMQR 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. C++20 is enabled by default for the Android, iOS, Python and WASM wrappers. + * 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 `ZING_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: 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++ @@ -67,22 +69,24 @@ int main(int argc, char** argv) auto image = ZXing::ImageView(data, width, height, ZXing::ImageFormat::Lum); auto options = ZXing::ReaderOptions().setFormats(ZXing::BarcodeFormat::Any); - auto results = ZXing::ReadBarcodes(image, options); + auto barcodes = ZXing::ReadBarcodes(image, options); - for (const auto& r : results) - std::cout << ZXing::ToString(r.format()) << ": " << r.text() << "\n"; + 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: 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). +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) @@ -94,12 +98,12 @@ 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 --config Release ``` diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index e870fb9c66..fb19617bb8 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,146 +1,200 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.16) -project (ZXing VERSION "2.2.1") +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_CORE_DEFINES ${ZXING_CORE_DEFINES} - /Zc:__cplusplus + 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> +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 + /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/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/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/DecodeHints.cpp> # [[deprecated]] src/DecoderResult.h src/DetectorResult.h - src/Error.h 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/ReaderOptions.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 @@ -154,44 +208,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/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/ReaderOptions.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/Range.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 @@ -202,7 +258,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 @@ -217,13 +273,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 @@ -235,7 +293,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 @@ -251,9 +309,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 @@ -265,13 +321,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 @@ -289,6 +347,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 @@ -296,10 +358,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 @@ -325,9 +386,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 @@ -354,9 +419,11 @@ if (BUILD_READERS) src/pdf417/PDFReader.cpp src/pdf417/PDFScanningDecoder.h src/pdf417/PDFScanningDecoder.cpp + src/pdf417/CustomData.h + 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 @@ -369,15 +436,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 @@ -395,7 +464,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 @@ -409,18 +478,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}) @@ -428,7 +485,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) @@ -442,7 +498,6 @@ add_library (ZXing ${ONED_FILES} ${PDF417_FILES} ${QRCODE_FILES} - ${LIBZUECI_FILES} ) target_include_directories (ZXing @@ -451,25 +506,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) @@ -484,6 +557,49 @@ endif() set_target_properties(ZXing PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") +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) set(ZX_INSTALL_TARGETS ZXing) @@ -498,10 +614,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" ) @@ -511,7 +627,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() 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/Result.cpp b/core/src/Barcode.cpp similarity index 52% rename from core/src/Result.cpp rename to core/src/Barcode.cpp index 427ed84e51..41b2137d36 100644 --- a/core/src/Result.cpp +++ b/core/src/Barcode.cpp @@ -4,12 +4,28 @@ */ // SPDX-License-Identifier: Apache-2.0 -#include "Result.h" +#include "Barcode.h" #include "DecoderResult.h" -#include "TextDecoder.h" +#include "DetectorResult.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 @@ -25,15 +41,18 @@ Result::Result(const std::string& text, int y, int xStart, int xStop, BarcodeFor _readerInit(readerInit) {} -Result::Result(DecoderResult&& decodeResult, Position&& position, BarcodeFormat format) +Result::Result(DecoderResult&& decodeResult, DetectorResult&& detectorResult, BarcodeFormat format) : _content(std::move(decodeResult).content()), _error(std::move(decodeResult).error()), - _position(std::move(position)), + _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())) +#endif { if (decodeResult.versionNumber()) snprintf(_version, 4, "%d", decodeResult.versionNumber()); @@ -42,9 +61,13 @@ Result::Result(DecoderResult&& decodeResult, Position&& position, BarcodeFormat // 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.symbology.code != 0 && !error(); + return format() != BarcodeFormat::None && !_content.bytes.empty() && !error(); } const ByteArray& Result::bytes() const @@ -121,6 +144,24 @@ Result& Result::setReaderOptions(const ReaderOptions& 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->row(0).begin(), _symbol->width(), _symbol->height(), ImageFormat::Lum}; +} + +void Result::zint(unique_zint_symbol&& z) +{ + _zint = std::shared_ptr(std::move(z)); +} +#endif + bool Result::operator==(const Result& o) const { // handle case where both are MatrixCodes first @@ -144,59 +185,64 @@ bool Result::operator==(const Result& o) const // the following code is only meant for this or other lineCount == 1 assert(lineCount() == 1 || o.lineCount() == 1); - const auto& r1 = lineCount() == 1 ? *this : o; - const auto& r2 = lineCount() == 1 ? o : *this; + // 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(r2.position().topLeft() - r1.position().topLeft()); - auto dBot = maxAbsComponent(r2.position().bottomLeft() - r1.position().topLeft()); - auto length = maxAbsComponent(r1.position().topLeft() - r1.position().bottomRight()); - auto dLength = std::abs(length - maxAbsComponent(r2.position().topLeft() - r2.position().bottomRight())); + // 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) < length / 2 && dLength < length / 5; + return std::min(dTop, dBot) < slLength / 2 && std::abs(slLength - mlLength) < slLength / 5; } -Result MergeStructuredAppendSequence(const Results& results) +Barcode MergeStructuredAppendSequence(const Barcodes& barcodes) { - if (results.empty()) + if (barcodes.empty()) return {}; - std::list allResults(results.begin(), results.end()); - allResults.sort([](const Result& r1, const Result& r2) { return r1.sequenceIndex() < r2.sequenceIndex(); }); + std::list allBarcodes(barcodes.begin(), barcodes.end()); + allBarcodes.sort([](const Barcode& r1, const Barcode& r2) { return r1.sequenceIndex() < r2.sequenceIndex(); }); - Result res = allResults.front(); - for (auto i = std::next(allResults.begin()); i != allResults.end(); ++i) + 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 (allResults.back().sequenceSize() != Size(allResults) || - !std::all_of(allResults.begin(), allResults.end(), - [&](Result& it) { return it.sequenceId() == allResults.front().sequenceId(); })) + 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; } -Results MergeStructuredAppendSequences(const Results& results) +Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes) { - std::map sas; - for (auto& res : results) { - if (res.isPartOfSequence()) - sas[res.sequenceId()].push_back(res); + std::map sas; + for (auto& barcode : barcodes) { + if (barcode.isPartOfSequence()) + sas[barcode.sequenceId()].push_back(barcode); } - Results saiResults; + Barcodes res; for (auto& [id, seq] : sas) { - auto res = MergeStructuredAppendSequence(seq); - if (res.isValid()) - saiResults.push_back(std::move(res)); + auto barcode = MergeStructuredAppendSequence(seq); + if (barcode.isValid()) + res.push_back(std::move(barcode)); } - return saiResults; + return res; } -} // ZXing +} // namespace ZXing diff --git a/core/src/Barcode.h b/core/src/Barcode.h new file mode 100644 index 0000000000..051205ae41 --- /dev/null +++ b/core/src/Barcode.h @@ -0,0 +1,215 @@ +/* +* 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; +} + +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; + + /** + * @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(); } +#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; +#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 01f4d3860e..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"}, @@ -66,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 72542ed363..1b29272410 100644 --- a/core/src/BarcodeFormat.h +++ b/core/src/BarcodeFormat.h @@ -1,4 +1,4 @@ -īģŋ/* +/* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors */ @@ -40,12 +40,15 @@ enum class BarcodeFormat 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, + 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 = RMQRCode, ///> implementation detail, don't use + _max = DataBarLimited, ///> implementation detail, don't use }; ZX_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat) diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index 4cbe5d5ef2..8b4914e9bf 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -69,6 +69,8 @@ void BinaryBitmap::invert() 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 a260b3a3b8..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 { 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 6d7dd54c9e..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. diff --git a/core/src/BitMatrixCursor.h b/core/src/BitMatrixCursor.h index 25cc95cb13..a7fae30dc9 100644 --- a/core/src/BitMatrixCursor.h +++ b/core/src/BitMatrixCursor.h @@ -7,7 +7,6 @@ #include "BitMatrix.h" -#include #include namespace ZXing { 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..e21f65e664 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; diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index 02c72c171c..68f29b6d45 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; } } diff --git a/core/src/Content.cpp b/core/src/Content.cpp index 4d3c44657c..d0f2e80a16 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 { @@ -98,6 +102,7 @@ std::string Content::render(bool withECI) const if (empty() || !canProcess()) return {}; +#ifdef ZXING_READERS std::string res; if (withECI) res = symbology.toString(true); @@ -136,6 +141,10 @@ std::string Content::render(bool withECI) const }); 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 +154,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 +162,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); @@ -190,6 +201,7 @@ ByteArray Content::bytesECI() const CharacterSet Content::guessEncoding() const { +#ifdef ZXING_READERS // assemble all blocks with unknown encoding ByteArray input; ForEachECIBlock([&](ECI eci, int begin, int end) { @@ -201,10 +213,14 @@ CharacterSet Content::guessEncoding() const return CharacterSet::Unknown; return TextDecoder::GuessEncoding(input.data(), input.size(), CharacterSet::ISO8859_1); +#else + return CharacterSet::Unknown; +#endif } ContentType Content::type() const { +#ifdef ZXING_READERS if (empty()) return ContentType::Text; @@ -235,6 +251,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/DecodeHints.cpp b/core/src/DecodeHints.cpp index 8dbd5a8685..9a54d8d2fb 100644 --- a/core/src/DecodeHints.cpp +++ b/core/src/DecodeHints.cpp @@ -17,12 +17,12 @@ struct DecodeHints char data[sizeof(ReaderOptions)]; }; -Result ReadBarcode(const ImageView& image, const DecodeHints& hints = {}) +Barcode ReadBarcode(const ImageView& image, const DecodeHints& hints = {}) { return ReadBarcode(image, reinterpret_cast(hints)); } -Results ReadBarcodes(const ImageView& image, const DecodeHints& hints = {}) +Barcodes ReadBarcodes(const ImageView& image, const DecodeHints& hints = {}) { return ReadBarcodes(image, reinterpret_cast(hints)); } diff --git a/core/src/DecoderResult.h b/core/src/DecoderResult.h index 02b2850841..de068c4263 100644 --- a/core/src/DecoderResult.h +++ b/core/src/DecoderResult.h @@ -24,6 +24,7 @@ class DecoderResult std::string _ecLevel; int _lineCount = 0; int _versionNumber = 0; + int _dataMask = 0; StructuredAppendInfo _structuredAppend; bool _isMirrored = false; bool _readerInit = false; @@ -43,7 +44,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; } @@ -71,6 +72,7 @@ class DecoderResult ZX_PROPERTY(std::string, ecLevel, setEcLevel) ZX_PROPERTY(int, lineCount, setLineCount) ZX_PROPERTY(int, versionNumber, setVersionNumber) + ZX_PROPERTY(int, dataMask, setDataMask) ZX_PROPERTY(StructuredAppendInfo, structuredAppend, setStructuredAppend) ZX_PROPERTY(Error, error, setError) ZX_PROPERTY(bool, isMirrored, setIsMirrored) 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..5ee06a9de7 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,17 +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 (!(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE | BarcodeFormat::EAN8).testFlag(barcode.format())) return {}; - auto txt = result.bytes().asString(); + auto txt = barcode.bytes().asString(); auto pos = txt.find(' '); return pos != std::string::npos ? std::string(txt.substr(pos + 1)) : std::string(); } diff --git a/core/src/GTIN.h b/core/src/GTIN.h index d56b604e5f..9148844ff3 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) @@ -47,10 +44,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/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..fb0b3ec26e 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -11,8 +11,6 @@ #include #include -#include -#include #include namespace ZXing { @@ -48,6 +46,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,9 +110,9 @@ 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; 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 9a72dc6b31..f264b608f8 100644 --- a/core/src/HRI.cpp +++ b/core/src/HRI.cpp @@ -10,27 +10,29 @@ #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); } }; -// https://github.com/gs1/gs1-syntax-dictionary 2023-09-22 +// https://github.com/gs1/gs1-syntax-dictionary 2024-06-10 static const AiInfo aiInfos[] = { //TWO_DIGIT_DATA_LENGTH { "00", 18 }, @@ -218,6 +220,16 @@ static const AiInfo aiInfos[] = { { "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 }, @@ -292,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 @@ -324,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 a74fbd2872..33562817a0 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -9,17 +9,19 @@ #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 constexpr int BLOCK_SIZE = 8; -static constexpr int MINIMUM_DIMENSION = BLOCK_SIZE * 5; +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); +#ifndef NDEBUG + 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); + +#ifndef NDEBUG + 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 + } + } + +#ifndef NDEBUG + 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()); + +#ifndef NDEBUG + 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); + +#ifndef NDEBUG + for (int yy = 0; yy < 8; ++yy) + for (int xx = 0; xx < 8; ++xx) + out.set(xoffset + xx, yoffset + yy, thresholds(x, y)); +#endif + } + } + +#ifndef NDEBUG + 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/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 27e7f31311..fedc9e3a99 100644 --- a/core/src/MultiFormatReader.cpp +++ b/core/src/MultiFormatReader.cpp @@ -46,30 +46,32 @@ MultiFormatReader::MultiFormatReader(const ReaderOptions& opts) : _opts(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 _opts.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 (!_opts.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()); +#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 fe7749b525..17e3229e7a 100644 --- a/core/src/MultiFormatReader.h +++ b/core/src/MultiFormatReader.h @@ -6,14 +6,13 @@ #pragma once -#include "Result.h" +#include "Barcode.h" #include #include namespace ZXing { -class Result; class Reader; class BinaryBitmap; class ReaderOptions; @@ -25,10 +24,10 @@ class MultiFormatReader 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; diff --git a/core/src/Pattern.h b/core/src/Pattern.h index f4d377fc4c..891060b45e 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; @@ -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] / (2 + (LEN < 6)) + .5}; for (int x = 0; x < LEN; ++x) if (std::abs(view[x] - pattern[x] * modSize[x]) > thr[x]) return 0; - const float_t moduleSize = (modSize[0] + modSize[1]) / 2; - return moduleSize; + 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/Point.h b/core/src/Point.h index 19e1366099..e1146f8056 100644 --- a/core/src/Point.h +++ b/core/src/Point.h @@ -6,7 +6,6 @@ #pragma once #include -#include #include namespace 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/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 43269e0c6d..88c0ac2e20 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -5,12 +5,17 @@ #include "ReadBarcode.h" -#include "ReaderOptions.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 @@ -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 && @@ -104,7 +108,14 @@ ImageView SetupLumImageView(ImageView iv, LumImage& lum, const ReaderOptions& op throw std::invalid_argument("Invalid image format"); if (opts.binarizer() == Binarizer::GlobalHistogram || opts.binarizer() == Binarizer::LocalAverage) { - if (iv.format() != ImageFormat::Lum) { + // 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,35 +139,38 @@ std::unique_ptr CreateBitmap(ZXing::Binarizer binarizer, const Ima return {}; // silence gcc warning } -Result ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) +Barcode ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) { return FirstOrDefault(ReadBarcodes(_iv, ReaderOptions(opts).setMaxNumberOfSymbols(1))); } -Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) +Barcodes ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) { - if (sizeof(PatternType) < 4 && opts.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, opts); MultiFormatReader reader(opts); if (opts.isPure()) - return {reader.read(*CreateBitmap(opts.binarizer(), iv))}; + return {reader.read(*CreateBitmap(opts.binarizer(), iv)).setReaderOptions(opts)}; std::unique_ptr closedReader; -#ifdef ZXING_BUILD_EXPERIMENTAL_API +#ifdef ZXING_EXPERIMENTAL_API auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode; ReaderOptions closedOptions = opts; - if (opts.tryDenoise() && opts.hasFormat(formatsBenefittingFromClosing)) { + 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, opts.downscaleThreshold() * opts.tryDownscale(), opts.downscaleFactor()); - Results results; + Barcodes res; int maxSymbols = opts.maxNumberOfSymbols() ? opts.maxNumberOfSymbols() : INT_MAX; for (auto&& iv : pyramid.layers) { auto bitmap = CreateBitmap(opts.binarizer(), iv); @@ -172,20 +186,34 @@ Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) for (auto& r : rs) { if (iv.width() != _iv.width()) r.setPosition(Scale(r.position(), _iv.width() / iv.width())); - if (!Contains(results, r)) { + 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 343ac8487b..d932398ad7 100644 --- a/core/src/ReadBarcode.h +++ b/core/src/ReadBarcode.h @@ -7,7 +7,7 @@ #include "ReaderOptions.h" #include "ImageView.h" -#include "Result.h" +#include "Barcode.h" namespace ZXing { @@ -16,18 +16,18 @@ namespace ZXing { * * @param image view of the image data including layout and format * @param options optional ReaderOptions to parameterize / speed up detection - * @return #Result structure + * @return #Barcode structure */ -Result ReadBarcode(const ImageView& image, const ReaderOptions& options = {}); +Barcode ReadBarcode(const ImageView& image, const ReaderOptions& options = {}); /** * Read barcodes from an ImageView * * @param image view of the image data including layout and format * @param options optional ReaderOptions to parameterize / speed up detection - * @return #Results list of results found, may be empty + * @return #Barcodes list of barcodes found, may be empty */ -Results ReadBarcodes(const ImageView& image, const ReaderOptions& options = {}); +Barcodes ReadBarcodes(const ImageView& image, const ReaderOptions& options = {}); } // ZXing diff --git a/core/src/Reader.h b/core/src/Reader.h index 7ece21b56d..fdac2d02cf 100644 --- a/core/src/Reader.h +++ b/core/src/Reader.h @@ -7,7 +7,7 @@ #pragma once #include "ReaderOptions.h" -#include "Result.h" +#include "Barcode.h" namespace ZXing { @@ -26,12 +26,12 @@ class Reader 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() || (_opts.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 index 3b4cabfa9e..97c1bb0b9e 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -63,7 +63,7 @@ class ReaderOptions Binarizer _binarizer : 2; TextMode _textMode : 3; CharacterSet _characterSet : 6; -#ifdef ZXING_BUILD_EXPERIMENTAL_API +#ifdef ZXING_EXPERIMENTAL_API bool _tryDenoise : 1; #endif @@ -80,26 +80,26 @@ class ReaderOptions _tryInvert(1), _tryDownscale(1), _isPure(0), - _tryCode39ExtendedMode(0), + _tryCode39ExtendedMode(1), _validateCode39CheckSum(0), _validateITFCheckSum(0), - _returnCodabarStartEnd(0), + _returnCodabarStartEnd(1), _returnErrors(0), _downscaleFactor(3), _eanAddOnSymbol(EanAddOnSymbol::Ignore), _binarizer(Binarizer::LocalAverage), _textMode(TextMode::HRI), _characterSet(CharacterSet::Unknown) -#ifdef ZXING_BUILD_EXPERIMENTAL_API +#ifdef ZXING_EXPERIMENTAL_API , _tryDenoise(0) #endif {} -#define ZX_PROPERTY(TYPE, GETTER, SETTER) \ +#define ZX_PROPERTY(TYPE, GETTER, SETTER, ...) \ TYPE GETTER() const noexcept { return _##GETTER; } \ - ReaderOptions& SETTER(TYPE v)& { return (void)(_##GETTER = std::move(v)), *this; } \ - ReaderOptions&& SETTER(TYPE v)&& { return (void)(_##GETTER = std::move(v)), std::move(*this); } + __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) @@ -116,7 +116,7 @@ class ReaderOptions /// Also try detecting code in downscaled images (depending on image size). ZX_PROPERTY(bool, tryDownscale, setTryDownscale) -#ifdef ZXING_BUILD_EXPERIMENTAL_API +#ifdef ZXING_EXPERIMENTAL_API /// Also try detecting code after denoising (currently morphological closing filter for 2D symbologies only). ZX_PROPERTY(bool, tryDenoise, setTryDenoise) #endif @@ -141,25 +141,25 @@ class ReaderOptions /// 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. + /// Enable the heuristic to detect and decode "full ASCII"/extended Code39 symbols ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode) - /// Assume Code-39 codes employ a check digit and validate it. - ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum) + /// Deprecated / does nothing. The Code39 symbol has a valid checksum iff symbologyIdentifier()[2] is an odd digit + ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum, [[deprecated]]) - /// Assume ITF codes employ a GS1 check digit and validate it. - ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum) + /// Deprecated / does nothing. The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'. + ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum, [[deprecated]]) - /// If true, return the start and end chars in a Codabar barcode instead of stripping them. - ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd) + /// 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 @Result::error()) + /// 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 Result::text() function + /// 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) diff --git a/core/src/RegressionLine.h b/core/src/RegressionLine.h index dac602ee9f..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,7 +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 std::accumulate(_points.begin(), _points.end(), PointF()) / _points.size(); } + PointF centroid() const { return Reduce(_points) / _points.size(); } void reset() { @@ -112,11 +111,18 @@ 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; @@ -124,6 +130,7 @@ class RegressionLine break; #ifdef PRINT_DEBUG 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.h b/core/src/Result.h index 30a180f48e..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 "ReaderOptions.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& setReaderOptions(const ReaderOptions& opts); - - friend Result MergeStructuredAppendSequence(const std::vector& results); - friend std::vector ReadBarcodes(const ImageView&, const ReaderOptions&); - 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 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; - - bool operator==(const Result& o) const; - -private: - Content _content; - Error _error; - Position _position; - ReaderOptions _readerOpts; - 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/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/WriteBarcode.cpp b/core/src/WriteBarcode.cpp new file mode 100644 index 0000000000..8b26843959 --- /dev/null +++ b/core/src/WriteBarcode.cpp @@ -0,0 +1,486 @@ +/* +* Copyright 2024 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ZXING_EXPERIMENTAL_API + +#include "WriteBarcode.h" +#include "BitMatrix.h" + +#if !defined(ZXING_READERS) && !defined(ZXING_WRITERS) +#include "Version.h" +#endif + +#include + +#ifdef ZXING_USE_ZINT + +#include + +#else + +struct zint_symbol {}; + +#endif // ZXING_USE_ZINT + +namespace ZXing { + +struct CreatorOptions::Data +{ + BarcodeFormat format; + 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) : format(f) {} +#endif +}; + +#define ZX_PROPERTY(TYPE, NAME) \ + 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) + +#undef ZX_PROPERTY + +CreatorOptions::CreatorOptions(BarcodeFormat format) : d(std::make_unique(format)) {} +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 IsLinearCode(BarcodeFormat format) +{ + return BarcodeFormats(BarcodeFormat::LinearCodes).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" +#include "ReadBarcode.h" + +#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_EANX}, + {BarcodeFormat::EAN13, BARCODE_EANX}, + {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; + 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("Invalid ecLevel: '" + std::string(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; + }; + + if (s.back()=='%'){ + switch (symbology) { + case BARCODE_QRCODE: + case BARCODE_MICROQR: + case BARCODE_RMQR: + return findClosestECLevel({20, 37, 55, 65}, res); + case BARCODE_AZTEC: + return findClosestECLevel({10, 23, 26, 50}, res); + case BARCODE_PDF417: + // TODO: do something sensible with PDF417? + default: + return -1; + } + } + + return res; +}; + +zint_symbol* CreatorOptions::zint() const +{ + auto& zint = d->zint; + + if (!zint) { +#ifdef PRINT_DEBUG + printf("zint version: %d, sizeof(zint_symbol): %ld\n", ZBarcode_Version(), sizeof(zint_symbol)); +#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())); + zint->symbology = i->zint; + + zint->scale = 0.5f; + + if (!ecLevel().empty()) + zint->option_1 = ParseECLevel(zint->symbology, ecLevel()); + } + + return zint.get(); +} + +#define CHECK(ZINT_CALL) \ + if (int err = (ZINT_CALL); err >= ZINT_ERROR) \ + throw std::invalid_argument(zint->errtxt); + +Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& opts) +{ + auto zint = opts.zint(); + + zint->input_mode = mode; + zint->output_options |= OUT_BUFFER_INTERMEDIATE | BARCODE_QUIET_ZONES; + + 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 + +#ifdef ZXING_READERS + 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 + //TODO: replace by proper construction from encoded data from within zint + auto res = Barcode(std::string((const char*)data, size), 0, 0, 0, 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, IsLinearCode(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 : std::basic_string_view((uint8_t*)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, IsLinearCode(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(), IsLinearCode(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 (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..6f149b2523 --- /dev/null +++ b/core/src/WriteBarcode.h @@ -0,0 +1,144 @@ +/* +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifdef ZXING_EXPERIMENTAL_API + +#include "Barcode.h" +#include "ImageView.h" + +#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); + + ~CreatorOptions(); + CreatorOptions(CreatorOptions&&); + CreatorOptions& operator=(CreatorOptions&&); + + zint_symbol* zint() const; + +#define ZX_PROPERTY(TYPE, NAME) \ + 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) + +#undef ZX_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..66898363eb 100644 --- a/core/src/ZXAlgorithms.h +++ b/core/src/ZXAlgorithms.h @@ -47,14 +47,24 @@ inline bool Contains(const char* str, char c) { } template