diff --git a/.gitignore b/.gitignore index 3e01eb7d..52eb8191 100644 --- a/.gitignore +++ b/.gitignore @@ -60,7 +60,7 @@ Makefile.in .cache/ # Debug outputs -core +/core vgcore.* callgrind.out.* diff --git a/tools/chatgui/zxing-cpp/.clang-format b/tools/chatgui/zxing-cpp/.clang-format deleted file mode 100644 index 50580847..00000000 --- a/tools/chatgui/zxing-cpp/.clang-format +++ /dev/null @@ -1,43 +0,0 @@ ---- -Language: Cpp -Standard: c++17 -BasedOnStyle: LLVM - -IndentWidth: 4 -TabWidth: 4 -UseTab: ForContinuationAndIndentation # ForIndentation - -AccessModifierOffset: -4 -ColumnLimit: 135 - -#AlignConsecutiveAssignments: true -AlignConsecutiveBitFields: true -AlignEscapedNewlines: DontAlign -AlignTrailingComments: true - -AllowShortCaseLabelsOnASingleLine: true -AllowShortFunctionsOnASingleLine: Inline -#AllowShortLambdasOnASingleLine: Inline -AllowShortEnumsOnASingleLine: true -AllowAllArgumentsOnNextLine: true -AllowAllParametersOfDeclarationOnNextLine: true - -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakTemplateDeclarations: Yes -BreakBeforeBraces: Custom -BraceWrapping: - AfterClass: true - AfterEnum: true - AfterStruct: true - AfterUnion: true - AfterFunction: true - SplitEmptyFunction: false -BreakBeforeBinaryOperators: NonAssignment -BreakBeforeTernaryOperators: true -ConstructorInitializerAllOnOneLineOrOnePerLine: true -FixNamespaceComments: true -IncludeBlocks: Regroup -KeepEmptyLinesAtTheStartOfBlocks: false -PointerAlignment: Left -ReflowComments: true -SortIncludes: true diff --git a/tools/chatgui/zxing-cpp/.clang-tidy b/tools/chatgui/zxing-cpp/.clang-tidy deleted file mode 100644 index e41d7957..00000000 --- a/tools/chatgui/zxing-cpp/.clang-tidy +++ /dev/null @@ -1,58 +0,0 @@ ---- -Checks: > - -*, - bugprone-*, - -bugprone-easily-swappable-parameters, - -bugprone-exception-escape, - -bugprone-implicit-widening-of-multiplication-result, - -bugprone-integer-division, - -bugprone-macro-parentheses, - -bugprone-narrowing-conversions, - -bugprone-signed-char-misuse, - -bugprone-use-after-move, - -bugprone-assignment-in-if-condition, - performance-*, - -performance-enum-size, - -performance-move-const-arg, - portability-*, - -portability-avoid-pragma-once, - readability-avoid-const-params-in-decls, - readability-const-return-type, - readability-delete-null-pointer, - readability-deleted-default, - readability-inconsistent-declaration-parameter-name, - readability-misleading-indentation, - readability-misplaced-array-index, - readability-non-const-parameter, - readability-redundant-*, - readability-simplify-*, - -readability-simplify-boolean-expr, - readability-static-*, - -readability-static-definition-in-anonymous-namespace, - readability-string-compare, - readability-suspicious-call-argument, - readability-uniqueptr-delete-release, - readability-use-anyofallof, - -readability-redundant-inline-specifier, - -readability-redundant-member-init, - modernize-deprecated-*, - modernize-loop-convert, - modernize-make-*, - modernize-pass-by-value, - modernize-raw-string-literal, - modernize-redundant-void-arg, - modernize-replace-*, - modernize-return-braced-init-list, - modernize-shrink-to-fit, - modernize-unary-static-assert, - modernize-use-*, - -modernize-use-constraints, - -modernize-use-designated-initializers, - -modernize-use-nodiscard, - -modernize-use-ranges, - -modernize-use-trailing-return-type, - -modernize-use-integer-sign-comparison, - -WarningsAsErrors: '' -HeaderFilterRegex: '^(?!.*zint\.h).*$' -FormatStyle: file diff --git a/tools/chatgui/zxing-cpp/.editorconfig b/tools/chatgui/zxing-cpp/.editorconfig deleted file mode 100644 index f8db50e1..00000000 --- a/tools/chatgui/zxing-cpp/.editorconfig +++ /dev/null @@ -1,19 +0,0 @@ -root = true - -[*] -insert_final_newline = true -trim_trailing_whitespace = true -charset = utf-8 - -[*.{cpp,c,h,html,py,kt,cs,qlm}] -indent_style = tab -indent_size = 4 -max_line_length = 135 - -[CMakeLists.txt] -indent_style = space -indent_size = 4 - -[*.md] -trim_trailing_whitespace = false - diff --git a/tools/chatgui/zxing-cpp/.gitattributes b/tools/chatgui/zxing-cpp/.gitattributes deleted file mode 100644 index 3266c089..00000000 --- a/tools/chatgui/zxing-cpp/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -test/samples export-ignore -zint export-ignore -.git* export-ignore diff --git a/tools/chatgui/zxing-cpp/.github/FUNDING.yml b/tools/chatgui/zxing-cpp/.github/FUNDING.yml deleted file mode 100644 index 7be90488..00000000 --- a/tools/chatgui/zxing-cpp/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: axxel -custom: 'www.paypal.com/donate/?hosted_button_id=8LDF4NNQF8Z3L' diff --git a/tools/chatgui/zxing-cpp/.github/create_release_archive.py b/tools/chatgui/zxing-cpp/.github/create_release_archive.py deleted file mode 100644 index 237cc044..00000000 --- a/tools/chatgui/zxing-cpp/.github/create_release_archive.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 - -import subprocess -import tempfile -import shutil -import sys -import re -from pathlib import Path - - -def run_command(cmd, cwd=None): - try: - return subprocess.run(cmd, check=True, capture_output=True, text=True, cwd=cwd) - except subprocess.CalledProcessError as e: - print(f"✗ Error {cmd}: {e.stderr}") - sys.exit(1) - - -def main(repo_root=None, output_dir=None): - repo_root = Path.cwd() if repo_root is None else Path(repo_root).resolve() - output_dir = repo_root if output_dir is None else Path(output_dir).resolve() - output_dir.mkdir(parents=True, exist_ok=True) - print(f"Working in repository: {repo_root}") - - # Extract version and optional suffix from core/CMakeLists.txt - cmake_txt = (repo_root / "core" / "CMakeLists.txt").read_text() - version = re.search(r'ZXing VERSION "?([0-9]+\.[0-9]+\.[0-9]+)"?', cmake_txt).group(1) - suffix = re.search(r'ZXING_VERSION_SUFFIX "?([^\)\s\"]*)"?', cmake_txt).group(1) - ver_tag = f"{version}{suffix}" - print(f"Project version from CMake: {ver_tag}") - - # Create output filenames with git tag - output_stem = f"zxing-cpp-{ver_tag}" - output_tgz = output_dir / f"{output_stem}.tar.gz" - output_zip = output_dir / f"{output_stem}.zip" - - # Step 1: Create initial tar ball via git archive - print("\n[1/4] Creating git archive...") - run_command(["git", "archive", f"--prefix={output_stem}/", f"--output={output_tgz}", "HEAD", repo_root]) - print(f"✓ Created git archive: {output_tgz}") - - # Step 2: Extract tar ball into temporary folder - print("\n[2/4] Extracting archive to temporary folder...") - with tempfile.TemporaryDirectory() as temp_dir: - print(f"✓ Created temporary directory: {temp_dir}") - - run_command(["tar", "-xf", str(output_tgz), "-C", temp_dir]) - print(f"✓ Extracted archive to temporary folder") - - # Step 3: Replace files in core/src/libzint/ with files from zint/backend/ - print("\n[3/4] Replacing files in core/src/libzint/ with zint/backend/ files...") - dst_dir = Path(temp_dir) / output_stem / "core" / "src" / "libzint" - src_dir = repo_root / "zint" / "backend" # Use actual repo directory - - # Find all files/symlinks currently in core/src/libzint/ (including subdirectories) - libzint_files = list(dst_dir.rglob("*")) - # Include both regular files and symlinks - libzint_files = [f for f in libzint_files if f.is_file() or f.is_symlink()] - - replaced_count = 0 - not_found_count = 0 - - for libzint_file in libzint_files: - # Get relative path from core/src/libzint/ - rel_path = libzint_file.relative_to(dst_dir) - - # Source path in zint/backend/ - source_file = src_dir / rel_path - - # Only replace if the file exists in zint/backend/ - if source_file.exists() and source_file.is_file(): - # Remove symlink if it exists - if libzint_file.is_symlink(): - libzint_file.unlink() - # Copy the actual file - shutil.copy2(source_file, libzint_file) - replaced_count += 1 - else: - not_found_count += 1 - print(f" - Skipped (not in zint/backend): {rel_path}") - - print(f"✓ Replaced {replaced_count} files, skipped {not_found_count} files") - - # Step 4: Create tar.gz and zip archives - print("\n[4/4] Creating archives (tar.gz and zip)...") - run_command(["tar", "-czf", str(output_tgz), "-C", temp_dir, output_stem]) - - if (output_zip.exists()): - output_zip.unlink() - - run_command(["zip", "-q", "-r", str(output_zip), output_stem], cwd=temp_dir) - - # Cleanup initial tar file - print(f"\n✅ Done! Source archives created:") - print(f" - {output_tgz}") - print(f" - {output_zip}") - - -if __name__ == "__main__": - repo_root = sys.argv[1] if len(sys.argv) > 1 else None - output_dir = sys.argv[2] if len(sys.argv) > 2 else None - main(repo_root=repo_root, output_dir=output_dir) diff --git a/tools/chatgui/zxing-cpp/.github/workflows/ci.yml b/tools/chatgui/zxing-cpp/.github/workflows/ci.yml deleted file mode 100644 index 602de571..00000000 --- a/tools/chatgui/zxing-cpp/.github/workflows/ci.yml +++ /dev/null @@ -1,435 +0,0 @@ -name: CI - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master branch -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - # 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: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Setup Python 3 - uses: actions/setup-python@v6 - with: - python-version: '3.x' - - - name: Configure - run: > - cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_SHARED_LIBS=ON - -DZXING_BLACKBOX_TESTS=ON -DZXING_TEST_DOTNET=ON -DZXING_TEST_GO=ON -DZXING_TEST_INSTALL=ON -DZXING_UNIT_TESTS=ON - -DZXING_PYTHON_MODULE=ON - - - name: Build - run: cmake --build build --parallel --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/bin/${BUILD_TYPE}" >> $GITHUB_PATH - - - name: Test - 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@v6 - with: - name: ${{matrix.os}}-artifacts - path: install - - build-exp: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest, ubuntu-latest, macos-latest, ubuntu-24.04-arm, macos-15-intel, windows-11-arm] - - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Setup Python 3 - id: setup-python - if: matrix.os == 'windows-11-arm' # the default python on the arm runner is an x64 build - uses: actions/setup-python@v6 - with: - python-version: '3.13' - architecture: arm64 - - - name: Configure - run: > - cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_SHARED_LIBS=ON - -DZXING_READERS=ON -DZXING_WRITERS=BOTH -DZXING_EXPERIMENTAL_API=ON - -DZXING_BLACKBOX_TESTS=ON -DZXING_TEST_INSTALL=ON -DZXING_UNIT_TESTS=OFF -DZXING_PYTHON_MODULE=ON - ${{ steps.setup-python.outputs.python-path != '' && format('-DPython_EXECUTABLE={0}', steps.setup-python.outputs.python-path) || '' }} - - - name: Build - run: cmake --build build --parallel --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/bin/${BUILD_TYPE}" >> $GITHUB_PATH - - - name: Test - 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@v6 - with: - name: ${{matrix.os}}-exp-artifacts - path: install - - build-ubuntu-gcc-12: # keep homebrew happy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Configure - run: > - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DZXING_WRITERS=BOTH - -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 - - - name: Build - run: cmake --build build --parallel - - - name: Test - run: ctest -V --test-dir build - - build-ubuntu-asan-ubsan: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Configure - run: > - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DZXING_WRITERS=BOTH - -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 build --parallel - - - name: Test - run: ctest -V --test-dir build - - build-ubuntu-tsan: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Configure - run: > - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DZXING_WRITERS=OFF -DZXING_EXAMPLES=OFF - -DZXING_BLACKBOX_TESTS=ON -DZXING_UNIT_TESTS=OFF -DZXING_PYTHON_MODULE=OFF -DZXING_C_API=OFF - -DCMAKE_CXX_COMPILER=clang++ - -DCMAKE_CXX_FLAGS="-march=native -fsanitize=thread -fno-optimize-sibling-calls -fno-omit-frame-pointer -g" - -DCMAKE_C_COMPILER=clang - -DCMAKE_C_FLAGS="-march=native -fsanitize=thread -fno-optimize-sibling-calls -fno-omit-frame-pointer -g" - - - name: Build - run: cmake --build build --parallel - - - name: Test - run: ctest -V --test-dir build - - build-old-writers: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Configure - run: > - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DZXING_READERS=ON -DZXING_WRITERS=OLD -DZXING_PYTHON_MODULE=ON -DZXING_C_API=ON - -DZXING_BLACKBOX_TESTS=ON -DZXING_TEST_INSTALL=ON -DZXING_UNIT_TESTS=ON -DZXING_PYTHON_MODULE=ON - - - name: Build - run: cmake --build build --parallel - - - name: Test - run: ctest -V --test-dir build - - build-ios: - runs-on: macos-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Build the swift package - run: swift build - - - name: Build the demo app - working-directory: wrappers/ios/demo - run: xcodebuild build -scheme demo -sdk "iphonesimulator" - - # - name: Validate the Pod - # run: pod lib lint --allow-warnings - - build-android: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Build the lib/app - working-directory: wrappers/android - run: ./gradlew assembleRelease - - - uses: actions/upload-artifact@v6 - with: - name: android-artifacts - path: "wrappers/android/zxingcpp/build/outputs/aar/zxingcpp-release.aar" - - - 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_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME }} - ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_KEY_ID }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_KEY_PASSWORD }} - run: ./gradlew publishToMavenCentral --no-configuration-cache - - build-kn: - runs-on: ubuntu-latest - defaults: - run: - working-directory: wrappers/kn - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - submodules: true - - - name: Prepare Zig toolchain - uses: mlugg/setup-zig@v2 - with: - version: 0.13.0 - - - name: Checkout toolchain initializer repository - uses: actions/checkout@v6 - with: - repository: ISNing/kn-toolchain-initializer - path: wrappers/kn/.kn-toolchain-initializer - - - name: Set up JDK - uses: actions/setup-java@v5 - with: - java-version: 17 - distribution: temurin - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 - - - name: Validate Gradle Wrapper - uses: gradle/actions/wrapper-validation@v5 - - - name: Initialize Kotlin/Native toolchain - working-directory: wrappers/kn/.kn-toolchain-initializer - run: ./gradlew build -DkotlinVersion=2.2.0 - - - name: Run test for linuxX64 target - run: | - echo -e "konan.dir=$HOME/.konan/kotlin-native-prebuilt-linux-x86_64-2.2.0" >> local.properties - ./gradlew linuxX64Test - - build-python: - runs-on: ${{ matrix.os }} - strategy: - matrix: - python-version: ['3.12'] - os: [ubuntu-latest, macos-latest, windows-latest] - - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install numpy pillow build - - - name: Build module - working-directory: wrappers/python - run: python -m pip -v install -e . - - - name: Test module - working-directory: wrappers/python - run: python -m unittest -v - - - name: Build wheel - working-directory: wrappers/python - run: python -m build --wheel - - - uses: actions/upload-artifact@v6 - with: - name: ${{matrix.os}}-python-artifacts - path: wrappers/python/dist/* - - build-rust: - runs-on: ubuntu-latest - defaults: - run: - working-directory: wrappers/rust - - steps: - - uses: actions/checkout@v6 - 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 --all-features - cargo publish --dry-run - - - uses: actions/upload-artifact@v6 - with: - name: rust-artifacts - path: wrappers/rust/target/package/*.crate - - build-wasm: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - uses: emscripten-core/setup-emsdk@v15 - - - name: Configure - run: emcmake cmake -Swrappers/wasm -Bbuild - - - name: Build - run: cmake --build build --parallel - -# - name: Test -# run: node build/EmGlueTests.js - - - uses: actions/upload-artifact@v6 - with: - name: wasm-artifacts - path: | - build/zxing* - build/demo* - -# disable WinRT build for now, as it does not compile with C++20 (see wrappers/winrt/CMakeLists.txt) - # build-winrt: - # runs-on: windows-latest - - # steps: - # - uses: actions/checkout@v6 - # with: - # submodules: true - - # - 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@v6 - # with: - # name: winrt-ARM64-artifacts - # path: build/dist - - build-tarball: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Build - run: python .github/create_release_archive.py - - - name: Find generated archives - id: find - run: | - tgz=$(ls zxing-cpp-*.tar.gz 2>/dev/null | head -n1 || true) - zip=$(ls zxing-cpp-*.zip 2>/dev/null | head -n1 || true) - echo "tgz=$tgz" >> $GITHUB_OUTPUT - echo "zip=$zip" >> $GITHUB_OUTPUT - - - name: Upload source_artifacts - uses: actions/upload-artifact@v6 - with: - name: source_artifacts - path: | - ${{ steps.find.outputs.tgz }} - ${{ steps.find.outputs.zip }} diff --git a/tools/chatgui/zxing-cpp/.github/workflows/codeql-analysis.yml b/tools/chatgui/zxing-cpp/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 59b3b74f..00000000 --- a/tools/chatgui/zxing-cpp/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,59 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: CodeQL - -on: - workflow_dispatch: - # push: - # branches: [ master ] - # pull_request: - # # The branches below must be a subset of the branches above - # branches: [ master ] - # schedule: - # - cron: '18 21 * * 6' - -jobs: - analyze: - name: Analyze - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - - strategy: - fail-fast: false - matrix: - language: [ 'cpp' ] # , 'java-kotlin', 'swift' currently fail with autobuild, 'python' is not worth the extra build time - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - submodules: true - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: ${{ matrix.language }} - build-mode: manual - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - - name: Build - run: | - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DZXING_EXPERIMENTAL_API=ON - cmake --build build -j - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 diff --git a/tools/chatgui/zxing-cpp/.github/workflows/msvc-analysis.yml b/tools/chatgui/zxing-cpp/.github/workflows/msvc-analysis.yml deleted file mode 100644 index f491908a..00000000 --- a/tools/chatgui/zxing-cpp/.github/workflows/msvc-analysis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# -# Find more information at: -# https://github.com/microsoft/msvc-code-analysis-action - -name: Microsoft C++ Code Analysis - -on: workflow_dispatch - -# on: -# push: -# branches: [ "master" ] -# pull_request: -# branches: [ "master" ] -# schedule: -# - cron: '34 6 * * 1' - -env: - # Path to the CMake build directory. - build: '${{ github.workspace }}/build' - -permissions: - contents: read - -jobs: - analyze: - permissions: - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status - name: Analyze - runs-on: windows-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - submodules: true - - - name: Configure CMake - run: cmake -B ${{ env.build }} -DZXING_EXPERIMENTAL_API=ON -DZXING_DISABLE_PCH=ON - - # Build is not required unless generated source files are used - # - name: Build CMake - # run: cmake --build ${{ env.build }} - - - name: Initialize MSVC Code Analysis - uses: microsoft/msvc-code-analysis-action@v0.1.1 - # Provide a unique ID to access the sarif output path - id: run-analysis - with: - cmakeBuildDirectory: ${{ env.build }} - # Ruleset file that will determine what checks will be run - ruleset: NativeRecommendedRules.ruleset - - # Upload SARIF file to GitHub Code Scanning Alerts - # this currently fails with the Error: The CodeQL Action does not support uploading multiple SARIF runs with the same category. - # - name: Upload SARIF to GitHub - # uses: github/codeql-action/upload-sarif@v4 - # 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@v6 - with: - name: sarif-file - path: ${{ steps.run-analysis.outputs.sarif }} diff --git a/tools/chatgui/zxing-cpp/.github/workflows/publish-android.yml b/tools/chatgui/zxing-cpp/.github/workflows/publish-android.yml deleted file mode 100644 index c1ebb3f4..00000000 --- a/tools/chatgui/zxing-cpp/.github/workflows/publish-android.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: publish-android - -on: -# release: -# types: [published] - - workflow_dispatch: - inputs: - publish: - description: 'Publish package (y/n)' - default: 'n' - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-java@v5 - 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_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME }} - ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_KEY_ID }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_KEY_PASSWORD }} - run: ./gradlew publishToMavenCentral --no-configuration-cache - - -# To actually publish the the blob, login to https://central.sonatype.com/publishing/deployments. diff --git a/tools/chatgui/zxing-cpp/.github/workflows/publish-dotnet.yml b/tools/chatgui/zxing-cpp/.github/workflows/publish-dotnet.yml deleted file mode 100644 index b68823ee..00000000 --- a/tools/chatgui/zxing-cpp/.github/workflows/publish-dotnet.yml +++ /dev/null @@ -1,130 +0,0 @@ -name: publish-dotnet - -on: -# release: -# types: [published] - - workflow_dispatch: - inputs: - publish: - description: 'Publish package (y/n)' - default: 'n' - -jobs: - build-native: - name: build-native ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - os: windows-latest - lib_name: ZXing.dll - rid: win-x64 - lib_path: build/core/Release/ZXing.dll - - os: windows-11-arm - lib_name: ZXing.dll - rid: win-arm64 - lib_path: build/core/Release/ZXing.dll - - os: ubuntu-latest - lib_name: libZXing.so - rid: linux-x64 - lib_path: build/core/libZXing.so - - os: ubuntu-24.04-arm - lib_name: libZXing.so - rid: linux-arm64 - lib_path: build/core/libZXing.so - - os: macos-15-intel - lib_name: libZXing.dylib - rid: osx-x64 - lib_path: build/core/libZXing.dylib - - os: macos-latest - lib_name: libZXing.dylib - rid: osx-arm64 - lib_path: build/core/libZXing.dylib - - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Configure CMake - run: > - cmake -S . -B build - -DCMAKE_BUILD_TYPE=Release - -DBUILD_SHARED_LIBS=ON - -DZXING_EXAMPLES=OFF - -DZXING_TEST_DOTNET=ON - - - name: Build - run: cmake --build build --config Release --parallel - - - name: Test - run: ctest --test-dir build -V -C Release -R DotNetTest - - - name: Stage native library - run: | - cmake -E make_directory artifact/runtimes/${{ matrix.rid }}/native - cmake -E copy_if_different ${{ matrix.lib_path }} artifact/runtimes/${{ matrix.rid }}/native/${{ matrix.lib_name }} - - - name: Upload native library - uses: actions/upload-artifact@v6 - with: - name: native-${{ matrix.rid }} - path: artifact - - publish-nuget: - needs: build-native - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Download native libraries - uses: actions/download-artifact@v8 - with: - pattern: native-* - path: wrappers/dotnet - merge-multiple: true - - - name: Validate runtime files - shell: bash - run: | - test -f wrappers/dotnet/runtimes/win-x64/native/ZXing.dll - test -f wrappers/dotnet/runtimes/win-arm64/native/ZXing.dll - test -f wrappers/dotnet/runtimes/linux-x64/native/libZXing.so - test -f wrappers/dotnet/runtimes/linux-arm64/native/libZXing.so - test -f wrappers/dotnet/runtimes/osx-x64/native/libZXing.dylib - test -f wrappers/dotnet/runtimes/osx-arm64/native/libZXing.dylib - - - name: Pack NuGet package - run: dotnet pack wrappers/dotnet/ZXingCpp/ZXingCpp.csproj --configuration Release --output wrappers/dotnet/artifacts - - - name: Smoke test local NuGet package - shell: bash - run: | - set -euo pipefail - pkg_file="$(ls wrappers/dotnet/artifacts/ZXingCpp.*.nupkg | head -n 1)" - pkg_version="$(basename "$pkg_file" | sed -E 's/^ZXingCpp\.//; s/\.nupkg$//')" - - tmp_dir="$(mktemp -d)" - dotnet new console -n ZxingPkgTest -o "$tmp_dir/ZxingPkgTest" - echo "Console.WriteLine(global::ZXingCpp.BarcodeFormat.QRCode.ToString());" > "$tmp_dir/ZxingPkgTest/Program.cs" - - dotnet add "$tmp_dir/ZxingPkgTest/ZxingPkgTest.csproj" package ZXingCpp --version "$pkg_version" --no-restore - dotnet restore "$tmp_dir/ZxingPkgTest/ZxingPkgTest.csproj" \ - --source "${{ github.workspace }}/wrappers/dotnet/artifacts" \ - --source https://api.nuget.org/v3/index.json - dotnet run --project "$tmp_dir/ZxingPkgTest/ZxingPkgTest.csproj" --no-restore - - - name: Upload NuGet package artifact - uses: actions/upload-artifact@v6 - with: - name: dotnet-nuget-package - path: wrappers/dotnet/artifacts/*.nupkg - - - name: Push NuGet package - if: ${{ github.event_name == 'release' || github.event.inputs.publish == 'y' }} - run: dotnet nuget push wrappers/dotnet/artifacts/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate diff --git a/tools/chatgui/zxing-cpp/.github/workflows/publish-gh-pages.yml b/tools/chatgui/zxing-cpp/.github/workflows/publish-gh-pages.yml deleted file mode 100644 index eb5fbad0..00000000 --- a/tools/chatgui/zxing-cpp/.github/workflows/publish-gh-pages.yml +++ /dev/null @@ -1,131 +0,0 @@ -name: publish-gh-pages - -on: - # Runs on pushes targeting the default branch -# push: -# branches: ["master"] - - # Runs on new releases - release: - types: [published] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build-wasm: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - submodules: true - - - name: Setup emsdk - uses: emscripten-core/setup-emsdk@v15 - # with: - # # Make sure to set a version number! - # version: 1.38.40 - # # This is the name of the cache folder. - # # The cache folder will be placed in the build directory, - # # so make sure it doesn't conflict with anything! - # actions-cache-folder: 'emsdk-cache' - - - name: Configure - run: emcmake cmake -Swrappers/wasm -Bbuild - - - name: Build - run: cmake --build build --parallel - - - name: Prepare Archive - shell: sh - run: | - mkdir -p public/demo - mv build/zxing_* build/*.html public/demo/ - - - name: Upload artifact - uses: actions/upload-artifact@v7 - with: - path: 'public' - name: 'artifact-wasm' - - build-docs: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Install dependencies - run: brew install doxygen - - - name: Configure - run: cmake -S . -B build - - - name: Build docs - run: cmake --build build --target docs - - - name: Prepare deploy folder - run: | - mkdir -p public/docs - if [[ "${GITHUB_REF}" == refs/tags/* ]]; then - VERSION=${GITHUB_REF#refs/tags/} - mkdir -p public/docs/$VERSION - cp -r build/docs/html/* public/docs/$VERSION/ - else - mkdir -p public/docs/latest - cp -r build/docs/html/* public/docs/latest/ - fi - - - uses: actions/upload-artifact@v7 - with: - path: public - name: 'artifact-docs' - - deploy: - needs: [build-wasm, build-docs] - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - - permissions: - contents: read - pages: write - id-token: write - - steps: - - name: Download WASM artifact - uses: actions/download-artifact@v8 - with: - name: artifact-wasm - path: wasm - - - name: Download Docs artifact - uses: actions/download-artifact@v8 - with: - name: artifact-docs - path: docs - - - name: Merge artifacts - run: | - mkdir public - cp -r wasm/* public/ - cp -r docs/* public/ - - - name: Upload merged artifact - uses: actions/upload-pages-artifact@v4 - with: - path: public - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v5 diff --git a/tools/chatgui/zxing-cpp/.github/workflows/publish-kn.yml b/tools/chatgui/zxing-cpp/.github/workflows/publish-kn.yml deleted file mode 100644 index eb8857a9..00000000 --- a/tools/chatgui/zxing-cpp/.github/workflows/publish-kn.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: publish-kn - -on: - workflow_dispatch: - inputs: - publish: - description: 'Publish package (y/n)' - default: 'n' - -jobs: - publish: - name: Library Publish - - runs-on: macos-latest # at least macos-13 is required to enable c++20 support - - defaults: - run: - working-directory: ./wrappers/kn - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - submodules: true - - - name: Prepare Zig toolchain - uses: mlugg/setup-zig@v2 - with: - version: 0.13.0 - - - name: Checkout toolchain initializer repository - uses: actions/checkout@v6 - with: - repository: ISNing/kn-toolchain-initializer - path: wrappers/kn/.kn-toolchain-initializer - - - name: Set up JDK - uses: actions/setup-java@v5 - with: - java-version: 17 - distribution: temurin - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 - - - name: Validate Gradle Wrapper - uses: gradle/actions/wrapper-validation@v5 - - - name: Initialize Kotlin/Native toolchain - working-directory: wrappers/kn/.kn-toolchain-initializer - run: ./gradlew build -DkotlinVersion=2.2.0 - - - name: Export Toolchain properties - run: | - echo -e "konan.dir=$HOME/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0" >> 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@v6 - with: - name: allTests-reports - path: wrappers/kn/build/reports/tests/allTests - - - name: Upload Built LinuxArm64 shared library - uses: actions/upload-artifact@v6 - with: - name: linuxArm64-shared - path: wrappers/kn/build/cmake/libZXing/linuxArm64 - - - name: Upload Built LinuxX64 shared library - uses: actions/upload-artifact@v6 - with: - name: linuxX64-shared - path: wrappers/kn/build/cmake/libZXing/linuxX64 - - - name: Publish Library - if: ${{ github.event.inputs.publish == 'y' }} - env: - ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME }} - ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_KEY_ID }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_KEY_PASSWORD }} - run: | - export PATH="$PATH:$(brew --prefix llvm@15)/bin" - ./gradlew publishToMavenCentral - -# To actually publish the blob, login to https://central.sonatype.com/publishing/deployments. diff --git a/tools/chatgui/zxing-cpp/.github/workflows/publish-python.yml b/tools/chatgui/zxing-cpp/.github/workflows/publish-python.yml deleted file mode 100644 index 0a3b2018..00000000 --- a/tools/chatgui/zxing-cpp/.github/workflows/publish-python.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: publish-python - -on: -# release: -# types: [published] - - workflow_dispatch: - inputs: - publish: - description: 'Publish package (y/n)' - default: 'n' -# push: -# branches: [master] -# tags: ["v*.*.*"] -# pull_request: -# branches: [master] - -jobs: - build-wheels: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest, macos-15-intel, ubuntu-24.04-arm, windows-11-arm] - - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.12' - - - name: Install cibuildwheel - run: python3 -m pip install cibuildwheel==3.3.0 - - - name: Build wheels - run: python3 -m cibuildwheel --output-dir wheelhouse wrappers/python - - - name: Upload wheels - uses: actions/upload-artifact@v6 - with: - name: cibw-wheels-${{ matrix.os }} - path: ./wheelhouse/*.whl - - build-sdist: - name: Build source distribution - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.14' - - - name: Install dependencies - working-directory: wrappers/python - run: python -m pip install --upgrade build - - - name: Build sdist - working-directory: wrappers/python - run: python3 -m build --sdist - - - name: Upload sdist - uses: actions/upload-artifact@v6 - with: - name: cibw-sdist - path: wrappers/python/dist/*.tar.gz - - upload-pypi: - name: Upload to PyPI - needs: [build-wheels, build-sdist] - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/zxing-cpp - permissions: - id-token: write # IMPORTANT: this permission is mandatory for trusted publishing - # only run if the commit is tagged... -# if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - if: github.event_name == 'release' || github.event.inputs.publish == 'y' - steps: - - uses: actions/download-artifact@v8 - with: - pattern: cibw-* - path: dist - merge-multiple: true - - - name: List wheels - run: ls dist - - - uses: pypa/gh-action-pypi-publish@release/v1 -# with: -# repository-url: https://test.pypi.org/legacy/ diff --git a/tools/chatgui/zxing-cpp/.github/workflows/publish-source.yml b/tools/chatgui/zxing-cpp/.github/workflows/publish-source.yml deleted file mode 100644 index a668bbe5..00000000 --- a/tools/chatgui/zxing-cpp/.github/workflows/publish-source.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: publish-source - -on: - release: - types: [published] - workflow_dispatch: - -jobs: - publish-source: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Create source archives - run: python .github/create_release_archive.py - - - name: Create test samples tarball - run: tar -czf test_samples.tar.gz test/samples - - - name: Upload workflow artifact - if: github.event_name != 'release' - uses: actions/upload-artifact@v6 - with: - name: source-archives - path: | - *.zip - *.gz - if-no-files-found: error - - - name: Upload to release - if: github.event_name == 'release' - run: | - tag='${{ github.event.release.tag_name }}' - version="${tag#v}" - gh release upload --clobber "$tag" "$GITHUB_WORKSPACE/zxing-cpp-$version.tar.gz" "$GITHUB_WORKSPACE/zxing-cpp-$version.zip" "$GITHUB_WORKSPACE/test_samples.tar.gz" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/tools/chatgui/zxing-cpp/.github/workflows/publish-winrt.yml b/tools/chatgui/zxing-cpp/.github/workflows/publish-winrt.yml deleted file mode 100644 index 9d9a1d98..00000000 --- a/tools/chatgui/zxing-cpp/.github/workflows/publish-winrt.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: publish-winrt -on: -# release: -# types: [published] - - workflow_dispatch: - inputs: - publish: - description: 'Publish package (y/n)' - default: 'n' - package_version: - description: 'Package version (major.minor.patch)' - default: '1.0.0' - -jobs: - build: - runs-on: windows-latest - strategy: - matrix: - target: [Win32, x64, ARM64] - - steps: - - uses: actions/checkout@v6 - - - name: Create build environment - shell: cmd - run: | - cmake -E make_directory ${{runner.workspace}}/build - cmake -E copy_directory ${{github.workspace}}/wrappers/winrt/UAP ${{runner.workspace}}/build/dist/UAP - cmake -E copy_directory ${{github.workspace}}/wrappers/winrt/nuget ${{runner.workspace}}/build/dist/nuget - - - 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 -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@v6 - with: - name: winrt-${{matrix.target}}-artifacts - path: ${{runner.workspace}}/build/dist - - publish-package: - needs: build - runs-on: windows-latest - if: ${{ github.event_name == 'release' || github.event.inputs.publish == 'y' }} - steps: - - uses: actions/download-artifact@v8 - with: - name: winrt-Win32-artifacts - - uses: actions/download-artifact@v8 - with: - name: winrt-x64-artifacts - - uses: actions/download-artifact@v8 - with: - name: winrt-ARM64-artifacts - - - name: Create NuGet package - if: ${{ github.event_name != 'release' }} - shell: cmd - run: nuget pack -Version ${{ github.event.inputs.package_version }} -OutputFileNamesWithoutVersion nuget/ZXingWinRT.nuspec - - - name: Create NuGet package - if: ${{ github.event_name == 'release' }} - shell: bash - env: - RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} - run: nuget pack -Version ${RELEASE_TAG_NAME:1} -OutputFileNamesWithoutVersion nuget/ZXingWinRT.nuspec - - - name: Publish NuGet package - 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@v6 - with: - name: nuget-package - path: huycn.zxingcpp.winrt.nupkg - diff --git a/tools/chatgui/zxing-cpp/.gitignore b/tools/chatgui/zxing-cpp/.gitignore deleted file mode 100644 index bf9ffaf6..00000000 --- a/tools/chatgui/zxing-cpp/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -*.o -*.so -*.lib -*.d -*.a -compile_commands.json -/build - -# QtCreator -CMakeLists.txt.user - -# Vim -*.swp - -# XCode/Swift -.swiftpm -.build - -# JetBrain (AndroidStudio, clion) -.idea - -# Visual Studio -/.vs -/CMakeUserPresets.json - -# Visual Studio Code -/.vscode diff --git a/tools/chatgui/zxing-cpp/.gitmodules b/tools/chatgui/zxing-cpp/.gitmodules deleted file mode 100644 index 23eb3946..00000000 --- a/tools/chatgui/zxing-cpp/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "zint"] - path = zint - url = https://github.com/zint/zint.git - shallow = true diff --git a/tools/chatgui/zxing-cpp/Package.swift b/tools/chatgui/zxing-cpp/Package.swift deleted file mode 100644 index edfacec4..00000000 --- a/tools/chatgui/zxing-cpp/Package.swift +++ /dev/null @@ -1,38 +0,0 @@ -// swift-tools-version:5.7.1 -import PackageDescription - -let package = Package( - name: "ZXingCpp", - platforms: [ - .macOS(.v13), .iOS(.v12) - ], - products: [ - .library( - name: "ZXingCpp", - targets: ["ZXingCpp"]) - ], - targets: [ - .target( - name: "ZXingCppCore", - path: "core/src", - exclude: ["libzint", "ZXingC.cpp", "ZXingCpp.cpp"], - publicHeadersPath: ".", - cxxSettings: [ - .headerSearchPath("../../wrappers/ios/Sources/Wrapper"), - .define("ZXING_INTERNAL") - ] - ), - .target( - name: "ZXingCpp", - dependencies: ["ZXingCppCore"], - path: "wrappers/ios/Sources/Wrapper", - publicHeadersPath: ".", - linkerSettings: [ - .linkedFramework("CoreGraphics"), - .linkedFramework("CoreImage"), - .linkedFramework("CoreVideo") - ] - ) - ], - cxxLanguageStandard: CXXLanguageStandard.cxx20 -) diff --git a/tools/chatgui/zxing-cpp/README.md b/tools/chatgui/zxing-cpp/README.md deleted file mode 100644 index 659cbf71..00000000 --- a/tools/chatgui/zxing-cpp/README.md +++ /dev/null @@ -1,154 +0,0 @@ -[![Build Status](https://github.com/zxing-cpp/zxing-cpp/actions/workflows/ci.yml/badge.svg)](https://github.com/zxing-cpp/zxing-cpp/actions?query=workflow%3ACI) - -# ZXing-C++ - -ZXing-C++ ("zebra crossing") is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. - -It was originally ported from the Java [ZXing library](https://github.com/zxing/zxing) but has been developed further and now includes many improvements in terms of runtime and detection performance. It can both read and write barcodes in a number of formats. Since version 3.0 the default writing backend is provided by the [zint library](https://sourceforge.net/projects/zint/). - -## Features - -* Written in pure C++20 (public API is C++17 compatible), no third-party dependencies (for the library itself) -* Thread safe -* Wrappers/Bindings for: - - | | | | - |:-|:-|:-| - | [Android](wrappers/android/README.md) | [C](wrappers/c/README.md) | [Go](wrappers/go/README.md) | - | [iOS](wrappers/ios/README.md) | [Kotlin/Native](wrappers/kn/README.md) | [.NET](wrappers/dotnet/README.md) | - | [Python](wrappers/python/README.md) | [Qt](wrappers/qt/README.md) | [Rust](wrappers/rust/README.md) | - | [WebAssembly](wrappers/wasm/README.md) | [WinRT](wrappers/winrt/README.md) | | - | External projects: | - | [Flutter](https://pub.dev/packages/flutter_zxing) | [Julia](https://github.com/rakeshksr/ZXingCPP.jl) | | - - -## Supported Formats - -| Symbology | Variants | -|:----------|:---------| -| ***Retail:*** | *(Point-of-Sale, Coupons)* -| EAN/UPC | EAN-13, EAN-8, EAN-5ᵂ, EAN-2ᵂ, UPC-A, UPC-E, ISBN -| DataBar | Omnidirectional, Stacked, Limited, Expanded, Expanded Stacked -| ***Industrial:*** | *(Logistics, Tracking, Pharma)* -| Code39 | Standard, Extended, PZN, Code32, (VIN, LOGMARS) -| Code93 | -| Code128 | -| ITF | ITF-14, (DHL Leitcode, DHL Identcode) -| Telepen | Telepen Alpha, Telepen Numeric -| ***Matrix:*** | *(Documents, Tickets, Logistics, IDs)* -| Aztec Code | Aztec Code, Aztec Rune -| Data Matrix | ECC200 -| MaxiCode | (partial read support) -| PDF417 | PDF417, Compact PDF417, MicroPDF417 -| QR Code | Model 1ᴿ, Model 2, Micro QR Code, rMQR -| ***Other:*** | *(Legacy, Niche)* -| Codabar | -| DXFilmEdge | - - -[Note:] - * ᵂ : write support only - * ᴿ : read support only - * DataBar used to be called RSS. - * DataBar, DX Film Edge, MaxiCode, Micro QR Code and rMQR Code are not supported for writing if the library is configured with `ZXING_WRITERS=OLD`. - -## Sponsors - -You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/axxel). - -| | Named Sponsors: | -|:-:|:-| -| [![ArAllAll](https://www.alliedvision.com/favicon.ico)](https://www.alliedvision.com/) | [Allied Vision Konstanz GmbH](https://www.alliedvision.com/) | -| [![jknlsn](https://avatars.githubusercontent.com/u/9172090?s=32)](https://github.com/jknlsn) | [Jake Nelson](https://github.com/jknlsn) | -| [![KURZ](https://avatars.githubusercontent.com/u/25196688?s=32)](https://github.com/kurzdigital) | [KURZ Digital Solutions GmbH & Co. KG](https://github.com/kurzdigital) | -| [![Moonshine AI](https://avatars.githubusercontent.com/u/98664891?s=32)](https://github.com/moonshine-ai) | [Moonshine AI](https://github.com/moonshine-ai) | -| [![SAP](https://avatars.githubusercontent.com/u/2531208?s=32)](https://www.sap.com/germany/about/company/innovation/open-source.html) | [SAP (Open Source Program Office)](https://www.sap.com/germany/about/company/innovation/open-source.html) | -| [![Somco Software](https://avatars.githubusercontent.com/u/41477867?s=32)](https://github.com/somcosoftware) | [Somco Software](https://somcosoftware.com/en) | -| [![synedra](https://www.synedra.com/favicon.ico)](https://synedra.com/) | [synedra information technologies GmbH](https://synedra.com/) | - -Thanks a lot for your contribution! - -## 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 `Barcode` objects. - -A very simple example looks like this: -```c++ -#include "ZXing/ZXingCpp.h" -#include - -int main(int argc, char** argv) -{ - int width, height; - unsigned char* data; - // load your image data from somewhere. ImageFormat::Lum assumes grey scale image data. - - auto image = ZXing::ImageView(data, width, height, ZXing::ImageFormat::Lum); - auto options = ZXing::ReaderOptions().formats(ZXing::BarcodeFormat::QRCode); - auto barcodes = ZXing::ReadBarcodes(image, options); - - for (const auto& b : barcodes) - std::cout << ZXing::ToString(b.format()) << ": " << b.text() << "\n"; - - return 0; -} -``` -To see the full capability of the API, have a look at [`ZXingReader.cpp`](example/ZXingReader.cpp). - -### To write barcodes: -1. Create a `Barcode` object with `CreateBarcodeFrom...()` from [`CreateBarcode.h`](core/src/CreateBarcode.h). -2. The `Barcode::symbol()` can be used to get access to the bit matrix (1 module == 1 pixel, no quiet zone) -3. Alternatively the 3 `WriteBarcodeTo...()` functions from [`WriteBarcode.h`](core/src/WriteBarcode.h) can be used to create an `Image`, a SVG string or a UTF-8 string representation. - -A very simple example looks like this: -```c++ -#include "ZXing/ZXingCpp.h" -#include - -int main(int argc, char** argv) -{ - auto barcode = ZXing::CreateBarcodeFromText("some text", ZXing::BarcodeFormat::QRCode); - auto svg = ZXing::WriteBarcodeToSVG(barcode); - - // see also ZXing::WriteBarcodeToImage() - - std::cout << svg << "\n"; - - return 0; -} -``` - -As an example for how to parameterize the process with `CreatorOptions` and `WriterOptions`, have a look at [`ZXingWriter.cpp`](example/ZXingWriter.cpp). - -### Online API documentation - -The latest API documentation can be found here: https://zxing-cpp.github.io/zxing-cpp/docs/latest - -## Web Demos -- [Read barcodes](https://zxing-cpp.github.io/zxing-cpp/demo/demo_reader.html) -- [Write barcodes](https://zxing-cpp.github.io/zxing-cpp/demo/demo_writer.html) -- [Read barcodes from camera](https://zxing-cpp.github.io/zxing-cpp/demo/demo_cam_reader.html) - -[Note: those live demos are not necessarily fully up-to-date at all times.] - -## Build Instructions -These are the generic instructions to build the library on Windows/macOS/Linux. For details on how to build the individual wrappers, follow the links above. - -1. Make sure [CMake](https://cmake.org) version 3.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 --recursive --depth 1 -cmake -S zxing-cpp -B zxing-cpp/build -DCMAKE_BUILD_TYPE=Release -cmake --build zxing-cpp/build --parallel --config Release -``` - -[Note: ready to use packages are available for/as -[vcpkg](https://github.com/Microsoft/vcpkg/tree/master/ports/nu-book-zxing-cpp), -[conan](https://github.com/conan-io/conan-center-index/tree/master/recipes/zxing-cpp), -[mingw](https://github.com/msys2/MINGW-packages/tree/master/mingw-w64-zxing-cpp), -[homebrew](https://formulae.brew.sh/formula/zxing-cpp) and a bunch of -[linux distributions](https://repology.org/project/zxing-cpp/versions).] diff --git a/tools/chatgui/zxing-cpp/core/CMakeLists.txt b/tools/chatgui/zxing-cpp/core/CMakeLists.txt new file mode 100644 index 00000000..736703e4 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/CMakeLists.txt @@ -0,0 +1,783 @@ +cmake_minimum_required(VERSION 3.16) + +project (ZXing VERSION "3.1.0") +set (ZXING_VERSION_SUFFIX "-rc1") +set (ZXING_SONAME 4) # see https://github.com/zxing-cpp/zxing-cpp/issues/333 + +option (ZXING_USE_BUNDLED_ZINT "Use the bundled libzint for barcode creation/writing" ON) +foreach(format 1D AZTEC DATAMATRIX MAXICODE PDF417 QRCODE) + option ("ZXING_ENABLE_${format}" "Enable support for ${format} barcodes" ON) +endforeach() + +if (NOT DEFINED CMAKE_CXX_STANDARD) + set (CMAKE_CXX_STANDARD 20) + set (CMAKE_CXX_STANDARD_REQUIRED ON) + set (CMAKE_CXX_EXTENSIONS OFF) +endif() + +if (BUILD_SHARED_LIBS) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + +if (NOT DEFINED ZXING_WRITERS) + set (ZXING_WRITERS OFF) +endif() + +if (NOT DEFINED ZXING_READERS) + set (ZXING_READERS ON) +endif() + +set (ZXING_WRITERS_NEW OFF) +set (ZXING_WRITERS_OLD OFF) +if (ZXING_WRITERS MATCHES "OLD") + set (ZXING_WRITERS ON) + set (ZXING_WRITERS_OLD ON) +elseif (ZXING_WRITERS MATCHES "NEW|ON") + 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() + +if (BUILD_SHARED_LIBS) + set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +endif() + +set (ZXING_USE_ZINT ${ZXING_WRITERS_NEW}) # see Version.h.in + +set (ZXING_PRIVATE_FLAGS + -DZXING_INTERNAL + $<$:-DZXING_BUILD_FOR_TEST> +) + +if (WINRT) + set (ZXING_PUBLIC_FLAGS -DWINRT ) +endif() + +if (MSVC) + list (APPEND 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() + list (APPEND 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) + list (APPEND ZXING_PRIVATE_FLAGS "${FILE_PREFIX_ARG}") +endif() + +# MARK: - COMMON_FILES + +set (COMMON_FILES + src/Barcode.h + src/Barcode.cpp + src/BarcodeData.h + src/BarcodeFormat.h + src/BarcodeFormat.cpp + src/BitMatrix.h + src/BitMatrix.cpp + src/BitMatrixIO.h + src/BitMatrixIO.cpp + src/ByteArray.h + src/CharacterSet.h + src/CharacterSet.cpp + src/CreateBarcode.h + src/CreateBarcode.cpp + src/Content.h + src/Content.cpp + src/ContentType.h + src/DecoderResult.h + src/DetectorResult.h + src/ECI.h + src/ECI.cpp + src/Error.h + src/Error.cpp + src/Flags.h + src/GTIN.h + src/GTIN.cpp + src/ImageView.h + src/JSON.h + src/JSON.cpp + src/Matrix.h + src/Point.h + src/Quadrilateral.h + src/Range.h + src/ReaderOptions.h + src/ReadBarcode.h + src/ReadBarcode.cpp + src/ReedSolomon.h + src/ReedSolomon.cpp + src/SymbologyIdentifier.h + src/Utf.h + src/Utf.cpp + src/WriteBarcode.h + src/WriteBarcode.cpp + src/ZXingCpp.h + src/ZXingCpp.cpp + src/ZXingQt.h + src/ZXAlgorithms.h + src/ZXConfig.h + src/ZXTestSupport.h + src/ZXVersion.h + $<$:src/ZXingC.h> + $<$:src/ZXingC.cpp> +) +if (ZXING_READERS OR ZXING_WRITERS_OLD) + set (COMMON_FILES ${COMMON_FILES} + src/BitArray.h + src/BitArray.cpp + ) +endif() +if (ZXING_READERS OR ZXING_WRITERS_NEW) + set (COMMON_FILES ${COMMON_FILES} + src/HRI.h + src/HRI.cpp + ) +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/ConcentricFinder.h + src/ConcentricFinder.cpp + src/GlobalHistogramBinarizer.h + src/GlobalHistogramBinarizer.cpp + src/GridSampler.h + src/GridSampler.cpp + src/LogMatrix.h + src/LocalGrid.h + src/LocalGrid.cpp + src/HybridBinarizer.h + src/HybridBinarizer.cpp + src/MultiFormatReader.h + src/MultiFormatReader.cpp + src/Pattern.h + src/PerspectiveTransform.h + src/PerspectiveTransform.cpp + src/Reader.h + src/RegressionLine.h + src/ResultPoint.h + src/ResultPoint.cpp + src/StructuredAppend.h + src/StdGenerator.h + src/StdPrint.h + src/StdScope.h + src/TextDecoder.h + src/TextDecoder.cpp + src/ThresholdBinarizer.h + src/TritMatrix.h # QRCode + src/WhiteRectDetector.h + src/WhiteRectDetector.cpp + ) +endif() + +if (ZXING_WRITERS) + set (COMMON_FILES ${COMMON_FILES} + src/TextEncoder.h + src/TextEncoder.cpp + ) +endif() + +if (ZXING_WRITERS_OLD) + set (COMMON_FILES ${COMMON_FILES} + src/MultiFormatWriter.h + src/MultiFormatWriter.cpp + ) +endif() + +# MARK: - PUBLIC_HEADERS + +# define subset of public headers that get distributed with the binaries +set (PUBLIC_HEADERS + src/Barcode.h + src/BarcodeFormat.h + src/CharacterSet.h + src/ContentType.h + src/CreateBarcode.h + src/Error.h + src/GTIN.h + src/ImageView.h + src/Point.h + src/Quadrilateral.h + src/ReadBarcode.h + src/ReaderOptions.h + src/WriteBarcode.h + src/ZXingCpp.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/ZXVersion.h # deprecated, but keep it for now to not break old clients + ${CMAKE_CURRENT_SOURCE_DIR}/src/ZXingQt.h + $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/ZXingC.h> +) +if (ZXING_READERS) + set (PUBLIC_HEADERS ${PUBLIC_HEADERS} + ) +endif() +if (ZXING_WRITERS_OLD) + set (PUBLIC_HEADERS ${PUBLIC_HEADERS} + src/BitMatrix.h + src/BitMatrixIO.h + src/Matrix.h + src/MultiFormatWriter.h + ) +endif() +# end of public header set + +# MARK: - READERS/WRITERS + +if (ZXING_READERS AND ZXING_ENABLE_AZTEC) + set (AZTEC_FILES ${AZTEC_FILES} + src/aztec/AZDecoder.h + src/aztec/AZDecoder.cpp + src/aztec/AZDetector.h + src/aztec/AZDetector.cpp + src/aztec/AZDetectorResult.h + src/aztec/AZReader.h + src/aztec/AZReader.cpp + ) +endif() +if (ZXING_WRITERS_OLD AND ZXING_ENABLE_AZTEC) + set (AZTEC_FILES ${AZTEC_FILES} + src/aztec/AZEncodingState.h + src/aztec/AZEncoder.h + src/aztec/AZEncoder.cpp + src/aztec/AZHighLevelEncoder.h + src/aztec/AZHighLevelEncoder.cpp + src/aztec/AZToken.h + src/aztec/AZToken.cpp + src/aztec/AZWriter.h + src/aztec/AZWriter.cpp + ) +endif() + + +if ((ZXING_READERS OR ZXING_WRITERS_OLD) AND ZXING_ENABLE_DATAMATRIX) + set (DATAMATRIX_FILES + src/datamatrix/DMBitLayout.h + src/datamatrix/DMBitLayout.cpp + src/datamatrix/DMVersion.h + src/datamatrix/DMVersion.cpp + ) +endif() +if (ZXING_READERS AND ZXING_ENABLE_DATAMATRIX) + set (DATAMATRIX_FILES ${DATAMATRIX_FILES} + src/datamatrix/DMDataBlock.h + src/datamatrix/DMDataBlock.cpp + src/datamatrix/DMDecoder.h + src/datamatrix/DMDecoder.cpp + src/datamatrix/DMDetector.h + src/datamatrix/DMDetector.cpp + src/datamatrix/DMReader.h + src/datamatrix/DMReader.cpp + ) +endif() +if (ZXING_WRITERS_OLD AND ZXING_ENABLE_DATAMATRIX) + set (DATAMATRIX_FILES ${DATAMATRIX_FILES} + src/datamatrix/DMECEncoder.h + src/datamatrix/DMECEncoder.cpp + src/datamatrix/DMEncoderContext.h + src/datamatrix/DMHighLevelEncoder.h + src/datamatrix/DMHighLevelEncoder.cpp + src/datamatrix/DMSymbolInfo.h + src/datamatrix/DMSymbolInfo.cpp + src/datamatrix/DMSymbolShape.h + src/datamatrix/DMWriter.h + src/datamatrix/DMWriter.cpp + ) +endif() + + +if (ZXING_READERS AND ZXING_ENABLE_MAXICODE) + set (MAXICODE_FILES ${MAXICODE_FILES} + src/maxicode/MCBitMatrixParser.h + src/maxicode/MCBitMatrixParser.cpp + src/maxicode/MCDecoder.h + src/maxicode/MCDecoder.cpp + src/maxicode/MCReader.h + src/maxicode/MCReader.cpp + ) +endif() + + +if ((ZXING_READERS OR ZXING_WRITERS_OLD) AND ZXING_ENABLE_1D) + set (ONED_FILES + src/oned/ODUPCEANCommon.h + src/oned/ODUPCEANCommon.cpp + src/oned/ODCode93Patterns.h + src/oned/ODCode128Patterns.h + src/oned/ODCode128Patterns.cpp + ) +endif() +if (ZXING_READERS AND ZXING_ENABLE_1D) + set (ONED_FILES ${ONED_FILES} + src/oned/ODCodabarReader.h + src/oned/ODCodabarReader.cpp + src/oned/ODCode39Reader.h + src/oned/ODCode39Reader.cpp + src/oned/ODCode93Reader.h + src/oned/ODCode93Reader.cpp + src/oned/ODCode128Reader.h + src/oned/ODCode128Reader.cpp + src/oned/ODDataBarCommon.h + src/oned/ODDataBarCommon.cpp + src/oned/ODDataBarReader.h + src/oned/ODDataBarReader.cpp + src/oned/ODDataBarExpandedBitDecoder.h + 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/ODTelepenReader.h + src/oned/ODTelepenReader.cpp + src/oned/ODMultiUPCEANReader.h + src/oned/ODMultiUPCEANReader.cpp + src/oned/ODReader.h + src/oned/ODReader.cpp + src/oned/ODRowReader.h + ) +endif() +if (ZXING_WRITERS_OLD AND ZXING_ENABLE_1D) + set (ONED_FILES ${ONED_FILES} + src/oned/ODCodabarWriter.h + src/oned/ODCodabarWriter.cpp + src/oned/ODCode39Writer.h + src/oned/ODCode39Writer.cpp + src/oned/ODCode93Writer.h + src/oned/ODCode93Writer.cpp + src/oned/ODCode128Writer.h + src/oned/ODCode128Writer.cpp + src/oned/ODEAN8Writer.h + src/oned/ODEAN8Writer.cpp + src/oned/ODEAN13Writer.h + src/oned/ODEAN13Writer.cpp + src/oned/ODITFWriter.h + src/oned/ODITFWriter.cpp + src/oned/ODUPCEWriter.h + src/oned/ODUPCEWriter.cpp + src/oned/ODUPCAWriter.h + src/oned/ODUPCAWriter.cpp + src/oned/ODWriterHelper.h + src/oned/ODWriterHelper.cpp + ) +endif() + + +if ((ZXING_READERS OR ZXING_WRITERS_OLD) AND ZXING_ENABLE_PDF417) + set (PDF417_FILES + src/pdf417/ZXBigInteger.h + src/pdf417/ZXBigInteger.cpp + ) +endif() +if (ZXING_READERS AND ZXING_ENABLE_PDF417) + set (PDF417_FILES ${PDF417_FILES} + src/pdf417/PDFBarcodeMetadata.h + src/pdf417/PDFBarcodeValue.h + src/pdf417/PDFBarcodeValue.cpp + src/pdf417/PDFBoundingBox.h + src/pdf417/PDFBoundingBox.cpp + src/pdf417/PDFCodeword.h + src/pdf417/PDFCodewordDecoder.h + src/pdf417/PDFCodewordDecoder.cpp + src/pdf417/PDFCustomData.h + src/pdf417/PDFDecoder.h + src/pdf417/PDFDecoder.cpp + src/pdf417/PDFDetectionResult.h + src/pdf417/PDFDetectionResult.cpp + src/pdf417/PDFDetectionResultColumn.h + src/pdf417/PDFDetectionResultColumn.cpp + src/pdf417/PDFDetector.h + src/pdf417/PDFDetector.cpp + src/pdf417/PDFReader.h + src/pdf417/PDFReader.cpp + src/pdf417/PDFScanningDecoder.h + src/pdf417/PDFScanningDecoder.cpp + src/pdf417/ZXNullable.h + src/pdf417/MicroPDFReader.h + src/pdf417/MicroPDFReader.cpp + ) +endif() +if (ZXING_WRITERS_OLD AND ZXING_ENABLE_PDF417) + set (PDF417_FILES ${PDF417_FILES} + src/pdf417/PDFCompaction.h + src/pdf417/PDFEncoder.h + src/pdf417/PDFEncoder.cpp + src/pdf417/PDFHighLevelEncoder.h + src/pdf417/PDFHighLevelEncoder.cpp + src/pdf417/PDFWriter.h + src/pdf417/PDFWriter.cpp + ) +endif() + + +if ((ZXING_READERS OR ZXING_WRITERS_OLD) AND ZXING_ENABLE_QRCODE) + 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 AND ZXING_ENABLE_QRCODE) + set (QRCODE_FILES ${QRCODE_FILES} + src/qrcode/QRBitMatrixParser.h + src/qrcode/QRBitMatrixParser.cpp + src/qrcode/QRDataBlock.h + src/qrcode/QRDataBlock.cpp + src/qrcode/QRDataMask.h + src/qrcode/QRDecoder.h + src/qrcode/QRDecoder.cpp + src/qrcode/QRDetector.h + src/qrcode/QRDetector.cpp + src/qrcode/QRECB.h + src/qrcode/QRFormatInformation.h + src/qrcode/QRFormatInformation.cpp + src/qrcode/QRReader.h + src/qrcode/QRReader.cpp + ) +endif() +if (ZXING_WRITERS_OLD AND ZXING_ENABLE_QRCODE) + set (QRCODE_FILES ${QRCODE_FILES} + src/qrcode/QREncoder.h + src/qrcode/QREncoder.cpp + src/qrcode/QREncodeResult.h + src/qrcode/QRMaskUtil.h + src/qrcode/QRMaskUtil.cpp + src/qrcode/QRMatrixUtil.h + src/qrcode/QRMatrixUtil.cpp + src/qrcode/QRWriter.h + src/qrcode/QRWriter.cpp + ) +endif() + +source_group (Sources FILES ${COMMON_FILES}) +source_group (Sources\\aztec FILES ${AZTEC_FILES}) +source_group (Sources\\datamatrix FILES ${DATAMATRIX_FILES}) +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}) + +# MARK: - Library target + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +set(THREADS_PREFER_PTHREAD_FLAG TRUE) +find_package(Threads REQUIRED) + +add_library (ZXing + ${COMMON_FILES} + ${AZTEC_FILES} + ${DATAMATRIX_FILES} + ${MAXICODE_FILES} + ${ONED_FILES} + ${PDF417_FILES} + ${QRCODE_FILES} +) + +target_include_directories (ZXing + PUBLIC "$" "$" + INTERFACE "$" +) + +target_compile_options (ZXing + PUBLIC ${ZXING_PUBLIC_FLAGS} + PRIVATE ${ZXING_PRIVATE_FLAGS} +) + +target_compile_features(ZXing PUBLIC cxx_std_17) + +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}) + + set (LIBRSCPP_FILES + src/librscpp/field.h + src/librscpp/poly.h + src/librscpp/encode.h + src/librscpp/decode.h + ) + target_sources(ZXing PRIVATE ${LIBRSCPP_FILES}) + source_group (Sources\\librscpp FILES ${LIBRSCPP_FILES}) +endif() + +# MARK: - ZINT + +if (ZXING_WRITERS_NEW) + if (ZXING_USE_BUNDLED_ZINT) + set (LIBZINT_FILES + src/libzint/stubs.c + src/libzint/common.c + src/libzint/common.h + src/libzint/big5.h + src/libzint/eci.c + src/libzint/eci.h + src/libzint/eci_sb.h + src/libzint/filemem.c + src/libzint/filemem.h + src/libzint/fonts/normal_woff2.h + src/libzint/fonts/upcean_woff2.h + src/libzint/gb18030.h + src/libzint/gb2312.h + src/libzint/gbk.h + src/libzint/general_field.c + src/libzint/general_field.h + src/libzint/gs1.c + src/libzint/gs1.h + src/libzint/gs1_lint.h + src/libzint/iso3166.h + src/libzint/iso4217.h + src/libzint/ksx1001.h + src/libzint/large.c + src/libzint/large.h + src/libzint/library.c + src/libzint/output.c + src/libzint/output.h + src/libzint/raster.c + src/libzint/raster_font.h + src/libzint/reedsol.c + src/libzint/reedsol.h + src/libzint/reedsol_logs.h + src/libzint/sjis.h + src/libzint/svg.c + src/libzint/vector.c + src/libzint/zfiletypes.h + src/libzint/zintconfig.h + src/libzint/zint.h + ) + if (ZXING_ENABLE_1D) + list(APPEND LIBZINT_FILES + src/libzint/2of5inter.c + src/libzint/2of5inter_based.c + src/libzint/telepen.c + src/libzint/codabar.c + src/libzint/code128.c + src/libzint/code128.h + src/libzint/code.c + src/libzint/dxfilmedge.c + src/libzint/medical.c + src/libzint/rss.c + src/libzint/rss.h + src/libzint/upcean.c + ) + endif() + if (ZXING_ENABLE_AZTEC) + list(APPEND LIBZINT_FILES + src/libzint/aztec.c + src/libzint/aztec.h + ) + endif() + if (ZXING_ENABLE_DATAMATRIX) + list(APPEND LIBZINT_FILES + src/libzint/dmatrix.c + src/libzint/dmatrix.h + ) + endif() + if (ZXING_ENABLE_MAXICODE) + list(APPEND LIBZINT_FILES + src/libzint/maxicode.c + src/libzint/maxicode.h + ) + endif() + if (ZXING_ENABLE_PDF417) + list(APPEND LIBZINT_FILES + src/libzint/pdf417.c + src/libzint/pdf417.h + src/libzint/pdf417_tabs.h + ) + endif() + + if (ZXING_ENABLE_QRCODE) + list(APPEND LIBZINT_FILES + src/libzint/qr.c + src/libzint/qr.h + ) + endif() + set_source_files_properties(${LIBZINT_FILES} PROPERTIES SKIP_PRECOMPILE_HEADERS ON COMPILE_OPTIONS -DZINT_NO_PNG) + 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 2.16.0) + target_link_libraries (ZXing PRIVATE $,zint::zint-static,zint::zint>) + endif() +endif() + +add_library(ZXing::ZXing ALIAS ZXing) +# 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) + +set_target_properties(ZXing PROPERTIES EXPORT_NAME ZXing) +# force position independent code to be able to link it as static lib into a DLL (e.g. the python module) +set_target_properties(ZXing PROPERTIES POSITION_INDEPENDENT_CODE ON) +if (PROJECT_VERSION) + set_target_properties(ZXing PROPERTIES VERSION ${PROJECT_VERSION}) + set_target_properties(ZXing PROPERTIES SOVERSION ${ZXING_SONAME}) +endif() + +# Add Windows resource file for version information +if (WIN32 AND BUILD_SHARED_LIBS) + # Convert version to comma-separated format for Windows resources + string(REPLACE "." "," FILE_VERSION_COMMA "${PROJECT_VERSION}.0") + set(FILE_VERSION_STR "${PROJECT_VERSION}.0") + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY + ) + target_sources(ZXing PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +# TODO: use FILE_SET HEADERS instead of PUBLIC_HEADER and target_sources, once CMake 3.23+ is required +set_target_properties(ZXing PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") + +# MARK: - Precompiled headers + +set(PRECOMPILE_HEADERS ${PUBLIC_HEADERS}) +list(REMOVE_ITEM PRECOMPILE_HEADERS + "$<$:${CMAKE_CURRENT_SOURCE_DIR}/src/ZXingC.h>" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ZXingQt.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ZXVersion.h") +list(APPEND PRECOMPILE_HEADERS "src/BarcodeData.h" "src/Content.h" "src/Range.h") +if (NOT ZXING_DISABLE_PCH) + target_precompile_headers(ZXing PRIVATE ${PRECOMPILE_HEADERS}) +endif() + +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/CreateBarcode.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() + +# MARK: - Installation + +include (GNUInstallDirs) + +set(ZX_INSTALL_TARGETS ZXing) + +install ( + TARGETS ${ZX_INSTALL_TARGETS} EXPORT ZXingTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + FRAMEWORK DESTINATION ${CMAKE_INSTALL_LIBDIR} +# INCLUDES DESTINATION include + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ZXing" +) + +configure_file (Version.h.in Version.h) + +install ( + FILES "${CMAKE_CURRENT_BINARY_DIR}/Version.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ZXing" +) + +if (MSVC) + set_target_properties(ZXing PROPERTIES + COMPILE_PDB_NAME ZXing + COMPILE_PDB_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ZXing.pdb + DESTINATION ${CMAKE_INSTALL_LIBDIR} + CONFIGURATIONS Debug RelWithDebInfo + OPTIONAL) +endif() + +set (CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/ZXing") + +install ( + EXPORT ZXingTargets + DESTINATION ${CMAKECONFIG_INSTALL_DIR} NAMESPACE ZXing:: +) + +IF (NOT WIN32 OR MINGW) + # if (${ZXING_EXPERIMENTAL_API}) + # set(COMPILE_FLAGS "-DZXING_EXPERIMENTAL_API") + # endif() + configure_file(zxing.pc.in zxing.pc @ONLY) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/zxing.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +ENDIF() + +include (CMakePackageConfigHelpers) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/ZXingConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) + +configure_package_config_file ( + ZXingConfig.cmake.in + ZXingConfig.cmake + INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} +) + +install ( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/ZXingConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/ZXingConfigVersion.cmake" + DESTINATION ${CMAKECONFIG_INSTALL_DIR} +) diff --git a/tools/chatgui/zxing-cpp/core/Version.h.in b/tools/chatgui/zxing-cpp/core/Version.h.in new file mode 100644 index 00000000..02c439c6 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/Version.h.in @@ -0,0 +1,28 @@ +/* +* Copyright 2019 Nu-book Inc. +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#cmakedefine ZXING_READERS +#cmakedefine ZXING_WRITERS + +#cmakedefine01 ZXING_ENABLE_1D +#cmakedefine01 ZXING_ENABLE_AZTEC +#cmakedefine01 ZXING_ENABLE_DATAMATRIX +#cmakedefine01 ZXING_ENABLE_MAXICODE +#cmakedefine01 ZXING_ENABLE_PDF417 +#cmakedefine01 ZXING_ENABLE_QRCODE + +#cmakedefine ZXING_EXPERIMENTAL_API +#cmakedefine ZXING_USE_ZINT + +// Version numbering +#define ZXING_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#define ZXING_VERSION_MINOR @PROJECT_VERSION_MINOR@ +#define ZXING_VERSION_PATCH @PROJECT_VERSION_PATCH@ +#define ZXING_VERSION_SUFFIX "@ZXING_VERSION_SUFFIX@" + +#define ZXING_VERSION_STR "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@@ZXING_VERSION_SUFFIX@" diff --git a/tools/chatgui/zxing-cpp/core/ZXingConfig.cmake.in b/tools/chatgui/zxing-cpp/core/ZXingConfig.cmake.in new file mode 100644 index 00000000..7bcb4ec8 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/ZXingConfig.cmake.in @@ -0,0 +1,9 @@ +@PACKAGE_INIT@ +include("${CMAKE_CURRENT_LIST_DIR}/ZXingTargets.cmake") + +# this does not work: add_library(ZXing::Core ALIAS ZXing::ZXing) +# this is a workaround available since 3.11 : +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.11 AND NOT TARGET ZXing::Core) + add_library(ZXing::Core INTERFACE IMPORTED) + target_link_libraries(ZXing::Core INTERFACE ZXing::ZXing) +endif() diff --git a/tools/chatgui/zxing-cpp/core/src/Barcode.cpp b/tools/chatgui/zxing-cpp/core/src/Barcode.cpp new file mode 100644 index 00000000..688f85b8 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Barcode.cpp @@ -0,0 +1,255 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "Barcode.h" + +#include "BarcodeData.h" +#include "JSON.h" +#include "ZXAlgorithms.h" + +#include "BitMatrix.h" + +#include +#include +#include +#include +#include + +#ifdef ZXING_USE_ZINT +#include +void zint_symbol_deleter::operator()(zint_symbol* p) const noexcept +{ + ZBarcode_Delete(p); +} +#endif + +namespace ZXing { + +Barcode::Barcode() : d(std::make_shared()) {} + +Barcode::Barcode(Data&& data) : d(std::make_shared(std::move(data))) {} + +bool Barcode::isValid() const +{ + return d->isValid(); +} + +const Error& Barcode::error() const +{ + return d->error; +} + +BarcodeFormat Barcode::format() const +{ + return d->format; +} + +const Position& Barcode::position() const +{ + return d->position; +} + +const std::vector& Barcode::bytes() const +{ + return d->content.bytes; +} + +std::vector Barcode::bytesECI() const +{ + return d->content.bytesECI(); +} + +std::string Barcode::text(TextMode mode) const +{ + return d->content.text(mode); +} + +std::string Barcode::text() const +{ + return text(d->defaultTextMode); +} + +ContentType Barcode::contentType() const +{ + return d->content.type(); +} + +bool Barcode::hasECI() const +{ + return d->content.hasECI; +} + +int Barcode::orientation() const +{ + return narrow_cast(std::lround(d->position.orientation() * 180 / std::numbers::pi)); +} + +bool Barcode::isMirrored() const +{ + return d->isMirrored; +} + +bool Barcode::isInverted() const +{ + return d->isInverted; +} + +std::string Barcode::symbologyIdentifier() const +{ + return d->content.symbology.toString(); +} + +int Barcode::sequenceSize() const +{ + return d->sai.count; +} + +int Barcode::sequenceIndex() const +{ + return d->sai.index; +} + +std::string Barcode::sequenceId() const +{ + return d->sai.id; +} + +int Barcode::lineCount() const +{ + return d->lineCount; +} + +Barcode& Barcode::setReaderOptions(const ReaderOptions& opts) +{ + if (opts.characterSet() != CharacterSet::Unknown) + d->content.defaultCharset = opts.characterSet(); + d->defaultTextMode = opts.textMode(); + return *this; +} + +ImageView Barcode::symbol() const +{ + return !d->symbol.empty() ? ImageView{d->symbol.row(0).begin(), d->symbol.width(), d->symbol.height(), ImageFormat::Lum} + : ImageView{}; +} + +#if defined(ZXING_USE_ZINT) && defined(ZXING_EXPERIMENTAL_API) +zint_symbol* Barcode::zint() const +{ + return d->zint.get(); +} +#endif + +std::string Barcode::extra(std::string_view key) const +{ + if (key == "ALL") { + if (format() == BarcodeFormat::None) + return {}; + auto res = + StrCat("{", JsonProp("Text", text(TextMode::Plain)), JsonProp("HRI", text(TextMode::HRI)), + JsonProp("TextECI", text(TextMode::ECI)), JsonProp("Bytes", text(TextMode::Hex)), + JsonProp("Identifier", symbologyIdentifier()), JsonProp("Format", Name(format())), + JsonProp("Symbology", Name(Symbology(format()))), JsonProp("ContentType", isValid() ? ToString(contentType()) : ""), + JsonProp("Position", ToString(position())), JsonProp("HasECI", hasECI()), JsonProp("IsMirrored", isMirrored()), + JsonProp("IsInverted", isInverted()), d->extra, JsonProp("Error", ToString(error()))); + res.back() = '}'; + return res; + } + return d->extra.empty() ? "" + : key.empty() ? StrCat("{", std::string_view(d->extra).substr(0, d->extra.size() - 1), "}") // remove trailing ',' + : JsonGet(d->extra, key).value_or(""); // make sure JsonUnescape() is called +} + +bool Barcode::operator==(const Barcode& o) const +{ + return *d == *o.d; +} + +bool BarcodeData::operator==(const BarcodeData& o) const +{ + if (format != o.format) + return false; + + // handle MatrixCodes first + if (!(format & BarcodeFormat::AllLinear)) { + if (content.bytes != o.content.bytes) + return false; + + // At this point both are valid with the same content or both are in error. + // Treat them as equal if their positions are about the same (center of one is inside the other). + return IsInside(Center(o.position), position); + } + + if (content.bytes != o.content.bytes || error != o.error || orientation() != o.orientation()) + return false; + + if (lineCount > 1 && o.lineCount > 1) + return HaveIntersectingBoundingBoxes(o.position, position); + + // the following code is only meant for this or other lineCount == 1 + assert(lineCount == 1 || o.lineCount == 1); + + // sl == single line, ml = multi line + const auto& sl = lineCount == 1 ? *this : o; + const auto& ml = lineCount == 1 ? o : *this; + + // If one line is less than half the length of the other away from the + // latter, we consider it to belong to the same symbol. + // Additionally, both need to have roughly the same length (see #367). + auto dTop = maxAbsComponent(ml.position.topLeft() - sl.position.topLeft()); + auto dBot = maxAbsComponent(ml.position.bottomLeft() - sl.position.topLeft()); + auto slLength = maxAbsComponent(sl.position.topLeft() - sl.position.bottomRight()); + bool isHorizontal = sl.position.topLeft().y == sl.position.bottomRight().y; + // Measure the multi line length in the same direction as the single line one (not diagonally) + // to make sure overly tall symbols don't get segmented (see #769). + auto mlLength = isHorizontal ? std::abs(ml.position.topLeft().x - ml.position.bottomRight().x) + : std::abs(ml.position.topLeft().y - ml.position.bottomRight().y); + + return std::min(dTop, dBot) < slLength / 2 && std::abs(slLength - mlLength) < slLength / 5; +} + +Barcode MergeStructuredAppendSequence(const Barcodes& barcodes) +{ + if (barcodes.empty()) + return {}; + + std::list allBarcodes(barcodes.begin(), barcodes.end()); + allBarcodes.sort([](const Barcode& r1, const Barcode& r2) { return r1.sequenceIndex() < r2.sequenceIndex(); }); + + Barcode res = allBarcodes.front(); + for (auto i = std::next(allBarcodes.begin()); i != allBarcodes.end(); ++i) + res.d->content.append(i->d->content); + + res.d->position = {}; + res.d->sai.index = -1; + + if (allBarcodes.back().sequenceSize() != Size(allBarcodes) || + !std::all_of(allBarcodes.begin(), allBarcodes.end(), + [&](Barcode& it) { return it.sequenceId() == allBarcodes.front().sequenceId(); })) + res.d->error = FormatError("sequenceIDs not matching during structured append sequence merging"); + + return res; +} + +Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes) +{ + std::map sas; + for (auto& barcode : barcodes) { + if (barcode.isPartOfSequence()) + sas[barcode.sequenceId()].push_back(barcode); + } + + Barcodes res; + for (auto& [id, seq] : sas) { + auto barcode = MergeStructuredAppendSequence(seq); + if (barcode.isValid()) + res.push_back(std::move(barcode)); + } + + return res; +} + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/Barcode.h b/tools/chatgui/zxing-cpp/core/src/Barcode.h new file mode 100644 index 00000000..d87cf807 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Barcode.h @@ -0,0 +1,191 @@ +/* +* 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 "ContentType.h" +#include "Error.h" +#include "ImageView.h" +#include "Quadrilateral.h" +#include "ReaderOptions.h" // for TextMode +#include "Version.h" // ZXING_... macros + +#include +#include +#include + +#ifdef ZXING_USE_ZINT +extern "C" struct zint_symbol; +#endif + +namespace ZXing { + +class CreatorOptions; +class ReaderOptions; +class WriterOptions; +class Barcode; +struct BarcodeData; + +using Position = QuadrilateralI; +using Barcodes = std::vector; + +/** + * @brief String keys for the extra() metadata of Barcode. The values depend on the symbology and may not be present for all barcodes. + */ +namespace BarcodeExtra { + #define ZX_EXTRA(NAME) static constexpr auto NAME = #NAME + ZX_EXTRA(DataMask); ///< QRCodes + ZX_EXTRA(Version); ///< QRCodes, DataMatrix, MaxiCode (e.g. "12x12" for DataMatrix) + ZX_EXTRA(EanAddOn); ///< The EAN/UPC add-on content if detected + ZX_EXTRA(ECLevel); ///< Error correction level (e.g. "L", "M", "Q", "H" for QRCode) + ZX_EXTRA(UEC); ///< Unused error correction margin in the range [0, 1] + ZX_EXTRA(UPCE); ///< The original (non-normalized) UPC-E code if UPC-E is detected + ZX_EXTRA(ReaderInit); + #undef ZX_EXTRA +} // namespace BarcodeExtra + +/** + * @brief The Barcode class encapsulates a decoded or created barcode symbol. + * + * Barcode represents a decoded or created barcode symbol, providing access to its content, format, + * position, and other metadata. It serves as the primary interface for working with barcodes in the library. + * + * Barcode objects are obtained from the ReadBarcodes function when scanning an image, or from the + * CreateBarcodeFrom... functions when generating a new barcode. To convert a Barcode to an image or SVG, + * use the WriteBarcodeTo... functions. + * + * @see ReadBarcodes, CreateBarcodeFromText, CreateBarcodeFromBytes, WriteBarcodeToImage, WriteBarcodeToSVG + */ +class Barcode +{ + using Data = BarcodeData; + + std::shared_ptr d; + + Barcode& setReaderOptions(const ReaderOptions& opts); + + friend Barcode MergeStructuredAppendSequence(const Barcodes&); + friend Barcodes ReadBarcodes(const ImageView&, const ReaderOptions&); + friend Barcode CreateBarcode(const void*, int, int, const CreatorOptions&); + friend Image WriteBarcodeToImage(const Barcode&, const WriterOptions&); + friend std::string WriteBarcodeToSVG(const Barcode&, const WriterOptions&); + +public: + Barcode(); + Barcode(Barcode::Data&& data); + + /// Returns whether the barcode is valid, i.e. it contains a successfully decoded or created symbol. + bool isValid() const; + + /// Returns the error associated with the barcode, if any. + const Error& error() const; + + /// Returns the BarcodeFormat of the barcode. + BarcodeFormat format() const; + + /// Returns the symbology of the barcode format (e.g. EAN/UPC for EAN13, EAN8, UPCA, etc.). + BarcodeFormat symbology() const { return Symbology(format()); } + + /// Returns the raw / standard content without any modifications like character set conversions. + const std::vector& bytes() const; + + /// Returns the raw / standard content following the ECI protocol. + std::vector bytesECI() const; + + /// Returns the bytes() content rendered to unicode/utf8 text according to specified TextMode. + std::string text(TextMode mode) const; + + /// Returns the bytes() content rendered to unicode/utf8 text according to the TextMode set in the ReaderOptions. + std::string text() const; + + /// Returns the content type, giving a hint to the type of content found (Text/Binary/GS1/etc.). + ContentType contentType() const; + + /// Returns whether or not an ECI tag was found. + bool hasECI() const; + + /// Returns the position of the barcode in the image as a quadrilateral of 4 points (topLeft, topRight, bottomRight, + /// bottomLeft). + const Position& position() const; + + /// Returns the orientation of the barcode in degrees, see also Position::orientation() + int orientation() const; + + /// Returns whether the symbol is mirrored (currently only supported by QRCode and DataMatrix). + bool isMirrored() const; + /// Returns whether the symbol is inverted / has reversed reflectance (see ReaderOptions::tryInvert). + bool isInverted() const; + + /// Returns the symbology identifier "]cm" where "c" is symbology code character, "m" the modifier. + std::string symbologyIdentifier() const; + + /// @brief Returns the 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; + + /// Returns the 0-based index of this symbol in a structured append sequence. + int sequenceIndex() const; + + /// @brief Returns the sequenceId 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; + + /// Returns whether this symbol is the last in a structured append sequence. + bool isLastInSequence() const { return sequenceSize() == sequenceIndex() + 1; } + + /// Returns whether this symbol is part of a structured append sequence. + bool isPartOfSequence() const { return sequenceSize() > -1 && sequenceIndex() > -1; } + + /// Returns how many lines have been detected with this code (applies only to linear symbologies). + int lineCount() const; + + /// Returns the bit matrix of the symbol as an ImageView. + ImageView symbol() const; + + /// @brief Retrieve supplementary metadata associated with this barcode. + /// + /// Returns a string containing additional and symbology specific information. In form of a JSON object + /// serialization. The optional parameter @p key can be used to retrieve a specific item only. + /// Key values are case insensitive. See BarcodeExtra namespace for valid keys. + /// If the key is not found or there is no info available, an empty string is returned. + std::string extra(std::string_view key = "") const; + + /// @cond DEPRECATED + /// Returns the error correction level of the symbol (empty string if not applicable). + // [[deprecated ("use extra(BarcodeExtra::ECLevel) instead")]] + std::string ecLevel() const { return extra(BarcodeExtra::ECLevel); } + + /// Returns whether the symbol is a Reader Initialisation/Programming symbol. + // [[deprecated ("use extra(BarcodeExtra::ReaderInit) instead")]] + bool readerInit() const { return !extra(BarcodeExtra::ReaderInit).empty(); } + + /// Returns the version QRCode / DataMatrix / Aztec version or size. + // [[deprecated ("use extra(BarcodeExtra::Version) instead")]] + std::string version() const { return extra(BarcodeExtra::Version); } + /// @endcond + +#if defined(ZXING_USE_ZINT) && defined(ZXING_EXPERIMENTAL_API) + zint_symbol* zint() const; +#endif + + bool operator==(const Barcode& o) const; +}; + +/// Merge a list of Barcodes from one Structured Append sequence to a single barcode. +Barcode MergeStructuredAppendSequence(const Barcodes& barcodes); + +/// Automatically merge all Structured Append sequences found in the given list of barcodes. +Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes); + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BarcodeData.h b/tools/chatgui/zxing-cpp/core/src/BarcodeData.h new file mode 100644 index 00000000..f8ebdaec --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BarcodeData.h @@ -0,0 +1,100 @@ +/* + * Copyright 2026 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Barcode.h" +#include "BitMatrix.h" +#include "Content.h" +#include "DecoderResult.h" +#include "DetectorResult.h" +#include "Error.h" +#include "Quadrilateral.h" +#include "ReaderOptions.h" +#include "StructuredAppend.h" + +#include +#include +#include +#include +#include + +#ifdef ZXING_USE_ZINT +extern "C" struct zint_symbol; +struct zint_symbol_deleter +{ + void operator()(zint_symbol* p) const noexcept; +}; +using unique_zint_symbol = std::unique_ptr; +#else +struct zint_symbol {}; +using unique_zint_symbol = std::unique_ptr; +#endif + +namespace ZXing { + +struct BarcodeData +{ + Content content; + Error error = {}; + Position position = {}; + BarcodeFormat format = BarcodeFormat::None; + std::string extra = {}; + StructuredAppendInfo sai = {}; + BitMatrix symbol = {}; +#ifdef ZXING_USE_ZINT + unique_zint_symbol zint = {}; + mutable std::unique_ptr zintMutex = {}; +#endif + TextMode defaultTextMode = TextMode::HRI; + int lineCount = 0; + bool isMirrored = false; + bool isInverted = false; + + bool operator==(const BarcodeData& other) const; + + inline bool isValid() const { return format != BarcodeFormat::None && !content.bytes.empty() && !error; } + + inline int orientation() const { return narrow_cast(std::lround(position.orientation() * 180 / std::numbers::pi)); } +}; + +using BarcodesData = std::vector; + +inline BarcodeData LinearBarcode(BarcodeFormat format, const std::string& text, int y, int xStart, int xStop, SymbologyIdentifier si, + Error error = {}, std::string extra = {}) +{ + return {.content = Content(ByteArray(text), si, CharacterSet::ISO8859_1), + .error = std::move(error), + .position = Line(y, xStart, xStop), + .format = format, + .extra = std::move(extra)}; +} + +inline BarcodeData MatrixBarcode(DecoderResult&& decodeResult, DetectorResult&& detectorResult, BarcodeFormat format) +{ + auto extra = std::move(decodeResult).json(); + if (JsonFind(extra, BarcodeExtra::Version).empty() && decodeResult.versionNumber()) + extra += JsonProp(BarcodeExtra::Version, std::to_string(decodeResult.versionNumber())); + if (JsonFind(extra, BarcodeExtra::ECLevel).empty() && !decodeResult.ecLevel().empty()) + extra += JsonProp(BarcodeExtra::ECLevel, decodeResult.ecLevel()); + + extra += JsonProp(BarcodeExtra::ReaderInit, decodeResult.readerInit()); + + // the BitMatrix stores 'black'/foreground as 0xFF and 'white'/background as 0, but we + // want the ImageView returned by symbol() to be a standard luminance image (black == 0) + std::move(detectorResult).bits().flipAll(); + + return {.content = std::move(decodeResult).content(), + .error = std::move(decodeResult).error(), + .position = std::move(detectorResult).position(), + .format = format, + .extra = std::move(extra), + .sai = std::move(decodeResult).structuredAppend(), + .symbol = std::move(detectorResult).bits(), + .lineCount = decodeResult.lineCount(), + .isMirrored = decodeResult.isMirrored()}; +} + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BarcodeFormat.cpp b/tools/chatgui/zxing-cpp/core/src/BarcodeFormat.cpp new file mode 100644 index 00000000..2b0e2fbe --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BarcodeFormat.cpp @@ -0,0 +1,286 @@ +/* + * Copyright 2026 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#include "BarcodeFormat.h" + +#include "Version.h" +#include "ZXAlgorithms.h" + +#include + +namespace ZXing { + +// =================================== BarcodeFormat ================================= + +static_assert(ZX_BCF_ID('\0', '\0') == 0, "BarcodeFormat encoding error"); + +BarcodeFormat Symbology(BarcodeFormat format) +{ + return BarcodeFormat(ZX_BCF_ID(SymbologyKey(format), ' ' * (VariantKey(format) != 0))); +} + +std::string_view Name(BarcodeFormat format) +{ + switch (format) { +#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \ + case BarcodeFormat(ZX_BCF_ID(SYM, VAR)): return HRI; + ZX_BCF_LIST(X) +#undef X + default: return "Unknown"; + }; +} + +BarcodeFormat BarcodeFormatFromString(std::string_view str) +{ + if (str.size() < 3) + throw std::invalid_argument(StrCat("This is not a valid barcode format: '", str, "'")); +#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \ + if ((str[0] == ']' && str[1] == SYM && str[2] == VAR) || IsEqualIgnoreCase(str, #NAME) || IsEqualIgnoreCaseAnd(str, HRI, " -_/")) \ + return BarcodeFormat(ZX_BCF_ID(SYM, VAR)); + ZX_BCF_LIST(X) +#undef X + throw std::invalid_argument(StrCat("This is not a valid barcode format: '", str, "'")); +} + +std::string ToString(BarcodeFormat format) +{ + return std::string(Name(format)); +} + +bool operator<=(BarcodeFormat e, BarcodeFormat s) +{ + return e == s || s == BarcodeFormat::All + || (SymbologyKey(s) == '*' ? SymbologyKey(e) != '*' && (e & s) + : SymbologyKey(s) == SymbologyKey(e) && VariantKey(s) == ' '); +} + +bool operator&(BarcodeFormat a, BarcodeFormat b) +{ + if (SymbologyKey(a) == '*' && SymbologyKey(b) == '*') { + switch (VariantKey(a)) { + case 'l': return ZXING_ENABLE_1D && !Contains("smp", VariantKey(b)); + case 's': return ZXING_ENABLE_PDF417 && !Contains("lmp", VariantKey(b)); + case 'p': return !Contains("lms", VariantKey(b)); // TODO: postal codes + case 'm': + return (ZXING_ENABLE_MAXICODE || ZXING_ENABLE_QRCODE || ZXING_ENABLE_DATAMATRIX || ZXING_ENABLE_AZTEC) + && !Contains("lsp", VariantKey(b)); + default: return true; + } + } + + if (SymbologyKey(a) == '*') + std::swap(a, b); + + if (SymbologyKey(b) == '*') { + char vkb = VariantKey(b); + if (vkb == '*') + return true; + + switch (a) { +#ifdef ZXING_USE_ZINT +#define USING_ZINT 1 +#else +#define USING_ZINT 0 +#endif +#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \ + case BarcodeFormat(ZX_BCF_ID(SYM, VAR)): \ + return ENABLED \ + && ((vkb == 'w' && USING_ZINT) ? ZINT : (FLAGS[0] == vkb || FLAGS[1] == vkb || FLAGS[2] == vkb || FLAGS[3] == vkb || FLAGS[4] == vkb)); + ZX_BCF_LIST(X) +#undef X + }; + } + + if (a == b || Symbology(a) == b || a == Symbology(b)) + return true; + + return false; +} + +// ==================================== BarcodeFormats ================================= + +inline bool operator<(BarcodeFormat a, BarcodeFormat b) +{ + return SymbologyKey(a) < SymbologyKey(b) || (SymbologyKey(a) == SymbologyKey(b) && VariantKey(a) < VariantKey(b)); +} + +void BarcodeFormats::normalize() +{ + std::erase_if(formats_, [](BarcodeFormat t) { return t == BarcodeFormat::None; }); + std::sort(formats_.begin(), formats_.end()); + formats_.erase(std::unique(formats_.begin(), formats_.end()), formats_.end()); +} + +BarcodeFormats::BarcodeFormats(std::string_view str) +{ + while (str.size() >= 3 && str[0] == ']') { + formats_.push_back(BarcodeFormat(ZX_BCF_ID(str[1], str[2]))); + str.remove_prefix(3); + } + + ForEachToken(TrimWS(str, " []"), ",|", [this](std::string_view token) { + if (!token.empty()) + formats_.push_back(BarcodeFormatFromString(token)); + }); + + normalize(); +} + +BarcodeFormats BarcodeFormats::list(const BarcodeFormats& filter) +{ + std::vector res; + res.reserve(100); + if (filter.empty()) { +#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \ + if (ENABLED) \ + res.push_back(BarcodeFormat(ZX_BCF_ID(SYM, VAR))); + ZX_BCF_LIST(X) +#undef X + return res; + } + for (auto f : filter) { + // printf("Filter for: %s\n", IdStr(f).c_str()); +#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \ + if (ENABLED && SYM != '*' \ + && (SymbologyKey(f) == '*' ? BarcodeFormat(ZX_BCF_ID(SYM, VAR)) & f \ + : SYM == SymbologyKey(f) && (VariantKey(f) == ' ' || VariantKey(f) == VAR))) \ + res.push_back(BarcodeFormat(ZX_BCF_ID(SYM, VAR))); //, printf("adding: %c %c\n", SYM, VAR); + ZX_BCF_LIST(X) +#undef X + // printf("N: %d\n", (int)res.size()); + } + return res; +} + +// BarcodeFormats&& BarcodeFormats::operator|(BarcodeFormat bt) && +// { +// types_.push_back(bt); +// normalize(); +// return std::move(*this); +// } + +// BarcodeFormats&& BarcodeFormats::operator|(const BarcodeFormats& other) && +// { +// types_.insert(std::end(types_), other.begin(), other.end()); +// normalize(); +// return std::move(*this); +// } + +BarcodeFormats BarcodeFormats::operator&(const BarcodeFormats& other) +{ + std::vector res; + for (auto l : formats_) + for (auto r : other.formats_) + if (l <= r) + res.push_back(l); + else if (r <= l) + res.push_back(r); + return res; +} + +BarcodeFormats BarcodeFormatsFromString(std::string_view str) +{ + return BarcodeFormats(str); +} + +std::string ToString(const BarcodeFormats& formats) +{ + if (formats.empty()) + return {}; + std::string res; + for (auto f : formats) + res += std::string(Name(f)) + ", "; + return res.substr(0, res.size() - 2); +} + +} // namespace ZXing + +#if 0 +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "BarcodeFormat.h" + +#include "ZXAlgorithms.h" + +#include +#include + +namespace ZXing { + +struct BarcodeFormatName +{ + BarcodeFormat format; + std::string_view name; +}; + +static const BarcodeFormatName NAMES[] = { + {BarcodeFormat::None, "None"}, + {BarcodeFormat::Aztec, "Aztec"}, + {BarcodeFormat::Codabar, "Codabar"}, + {BarcodeFormat::Code39, "Code39"}, + {BarcodeFormat::Code93, "Code93"}, + {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"}, + {BarcodeFormat::MaxiCode, "MaxiCode"}, + {BarcodeFormat::MicroPDF417, "MicroPDF417"}, + {BarcodeFormat::MicroQRCode, "MicroQRCode"}, + {BarcodeFormat::PDF417, "PDF417"}, + {BarcodeFormat::QRCode, "QRCode"}, + {BarcodeFormat::RMQRCode, "rMQRCode"}, + {BarcodeFormat::UPCA, "UPC-A"}, + {BarcodeFormat::UPCE, "UPC-E"}, + {BarcodeFormat::LinearCodes, "Linear-Codes"}, + {BarcodeFormat::MatrixCodes, "Matrix-Codes"}, +}; + +std::string ToString(BarcodeFormat format) +{ + auto i = FindIf(NAMES, [format](auto& v) { return v.format == format; }); + return i == std::end(NAMES) ? std::string() : std::string(i->name); +} + +std::string ToString(BarcodeFormats formats) +{ + if (formats.empty()) + return ToString(BarcodeFormat::None); + std::string res; + for (auto f : formats) + res += ToString(f) + "|"; + return res.substr(0, res.size() - 1); +} + +BarcodeFormat BarcodeFormatFromString(std::string_view str) +{ + auto i = FindIf(NAMES, [str](auto& v) { return IsEqualIgnoreCaseAnd(v.name, str, "_-"); }); + return i == std::end(NAMES) ? BarcodeFormat::None : i->format; +} + +BarcodeFormats BarcodeFormatsFromString(std::string_view str) +{ + BarcodeFormats res; + ForEachToken(TrimWS(str, " []"), " ,|", [&res](std::string_view token) { + if (!token.empty()) { + auto bc = BarcodeFormatFromString(token); + if (bc == BarcodeFormat::None) + throw std::invalid_argument("This is not a valid barcode format: '" + std::string(token) + "'"); + res |= bc; + } + }); + return res; +} + +} // ZXing +#endif // 0 diff --git a/tools/chatgui/zxing-cpp/core/src/BarcodeFormat.h b/tools/chatgui/zxing-cpp/core/src/BarcodeFormat.h new file mode 100644 index 00000000..ab54061f --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BarcodeFormat.h @@ -0,0 +1,272 @@ +/* + * Copyright 2026 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Version.h" + +#include + +// FLAGS[0]: l == linear, m == matrix +// FLAGS[1]: r == readable +// FLAGS[2]: w == writable (MultiformatWriter)) +// FLAGS[3]: G == GS1 +// FLAGS[4]: R == Retail, I == Industrial, O == Other + +// clang-format off +// NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI +#define ZX_BCF_LIST(X) \ + X(None, 0 , 0, " ", 0, 1, "None") \ + X(All, '*', '*', " ", 0, 1, "All") \ + X(AllReadable, '*', 'r', " ", 0, 1, "All Readable") \ + X(AllCreatable, '*', 'w', " ", 0, 1, "All Creatable") \ + X(AllLinear, '*', 'l', " ", 0, 1, "All Linear") \ + X(AllMatrix, '*', 'm', " ", 0, 1, "All Matrix") \ + X(AllGS1, '*', 'G', " ", 0, 1, "All GS1") \ + X(AllRetail, '*', 'R', " ", 0, 1, "All Retail") \ + X(AllIndustrial, '*', 'I', " ", 0, 1, "All Industrial") \ + X(Codabar, 'F', ' ', "lrw ", 18, ZXING_ENABLE_1D, "Codabar") \ + X(Code39, 'A', ' ', "lrw I", 8, ZXING_ENABLE_1D, "Code 39") \ + X(Code39Std, 'A', 's', "lrw I", 8, ZXING_ENABLE_1D, "Code 39 Standard") \ + X(Code39Ext, 'A', 'e', "lr I", 9, ZXING_ENABLE_1D, "Code 39 Extended") \ + X(Code32, 'A', '2', "lr I", 129, ZXING_ENABLE_1D, "Code 32") \ + X(PZN, 'A', 'p', "lr I", 52, ZXING_ENABLE_1D, "Pharmazentralnummer") \ + X(Code93, 'G', ' ', "lrw I", 25, ZXING_ENABLE_1D, "Code 93") \ + X(Code128, 'C', ' ', "lrwGI", 20, ZXING_ENABLE_1D, "Code 128") \ + X(ITF, 'I', ' ', "lrw I", 3, ZXING_ENABLE_1D, "ITF") \ + X(ITF14, 'I', '4', "lr I", 89, ZXING_ENABLE_1D, "ITF-14") \ + X(DataBar, 'e', ' ', "lr GR", 29, ZXING_ENABLE_1D, "DataBar") \ + X(DataBarOmni, 'e', 'o', "lr GR", 29, ZXING_ENABLE_1D, "DataBar Omni") \ + X(DataBarStk, 'e', 's', "lr GR", 79, ZXING_ENABLE_1D, "DataBar Stacked") \ + X(DataBarStkOmni, 'e', 'O', "lr GR", 80, ZXING_ENABLE_1D, "DataBar Stacked Omni") \ + X(DataBarLtd, 'e', 'l', "lr GR", 30, ZXING_ENABLE_1D, "DataBar Limited") \ + X(DataBarExp, 'e', 'e', "lr GR", 31, ZXING_ENABLE_1D, "DataBar Expanded") \ + X(DataBarExpStk, 'e', 'E', "lr GR", 81, ZXING_ENABLE_1D, "DataBar Expanded Stacked") \ + X(EANUPC, 'E', ' ', "lr R", 15, ZXING_ENABLE_1D, "EAN/UPC") \ + X(EAN13, 'E', '1', "lrw R", 15, ZXING_ENABLE_1D, "EAN-13") \ + X(EAN8, 'E', '8', "lrw R", 10, ZXING_ENABLE_1D, "EAN-8") \ + X(EAN5, 'E', '5', "l R", 12, ZXING_ENABLE_1D, "EAN-5") \ + X(EAN2, 'E', '2', "l R", 11, ZXING_ENABLE_1D, "EAN-2") \ + X(ISBN, 'E', 'i', "lr R", 69, ZXING_ENABLE_1D, "ISBN") \ + X(UPCA, 'E', 'a', "lrw R", 34, ZXING_ENABLE_1D, "UPC-A") \ + X(UPCE, 'E', 'e', "lrw R", 37, ZXING_ENABLE_1D, "UPC-E") \ + X(Telepen, 'B', ' ', "lr I", 32, ZXING_ENABLE_1D, "Telepen") \ + X(TelepenAlpha, 'B', '0', "lr I", 32, ZXING_ENABLE_1D, "Telepen Alpha") \ + X(TelepenNumeric, 'B', '1', "lr I", 87, ZXING_ENABLE_1D, "Telepen Numeric") \ + X(OtherBarcode, 'X', ' ', " r ", 0, ZXING_ENABLE_1D, "Other barcode") /* see ISO/IEC 15424:2025 */ \ + X(DXFilmEdge, 'X', 'x', "lr ", 147, ZXING_ENABLE_1D, "DX Film Edge") \ + X(PDF417, 'L', ' ', "mrw ", 55, ZXING_ENABLE_PDF417, "PDF417") \ + X(CompactPDF417, 'L', 'c', "mr ", 56, ZXING_ENABLE_PDF417, "Compact PDF417") \ + X(MicroPDF417, 'L', 'm', "mr ", 84, ZXING_ENABLE_PDF417, "MicroPDF417") \ + X(Aztec, 'z', ' ', "mr G ", 92, ZXING_ENABLE_AZTEC, "Aztec") \ + X(AztecCode, 'z', 'c', "mrwG ", 92, ZXING_ENABLE_AZTEC, "Aztec Code") \ + X(AztecRune, 'z', 'r', "mr ", 128, ZXING_ENABLE_AZTEC, "Aztec Rune") \ + X(QRCode, 'Q', ' ', "mrwG ", 58, ZXING_ENABLE_QRCODE, "QR Code") \ + X(QRCodeModel1, 'Q', '1', "mr ", 0, ZXING_ENABLE_QRCODE, "QR Code Model 1") \ + X(QRCodeModel2, 'Q', '2', "mr ", 58, ZXING_ENABLE_QRCODE, "QR Code Model 2") \ + X(MicroQRCode, 'Q', 'm', "mr ", 97, ZXING_ENABLE_QRCODE, "Micro QR Code") \ + X(RMQRCode, 'Q', 'r', "mr G ", 145, ZXING_ENABLE_QRCODE, "rMQR Code") \ + X(DataMatrix, 'd', ' ', "mrwG ", 71, ZXING_ENABLE_DATAMATRIX, "Data Matrix") \ + X(MaxiCode, 'U', ' ', "mr ", 57, ZXING_ENABLE_MAXICODE, "MaxiCode") \ + /* Add new formats here */ +// clang-format on + +#define ZX_BCF_ID(SYM, VAR) (((uint8_t)(SYM) << 0) | ((uint8_t)(VAR) << 8)) + +#ifdef __cplusplus + +#include +#include +#include +#include +#include +#include + +namespace ZXing { + +/** + * @brief Enumerates barcode formats known to this package. + * + * Some formats represent a symbology (e.g. EANUPC) with multiple variants (EAN13, EAN8, etc.), while others represent a specific + * symbology variant (e.g. MicroQRCode). Both can be used with ReaderOptions::formats() to select which formats to read/scan for, but + * only specific variants are returned by the library when reading barcodes. + * + * If the format name starts with "All", it is a pseudo-format representing a set of formats (e.g. AllLinear, AllGS1, etc.). These can + * be used for filtering and selection purposes, but are not returned by the library when reading barcodes and can not be used when + * creating barcodes. + */ +enum class BarcodeFormat : unsigned int +{ +#define ZX_(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) NAME = ZX_BCF_ID(SYM, VAR), + ZX_BCF_LIST(ZX_) +#undef ZX_ + /// @cond DEPRECATED + DataBarExpanded [[deprecated("Use DataBarExp instead")]] = DataBarExp, + DataBarLimited [[deprecated("Use DataBarLtd instead")]] = DataBarLtd, + LinearCodes [[deprecated("Use AllLinear instead")]] = AllLinear, + MatrixCodes [[deprecated("Use AllMatrix instead")]] = AllMatrix, + Any [[deprecated("Use All instead")]] = All, + /// @endcond +}; + +/// @cond INTERNAL +inline char SymbologyKey(const BarcodeFormat& format) +{ + return uint32_t(format) & 0xFF; +} + +inline char VariantKey(const BarcodeFormat& format) +{ + return (uint32_t(format) >> 8) & 0xFF; +} + +inline std::string IdStr(const BarcodeFormat& format) +{ + return {']', SymbologyKey(format), VariantKey(format)}; +} +/// @endcond + +/// Returns the symbology (base type) of the given barcode format (e.g. EAN/UPC for EAN13, EAN8, UPCA, etc.). +BarcodeFormat Symbology(BarcodeFormat format); + +/// Returns the human-readable name of the given barcode format. +std::string_view Name(BarcodeFormat format); + +/// Test if left hand side (e == element) is 'inside' right hand side (s == set) (e.g. MicroQRCode <= QRCode). +bool operator<=(BarcodeFormat e, BarcodeFormat s); + +/// Test if the two BarcodeFormats have a non-empty intersection (e.g. AllMatrix & QRCode). +bool operator&(BarcodeFormat a, BarcodeFormat b); + +template +using BarcodeFormatArray = std::array; + +constexpr BarcodeFormatArray<2> operator|(BarcodeFormat a, BarcodeFormat b) +{ + return {a, b}; +} + +template +constexpr BarcodeFormatArray operator|(BarcodeFormatArray bts, BarcodeFormat bt) +{ + BarcodeFormatArray ret{}; + for (std::size_t i = 0; i < N; ++i) + ret[i] = bts[i]; + ret[N] = bt; + return ret; +} + +template +constexpr bool operator&(BarcodeFormat lhs, const BarcodeFormatArray& rhs) +{ + return std::any_of(rhs.begin(), rhs.end(), [lhs](BarcodeFormat bt) { return lhs & bt; }); +} + +/// @brief Parse a string into a BarcodeFormat. '-', '_', '/' and ' ' are optional. +/// @throws std::invalid_parameter if the string can not be fully parsed. +BarcodeFormat BarcodeFormatFromString(std::string_view str); + +std::string ToString(BarcodeFormat format); + +/** + * @brief A small container representing a collection of BarcodeFormat values. + * + * BarcodeFormats encapsulates an ordered collection of BarcodeFormat values and + * provides convenient construction, iteration, and set-like operations. + * + * General behavior and invariants: + * - The internal representation is kept in a canonical form (sorted, no duplicates) + * using normalize(). + * - The API exposes read-only access and iteration. + */ +class BarcodeFormats +{ + std::vector formats_; + + void normalize(); + +public: + BarcodeFormats() = default; + BarcodeFormats(BarcodeFormat f) : formats_{f} {} + + /// @brief Constructs a collection from a compile-time array of BarcodeFormat values. + /// ```c++ + /// BarcodeFormats formats = BarcodeFormat::QRCode | BarcodeFormat::EAN13; + /// ``` + template + BarcodeFormats(BarcodeFormatArray formats) : formats_{formats.begin(), formats.end()} + { + normalize(); + } + + BarcodeFormats(std::vector&& formats) : formats_(std::move(formats)) { normalize(); } + + /// Constructs a collection from a textual representation (e.g. a comma-separated list of format identifiers). + explicit BarcodeFormats(std::string_view str); + + BarcodeFormats(const BarcodeFormats&) = default; + BarcodeFormats(BarcodeFormats&&) = default; + BarcodeFormats& operator=(const BarcodeFormats&) = default; + BarcodeFormats& operator=(BarcodeFormats&&) = default; + bool operator==(const BarcodeFormats& o) const noexcept { return formats_ == o.formats_; } + bool operator!=(const BarcodeFormats& o) const noexcept { return !(*this == o); } + // ~BarcodeFormats() = default; + + auto begin() const noexcept { return formats_.cbegin(); } + auto end() const noexcept { return formats_.cend(); } + + using value_type = typename std::vector::value_type; + using pointer = typename std::vector::pointer; + auto data() const noexcept { return formats_.data(); } + + bool empty() const noexcept { return formats_.empty(); } + int size() const noexcept { return static_cast(formats_.size()); } + + // BarcodeFormats&& operator|(BarcodeFormat bt) &&; + // BarcodeFormats&& operator|(const BarcodeFormats& other) &&; + + /// Returns a BarcodeFormats containing the intersection of this collection and other. + BarcodeFormats operator&(const BarcodeFormats& other); + + /// Returns a list of available/supported barcode formats, optionally filtered by the provided formats. + /// e.g. `BarcodeFormats::list(BarcodeFormat::AllReadable);` + static BarcodeFormats list(const BarcodeFormats& filter = {}); + + /// @cond DEPRECATED + [[deprecated]] inline int count() const noexcept { return size(); } + [[deprecated]] inline bool testFlag(BarcodeFormat format) const noexcept + { + return std::any_of(begin(), end(), [format](BarcodeFormat f) { return f & format; }); + } + [[deprecated]] inline bool testFlags(const BarcodeFormats& formats) const noexcept + { + return std::any_of(begin(), end(), [formats](BarcodeFormat fo) { + return std::any_of(formats.begin(), formats.end(), [fo](BarcodeFormat fi) { return fo & fi; }); + }); + } + /// @endcond +}; + +inline bool operator<=(BarcodeFormat lhs, const BarcodeFormats& rhs) +{ + return std::any_of(rhs.begin(), rhs.end(), [lhs](BarcodeFormat bf) { return lhs <= bf; }); +} + +inline bool operator&(BarcodeFormat lhs, const BarcodeFormats& rhs) +{ + return std::any_of(rhs.begin(), rhs.end(), [lhs](BarcodeFormat bf) { return lhs & bf; }); +} + +/// @brief Parses a string into a set of BarcodeFormats. +/// Separators can be (any combination of) '|' or ','. Input can be lower case and any of '-', '_', '/' or ' ' are optional, +/// e.g. "EAN-8 | qrcode, Itf" would be parsed into [EAN8, QRCode, ITF]. +/// @throws std::invalid_parameter if the string can not be fully parsed. +BarcodeFormats BarcodeFormatsFromString(std::string_view str); + +std::string ToString(const BarcodeFormats& formats); + +} // namespace ZXing + +#endif // __cplusplus diff --git a/tools/chatgui/zxing-cpp/core/src/BinaryBitmap.cpp b/tools/chatgui/zxing-cpp/core/src/BinaryBitmap.cpp new file mode 100644 index 00000000..533e229e --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BinaryBitmap.cpp @@ -0,0 +1,111 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "BinaryBitmap.h" + +#include "BitMatrix.h" + +namespace ZXing { + +struct BinaryBitmap::Cache +{ + std::shared_ptr matrix, transposed; +}; + +BitMatrix BinaryBitmap::binarize(const uint8_t threshold) const +{ + BitMatrix res(width(), height()); + + if (_buffer.pixStride() == 1 && _buffer.rowStride() == _buffer.width()) { + // Specialize for a packed buffer with pixStride 1 to support auto vectorization (16x speedup on AVX2) + auto dst = res.row(0).begin(); + for (auto src = _buffer.data(0, 0), end = _buffer.data(0, height()); src != end; ++src, ++dst) + *dst = (*src <= threshold) * BitMatrix::SET_V; + } else { + auto processLine = [&res, threshold](int y, const auto* src, const int stride) { + for (auto& dst : res.row(y)) { + dst = (*src <= threshold) * BitMatrix::SET_V; + src += stride; + } + }; + for (int y = 0; y < res.height(); ++y) { + auto src = _buffer.data(0, y) + GreenIndex(_buffer.format()); + // Specialize the inner loop for strides 1 and 4 to support auto vectorization + switch (_buffer.pixStride()) { + case 1: processLine(y, src, 1); break; + case 4: processLine(y, src, 4); break; + default: processLine(y, src, _buffer.pixStride()); break; + } + } + } + + return res; +} + +BinaryBitmap::BinaryBitmap(const ImageView& buffer) : _cache(new Cache), _buffer(buffer) {} + +BinaryBitmap::~BinaryBitmap() = default; + +const BitMatrix* BinaryBitmap::getBitMatrix(bool transposed) const +{ + if (!_cache->matrix) { + _cache->matrix = getBlackMatrix(); + } + if (transposed) { + if (!_cache->transposed) { + auto copy = std::make_shared(_cache->matrix->copy()); + copy->rotate90(); + _cache->transposed = std::move(copy); + } + return _cache->transposed.get(); + } + return _cache->matrix.get(); +} + +void BinaryBitmap::invert() +{ + if (_cache->matrix) + const_cast(_cache->matrix.get())->flipAll(); + + if (_cache->transposed) + const_cast(_cache->transposed.get())->flipAll(); + + _inverted = !_inverted; +} + +template +void SumFilter(const BitMatrix& in, BitMatrix& out, F func) +{ + assert(in.height() >= 3); + + const auto* in0 = in.row(0).begin(); + const auto* in1 = in.row(1).begin(); + const auto* in2 = in.row(2).begin(); + + for (auto *out1 = out.row(1).begin() + 1, *end = out.row(out.height() - 1).begin() - 1; out1 != end; ++in0, ++in1, ++in2, ++out1) { + int sum = 0; + for (int j = 0; j < 3; ++j) + sum += in0[j] + in1[j] + in2[j]; + + *out1 = func(sum); + } +} + +void BinaryBitmap::close() +{ + if (_cache->matrix) { + auto& matrix = *const_cast(_cache->matrix.get()); + BitMatrix tmp(matrix.width(), matrix.height()); + + // dilate + SumFilter(matrix, tmp, [](int sum) { return (sum > 0 * BitMatrix::SET_V) * BitMatrix::SET_V; }); + // erode + SumFilter(tmp, matrix, [](int sum) { return (sum == 9 * BitMatrix::SET_V) * BitMatrix::SET_V; }); + } + _cache->transposed.reset(); + _closed = true; +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BinaryBitmap.h b/tools/chatgui/zxing-cpp/core/src/BinaryBitmap.h new file mode 100644 index 00000000..e8889c3b --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BinaryBitmap.h @@ -0,0 +1,66 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2021 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ImageView.h" + +#include +#include +#include + +namespace ZXing { + +class BitMatrix; + +using PatternRow = std::vector; + +/** +* This class is the core bitmap class used by ZXing to represent 1 bit data. Reader objects +* accept a BinaryBitmap and attempt to decode it. +*/ +class BinaryBitmap +{ + struct Cache; + std::unique_ptr _cache; + bool _inverted = false; + bool _closed = false; + +protected: + const ImageView _buffer; + + /** + * Converts a 2D array of luminance data to 1 bit (true means black). + * + * @return The 2D array of bits for the image, nullptr on error. + */ + virtual std::shared_ptr getBlackMatrix() const = 0; + + BitMatrix binarize(uint8_t threshold) const; + +public: + BinaryBitmap(const ImageView& buffer); + virtual ~BinaryBitmap(); + + int width() const { return _buffer.width(); } + int height() const { return _buffer.height(); } + + /** + * Converts one row of luminance data to a vector of ints denoting the widths of the bars and spaces. + */ + virtual bool getPatternRow(int row, int rotation, PatternRow& res) const = 0; + + const BitMatrix* getBitMatrix(bool transposed = false) const; + + void invert(); + bool inverted() const { return _inverted; } + + void close(); + bool closed() const { return _closed; } +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BitArray.cpp b/tools/chatgui/zxing-cpp/core/src/BitArray.cpp new file mode 100644 index 00000000..78a6487f --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BitArray.cpp @@ -0,0 +1,38 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "BitArray.h" + +#include "ByteArray.h" + +#include +#include + +namespace ZXing { + +void +BitArray::bitwiseXOR(const BitArray& other) +{ + if (size() != other.size()) { + throw std::invalid_argument("BitArray::xor(): Sizes don't match"); + } + for (size_t i = 0; i < _bits.size(); i++) { + // The last int could be incomplete (i.e. not have 32 bits in + // it) but there is no problem since 0 XOR 0 == 0. + _bits[i] ^= other._bits[i]; + } +} + +ByteArray BitArray::toBytes(int bitOffset, int numBytes) const +{ + ByteArray res(numBytes == -1 ? (size() - bitOffset + 7) / 8 : numBytes); + for (int i = 0; i < Size(res); i++) + for (int j = 0; j < 8; j++) + AppendBit(res[i], (numBytes != -1 || bitOffset < size()) ? get(bitOffset++) : false); + return res; +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BitArray.h b/tools/chatgui/zxing-cpp/core/src/BitArray.h new file mode 100644 index 00000000..38bbe075 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BitArray.h @@ -0,0 +1,201 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2017 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Range.h" +#include "ZXAlgorithms.h" + +#include +#include +#include +#include +#include +#include + +namespace ZXing { + +class ByteArray; + +/** +* A simple, fast array of bits. +*/ +class BitArray +{ + std::vector _bits; + + friend class BitMatrix; + + // Nothing wrong to support it, just to make it explicit, instead of by mistake. + // Use copy() below. + BitArray(const BitArray &) = default; + +public: + + using Iterator = std::vector::const_iterator; + + BitArray() = default; + + explicit BitArray(int size) : _bits(size, 0) {} + + BitArray(BitArray&& other) noexcept = default; + BitArray& operator=(BitArray&& other) noexcept = default; + BitArray& operator=(const BitArray &) = delete; + + BitArray copy() const { return *this; } + + int size() const noexcept { return Size(_bits); } + + int sizeInBytes() const noexcept { return (size() + 7) / 8; } + + bool get(int i) const { return _bits.at(i) != 0; } + void set(int i, bool val) { _bits.at(i) = val; } + + // If you know exactly how may bits you are going to iterate + // and that you access bit in sequence, iterator is faster than get(). + // However, be extremely careful since there is no check whatsoever. + // (Performance is the reason for the iterator to exist in the first place.) + Iterator iterAt(int i) const noexcept { return {_bits.cbegin() + i}; } + Iterator begin() const noexcept { return _bits.cbegin(); } + Iterator end() const noexcept { return _bits.cend(); } + + /** + * Appends the least-significant bits, from value, in order from most-significant to + * least-significant. For example, appending 6 bits from 0x000001E will append the bits + * 0, 1, 1, 1, 1, 0 in that order. + * + * @param value {@code int} containing bits to append + * @param numBits bits from value to append + */ + void appendBits(int value, int numBits) + { + assert(numBits >= 1 && numBits <= 32); + + for (; numBits; --numBits) + _bits.push_back((value >> (numBits-1)) & 1); + } + + void appendBits(uint32_t value, int numBits) + { + appendBits(static_cast(value), numBits); + } + + void appendBit(bool bit) { _bits.push_back(bit); } + + void appendBitArray(const BitArray& other) { _bits.insert(_bits.end(), other.begin(), other.end()); } + + /** + * Reverses all bits in the array. + */ + void reverse() { std::reverse(_bits.begin(), _bits.end()); } + + void bitwiseXOR(const BitArray& other); + + /** + * @param bitOffset first bit to extract + * @param numBytes how many bytes to extract (-1 == until the end, padded with '0') + * @return Bytes are written most-significant bit first. + */ + ByteArray toBytes(int bitOffset = 0, int numBytes = -1) const; + + using Range = ZXing::Range; + Range range() const { return {begin(), end()}; } + + friend bool operator==(const BitArray& a, const BitArray& b) { return a._bits == b._bits; } +}; + +template>> +constexpr T& AppendBit(T& val, bool bit) +{ + return (val <<= 1) |= static_cast(bit); +} + +template >> +constexpr int ToInt(const ARRAY& a) +{ + assert(Reduce(a) <= 32); + + int pattern = 0; + for (int i = 0; i < Size(a); i++) { + if (a[i] < 0) // negative shift is undefined behavior + return -1; + pattern = (pattern << a[i]) | ~(0xffffffff << a[i]) * (~i & 1); + } + return pattern; +} + +template >> +T ToInt(const BitArray& bits, int pos = 0, int count = 8 * sizeof(T)) +{ + assert(0 <= count && count <= 8 * (int)sizeof(T)); + assert(0 <= pos && pos + count <= bits.size()); + + count = std::min(count, bits.size()); + int res = 0; + auto it = bits.iterAt(pos); + for (int i = 0; i < count; ++i, ++it) + AppendBit(res, *it); + + return res; +} + +template >> +std::vector ToInts(const BitArray& bits, int wordSize, int totalWords, int offset = 0) +{ + assert(totalWords >= bits.size() / wordSize); + assert(wordSize <= 8 * (int)sizeof(T)); + + std::vector res(totalWords, 0); + for (int i = offset; i < bits.size(); i += wordSize) + res[(i - offset) / wordSize] = ToInt(bits, i, wordSize); + + return res; +} + +class BitArrayView +{ + const BitArray& bits; + BitArray::Iterator cur; + +public: + BitArrayView(const BitArray& bits) : bits(bits), cur(bits.begin()) {} + + BitArrayView& skipBits(int n) + { + if (cur + n > bits.end()) + throw std::out_of_range("BitArrayView::skipBits() out of range."); + cur += n; + return *this; + } + + int peekBits(int n) const + { + assert(n <= 32); + if (cur + n > bits.end()) + throw std::out_of_range("BitArrayView::peekBits() out of range."); + int res = 0; + for (auto i = cur; n > 0; --n, i++) + AppendBit(res, *i); + return res; + } + + int readBits(int n) + { + int res = peekBits(n); + cur += n; + return res; + } + + int size() const + { + return narrow_cast(bits.end() - cur); + } + + explicit operator bool() const { return size(); } +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BitMatrix.cpp b/tools/chatgui/zxing-cpp/core/src/BitMatrix.cpp new file mode 100644 index 00000000..f03cde74 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BitMatrix.cpp @@ -0,0 +1,175 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "BitMatrix.h" + +#include "Pattern.h" + +#include +#include +#include + +namespace ZXing { + +void +BitMatrix::setRegion(int left, int top, int width, int height) +{ + if (top < 0 || left < 0) { + throw std::invalid_argument("BitMatrix::setRegion(): Left and top must be nonnegative"); + } + if (height < 1 || width < 1) { + throw std::invalid_argument("BitMatrix::setRegion(): Height and width must be at least 1"); + } + int right = left + width; + int bottom = top + height; + if (bottom > _height || right > _width) { + throw std::invalid_argument("BitMatrix::setRegion(): The region must fit inside the matrix"); + } + for (int y = top; y < bottom; y++) { + auto offset = y * _width; + for (int x = left; x < right; x++) { + _bits[offset + x] = SET_V; + } + } +} + +void +BitMatrix::rotate90() +{ + BitMatrix result(height(), width()); + for (int x = 0; x < width(); ++x) { + for (int y = 0; y < height(); ++y) { + if (get(x, y)) { + result.set(y, width() - x - 1); + } + } + } + *this = std::move(result); +} + +void +BitMatrix::rotate180() +{ + std::reverse(_bits.begin(), _bits.end()); +} + +void +BitMatrix::mirror() +{ + for (int x = 0; x < _width; x++) { + for (int y = x + 1; y < _height; y++) { + if (get(x, y) != get(y, x)) { + flip(y, x); + flip(x, y); + } + } + } +} + +bool +BitMatrix::findBoundingBox(int &left, int& top, int& width, int& height, int minSize) const +{ + int right, bottom; + if (!getTopLeftOnBit(left, top) || !getBottomRightOnBit(right, bottom) || bottom - top + 1 < minSize) + return false; + + for (int y = top; y <= bottom; y++ ) { + for (int x = 0; x < left; ++x) + if (get(x, y)) { + left = x; + break; + } + for (int x = _width-1; x > right; x--) + if (get(x, y)) { + right = x; + break; + } + } + + width = right - left + 1; + height = bottom - top + 1; + return width >= minSize && height >= minSize; +} + +static auto isSet = [](auto v) { return bool(v); }; + +bool +BitMatrix::getTopLeftOnBit(int& left, int& top) const +{ + int bitsOffset = (int)std::distance(_bits.begin(), std::find_if(_bits.begin(), _bits.end(), isSet)); + if (bitsOffset == Size(_bits)) { + return false; + } + top = bitsOffset / _width; + left = (bitsOffset % _width); + return true; +} + +bool +BitMatrix::getBottomRightOnBit(int& right, int& bottom) const +{ + int bitsOffset = Size(_bits) - 1 - (int)std::distance(_bits.rbegin(), std::find_if(_bits.rbegin(), _bits.rend(), isSet)); + if (bitsOffset < 0) { + return false; + } + + bottom = bitsOffset / _width; + right = (bitsOffset % _width); + return true; +} + +void GetPatternRow(const BitMatrix& matrix, int r, std::vector& pr, bool transpose) +{ + if (transpose) + GetPatternRow(matrix.col(r), pr); + else + GetPatternRow(matrix.row(r), pr); +} + +BitMatrix Inflate(BitMatrix&& input, int width, int height, int quietZone) +{ + const int codeWidth = input.width(); + const int codeHeight = input.height(); + const int outputWidth = std::max(width, codeWidth + 2 * quietZone); + const int outputHeight = std::max(height, codeHeight + 2 * quietZone); + + if (input.width() == outputWidth && input.height() == outputHeight) + return std::move(input); + + const int scale = std::min((outputWidth - 2*quietZone) / codeWidth, (outputHeight - 2*quietZone) / codeHeight); + // Padding includes both the quiet zone and the extra white pixels to + // accommodate the requested dimensions. + const int leftPadding = (outputWidth - (codeWidth * scale)) / 2; + const int topPadding = (outputHeight - (codeHeight * scale)) / 2; + + BitMatrix result(outputWidth, outputHeight); + + for (int inputY = 0, outputY = topPadding; inputY < input.height(); ++inputY, outputY += scale) { + for (int inputX = 0, outputX = leftPadding; inputX < input.width(); ++inputX, outputX += scale) { + if (input.get(inputX, inputY)) + result.setRegion(outputX, outputY, scale, scale); + } + } + + return result; +} + +BitMatrix Deflate(const BitMatrix& input, int width, int height, float top, float left, float subSampling) +{ + BitMatrix result(width, height); + + for (int y = 0; y < result.height(); y++) { + auto yOffset = top + y * subSampling; + for (int x = 0; x < result.width(); x++) { + if (input.get(PointF(left + x * subSampling, yOffset))) + result.set(x, y); + } + } + + return result; +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BitMatrix.h b/tools/chatgui/zxing-cpp/core/src/BitMatrix.h new file mode 100644 index 00000000..ceb110a0 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BitMatrix.h @@ -0,0 +1,199 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Matrix.h" +#include "Point.h" + +#ifdef ZXING_INTERNAL +#include "Range.h" +#endif + +#include +#include +#include + +namespace ZXing { + +class BitArray; + +/** + * @brief A simple, fast 2D array of bits. + */ +class BitMatrix +{ + int _width = 0; + int _height = 0; + using data_t = uint8_t; + + std::vector _bits; + // There is nothing wrong to support this but disable to make it explicit since we may copy something very big here. + // Use copy() below. + BitMatrix(const BitMatrix&) = default; + + const data_t& get(int i) const + { +#if 1 + return _bits.at(i); +#else + return _bits[i]; +#endif + } + + data_t& get(int i) { return const_cast(static_cast(this)->get(i)); } + + bool getTopLeftOnBit(int &left, int& top) const; + bool getBottomRightOnBit(int &right, int& bottom) const; + +public: + static constexpr data_t SET_V = 0xff; // allows playing with SIMD binarization + static constexpr data_t UNSET_V = 0; + static_assert(bool(SET_V) && !bool(UNSET_V), "SET_V needs to evaluate to true, UNSET_V to false, see iterator usage"); + + BitMatrix() = default; + +#if defined(__llvm__) || (defined(__GNUC__) && (__GNUC__ > 7)) + __attribute__((no_sanitize("signed-integer-overflow"))) +#endif + BitMatrix(int width, int height) : _width(width), _height(height), _bits(width * height, UNSET_V) + { + if (width != 0 && int(_bits.size()) / width != height) + throw std::invalid_argument("Invalid size: width * height is too big"); + } + + explicit BitMatrix(int dimension) : BitMatrix(dimension, dimension) {} // Construct a square matrix. + + BitMatrix(BitMatrix&& other) noexcept = default; + BitMatrix& operator=(BitMatrix&& other) noexcept = default; + BitMatrix& operator=(const BitMatrix&) = delete; + + BitMatrix copy() const { return *this; } + +#ifdef ZXING_INTERNAL + Range row(int y) { return {_bits.data() + y * _width, _bits.data() + (y + 1) * _width}; } + Range row(int y) const { return {_bits.data() + y * _width, _bits.data() + (y + 1) * _width}; } + + Range> col(int x) const + { + return {{_bits.data() + x + (_height - 1) * _width, -_width}, {_bits.data() + x - _width, -_width}}; + } +#endif + + bool get(int x, int y) const { return get(y * _width + x); } + void set(int x, int y, bool val = true) { get(y * _width + x) = val * SET_V; } + + /** + *

Flips the given bit.

+ * + * @param x The horizontal component (i.e. which column) + * @param y The vertical component (i.e. which row) + */ + void flip(int x, int y) + { + auto& v = get(y * _width + x); + v = !v; + } + + void flipAll() + { + for (auto& i : _bits) + i = !i * SET_V; + } + + /** + *

Sets a square region of the bit matrix to true.

+ * + * @param left The horizontal position to begin at (inclusive) + * @param top The vertical position to begin at (inclusive) + * @param width The width of the region + * @param height The height of the region + */ + void setRegion(int left, int top, int width, int height); + + void rotate90(); + + void rotate180(); + + void mirror(); + + /** + * Find the rectangle that contains all non-white pixels. Useful for detection of 'pure' barcodes. + * + * @return True iff this rectangle is at least minWidth x minHeight pixels big + */ + bool findBoundingBox(int &left, int& top, int& width, int& height, int minSize = 1) const; + + int width() const { return _width; } + + int height() const { return _height; } + + bool empty() const { return _bits.empty(); } + + friend bool operator==(const BitMatrix& a, const BitMatrix& b) + { + return a._width == b._width && a._height == b._height && a._bits == b._bits; + } + + template + bool isIn(PointT p, int b = 0) const noexcept + { + return b <= p.x && p.x < width() - b && b <= p.y && p.y < height() - b; + } + + bool get(PointI p) const { return get(p.x, p.y); } + bool get(PointF p) const { return get(PointI(p)); } + void set(PointI p, bool v = true) { set(p.x, p.y, v); } + void set(PointF p, bool v = true) { set(PointI(p), v); } +}; + +void GetPatternRow(const BitMatrix& matrix, int r, std::vector& pr, bool transpose); + +/** + * @brief Inflate scales a BitMatrix up and adds a quiet Zone plus padding + * @param input matrix to be expanded + * @param width new width in bits (pixel) + * @param height new height in bits (pixel) + * @param quietZone size of quiet zone to add in modules + * @return expanded BitMatrix, maybe move(input) if size did not change + */ +BitMatrix Inflate(BitMatrix&& input, int width, int height, int quietZone); + +/** + * @brief Deflate (crop + subsample) a bit matrix + * @param input matrix to be shrunk + * @param width new width + * @param height new height + * @param top cropping starts at top row + * @param left cropping starts at left col + * @param subSampling typically the module size + * @return deflated input + */ +BitMatrix Deflate(const BitMatrix& input, int width, int height, float top, float left, float subSampling); + +template +BitMatrix ToBitMatrix(const Matrix& in, T trueValue = {true}) +{ + BitMatrix out(in.width(), in.height()); + for (int y = 0; y < in.height(); ++y) + for (int x = 0; x < in.width(); ++x) + if (in.get(x, y) == trueValue) + out.set(x, y); + return out; +} + +template +Matrix ToMatrix(const BitMatrix& in, T black = 0, T white = ~0) +{ + Matrix res(in.width(), in.height()); + for (int y = 0; y < in.height(); ++y) + for (int x = 0; x < in.width(); ++x) + res.set(x, y, in.get(x, y) ? black : white); + return res; +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BitMatrixCursor.h b/tools/chatgui/zxing-cpp/core/src/BitMatrixCursor.h new file mode 100644 index 00000000..89a1f3e4 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BitMatrixCursor.h @@ -0,0 +1,245 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BitMatrix.h" +#include "Point.h" +#include "StdGenerator.h" + +#include + +namespace ZXing { + +enum class Direction { LEFT = -1, RIGHT = 1 }; + +inline Direction opposite(Direction dir) noexcept +{ + return dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT; +} + +/** + * @brief The BitMatrixCursor represents a current position inside an image and current direction it can advance towards. + * + * The current position and direction is a PointT. So depending on the type it can be used to traverse the image + * in a Bresenham style (PointF) or in a discrete way (step only horizontal/vertical/diagonal (PointI)). + */ +template +class BitMatrixCursor +{ + using this_t = BitMatrixCursor; + +public: + const BitMatrix* img; + + POINT p; // current position + POINT d; // current direction + + BitMatrixCursor(const BitMatrix& image, POINT p, POINT d) : img(&image), p(p) { setDirection(d); } + + class Value + { + enum { INVALID = -1, WHITE = 0, BLACK = 1 }; + int v = INVALID; + public: + Value() = default; + Value(bool isBlack) : v(isBlack) {} + bool isValid() const noexcept { return v != INVALID; } + bool isWhite() const noexcept { return v == WHITE; } + bool isBlack() const noexcept { return v == BLACK; } + + operator bool() const noexcept { return isValid(); } + + bool operator==(Value o) const { return v == o.v; } + }; + + template + Value testAt(PointT p) const + { + return img->isIn(p) ? Value{img->get(p)} : Value{}; + } + + bool blackAt(POINT pos) const noexcept { return testAt(pos).isBlack(); } + bool whiteAt(POINT pos) const noexcept { return testAt(pos).isWhite(); } + + bool isIn(POINT p) const noexcept { return img->isIn(p); } + bool isIn() const noexcept { return isIn(p); } + bool isBlack() const noexcept { return blackAt(p); } + bool isWhite() const noexcept { return whiteAt(p); } + + POINT front() const noexcept { return d; } + POINT back() const noexcept { return {-d.x, -d.y}; } + POINT left() const noexcept { return {d.y, -d.x}; } + POINT right() const noexcept { return {-d.y, d.x}; } + POINT direction(Direction dir) const noexcept { return static_cast(dir) * right(); } + + void turnBack() noexcept { d = back(); } + void turnLeft() noexcept { d = left(); } + void turnRight() noexcept { d = right(); } + void turn(Direction dir) noexcept { d = direction(dir); } + + Value edgeAt(POINT d) const noexcept + { + Value v = testAt(p); + return testAt(p + d) != v ? v : Value(); + } + + Value edgeAtFront() const noexcept { return edgeAt(front()); } + Value edgeAtBack() const noexcept { return edgeAt(back()); } + Value edgeAtLeft() const noexcept { return edgeAt(left()); } + Value edgeAtRight() const noexcept { return edgeAt(right()); } + Value edgeAt(Direction dir) const noexcept { return edgeAt(direction(dir)); } + + this_t& setDirection(PointF dir) { return d = bresenhamDirection(dir), *this; } + this_t& setDirection(PointI dir) { return d = dir, *this; } + + bool step(typename POINT::value_t s = 1) + { + p += s * d; + return isIn(p); + } + + this_t movedBy(POINT o) const noexcept { return {*img, p + o, d}; } + this_t turnedBack() const noexcept { return {*img, p, back()}; } + + /** + * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge. + * @param nth number of edges to pass + * @param range max number of steps to take + * @param backup whether or not to backup one step so we land in front of the edge + * @return number of steps taken or 0 if moved outside of range/image + */ + int stepToEdge(int nth = 1, int range = 0, bool backup = false) + { + int steps = 0; + auto lv = testAt(p); + + while (nth && (!range || steps < range) && lv.isValid()) { + ++steps; + auto v = testAt(p + steps * d); + if (lv != v) { + lv = v; + --nth; + } + } + if (backup) + --steps; + p += steps * d; + return steps * (nth == 0); + } + + bool stepAlongEdge(Direction dir, bool skipCorner = false) + { + if (!edgeAt(dir)) + turn(dir); + else if (edgeAtFront()) { + turn(opposite(dir)); + if (edgeAtFront()) { + turn(opposite(dir)); + if (edgeAtFront()) + return false; + } + } + + bool ret = step(); + + if (ret && skipCorner && !edgeAt(dir)) { + turn(dir); + ret = step(); + } + + return ret; + } + + int countEdges(int range) + { + int res = 0; + + while (int steps = range ? stepToEdge(1, range) : 0) { + range -= steps; + ++res; + } + + return res; + } + + template + ARRAY readPattern(int range = 0) + { + ARRAY res = {}; + for (auto& i : res) { + i = stepToEdge(1, range); + if (!i) + return res; + if (range) + range -= i; + } + return res; + } + + template + ARRAY readPatternFromBlack(int maxWhitePrefix, int range = 0) + { + if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix)) + return {}; + return readPattern(range); + } +}; + +using BitMatrixCursorF = BitMatrixCursor; +using BitMatrixCursorI = BitMatrixCursor; + +class FastEdgeToEdgeCounter +{ + const uint8_t* p = nullptr; + int stride = 0; + int stepsToBorder = 0; + +public: + FastEdgeToEdgeCounter(const BitMatrixCursorI& cur) + { + stride = cur.d.y * cur.img->width() + cur.d.x; + p = cur.img->row(cur.p.y).begin() + cur.p.x; + + int maxStepsX = cur.d.x ? (cur.d.x > 0 ? cur.img->width() - 1 - cur.p.x : cur.p.x) : INT_MAX; + int maxStepsY = cur.d.y ? (cur.d.y > 0 ? cur.img->height() - 1 - cur.p.y : cur.p.y) : INT_MAX; + stepsToBorder = std::min(maxStepsX, maxStepsY); + } + + int stepToNextEdge(int range) + { + int maxSteps = std::min(stepsToBorder, range); + int steps = 0; + do { + if (++steps > maxSteps) { + if (maxSteps == stepsToBorder) + break; + else + return 0; + } + } while (p[steps * stride] == p[0]); + + p += steps * stride; + stepsToBorder -= steps; + + return steps; + } +}; + +// Generate points in a spiral pattern around the center +inline std::generator Spiral(int radius) +{ + co_yield{0, 0}; + for (int r = 1; r <= radius; ++r) { + // clang-format off + for (int k = 0; k < 2 * r; ++k) co_yield{ r, -(r - 1) + k}; // right -> down + for (int k = 0; k < 2 * r; ++k) co_yield{ (r - 1) - k, r}; // bottom -> left + for (int k = 0; k < 2 * r; ++k) co_yield{ -r, (r - 1) - k}; // left -> up + for (int k = 0; k < 2 * r; ++k) co_yield{-(r - 1) + k, -r}; // top -> right + // clang-format on + } +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BitMatrixIO.cpp b/tools/chatgui/zxing-cpp/core/src/BitMatrixIO.cpp new file mode 100644 index 00000000..9d5d72fe --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BitMatrixIO.cpp @@ -0,0 +1,101 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2017 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "BitMatrixIO.h" + +#include +#include +#include + +namespace ZXing { + +std::string ToString(const BitMatrix& matrix, char one, char zero, bool addSpace, bool printAsCString) +{ + std::string result; + result.reserve((addSpace ? 2 : 1) * (matrix.width() * matrix.height()) + matrix.height()); + for (int y = 0; y < matrix.height(); ++y) { + if (printAsCString) + result += '"'; + for (auto bit : matrix.row(y)) { + result += bit ? one : zero; + if (addSpace) + result += ' '; + } + if (printAsCString) + result += "\\n\""; + result += '\n'; + } + return result; +} + +std::string ToString(const BitMatrix& matrix, bool inverted) +{ + constexpr auto map = std::array{" ", "▀", "▄", "█"}; + std::string res; + + for (int y = 0; y < matrix.height(); y += 2) { + for (int x = 0; x < matrix.width(); ++x) { + int tp = matrix.get(x, y) ^ inverted; + int bt = (matrix.height() == 1 && tp) || (y + 1 < matrix.height() && (matrix.get(x, y + 1) ^ inverted)); + res += map[tp | (bt << 1)]; + } + res.push_back('\n'); + } + + return res; +} + +std::string ToSVG(const BitMatrix& matrix) +{ + // see https://stackoverflow.com/questions/10789059/create-qr-code-in-vector-image/60638350#60638350 + + const int width = matrix.width(); + const int height = matrix.height(); + std::ostringstream out; + + out << R"()" << "\n" + << R"()" + << "\n\n"; + + return out.str(); +} + +BitMatrix ParseBitMatrix(const std::string& str, char one, bool expectSpace) +{ + auto lineLength = str.find('\n'); + if (lineLength == std::string::npos) + return {}; + + int strStride = expectSpace ? 2 : 1; + int height = narrow_cast(str.length() / (lineLength + 1)); + int width = narrow_cast(lineLength / strStride); + BitMatrix mat(width, height); + for (int y = 0; y < height; ++y) { + size_t offset = y * (lineLength + 1); + for (int x = 0; x < width; ++x, offset += strStride) { + if (str[offset] == one) + mat.set(x, y); + } + } + return mat; +} + +void SaveAsPBM(const BitMatrix& matrix, const std::string& filename, int quietZone) +{ + auto out = ToMatrix(Inflate(matrix.copy(), 0, 0, quietZone)); + std::ofstream file(filename); + file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file.write(reinterpret_cast(out.data()), out.size()); +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BitMatrixIO.h b/tools/chatgui/zxing-cpp/core/src/BitMatrixIO.h new file mode 100644 index 00000000..c44e105a --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BitMatrixIO.h @@ -0,0 +1,21 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +* Copyright 2017 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "BitMatrix.h" + +namespace ZXing { + +std::string ToString(const BitMatrix& matrix, bool inverted = false); +std::string ToString(const BitMatrix& matrix, char one, char zero = ' ', bool addSpace = true, bool printAsCString = false); +std::string ToSVG(const BitMatrix& matrix); +BitMatrix ParseBitMatrix(const std::string& str, char one = 'X', bool expectSpace = true); +void SaveAsPBM(const BitMatrix& matrix, const std::string& filename, int quietZone = 0); + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BitSource.cpp b/tools/chatgui/zxing-cpp/core/src/BitSource.cpp new file mode 100644 index 00000000..ae496569 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BitSource.cpp @@ -0,0 +1,77 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "BitSource.h" + +#include "ByteArray.h" +#include "ZXAlgorithms.h" + +#include + +namespace ZXing { + +int +BitSource::available() const +{ + return 8 * (Size(_bytes) - _byteOffset) - _bitOffset; +} + +static int ReadBitsImpl(int numBits, const ByteArray& _bytes, int available, int& _byteOffset, int& _bitOffset) +{ + if (numBits < 1 || numBits > 32 || numBits > available) { + throw std::out_of_range("BitSource::readBits: out of range"); + } + + int result = 0; + + // First, read remainder from current byte + if (_bitOffset > 0) { + int bitsLeft = 8 - _bitOffset; + int toRead = numBits < bitsLeft ? numBits : bitsLeft; + int bitsToNotRead = bitsLeft - toRead; + int mask = (0xFF >> (8 - toRead)) << bitsToNotRead; + result = (_bytes[_byteOffset] & mask) >> bitsToNotRead; + numBits -= toRead; + _bitOffset += toRead; + if (_bitOffset == 8) { + _bitOffset = 0; + _byteOffset++; + } + } + + // Next read whole bytes + if (numBits > 0) { + while (numBits >= 8) { + result = (result << 8) | _bytes[_byteOffset]; + _byteOffset++; + numBits -= 8; + } + + // Finally read a partial byte + if (numBits > 0) { + int bitsToNotRead = 8 - numBits; + int mask = (0xFF >> bitsToNotRead) << bitsToNotRead; + result = (result << numBits) | ((_bytes[_byteOffset] & mask) >> bitsToNotRead); + _bitOffset += numBits; + } + } + + return result; +} + +int BitSource::readBits(int numBits) +{ + return ReadBitsImpl(numBits, _bytes, available(), _byteOffset, _bitOffset); +} + +int BitSource::peekBits(int numBits) const +{ + int bitOffset = _bitOffset; + int byteOffset = _byteOffset; + return ReadBitsImpl(numBits, _bytes, available(), byteOffset, bitOffset); +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/BitSource.h b/tools/chatgui/zxing-cpp/core/src/BitSource.h new file mode 100644 index 00000000..a78a758f --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/BitSource.h @@ -0,0 +1,72 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace ZXing { + +class ByteArray; + +/** +*

This provides an easy abstraction to read bits at a time from a sequence of bytes, where the +* number of bits read is not often a multiple of 8.

+* +*

This class is thread-safe but not reentrant -- unless the caller modifies the bytes array +* it passed in, in which case all bets are off.

+* +* @author Sean Owen +*/ +class BitSource +{ + const ByteArray& _bytes; + int _byteOffset = 0; + int _bitOffset = 0; + +public: + /** + * @param bytes bytes from which this will read bits. Bits will be read from the first byte first. + * Bits are read within a byte from most-significant to least-significant bit. + * IMPORTANT: Bit source DOES NOT copy data byte, thus make sure that the bytes outlive the bit source object. + */ + explicit BitSource(const ByteArray& bytes) : _bytes(bytes) {} + + BitSource(BitSource &) = delete; + BitSource& operator=(const BitSource &) = delete; + + /** + * @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}. + */ + int bitOffset() const { + return _bitOffset; + } + + /** + * @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}. + */ + int byteOffset() const { + return _byteOffset; + } + + /** + * @param numBits number of bits to read + * @return int representing the bits read. The bits will appear as the least-significant bits of the int + */ + int readBits(int numBits); + + /** + * @param numBits number of bits to peek at + * @return int representing the bits peeked at. The bits will appear as the least-significant + * bits of the int + */ + int peekBits(int numBits) const; + + /** + * @return number of bits that can be read successfully + */ + int available() const; +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ByteArray.h b/tools/chatgui/zxing-cpp/core/src/ByteArray.h new file mode 100644 index 00000000..3906da04 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ByteArray.h @@ -0,0 +1,42 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Range.h" + +#include +#include +#include + +namespace ZXing { + +/** + ByteArray is an extension of std::vector. +*/ +class ByteArray : public std::vector +{ +public: + ByteArray() = default; + ByteArray(std::initializer_list list) : std::vector(list) {} + explicit ByteArray(int len) : std::vector(len, 0) {} + explicit ByteArray(std::string_view str) : std::vector(str.begin(), str.end()) {} + + void append(ByteView other) { insert(end(), other.begin(), other.end()); } + void append(std::string_view other) { insert(end(), other.begin(), other.end()); } + + std::string_view asString(size_t pos = 0, size_t len = std::string_view::npos) const + { + return std::string_view(reinterpret_cast(data()), size()).substr(pos, len); + } + + ByteView asView(size_t pos = 0, size_t len = size_t(-1)) const + { + return ByteView(*this).subview(pos, len); + } +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/CharacterSet.cpp b/tools/chatgui/zxing-cpp/core/src/CharacterSet.cpp new file mode 100644 index 00000000..00e5f24f --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/CharacterSet.cpp @@ -0,0 +1,78 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "CharacterSet.h" + +#include "ZXAlgorithms.h" + +#include + +namespace ZXing { + +struct CharacterSetName +{ + std::string_view name; + CharacterSet cs; +}; + +static CharacterSetName NAME_TO_CHARSET[] = { + {"Cp437", CharacterSet::Cp437}, + {"ISO-8859-1", CharacterSet::ISO8859_1}, + {"ISO-8859-2", CharacterSet::ISO8859_2}, + {"ISO-8859-3", CharacterSet::ISO8859_3}, + {"ISO-8859-4", CharacterSet::ISO8859_4}, + {"ISO-8859-5", CharacterSet::ISO8859_5}, + {"ISO-8859-6", CharacterSet::ISO8859_6}, + {"ISO-8859-7", CharacterSet::ISO8859_7}, + {"ISO-8859-8", CharacterSet::ISO8859_8}, + {"ISO-8859-9", CharacterSet::ISO8859_9}, + {"ISO-8859-10", CharacterSet::ISO8859_10}, + {"ISO-8859-11", CharacterSet::ISO8859_11}, + {"ISO-8859-13", CharacterSet::ISO8859_13}, + {"ISO-8859-14", CharacterSet::ISO8859_14}, + {"ISO-8859-15", CharacterSet::ISO8859_15}, + {"ISO-8859-16", CharacterSet::ISO8859_16}, + {"SJIS", CharacterSet::Shift_JIS}, + {"Shift_JIS", CharacterSet::Shift_JIS}, + {"Cp1250", CharacterSet::Cp1250}, + {"windows-1250",CharacterSet::Cp1250}, + {"Cp1251", CharacterSet::Cp1251}, + {"windows-1251",CharacterSet::Cp1251}, + {"Cp1252", CharacterSet::Cp1252}, + {"windows-1252",CharacterSet::Cp1252}, + {"Cp1256", CharacterSet::Cp1256}, + {"windows-1256",CharacterSet::Cp1256}, + {"UTF-16BE", CharacterSet::UTF16BE}, + {"UTF-16LE", CharacterSet::UTF16LE}, + {"UTF-32BE", CharacterSet::UTF32BE}, + {"UTF-32LE", CharacterSet::UTF32LE}, + {"UnicodeBigUnmarked", CharacterSet::UTF16BE}, + {"UnicodeBig", CharacterSet::UTF16BE}, + {"UTF-8", CharacterSet::UTF8}, + {"ASCII", CharacterSet::ASCII}, + {"US-ASCII", CharacterSet::ASCII}, + {"Big5", CharacterSet::Big5}, + {"GB2312", CharacterSet::GB2312}, + {"GB18030", CharacterSet::GB18030}, + {"EUC-CN", CharacterSet::GB18030}, + {"GBK", CharacterSet::GB18030}, + {"EUC-KR", CharacterSet::EUC_KR}, + {"BINARY", CharacterSet::BINARY}, +}; + +CharacterSet CharacterSetFromString(std::string_view name) +{ + auto i = FindIf(NAME_TO_CHARSET, [name](auto& v) { return IsEqualIgnoreCaseAnd(v.name, name, "_- "); }); + return i == std::end(NAME_TO_CHARSET) ? CharacterSet::Unknown : i->cs; +} + +std::string ToString(CharacterSet cs) +{ + auto i = FindIf(NAME_TO_CHARSET, [cs](auto& v) { return v.cs == cs; }); + return i == std::end(NAME_TO_CHARSET) ? "" : std::string(i->name); +} + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/CharacterSet.h b/tools/chatgui/zxing-cpp/core/src/CharacterSet.h new file mode 100644 index 00000000..0a291a80 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/CharacterSet.h @@ -0,0 +1,58 @@ +/* +* Copyright 2016 Nu-book Inc. +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace ZXing { + +enum class CharacterSet : unsigned char +{ + Unknown, + ASCII, + ISO8859_1, + ISO8859_2, + ISO8859_3, + ISO8859_4, + ISO8859_5, + ISO8859_6, + ISO8859_7, + ISO8859_8, + ISO8859_9, + ISO8859_10, + ISO8859_11, + ISO8859_13, + ISO8859_14, + ISO8859_15, + ISO8859_16, + Cp437, + Cp1250, + Cp1251, + Cp1252, + Cp1256, + + Shift_JIS, + Big5, + GB2312, + GB18030, + EUC_JP, + EUC_KR, + UTF16BE, + UTF8, + UTF16LE, + UTF32BE, + UTF32LE, + + BINARY, + + CharsetCount +}; + +CharacterSet CharacterSetFromString(std::string_view name); +std::string ToString(CharacterSet cs); + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ConcentricFinder.cpp b/tools/chatgui/zxing-cpp/core/src/ConcentricFinder.cpp new file mode 100644 index 00000000..75f55908 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ConcentricFinder.cpp @@ -0,0 +1,270 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "ConcentricFinder.h" + +#include "LogMatrix.h" +#include "RegressionLine.h" +#include "ZXAlgorithms.h" + +namespace ZXing { + +std::optional AverageEdgePixels(BitMatrixCursorI cur, int range, int numOfEdges) +{ + PointF sum = {}; + for (int i = 0; i < numOfEdges; ++i) { + if (!cur.isIn()) + return {}; + cur.stepToEdge(1, range); + sum += centered(cur.p) + centered(cur.p + cur.back()); + log(cur.p + cur.back(), 2); + } + return sum / (2 * numOfEdges); +} + +std::optional CenterOfDoubleCross(const BitMatrix& image, PointI center, int range, int numOfEdges) +{ + PointF sum = {}; + for (auto d : {PointI{0, 1}, {1, 0}, {1, 1}, {1, -1}}) { + auto avr1 = AverageEdgePixels({image, center, d}, range, numOfEdges); + auto avr2 = AverageEdgePixels({image, center, -d}, range, numOfEdges); + if (!avr1 || !avr2) + return {}; + sum += *avr1 + *avr2; + } + return sum / 8; +} + +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; + bool inner = nth < 0; + nth = std::abs(nth); + log(center, 3); + BitMatrixCursorI cur(image, center, {1, 0}); + if (!cur.stepToEdge(nth, radius, inner)) + return {}; + cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup + const auto edgeDir = inner ? Direction::LEFT : Direction::RIGHT; + + uint32_t neighbourMask = 0; + auto start = cur.p; + PointF sum = {}; + int n = 0; + do { + log(cur.p, 4); + sum += centered(cur.p); + ++n; + + // find out if we come full circle around the center. 8 bits have to be set in the end. + neighbourMask |= (1 << (4 + dot(bresenhamDirection(cur.p - center), PointI(1, 3)))); + + if (!cur.stepAlongEdge(edgeDir)) + return {}; + + // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate + if (maxAbsComponent(cur.p - center) > radius || center == cur.p || n > 4 * 2 * range) + return {}; + } while (cur.p != start); + + if (requireCircle && neighbourMask != 0b111101111) + return {}; + + return sum / n; +} + +std::optional CenterOfRings(const BitMatrix& image, PointF center, int range, int numOfRings) +{ + int n = 1; + PointF sum = center; + for (int i = 2; i < numOfRings + 1; ++i) { + auto c = CenterOfRing(image, PointI(center), range, i); + if (!c) { + if (n == 1) + return {}; + else + return sum / n; + } else if (distance(*c, center) > range / numOfRings / 2) { + return {}; + } + + sum += *c; + n++; + } + return sum / n; +} + +static std::vector CollectRingPoints(const BitMatrix& image, PointF center, int range, int edgeIndex, bool backup) +{ + PointI centerI(center); + int radius = range; + BitMatrixCursorI cur(image, centerI, {1, 0}); + if (!cur.stepToEdge(edgeIndex, radius, backup)) + return {}; + cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup + const auto edgeDir = backup ? Direction::LEFT : Direction::RIGHT; + + uint32_t neighbourMask = 0; + auto start = cur.p; + std::vector points; + points.reserve(4 * range); + + do { + log(cur.p, 4); + points.push_back(centered(cur.p)); + + // find out if we come full circle around the center. 8 bits have to be set in the end. + neighbourMask |= (1 << (4 + dot(bresenhamDirection(cur.p - centerI), PointI(1, 3)))); + + if (!cur.stepAlongEdge(edgeDir)) + return {}; + + // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate + if (maxAbsComponent(cur.p - centerI) > radius || centerI == cur.p || Size(points) > 4 * 2 * range) + return {}; + + } while (cur.p != start); + + if (neighbourMask != 0b111101111) + return {}; + + return points; +} + +static std::optional FitQadrilateralToPoints(PointF center, std::vector& points) +{ + auto dist2Center = [c = center](auto a, auto b) { return distance(a, c) < distance(b, c); }; + auto [minDistElem, maxDistElem] = std::minmax_element(points.begin(), points.end(), dist2Center); + + // check if points are on a circle: for a square the min/max ratio is 0.7, for a circle it is 1 + if (distance(center, *minDistElem) / distance(center, *maxDistElem) > 0.85) + return {}; + + // rotate points such that the first one is the furthest away from the center (hence, a corner) + std::rotate(points.begin(), maxDistElem, points.end()); + + std::array corners; + corners[0] = &points[0]; + // find the opposite corner by looking for the farthest point near the opposite point + corners[2] = std::max_element(&points[Size(points) * 3 / 8], &points[Size(points) * 5 / 8], dist2Center); + + // find the two in between corners by looking for the points farthest from the long diagonal + auto dist2Diagonal = [l = RegressionLine(*corners[0], *corners[2])](auto a, auto b) { return l.distance(a) < l.distance(b); }; + corners[1] = std::max_element(&points[Size(points) * 1 / 8], &points[Size(points) * 3 / 8], dist2Diagonal); + corners[3] = std::max_element(&points[Size(points) * 5 / 8], &points[Size(points) * 7 / 8], dist2Diagonal); + + std::array lines{RegressionLine{corners[0] + 1, corners[1]}, RegressionLine{corners[1] + 1, corners[2]}, + RegressionLine{corners[2] + 1, corners[3]}, RegressionLine{corners[3] + 1, &points.back() + 1}}; + + if (std::any_of(lines.begin(), lines.end(), [](const auto& line) { return !line.isValid(); })) + return {}; + + std::array beg = {corners[0] + 1, corners[1] + 1, corners[2] + 1, corners[3] + 1}; + std::array end = {corners[1], corners[2], corners[3], &points.back() + 1}; + + // check if all points belonging to each line segment are sufficiently close to that line + for (int i = 0; i < 4; ++i) + for (const PointF* p = beg[i]; p != end[i]; ++p) { + auto len = std::distance(beg[i], end[i]); + if (len > 3 && lines[i].distance(*p) > std::max(1., std::min(8., len / 8.))) { +#ifdef PRINT_DEBUG + printf("%d: %.2f > %.2f @ %.fx%.f\n", i, lines[i].distance(*p), std::distance(beg[i], end[i]) / 1., p->x, p->y); +#endif + return {}; + } + } + + QuadrilateralF res; + for (int i = 0; i < 4; ++i) + res[i] = intersect(lines[i], lines[(i + 1) % 4]); + + return res; +} + +static bool QuadrilateralIsPlausibleSquare(const QuadrilateralF q, int lineIndex) +{ + double m, M; + m = M = distance(q[0], q[3]); + for (int i = 1; i < 4; ++i) + UpdateMinMax(m, M, distance(q[i - 1], q[i])); + + return m >= lineIndex * 2 && m > M / 3; +} + +std::optional FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup) +{ + auto points = CollectRingPoints(image, center, range, lineIndex, backup); + if (points.empty()) + return {}; + + auto res = FitQadrilateralToPoints(center, points); + if (!res || !QuadrilateralIsPlausibleSquare(*res, lineIndex - backup)) + return {}; + + return res; +} + +std::optional FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int ringIndex) +{ + auto innerCorners = FitSquareToPoints(image, center, range, ringIndex, false); + if (!innerCorners) + return {}; + + auto outerCorners = FitSquareToPoints(image, center, range, ringIndex + 1, true); + if (!outerCorners) + return {}; + + auto res = Blend(*innerCorners, *outerCorners); + + for (auto p : *innerCorners) + log(p, 3); + + for (auto p : *outerCorners) + log(p, 3); + + for (auto p : res) + log(p, 3); + + return res; +} + +std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize) +{ + // make sure we have at least one path of white around the center + if (auto res1 = CenterOfRing(image, PointI(center), range, 1); res1 && image.get(*res1)) { + // and then either at least one more ring around that + if (auto res2 = CenterOfRings(image, *res1, range, finderPatternSize / 2); res2 && image.get(*res2)) + return res2; + // or the center can be approximated by a square + if (FitSquareToPoints(image, *res1, range, 1, false)) + return res1; + // TODO: this is currently only keeping #258 alive, evaluate if still worth it + if (auto res2 = CenterOfDoubleCross(image, PointI(*res1), range, finderPatternSize / 2 + 1); res2 && image.get(*res2)) + return res2; + } + return {}; +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ConcentricFinder.h b/tools/chatgui/zxing-cpp/core/src/ConcentricFinder.h new file mode 100644 index 00000000..f8490e0c --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ConcentricFinder.h @@ -0,0 +1,150 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BitMatrixCursor.h" +#include "Pattern.h" +#include "Quadrilateral.h" +#include "ZXAlgorithms.h" + +#include + +namespace ZXing { + +template +static float CenterFromEnd(const std::array& pattern, float end) +{ + if (N == 5) { + float a = pattern[4] + pattern[3] + pattern[2] / 2.f; + float b = pattern[4] + (pattern[3] + pattern[2] + pattern[1]) / 2.f; + float c = (pattern[4] + pattern[3] + pattern[2] + pattern[1] + pattern[0]) / 2.f; + return end - (2 * a + b + c) / 4; + } else if (N == 3) { + float a = pattern[2] + pattern[1] / 2.f; + float b = (pattern[2] + pattern[1] + pattern[0]) / 2.f; + return end - (2 * a + b) / 3; + } else { // aztec + auto a = Reduce(pattern.begin() + (N/2 + 1), pattern.end(), pattern[N/2] / 2.f); + return end - a; + } +} + +template +std::optional> ReadSymmetricPattern(Cursor& cur, int range) +{ + static_assert(N % 2 == 1); + assert(range > 0); + Pattern res = {}; + auto constexpr s_2 = Size(res)/2; + auto cuo = cur.turnedBack(); + + auto next = [&](auto& cur, int i) { + auto v = cur.stepToEdge(1, range); + res[s_2 + i] += v; + if (range) + range -= v; + return v; + }; + + for (int i = 0; i <= s_2; ++i) { + if (!next(cur, i) || !next(cuo, -i)) + return {}; + } + res[s_2]--; // the starting pixel has been counted twice, fix this + + return res; +} + +template +int CheckSymmetricPattern(BitMatrixCursorI& cur, PATTERN pattern, int range, bool updatePosition) +{ + FastEdgeToEdgeCounter curFwd(cur), curBwd(cur.turnedBack()); + + int centerFwd = curFwd.stepToNextEdge(range); + if (!centerFwd) + return 0; + int centerBwd = curBwd.stepToNextEdge(range); + if (!centerBwd) + return 0; + + assert(range > 0); + Pattern res = {}; + auto constexpr s_2 = Size(res)/2; + res[s_2] = centerFwd + centerBwd - 1; // -1 because the starting pixel is counted twice + range -= res[s_2]; + + auto next = [&](auto& cur, int i) { + auto v = cur.stepToNextEdge(range); + res[s_2 + i] = v; + range -= v; + return v; + }; + + for (int i = 1; i <= s_2; ++i) { + if (!next(curFwd, i) || !next(curBwd, -i)) + return 0; + } + + if (!IsPattern(res, pattern)) + return 0; + + if (updatePosition) + cur.step(res[s_2] / 2 - (centerBwd - 1)); + + return Reduce(res); +} + +std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle = true); + +std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize); + +std::optional FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup); + +std::optional FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int ringIndex); + +struct ConcentricPattern : public PointF +{ + int size = 0; +}; + +template +std::optional LocateConcentricPattern(const BitMatrix& image, PATTERN pattern, PointF center, int range) +{ + auto cur = BitMatrixCursor(image, PointI(center), {}); + int minSpread = image.width(), maxSpread = 0; + // TODO: setting maxError to 1 can subtantially help with detecting symbols with low print quality resulting in damaged + // finder patterns, but it sutantially increases the runtime (approx. 20% slower for the falsepositive images). + int maxError = 0; + for (auto d : {PointI{0, 1}, {1, 0}}) { + int spread = CheckSymmetricPattern(cur.setDirection(d), pattern, range, true); + if (spread) + UpdateMinMax(minSpread, maxSpread, spread); + else if (--maxError < 0) + return {}; + } + +#if 1 + for (auto d : {PointI{1, 1}, {1, -1}}) { + int spread = CheckSymmetricPattern(cur.setDirection(d), pattern, range * 2, false); + if (spread) + UpdateMinMax(minSpread, maxSpread, spread); + else if (--maxError < 0) + return {}; + } +#endif + + if (maxSpread > 5 * minSpread) + return {}; + + auto newCenter = FinetuneConcentricPatternCenter(image, PointF(cur.p), range, pattern.size()); + if (!newCenter) + return {}; + + return ConcentricPattern{*newCenter, (maxSpread + minSpread) / 2}; +} + +} // ZXing + diff --git a/tools/chatgui/zxing-cpp/core/src/Content.cpp b/tools/chatgui/zxing-cpp/core/src/Content.cpp new file mode 100644 index 00000000..1d9ad518 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Content.cpp @@ -0,0 +1,471 @@ +/* + * Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "Content.h" + +#include "ByteArray.h" +#include "CharacterSet.h" +#include "ECI.h" +#include "HRI.h" +#include "TextDecoder.h" +#include "Utf.h" +#include "ZXAlgorithms.h" +#include "Version.h" + +#include + +namespace ZXing { + +std::string ToString(ContentType type) +{ + const char* t2s[] = {"Text", "Binary", "Mixed", "GS1", "ISO15434", "UnknownECI"}; + int idx = static_cast(type); + if (idx < 0 || idx >= Size(t2s)) + return "InvalidContentType"; + return t2s[idx]; +} + +template +void Content::ForEachECIBlock(FUNC func) const +{ + ECI defaultECI = hasECI ? ECI::ISO8859_1 : ECI::Unknown; + if (encodings.empty()) + func(defaultECI, 0, Size(bytes)); + else if (encodings.front().pos != 0) + func(defaultECI, 0, encodings.front().pos); + + for (int i = 0; i < Size(encodings); ++i) { + auto [eci, start] = encodings[i]; + int end = i + 1 == Size(encodings) ? Size(bytes) : encodings[i + 1].pos; + + if (start != end) + func(eci, start, end); + } +} + +void Content::switchEncoding(ECI eci, bool isECI) +{ + // remove all non-ECI entries on first ECI entry + if (isECI && !hasECI) + encodings.clear(); + if (isECI || !hasECI) + encodings.push_back({eci, Size(bytes)}); + + hasECI |= isECI; +} + +Content::Content(ByteArray&& bytes, SymbologyIdentifier si, CharacterSet defaultCharset) + : bytes(std::move(bytes)), symbology(si), defaultCharset(defaultCharset) {} + +void Content::switchEncoding(CharacterSet cs) +{ + switchEncoding(ToECI(cs), false); +} + +void Content::append(const Content& other) +{ + if (!hasECI && other.hasECI) + encodings.clear(); + if (other.hasECI || !hasECI) + for (auto& e : other.encodings) + encodings.push_back({e.eci, Size(bytes) + e.pos}); + append(other.bytes); + + hasECI |= other.hasECI; +} + +void Content::erase(int pos, int n) +{ + bytes.erase(bytes.begin() + pos, bytes.begin() + pos + n); + for (auto& e : encodings) + if (e.pos > pos) + e.pos -= n; +} + +void Content::insert(int pos, std::string_view str) +{ + bytes.insert(bytes.begin() + pos, str.begin(), str.end()); + for (auto& e : encodings) + if (e.pos > pos) + e.pos += Size(str); +} + +bool Content::canProcess() const +{ + return std::all_of(encodings.begin(), encodings.end(), [](Encoding e) { return CanProcess(e.eci); }); +} + +std::string Content::render(bool withECI) const +{ + if (empty() || !canProcess()) + return {}; + +#ifdef ZXING_READERS + std::string res; + res.reserve(bytes.size() * 2); + if (withECI) + res += symbology.toString(true); + ECI lastECI = ECI::Unknown; + auto fallbackCS = defaultCharset; + if (!hasECI && fallbackCS == CharacterSet::Unknown) + fallbackCS = guessEncoding(); + + ForEachECIBlock([&](ECI eci, int begin, int end) { + // basic idea: if IsText(eci), we transcode it to UTF8, otherwise we treat it as binary but + // transcoded it to valid UTF8 bytes sequences representing the code points 0-255. The eci we report + // back to the caller by inserting their "\XXXXXX" ECI designator is UTF8 for text and + // the original ECI for everything else. + // first determine how to decode the content (use fallback if unknown) + auto inEci = IsText(eci) ? eci : eci == ECI::Unknown ? ToECI(fallbackCS) : ECI::Binary; + if (withECI) { + // then find the eci to report back in the ECI designator + auto outEci = IsText(inEci) ? ECI::UTF8 : eci; + + if (lastECI != outEci) + res += ToString(outEci); + lastECI = outEci; + + for (auto c : BytesToUtf8(bytes.asView(begin, end - begin), inEci)) { + res += c; + if (c == '\\') // in the ECI protocol a '\' (0x5c) has to be doubled, works only because 0x5c can only mean `\` + res += c; + } + } else { + res += BytesToUtf8(bytes.asView(begin, end - begin), inEci); + } + }); + + return res; +#elif defined(ZXING_USE_ZINT) + assert(!utf8Cache.empty()); + if (!withECI) + return std::accumulate(utf8Cache.begin(), utf8Cache.end(), std::string()); + + std::string res; + res.reserve(3 + TransformReduce(utf8Cache, 0, std::size) * 2 + encodings.size() * 7); + res += symbology.toString(true); + + ECI lastECI = ECI::Unknown; + auto fallbackCS = defaultCharset; + if (!hasECI && fallbackCS == CharacterSet::Unknown) + fallbackCS = guessEncoding(); + + assert(utf8Cache.size() == encodings.size()); + for (int i = 0; i < Size(encodings); ++i) { + const auto eci = encodings[i].eci; + const auto inEci = IsText(eci) ? eci : eci == ECI::Unknown ? ToECI(fallbackCS) : ECI::Binary; + const auto outEci = IsText(inEci) ? ECI::UTF8 : eci; + + if (lastECI != outEci) + res += ToString(outEci); + lastECI = outEci; + + for (auto c : utf8Cache[i]) { + res += c; + if (c == '\\') // in the ECI protocol a '\' (0x5c) has to be doubled, works only because 0x5c can only mean `\` + res += c; + } + } + + return res; +#else + (void)withECI; + return std::string(bytes.asString()); +#endif +} + +std::string Content::text(TextMode mode) const +{ + switch (mode) { + case TextMode::Plain: return render(false); + case TextMode::ECI: return render(true); + case TextMode::HRI: + switch (type()) { +#if defined(ZXING_READERS) || defined(ZXING_USE_ZINT) + case ContentType::GS1: { + auto plain = render(false); + auto hri = HRIFromGS1(plain); + return hri.empty() ? EscapeNonGraphical(plain) : hri; + } + case ContentType::ISO15434: return HRIFromISO15434(render(false)); + case ContentType::Text: return render(false); +#endif + default: return text(TextMode::Escaped); + } + case TextMode::Escaped: return EscapeNonGraphical(render(false)); + case TextMode::Hex: return ToHex(bytes); + case TextMode::HexECI: return ToHex(bytesECI()); + } + + return {}; // silence compiler warning +} + +std::wstring Content::utfW() const +{ + return FromUtf8(render(false)); +} + +ByteArray Content::bytesECI() const +{ + if (empty()) + return {}; + + ByteArray res; + res.reserve(3 + bytes.size() + hasECI * encodings.size() * 7); + + // report ECI protocol only if actually found ECI data in the barode bit stream + // see also https://github.com/zxing-cpp/zxing-cpp/issues/936 + res.append(symbology.toString(hasECI)); + + if (hasECI) + ForEachECIBlock([&](ECI eci, int begin, int end) { + if (hasECI) + res.append(ToString(eci)); + + for (auto b : bytes.asView(begin, end - begin)) { + res.push_back(b); + if (b == '\\') // in the ECI protocol a '\' has to be doubled + res.push_back(b); + } + }); + else + res.append(bytes); + + return res; +} + +#if defined(ZXING_READERS) || defined(ZXING_USE_ZINT) +/** +* @param bytes bytes encoding a string, whose encoding should be guessed +* @return name of guessed encoding; at the moment will only guess one of: +* {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform +* default encoding if none of these can possibly be correct +*/ +CharacterSet GuessTextEncoding(ByteView bytes, CharacterSet fallback = CharacterSet::ISO8859_1) +{ + // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS, + // which should be by far the most common encodings. + bool canBeISO88591 = true; + bool canBeShiftJIS = true; + bool canBeUTF8 = IsValidUtf8(bytes); + int utf8BytesLeft = 0; + //int utf8LowChars = 0; + int utf2BytesChars = 0; + int utf3BytesChars = 0; + int utf4BytesChars = 0; + int sjisBytesLeft = 0; + //int sjisLowChars = 0; + int sjisKatakanaChars = 0; + //int sjisDoubleBytesChars = 0; + int sjisCurKatakanaWordLength = 0; + int sjisCurDoubleBytesWordLength = 0; + int sjisMaxKatakanaWordLength = 0; + int sjisMaxDoubleBytesWordLength = 0; + //int isoLowChars = 0; + //int isoHighChars = 0; + int isoHighOther = 0; + + bool utf8bom = bytes.size() > 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; + + for (int value : bytes) + { + if(!(canBeISO88591 || canBeShiftJIS || canBeUTF8)) + break; + + // UTF-8 stuff + if (canBeUTF8) { + if (utf8BytesLeft > 0) { + if ((value & 0x80) == 0) { + canBeUTF8 = false; + } + else { + utf8BytesLeft--; + } + } + else if ((value & 0x80) != 0) { + if ((value & 0x40) == 0) { + canBeUTF8 = false; + } + else { + utf8BytesLeft++; + if ((value & 0x20) == 0) { + utf2BytesChars++; + } + else { + utf8BytesLeft++; + if ((value & 0x10) == 0) { + utf3BytesChars++; + } + else { + utf8BytesLeft++; + if ((value & 0x08) == 0) { + utf4BytesChars++; + } + else { + canBeUTF8 = false; + } + } + } + } + } //else { + //utf8LowChars++; + //} + } + + // ISO-8859-1 stuff + if (canBeISO88591) { + if (value > 0x7F && value < 0xA0) { + canBeISO88591 = false; + } + else if (value > 0x9F) { + if (value < 0xC0 || value == 0xD7 || value == 0xF7) { + isoHighOther++; + } //else { + //isoHighChars++; + //} + } //else { + //isoLowChars++; + //} + } + + // Shift_JIS stuff + if (canBeShiftJIS) { + if (sjisBytesLeft > 0) { + if (value < 0x40 || value == 0x7F || value > 0xFC) { + canBeShiftJIS = false; + } + else { + sjisBytesLeft--; + } + } + else if (value == 0x80 || value == 0xA0 || value > 0xEF || (value < 0x20 && value != 0xa && value != 0xd)) { + canBeShiftJIS = false; // use non-printable ASCII as indication for binary content + } + else if (value > 0xA0 && value < 0xE0) { + sjisKatakanaChars++; + sjisCurDoubleBytesWordLength = 0; + sjisCurKatakanaWordLength++; + if (sjisCurKatakanaWordLength > sjisMaxKatakanaWordLength) { + sjisMaxKatakanaWordLength = sjisCurKatakanaWordLength; + } + } + else if (value > 0x7F) { + sjisBytesLeft++; + //sjisDoubleBytesChars++; + sjisCurKatakanaWordLength = 0; + sjisCurDoubleBytesWordLength++; + if (sjisCurDoubleBytesWordLength > sjisMaxDoubleBytesWordLength) { + sjisMaxDoubleBytesWordLength = sjisCurDoubleBytesWordLength; + } + } + else { + //sjisLowChars++; + sjisCurKatakanaWordLength = 0; + sjisCurDoubleBytesWordLength = 0; + } + } + } + + if (canBeUTF8 && utf8BytesLeft > 0) { + canBeUTF8 = false; + } + if (canBeShiftJIS && sjisBytesLeft > 0) { + canBeShiftJIS = false; + } + + // Easy -- if there is BOM or at least 1 valid not-single byte character (and no evidence it can't be UTF-8), done + if (canBeUTF8 && (utf8bom || utf2BytesChars + utf3BytesChars + utf4BytesChars > 0)) { + return CharacterSet::UTF8; + } + + bool assumeShiftJIS = fallback == CharacterSet::Shift_JIS || fallback == CharacterSet::EUC_JP; + // Easy -- if assuming Shift_JIS or at least 3 valid consecutive not-ascii characters (and no evidence it can't be), done + if (canBeShiftJIS && (assumeShiftJIS || sjisMaxKatakanaWordLength >= 3 || sjisMaxDoubleBytesWordLength >= 3)) { + return CharacterSet::Shift_JIS; + } + // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough for short words. The crude heuristic is: + // - If we saw + // - only two consecutive katakana chars in the whole text, or + // - at least 10% of bytes that could be "upper" not-alphanumeric Latin1, + // - then we conclude Shift_JIS, else ISO-8859-1 + if (canBeISO88591 && canBeShiftJIS) { + return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= Size(bytes) + ? CharacterSet::Shift_JIS : CharacterSet::ISO8859_1; + } + + // Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to default platform encoding + if (canBeISO88591) { + return CharacterSet::ISO8859_1; + } + if (canBeShiftJIS) { + return CharacterSet::Shift_JIS; + } + if (canBeUTF8) { + return CharacterSet::UTF8; + } + // Otherwise, we take a wild guess with platform encoding + return fallback; +} +#endif + +CharacterSet Content::guessEncoding() const +{ +#if defined(ZXING_READERS) || defined(ZXING_USE_ZINT) + // assemble all blocks with unknown encoding + ByteArray input; + ForEachECIBlock([&](ECI eci, int begin, int end) { + if (eci == ECI::Unknown) + input.append(bytes.asView(begin, end - begin)); + }); + + if (input.empty()) + return CharacterSet::Unknown; + + return GuessTextEncoding(input); +#else + return CharacterSet::ISO8859_1; +#endif +} + +ContentType Content::type() const +{ +#if 1 //def ZXING_READERS + if (empty()) + return ContentType::Text; + + if (!canProcess()) + return ContentType::UnknownECI; + + if (symbology.aiFlag == AIFlag::GS1) + return ContentType::GS1; + + // check for the absolute minimum of a ISO 15434 conforming message ("[)>" + RS + digit + digit) + if (bytes.size() > 6 && bytes.asString(0, 4) == "[)>\x1E" && IsDigit(bytes[4]) && IsDigit(bytes[5])) + return ContentType::ISO15434; + + ECI fallback = ToECI(guessEncoding()); + std::vector binaryECIs; + ForEachECIBlock([&](ECI eci, int begin, int end) { + if (eci == ECI::Unknown) + eci = fallback; + binaryECIs.push_back((!IsText(eci) + || (ToInt(eci) > 0 && ToInt(eci) < 28 && ToInt(eci) != 25 + && std::any_of(bytes.begin() + begin, bytes.begin() + end, + [](auto c) { return c < 0x20 && c != 0x9 && c != 0xa && c != 0xd; })))); + }); + + if (!Contains(binaryECIs, true)) + return ContentType::Text; + if (!Contains(binaryECIs, false)) + 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/tools/chatgui/zxing-cpp/core/src/Content.h b/tools/chatgui/zxing-cpp/core/src/Content.h new file mode 100644 index 00000000..e5a47642 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Content.h @@ -0,0 +1,82 @@ +/* +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ByteArray.h" +#include "CharacterSet.h" +#include "ContentType.h" +#include "ECI.h" +#include "ReaderOptions.h" +#include "SymbologyIdentifier.h" +#include "Version.h" +#include "ZXAlgorithms.h" + +#include +#include +#include + +namespace ZXing { + +class Content +{ + template + void ForEachECIBlock(FUNC f) const; + + void switchEncoding(ECI eci, bool isECI); + std::string render(bool withECI) const; + +public: + struct Encoding + { + ECI eci; + int pos; + }; + + ByteArray bytes; + std::vector encodings; +#if !defined(ZXING_READERS) && defined(ZXING_USE_ZINT) + std::vector utf8Cache; +#endif + SymbologyIdentifier symbology; + CharacterSet defaultCharset = CharacterSet::Unknown; + bool hasECI = false; + + Content() = default; + Content(ByteArray&& bytes, SymbologyIdentifier si, CharacterSet defaultCharset = CharacterSet::Unknown); + + // make movable but not copyable + Content(const Content& other) = delete; + Content& operator=(const Content& other) = delete; + Content(Content&& other) = default; + Content& operator=(Content&& other) = default; + + void switchEncoding(ECI eci) { switchEncoding(eci, true); } + void switchEncoding(CharacterSet cs); + + void reserve(int count) { bytes.reserve(bytes.size() + count); } + + void push_back(uint8_t val) { bytes.push_back(val); } + void push_back(int val) { bytes.push_back(narrow_cast(val)); } + void append(std::string_view str) { bytes.insert(bytes.end(), str.begin(), str.end()); } + void append(ByteView bv) { bytes.insert(bytes.end(), bv.begin(), bv.end()); } + void append(const Content& other); + + void erase(int pos, int n); + void insert(int pos, std::string_view str); + + bool empty() const { return bytes.empty(); } + bool canProcess() const; + + std::string text(TextMode mode) const; + std::wstring utfW() const; // utf16 or utf32 depending on the platform, i.e. on size_of(wchar_t) + std::string utf8() const { return render(false); } + + ByteArray bytesECI() const; + CharacterSet guessEncoding() const; + ContentType type() const; +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ContentType.h b/tools/chatgui/zxing-cpp/core/src/ContentType.h new file mode 100644 index 00000000..a5739dca --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ContentType.h @@ -0,0 +1,19 @@ +/* +* Copyright 2026 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace ZXing { + +/** + * @brief Provides a hint to the type of content encoded in a barcode, such as text, binary data, etc. + */ +enum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }; + +std::string ToString(ContentType type); + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/CreateBarcode.cpp b/tools/chatgui/zxing-cpp/core/src/CreateBarcode.cpp new file mode 100644 index 00000000..c3e442b3 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/CreateBarcode.cpp @@ -0,0 +1,490 @@ +/* +* Copyright 2024 Axel Waggershauser +* Copyright 2025 gitlost +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "CreateBarcode.h" + +#include "BarcodeData.h" +#include "BitMatrix.h" +#include "ByteArray.h" +#include "DecoderResult.h" +#include "DetectorResult.h" +#include "JSON.h" +#include "Version.h" +#include "ZXAlgorithms.h" + +#ifdef ZXING_READERS +#include "ReadBarcode.h" +#endif + +#ifdef ZXING_USE_ZINT +#include "ECI.h" +#include "TextEncoder.h" +#include +#else +#include "MultiFormatWriter.h" +#endif // ZXING_USE_ZINT + +#include +#include + +using namespace std::literals; + +namespace ZXing { + +struct CreatorOptions::Data +{ + BarcodeFormat format; + std::string options; + + // symbol size (qrcode, datamatrix, etc), map from I, 'WxH' + // structured_append (idx, cnt, ID) + + mutable unique_zint_symbol zint; + +#ifndef __cpp_aggregate_paren_init // make XCode 14.x happy + Data(BarcodeFormat format, std::string options) : format(format), options(std::move(options)) {} +#endif +}; + +// TODO: check return type +#define ZX_PROPERTY(TYPE, NAME) \ + const TYPE& CreatorOptions::NAME() const noexcept { return d->NAME; } \ + CreatorOptions& CreatorOptions::NAME(TYPE v)& { return d->NAME = std::move(v), *this; } \ + CreatorOptions&& CreatorOptions::NAME(TYPE v)&& { return d->NAME = std::move(v), std::move(*this); } + +ZX_PROPERTY(BarcodeFormat, format) +ZX_PROPERTY(std::string, options) + +#undef ZX_PROPERTY + +#define ZX_RO_PROPERTY(TYPE, NAME) \ + std::optional CreatorOptions::NAME() const noexcept { return JsonGet(d->options, #NAME); } + +ZX_RO_PROPERTY(std::string, ecLevel); +ZX_RO_PROPERTY(std::string, eci); +ZX_RO_PROPERTY(bool, gs1); +ZX_RO_PROPERTY(bool, readerInit); +ZX_RO_PROPERTY(bool, forceSquare); +ZX_RO_PROPERTY(int, columns); +ZX_RO_PROPERTY(int, rows); +ZX_RO_PROPERTY(int, version); +ZX_RO_PROPERTY(int, dataMask); + +#undef ZX_RO_PROPERTY + +CreatorOptions::CreatorOptions(BarcodeFormat format, std::string options) : d(std::make_unique(format, std::move(options))) {} +CreatorOptions::~CreatorOptions() = default; +CreatorOptions::CreatorOptions(CreatorOptions&&) noexcept = default; +CreatorOptions& CreatorOptions::operator=(CreatorOptions&&) noexcept = default; + +inline bool IsAscii(ByteView bv) +{ + return std::all_of(bv.begin(), bv.end(), [](uint8_t c) { return c <= 127; }); +} + +#ifdef ZXING_WRITERS +#ifdef ZXING_USE_ZINT + +struct String2Int +{ + const char* str; + int val; +}; + +static int ParseECLevel(int symbology, std::string_view s) +{ + constexpr std::string_view EC_LABELS_QR[4] = {"L", "M", "Q", "H"}; + int res = 0; + + // Convert L/M/Q/H to Zint 1-4 + if (Contains({BARCODE_QRCODE, BARCODE_MICROQR, BARCODE_RMQR}, symbology)) + if ((res = IndexOf(EC_LABELS_QR, s)) != -1) + return res + 1; + + if (std::from_chars(s.data(), s.data() + s.size() - (s.back() == '%'), res).ec != std::errc{}) + throw std::invalid_argument(StrCat("Invalid ecLevel: '", s, "'")); + + auto findClosestECLevel = [](const std::vector& list, int val) { + int mIdx = -2, mAbs = 100; + for (int i = 0; i < Size(list); ++i) + if (int abs = std::abs(val - list[i]); abs < mAbs) { + mIdx = i; + mAbs = abs; + } + return mIdx + 1; + }; + + // Convert percentage to Zint + if (s.back() == '%') { + switch (symbology) { + case BARCODE_QRCODE: return findClosestECLevel({20, 37, 55, 65}, res); + case BARCODE_MICROQR: return findClosestECLevel({20, 37, 55}, res); + case BARCODE_RMQR: return res <= 46 ? 2 : 4; + case BARCODE_AZTEC: return findClosestECLevel({10, 23, 36, 50}, res); + case BARCODE_PDF417: + // TODO: do something sensible with PDF417? + default: + return -1; + } + } + + return res; +}; + +static constexpr struct { BarcodeFormat format; SymbologyIdentifier si; } barcodeFormat2SymbologyIdentifier[] = { + {BarcodeFormat::Aztec, {'z', '0', 3}}, // '1' GS1, '2' AIM + {BarcodeFormat::Codabar, {'F', '0'}}, // if checksum processing were implemented and checksum present and stripped then modifier would be 4 + // {BarcodeFormat::CodablockF, {'O', '4'}}, // '5' GS1 + {BarcodeFormat::Code128, {'C', '0'}}, // '1' GS1, '2' AIM + // {BarcodeFormat::Code16K, {'K', '0'}}, // '1' GS1, '2' AIM, '4' D1 PAD + // {BarcodeFormat::Code39, {'A', '0'}}, // '3' checksum, '4' extended, '7' checksum,extended + {BarcodeFormat::Code39Ext, {'A', '4'}}, // '3' checksum, '4' extended, '7' checksum,extended + {BarcodeFormat::DataBar, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataBarOmni, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataBarStk, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataBarStkOmni, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataBarExp, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataBarExpStk, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataBarLtd, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataMatrix, {'d', '1', 3}}, // '2' GS1, '3' AIM + // {BarcodeFormat::DotCode, {'J', '0', 3}}, // '1' GS1, '2' AIM + {BarcodeFormat::DXFilmEdge, {'X', 'F'}}, + {BarcodeFormat::EAN8, {'E', '4'}}, + // {BarcodeFormat::HanXin, {'h', '0', 1}}, // '2' GS1 + // {BarcodeFormat::ITF, {'I', '0'}}, // '1' check digit + {BarcodeFormat::MaxiCode, {'U', '0', 2}}, // '1' mode 2 or 3 + {BarcodeFormat::MicroPDF417, {'L', '2', -1}}, + {BarcodeFormat::MicroQRCode, {'Q', '1', 1}}, + {BarcodeFormat::PDF417, {'L', '2', -1}}, + {BarcodeFormat::QRCode, {'Q', '1', 1}}, // '3' GS1, '5' AIM + {BarcodeFormat::RMQRCode, {'Q', '1', 1}}, // '3' GS1, '5' AIM +}; + +static SymbologyIdentifier SymbologyIdentifierZint2ZXing(const CreatorOptions& opts, const ByteArray& ba) +{ + using enum BarcodeFormat; + + const BarcodeFormat format = opts.format(); + + auto i = FindIf(barcodeFormat2SymbologyIdentifier, [format](auto& v) { return v.format == format; }); + SymbologyIdentifier ret = i != std::end(barcodeFormat2SymbologyIdentifier) ? i->si : SymbologyIdentifier{SymbologyKey(format), '0'}; + + if (format & (EAN13 | UPCA | UPCE)) { + if (ba.size() > 13) // Have EAN-2/5 add-on? + ret.modifier = '3'; // Combined packet, EAN-13, UPC-A, UPC-E, with add-on + } else if (opts.gs1() && format & AllGS1) { + if (format & (Aztec | Code128)) + ret.modifier = '1'; + else if (format == DataMatrix) + ret.modifier = '2'; + else if (format & (QRCode | RMQRCode)) + ret.modifier = '3'; + ret.aiFlag = AIFlag::GS1; + } + + return ret; +} + +static std::string ECLevelZint2ZXing(const zint_symbol* zint) +{ + constexpr char EC_LABELS_QR[4] = {'L', 'M', 'Q', 'H'}; + + const int symbology = zint->symbology; + const int option_1 = zint->option_1; + + switch (symbology) { + case BARCODE_AZTEC: + if ((option_1 >> 8) >= 0 && (option_1 >> 8) <= 99) + return std::to_string(option_1 >> 8) + "%"; + break; + case BARCODE_MAXICODE: + // Mode + if (option_1 >= 2 && option_1 <= 6) + return std::to_string(option_1); + break; + case BARCODE_PDF417: + case BARCODE_PDF417COMP: + // Convert to percentage + if (option_1 >= 0 && option_1 <= 8) { + int overhead = symbology == BARCODE_PDF417COMP ? 35 : 69; + int cols = (zint->width - overhead) / 17; + int tot_cws = zint->rows * cols; + assert(tot_cws); + return std::to_string((2 << option_1) * 100 / tot_cws) + "%"; + } + break; + // case BARCODE_MICROPDF417: + // if ((option_1 >> 8) >= 0 && (option_1 >> 8) <= 99) + // return std::to_string(option_1 >> 8) + "%"; + // break; + case BARCODE_QRCODE: + case BARCODE_MICROQR: + case BARCODE_RMQR: + // Convert to L/M/Q/H + if (option_1 >= 1 && option_1 <= 4) + return {EC_LABELS_QR[option_1 - 1]}; + break; + // case BARCODE_HANXIN: + // if (option_1 >= 1 && option_1 <= 4) + // return "L" + std::to_string(option_1); + // break; + default: + break; + } + + return {}; +} + +#ifndef ZXING_READERS +// Convert bytes to UTF-8 code points 0-255 +static std::string BinaryToUtf8(ByteView ba) +{ + std::string utf8; + utf8.reserve(ba.size() * 2); + for (auto c : ba) + if (c < 0x80) { + utf8.push_back(c); + } else if (c < 0xC0) { + utf8.push_back(0xC2); + utf8.push_back(c); + } else { + utf8.push_back(0xC3); + utf8.push_back(c - 0x40); + } + return utf8; +} +#endif + +zint_symbol* CreatorOptions::zint() const +{ + using enum BarcodeFormat; + + auto& zint = d->zint; + + if (!zint) { +#ifdef PRINT_DEBUG +// printf("zint version: %d, sizeof(zint_symbol): %ld, options: %s\n", ZBarcode_Version(), sizeof(zint_symbol), options().c_str()); +#endif + zint.reset(ZBarcode_Create()); + + switch (format()) { +#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \ + case BarcodeFormat(ZX_BCF_ID(SYM, VAR)): zint->symbology = ZINT; break; + ZX_BCF_LIST(X) +#undef X + }; + + if (zint->symbology == 0) + throw std::invalid_argument(StrCat("Unsupported barcode format for creation: ", ToString(format()))); + + if (format() == Code128 && gs1()) + zint->symbology = BARCODE_GS1_128; + + zint->scale = 0.5f; + + if (auto val = ecLevel(); val) + zint->option_1 = ParseECLevel(zint->symbology, *val); + + if (auto val = version(); val && !(format() & AllLinear)) + zint->option_2 = *val; + + if (auto val = columns(); val && format() & (DataBarExpStk | PDF417 | MicroPDF417 | CompactPDF417)) + zint->option_2 = *val; + + if (auto val = rows(); val && format() & (DataBarExpStk | PDF417)) + zint->option_3 = *val; + + if (auto val = dataMask(); val && format() & (QRCode | MicroQRCode)) + zint->option_3 = (zint->option_3 & 0xFF) | (*val + 1) << 8; + + if (format() == DataMatrix) + zint->option_3 = (forceSquare() ? DM_SQUARE : DM_DMRE) | DM_ISO_144; + } + + return zint.get(); +} + +#define CHECK_WARN(ZINT_CALL, WARN) \ + if (WARN = (ZINT_CALL); WARN >= ZINT_ERROR) \ + throw std::invalid_argument(StrCat(zint->errtxt, " (retval: ", std::to_string(WARN), ")")); + +Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& opts) +{ + if (!data || size < 1) + throw std::invalid_argument("Can not create a barcode from NULL or empty data"); + + auto zint = opts.zint(); + + zint->input_mode = mode == UNICODE_MODE && opts.gs1() && (opts.format() & BarcodeFormat::AllGS1) ? GS1_MODE : mode; + if (mode == UNICODE_MODE && static_cast(data)[0] != '[') + zint->input_mode |= GS1PARENS_MODE; + zint->output_options |= OUT_BUFFER_INTERMEDIATE | BARCODE_NO_QUIET_ZONES | BARCODE_CONTENT_SEGS; + if (opts.readerInit()) + zint->output_options |= READER_INIT; + + if (ZBarcode_Cap(zint->symbology, ZINT_CAP_ECI)) { + if (auto eci = opts.eci(); eci) { + if (auto cs = CharacterSetFromString(*eci); cs != CharacterSet::Unknown) { + zint->eci = static_cast(ToECI(cs)); + } else if (std::all_of(eci->begin(), eci->end(), IsDigit)) { + zint->eci = std::stoi(*eci); + } + } else if (mode == DATA_MODE) { + zint->eci = static_cast(ECI::Binary); + } + // the following would make sure that non-ASCII text is encoded as UTF-8, hence the GuessTextEncoding for + // non-ECI data can't produce a wrong result. + // else if (mode == UNICODE_MODE && !IsAscii({data, narrow_cast(size)})) + // zint->eci = static_cast(ECI::UTF8); + } + + if (opts.format() == BarcodeFormat::Telepen && (std::all_of((const char*)data, (const char*)data + size, IsDigit))) + zint->symbology = BARCODE_TELEPEN_NUM; + + int warning; + CHECK_WARN(ZBarcode_Encode_and_Buffer(zint, (uint8_t*)data, size, 0), warning); + +#ifdef PRINT_DEBUG + printf("create symbol with size: %dx%d\n", zint->width, zint->rows); +#endif + +#if 0 // use ReadBarcode to create Barcode object + auto buffer = std::vector(zint->bitmap_width * zint->bitmap_height); + std::transform(zint->bitmap, zint->bitmap + zint->bitmap_width * zint->bitmap_height, buffer.data(), + [](unsigned char v) { return (v == '0') * 0xff; }); + + auto res = ReadBarcode({buffer.data(), zint->bitmap_width, zint->bitmap_height, ImageFormat::Lum}, + ReaderOptions().formats(opts.format()).isPure(true).binarizer(Binarizer::BoolCast)); +#else + assert(zint->content_seg_count == 1); + const auto& content_seg = zint->content_segs[0]; + const size_t content_seg_len = + static_cast(content_seg.length - (opts.format() == BarcodeFormat::Code93 && content_seg.length >= 2 ? 2 : 0)); + + Content content; + + if (zint->eci || warning == ZINT_WARN_USES_ECI) + content.switchEncoding(ECI(content_seg.eci)); + else + content.switchEncoding(ToCharacterSet(ECI(content_seg.eci))); + + if ((zint->input_mode & 0x07) == UNICODE_MODE) { + // `content_segs` returned as UTF-8 + std::string utf8(reinterpret_cast(content_seg.source), content_seg_len); + content.append(TextEncoder::FromUnicode(utf8, ToCharacterSet(ECI(content_seg.eci)))); +#ifndef ZXING_READERS + content.utf8Cache.push_back(std::move(utf8)); +#endif + } else { + content.append({content_seg.source, content_seg_len}); +#ifndef ZXING_READERS + content.utf8Cache.push_back(BinaryToUtf8(content.bytes)); +#endif + } + + content.symbology = SymbologyIdentifierZint2ZXing(opts, content.bytes); + + DecoderResult decRes(std::move(content)); + decRes.setEcLevel(ECLevelZint2ZXing(zint)); + decRes.setReaderInit(zint->output_options & READER_INIT); + 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; }); + int left, top, width, height; + bits.findBoundingBox(left, top, width, height); + + auto res = MatrixBarcode(std::move(decRes), {std::move(bits), Rectangle(left, top, width, height)}, opts.format()); +#endif + + res.zint = std::move(opts.d->zint); + res.zintMutex = std::make_unique(); + + return res; +} + +Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& options) +{ + return CreateBarcode(contents.data(), contents.size(), UNICODE_MODE, options); +} + +Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& options) +{ + return CreateBarcode(contents.data(), contents.size(), UNICODE_MODE, options); +} + +Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& options) +{ + return CreateBarcode(data, size, DATA_MODE, options); +} + +#else // ZXING_USE_ZINT + +zint_symbol* CreatorOptions::zint() const { return nullptr; } + +static Barcode CreateBarcode(BitMatrix&& bits, std::string_view contents, const CreatorOptions& opts) +{ + Content content; + content.append(contents); + + DecoderResult decRes(std::move(content)); + DetectorResult detRes(std::move(bits), Rectangle(0, 0, bits.width(), bits.height())); + return MatrixBarcode(std::move(decRes), std::move(detRes), opts.format()); +} + +Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& opts) +{ + auto writer = MultiFormatWriter(opts.format()).setMargin(0); + if (auto ecLevel = opts.ecLevel(); ecLevel && ecLevel->size() == 1 && strchr("012345678", (*ecLevel)[0])) + writer.setEccLevel(std::stoi(*ecLevel)); + if (!IsAscii({(const uint8_t*)contents.data(), contents.size()})) + writer.setEncoding(CharacterSet::UTF8); // write UTF8 (ECI value 26) for maximum compatibility + + return CreateBarcode(writer.encode(std::string(contents), 0, opts.format() & BarcodeFormat::AllLinear ? 50 : 0), contents, opts); +} + +Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& opts) +{ + return CreateBarcodeFromText({reinterpret_cast(contents.data()), contents.size()}, opts); +} + +Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& opts) +{ + std::wstring bytes; + for (uint8_t c : ByteView(data, size)) + bytes.push_back(c); + + auto writer = MultiFormatWriter(opts.format()).setMargin(0); + if (auto ecLevel = opts.ecLevel(); ecLevel && ecLevel->size() == 1 && strchr("012345678", (*ecLevel)[0])) + writer.setEccLevel(std::stoi(*ecLevel)); + writer.setEncoding(CharacterSet::BINARY); + + return CreateBarcode(writer.encode(bytes, 0, opts.format() & BarcodeFormat::AllLinear ? 50 : 0), {(const char*)data, (size_t)size}, opts); +} + +#endif // ZXING_USE_ZINT + +#else // ZXING_WRITERS + +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."); +} + +Barcode CreateBarcodeFromText(std::u8string_view, const CreatorOptions&) +{ + throw std::runtime_error("This build of zxing-cpp does not support creating barcodes."); +} + +Barcode CreateBarcodeFromBytes(const void*, int, const CreatorOptions&) +{ + throw std::runtime_error("This build of zxing-cpp does not support creating barcodes."); +} + +#endif // ZXING_WRITERS + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/CreateBarcode.h b/tools/chatgui/zxing-cpp/core/src/CreateBarcode.h new file mode 100644 index 00000000..5d3d61c4 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/CreateBarcode.h @@ -0,0 +1,132 @@ +/* +* Copyright 2024 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Barcode.h" + +#include +#include +#include + +extern "C" struct zint_symbol; + +namespace ZXing { + +/** + * @brief Configuration options for barcode creation. + * + * This class encapsulates all the parameters needed to create a barcode with + * specific format and settings. + * + * The options property is a string that can contain multiple key-value pairs + * separated by commas. Each key-value pair configures a specific aspect of the + * barcode generation process and are dependent on the used BarcodeFormat. + * Boolean properties are interpreted as true if only the property name is present. + * Keys are case-insensitive. Passing a serialized JSON object is also supported. + * + * For a list of all supported options, see the list of read-only properties below. + * + * ```c++ + * // example of creating a QR code with 30% error correction level and GS1 mode enabled + * auto opts = CreatorOptions(BarcodeFormat::QRCode, "ecLevel=30%, gs1"); + * ``` + */ +class CreatorOptions +{ + struct Data; + + std::unique_ptr d; + + friend Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& options); + +public: + CreatorOptions(BarcodeFormat format, std::string options = {}); + ~CreatorOptions(); + CreatorOptions(CreatorOptions&&) noexcept; + CreatorOptions& operator=(CreatorOptions&&) noexcept; + + zint_symbol* zint() const; + +#define ZX_PROPERTY(TYPE, NAME) \ + const TYPE& NAME() const noexcept; \ + CreatorOptions& NAME(TYPE v)&; \ + CreatorOptions&& NAME(TYPE v)&&; + + ZX_PROPERTY(BarcodeFormat, format) + ZX_PROPERTY(std::string, options) + +#undef ZX_PROPERTY + +#define ZX_RO_PROPERTY(TYPE, NAME) \ + std::optional NAME() const noexcept; + + /// EcLevel, e.g. "30%", see also libzint docs for supported values per symbology + ZX_RO_PROPERTY(std::string, ecLevel); + + /// Specify ECI designator to use (e.g. "UTF-8" or "26" for UTF-8), see also libzint docs (most 2D symbologies) + ZX_RO_PROPERTY(std::string, eci); + + /// GS1 mode (most 2D symbologies + Code128) + ZX_RO_PROPERTY(bool, gs1); + + /// Set the "reader init" flag (most 2D symbologies) + ZX_RO_PROPERTY(bool, readerInit); + + /// DataMatrix: only consider square symbol versions + ZX_RO_PROPERTY(bool, forceSquare); + + /// Specify number of columns (e.g. for DataBarExpStk, PDF417) + ZX_RO_PROPERTY(int, columns); + + /// Specify number of rows (e.g. for DataBarExpStk, PDF417) + ZX_RO_PROPERTY(int, rows); + + /// Specify the version/size of the symbol (most 2D symbologies) + ZX_RO_PROPERTY(int, version); + + /// Specify dataMask to use (QRCode/MicroQRCode) + ZX_RO_PROPERTY(int, dataMask); + +#undef ZX_RO_PROPERTY +}; + +/** + * @brief Generate Barcode from unicode text + * + * @param contents UTF-8 string to encode into a barcode + * @param options CreatorOptions (including BarcodeFormat) + */ +Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& options); + +/** + * @brief 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) + */ +Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& options); + +#if defined(__cpp_lib_char8_t) +Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& options); +#endif + +#if defined(__cpp_lib_ranges) +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 + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/DecoderResult.h b/tools/chatgui/zxing-cpp/core/src/DecoderResult.h new file mode 100644 index 00000000..f7cb5d2d --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/DecoderResult.h @@ -0,0 +1,92 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Content.h" +#include "Error.h" +#include "JSON.h" +#include "StructuredAppend.h" + +#include +#include +#include + +namespace ZXing { + +struct CustomData +{ + virtual ~CustomData() = default; +}; + +class DecoderResult +{ + Content _content; + std::string _ecLevel; + int _lineCount = 0; + int _versionNumber = 0; + StructuredAppendInfo _structuredAppend; + bool _isMirrored = false; + bool _readerInit = false; + Error _error; + std::string _json; + std::shared_ptr _customData; + +public: + DecoderResult() = default; + DecoderResult(Error error) : _error(std::move(error)) {} + DecoderResult(Content&& bytes) : _content(std::move(bytes)) {} + + DecoderResult(DecoderResult&&) noexcept = default; + DecoderResult& operator=(DecoderResult&&) noexcept = default; + + DecoderResult(const DecoderResult&) = delete; + DecoderResult& operator=(const DecoderResult&) = delete; + + bool isValid(bool includeErrors = false) const + { + return (!_content.bytes.empty() && !_error) || (includeErrors && !!_error); + } + + const Content& content() const & { return _content; } + Content&& content() && { return std::move(_content); } + + // to keep the unit tests happy for now: + std::wstring text() const { return _content.utfW(); } + std::string symbologyIdentifier() const { return _content.symbology.toString(false); } + + // Simple macro to set up getter/setter methods that save lots of boilerplate. + // It sets up a standard 'const & () const', 2 setters for setting lvalues via + // copy and 2 for setting rvalues via move. They are provided each to work + // either on lvalues (normal 'void (...)') or on rvalues (returning '*this' as + // rvalue). The latter can be used to optionally initialize a temporary in a + // return statement, e.g. + // return DecoderResult(bytes, text).setEcLevel(level); +#define ZX_PROPERTY(TYPE, GETTER, SETTER) \ + const TYPE& GETTER() const & { return _##GETTER; } \ + TYPE&& GETTER() && { return std::move(_##GETTER); } \ + void SETTER(const TYPE& v) & { _##GETTER = v; } \ + void SETTER(TYPE&& v) & { _##GETTER = std::move(v); } \ + DecoderResult&& SETTER(const TYPE& v) && { _##GETTER = v; return std::move(*this); } \ + DecoderResult&& SETTER(TYPE&& v) && { _##GETTER = std::move(v); return std::move(*this); } + + ZX_PROPERTY(std::string, ecLevel, setEcLevel) + ZX_PROPERTY(int, lineCount, setLineCount) + ZX_PROPERTY(int, versionNumber, setVersionNumber) + ZX_PROPERTY(StructuredAppendInfo, structuredAppend, setStructuredAppend) + ZX_PROPERTY(Error, error, setError) + ZX_PROPERTY(bool, isMirrored, setIsMirrored) + ZX_PROPERTY(bool, readerInit, setReaderInit) + ZX_PROPERTY(std::string, json, setJson) + ZX_PROPERTY(std::shared_ptr, customData, setCustomData) +#undef ZX_PROPERTY + + template + DecoderResult&& addExtra(std::string_view key, T val, T ignore = {}) { _json += JsonProp(key, val, ignore); return std::move(*this); } + +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/DetectorResult.h b/tools/chatgui/zxing-cpp/core/src/DetectorResult.h new file mode 100644 index 00000000..99c4d2ea --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/DetectorResult.h @@ -0,0 +1,44 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BitMatrix.h" +#include "Quadrilateral.h" + +#include + +namespace ZXing { + +/** +* Encapsulates the result of detecting a barcode in an image. This includes the raw +* matrix of black/white pixels corresponding to the barcode and the position of the code +* in the input image. +*/ +class DetectorResult +{ + BitMatrix _bits; + QuadrilateralI _position; + +public: + DetectorResult() = default; + DetectorResult(DetectorResult&&) noexcept = default; + DetectorResult& operator=(DetectorResult&&) noexcept = default; + + DetectorResult(const DetectorResult&) = delete; + DetectorResult& operator=(const DetectorResult&) = delete; + + DetectorResult(BitMatrix&& bits, QuadrilateralI&& position) : _bits(std::move(bits)), _position(std::move(position)) {} + + const BitMatrix& bits() const & { return _bits; } + BitMatrix&& bits() && { return std::move(_bits); } + const QuadrilateralI& position() const & { return _position; } + QuadrilateralI&& position() && { return std::move(_position); } + + bool isValid() const { return !_bits.empty(); } +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ECI.cpp b/tools/chatgui/zxing-cpp/core/src/ECI.cpp new file mode 100644 index 00000000..83afb88b --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ECI.cpp @@ -0,0 +1,81 @@ +/* +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "ECI.h" + +#include "ZXAlgorithms.h" + +#include + +namespace ZXing { + +static const std::map ECI_TO_CHARSET = { + {ECI(0), CharacterSet::Cp437}, // Obsolete + {ECI(1), CharacterSet::ISO8859_1}, // Obsolete + {ECI::Cp437, CharacterSet::Cp437}, // Obsolete but still used by PDF417 Macro fields (ISO/IEC 15438:2015 Annex H.2.3) + {ECI::ISO8859_1, CharacterSet::ISO8859_1}, + {ECI::ISO8859_2, CharacterSet::ISO8859_2}, + {ECI::ISO8859_3, CharacterSet::ISO8859_3}, + {ECI::ISO8859_4, CharacterSet::ISO8859_4}, + {ECI::ISO8859_5, CharacterSet::ISO8859_5}, + {ECI::ISO8859_6, CharacterSet::ISO8859_6}, + {ECI::ISO8859_7, CharacterSet::ISO8859_7}, + {ECI::ISO8859_8, CharacterSet::ISO8859_8}, + {ECI::ISO8859_9, CharacterSet::ISO8859_9}, + {ECI::ISO8859_10, CharacterSet::ISO8859_10}, + {ECI::ISO8859_11, CharacterSet::ISO8859_11}, + {ECI::ISO8859_13, CharacterSet::ISO8859_13}, + {ECI::ISO8859_14, CharacterSet::ISO8859_14}, + {ECI::ISO8859_15, CharacterSet::ISO8859_15}, + {ECI::ISO8859_16, CharacterSet::ISO8859_16}, + {ECI::Shift_JIS, CharacterSet::Shift_JIS}, + {ECI::Cp1250, CharacterSet::Cp1250}, + {ECI::Cp1251, CharacterSet::Cp1251}, + {ECI::Cp1252, CharacterSet::Cp1252}, + {ECI::Cp1256, CharacterSet::Cp1256}, + {ECI::UTF8, CharacterSet::UTF8}, + {ECI::UTF16BE, CharacterSet::UTF16BE}, + {ECI::UTF16LE, CharacterSet::UTF16LE}, + {ECI::UTF32BE, CharacterSet::UTF32BE}, + {ECI::UTF32LE, CharacterSet::UTF32LE}, + {ECI::ASCII, CharacterSet::ASCII}, + {ECI::Big5, CharacterSet::Big5}, + {ECI::GB18030, CharacterSet::GB18030}, + {ECI::GB2312, CharacterSet::GB2312}, + {ECI::EUC_KR, CharacterSet::EUC_KR}, + {ECI::ISO646_Inv, CharacterSet::ASCII}, + {ECI::Binary, CharacterSet::BINARY}, +}; + +std::string ToString(ECI eci) +{ + return '\\' + ToString(ToInt(eci), 6); +} + +CharacterSet ToCharacterSet(ECI eci) +{ + if (auto it = ECI_TO_CHARSET.find(eci); it != ECI_TO_CHARSET.end()) + return it->second; + + return CharacterSet::Unknown; +} + +ECI ToECI(CharacterSet cs) +{ + // Special case ISO8859_1 to avoid obsolete ECI 1 + if (cs == CharacterSet::ISO8859_1) + return ECI::ISO8859_1; + // Special case Cp437 to avoid obsolete ECI 0 for slightly less obsolete ECI 2 + if (cs == CharacterSet::Cp437) + return ECI::Cp437; + + for (auto& [key, value] : ECI_TO_CHARSET) + if (value == cs) + return key; + + return ECI::Unknown; +} + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ECI.h b/tools/chatgui/zxing-cpp/core/src/ECI.h new file mode 100644 index 00000000..691f4780 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ECI.h @@ -0,0 +1,78 @@ +/* +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "CharacterSet.h" + +#include + +namespace ZXing { + +enum class ECI : int +{ + Unknown = -1, + Cp437 = 2, // obsolete + ISO8859_1 = 3, + ISO8859_2 = 4, + ISO8859_3 = 5, + ISO8859_4 = 6, + ISO8859_5 = 7, + ISO8859_6 = 8, + ISO8859_7 = 9, + ISO8859_8 = 10, + ISO8859_9 = 11, + ISO8859_10 = 12, + ISO8859_11 = 13, + ISO8859_13 = 15, + ISO8859_14 = 16, + ISO8859_15 = 17, + ISO8859_16 = 18, + Shift_JIS = 20, + Cp1250 = 21, + Cp1251 = 22, + Cp1252 = 23, + Cp1256 = 24, + UTF16BE = 25, + UTF8 = 26, + ASCII = 27, + Big5 = 28, + GB2312 = 29, + EUC_KR = 30, + GB18030 = 32, + UTF16LE = 33, + UTF32BE = 34, + UTF32LE = 35, + ISO646_Inv = 170, + Binary = 899 +}; + +inline constexpr int ToInt(ECI eci) +{ + return static_cast(eci); +} + +inline constexpr bool IsText(ECI eci) +{ + return ToInt(eci) >= 0 && ToInt(eci) <= 170; +} + +inline constexpr bool CanProcess(ECI eci) +{ + // see https://github.com/zxing-cpp/zxing-cpp/commit/d8587545434d533c4e568181e1c12ef04a8e42d9#r74864359 + return ToInt(eci) <= 899; +} + +/** + * @brief ToString converts the numerical ECI value to a 7 character string as used in the ECI protocol + * @return e.g. "\000020" + */ +std::string ToString(ECI eci); + +CharacterSet ToCharacterSet(ECI eci); + +ECI ToECI(CharacterSet cs); + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/Error.cpp b/tools/chatgui/zxing-cpp/core/src/Error.cpp new file mode 100644 index 00000000..c46a6c62 --- /dev/null +++ b/tools/chatgui/zxing-cpp/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/tools/chatgui/zxing-cpp/core/src/Error.h b/tools/chatgui/zxing-cpp/core/src/Error.h new file mode 100644 index 00000000..f1070a45 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Error.h @@ -0,0 +1,79 @@ +/* + * Copyright 2022 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +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. + */ + +class Error +{ +public: + enum class Type : uint8_t + { + None, ///< No error + Format, ///< Format error (i.e. the decoded data is not valid for the detected symbology) + Checksum, ///< Checksum error (i.e. the decoded data does not pass the checksum validation) + Unsupported ///< Unsupported error (i.e. a particular feature detected is not supported by zxing-cpp) + }; + Type type() const noexcept { return _type; } + const std::string& msg() const noexcept { return _msg; } + explicit operator bool() const noexcept { return _type != Type::None; } + + /// The source code location where the error was detected (if available) + std::string location() const; + + Error() = default; + Error(Type type, std::string msg = {}) : _msg(std::move(msg)), _type(type) {} + Error(const char* file, short line, Type type, std::string msg = {}) : _msg(std::move(msg)), _file(file), _line(line), _type(type) {} + + static constexpr auto Format = Type::Format; + static constexpr auto Checksum = Type::Checksum; + static constexpr auto Unsupported = Type::Unsupported; + + inline bool operator==(const Error& o) const noexcept + { + return _type == o._type && _msg == o._msg && _file == o._file && _line == o._line; + } + inline bool operator!=(const Error& o) const noexcept { return !(*this == o); } + +protected: + /// @cond INTERNAL + std::string _msg; + const char* _file = nullptr; + short _line = -1; + Type _type = Type::None; + /// @endcond +}; + +/// @cond INTERNAL +inline bool operator==(const Error& e, Error::Type t) noexcept { return e.type() == t; } +inline bool operator!=(const Error& e, Error::Type t) noexcept { return !(e == t); } +inline bool operator==(Error::Type t, const Error& e) noexcept { return e.type() == t; } +inline bool operator!=(Error::Type t, const Error& e) noexcept { return !(t == e); } + +#define FormatError(...) Error(__FILE__, __LINE__, Error::Format, std::string(__VA_ARGS__)) +#define ChecksumError(...) Error(__FILE__, __LINE__, Error::Checksum, std::string(__VA_ARGS__)) +#define UnsupportedError(...) Error(__FILE__, __LINE__, Error::Unsupported, std::string(__VA_ARGS__)) +/// @endcond + +std::string ToString(const Error& e); + +} diff --git a/tools/chatgui/zxing-cpp/core/src/Flags.h b/tools/chatgui/zxing-cpp/core/src/Flags.h new file mode 100644 index 00000000..8cc61fe0 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Flags.h @@ -0,0 +1,165 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +#include +#ifdef __cpp_lib_bitops +#include +#else +namespace std { +template >> +constexpr int popcount(T x) noexcept +{ + int n = 0; + for (; x; ++n) + x &= x - 1; // clear lowest bit + return n; +} + +template >> +constexpr int countr_zero(T x) noexcept +{ + if (x == 0) + return std::numeric_limits::digits; + + int n = 0; + while ((x & T(1)) == 0) { + x >>= 1; + ++n; + } + return n; +} + +template >> +constexpr int countl_zero(T x) noexcept +{ + if (x == 0) + return std::numeric_limits::digits; + + int n = 0; + for (int i = std::numeric_limits::digits; i-- && !(x & (T(1) << i)); ++n) + ; + return n; +} +} // namespace std +#endif + +namespace ZXing { + +template +class Flags +{ + static_assert(std::is_enum::value, "Flags is only usable on enumeration types."); + + using Int = std::make_unsigned_t::type>; + Int i = 0; + + constexpr inline Flags(Int other) : i(other) {} + + constexpr static inline auto highestBitSet(Int x) noexcept + { + return std::numeric_limits::digits - 1 - std::countl_zero(x); + } + +public: + using enum_type = Enum; + + constexpr inline Flags() noexcept = default; + constexpr inline Flags(Enum flag) noexcept : i(Int(flag)) {} + +// constexpr inline Flags(std::initializer_list flags) noexcept +// : i(initializer_list_helper(flags.begin(), flags.end())) +// {} + + class iterator + { + friend class Flags; + const Int _flags = 0; + int _pos = 0; + iterator(Int i, int p) : _flags(i), _pos(p) {} + public: + using iterator_category = std::input_iterator_tag; + using value_type = Enum; + using difference_type = std::ptrdiff_t; + using pointer = Enum*; + using reference = Enum&; + + Enum operator*() const noexcept { return Enum(Int(1) << _pos); } + + iterator& operator++() noexcept + { + while (++_pos < highestBitSet(_flags) && !((Int(1) << _pos) & _flags)) + ; + return *this; + } + + bool operator==(const iterator& rhs) const noexcept { return _pos == rhs._pos; } + bool operator!=(const iterator& rhs) const noexcept { return !(*this == rhs); } + }; + + iterator begin() const noexcept { return {i, std::countr_zero(i)}; } + iterator end() const noexcept { return {i, highestBitSet(i) + 1}; } + + bool empty() const noexcept { return i == 0; } + int count() const noexcept { return std::popcount(i); } + + constexpr inline bool operator==(Flags other) const noexcept { return i == other.i; } + + inline Flags& operator&=(Flags mask) noexcept { return i &= mask.i, *this; } + inline Flags& operator&=(Enum mask) noexcept { return i &= Int(mask), *this; } + inline Flags& operator|=(Flags other) noexcept { return i |= other.i, *this; } + inline Flags& operator|=(Enum other) noexcept { return i |= Int(other), *this; } +// inline Flags &operator^=(Flags other) noexcept { return i ^= other.i, *this; } +// inline Flags &operator^=(Enum other) noexcept { return i ^= Int(other), *this; } + + constexpr inline Flags operator&(Flags other) const noexcept { return i & other.i; } + constexpr inline Flags operator&(Enum other) const noexcept { return i & Int(other); } + constexpr inline Flags operator|(Flags other) const noexcept { return i | other.i; } + constexpr inline Flags operator|(Enum other) const noexcept { return i | Int(other); } +// constexpr inline Flags operator^(Flags other) const noexcept { return i ^ other.i; } +// constexpr inline Flags operator^(Enum other) const noexcept { return i ^ Int(other); } +// constexpr inline Flags operator~() const noexcept { return ~i; } + +// constexpr inline operator Int() const noexcept { return i; } +// constexpr inline bool operator!() const noexcept { return !i; } +// constexpr inline Int asInt() const noexcept { return i; } + + constexpr inline bool testFlag(Enum flag) const noexcept + { + return (i & Int(flag)) == Int(flag) && (Int(flag) != 0 || i == Int(flag)); + } + constexpr inline bool testFlags(Flags mask) const noexcept { return i & mask.i; } + inline Flags& setFlag(Enum flag, bool on = true) noexcept + { + return on ? (*this |= flag) : (*this &= ~Int(flag)); + } + inline void clear() noexcept { i = 0; } + + constexpr static Flags all() noexcept { return ~(Int(~0) << highestBitSet(Int(Enum::_max))); } + +private: +// constexpr static inline Int +// initializer_list_helper(typename std::initializer_list::const_iterator it, +// typename std::initializer_list::const_iterator end) noexcept +// { +// return (it == end ? Int(0) : (Int(*it) | initializer_list_helper(it + 1, end))); +// } +}; + +#define ZX_DECLARE_FLAGS(FLAGS, ENUM) \ + using FLAGS = Flags; \ + constexpr inline FLAGS operator|(FLAGS::enum_type e1, FLAGS::enum_type e2) noexcept { return FLAGS(e1) | e2; } \ + constexpr inline FLAGS operator|(FLAGS::enum_type e, FLAGS f) noexcept { return f | e; } \ + constexpr inline bool operator==(FLAGS::enum_type e, FLAGS f) noexcept { return FLAGS(e) == f; } \ + constexpr inline bool operator==(FLAGS f, FLAGS::enum_type e) noexcept { return FLAGS(e) == f; } + +} // ZXing + diff --git a/tools/chatgui/zxing-cpp/core/src/GTIN.cpp b/tools/chatgui/zxing-cpp/core/src/GTIN.cpp new file mode 100644 index 00000000..2b3fd1df --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/GTIN.cpp @@ -0,0 +1,253 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "GTIN.h" + +#include +#ifdef __cpp_lib_format // not available on gcc 12 +#include +#endif +#include +#include +#include + +namespace ZXing::GTIN { + +struct CountryId +{ + uint16_t first; + uint16_t last; + const char id[3]; +}; + +bool operator<(const CountryId& lhs, const CountryId& rhs) +{ + return lhs.last < rhs.last; +} + +// https://www.gs1.org/standards/id-keys/company-prefix (as of 7 Feb 2022) +// and https://en.wikipedia.org/wiki/List_of_GS1_country_codes +static const CountryId COUNTRIES[] = { + // clang-format off + {1, 19, "US"}, + {30, 39, "US"}, + {60, 99, "US"}, // Note 99 coupon identification + {100, 139, "US"}, + {300, 379, "FR"}, // France (and Monaco according to Wikipedia) + {380, 380, "BG"}, // Bulgaria + {383, 383, "SI"}, // Slovenia + {385, 385, "HR"}, // Croatia + {387, 387, "BA"}, // Bosnia and Herzegovina + {389, 389, "ME"}, // Montenegro + //{390, 390, "XK"}, // (Kosovo according to Wikipedia, unsourced) + {400, 440, "DE"}, // Germany + {450, 459, "JP"}, // Japan + {460, 469, "RU"}, // Russia + {470, 470, "KG"}, // Kyrgyzstan + {471, 471, "TW"}, // Taiwan + {474, 474, "EE"}, // Estonia + {475, 475, "LV"}, // Latvia + {476, 476, "AZ"}, // Azerbaijan + {477, 477, "LT"}, // Lithuania + {478, 478, "UZ"}, // Uzbekistan + {479, 479, "LK"}, // Sri Lanka + {480, 480, "PH"}, // Philippines + {481, 481, "BY"}, // Belarus + {482, 482, "UA"}, // Ukraine + {483, 483, "TM"}, // Turkmenistan + {484, 484, "MD"}, // Moldova + {485, 485, "AM"}, // Armenia + {486, 486, "GE"}, // Georgia + {487, 487, "KZ"}, // Kazakhstan + {488, 488, "TJ"}, // Tajikistan + {489, 489, "HK"}, // Hong Kong + {490, 499, "JP"}, // Japan + {500, 509, "GB"}, // UK + {520, 521, "GR"}, // Greece + {528, 528, "LB"}, // Lebanon + {529, 529, "CY"}, // Cyprus + {530, 530, "AL"}, // Albania + {531, 531, "MK"}, // North Macedonia + {535, 535, "MT"}, // Malta + {539, 539, "IE"}, // Ireland + {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) + {590, 590, "PL"}, // Poland + {594, 594, "RO"}, // Romania + {599, 599, "HU"}, // Hungary + {600, 601, "ZA"}, // South Africa + {603, 603, "GH"}, // Ghana + {604, 604, "SN"}, // Senegal + {608, 608, "BH"}, // Bahrain + {609, 609, "MU"}, // Mauritius + {611, 611, "MA"}, // Morocco + {613, 613, "DZ"}, // Algeria + {615, 615, "NG"}, // Nigeria + {616, 616, "KE"}, // Kenya + {617, 617, "CM"}, // Cameroon + {618, 618, "CI"}, // Côte d'Ivoire + {619, 619, "TN"}, // Tunisia + {620, 620, "TZ"}, // Tanzania + {621, 621, "SY"}, // Syria + {622, 622, "EG"}, // Egypt + {623, 623, "BN"}, // Brunei + {624, 624, "LY"}, // Libya + {625, 625, "JO"}, // Jordan + {626, 626, "IR"}, // Iran + {627, 627, "KW"}, // Kuwait + {628, 628, "SA"}, // Saudi Arabia + {629, 629, "AE"}, // United Arab Emirates + {630, 630, "QA"}, // Qatar + {631, 631, "NA"}, // Namibia + {640, 649, "FI"}, // Finland + {690, 699, "CN"}, // China + {700, 709, "NO"}, // Norway + {729, 729, "IL"}, // Israel + {730, 739, "SE"}, // Sweden + {740, 740, "GT"}, // Guatemala + {741, 741, "SV"}, // El Salvador + {742, 742, "HN"}, // Honduras + {743, 743, "NI"}, // Nicaragua + {744, 744, "CR"}, // Costa Rica + {745, 745, "PA"}, // Panama + {746, 746, "DO"}, // Dominican Republic + {750, 750, "MX"}, // Mexico + {754, 755, "CA"}, // Canada + {759, 759, "VE"}, // Venezuela + {760, 769, "CH"}, // Switzerland (and Liechtenstein according to Wikipedia) + {770, 771, "CO"}, // Colombia + {773, 773, "UY"}, // Uruguay + {775, 775, "PE"}, // Peru + {777, 777, "BO"}, // Bolivia + {778, 779, "AR"}, // Argentina + {780, 780, "CL"}, // Chile + {784, 784, "PY"}, // Paraguay + {786, 786, "EC"}, // Ecuador + {789, 790, "BR"}, // Brazil + {800, 839, "IT"}, // Italy (and San Marino and Vatican City according to Wikipedia) + {840, 849, "ES"}, // Spain (and Andorra according to Wikipedia) + {850, 850, "CU"}, // Cuba + {858, 858, "SK"}, // Slovakia + {859, 859, "CZ"}, // Czechia + {860, 860, "RS"}, // Serbia + {865, 865, "MN"}, // Mongolia + {867, 867, "KP"}, // North Korea + {868, 869, "TR"}, // Turkey + {870, 879, "NL"}, // Netherlands + {880, 880, "KR"}, // South Korea + {883, 883, "MM"}, // Myanmar + {884, 884, "KH"}, // Cambodia + {885, 885, "TH"}, // Thailand + {888, 888, "SG"}, // Singapore + {890, 890, "IN"}, // India + {893, 893, "VN"}, // Vietnam + {896, 896, "PK"}, // Pakistan + {899, 899, "ID"}, // Indonesia + {900, 919, "AT"}, // Austria + {930, 939, "AU"}, // Australia + {940, 949, "NZ"}, // New Zealand + {955, 955, "MY"}, // Malaysia + {958, 958, "MO"}, // Macao + //{960, 961, "GB"}, // Global Office - assigned to GS1 UK for GTIN-8 allocations (also 9620-9624999) + // clang-format on +}; + +std::string LookupCountryIdentifier(std::string_view GTIN, const BarcodeFormat format) +{ + // Ignore add-on if any + const auto space = GTIN.find(' '); + const std::string::size_type size = space != std::string::npos ? space : GTIN.size(); + + if (size != 14 && size != 13 && size != 12 && size != 8) + return {}; + + // GTIN-14 leading packaging level indicator + const int first = size == 14 ? 1 : 0; + // UPC-A/E implicit leading 0 + const int implicitZero = size == 12 || (size == 8 && format != BarcodeFormat::EAN8) ? 1 : 0; + + if (size != 8 || format != BarcodeFormat::EAN8) { // Assuming following doesn't apply to EAN-8 + // 0000000 Restricted Circulation Numbers; 0000001-0000099 unused to avoid collision with GTIN-8 + int prefix = FromString(GTIN.substr(first, 7 - implicitZero)); + if (prefix >= 0 && prefix <= 99) + return {}; + + // 00001-00009 US + prefix = FromString(GTIN.substr(first, 5 - implicitZero)); + if (prefix >= 1 && prefix <= 9) + return "US"; + + // 0001-0009 US + prefix = FromString(GTIN.substr(first, 4 - implicitZero)); + if (prefix >= 1 && prefix <= 9) + return "US"; + } + + const int prefix = FromString(GTIN.substr(first, 3 - implicitZero)); + + // Special case EAN-8 for prefix < 100 (GS1 General Specifications Figure 1.4.3-1) + 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, narrow_cast(prefix), ""}); + + return it != std::end(COUNTRIES) && prefix >= it->first && prefix <= it->last ? it->id : std::string(); +} + +std::string EanAddOn(const Barcode& barcode) +{ + if (barcode.symbologyIdentifier() != "]E3") + return {}; + return barcode.text().substr(barcode.format() == BarcodeFormat::EAN8 ? 8 : 13); +} + +std::string IssueNr(const std::string& ean2AddOn) +{ + if (ean2AddOn.size() != 2) + return {}; + + return std::to_string(std::stoi(ean2AddOn)); +} + +std::string Price(const std::string& ean5AddOn) +{ + if (ean5AddOn.size() != 5) + return {}; + + std::string currency; + switch (ean5AddOn.front()) { + case '0': [[fallthrough]]; + case '1': currency = "GBP £"; break; // UK + case '3': currency = "AUD $"; break; // AUS + case '4': currency = "NZD $"; break; // NZ + case '5': currency = "USD $"; break; // US + case '6': currency = "CAD $"; break; // CA + case '9': + // Reference: http://www.jollytech.com + if (ean5AddOn == "90000") // No suggested retail price + return {}; + if (ean5AddOn == "99991") // Complementary + return "0.00"; + if (ean5AddOn == "99990") + return "Used"; + + // Otherwise... unknown currency? + currency = ""; + break; + default: currency = ""; break; + } + + int rawAmount = std::stoi(ean5AddOn.substr(1)); +#if !defined(__cpp_lib_to_chars) || !defined(__cpp_lib_format) // not available on older macOS / gcc 12 + return currency + std::to_string(rawAmount / 100) + '.' + std::to_string(rawAmount % 100); +#else + return std::format("{}{:.2f}", currency, float(rawAmount) / 100); +#endif +} + +} // namespace ZXing::GTIN diff --git a/tools/chatgui/zxing-cpp/core/src/GTIN.h b/tools/chatgui/zxing-cpp/core/src/GTIN.h new file mode 100644 index 00000000..1cc46706 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/GTIN.h @@ -0,0 +1,58 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Barcode.h" +#include "BarcodeFormat.h" + +#ifdef ZXING_INTERNAL +#include "ZXAlgorithms.h" +#endif + +#include +#include + +namespace ZXing::GTIN { + +#ifdef ZXING_INTERNAL +template +T ComputeCheckDigit(const std::basic_string& digits, bool skipTail = false) +{ + int sum = 0, N = Size(digits) - skipTail; + for (int i = N - 1; i >= 0; i -= 2) + sum += digits[i] - '0'; + sum *= 3; + for (int i = N - 2; i >= 0; i -= 2) + sum += digits[i] - '0'; + return ToDigit((10 - (sum % 10)) % 10); +} + +template +bool IsCheckDigitValid(const std::basic_string& s) +{ + return ComputeCheckDigit(s, true) == s.back(); +} +#endif + +/** + * Evaluate the prefix of the GTIN to estimate the country of origin. See + * + * https://www.gs1.org/standards/id-keys/company-prefix and + * + * https://en.wikipedia.org/wiki/List_of_GS1_country_codes. + * + * `format` required for EAN-8 (UPC-E assumed if not given) + */ +std::string LookupCountryIdentifier(std::string_view GTIN, BarcodeFormat format = BarcodeFormat::None); + +std::string EanAddOn(const Barcode& barcode); + +std::string IssueNr(const std::string& ean2AddOn); +std::string Price(const std::string& ean5AddOn); + +} // namespace ZXing::GTIN diff --git a/tools/chatgui/zxing-cpp/core/src/GlobalHistogramBinarizer.cpp b/tools/chatgui/zxing-cpp/core/src/GlobalHistogramBinarizer.cpp new file mode 100644 index 00000000..d57bc281 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/GlobalHistogramBinarizer.cpp @@ -0,0 +1,197 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "GlobalHistogramBinarizer.h" + +#include "BitMatrix.h" +#include "Pattern.h" +#include "ZXConfig.h" + +#include +#include +#include + +namespace ZXing { + +static constexpr int LUMINANCE_BITS = 5; +static constexpr int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; +static constexpr int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; + +using Histogram = std::array; + +GlobalHistogramBinarizer::GlobalHistogramBinarizer(const ImageView& buffer) : BinaryBitmap(buffer) {} + +GlobalHistogramBinarizer::~GlobalHistogramBinarizer() = default; + +using ImageLineView = Range>; + +inline ImageLineView RowView(const ImageView& iv, int row) +{ + return {{iv.data(0, row), iv.pixStride()}, {iv.data(iv.width(), row), iv.pixStride()}}; +} + +template +static void ThresholdSharpened(Range in, int threshold, std::vector& out) +{ + out.resize(in.size()); + auto i = in.begin(); + auto o = out.begin(); + + *o++ = (*i++ <= threshold) * BitMatrix::SET_V; + for (auto end = in.end() - 1; i != end; ++i) + *o++ = ((-i[-1] + (int(i[0]) * 4) - i[1]) / 2 <= threshold) * BitMatrix::SET_V; + *o++ = (*i++ <= threshold) * BitMatrix::SET_V; +} + +template +static auto GenHistogram(Range 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 on the Intel + // system but was able to make the total runtime for that use case go down about 10% on an ARM M4. +#if 1 + Histogram res0 = {}, res1 = {}, res2 = {}, res3 = {}; + + auto src = line.begin(); + const auto end = line.end(); + + while (end - src >= 4) { + res0[*src++ >> LUMINANCE_SHIFT]++; + res1[*src++ >> LUMINANCE_SHIFT]++; + res2[*src++ >> LUMINANCE_SHIFT]++; + res3[*src++ >> LUMINANCE_SHIFT]++; + } + + Histogram res = {}; + for (int i = 0; i < LUMINANCE_BUCKETS; ++i) + res[i] = res0[i] + res1[i] + res2[i] + res3[i]; + + while (src != end) + res[*src++ >> LUMINANCE_SHIFT]++; + + return res; +#else + Histogram res = {}; + for (auto pix : line) + res[pix >> LUMINANCE_SHIFT]++; + return res; +#endif +} + +// Return -1 on error +static int EstimateBlackPoint(const Histogram& buckets) +{ + // Find the tallest peak in the histogram. + auto firstPeakPos = std::max_element(buckets.begin(), buckets.end()); + int firstPeak = narrow_cast(firstPeakPos - buckets.begin()); + int firstPeakSize = *firstPeakPos; + int maxBucketCount = firstPeakSize; + + // Find the second-tallest peak which is somewhat far from the tallest peak. + int secondPeak = 0; + int secondPeakScore = 0; + for (int x = 0; x < Size(buckets); x++) { + int distanceToBiggest = x - firstPeak; + // Encourage more distant second peaks by multiplying by square of distance. + int score = buckets[x] * distanceToBiggest * distanceToBiggest; + if (score > secondPeakScore) { + secondPeak = x; + secondPeakScore = score; + } + } + + // Make sure firstPeak corresponds to the black peak. + if (firstPeak > secondPeak) { + std::swap(firstPeak, secondPeak); + } + + // If there is too little contrast in the image to pick a meaningful black point, throw rather + // than waste time trying to decode the image, and risk false positives. + if (secondPeak - firstPeak <= LUMINANCE_BUCKETS / 16) { + return -1; + } + + // Find a valley between them that is low and closer to the white peak. + int bestValley = secondPeak - 1; + int bestValleyScore = -1; + for (int x = secondPeak - 1; x > firstPeak; x--) { + int fromFirst = x - firstPeak; + int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]); + if (score > bestValleyScore) { + bestValley = x; + bestValleyScore = score; + } + } + + return bestValley << LUMINANCE_SHIFT; +} + +bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const +{ + auto buffer = _buffer.rotated(rotation); + auto lineView = RowView(buffer, row); + + if (buffer.width() < 3) + return false; // special casing the code below for a width < 3 makes no sense + +#if defined(__AVX__) || 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 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. + ZX_THREAD_LOCAL std::vector line; + if (std::abs(buffer.pixStride()) > 4) { + line.resize(lineView.size()); + std::copy(lineView.begin(), lineView.end(), line.begin()); + lineView = {{line.data(), 1}, {line.data() + line.size(), 1}}; + } +#endif + + auto packedLineView = + lineView.begin().stride == 1 ? Range(lineView.begin().pos, lineView.end().pos) : Range(nullptr, nullptr); + auto histogram = packedLineView ? GenHistogram(packedLineView) : GenHistogram(lineView); + auto threshold = EstimateBlackPoint(histogram) - 1; + if (threshold <= 0) + return false; + + ZX_THREAD_LOCAL std::vector binarized; + // the optimizer can generate a specialized version for pixStride==1 (non-rotated input) that is about 8x faster on AVX2 hardware + if (packedLineView) + ThresholdSharpened(packedLineView, threshold, binarized); + else + ThresholdSharpened(lineView, threshold, binarized); + GetPatternRow(Range(binarized), res); + + return true; +} + +// Does not sharpen the data, as this call is intended to only be used by 2D Readers. +std::shared_ptr +GlobalHistogramBinarizer::getBlackMatrix() const +{ + // Quickly calculates the histogram by sampling four rows from the image. This proved to be + // more robust on the blackbox tests than sampling a diagonal as we used to do. + Histogram localBuckets = {}; + { + for (int y = 1; y < 5; y++) { + int row = height() * y / 5; + const uint8_t* luminances = _buffer.data(0, row); + int right = (width() * 4) / 5; + for (int x = width() / 5; x < right; x++) + localBuckets[luminances[x] >> LUMINANCE_SHIFT]++; + } + } + + int blackPoint = EstimateBlackPoint(localBuckets); + if (blackPoint <= 0) + return {}; + + + + return std::make_shared(binarize(blackPoint)); +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/GlobalHistogramBinarizer.h b/tools/chatgui/zxing-cpp/core/src/GlobalHistogramBinarizer.h new file mode 100644 index 00000000..8a0bcfa4 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/GlobalHistogramBinarizer.h @@ -0,0 +1,34 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BinaryBitmap.h" + +namespace ZXing { + +/** +* This Binarizer implementation uses the old ZXing global histogram approach. It is suitable +* for low-end mobile devices which don't have enough CPU or memory to use a local thresholding +* algorithm. However, because it picks a global black point, it cannot handle difficult shadows +* and gradients. +* +* Faster mobile devices and all desktop applications should probably use HybridBinarizer instead. +* +* @author dswitkin@google.com (Daniel Switkin) +* @author Sean Owen +*/ +class GlobalHistogramBinarizer : public BinaryBitmap +{ +public: + explicit GlobalHistogramBinarizer(const ImageView& buffer); + ~GlobalHistogramBinarizer() override; + + bool getPatternRow(int row, int rotation, PatternRow &res) const override; + std::shared_ptr getBlackMatrix() const override; +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/GridSampler.cpp b/tools/chatgui/zxing-cpp/core/src/GridSampler.cpp new file mode 100644 index 00000000..6a5c05a1 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/GridSampler.cpp @@ -0,0 +1,124 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "GridSampler.h" + +#ifdef PRINT_DEBUG +#include "LogMatrix.h" +#include "BitMatrixIO.h" +#endif + +namespace ZXing { + +#ifdef PRINT_DEBUG +LogMatrix log; +#endif + +DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix) +{ + return SampleGrid(image, width, height, {ROI{0, width, 0, height, mod2Pix}}); +} + +DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const ROIs& rois) +{ +#ifdef PRINT_DEBUG + LogMatrix log; + static int i = 0; + LogMatrixWriter lmw(log, image, 5, "grid" + std::to_string(i++) + ".pnm"); +#endif + if (width <= 0 || height <= 0) + return {}; + + for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) { + // Precheck the corners of every roi to bail out early if the grid is "obviously" not completely inside the image + auto isInside = [&mod2Pix = mod2Pix, &image](int x, int y) { return image.isIn(mod2Pix(centered(PointI(x, y)))); }; + if (!mod2Pix.isValid() || !isInside(x0, y0) || !isInside(x1 - 1, y0) || !isInside(x1 - 1, y1 - 1) || !isInside(x0, y1 - 1)) + return {}; + } + + BitMatrix res(width, height); + for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) { + for (int y = y0; y < y1; ++y) + for (int x = x0; x < x1; ++x) { + auto p = mod2Pix(centered(PointI{x, y})); + // Due to a "numerical instability" in the PerspectiveTransform generation/application it has been observed + // that even though all boundary grid points get projected inside the image, it can still happen that an + // inner grid points is not. See #563. A true perspective transformation cannot have this property. + // The following check takes 100% care of the issue and turned out to be less of a performance impact than feared. + // TODO: Check some mathematical/numercial property of mod2Pix to determine if it is a perspective transformation. + if (!image.isIn(p)) + return {}; + +#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); + } + } + +#ifdef PRINT_DEBUG + printf("width: %d, height: %d\n", width, height); +// printf("%s", ToString(res).c_str()); +#endif + + auto projectCorner = [&](PointI p) { + for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) + if (x0 <= p.x && p.x <= x1 && y0 <= p.y && p.y <= y1) + return PointI(mod2Pix(PointF(p)) + PointF(0.5, 0.5)); + + return PointI(); + }; + + return {std::move(res), + {projectCorner({0, 0}), projectCorner({width, 0}), projectCorner({width, height}), projectCorner({0, height})}}; +} + +DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix, + Matrix>&& apP, const std::vector& apMX, const std::vector& apMY) +{ + const int W = Size(apMX) - 1, H = Size(apMY) - 1; + + // go over the set of alignment patters and fill any remaining gaps by a projection based on the fallback + // mod2Pix projection. This works if the symbol is flat, which is a reasonable fall-back assumption. + for (int y = 0; y <= H; ++y) + for (int x = 0; x <= W; ++x) { + if (apP(x, y)) { +#ifdef PRINT_DEBUG + log(*apP(x, y), 2); +#endif + } else { +#ifdef PRINT_DEBUG + printf("locate failed at %dx%d\n", x, y); +#endif + // project the alignment pattern at module coordinates x/y to pixel coordinate based on current mod2Pix + apP.set(x, y, mod2Pix(centered(PointI(apMX[x], apMY[y])))); + } + } + + // assemble a list of region-of-interests based on the found alignment pattern pixel positions + ROIs rois; + for (int y = 0; y < H; ++y) + for (int x = 0; x < W; ++x) { + int x0 = apMX[x], x1 = apMX[x + 1], y0 = apMY[y], y1 = apMY[y + 1]; + rois.push_back({x == 0 ? 0 : x0, (x == W - 1) ? width : x1, (y == 0) ? 0 : y0, (y == H - 1) ? height : y1, + PerspectiveTransform{Rectangle(x0, x1, y0, y1, 0.5), + {*apP(x, y), *apP(x + 1, y), *apP(x + 1, y + 1), *apP(x, y + 1)}}}); + } + + return SampleGrid(image, width, height, rois); +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/GridSampler.h b/tools/chatgui/zxing-cpp/core/src/GridSampler.h new file mode 100644 index 00000000..00ba6b28 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/GridSampler.h @@ -0,0 +1,59 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "DetectorResult.h" +#include "Matrix.h" +#include "PerspectiveTransform.h" + +#include +#include + +namespace ZXing { + +/** +* Samples an image for a rectangular matrix of bits of the given dimension. The sampling +* transformation is determined by the coordinates of 4 points, in the original and transformed +* image space. +* +* The following figure is showing the layout a 'pixel'. The point (0,0) is the upper left corner +* of the first pixel. (1,1) is its lower right corner. +* +* 0 1 ... w +* 0 #----#-- ... --# +* | | ... | +* | | ... | +* 1 #----# ... --# +* | | ... | +* +* | | ... | +* h #----#-- ... --# +* +* @param image image to sample +* @param width width of {@link BitMatrix} to sample from image +* @param height height of {@link BitMatrix} to sample from image +* @param mod2Pix transforming a module (grid) coordinate into an image (pixel) coordinate +* @return {@link DetectorResult} representing a grid of points sampled from the image within a region +* defined by the "src" parameters. Result is empty if transformation is invalid (out of bound access). +*/ +DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix); + +class ROI +{ +public: + int x0, x1, y0, y1; + PerspectiveTransform mod2Pix; +}; + +using ROIs = std::vector; + +DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const ROIs& rois); + +DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix, + Matrix>&& apP, const std::vector& apMX, const std::vector& apMY); + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/HRI.cpp b/tools/chatgui/zxing-cpp/core/src/HRI.cpp new file mode 100644 index 00000000..a7d2978d --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/HRI.cpp @@ -0,0 +1,343 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "HRI.h" + +#include "ZXAlgorithms.h" + +#include +#include +#include + +namespace ZXing { + +struct AiInfo +{ + 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 + { + using namespace std::literals; + if ((aiPrefix[0] == '3' && Contains("1234569", aiPrefix[1])) || aiPrefix == "703"sv || aiPrefix == "723"sv) + return 4; + else + return narrow_cast(strlen(aiPrefix)); + } +}; + +// https://github.com/gs1/gs1-syntax-dictionary 2025-12-08 HEAD [c7e0c5b] +static const AiInfo aiInfos[] = { +//TWO_DIGIT_DATA_LENGTH + { "00", 18 }, + { "01", 14 }, + { "02", 14 }, + { "03", 14 }, + + { "10", -20 }, + { "11", 6 }, + { "12", 6 }, + { "13", 6 }, + { "15", 6 }, + { "16", 6 }, + { "17", 6 }, + + { "20", 2 }, + { "21", -20 }, + { "22", -20 }, + + { "30", -8 }, + { "37", -8 }, + + { "90", -30 }, + { "91", -90 }, + { "92", -90 }, + { "93", -90 }, + { "94", -90 }, + { "95", -90 }, + { "96", -90 }, + { "97", -90 }, + { "98", -90 }, + { "99", -90 }, + +//THREE_DIGIT_DATA_LENGTH + { "235", -28 }, + { "240", -30 }, + { "241", -30 }, + { "242", -6 }, + { "243", -20 }, + { "250", -30 }, + { "251", -30 }, + { "253", -30 }, + { "254", -20 }, + { "255", -25 }, + + { "400", -30 }, + { "401", -30 }, + { "402", 17 }, + { "403", -30 }, + { "410", 13 }, + { "411", 13 }, + { "412", 13 }, + { "413", 13 }, + { "414", 13 }, + { "415", 13 }, + { "416", 13 }, + { "417", 13 }, + { "420", -20 }, + { "421", -12 }, + { "422", 3 }, + { "423", -15 }, + { "424", 3 }, + { "425", -15 }, + { "426", 3 }, + { "427", -3 }, + + { "710", -20 }, + { "711", -20 }, + { "712", -20 }, + { "713", -20 }, + { "714", -20 }, + { "715", -20 }, + { "716", -20 }, + { "717", -20 }, + +//THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH + { "310", 6 }, + { "311", 6 }, + { "312", 6 }, + { "313", 6 }, + { "314", 6 }, + { "315", 6 }, + { "316", 6 }, + { "320", 6 }, + { "321", 6 }, + { "322", 6 }, + { "323", 6 }, + { "324", 6 }, + { "325", 6 }, + { "326", 6 }, + { "327", 6 }, + { "328", 6 }, + { "329", 6 }, + { "330", 6 }, + { "331", 6 }, + { "332", 6 }, + { "333", 6 }, + { "334", 6 }, + { "335", 6 }, + { "336", 6 }, + { "337", 6 }, + { "340", 6 }, + { "341", 6 }, + { "342", 6 }, + { "343", 6 }, + { "344", 6 }, + { "345", 6 }, + { "346", 6 }, + { "347", 6 }, + { "348", 6 }, + { "349", 6 }, + { "350", 6 }, + { "351", 6 }, + { "352", 6 }, + { "353", 6 }, + { "354", 6 }, + { "355", 6 }, + { "356", 6 }, + { "357", 6 }, + { "360", 6 }, + { "361", 6 }, + { "362", 6 }, + { "363", 6 }, + { "364", 6 }, + { "365", 6 }, + { "366", 6 }, + { "367", 6 }, + { "368", 6 }, + { "369", 6 }, + { "390", -15 }, + { "391", -18 }, + { "392", -15 }, + { "393", -18 }, + { "394", 4 }, + { "395", 6 }, + { "703", -30 }, + { "723", -30 }, + +//FOUR_DIGIT_DATA_LENGTH + { "4300", -35 }, + { "4301", -35 }, + { "4302", -70 }, + { "4303", -70 }, + { "4304", -70 }, + { "4305", -70 }, + { "4306", -70 }, + { "4307", 2 }, + { "4308", -30 }, + { "4309", 20 }, + { "4310", -35 }, + { "4311", -35 }, + { "4312", -70 }, + { "4313", -70 }, + { "4314", -70 }, + { "4315", -70 }, + { "4316", -70 }, + { "4317", 2 }, + { "4318", -20 }, + { "4319", -30 }, + { "4320", -35 }, + { "4321", 1 }, + { "4322", 1 }, + { "4323", 1 }, + { "4324", 10 }, + { "4325", 10 }, + { "4326", 6 }, + { "4330", -7 }, + { "4331", -7 }, + { "4332", -7 }, + { "4333", -7 }, + + { "7001", 13 }, + { "7002", -30 }, + { "7003", 10 }, + { "7004", -4 }, + { "7005", -12 }, + { "7006", 6 }, + { "7007", -12 }, + { "7008", -3 }, + { "7009", -10 }, + { "7010", -2 }, + { "7011", -10 }, + { "7020", -20 }, + { "7021", -20 }, + { "7022", -20 }, + { "7023", -30 }, + { "7040", 4 }, + { "7041", -4 }, + { "7240", -20 }, + { "7241", 2 }, + { "7242", -25 }, + { "7250", 8 }, + { "7251", 12 }, + { "7252", 1 }, + { "7253", -40 }, + { "7254", -40 }, + { "7255", -10 }, + { "7256", -90 }, + { "7257", -70 }, + { "7258", 3 }, + { "7259", -40 }, + + { "8001", 14 }, + { "8002", -20 }, + { "8003", -30 }, + { "8004", -30 }, + { "8005", 6 }, + { "8006", 18 }, + { "8007", -34 }, + { "8008", -12 }, + { "8009", -50 }, + { "8010", -30 }, + { "8011", -12 }, + { "8012", -20 }, + { "8013", -25 }, + { "8014", -25 }, + { "8017", 18 }, + { "8018", 18 }, + { "8019", -10 }, + { "8020", -25 }, + { "8026", 18 }, + { "8030", -90 }, + { "8040", 15 }, + { "8041", 15 }, + { "8042", 32 }, + { "8043", -20 }, + { "8110", -70 }, + { "8111", 4 }, + { "8112", -70 }, + { "8200", -70 }, +}; + +std::string HRIFromGS1(std::string_view gs1) +{ + constexpr char GS = 29; // GS character (29 / 0x1D) + + std::string_view rem = gs1; + std::string res; + + while (rem.size()) { + const AiInfo* i = FindIf(aiInfos, [&](const AiInfo& i) { return rem.starts_with(i.aiPrefix); }); + if (i == std::end(aiInfos)) + return {}; + + int aiSize = i->aiSize(); + if (Size(rem) < aiSize) + return {}; + + res += '('; + res += rem.substr(0, aiSize); + res += ')'; + rem.remove_prefix(aiSize); + + int fieldSize = i->fieldSize(); + if (i->isVariableLength()) { + auto gsPos = rem.find(GS); +#if 1 + fieldSize = std::min(gsPos == std::string_view::npos ? Size(rem) : narrow_cast(gsPos), fieldSize); +#else + // TODO: ignore the 'max field size' part for now as it breaks rssexpanded-3/13.png? + fieldSize = gsPos == std::string_view::npos ? Size(rem) : narrow_cast(gsPos); +#endif + } + if (fieldSize == 0 || Size(rem) < fieldSize) + return {}; + + res += rem.substr(0, fieldSize); + rem.remove_prefix(fieldSize); + + // See General Specification v22.0 Section 7.8.6.3: "...the processing routine SHALL tolerate a single separator character + // immediately following any element string, whether necessary or not..." + if (Size(rem) && rem.front() == GS) + rem.remove_prefix(1); + } + + return res; +} + +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::string res; + res.reserve(str.size()); + + for (char c : str) { +#if 1 + if (!((c) & ~0x1f) || c == 0x20) + (res += "\xe2\x90") += char(0x80 + c); // Unicode Block “Control Pictures”: 0x2400 + else + res += c; +#else + switch (c) { + case 4: oss << u8"\u1d31\u1d52\u209c"; break; // EOT + case 28: oss << u8"\ua7f3\u209b"; break; // FS + case 29: oss << u8"\u1d33\u209b"; break; // GS + case 30: oss << u8"\u1d3f\u209b"; break; // RS + case 31: oss << u8"\u1d41\u209b"; break; // US + default: oss << c; + } +#endif + } + + return res; +} + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/HRI.h b/tools/chatgui/zxing-cpp/core/src/HRI.h new file mode 100644 index 00000000..0929af0c --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/HRI.h @@ -0,0 +1,16 @@ +/* + * Copyright 2022 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace ZXing { + +std::string HRIFromGS1(std::string_view gs1); +std::string HRIFromISO15434(std::string_view str); + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/HybridBinarizer.cpp b/tools/chatgui/zxing-cpp/core/src/HybridBinarizer.cpp new file mode 100644 index 00000000..4e20df77 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/HybridBinarizer.cpp @@ -0,0 +1,315 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "HybridBinarizer.h" + +#include "BitMatrix.h" +#include "Matrix.h" + +#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 WINDOW_SIZE = BLOCK_SIZE * (1 + 2 * 2); +static constexpr int MIN_DYNAMIC_RANGE = 24; + +HybridBinarizer::HybridBinarizer(const ImageView& iv) : GlobalHistogramBinarizer(iv) {} + +HybridBinarizer::~HybridBinarizer() = default; + +bool HybridBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const +{ +#if 1 + // This is the original "hybrid" behavior: use GlobalHistogram for the 1D case + return GlobalHistogramBinarizer::getPatternRow(row, rotation, res); +#else + // This is an alternative that can be faster in general and perform better in unevenly lit situations like + // https://github.com/zxing-cpp/zxing-cpp/blob/master/test/samples/ean13-2/21.png. That said, it fairs + // worse in borderline low resolution situations. With the current black box sample set we'd loose 94 + // test cases while gaining 53 others. + auto bits = getBitMatrix(); + if (bits) + GetPatternRow(*bits, row, res, rotation % 180 != 0); + return bits != nullptr; +#endif +} + +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, + int rowStride) +{ + Matrix blackPoints(subWidth, subHeight); + + for (int y = 0; y < subHeight; y++) { + int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE); + for (int x = 0; x < subWidth; x++) { + int xoffset = std::min(x * BLOCK_SIZE, width - BLOCK_SIZE); + int sum = 0; + uint8_t min = luminances[yoffset * rowStride + xoffset]; + uint8_t max = min; + for (int yy = 0, offset = yoffset * rowStride + xoffset; yy < BLOCK_SIZE; yy++, offset += rowStride) { + for (int xx = 0; xx < BLOCK_SIZE; xx++) { + auto pixel = luminances[offset + xx]; + sum += pixel; + if (pixel < min) + min = pixel; + if (pixel > max) + max = pixel; + } + // short-circuit min/max tests once dynamic range is met + if (max - min > MIN_DYNAMIC_RANGE) { + // finish the rest of the rows quickly + for (yy++, offset += rowStride; yy < BLOCK_SIZE; yy++, offset += rowStride) { + for (int xx = 0; xx < BLOCK_SIZE; xx++) { + sum += luminances[offset + xx]; + } + } + } + } + + // The default estimate is the average of the values in the block. + int average = sum / (BLOCK_SIZE * BLOCK_SIZE); + if (max - min <= MIN_DYNAMIC_RANGE) { + // If variation within the block is low, assume this is a block with only light or only + // dark pixels. In that case we do not want to use the average, as it would divide this + // low contrast area into black and white pixels, essentially creating data out of noise. + // + // The default assumption is that the block is light/background. Since no estimate for + // the level of dark pixels exists locally, use half the min for the block. + average = min / 2; + + if (y > 0 && x > 0) { + // Correct the "white background" assumption for blocks that have neighbors by comparing + // the pixels in this block to the previously calculated black points. This is based on + // the fact that dark barcode symbology is always surrounded by some amount of light + // background for which reasonable black point estimates were made. The bp estimated at + // the boundaries is used for the interior. + + // The (min < bp) is arbitrary but works better than other heuristics that were tried. + int averageNeighborBlackPoint = + (blackPoints(x, y - 1) + (2 * blackPoints(x - 1, y)) + blackPoints(x - 1, y - 1)) / 4; + if (min < averageNeighborBlackPoint) { + average = averageNeighborBlackPoint; + } + } + } + blackPoints(x, y) = average; + } + } + return blackPoints; +} + +/** +* 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) +{ + auto matrix = std::make_shared(width, height); + +#ifdef PRINT_DEBUG + Matrix out(width, height); + Matrix out2(width, height); +#endif + + for (int y = 0; y < subHeight; y++) { + int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE); + for (int x = 0; x < subWidth; x++) { + int xoffset = std::min(x * BLOCK_SIZE, width - BLOCK_SIZE); + int left = std::clamp(x, 2, subWidth - 3); + int top = std::clamp(y, 2, subHeight - 3); + int sum = 0; + for (int dy = -2; dy <= 2; ++dy) { + for (int dx = -2; dx <= 2; ++dx) { + sum += blackPoints(left + dx, top + dy); + } + } + int average = sum / 25; + ThresholdBlock(luminances, xoffset, yoffset, average, rowStride, *matrix); + +#ifdef PRINT_DEBUG + for (int yy = 0; yy < 8; ++yy) + for (int xx = 0; xx < 8; ++xx) { + out.set(xoffset + xx, yoffset + yy, blackPoints(x, y)); + out2.set(xoffset + xx, yoffset + yy, average); + } +#endif + } + } + +#ifdef PRINT_DEBUG + std::ofstream file("thresholds.pnm"); + file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file.write(reinterpret_cast(out.data()), out.size()); + std::ofstream file2("thresholds_avg.pnm"); + file2 << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file2.write(reinterpret_cast(out2.data()), out2.size()); +#endif + + return matrix; +} + +#else + +// Subdivide the image in blocks of BLOCK_SIZE and calculate one threshold 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 remaining 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 remaining gaps of (very large) no-contrast regions + auto last = out.begin() - 1; + for (auto* i = out.begin(); i != out.end(); ++i) { + if (*i) { + if (last != i - 1) + std::fill(last + 1, i, *i); + last = i; + } + } + std::fill(last + 1, out.end(), *(std::max(last, out.begin()))); + + return out; +} + +static std::shared_ptr ThresholdImage(const ImageView iv, const Matrix& thresholds) +{ + auto matrix = std::make_shared(iv.width(), iv.height()); + +#ifdef PRINT_DEBUG + Matrix out(iv.width(), iv.height()); +#endif + + for (int y = 0; y < thresholds.height(); y++) { + int yoffset = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE); + for (int x = 0; x < thresholds.width(); x++) { + int xoffset = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE); + ThresholdBlock(iv.data(), xoffset, yoffset, thresholds(x, y), iv.rowStride(), *matrix); + +#ifdef PRINT_DEBUG + for (int yy = 0; yy < 8; ++yy) + for (int xx = 0; xx < 8; ++xx) + out.set(xoffset + xx, yoffset + yy, thresholds(x, y)); +#endif + } + } + +#ifdef PRINT_DEBUG + std::ofstream file("thresholds_new.pnm"); + file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file.write(reinterpret_cast(out.data()), out.size()); +#endif + + return matrix; +} + +#endif + +std::shared_ptr HybridBinarizer::getBlackMatrix() const +{ + if (width() >= WINDOW_SIZE && height() >= WINDOW_SIZE) { +#ifdef USE_NEW_ALGORITHM + auto thrs = SmoothThresholds(BlockThresholds(_buffer)); + if (std::ranges::max(thrs) == 0) + return GlobalHistogramBinarizer::getBlackMatrix(); + 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(); + } +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/HybridBinarizer.h b/tools/chatgui/zxing-cpp/core/src/HybridBinarizer.h new file mode 100644 index 00000000..3175fa63 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/HybridBinarizer.h @@ -0,0 +1,40 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "GlobalHistogramBinarizer.h" + +namespace ZXing { + +/** +* This class implements a local thresholding algorithm, which while slower than the +* GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for +* high frequency images of barcodes with black data on white backgrounds. For this application, +* it does a much better job than a global blackpoint with severe shadows and gradients. +* However it tends to produce artifacts on lower frequency images and is therefore not +* a good general purpose binarizer for uses outside ZXing. +* +* This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, +* and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already +* inherently local, and only fails for horizontal gradients. We can revisit that problem later, +* but for now it was not a win to use local blocks for 1D. +* +* This Binarizer is the default for the unit tests and the recommended class for library users. +* +* @author dswitkin@google.com (Daniel Switkin) +*/ +class HybridBinarizer : public GlobalHistogramBinarizer +{ +public: + explicit HybridBinarizer(const ImageView& iv); + ~HybridBinarizer() override; + + bool getPatternRow(int row, int rotation, PatternRow &res) const override; + std::shared_ptr getBlackMatrix() const override; +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ImageView.h b/tools/chatgui/zxing-cpp/core/src/ImageView.h new file mode 100644 index 00000000..2b109f01 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ImageView.h @@ -0,0 +1,168 @@ +/* +* Copyright 2019 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include +#include + +namespace ZXing { + +/// @brief Supported image formats for ImageView. The format encodes the pixel format and layout information. +enum class ImageFormat : uint32_t +{ + None = 0, + Lum = 0x01000000, + LumA = 0x02000000, + RGB = 0x03000102, + BGR = 0x03020100, + 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, +}; + +/// @cond INTERNAL +constexpr inline int PixStride(ImageFormat format) { return (static_cast(format) >> 3*8) & 0xFF; } +constexpr inline int RedIndex(ImageFormat format) { return (static_cast(format) >> 2*8) & 0xFF; } +constexpr inline int GreenIndex(ImageFormat format) { return (static_cast(format) >> 1*8) & 0xFF; } +constexpr inline int BlueIndex(ImageFormat format) { return (static_cast(format) >> 0*8) & 0xFF; } + +constexpr inline uint8_t RGBToLum(unsigned r, unsigned g, unsigned b) +{ + // .299R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC), + // (306*R) >> 10 is approximately equal to R*0.299, and so on. + // 0x200 >> 10 is 0.5, it implements rounding. + return static_cast((306 * r + 601 * g + 117 * b + 0x200) >> 10); +} +/// @endcond + +/** + * @brief Simple class that stores a non-owning const pointer to image data plus layout and format information. + */ +class ImageView +{ +/// @cond INTERNAL +protected: + const uint8_t* _data = nullptr; + ImageFormat _format = ImageFormat::None; + int _width = 0, _height = 0, _pixStride = 0, _rowStride = 0; +/// @endcond + +public: + /** + * ImageView default constructor creates a 'null' image view + */ + ImageView() = default; + + /** + * ImageView constructor + * + * @param data pointer to image buffer + * @param width image width in pixels + * @param height image height in pixels + * @param format image/pixel format + * @param rowStride optional row stride in bytes, default is width * pixStride + * @param pixStride optional pixel stride in bytes, default is calculated from format + */ + ImageView(const uint8_t* data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) + : _data(data), + _format(format), + _width(width), + _height(height), + _pixStride(pixStride ? pixStride : PixStride(format)), + _rowStride(rowStride ? rowStride : width * _pixStride) + { + 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. + * + * This constructor checks if the provided size is consistent with the width, height, row stride and pixel stride + * parameters to prevent out of bounds access when using the ImageView. + * + * @param data pointer to image buffer + * @param size size of the image buffer in bytes + * @param width image width in pixels + * @param height image height in pixels + * @param format image/pixel format + * @param rowStride optional row stride in bytes, default is width * pixStride + * @param pixStride optional pixel stride in bytes, default is calculated from format + */ + 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; } + int pixStride() const { return _pixStride; } + 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; } + + /// Create a new ImageView that is a cropped version of this one. + /// Negative width/height will be interpreted as "until the end of the line/image". + ImageView cropped(int left, int top, int width, int height) const + { + 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}; + } + + /// Create a new ImageView that is a rotated version of this one. + /// Rotation is clockwise and only supports 90 degree steps. + ImageView rotated(int degree) const + { + switch ((degree + 360) % 360) { + case 90: return {data(0, _height - 1), _height, _width, _format, _pixStride, -_rowStride}; + case 180: return {data(_width - 1, _height - 1), _width, _height, _format, -_rowStride, -_pixStride}; + case 270: return {data(_width - 1, 0), _height, _width, _format, -_pixStride, _rowStride}; + default: return *this; + } + } + + /// Create a new ImageView that is a subsampled version of this one. + /// The subsampled image will have width/height of original divided by scale. + ImageView subsampled(int scale) const + { + return {_data, _width / scale, _height / scale, _format, _rowStride * scale, _pixStride * scale}; + } + +}; + +/** + * @brief Simple class that inherits ImageView but owns the image data and frees it on destruction. + */ +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/tools/chatgui/zxing-cpp/core/src/JSON.cpp b/tools/chatgui/zxing-cpp/core/src/JSON.cpp new file mode 100644 index 00000000..1417015f --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/JSON.cpp @@ -0,0 +1,154 @@ +/* +* Copyright 2025 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "JSON.h" + +#include "ZXAlgorithms.h" + +#include + +// This code is trying to find the value of a key-value pair in a string of those. +// The input could be valid JSON, like '{"key": "val"}' or a stripped down version like +// 'key:val'. This is also compatible with the string serialization of a python dictionary. +// For convenience, also 'key=val' is supported, key is checked case insensitive and '_' is ignored. +// This could easily be done with the following regex (see below). +// But using std::regex adds 140k+ to the binary size. ctre is _very_ nice to use and has +// the same binary footprint as the hand-roled code below! But I don't want to add +// 5k lines of c++ code for that. + +// #define ZXING_USE_CTRE +#ifdef ZXING_USE_CTRE +#pragma GCC diagnostic ignored "-Wundef" // see https://github.com/hanickadot/compile-time-regular-expressions/issues/340 +#include "ctre.hpp" +#pragma GCC diagnostic error "-Wundef" + +static constexpr auto PATTERN = + ctll::fixed_string{R"(["']?([[:alpha:]][[:alnum:]]*)["']?\s*(?:[:]\s*["']?([[:alnum:]]+)["']?)?(?:,|\}|$))"}; +#endif + +namespace ZXing { + +// Trim whitespace and quotes/braces from both ends +inline std::string_view Trim(std::string_view sv) +{ + return TrimWS(sv, " \t\n\r\"'{}"); +} + +inline bool IsEqualIgnoreCaseAndUnderscore(std::string_view a, std::string_view b) +{ + return IsEqualIgnoreCaseAnd(a, b, "_"); +} + +std::string_view JsonFind(std::string_view json, std::string_view key) +{ +#ifdef ZXING_USE_CTRE + for (auto [ma, mk, mv] : ctre::search_all(json)) + if (IsEqualCaseInsensitive(key, mk)) + return mv.size() ? mv.to_view() : std::string_view(mk.data(), 0); + + return {}; +#else + json = Trim(json); + + // TODO: use something like ForEachToken here? + while (!json.empty()) { + auto posComma = json.find(','); + auto pair = Trim(json.substr(0, posComma)); + + if (IsEqualIgnoreCaseAndUnderscore(pair, key)) + return {pair.data(), 0}; // return non-null data to signal that we found the key + + auto posSep = pair.find_first_of(":="); + + if (posSep != std::string_view::npos && IsEqualIgnoreCaseAndUnderscore(Trim(pair.substr(0, posSep)), key)) + return Trim(pair.substr(posSep + 1)); + + json = (posComma == std::string_view::npos) ? std::string_view{} : json.substr(posComma + 1); + } + + return {}; +#endif +} + +std::string JsonEscapeStr(std::string_view str) +{ + std::string res; + res.reserve(str.size() + 10); + + for (unsigned char c : str) { + switch (c) { + case '\"': res += "\\\""; break; + case '\\': res += "\\\\"; break; + case '\b': res += "\\b"; break; + case '\f': res += "\\f"; break; + case '\n': res += "\\n"; break; + case '\r': res += "\\r"; break; + case '\t': res += "\\t"; break; + default: + if (c <= 0x1F) { + char buf[7]; + std::snprintf(buf, sizeof(buf), "\\u%04X", c); + res += buf; + } else { + res += c; + } + break; + } + } + + return res; +} + +std::string JsonUnEscapeStr(std::string_view str) +{ + std::string res; + res.reserve(str.size()); + + for (size_t i = 0; i < str.size(); ++i) { + char c = str[i]; + if (c == '\\') { + if (++i >= str.size()) + throw std::runtime_error("Invalid escape sequence"); + + char esc = str[i]; + switch (esc) { + case '"': res += '\"'; break; + case '\\': res += '\\'; break; + case '/': res += '/'; break; + case 'b': res += '\b'; break; + case 'f': res += '\f'; break; + case 'n': res += '\n'; break; + case 'r': res += '\r'; break; + case 't': res += '\t'; break; + case 'u': { + if (i + 4 >= str.size()) + throw std::runtime_error("Incomplete \\u escape"); + + uint32_t code = 0; + auto first = str.data() + i + 1; + auto last = first + 4; + + auto [ptr, ec] = std::from_chars(first, last, code, 16); + if (ec != std::errc() || ptr != last) + throw std::runtime_error("Failed to parse hex code"); + + if (code > 0x1F) + throw std::runtime_error("Unexpected code point in \\u escape"); + + res += static_cast(code); + i += 4; + break; + } + default: throw std::runtime_error("Unknown escape sequence"); + } + } else { + res += c; + } + } + + return res; +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/JSON.h b/tools/chatgui/zxing-cpp/core/src/JSON.h new file mode 100644 index 00000000..6f978720 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/JSON.h @@ -0,0 +1,66 @@ +/* +* Copyright 2025 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ZXAlgorithms.h" + +#include +#ifdef __cpp_lib_format // not available on gcc 12 +#include +#endif +#include +#include +#include + +namespace ZXing { + +std::string JsonEscapeStr(std::string_view str); +std::string JsonUnEscapeStr(std::string_view str); + +template +inline std::string JsonProp(std::string_view key, const T& val, const T& ignore = {}) +{ + #define ZX_JSON_KEY_VAL(...) StrCat("\"", key, "\":", __VA_ARGS__, ',') +#if !defined(__cpp_lib_to_chars) || !defined(__cpp_lib_format) // not available on older macOS / gcc 12 + #define ZX_JSON_FMT_FLOAT(val) (std::to_string(int(val)) + '.' + std::to_string(int(val * 100) % 100)) +#else + #define ZX_JSON_FMT_FLOAT(val) std::format("{:.2f}", val) +#endif + + if constexpr (std::is_same_v) + return val != ignore ? ZX_JSON_KEY_VAL(val ? "true" : "false") : ""; + else if constexpr (std::is_floating_point_v) + return val != ignore ? ZX_JSON_KEY_VAL(ZX_JSON_FMT_FLOAT(val)) : ""; + else if constexpr (std::is_integral_v) + return val != ignore ? ZX_JSON_KEY_VAL(std::to_string(val)) : ""; + else if constexpr (std::is_convertible_v) + return std::string_view(val) != std::string_view(ignore) ? ZX_JSON_KEY_VAL("\"", JsonEscapeStr(val), "\"") : ""; + else + static_assert("unsupported JSON value type"); + #undef ZX_JSON_KEY_VAL +} + +// Finds the (potentially escaped / raw) value of the provided key +std::string_view JsonFind(std::string_view json, std::string_view key); + +template +inline std::optional JsonGet(std::string_view json, std::string_view key) +{ + auto str = JsonFind(json, key); + if (!str.data()) + return std::nullopt; + + if constexpr (std::is_same_v) + return str.empty() || Contains("1tT", str.front()) ? std::optional(true) : std::nullopt; + else if constexpr (std::is_arithmetic_v) + return str.empty() ? std::nullopt : std::optional(FromString(str)); + else if constexpr (std::is_same_v) + return JsonUnEscapeStr(str); + else + static_assert("unsupported JSON value type"); +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/LocalGrid.cpp b/tools/chatgui/zxing-cpp/core/src/LocalGrid.cpp new file mode 100644 index 00000000..ea437651 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/LocalGrid.cpp @@ -0,0 +1,242 @@ +// Copyright 2026 Axel Waggershauser +// SPDX-License-Identifier: Apache-2.0 + +#include "LocalGrid.h" + +#include "LogMatrix.h" +#include "StdGenerator.h" +#include "ZXAlgorithms.h" + +#include +#include +#include +#include +#include + +#ifndef PRINT_DEBUG +#define printf(...){} +#endif + +namespace ZXing { + +// returns the average of the largest cluster of values where cluster is defined as values that are within threshold of each other +double clusterAvg(std::ranges::range auto& v, double threshold) +{ + std::ranges::sort(v); + size_t bestStart = 0, bestLen = 0, start = 0; + for (size_t end = 0; end < v.size(); ++end) { + while (v[end] - v[start] >= threshold) + ++start; + if (end - start + 1 > bestLen) { + bestLen = end - start + 1; + bestStart = start; + } + } + double sum = 0; + for (size_t i = bestStart; i < bestStart + bestLen; ++i) + sum += v[i]; + sum /= bestLen; + +#ifdef PRINT_DEBUG + printf("ds: "); + for (auto d : v) + printf("%5.2f ", d); + printf(" -> len: %zu, avg: %5.2f\n", bestLen, sum); +#endif + return sum; +}; + +void LocalGrid::adjustOriginAndStep(PointF& step, int radius, const std::span offsets) +{ + auto modSize = length(step); + int limit = int(6 * modSize); + auto dir = bresenhamDirection(step); + modSize /= length(dir); // mod size in terms of steps in the given direction + printf("step: (%.2f, %.2f) %.2f: ", step.x, step.y, modSize); + + auto nearestHalfResidual = [](double n, double s) { return n - (std::round((n - s / 2) / s) * s + s / 2); }; + + struct DistMod { + double dist, modSize; + }; + thread_local std::vector distMod; + distMod.clear(); + distMod.reserve(offsets.size() * radius + 1); + for (int r = 0; r <= radius; ++r) + for (auto offset : offsets) { + auto start = origin + r * offset; + auto startC = centered(start); + int stepsPos = BitMatrixCursorF(*img, startC, dir).stepToEdge(1, limit); + int stepsNeg = BitMatrixCursorF(*img, startC, -dir).stepToEdge(1, limit); + auto distPos = stepsPos - dot(start - startC, dir) - 0.5; // +0.5 because the center of the pixel is at .5, .5 + auto distNeg = stepsNeg - dot(start - startC, -dir) - 0.5; + + if (stepsPos && stepsNeg) { + int blockSize = stepsPos + stepsNeg - 1; + auto localModSize = blockSize / std::max(1.0, std::round(blockSize / modSize)); + distMod.emplace_back(distPos, blockSize / modSize < 5 ? localModSize : 0.0); + // if (r == 0 && (std::min(distNeg, distPos) < modSize / 4)) { + // origin += (distNeg < distPos ? 1 : -1) * modSize / 4 * dir; + // continue; + // } + } + else if (stepsPos) + distMod.emplace_back(distPos, 0.0); + else if (stepsNeg) + distMod.emplace_back(-distNeg, 0.0); + + printf("+%.2f -%.2f (%.1f) | ", distPos, distNeg, distMod.empty() ? 0.0 : distMod.back().modSize); + + if (r == 0) + break; // only do the center point once + } + + // calcuate the average local module size from the points where we found one... + double localModSize = 0.0; + int n = 0; + for (const auto& t : distMod | std::views::filter([](const DistMod& t) { return t.modSize > 0; })) + localModSize += t.modSize, ++n; + if (n == 0) + return; + localModSize /= n; + printf("\nlocal mod size: %.2f\n", localModSize); + + // ... and use it for the points where we didn't find one + thread_local std::vector d; + d.clear(); + d.reserve(distMod.size()); + for (auto& t : distMod) { + if (t.modSize == 0.0) + t.modSize = localModSize; + d.push_back(t.dist < 0 ? -nearestHalfResidual(-t.dist, t.modSize) : nearestHalfResidual(t.dist, t.modSize)); + printf("%.2f (%.1f) -> %.2f | ", t.dist, t.modSize, d.back()); + } + printf("\n"); + + origin += clusterAvg(d, modSize / 2) * dir; + + // if we found local mod sizes for each point (hopefully at the timing pattern crosses), we update the step size + if (n == Size(distMod) && std::abs(localModSize - modSize) > modSize * 0.1) { + step = localModSize / modSize * step; + printf(" adjusted mod size from %.2f to %.2f\n", modSize, localModSize); + } +} + +LocalGrid::LocalGrid(const BitMatrix& image, const PerspectiveTransform& mod2Pix, PointI p, PointI dim, PointI offset) + : img(&image), dim(dim), center(p) +{ + origin = mod2Pix(centered(p)); + stepX = mod2Pix(centered(p) + PointF{1, 0}) - origin; + stepY = mod2Pix(centered(p) + PointF{0, 1}) - origin; + + printf("LocalGrid @ (%d, %d), initial origin: (%.2f, %.2f), offset: (%d, %d), stepX: (%.2f, %.2f), stepY: (%.2f, %.2f)\n", + center.x, center.y, origin.x, origin.y, offset.x, offset.y, stepX.x, stepX.y, stepY.x, stepY.y); + log(origin, 3); + + auto offsets = std::array{-stepX, -stepY, stepX, stepY}; // works better for DataMatrix (especially near the symbol edges) + // auto offsets = std::array{-stepX - stepY, stepX - stepY, stepX + stepY, -stepX + stepY}; + // auto offsets = std::array{-stepX, -stepY, stepX, stepY, -stepX - stepY, stepX - stepY, stepX + stepY, -stepX + stepY}; + + // evaluate the image at origin + offset (allows to effectively work near the border of the symbol) + origin = getPos(PointF(offset)); + for (int i = 0; i < 2; ++i) { + adjustOriginAndStep(stepX, 2, offsets); + adjustOriginAndStep(stepY, 2, offsets); + printf("\n"); + } + origin = getPos(PointF(-offset)); +} + +bool LocalGrid::isTimingPatternCross(PointI p, bool isBlack, int radius, int errorThreshold) +{ + // TODO: look into replacing this with something along the lines of CheckSymmetricAztecCenterPattern + auto wrapOffset = [&](int center, int offset, int dim) { + int pos = center + offset; + return pos < 0 ? radius - pos : (pos >= dim ? -(radius + (pos - dim) + 1) : offset); + }; + + int errors = 0; + for (int r = 0; r <= radius; ++r) { + auto check = [&](int x, int y) { + x = wrapOffset(center.x, x, dim.x); + y = wrapOffset(center.y, y, dim.y); + auto d = PointI(x, y); + errors += !findValue(p + d, d, Value((x + y) % 2 == (isBlack ? 0 : 1))); + }; + check(-r, 0); + check(r, 0); + check(0, -r); + check(0, r); + if (errors > errorThreshold) + return false; + } + return errors <= errorThreshold; +}; + +std::optional LocalGrid::findTimingPatternCross(bool isBlack, int radius) +{ + for (auto p : Spiral(3)) { + // check if there is a timing pattern cross candidate centered at p with half the radius + if (isTimingPatternCross(p, isBlack, radius / 2)) { + auto original = origin; + origin = getPos(p); + // adjust origin and step with full radius and only in the direction of the timing pattern + printf("timing pattern:\n"); + adjustOriginAndStep(stepX, radius, std::array{-stepX, stepX}); + adjustOriginAndStep(stepY, radius, std::array{-stepY, stepY}); + + // check again, now with the full radius, to make sure we are correctly aligned to the timing pattern + if (isTimingPatternCross(PointI{0, 0}, isBlack, radius)) + return origin; + + origin = original; + } + } + return {}; +} + +bool LocalGrid::findPattern(int radius, PointI timingStart, Directions timingDirs, PointI blackStart, Directions blackDirs, + PointI whiteStart, Directions whiteDirs) +{ + auto isPatternAt = [&](PointI p) { + for (int r = 0; r <= radius; ++r) { + for (auto d : timingDirs) + if (!findValue(p + timingStart + r * d, d, Value(r % 2 == 1))) + return false; + for (auto d : blackDirs) + if (!findValue(p + blackStart + r * d, d, Value(true))) + return false; + for (auto d : whiteDirs) + if (!findValue(p + whiteStart + r * d, d, Value(false))) + return false; + } + return true; + }; + + for (auto p : Spiral(3)) { + if (isPatternAt(p)) { + auto original = origin; + origin = getPos(PointF(p)); + // adjust origin and step with full radius and only in the direction of the timing pattern + printf("found pattern at (%.2f, %.2f)\n", origin.x, origin.y); + std::vector stepsX, stepsY; + for (auto d : timingDirs) + d.y == 0 ? stepsX.push_back(d.x * stepX) : stepsY.push_back(d.y * stepY); + + if (!stepsX.empty()) + adjustOriginAndStep(stepX, radius, stepsX); + if (!stepsY.empty()) + adjustOriginAndStep(stepY, radius, stepsY); + if ((!stepsX.empty() || !stepsY.empty()) && !isPatternAt(PointI{0, 0})) { + origin = original; + printf("pattern lost after adjusting for timing pattern, reverting origin\n"); + } + printf("\n"); + return true; + } + } + printf("\n"); + return false; +} + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/LocalGrid.h b/tools/chatgui/zxing-cpp/core/src/LocalGrid.h new file mode 100644 index 00000000..299475c7 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/LocalGrid.h @@ -0,0 +1,91 @@ +// Copyright 2026 Axel Waggershauser +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BitMatrixCursor.h" +#include "BitMatrix.h" +#include "LogMatrix.h" +#include "Point.h" +#include "PerspectiveTransform.h" + +#include +#include + +namespace ZXing { + +/** + * @brief Represents a local grid for sampling and analyzing regions within a BitMatrix image. + * + * The LocalGrid class tries to find the center of a module and step vectors that point to the next module in x and y direction. + * It provides methods to get the position of a point in the grid and to sample values from the image at those positions. + * It supports searching for timing pattern crosses and specific (alignment) patterns within the grid, which updates the origin and + * step vectors accordingly. + */ +class LocalGrid +{ + const BitMatrix* img; + PointF origin, stepX, stepY; + PointI dim, center; + + using Value = BitMatrixCursorF::Value; + + void adjustOriginAndStep(PointF& dir, int radius, std::span offsets); + bool isTimingPatternCross(PointI p, bool isBlack, int radius, int errorThreshold = 0); + bool findValue(PointI p, PointI dir, Value v) + { + // check if there is a value v at p or a quarter module before or after p or p is outside the image + auto d = PointF(dir.x ? 0.25 : 0.0, dir.y ? 0.25 : 0.0); + return !img->isIn(getPos(p)) || get(p) == v || get(p + d) == v || get(p - d) == v; + }; + +public: + + /** + * @brief Constructs a LocalGrid object for mapping a region of a BitMatrix using a perspective transform. + * + * @param image The source BitMatrix representing the image data. + * @param mod2Pix The (global) PerspectiveTransform used to map module coordinates to pixel coordinates. + * @param p The logical position of the grid origin in module coordinates. + * @param dim The dimensions (width and height) of the (global) grid in modules. + * @param offset Optional offset in modules to specify the starting position of the search for the module center. + */ + LocalGrid(const BitMatrix& image, const PerspectiveTransform& mod2Pix, PointI p, PointI dim, PointI offset = {}); + + inline PointF getPos(PointF p = {}) const { return origin + p.x * stepX + p.y * stepY; } + inline PointF getPos(PointI p) const { return getPos(PointF(p.x, p.y)); } + + inline Value get(PointF p) const + { + auto q = getPos(p); + log(q, 4); + return img->isIn(q) ? Value{img->get(q)} : Value{}; + } + + inline Value get(PointI p) const { return get(PointF(p.x, p.y)); } + + /** + * @brief Finds the center of a timing pattern cross in the local grid. + * + * This function searches for the center point of a timing pattern cross, which is a key feature in Aztec code detection, within a + * specified radius. + * + * @param isBlack Indicates whether the central module of the timing pattern cross is expected to be black or white. + * @param radius The length of the timing pattern cross arms. + * @return An optional PointF representing the center of the timing pattern cross if found; std::nullopt otherwise. + */ + std::optional findTimingPatternCross(bool isBlack, int radius); + + using Directions = std::span; + + /** + * @brief Searches for a specific pattern within a grid based on provided parameters. + * + * This function attempts to locate a pattern in the grid by starting from given points + * and following specified directions for timing, black, and white modules. + */ + bool findPattern(int radius, PointI timingStart, Directions timingDirs, PointI blackStart, Directions blackDirs, PointI whiteStart, + Directions whiteDirs); +}; + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/LogMatrix.h b/tools/chatgui/zxing-cpp/core/src/LogMatrix.h new file mode 100644 index 00000000..6d9914b2 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/LogMatrix.h @@ -0,0 +1,104 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BitMatrix.h" +#include "Matrix.h" +#include "Point.h" + +#include +#include +#include +#include + +namespace ZXing { + +#ifdef PRINT_DEBUG + +class LogMatrix +{ + using LogBuffer = Matrix; + LogBuffer _log; + const BitMatrix* _image = nullptr; + int _scale = 1; + +public: + void init(const BitMatrix* image, int scale = 1) + { + _image = image; + _scale = scale; + _log = LogBuffer(_image->width() * _scale, _image->height() * _scale); + } + + void write(const char* fn) + { + assert(_image); + FILE* f = fopen(fn, "wb"); + + // Write PPM header, P5 == grey, P6 == rgb + fprintf(f, "P6\n%d %d\n255\n", _log.width(), _log.height()); + + // Write pixels + for (int y = 0; y < _log.height(); ++y) + for (int x = 0; x < _log.width(); ++x) { + unsigned char pix[3]; + unsigned char &r = pix[0], &g = pix[1], &b = pix[2]; + r = g = b = _image->get(x / _scale, y / _scale) ? 0 : 255; + if (_scale > 1 && x % _scale == _scale / 2 && y % _scale == _scale / 2) + r = g = b = r ? 230 : 50; + switch (_log.get(x, y)) { + case 1: r = g = b = _scale > 1 ? 128 : (r ? 230 : 50); break; + case 2: r = b = 50, g = 220; break; + case 3: g = r = 100, b = 250; break; + case 4: g = b = 100, r = 250; break; + } + fwrite(&pix, 3, 1, f); + } + fclose(f); + } + + template + void operator()(const PointT& p, int color = 1) + { + if (_image && _image->isIn(p)) + _log.set(static_cast(p.x * _scale), static_cast(p.y * _scale), color); + } + + void operator()(const PointT& p, int color) + { + operator()(centered(p), color); + } + + template + void operator()(const std::vector>& points, int color = 2) + { + for (auto p : points) + operator()(p, color); + } +}; + +extern LogMatrix log; + +class LogMatrixWriter +{ + LogMatrix &log; + std::string fn; + +public: + LogMatrixWriter(LogMatrix& log, const BitMatrix& image, int scale, std::string fn) : log(log), fn(fn) + { + log.init(&image, scale); + } + ~LogMatrixWriter() { log.write(fn.c_str()); } +}; + +#else + +template void log(PointT, int = 0) {} + +#endif + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/Matrix.h b/tools/chatgui/zxing-cpp/core/src/Matrix.h new file mode 100644 index 00000000..4990e719 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Matrix.h @@ -0,0 +1,125 @@ +/* +* Copyright 2016 Huy Cuong Nguyen +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Point.h" + +#include +#include +#include +#include + +namespace ZXing { + +template +class Matrix +{ +public: + using value_t = T; + +private: + int _width = 0; + int _height = 0; + std::vector _data; + + // Nothing wrong to support it, just to make it explicit, instead of by mistake. + // Use copy() below. + Matrix(const Matrix &) = default; + +public: + Matrix() = default; + +#if defined(__llvm__) || (defined(__GNUC__) && (__GNUC__ > 7)) + __attribute__((no_sanitize("signed-integer-overflow"))) +#endif + Matrix(int width, int height, value_t val = {}) : _width(width), _height(height), _data(_width * _height, val) { + if (width != 0 && int(_data.size()) / width != height) + throw std::invalid_argument("Invalid size: width * height is too big"); + } + + Matrix(Matrix&&) noexcept = default; + Matrix& operator=(Matrix&&) noexcept = default; + Matrix& operator=(const Matrix &) = delete; + + Matrix copy() const { + return *this; + } + + int height() const { + return _height; + } + + int width() const { + return _width; + } + + int size() const { + return int(_data.size()); + } + + value_t& operator()(int x, int y) + { + assert(x >= 0 && x < _width && y >= 0 && y < _height); + return _data[y * _width + x]; + } + + const T& operator()(int x, int y) const + { + assert(x >= 0 && x < _width && y >= 0 && y < _height); + return _data[y * _width + x]; + } + + value_t& operator()(PointI p) { + return operator()(p.x, p.y); + } + + const T& operator()(PointI p) const { + return operator()(p.x, p.y); + } + + const value_t& get(int x, int y) const { + return operator()(x, y); + } + + value_t& set(int x, int y, value_t value) { + return operator()(x, y) = value; + } + + const value_t& get(PointI p) const { + return operator()(p.x, p.y); + } + + value_t& set(PointI p, value_t value) { + return operator()(p.x, p.y) = value; + } + + const value_t* data() const { + return _data.data(); + } + + const value_t* begin() const { + return _data.data(); + } + + const value_t* end() const { + 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); + } +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/MultiFormatReader.cpp b/tools/chatgui/zxing-cpp/core/src/MultiFormatReader.cpp new file mode 100644 index 00000000..4c3f80ef --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/MultiFormatReader.cpp @@ -0,0 +1,107 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "MultiFormatReader.h" + +#include "BarcodeFormat.h" +#include "BinaryBitmap.h" +#include "Reader.h" +#include "ReaderOptions.h" +#include "Version.h" + +#if ZXING_ENABLE_AZTEC +#include "aztec/AZReader.h" +#endif +#if ZXING_ENABLE_DATAMATRIX +#include "datamatrix/DMReader.h" +#endif +#if ZXING_ENABLE_MAXICODE +#include "maxicode/MCReader.h" +#endif +#if ZXING_ENABLE_1D +#include "oned/ODReader.h" +#endif +#if ZXING_ENABLE_PDF417 +#include "pdf417/PDFReader.h" +#include "pdf417/MicroPDFReader.h" +#endif +#if ZXING_ENABLE_QRCODE +#include "qrcode/QRReader.h" +#endif + +#include + +namespace ZXing { + +MultiFormatReader::MultiFormatReader(const ReaderOptions& opts) : _opts(opts) +{ + using enum BarcodeFormat; + + // Put linear readers upfront in "normal" mode +#if ZXING_ENABLE_1D + if (!opts.tryHarder() && opts.hasAnyFormat(AllLinear)) + _readers.emplace_back(new OneD::Reader(opts)); +#endif + +#if ZXING_ENABLE_QRCODE + if (opts.hasAnyFormat(QRCode)) + _readers.emplace_back(new QRCode::Reader(opts, true)); +#endif +#if ZXING_ENABLE_DATAMATRIX + if (opts.hasAnyFormat(DataMatrix)) + _readers.emplace_back(new DataMatrix::Reader(opts, true)); +#endif +#if ZXING_ENABLE_AZTEC + if (opts.hasAnyFormat(Aztec)) + _readers.emplace_back(new Aztec::Reader(opts, true)); +#endif +#if ZXING_ENABLE_PDF417 + if (opts.hasFormat(PDF417 | CompactPDF417)) + _readers.emplace_back(new Pdf417::Reader(opts)); + if (opts.hasFormat(MicroPDF417)) + _readers.emplace_back(new MicroPdf417::Reader(opts)); +#endif +#if ZXING_ENABLE_MAXICODE + if (opts.hasAnyFormat(MaxiCode)) + _readers.emplace_back(new MaxiCode::Reader(opts)); +#endif + + // At end in "try harder" mode +#if ZXING_ENABLE_1D + if (opts.tryHarder() && opts.hasAnyFormat(AllLinear)) + _readers.emplace_back(new OneD::Reader(opts)); +#endif +} + +MultiFormatReader::~MultiFormatReader() = default; + +Barcodes MultiFormatReader::read(const BinaryBitmap& image, int maxSymbols) const +{ + Barcodes res; + + for (const auto& reader : _readers) { + if (image.inverted() && !reader->supportsInversion) + continue; + auto r = reader->read(image, maxSymbols); + if (!_opts.returnErrors()) + std::erase_if(r, [](auto&& s) { return !s.isValid(); }); + maxSymbols -= Size(r); + res.insert(res.end(), std::move_iterator(r.begin()), std::move_iterator(r.end())); + if (maxSymbols <= 0) + break; + } + + // 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); + }); + + return res; +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/MultiFormatReader.h b/tools/chatgui/zxing-cpp/core/src/MultiFormatReader.h new file mode 100644 index 00000000..54309d42 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/MultiFormatReader.h @@ -0,0 +1,34 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Barcode.h" + +#include +#include + +namespace ZXing { + +class Reader; +class BinaryBitmap; +class ReaderOptions; + +class MultiFormatReader +{ +public: + explicit MultiFormatReader(const ReaderOptions& opts); + explicit MultiFormatReader(ReaderOptions&& opts) = delete; + ~MultiFormatReader(); + + Barcodes read(const BinaryBitmap& image, int maxSymbols = 0xFF) const; + +private: + std::vector> _readers; + const ReaderOptions& _opts; +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/MultiFormatWriter.cpp b/tools/chatgui/zxing-cpp/core/src/MultiFormatWriter.cpp new file mode 100644 index 00000000..ddf9dc6e --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/MultiFormatWriter.cpp @@ -0,0 +1,109 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "MultiFormatWriter.h" + +#include "BitMatrix.h" +#include "Utf.h" +#include "Version.h" + +#if ZXING_ENABLE_AZTEC +#include "aztec/AZWriter.h" +#endif +#if ZXING_ENABLE_DATAMATRIX +#include "datamatrix/DMWriter.h" +#endif +#if ZXING_ENABLE_1D +#include "oned/ODCodabarWriter.h" +#include "oned/ODCode128Writer.h" +#include "oned/ODCode39Writer.h" +#include "oned/ODCode93Writer.h" +#include "oned/ODEAN13Writer.h" +#include "oned/ODEAN8Writer.h" +#include "oned/ODITFWriter.h" +#include "oned/ODUPCAWriter.h" +#include "oned/ODUPCEWriter.h" +#endif +#if ZXING_ENABLE_PDF417 +#include "pdf417/PDFWriter.h" +#endif +#if ZXING_ENABLE_QRCODE +#include "qrcode/QRErrorCorrectionLevel.h" +#include "qrcode/QRWriter.h" +#endif + +#include + +namespace ZXing { + +BitMatrix +MultiFormatWriter::encode(const std::wstring& contents, int width, int height) const +{ + [[maybe_unused]] auto exec0 = [&](auto&& writer) { + if (_margin >=0) + writer.setMargin(_margin); + return writer.encode(contents, width, height); + }; + +#if ZXING_ENABLE_AZTEC + auto AztecEccLevel = [&](Aztec::Writer& writer, int eccLevel) { writer.setEccPercent(eccLevel * 100 / 8); }; +#endif +#if ZXING_ENABLE_PDF417 + auto Pdf417EccLevel = [&](Pdf417::Writer& writer, int eccLevel) { writer.setErrorCorrectionLevel(eccLevel); }; +#endif +#if ZXING_ENABLE_QRCODE + auto QRCodeEccLevel = [&](QRCode::Writer& writer, int eccLevel) { + writer.setErrorCorrectionLevel(static_cast(--eccLevel / 2)); + }; +#endif + + [[maybe_unused]] auto exec1 = [&](auto&& writer, auto setEccLevel) { + if (_encoding != CharacterSet::Unknown) + writer.setEncoding(_encoding); + if (_eccLevel >= 0 && _eccLevel <= 8) + setEccLevel(writer, _eccLevel); + return exec0(std::forward(writer)); + }; + + [[maybe_unused]] auto exec2 = [&](auto&& writer) { + if (_encoding != CharacterSet::Unknown) + writer.setEncoding(_encoding); + return exec0(std::forward(writer)); + }; + + switch (_format) { +#if ZXING_ENABLE_AZTEC + case BarcodeFormat::AztecCode: return exec1(Aztec::Writer(), AztecEccLevel); +#endif +#if ZXING_ENABLE_DATAMATRIX + case BarcodeFormat::DataMatrix: return exec2(DataMatrix::Writer()); +#endif +#if ZXING_ENABLE_PDF417 + case BarcodeFormat::PDF417: return exec1(Pdf417::Writer(), Pdf417EccLevel); +#endif +#if ZXING_ENABLE_QRCODE + case BarcodeFormat::QRCode: return exec1(QRCode::Writer(), QRCodeEccLevel); +#endif +#if ZXING_ENABLE_1D + case BarcodeFormat::Codabar: return exec0(OneD::CodabarWriter()); + case BarcodeFormat::Code39: return exec0(OneD::Code39Writer()); + case BarcodeFormat::Code93: return exec0(OneD::Code93Writer()); + case BarcodeFormat::Code128: return exec0(OneD::Code128Writer()); + case BarcodeFormat::EAN8: return exec0(OneD::EAN8Writer()); + case BarcodeFormat::EAN13: return exec0(OneD::EAN13Writer()); + case BarcodeFormat::ITF: return exec0(OneD::ITFWriter()); + case BarcodeFormat::UPCA: return exec0(OneD::UPCAWriter()); + case BarcodeFormat::UPCE: return exec0(OneD::UPCEWriter()); +#endif + default: throw std::invalid_argument("Unsupported format: " + ToString(_format)); + } +} + +BitMatrix MultiFormatWriter::encode(const std::string& contents, int width, int height) const +{ + return encode(FromUtf8(contents), width, height); +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/MultiFormatWriter.h b/tools/chatgui/zxing-cpp/core/src/MultiFormatWriter.h new file mode 100644 index 00000000..2eb47feb --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/MultiFormatWriter.h @@ -0,0 +1,62 @@ +/* +* Copyright 2017 Huy Cuong Nguyen +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BarcodeFormat.h" +#include "CharacterSet.h" + +#include + +namespace ZXing { + +class BitMatrix; + +/** +* This class is here just for convenience as it offers single-point service +* to generate barcodes for all supported formats. As a result, this class +* offer very limited customization compared to what are available in each +* individual encoder. +*/ +class MultiFormatWriter +{ +public: + [[deprecated("use CreateBarcode()")]] explicit MultiFormatWriter(BarcodeFormat format) : _format(format) {} + + /** + * Used for Aztec, PDF417, and QRCode only. + */ + MultiFormatWriter& setEncoding(CharacterSet encoding) { + _encoding = encoding; + return *this; + } + + /** + * Used for Aztec, PDF417, and QRCode only, [0-8]. + */ + MultiFormatWriter& setEccLevel(int level) { + _eccLevel = level; + return *this; + } + + /** + * Used for all formats, sets the minimum number of quiet zone pixels. + */ + MultiFormatWriter& setMargin(int margin) { + _margin = margin; + return *this; + } + + BitMatrix encode(const std::wstring& contents, int width, int height) const; + BitMatrix encode(const std::string& contents, int width, int height) const; + +private: + BarcodeFormat _format; + CharacterSet _encoding = CharacterSet::Unknown; + int _margin = -1; + int _eccLevel = -1; +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/Pattern.h b/tools/chatgui/zxing-cpp/core/src/Pattern.h new file mode 100644 index 00000000..771b1068 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Pattern.h @@ -0,0 +1,427 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Range.h" +#include "ZXAlgorithms.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ZXing { + +using PatternType = uint16_t; +template using Pattern = std::array; +using PatternRow = std::vector; + +class PatternView +{ + using Iterator = PatternRow::const_pointer; + Iterator _data = nullptr; + int _size = 0; + Iterator _base = nullptr; + Iterator _end = nullptr; + +public: + using value_type = PatternRow::value_type; + + PatternView() = default; + + // A PatternRow always starts with the width of whitespace in front of the first black bar. + // The first element of the PatternView is the first bar. + PatternView(const PatternRow& bars) + : _data(bars.data() + 1), _size(Size(bars) - 1), _base(bars.data()), _end(bars.data() + bars.size()) + {} + + PatternView(Iterator data, int size, Iterator base, Iterator end) : _data(data), _size(size), _base(base), _end(end) {} + + template + PatternView(const Pattern& row) : _data(row.data()), _size(N) + {} + + Iterator data() const { return _data; } + Iterator begin() const { return _data; } + Iterator end() const { return _data + _size; } + + value_type operator[](int i) const + { +// assert(i < _count); + return _data[i]; + } + + 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 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; } + bool isValid() const { return isValid(size()); } + int spaceInFront() const { return isAtFirstBar() ? INT_MAX : _data[-1]; } + + template + bool hasQuietZoneBefore(float scale) const + { + return (acceptIfAtFirstBar && isAtFirstBar()) || _data[-1] >= sum() * scale; + } + + template + bool hasQuietZoneAfter(float scale) const + { + return (acceptIfAtLastBar && isAtLastBar()) || _data[_size] >= sum() * scale; + } + + PatternView subView(int offset, int size = 0) const + { +// if(std::abs(size) > count()) +// printf("%d > %d\n", std::abs(size), _count); +// assert(std::abs(size) <= count()); + if (size == 0) + size = _size - offset; + else if (size < 0) + size = _size - offset + size; + return {begin() + offset, std::max(size, 0), _base, _end}; + } + + bool shift(int n) + { + return _data && ((_data += n) + _size <= _end); + } + + bool skipPair() + { + return shift(2); + } + + bool skipSymbol() + { + return shift(_size); + } + + bool skipSingle(int maxWidth) + { + return shift(1) && _data[-1] <= maxWidth; + } + + void extend() + { + _size = std::max(0, narrow_cast(_end - _data)); + } +}; + +/** + * @brief The BarAndSpace struct is a simple 2 element data structure to hold information about bar(s) and space(s). + * + * The operator[](int) can be used in combination with a PatternView + */ +template +struct BarAndSpace +{ + using value_type = T; + T bar = {}, space = {}; + // even index -> bar, odd index -> space + constexpr T& operator[](int i) noexcept { return reinterpret_cast(this)[i & 1]; } + constexpr T operator[](int i) const noexcept { return reinterpret_cast(this)[i & 1]; } + bool isValid() const { return bar != T{} && space != T{}; } +}; + +using BarAndSpaceI = BarAndSpace; + +template +constexpr auto BarAndSpaceSum(const T* view) noexcept +{ + BarAndSpace res; + for (int i = 0; i < LEN; ++i) + res[i] += view[i]; + return res; +} + +/** + * @brief FixedPattern describes a compile-time constant (start/stop) pattern. + * + * N = number of bars/spaces + * SUM = sum over all N elements (size of pattern in modules) + * IS_SPARSE = whether or not the pattern contains '0's denoting 'wide' bars/spaces + */ +template +struct FixedPattern +{ + using value_type = PatternRow::value_type; + value_type _data[N]; + 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 const value_type* begin() const noexcept { return _data; } + constexpr const value_type* end() const noexcept { return _data + N; } + constexpr BarAndSpace sums() const noexcept { return BarAndSpaceSum(_data); } +}; + +template +using FixedSparsePattern = FixedPattern; + +template +double IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, + double minQuietZone = 0, double moduleSizeRef = 0) +{ + if constexpr (E2E) { + auto widths = BarAndSpaceSum(view.data()); + auto sums = pattern.sums(); + BarAndSpace modSize = {widths[0] / sums[0], widths[1] / sums[1]}; + + auto [m, M] = std::minmax(modSize[0], modSize[1]); + if (M > 4 * m) // make sure module sizes of bars and spaces are not too far away from each other + return 0; + + if (minQuietZone && spaceInPixel < minQuietZone * modSize.space) + return 0; + + const BarAndSpace thr = {modSize[0] * .75 + .5, modSize[1] * .6 + .5}; + + for (int x = 0; x < LEN; ++x) + if (std::abs(view[x] - pattern[x] * modSize[x]) > thr[x]) + return 0; + + return (modSize[0] + modSize[1]) / 2; + } + + double width = view.sum(LEN); + if (SUM > LEN && width < SUM) + return 0; + + const auto moduleSize = width / SUM; + + if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1) + return 0; + + if (!moduleSizeRef) + moduleSizeRef = moduleSize; + + // 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 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) + return 0; + + return moduleSize; +} + +template +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 + double width = 0; + for (int x = 0; x < SUM; ++x) + width += view[pattern[x]]; + + const auto moduleSize = width / SUM; + + if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1) + return 0; + + if (!moduleSizeRef) + moduleSizeRef = moduleSize; + + // 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 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) + return 0; + + return moduleSize; +} + +template +bool IsRightGuard(const PatternView& view, const FixedPattern& pattern, double minQuietZone, + double moduleSizeRef = 0) +{ + assert(view.size() == pattern.size()); + if (!view.isValid()) + return false; + int spaceInPixel = view.isAtLastBar() ? std::numeric_limits::max() : *view.end(); + return IsPattern(view, pattern, spaceInPixel, minQuietZone, moduleSizeRef) != 0; +} + +template +PatternView FindLeftGuard(const PatternView& view, int minSize, Pred isGuard) +{ + if (view.size() < minSize) + return {}; + + auto window = view.subView(0, LEN); + if (window.isAtFirstBar() && isGuard(window, std::numeric_limits::max())) + return window; + for (auto end = view.end() - minSize; window.data() < end; window.skipPair()) + if (isGuard(window, window[-1])) + return window; + + return {}; +} + +template +PatternView FindLeftGuard(const PatternView& view, int minSize, const FixedPattern& pattern, + double minQuietZone) +{ + return FindLeftGuard(view, std::max(minSize, LEN), + [&pattern, minQuietZone](const PatternView& window, int spaceInPixel) { + return IsPattern(window, pattern, spaceInPixel, minQuietZone); + }); +} + +template +std::array NormalizedE2EPattern(const PatternView& view, int mods, bool reverse = false) +{ + double moduleSize = static_cast(view.sum(LEN)) / mods; + std::array e2e; + + for (int i = 0; i < LEN - 2; i++) { + 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; +} + +template +std::array NormalizedPattern(const PatternView& view) +{ + double moduleSize = static_cast(view.sum(LEN)) / SUM; +#if 1 + int err = SUM; + std::array is; + std::array rs; + for (int i = 0; i < LEN; i++) { + double v = view[i] / moduleSize; + is[i] = int(v + .5); + rs[i] = v - is[i]; + err -= is[i]; + } + + if (std::abs(err) > 1) + return {}; + + if (err) { + auto mi = err > 0 ? std::ranges::max_element(rs) - rs.begin() + : std::ranges::min_element(rs) - rs.begin(); + 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; +} + +template +void GetPatternRow(Range b_row, PatternRow& p_row) +{ + // TODO: if reactivating the bit-packed array (!ZX_FAST_BIT_STORAGE) should be of interest then the following code could be + // considerably speed up by using a specialized variant along the lines of the old BitArray::getNextSetTo() function that + // was removed between 1.4 and 2.0. + +#if 0 + p_row.reserve(64); + p_row.clear(); + + auto lastPos = b_row.begin(); + if (*lastPos) + p_row.push_back(0); // first value is number of white pixels, here 0 + + for (auto p = b_row.begin() + 1; p != b_row.end(); ++p) + if (bool(*p) != bool(*lastPos)) + p_row.push_back(p - std::exchange(lastPos, p)); + + p_row.push_back(b_row.end() - lastPos); + + if (*lastPos) + p_row.push_back(0); // last value is number of white pixels, here 0 +#else + p_row.resize(b_row.size() + 2); + std::fill(p_row.begin(), p_row.end(), 0); + + auto bitPos = b_row.begin(); + const auto bitPosEnd = b_row.end(); + auto intPos = p_row.data(); + + if (*bitPos) + intPos++; // first value is number of white pixels, here 0 + + // The following code has been observed to cause a speedup of up to 30% on large images on an AVX cpu + // and on an a Google Pixel 3 Android phone. Your mileage may vary. + if constexpr (std::contiguous_iterator && sizeof(std::remove_cv_t>) == 1) { + using simd_t = uint64_t; + const auto* const bitPtrBegin = std::to_address(bitPos); + const auto* const bitPtrEnd = std::to_address(bitPosEnd); + auto* bitPtr = bitPtrBegin; + + while (bitPtr < bitPtrEnd - sizeof(simd_t)) { + auto asSimd0 = LoadU(bitPtr); + auto asSimd1 = LoadU(bitPtr + 1); + auto z = asSimd0 ^ asSimd1; + if (z) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + int step = std::countr_zero(z) / 8 + 1; +#else + int step = std::countl_zero(z) / 8 + 1; +#endif + (*intPos++) += step; + bitPtr += step; + } else { + (*intPos) += sizeof(simd_t); + bitPtr += sizeof(simd_t); + } + } + + bitPos += bitPtr - bitPtrBegin; + } + + while (++bitPos != bitPosEnd) { + ++(*intPos); + intPos += bitPos[0] != bitPos[-1]; + } + ++(*intPos); + + if (bitPos[-1]) + intPos++; + + p_row.resize(intPos - p_row.data() + 1); +#endif +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/PerspectiveTransform.cpp b/tools/chatgui/zxing-cpp/core/src/PerspectiveTransform.cpp new file mode 100644 index 00000000..f3d1aaa2 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/PerspectiveTransform.cpp @@ -0,0 +1,81 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "PerspectiveTransform.h" + +#include + +namespace ZXing { + +PerspectiveTransform PerspectiveTransform::inverse() const +{ + // Here, the adjoint serves as the inverse: + // Adjoint is the transpose of the cofactor matrix: + return { + a22 * a33 - a23 * a32, + a23 * a31 - a21 * a33, + a21 * a32 - a22 * a31, + a13 * a32 - a12 * a33, + a11 * a33 - a13 * a31, + a12 * a31 - a11 * a32, + a12 * a23 - a13 * a22, + a13 * a21 - a11 * a23, + a11 * a22 - a12 * a21 + }; +} + +PerspectiveTransform PerspectiveTransform::times(const PerspectiveTransform& other) const +{ + return { + a11 * other.a11 + a21 * other.a12 + a31 * other.a13, + a11 * other.a21 + a21 * other.a22 + a31 * other.a23, + a11 * other.a31 + a21 * other.a32 + a31 * other.a33, + a12 * other.a11 + a22 * other.a12 + a32 * other.a13, + a12 * other.a21 + a22 * other.a22 + a32 * other.a23, + a12 * other.a31 + a22 * other.a32 + a32 * other.a33, + a13 * other.a11 + a23 * other.a12 + a33 * other.a13, + a13 * other.a21 + a23 * other.a22 + a33 * other.a23, + a13 * other.a31 + a23 * other.a32 + a33 * other.a33 + }; +} + +PerspectiveTransform PerspectiveTransform::UnitSquareTo(const QuadrilateralF& q) +{ + auto [x0, y0, x1, y1, x2, y2, x3, y3] = reinterpret_cast&>(q); + + auto d3 = q[0] - q[1] + q[2] - q[3]; + if (d3 == PointF(0, 0)) { + // Affine + return {x1 - x0, x2 - x1, x0, + y1 - y0, y2 - y1, y0, + 0.0f, 0.0f, 1.0f}; + } else { + auto d1 = q[1] - q[2]; + auto d2 = q[3] - q[2]; + auto denominator = cross(d1, d2); + auto a13 = cross(d3, d2) / denominator; + auto a23 = cross(d1, d3) / denominator; + return {x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0, + y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0, + a13, a23, 1.0f}; + } +} + +PerspectiveTransform::PerspectiveTransform(const QuadrilateralF& src, const QuadrilateralF& dst) +{ + if (!IsConvex(src) || !IsConvex(dst)) + return; + *this = UnitSquareTo(dst).times(UnitSquareTo(src).inverse()); +} + +PointF PerspectiveTransform::operator()(PointF p) const +{ + auto denominator = a13 * p.x + a23 * p.y + a33; + return {(a11 * p.x + a21 * p.y + a31) / denominator, (a12 * p.x + a22 * p.y + a32) / denominator}; +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/PerspectiveTransform.h b/tools/chatgui/zxing-cpp/core/src/PerspectiveTransform.h new file mode 100644 index 00000000..3ac66998 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/PerspectiveTransform.h @@ -0,0 +1,47 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Point.h" +#include "Quadrilateral.h" + +namespace ZXing { + +/** +* This class implements a perspective transform in two dimensions. Given four source and four +* destination points, it will compute the transformation implied between them. The code is based +* directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56. +* +* See also e.g. https://math.stackexchange.com/a/339033 +*/ +class PerspectiveTransform +{ + using value_t = PointF::value_t; + value_t a11, a12, a13, a21, a22, a23, a31, a32, a33 = NAN; + + PerspectiveTransform(value_t a11, value_t a21, value_t a31, value_t a12, value_t a22, value_t a32, value_t a13, + value_t a23, value_t a33) + : a11(a11), a12(a12), a13(a13), a21(a21), a22(a22), a23(a23), a31(a31), a32(a32), a33(a33) + {} + + PerspectiveTransform inverse() const; + PerspectiveTransform times(const PerspectiveTransform& other) const; + + static PerspectiveTransform UnitSquareTo(const QuadrilateralF& q); + +public: + PerspectiveTransform() = default; + PerspectiveTransform(const QuadrilateralF& src, const QuadrilateralF& dst); + + /// Project from the destination space (grid of modules) into the image space (bit matrix) + PointF operator()(PointF p) const; + + bool isValid() const { return !std::isnan(a33); } +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/Point.h b/tools/chatgui/zxing-cpp/core/src/Point.h new file mode 100644 index 00000000..0fa0e501 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Point.h @@ -0,0 +1,173 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +namespace ZXing { + +/** + * @brief A simple 2D point class template. + * + * PointT is a simple 2D point class template with direct access to its x and y coordinate members. + */ +template +struct PointT +{ + using value_t = T; + T x = 0, y = 0; + + constexpr PointT() = default; + constexpr PointT(T xx, T yy) : x(xx), y(yy) {} + + template + constexpr explicit PointT(const PointT& p) : x(static_cast(p.x)), y(static_cast(p.y)) + {} + + template + PointT& operator+=(const PointT& b) + { + x += b.x; + y += b.y; + return *this; + } +}; + +using PointI = PointT; +using PointF = PointT; + +/// @cond INTERNAL + +template +bool operator==(const PointT& a, const PointT& b) +{ + return a.x == b.x && a.y == b.y; +} + +template +bool operator!=(const PointT& a, const PointT& b) +{ + return !(a == b); +} + +template +auto operator-(const PointT& a) -> PointT +{ + return {-a.x, -a.y}; +} + +template +auto operator+(const PointT& a, const PointT& b) -> PointT +{ + return {a.x + b.x, a.y + b.y}; +} + +template +auto operator-(const PointT& a, const PointT& b) -> PointT +{ + return {a.x - b.x, a.y - b.y}; +} + +template +auto operator*(const PointT& a, const PointT& b) -> PointT +{ + return {a.x * b.x, a.y * b.y}; +} + +template +PointT operator*(U s, const PointT& a) +{ + return {s * a.x, s * a.y}; +} + +template +PointT operator/(const PointT& a, U d) +{ + return {a.x / d, a.y / d}; +} + +template +auto dot(const PointT& a, const PointT& b) -> decltype (a.x * b.x) +{ + return a.x * b.x + a.y * b.y; +} + +template +auto cross(PointT a, PointT b) -> decltype(a.x * b.x) +{ + return a.x * b.y - b.x * a.y; +} + +/// L1 norm +template +T sumAbsComponent(PointT p) +{ + return std::abs(p.x) + std::abs(p.y); +} + +/// L2 norm +template +auto length(PointT p) -> decltype(std::sqrt(dot(p, p))) +{ + return std::sqrt(dot(p, p)); +} + +/// L-inf norm +template +T maxAbsComponent(PointT p) +{ + return std::max(std::abs(p.x), std::abs(p.y)); +} + +template +auto distance(PointT a, PointT b) -> decltype(length(a - b)) +{ + return length(a - b); +} + +template +PointF normalized(PointT d) +{ + return PointF(d) / length(PointF(d)); +} + +template +PointT bresenhamDirection(PointT d) +{ + return d / maxAbsComponent(d); +} + +template +PointT mainDirection(PointT d) +{ + return std::abs(d.x) > std::abs(d.y) ? PointT(d.x, 0) : PointT(0, d.y); +} + +/// Calculate a floating point pixel coordinate representing the 'center' of the pixel. +/// This is sort of the inverse operation of the PointI(PointF) conversion constructor. +/// See also the documentation of the GridSampler API. +inline PointF centered(PointI p) +{ + return p + PointF(0.5f, 0.5f); +} + +inline PointF centered(PointF p) +{ + return {std::floor(p.x) + 0.5f, std::floor(p.y) + 0.5f}; +} + +/// @endcond + +template +std::string ToString(const PointT& p, bool swap = false, char delim = 'x') +{ + return std::to_string(swap ? p.y : p.x) + delim + std::to_string(swap ? p.x : p.y); +} + +} // ZXing + diff --git a/tools/chatgui/zxing-cpp/core/src/Quadrilateral.h b/tools/chatgui/zxing-cpp/core/src/Quadrilateral.h new file mode 100644 index 00000000..484767ed --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Quadrilateral.h @@ -0,0 +1,215 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Point.h" + +#ifdef ZXING_INTERNAL +#include "ZXAlgorithms.h" +#endif + +#include +#include +#include + +namespace ZXing { + +/** + * @brief A simple class representing a quadrilateral defined by its four corner points. + * + * It represents a quadrilateral defined by its four corner points: + * top-left, top-right, bottom-right, and bottom-left. Those points are relative to the detected symbol + * i.e. topLeft() will return the coordinates of the top-left corner of the symbol. If e.g. the symbol + * is rotated 180 degrees, the top-left corner will be the one that is at the bottom-right position in the image. + */ +template +class Quadrilateral : public std::array +{ + using Base = std::array; + using Base::at; +public: + using Point = T; + + Quadrilateral() = default; + Quadrilateral(T tl, T tr, T br, T bl) : Base{tl, tr, br, bl} {} + template + Quadrilateral(PointT tl, PointT tr, PointT br, PointT bl) + : Quadrilateral(Point(tl), Point(tr), Point(br), Point(bl)) + {} + + constexpr Point topLeft() const noexcept { return at(0); } + constexpr Point topRight() const noexcept { return at(1); } + constexpr Point bottomRight() const noexcept { return at(2); } + constexpr Point bottomLeft() const noexcept { return at(3); } + + /// Return the orientation of the quadrilateral in radians, where 0 means the horizontal center line + /// is parallel to the x-axis and positive values mean a clockwise rotation. + double orientation() const + { + auto centerLine = (topRight() + bottomRight()) - (topLeft() + bottomLeft()); + if (centerLine == Point{}) + return 0.; + auto centerLineF = normalized(centerLine); + return std::atan2(centerLineF.y, centerLineF.x); + } +}; + +using QuadrilateralF = Quadrilateral; +using QuadrilateralI = Quadrilateral; + +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; +} + +#ifdef ZXING_INTERNAL + +template +Quadrilateral Rectangle(int width, int height, typename PointT::value_t margin = 0) +{ + return { + PointT{margin, margin}, {width - margin, margin}, {width - margin, height - margin}, {margin, height - margin}}; +} + +template +Quadrilateral Rectangle(int x0, int x1, int y0, int y1, typename PointT::value_t o) +{ + return {PointT{x0 + o, y0 + o}, {x1 + o, y0 + o}, {x1 + o, y1 + o}, {x0 + o, y1 + o}}; +} + +template +Quadrilateral Rectangle(int left, int top, int width, int height) +{ + int right = left + width - 1; + int bottom = top + height - 1; + + return {PointT{left, top}, {right, top}, {right, bottom}, {left, bottom}}; +} + +template +Quadrilateral CenteredSquare(int size) +{ + return Scale(Quadrilateral(PointT{-1, -1}, {1, -1}, {1, 1}, {-1, 1}), size / 2); +} + +template +Quadrilateral Line(int y, int xStart, int xStop) +{ + return {PointT{xStart, y}, {xStop, y}, {xStop, y}, {xStart, y}}; +} + +template +bool IsConvex(const Quadrilateral& poly) +{ + const int N = Size(poly); + bool sign = false; + + typename PointT::value_t m = INFINITY, M = 0; + + for(int i = 0; i < N; i++) + { + auto d1 = poly[(i + 2) % N] - poly[(i + 1) % N]; + auto d2 = poly[i] - poly[(i + 1) % N]; + auto cp = cross(d1, d2); + + // TODO: see if the isInside check for all boundary points in GridSampler is still required after fixing the wrong fabs() + // application in the following line + UpdateMinMax(m, M, std::fabs(cp)); + + if (i == 0) + sign = cp > 0; + else if (sign != (cp > 0)) + return false; + } + + // It turns out being convex is not enough to prevent a "numerical instability" + // that can cause the corners being projected inside the image boundaries but + // some points near the corners being projected outside. This has been observed + // where one corner is almost in line with two others. The M/m ratio is below 2 + // for the complete existing sample set. For very "skewed" QRCodes a value of + // around 3 is realistic. A value of 14 has been observed to trigger the + // instability. + return M / m < 4.0; +} + +template +Quadrilateral Scale(const Quadrilateral& q, int factor) +{ + return {factor * q[0], factor * q[1], factor * q[2], factor * q[3]}; +} + +template +Quadrilateral Move(const Quadrilateral& q, PointT offset) +{ + return {q[0] + offset, q[1] + offset, q[2] + offset, q[3] + offset}; +} + +template +PointT Center(const Quadrilateral& q) +{ + return Reduce(q) / Size(q); +} + +template +Quadrilateral RotatedCorners(const Quadrilateral& q, int n = 1, bool mirror = false) +{ + Quadrilateral res; + std::rotate_copy(q.begin(), q.begin() + ((n + 4) % 4), q.end(), res.begin()); + if (mirror) + std::swap(res[1], res[3]); + return res; +} + +template +bool IsInside(const PointT& p, const Quadrilateral& q) +{ + // Test if p is on the same side (right or left) of all polygon segments + int pos = 0, neg = 0; + for (int i = 0; i < Size(q); ++i) + (cross(p - q[i], q[(i + 1) % Size(q)] - q[i]) < 0 ? neg : pos)++; + return pos == 0 || neg == 0; +} + +template +Quadrilateral BoundingBox(const Quadrilateral& q) +{ + auto [minX, maxX] = std::minmax({q[0].x, q[1].x, q[2].x, q[3].x}); + auto [minY, maxY] = std::minmax({q[0].y, q[1].y, q[2].y, q[3].y}); + return {PointT{minX, minY}, {maxX, minY}, {maxX, maxY}, {minX, maxY}}; +} + +template +bool HaveIntersectingBoundingBoxes(const Quadrilateral& a, const Quadrilateral& b) +{ + auto bba = BoundingBox(a), bbb = BoundingBox(b); + + bool x = bbb.topRight().x < bba.topLeft().x || bbb.topLeft().x > bba.topRight().x; + bool y = bbb.bottomLeft().y < bba.topLeft().y || bbb.topLeft().y > bba.bottomLeft().y; + return !(x || y); +} + +template +Quadrilateral Blend(const Quadrilateral& a, const Quadrilateral& b) +{ + auto dist2First = [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(); + + Quadrilateral res; + for (int i = 0; i < 4; ++i) + res[i] = (a[i] + b[(i + offset) % 4]) / 2; + + return res; +} + +#endif + +} // ZXing + diff --git a/tools/chatgui/zxing-cpp/core/src/Range.h b/tools/chatgui/zxing-cpp/core/src/Range.h new file mode 100644 index 00000000..9f025736 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Range.h @@ -0,0 +1,112 @@ +/* +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ZXAlgorithms.h" + +#include +#include + +namespace ZXing { + +template +struct StrideIter +{ + Iterator pos; + int stride; + + using iterator_category = std::random_access_iterator_tag; + using difference_type = typename std::iterator_traits::difference_type; + using value_type = typename std::iterator_traits::value_type; + using pointer = Iterator; + using reference = typename std::iterator_traits::reference; + + auto operator*() const { return *pos; } + auto operator[](int i) const { return *(pos + i * stride); } + StrideIter& operator++() { return pos += stride, *this; } + StrideIter operator++(int) { auto temp = *this; ++*this; return temp; } + bool operator==(const StrideIter& rhs) const { return pos == rhs.pos; } + StrideIter operator+(int i) const { return {pos + i * stride, stride}; } + StrideIter operator-(int i) const { return {pos - i * stride, stride}; } + int operator-(const StrideIter& rhs) const { return narrow_cast((pos - rhs.pos) / stride); } +}; + +template +StrideIter(const Iterator&, int) -> StrideIter; + + +template +struct Range +{ + Iterator _begin, _end; + + Range(Iterator b, Iterator e) : _begin(b), _end(e) {} + + template + Range(const C& c) : _begin(std::begin(c)), _end(std::end(c)) {} + + Iterator begin() const noexcept { return _begin; } + Iterator end() const noexcept { return _end; } + explicit operator bool() const { return begin() < end(); } + int size() const { return narrow_cast(end() - begin()); } +}; + +template +Range(const C&) -> Range; + +/** + * ArrayView is a lightweight, non-owning, non-mutable view over a contiguous sequence of elements. + * Similar to std::span. See also Range template for general iterator use case. + */ +template +class ArrayView +{ + const T* _data = nullptr; + std::size_t _size = 0; + +public: + using value_type = T; + using pointer = const value_type*; + using const_pointer = const value_type*; + using reference = const value_type&; + using const_reference = const value_type&; + using size_type = std::size_t; + + constexpr ArrayView() noexcept = default; + + constexpr ArrayView(pointer data, size_type size) noexcept : _data(data), _size(size) {} + + template >>>>> + constexpr ArrayView(P data, size_type size) noexcept : _data(reinterpret_cast(data)), _size(size) + {} + + template ())), const_pointer>>> + constexpr ArrayView(const Container& c) noexcept : _data(std::data(c)), _size(std::size(c)) + {} + + constexpr pointer data() const noexcept { return _data; } + constexpr size_type size() const noexcept { return _size; } + constexpr bool empty() const noexcept { return _size == 0; } + + constexpr const_reference operator[](size_type index) const noexcept { return _data[index]; } + + constexpr pointer begin() const noexcept { return _data; } + constexpr pointer end() const noexcept { return _data + _size; } + + constexpr ArrayView subview(size_type pos, size_type len = size_type(-1)) const noexcept + { + if (pos > _size) + return {}; + return {_data + pos, std::min(len, _size - pos)}; + } +}; + +using ByteView = ArrayView; + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ReadBarcode.cpp b/tools/chatgui/zxing-cpp/core/src/ReadBarcode.cpp new file mode 100644 index 00000000..9d213934 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ReadBarcode.cpp @@ -0,0 +1,335 @@ +/* +* Copyright 2019 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "ReadBarcode.h" +#include "ReaderOptions.h" +#include "BarcodeData.h" +#include "Version.h" + +#include + + +#ifdef ZXING_READERS +#include "GlobalHistogramBinarizer.h" +#include "HybridBinarizer.h" +#include "MultiFormatReader.h" +#include "Pattern.h" +#include "ThresholdBinarizer.h" +#endif + +#include +#include +#include + +namespace ZXing { + +// ============================================================================== +// ReaderOptions implementation +// ============================================================================== + +struct ReaderOptions::Data +{ + bool tryHarder : 1 = true; + bool tryRotate : 1 = true; + bool tryInvert : 1 = true; + bool tryDownscale : 1 = true; +#ifdef ZXING_EXPERIMENTAL_API + bool tryDenoise : 1 = false; +#endif + bool isPure : 1 = false; + bool validateOptionalChecksum : 1 = false; + bool returnErrors : 1 = false; + uint8_t downscaleFactor : 3 = 3; // values 2, 3, 4 + EanAddOnSymbol eanAddOnSymbol : 2 = EanAddOnSymbol::Ignore; + Binarizer binarizer : 2 = Binarizer::LocalAverage; + TextMode textMode : 3 = TextMode::HRI; + CharacterSet characterSet : 6 = CharacterSet::Unknown; + + uint8_t minLineCount = 2; + uint8_t maxNumberOfSymbols = 0xff; + uint16_t downscaleThreshold = 500; + BarcodeFormats formats = {}; +}; + +ReaderOptions::ReaderOptions() : d(std::make_unique()) {} +ReaderOptions::~ReaderOptions() = default; + +// copy +ReaderOptions::ReaderOptions(const ReaderOptions& other) : d(std::make_unique(*other.d)) {} +ReaderOptions& ReaderOptions::operator=(const ReaderOptions& other) +{ + if (this != &other) + d = std::make_unique(*other.d); + return *this; +} + +// move +ReaderOptions::ReaderOptions(ReaderOptions&&) noexcept = default; +ReaderOptions& ReaderOptions::operator=(ReaderOptions&&) noexcept = default; + +const BarcodeFormats& ReaderOptions::formats() const noexcept { return d->formats; } +ReaderOptions& ReaderOptions::formats(BarcodeFormats&& v) & { return (void)(d->formats = std::move(v)), *this; } +ReaderOptions&& ReaderOptions::formats(BarcodeFormats&& v) && { return (void)(d->formats = std::move(v)), std::move(*this); } + +#define ZX_PROPERTY(TYPE, NAME, SETTER) \ + TYPE ReaderOptions::NAME() const noexcept { return d->NAME; } \ + ReaderOptions& ReaderOptions::NAME(TYPE v) & { return (void)(d->NAME = std::move(v)), *this; } \ + ReaderOptions&& ReaderOptions::NAME(TYPE v) && { return (void)(d->NAME = std::move(v)), std::move(*this); } + +ZX_PROPERTY(bool, tryHarder, setTryHarder) +ZX_PROPERTY(bool, tryRotate, setTryRotate) +ZX_PROPERTY(bool, tryInvert, setTryInvert) +ZX_PROPERTY(bool, tryDownscale, setTryDownscale) +#ifdef ZXING_EXPERIMENTAL_API +ZX_PROPERTY(bool, tryDenoise, setTryDenoise) +#endif +ZX_PROPERTY(Binarizer, binarizer, setBinarizer) +ZX_PROPERTY(bool, isPure, setIsPure) +ZX_PROPERTY(uint16_t, downscaleThreshold, setDownscaleThreshold) +ZX_PROPERTY(uint8_t, downscaleFactor, setDownscaleFactor) +ZX_PROPERTY(uint8_t, minLineCount, setMinLineCount) +ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) +ZX_PROPERTY(bool, validateOptionalChecksum, setValidateOptionalChecksum) +ZX_PROPERTY(bool, returnErrors, setReturnErrors) +ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol) +ZX_PROPERTY(TextMode, textMode, setTextMode) +ZX_PROPERTY(CharacterSet, characterSet, setCharacterSet) + +#undef ZX_PROPERTY + +ReaderOptions& ReaderOptions::characterSet(std::string_view v) & +{ + d->characterSet = CharacterSetFromString(v); + return *this; +} +ReaderOptions&& ReaderOptions::characterSet(std::string_view v) && +{ + d->characterSet = CharacterSetFromString(v); + return std::move(*this); +} + +bool ReaderOptions::hasFormat(const BarcodeFormats& formats) const noexcept +{ + return d->formats.empty() || std::any_of(formats.begin(), formats.end(), [this](BarcodeFormat bt) { return bt <= d->formats; }); +} + +bool ReaderOptions::hasAnyFormat(const BarcodeFormats& formats) const noexcept +{ + return d->formats.empty() || std::any_of(formats.begin(), formats.end(), [this](BarcodeFormat bt) { return bt & d->formats; }); +} + +// ============================================================================== +// ReadBarcode implementation +// ============================================================================== + +#ifdef ZXING_READERS + +class LumImage : public Image +{ +public: + using Image::Image; + + uint8_t* data() { return const_cast(Image::data()); } +}; + +template +static LumImage ExtractLum(const ImageView& iv, P projection) +{ + LumImage res(iv.width(), iv.height()); + + auto* dst = res.data(); + for(int y = 0; y < iv.height(); ++y) + for(int x = 0, w = iv.width(); x < w; ++x) + *dst++ = projection(iv.data(x, y)); + + return res; +} + +class LumImagePyramid +{ + std::vector buffers; + + template + void addLayer() + { + auto siv = layers.back(); + buffers.emplace_back(siv.width() / N, siv.height() / N); + layers.push_back(buffers.back()); + auto& div = buffers.back(); + auto* d = div.data(); + + for (int dy = 0; dy < div.height(); ++dy) + for (int dx = 0; dx < div.width(); ++dx) { + int sum = (N * N) / 2; + for (int ty = 0; ty < N; ++ty) + for (int tx = 0; tx < N; ++tx) + sum += *siv.data(dx * N + tx, dy * N + ty); + *d++ = sum / (N * N); + } + } + + void addLayer(int factor) + { + // help the compiler's auto-vectorizer by hard-coding the scale factor + switch (factor) { + case 2: addLayer<2>(); break; + case 3: addLayer<3>(); break; + case 4: addLayer<4>(); break; + default: throw std::invalid_argument("Invalid ReaderOptions::downscaleFactor"); break; + } + } + +public: + std::vector layers; + + 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 && + std::min(layers.back().width(), layers.back().height()) >= factor) + addLayer(factor); +#if 0 + // Reversing the layers means we'd start with the smallest. that can make sense if we are only looking for a + // single symbol. If we start with the higher resolution, we get better (high res) position information. + // TODO: see if masking out higher res layers based on found symbols in lower res helps overall performance. + std::reverse(layers.begin(), layers.end()); +#endif + } +}; + +ImageView SetupLumImageView(ImageView iv, LumImage& lum, const ReaderOptions& opts) +{ + if (iv.format() == ImageFormat::None) + throw std::invalid_argument("Invalid image format"); + + if (opts.binarizer() == Binarizer::GlobalHistogram || opts.binarizer() == Binarizer::LocalAverage) { + // manually spell out the 3 most common pixel formats to get at least gcc to vectorize the code + if (iv.format() == ImageFormat::RGB && iv.pixStride() == 3) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); }); + } else if (iv.format() == ImageFormat::RGBA && iv.pixStride() == 4) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); }); + } else if (iv.format() == ImageFormat::BGR && iv.pixStride() == 3) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[2], src[1], src[0]); }); + } else if (iv.format() != ImageFormat::Lum) { + lum = ExtractLum(iv, [r = RedIndex(iv.format()), g = GreenIndex(iv.format()), b = BlueIndex(iv.format())]( + const uint8_t* src) { return RGBToLum(src[r], src[g], src[b]); }); + } else if (iv.pixStride() != 1) { + // GlobalHistogram and LocalAverage need dense line memory layout + lum = ExtractLum(iv, [](const uint8_t* src) { return *src; }); + } + if (lum.data()) + return lum; + } + return iv; +} + +std::unique_ptr CreateBitmap(ZXing::Binarizer binarizer, const ImageView& iv) +{ + switch (binarizer) { + case Binarizer::BoolCast: return std::make_unique(iv, 0); + case Binarizer::FixedThreshold: return std::make_unique(iv, 127); + case Binarizer::GlobalHistogram: return std::make_unique(iv); + case Binarizer::LocalAverage: return std::make_unique(iv); + } + return {}; // silence gcc warning +} + +Barcode ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) +{ + return FirstOrDefault(ReadBarcodes(_iv, ReaderOptions(opts).maxNumberOfSymbols(1))); +} + +Barcodes ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) +{ + 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 {FirstOrDefault(reader.read(*CreateBitmap(opts.binarizer(), iv), 1)).setReaderOptions(opts)}; + + std::unique_ptr closedReader; +#ifdef ZXING_EXPERIMENTAL_API + using enum BarcodeFormat; + BarcodeFormats formatsBenefittingFromClosing = Aztec | DataMatrix | QRCode; + ReaderOptions closedOptions = opts; + if (opts.tryDenoise() && opts.hasAnyFormat(formatsBenefittingFromClosing) && _iv.height() >= 3) { + closedOptions.formats(opts.formats().empty() ? formatsBenefittingFromClosing : formatsBenefittingFromClosing & opts.formats()); + closedReader = std::make_unique(closedOptions); + } +#endif + LumImagePyramid pyramid(iv, opts.downscaleThreshold() * opts.tryDownscale(), opts.downscaleFactor()); + + Barcodes res; + int maxSymbols = opts.maxNumberOfSymbols() ? opts.maxNumberOfSymbols() : INT_MAX; + for (auto&& iv : pyramid.layers) { + auto bitmap = CreateBitmap(opts.binarizer(), iv); + for (int close = 0; close <= (closedReader ? 1 : 0); ++close) { + if (close) { + // if we already inverted the image in the first round, we need to undo that first + if (bitmap->inverted()) + bitmap->invert(); + bitmap->close(); + } + + // TODO: check if closing after invert would be beneficial + for (int invert = 0; invert <= static_cast(opts.tryInvert() && !close); ++invert) { + if (invert) + bitmap->invert(); + auto rs = (close ? *closedReader : reader).read(*bitmap, maxSymbols); + for (auto& r : rs) { + if (iv.width() != _iv.width()) + r.d->position = Scale(r.position(), _iv.width() / iv.width()); + if (!Contains(res, r)) { + r.setReaderOptions(opts); + r.d->isInverted = bitmap->inverted(); + res.push_back(std::move(r)); + --maxSymbols; + } + } + if (maxSymbols <= 0) + return res; + } + } + } + + if (opts.returnErrors()) { + // if symbols overlap and one is in error remove it + for (auto a = res.begin(); a != res.end(); ++a) + for (auto b = std::next(a); b != res.end(); ++b) + if (HaveIntersectingBoundingBoxes(a->position(), b->position()) && (a->error() != b->error())) + *(a->error() ? a : b) = BarcodeData(); + + std::erase_if(res, [](auto&& r) { return r.format() == BarcodeFormat::None; }); + } + + 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/tools/chatgui/zxing-cpp/core/src/ReadBarcode.h b/tools/chatgui/zxing-cpp/core/src/ReadBarcode.h new file mode 100644 index 00000000..45fe97f9 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ReadBarcode.h @@ -0,0 +1,33 @@ +/* +* Copyright 2019 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ReaderOptions.h" +#include "ImageView.h" +#include "Barcode.h" + +namespace ZXing { + +/** + * Read barcode from an ImageView + * + * @param image view of the image data including layout and format + * @param options optional ReaderOptions to parameterize / speed up detection + * @return Barcode found, if any, otherwise a Barcode with empty content and format ZXing::BarcodeFormat::None + */ +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 List of Barcode found, may be empty + */ +Barcodes ReadBarcodes(const ImageView& image, const ReaderOptions& options = {}); + +} // ZXing + diff --git a/tools/chatgui/zxing-cpp/core/src/Reader.h b/tools/chatgui/zxing-cpp/core/src/Reader.h new file mode 100644 index 00000000..ffd1be88 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Reader.h @@ -0,0 +1,32 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ReaderOptions.h" +#include "BarcodeData.h" + +namespace ZXing { + +class BinaryBitmap; +class ReaderOptions; + +class Reader +{ +protected: + const ReaderOptions& _opts; + +public: + const bool supportsInversion; + + explicit Reader(const ReaderOptions& opts, bool supportsInversion = false) : _opts(opts), supportsInversion(supportsInversion) {} + explicit Reader(ReaderOptions&& opts) = delete; + virtual ~Reader() = default; + + virtual BarcodesData read(const BinaryBitmap& image, int maxSymbols) const = 0; +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ReaderOptions.h b/tools/chatgui/zxing-cpp/core/src/ReaderOptions.h new file mode 100644 index 00000000..b3849e4d --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ReaderOptions.h @@ -0,0 +1,210 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BarcodeFormat.h" +#include "CharacterSet.h" +#include "Version.h" + +#include +#include +#include + +namespace ZXing { + +/** + * @brief Specify which algorithm to use for the grayscale to binary transformation. + * + * The difference is how to get to a threshold value T which results in a bit value R(esult) = L(uminance) <= T(hreshold). + */ +enum class Binarizer : unsigned char // needs to be unsigned for the bitfield below to work, uint8_t fails as well +{ + LocalAverage, ///< T = average of neighboring pixels for matrix and GlobalHistogram for linear (HybridBinarizer) + GlobalHistogram, ///< T = valley between the 2 largest peaks in the histogram (per line in linear case) + FixedThreshold, ///< T = 127 + BoolCast, ///< T = 0, fastest possible +}; + +/** + * @brief Specify whether to ignore, read or require EAN-2/5 add-on symbols while scanning EAN/UPC codes. + */ +enum class EanAddOnSymbol : unsigned char // see above +{ + Ignore, ///< Ignore any Add-On symbol during read/scan + Read, ///< Read EAN-2/EAN-5 Add-On symbol if found + Require, ///< Require EAN-2/EAN-5 Add-On symbol to be present +}; + +/** + * @brief Specify how the decoded byte content of a barcode should be transcoded to text. + * + * @see Barcode::text(), ReaderOptions::textMode(). + */ +enum class TextMode : unsigned char // see above +{ + Plain, ///< bytes() transcoded to unicode based on ECI info or guessed charset (the default mode prior to 2.0) + ECI, ///< standard content following the ECI protocol with every character set ECI segment transcoded to unicode + HRI, ///< Human Readable Interpretation (dependent on the ContentType) + Escaped, ///< Use the EscapeNonGraphical() function (e.g. ASCII 29 will be transcoded to "") + Hex, ///< bytes() transcoded to ASCII string of HEX values + HexECI, ///< bytesECI() transcoded to ASCII string of HEX values +}; + +/** + * @brief Configuration options for barcode reading and decoding behavior. + * + * ReaderOptions encapsulates a set of flags and parameters that control + * how barcode detection and decoding is performed. It provides + * fluent setters that support chaining. Both `name(val)` and `setName(val)` + * forms are available for convenience and compatibility. + * + * The class is intended to be passed to the ReadBarcodes function to + * influence scanning heuristics, performance vs. accuracy trade-offs, output + * formatting, and symbol filtering. Instances can be reused across multiple + * read operations. + * + * The default settings are optimized for detection rate and can be tuned + * for speed or specific use-cases. + * + * @see BarcodeFormats, Binarizer, TextMode, CharacterSet, ReadBarcodes + */ +class ReaderOptions +{ + struct Data; + std::unique_ptr d; + +public: + ReaderOptions(); + ~ReaderOptions(); + ReaderOptions(const ReaderOptions&); + ReaderOptions& operator=(const ReaderOptions&); + ReaderOptions(ReaderOptions&&) noexcept; + ReaderOptions& operator=(ReaderOptions&&) noexcept; + +#define ZX_PROPERTY(TYPE, NAME, SETTER, ...) \ + TYPE NAME() const noexcept; \ + __VA_ARGS__ ReaderOptions& NAME(TYPE v) &; \ + __VA_ARGS__ ReaderOptions&& NAME(TYPE v) &&; \ + __VA_ARGS__ inline ReaderOptions& SETTER(TYPE v) & { return NAME(v); } \ + __VA_ARGS__ inline ReaderOptions&& SETTER(TYPE v) && { return std::move(*this).NAME(v); } + + /// Specify a set of BarcodeFormats that should be searched for, the default is all supported formats. + const BarcodeFormats& formats() const noexcept; + ReaderOptions& formats(BarcodeFormats&& v) &; + ReaderOptions&& formats(BarcodeFormats&& v) &&; + ReaderOptions& formats(const BarcodeFormats& v) & { return formats(BarcodeFormats(v)); } + ReaderOptions&& formats(const BarcodeFormats& v) && { return std::move(*this).formats(BarcodeFormats(v)); } + inline ReaderOptions& setFormats(BarcodeFormats&& v) & { return formats(std::move(v)); } + inline ReaderOptions&& setFormats(BarcodeFormats&& v) && { return std::move(*this).formats(std::move(v)); } + inline ReaderOptions& setFormats(const BarcodeFormats& v) & { return formats(BarcodeFormats(v)); } + inline ReaderOptions&& setFormats(const BarcodeFormats& v) && { return std::move(*this).formats(BarcodeFormats(v)); } + + /// Spend more time to try to find a barcode; optimize for accuracy instead of not speed (default: true). + ZX_PROPERTY(bool, tryHarder, setTryHarder) + + /// Try detecting codes in 90, 180 and 270 degree rotated images (default: true). + ZX_PROPERTY(bool, tryRotate, setTryRotate) + + /// Try detecting inverted ("reversed reflectance") codes if the format allows for those (default: true). + ZX_PROPERTY(bool, tryInvert, setTryInvert) + + /// Try detecting code in downscaled images (depending on image size) (default: true). + ZX_PROPERTY(bool, tryDownscale, setTryDownscale) + +#ifdef ZXING_EXPERIMENTAL_API + /// Also try detecting code after denoising (currently morphological closing filter for 2D formats only). + ZX_PROPERTY(bool, tryDenoise, setTryDenoise) +#endif + + /// Binarizer to use for grayscale to binary transformation (default: Binarizer::LocalAverage). + ZX_PROPERTY(Binarizer, binarizer, setBinarizer) + + /// Set to true if the input contains nothing but a single perfectly aligned barcode (generated image). + ZX_PROPERTY(bool, isPure, setIsPure) + + /// Image size ( min(width, height) ) threshold at which to start downscaled scanning. + ZX_PROPERTY(uint16_t, downscaleThreshold, setDownscaleThreshold) + + /// Scale factor used during downscaling, meaningful values are 2, 3 and 4. + ZX_PROPERTY(uint8_t, downscaleFactor, setDownscaleFactor) + + /// The number of scan lines in a linear barcode that have to be equal to accept the result (default: 2). + ZX_PROPERTY(uint8_t, minLineCount, setMinLineCount) + + /// The maximum number of symbols (barcodes) to detect / look for with ReadBarcodes(). + ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) + + /// Validate optional checksums where applicable (e.g. Code39, ITF) (default: false). + ZX_PROPERTY(bool, validateOptionalChecksum, setValidateOptionalChecksum) + + /// If true, return the barcodes with errors as well (e.g. checksum errors, see Barcode::error()) (default: false). + ZX_PROPERTY(bool, returnErrors, setReturnErrors) + + /// Specify whether to ignore, read or require EAN-2/5 add-on symbols while scanning EAN/UPC codes. + ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol) + + /// Specifies the TextMode that controls the return of the Barcode::text() function (default: TextMode::HRI). + ZX_PROPERTY(TextMode, textMode, setTextMode) + + /// Specifies fallback character set to use instead of auto-detecting it (when applicable). + ZX_PROPERTY(CharacterSet, characterSet, setCharacterSet) + ReaderOptions& characterSet(std::string_view v) &; + ReaderOptions&& characterSet(std::string_view v) &&; + inline ReaderOptions& setCharacterSet(std::string_view v) & { return characterSet(v); } + inline ReaderOptions&& setCharacterSet(std::string_view v) && { return std::move(*this).characterSet(v); } + +#undef ZX_PROPERTY + +/// @cond DEPRECATED + + // Silence deprecated-declarations warnings, only happening here for deprecated inline functions +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + +#define ZX_DEPRECATED_PROPERTY(TYPE, NAME, SETTER, GET_IMPL, SET_IMPL) \ + [[deprecated]] inline TYPE NAME() const noexcept { return GET_IMPL; } \ + [[deprecated]] ReaderOptions& NAME(TYPE v) & { SET_IMPL; return *this; } \ + [[deprecated]] ReaderOptions&& NAME(TYPE v) && { SET_IMPL; return std::move(*this); } \ + [[deprecated]] inline ReaderOptions& SETTER(TYPE v) & { return NAME(v); } \ + [[deprecated]] inline ReaderOptions&& SETTER(TYPE v) && { return std::move(*this).NAME(v); } + + /// @deprecated (does nothing). See BarcodeFormat::Code39Ext and ::Code39Std to select full ASCII or standard Code39 mode. + ZX_DEPRECATED_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode, true, (void)v) + + /// @deprecated (use validateOptionalChecksum). The Code39 symbol has a valid checksum iff symbologyIdentifier()[2] is an odd digit + ZX_DEPRECATED_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum, validateOptionalChecksum(), + validateOptionalChecksum(v)) + + /// @deprecated (use validateOptionalChecksum). The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'. + ZX_DEPRECATED_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum, validateOptionalChecksum(), validateOptionalChecksum(v)) + +#undef ZX_DEPRECATED_PROPERTY + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +/// @endcond + +#ifdef ZXING_INTERNAL + /// Check if a specific format is explicitly enabled in the formats set + bool hasFormat(const BarcodeFormats& formats) const noexcept; + + /// Check if any format is explicitly or implicitly enabled in the formats set + bool hasAnyFormat(const BarcodeFormats& formats) const noexcept; +#endif +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ReedSolomon.cpp b/tools/chatgui/zxing-cpp/core/src/ReedSolomon.cpp new file mode 100644 index 00000000..74f9b1a7 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ReedSolomon.cpp @@ -0,0 +1,101 @@ +// Copyright 2026 Axel Waggershauser +// SPDX-License-Identifier: Apache-2.0 + +#include "ReedSolomon.h" +#include "Version.h" +#include "ZXAlgorithms.h" + +#include "librscpp/encode.h" +#include "librscpp/decode.h" + +#include +#include +#include +#include + +namespace ZXing { + +namespace rs = librscpp; + +using GF2nI = rs::GF2n<>; + +const GF2nI& GetGF2n(RSField field) +{ + static GF2nI gf2n_4_1(0x0013, 1); // x^4 + x + 1 + static GF2nI gf2n_6_1(0x0043, 1); // x^6 + x + 1 + static GF2nI gf2n_8_5_3_2(0x012D, 1); // x^8 + x^5 + x^3 + x^2 + 1 : DataMatrix + static GF2nI gf2n_8_4_3_2(0x011D, 0); // x^8 + x^4 + x^3 + x^2 + 1 : QRCode + static GF2nI gf2n_10_3(0x0409, 1); // x^10 + x^3 + 1 + static GF2nI gf2n_12_6_5_3(0x1069, 1); // x^12 + x^6 + x^5 + x^3 + 1 + + switch (field) { + case RSField::Aztec4: return gf2n_4_1; + case RSField::Aztec6: return gf2n_6_1; + case RSField::Aztec8: return gf2n_8_5_3_2; + case RSField::Aztec10: return gf2n_10_3; + case RSField::Aztec12: return gf2n_12_6_5_3; + case RSField::DataMatrix: return gf2n_8_5_3_2; + case RSField::MaxiCode: return gf2n_6_1; + case RSField::QRCode: return gf2n_8_4_3_2; + default: throw std::invalid_argument("Unsupported RSField"); + } +} + +const rs::GFp<>& GetGFPDF417() +{ + static rs::GFp<> gf929_3(929, 3, 1); + return gf929_3; +} + +// See ISO/IEC 15415:2024 7.5.9 +static std::optional UEC(std::optional usedECC, int numECC) +{ + return usedECC ? std::optional(std::clamp(1.0 - static_cast(*usedECC) / numECC, 0.0, 1.0)) : std::nullopt; +} + +#ifdef ZXING_READERS +std::optional ReedSolomonDecode(RSField field, std::span codeword, int numECC, std::span erasures) +{ + return UEC(rs::decode(GetGF2n(field), codeword, numECC, erasures), numECC); +} + +std::optional ReedSolomonDecode(RSField field, std::span codeword, int numECC, std::span erasures) +{ + // The number of erasures may at most be numECC. + // Note that leaves 0 parity symbols to error correction (or even detection) but the final syndrome verification in rs::decode will + // still catch any errors in that case. + if (Size(erasures) > numECC) + return {}; + +#if ZXING_ENABLE_PDF417 + if (field == RSField::PDF417) + return UEC(rs::decode(GetGFPDF417(), codeword, numECC, erasures), numECC); + else +#endif + return UEC(rs::decode(GetGF2n(field), codeword, numECC, erasures), numECC); +} +#endif + +#ifdef ZXING_WRITERS +void ReedSolomonEncode(RSField field, std::span data, std::span parity) +{ + rs::encode(GetGF2n(field), data, parity); +} + +void ReedSolomonEncode(RSField field, std::span codeword, int numECC) +{ + rs::encode_inplace(GetGF2n(field), codeword, numECC); +} + +void ReedSolomonEncode(RSField field, std::span codeword, int numECC) +{ +#if ZXING_ENABLE_PDF417 + if (field == RSField::PDF417) + return rs::encode_inplace(GetGFPDF417(), codeword, numECC); + else +#endif + rs::encode_inplace(GetGF2n(field), codeword, numECC); +} +#endif + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ReedSolomon.h b/tools/chatgui/zxing-cpp/core/src/ReedSolomon.h new file mode 100644 index 00000000..1165acd4 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ReedSolomon.h @@ -0,0 +1,65 @@ +// Copyright 2026 Axel Waggershauser +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +namespace ZXing { + +enum class RSField +{ + Aztec4, + Aztec6, + Aztec8, + Aztec10, + Aztec12, + DataMatrix, + MaxiCode, + PDF417, + QRCode +}; + +inline RSField GF2nAztec(int wordSize) +{ + switch (wordSize) { + case 4: return RSField::Aztec4; + case 6: return RSField::Aztec6; + case 8: return RSField::Aztec8; + case 10: return RSField::Aztec10; + case 12: return RSField::Aztec12; + default: throw std::invalid_argument("Unsupported word size " + std::to_string(wordSize)); + } +} + +/** + * @brief ReedSolomonDecode fixes errors in a codeword containing both data and parity symbols. + * + * @param codeword data and error-correction/parity symbols; corrected in place on success + * @param numECC number of error-correction/parity symbols + * @param erasures positions of known erasures in the codeword + * @return optional unused error correction in the range [0, 1] if codeword errors could successfully be fixed (or there have not been + * any), std::nullopt otherwise + */ +std::optional ReedSolomonDecode(RSField field, std::span codeword, int numECC, std::span erasures = {}); + +std::optional ReedSolomonDecode(RSField field, std::span codeword, int numECC, + std::span erasures = {}); + +/** + * @brief ReedSolomonEncode generates error correction symbols for the given data symbols. + * + * @param field The Galois field to use for encoding. + * @param data The input data symbols. + * @param parity The output buffer for the generated error correction symbols. + */ +void ReedSolomonEncode(RSField field, std::span data, std::span parity); + +/// @brief ReedSolomonEncode replaces the last numECC symbols in codeword with parity symbols +void ReedSolomonEncode(RSField field, std::span codeword, int numECC); +void ReedSolomonEncode(RSField field, std::span codeword, int numECC); + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/RegressionLine.h b/tools/chatgui/zxing-cpp/core/src/RegressionLine.h new file mode 100644 index 00000000..7a53efce --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/RegressionLine.h @@ -0,0 +1,161 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Point.h" +#include "ZXAlgorithms.h" + +#include +#include +#include + +#ifdef PRINT_DEBUG +#include +#endif + +namespace ZXing { + +class RegressionLine +{ +protected: + std::vector _points; + PointF _directionInward; + PointF::value_t a = NAN, b = NAN, c = NAN; + + friend PointF intersect(const RegressionLine& l1, const RegressionLine& l2); + + template bool evaluate(const PointT* begin, const PointT* 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; + sumXX += d.x * d.x; + sumYY += d.y * d.y; + sumXY += d.x * d.y; + } + if (sumYY >= sumXX) { + auto l = std::sqrt(sumYY * sumYY + sumXY * sumXY); + a = +sumYY / l; + b = -sumXY / l; + } else { + auto l = std::sqrt(sumXX * sumXX + sumXY * sumXY); + a = +sumXY / l; + b = -sumXX / l; + } + if (dot(_directionInward, normal()) < 0) { + a = -a; + b = -b; + } + c = dot(normal(), mean); // (a*mean.x + b*mean.y); + return dot(_directionInward, normal()) > 0.5f; // angle between original and new direction is at most 60 degree + } + + template bool evaluate(const std::vector>& points) { return evaluate(&points.front(), &points.back() + 1); } + + template static auto distance(PointT a, PointT b) { return ZXing::distance(a, b); } + +public: + RegressionLine() { _points.reserve(16); } // arbitrary but plausible start size (tiny performance improvement) + + template RegressionLine(PointT a, PointT b) + { + evaluate(std::vector{a, b}); + } + + template RegressionLine(const PointT* b, const PointT* e) + { + evaluate(b, e); + } + + const auto& points() const { return _points; } + int length() const { return _points.size() >= 2 ? int(distance(_points.front(), _points.back())) : 0; } + bool isValid() const { return !std::isnan(a); } + PointF normal() const { return isValid() ? PointF(a, b) : _directionInward; } + auto signedDistance(PointF p) const { return dot(normal(), p) - c; } + template auto distance(PointT p) const { return std::abs(signedDistance(PointF(p))); } + PointF project(PointF p) const { return p - signedDistance(p) * normal(); } + PointF centroid() const { return Reduce(_points) / _points.size(); } + + void reset() + { + _points.clear(); + _directionInward = {}; + a = b = c = NAN; + } + + void add(PointF p) { + assert(_directionInward != PointF()); + _points.push_back(p); + if (_points.size() == 1) + c = dot(normal(), p); + } + + void pop_back() { _points.pop_back(); } + void pop_front() + { + std::rotate(_points.begin(), _points.begin() + 1, _points.end()); + _points.pop_back(); + } + + void setDirectionInward(PointF d) { _directionInward = normalized(d); } + + bool evaluate(double maxSignedDist = -1, bool updatePoints = false) + { + bool ret = evaluate(_points); + if (maxSignedDist > 0) { + auto points = _points; + while (true) { + auto old_points_size = points.size(); + // remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist + std::erase_if(points, [this, maxSignedDist](auto p) { + auto sd = this->signedDistance(p); + return sd > maxSignedDist || sd < -2 * maxSignedDist; + }); + // if we threw away too many points, something is off with the line to begin with + if (points.size() < old_points_size / 2 || points.size() < 2) + return false; + if (old_points_size == points.size()) + break; +#ifdef PRINT_DEBUG + printf("removed %zu points -> %zu remaining\n", old_points_size - points.size(), points.size()); + fflush(stdout); +#endif + ret = evaluate(points); + } + + if (updatePoints) + _points = std::move(points); + } + return ret; + } + + bool isHighRes() const + { + PointF min = _points.front(), max = _points.front(); + for (auto p : _points) { + UpdateMinMax(min.x, max.x, p.x); + UpdateMinMax(min.y, max.y, p.y); + } + auto diff = max - min; + auto len = maxAbsComponent(diff); + auto steps = std::min(std::abs(diff.x), std::abs(diff.y)); + // due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal + return steps > 2 || len > 50; + } +}; + +inline PointF intersect(const RegressionLine& l1, const RegressionLine& l2) +{ + assert(l1.isValid() && l2.isValid()); + auto d = l1.a * l2.b - l1.b * l2.a; + auto x = (l1.c * l2.b - l1.b * l2.c) / d; + auto y = (l1.a * l2.c - l1.c * l2.a) / d; + return {x, y}; +} + +} // ZXing + diff --git a/tools/chatgui/zxing-cpp/core/src/ResultPoint.cpp b/tools/chatgui/zxing-cpp/core/src/ResultPoint.cpp new file mode 100644 index 00000000..134216aa --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ResultPoint.cpp @@ -0,0 +1,20 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "ResultPoint.h" + +#include + +namespace ZXing { + +float ResultPoint::Distance(int aX, int aY, int bX, int bY) +{ + auto dx = static_cast(aX - bX); + auto dy = static_cast(aY - bY); + return std::sqrt(dx * dx + dy * dy); +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ResultPoint.h b/tools/chatgui/zxing-cpp/core/src/ResultPoint.h new file mode 100644 index 00000000..067b648f --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ResultPoint.h @@ -0,0 +1,35 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Point.h" + +namespace ZXing { + +/** +*

Encapsulates a point of interest in an image containing a barcode. Typically, this +* would be the location of a finder pattern or the corner of the barcode, for example.

+* +* @author Sean Owen +*/ +class ResultPoint : public PointF +{ +public: + ResultPoint() = default; + ResultPoint(float x, float y) : PointF(x, y) {} + ResultPoint(int x, int y) : PointF(x, y) {} + template ResultPoint(PointT p) : PointF(p) {} + + float x() const { return static_cast(PointF::x); } + float y() const { return static_cast(PointF::y); } + + void set(float x, float y) { *this = PointF(x, y); } + + static float Distance(int aX, int aY, int bX, int bY); +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/StdGenerator.h b/tools/chatgui/zxing-cpp/core/src/StdGenerator.h new file mode 100644 index 00000000..68a9ec28 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/StdGenerator.h @@ -0,0 +1,121 @@ +/* + * Copyright 2026 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#if defined(__cpp_lib_generator) + +#include + +#else + +#include +#include +#include +#include +#include + +namespace std { + +template +class generator +{ +public: + struct promise_type + { + std::optional value_; + + generator get_return_object() noexcept { return generator{handle_type::from_promise(*this)}; } + + static std::suspend_always initial_suspend() noexcept { return {}; } + static std::suspend_always final_suspend() noexcept { return {}; } + + std::suspend_always yield_value(T&& value) noexcept + { + value_ = std::move(value); + return {}; + } + + static void return_void() noexcept {} + // Disallow co_await in generator coroutines. + void await_transform() = delete; + [[noreturn]] static void unhandled_exception() { throw; } + }; + + using handle_type = std::coroutine_handle; + + generator() noexcept = default; + explicit generator(handle_type h) noexcept : h_(h) {} + ~generator() + { + if (h_) + h_.destroy(); + } + + generator(generator&& other) noexcept : h_(other.h_) { other.h_ = nullptr; } + + generator& operator=(generator&& other) noexcept + { + if (this != &other) { + if (h_) + h_.destroy(); + h_ = other.h_; + other.h_ = nullptr; + } + return *this; + } + + generator(const generator&) = delete; + generator& operator=(const generator&) = delete; + + class iterator + { + public: + void operator++() { h_.resume(); } + T&& operator*() const noexcept { return std::move(*h_.promise().value_); } + bool operator==(std::default_sentinel_t) const noexcept { return !h_ || h_.done(); } + + explicit iterator(handle_type h) : h_(h) {} + + private: + handle_type h_ = nullptr; + }; + + iterator begin() + { + if (h_) + h_.resume(); + return iterator{h_}; + } + + std::default_sentinel_t end() noexcept { return {}; } + +private: + handle_type h_; +}; + +} // namespace std + +#endif // __cpp_lib_generator + +/* +usage example: + +template +std::generator range(T first, const T last) +{ + while (first < last) + co_yield first++; +} + +int main() +{ + for (const char i : range(65, 91)) + std::print("{} ", i); + std::println(""); +} +*/ diff --git a/tools/chatgui/zxing-cpp/core/src/StdPrint.h b/tools/chatgui/zxing-cpp/core/src/StdPrint.h new file mode 100644 index 00000000..1b4fa64a --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/StdPrint.h @@ -0,0 +1,38 @@ +/* + * Copyright 2026 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#if defined(__cpp_lib_print) && __cpp_lib_print >= 202207L + +#include + +#else + +#include +#include +#include + +namespace std { + +template +inline void print(std::format_string fmt, Args&&... args) +{ + auto s = std::format(fmt, std::forward(args)...); + std::fwrite(s.data(), 1, s.size(), stdout); +} + +template +inline void println(std::format_string fmt, Args&&... args) +{ + std::print(fmt, std::forward(args)...); + std::fputc('\n', stdout); +} + +} // namespace std + +#endif diff --git a/tools/chatgui/zxing-cpp/core/src/StdScope.h b/tools/chatgui/zxing-cpp/core/src/StdScope.h new file mode 100644 index 00000000..4cad2576 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/StdScope.h @@ -0,0 +1,55 @@ +/* + * Copyright 2026 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// idea from (library fundamentals TS v3) + +#include +#include + +namespace std { + +template +class scope_exit +{ +public: + explicit scope_exit(F&& f) noexcept(std::is_nothrow_move_constructible_v) : func_(std::forward(f)), active_(true) {} + + scope_exit(scope_exit&& other) noexcept(std::is_nothrow_move_constructible_v) + : func_(std::move(other.func_)), active_(std::exchange(other.active_, false)) + {} + + scope_exit(const scope_exit&) = delete; + scope_exit& operator=(const scope_exit&) = delete; + scope_exit& operator=(scope_exit&&) = delete; + + ~scope_exit() noexcept(noexcept(std::declval()())) + { + if (active_) + func_(); + } + + void release() noexcept { active_ = false; } + +private: + F func_; + bool active_; +}; + +template +scope_exit(F) -> scope_exit; + +} // namespace std + +/** + * The SCOPE_EXIT macro is eliminating the need to give the object a name. + * Example usage: + * SCOPE_EXIT([]{ printf("exiting scope"); }); + */ + +#define SCOPE_EXIT_CAT2(x, y) x##y +#define SCOPE_EXIT_CAT(x, y) SCOPE_EXIT_CAT2(x, y) +#define SCOPE_EXIT const auto SCOPE_EXIT_CAT(scopeExit_, __COUNTER__) = std::scope_exit diff --git a/tools/chatgui/zxing-cpp/core/src/StructuredAppend.h b/tools/chatgui/zxing-cpp/core/src/StructuredAppend.h new file mode 100644 index 00000000..105d5ebd --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/StructuredAppend.h @@ -0,0 +1,19 @@ +/* +* Copyright 2021 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace ZXing { + +struct StructuredAppendInfo +{ + int index = -1; + int count = -1; + std::string id; +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/SymbologyIdentifier.h b/tools/chatgui/zxing-cpp/core/src/SymbologyIdentifier.h new file mode 100644 index 00000000..d05d495b --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/SymbologyIdentifier.h @@ -0,0 +1,29 @@ +/* +* Copyright 2026 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ZXAlgorithms.h" + +#include + +namespace ZXing { + +enum class AIFlag : char { None, GS1, AIM }; + +struct SymbologyIdentifier +{ + char code = 0, modifier = 0; + signed char eciModifierOffset = 0; + AIFlag aiFlag = AIFlag::None; + + std::string toString(bool hasECI = false) const + { + int modVal = (modifier >= 'A' ? modifier - 'A' + 10 : modifier - '0') + eciModifierOffset * hasECI; + return code ? StrCat(']', code, static_cast((modVal >= 10 ? 'A' - 10 : '0') + modVal)) : std::string(); + } +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/TextDecoder.cpp b/tools/chatgui/zxing-cpp/core/src/TextDecoder.cpp new file mode 100644 index 00000000..8a9460a9 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/TextDecoder.cpp @@ -0,0 +1,54 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2022 gitlost +* Copyright 2025 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "TextDecoder.h" + +#include "ZXAlgorithms.h" +#include "libzueci/zueci.h" + +#include +#include +#include + +namespace ZXing { + +std::string BytesToUtf8(ByteView bytes, ECI eci) +{ + constexpr unsigned int replacement = 0xFFFD; + constexpr unsigned int flags = ZUECI_FLAG_SB_STRAIGHT_THRU | ZUECI_FLAG_SJIS_STRAIGHT_THRU; + int utf8_len; + + if (eci == ECI::Unknown) + eci = ECI::Binary; + + int error_number = zueci_dest_len_utf8(ToInt(eci), bytes.data(), Size(bytes), replacement, flags, &utf8_len); + if (error_number >= ZUECI_ERROR) { +#ifdef PRINT_DEBUG + fprintf(stderr, "zueci_dest_len_utf8 error: %d\n", error_number); +#endif + // throw FormatError("Invalid UTF-8 data"); + return {}; + } + + std::string utf8(utf8_len, 0); + + error_number = zueci_eci_to_utf8(ToInt(eci), bytes.data(), Size(bytes), replacement, flags, + reinterpret_cast(utf8.data()), &utf8_len); + if (error_number >= ZUECI_ERROR) { +#ifdef PRINT_DEBUG + fprintf(stderr, "zueci_eci_to_utf8 error: %d\n", error_number); +#endif + // throw FormatError("Invalid UTF-8 data"); + return {}; + } + + assert(Size(utf8) == utf8_len); + + return utf8; +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/TextDecoder.h b/tools/chatgui/zxing-cpp/core/src/TextDecoder.h new file mode 100644 index 00000000..0117a422 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/TextDecoder.h @@ -0,0 +1,23 @@ +/* +* Copyright 2025 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "CharacterSet.h" +#include "ECI.h" +#include "Range.h" + +#include + +namespace ZXing { + +std::string BytesToUtf8(ByteView bytes, ECI eci); + +inline std::string BytesToUtf8(ByteView bytes, CharacterSet cs) +{ + return BytesToUtf8(bytes, ToECI(cs)); +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/TextEncoder.cpp b/tools/chatgui/zxing-cpp/core/src/TextEncoder.cpp new file mode 100644 index 00000000..afcaef85 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/TextEncoder.cpp @@ -0,0 +1,71 @@ +/* +* Copyright 2016 Nu-book Inc. +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "TextEncoder.h" + +#include "CharacterSet.h" +#include "ECI.h" +#include "Utf.h" +#include "ZXAlgorithms.h" +#include "Version.h" + +#ifdef ZXING_USE_ZINT +#include +#else +#include "libzueci/zueci.h" +#endif + +#include + +namespace ZXing { + +void TextEncoder::GetBytes(const std::string& str, CharacterSet charset, std::string& bytes) +{ + int eci = ToInt(ToECI(charset)); + const int str_len = narrow_cast(str.length()); + int eci_len; + + if (eci == -1) + eci = 899; // Binary + + bytes.clear(); + +#ifdef ZXING_USE_ZINT + int error_number = ZBarcode_Dest_Len_ECI(eci, reinterpret_cast(str.data()), str_len, &eci_len); + if (error_number >= ZINT_ERROR) // Shouldn't happen + throw std::logic_error("Internal error `ZBarcode_Dest_Len_ECI()`"); + + bytes.resize(eci_len); // Sufficient but approximate length + + error_number = ZBarcode_UTF8_To_ECI(eci,reinterpret_cast(str.data()), str_len, + reinterpret_cast(bytes.data()), &eci_len); + if (error_number >= ZINT_ERROR) { + bytes.clear(); + throw std::invalid_argument("Unexpected charcode"); + } +#else + int error_number = zueci_dest_len_eci(eci, reinterpret_cast(str.data()), str_len, &eci_len); + if (error_number >= ZUECI_ERROR) // Shouldn't happen + throw std::logic_error("Internal error `zueci_dest_len_eci()`"); + + bytes.resize(eci_len); // Sufficient but approximate length + + error_number = zueci_utf8_to_eci(eci, reinterpret_cast(str.data()), str_len, + reinterpret_cast(bytes.data()), &eci_len); + if (error_number >= ZUECI_ERROR) { + bytes.clear(); + throw std::invalid_argument("Unexpected charcode"); + } +#endif // ZXING_USE_ZINT + + bytes.resize(eci_len); // Actual length +} + +void TextEncoder::GetBytes(const std::wstring& str, CharacterSet charset, std::string& bytes) +{ + GetBytes(ToUtf8(str), charset, bytes); +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/TextEncoder.h b/tools/chatgui/zxing-cpp/core/src/TextEncoder.h new file mode 100644 index 00000000..15c85671 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/TextEncoder.h @@ -0,0 +1,31 @@ +/* +* Copyright 2016 Nu-book Inc. +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "CharacterSet.h" + +#include + +namespace ZXing { + +class TextEncoder +{ + static void GetBytes(const std::string& str, CharacterSet charset, std::string& bytes); + static void GetBytes(const std::wstring& str, CharacterSet charset, std::string& bytes); +public: + static std::string FromUnicode(const std::string& str, CharacterSet charset) { + std::string r; + GetBytes(str, charset, r); + return r; + } + static std::string FromUnicode(const std::wstring& str, CharacterSet charset) { + std::string r; + GetBytes(str, charset, r); + return r; + } +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ThresholdBinarizer.h b/tools/chatgui/zxing-cpp/core/src/ThresholdBinarizer.h new file mode 100644 index 00000000..c24636aa --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ThresholdBinarizer.h @@ -0,0 +1,58 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BinaryBitmap.h" +#include "BitMatrix.h" + +#include + +namespace ZXing { + +class ThresholdBinarizer : public BinaryBitmap +{ + const uint8_t _threshold = 0; + +public: + ThresholdBinarizer(const ImageView& buffer, uint8_t threshold = 128) : BinaryBitmap(buffer), _threshold(threshold) {} + + bool getPatternRow(int row, int rotation, PatternRow& res) const override + { + auto buffer = _buffer.rotated(rotation); + + const int stride = buffer.pixStride(); + const uint8_t* begin = buffer.data(0, row) + GreenIndex(buffer.format()); + const uint8_t* end = begin + buffer.width() * stride; + + auto* lastPos = begin; + bool lastVal = false; + + res.clear(); + + for (const uint8_t* p = begin; p != end; p += stride) { + bool val = *p <= _threshold; + if (val != lastVal) { + res.push_back(narrow_cast((p - lastPos) / stride)); + lastVal = val; + lastPos = p; + } + } + + res.push_back(narrow_cast((end - lastPos) / stride)); + + if (*(end - stride) <= _threshold) + res.push_back(0); // last value is number of white pixels, here 0 + + return true; + } + + std::shared_ptr getBlackMatrix() const override + { + return std::make_shared(binarize(_threshold)); + } +}; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/TritMatrix.h b/tools/chatgui/zxing-cpp/core/src/TritMatrix.h new file mode 100644 index 00000000..8e70ff9f --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/TritMatrix.h @@ -0,0 +1,29 @@ +/* +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Matrix.h" + +#include + +namespace ZXing { + +/** + * @brief Represent a tri-state value false/true/empty + */ +class Trit +{ +public: + enum value_t : uint8_t {false_v, true_v, empty_v} value = empty_v; + Trit() = default; + Trit(bool v) : value(static_cast(v)) {} + operator bool() const { return value == true_v; } + bool isEmpty() const { return value == empty_v; } +}; + +using TritMatrix = Matrix; + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/Utf.cpp b/tools/chatgui/zxing-cpp/core/src/Utf.cpp new file mode 100644 index 00000000..c9ae58bd --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Utf.cpp @@ -0,0 +1,276 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2021 gitlost +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "Utf.h" + +#include "ZXTestSupport.h" +#include "ZXAlgorithms.h" + +#include +#include +#include +#include + +using utf8_t = std::u8string_view; + +namespace ZXing { + +using state_t = uint8_t; +constexpr state_t kAccepted = 0; +constexpr state_t kRejected [[maybe_unused]] = 12; + +inline char32_t Utf8Decode(char8_t byte, state_t& state, char32_t& codep) +{ + // Copyright (c) 2008-2009 Bjoern Hoehrmann + // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + static constexpr const state_t kUtf8Data[] = { + /* The first part of the table maps bytes to character classes that + * reduce the size of the transition table and create bitmasks. */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + /* The second part is a transition table that maps a combination + * of a state of the automaton and a character class to a state. */ + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, + }; + + state_t type = kUtf8Data[byte]; + codep = (state != kAccepted) ? (byte & 0x3fu) | (codep << 6) : (0xff >> type) & (byte); + state = kUtf8Data[256 + state + type]; + return state; +} + +static_assert(sizeof(wchar_t) == 4 || sizeof(wchar_t) == 2, "wchar_t needs to be 2 or 4 bytes wide"); + +inline bool IsUtf16SurrogatePair(std::wstring_view str) +{ + return sizeof(wchar_t) == 2 && str.size() >= 2 && (str[0] & 0xfc00) == 0xd800 && (str[1] & 0xfc00) == 0xdc00; +} + +inline char32_t Utf32FromUtf16Surrogates(std::wstring_view str) +{ + return (static_cast(str[0]) << 10) + str[1] - 0x35fdc00; +} + +static size_t Utf8CountCodePoints(utf8_t utf8) +{ + size_t count = 0; + + for (size_t i = 0; i < utf8.size();) { + if (utf8[i] < 128) { + ++i; + } else { + switch (utf8[i] & 0xf0) { + case 0xc0: [[fallthrough]]; + case 0xd0: i += 2; break; + case 0xe0: i += 3; break; + case 0xf0: i += 4; break; + default: // we are in middle of a sequence + ++i; + while (i < utf8.size() && (utf8[i] & 0xc0) == 0x80) + ++i; + break; + } + } + ++count; + } + + return count; +} + +static void AppendFromUtf8(utf8_t utf8, std::wstring& buffer) +{ + buffer.reserve(buffer.size() + Utf8CountCodePoints(utf8)); + + char32_t codePoint = 0; + state_t state = kAccepted; + + for (auto b : utf8) { + if (Utf8Decode(b, state, codePoint) != kAccepted) + continue; + + if (sizeof(wchar_t) == 2 && codePoint > 0xffff) { // surrogate pair + buffer.push_back(narrow_cast(0xd7c0 + (codePoint >> 10))); + buffer.push_back(narrow_cast(0xdc00 + (codePoint & 0x3ff))); + } else { + buffer.push_back(narrow_cast(codePoint)); + } + } +} + +bool IsValidUtf8(ByteView bytes) +{ + state_t state = kAccepted; + char32_t codepoint = 0; + for (int value : bytes) { + Utf8Decode(value, state, codepoint); + if (state == kRejected) + return false; + } + return state == kAccepted; +} + +std::wstring FromUtf8(std::string_view utf8) +{ + std::wstring str; + AppendFromUtf8({reinterpret_cast(utf8.data()), utf8.size()}, str); + return str; +} + +std::wstring FromUtf8(std::u8string_view utf8) +{ + std::wstring str; + AppendFromUtf8(utf8, str); + return str; +} + +// Count the number of bytes required to store given code points in UTF-8. +static size_t Utf8CountBytes(std::wstring_view str) +{ + int result = 0; + for (; str.size(); str.remove_prefix(1)) { + if (str.front() < 0x80) + result += 1; + else if (str.front() < 0x800) + result += 2; + else if (sizeof(wchar_t) == 4) { + if (str.front() < 0x10000) + result += 3; + else + result += 4; + } else { + if (IsUtf16SurrogatePair(str)) { + result += 4; + str.remove_prefix(1); + } else + result += 3; + } + } + return result; +} + +ZXING_EXPORT_TEST_ONLY +int Utf32ToUtf8(char32_t utf32, char* out) +{ + if (utf32 < 0x80) { + *out++ = narrow_cast(utf32); + return 1; + } + if (utf32 < 0x800) { + *out++ = narrow_cast((utf32 >> 6) | 0xc0); + *out++ = narrow_cast((utf32 & 0x3f) | 0x80); + return 2; + } + if (utf32 < 0x10000) { + *out++ = narrow_cast((utf32 >> 12) | 0xe0); + *out++ = narrow_cast(((utf32 >> 6) & 0x3f) | 0x80); + *out++ = narrow_cast((utf32 & 0x3f) | 0x80); + return 3; + } + + *out++ = narrow_cast((utf32 >> 18) | 0xf0); + *out++ = narrow_cast(((utf32 >> 12) & 0x3f) | 0x80); + *out++ = narrow_cast(((utf32 >> 6) & 0x3f) | 0x80); + *out++ = narrow_cast((utf32 & 0x3f) | 0x80); + return 4; +} + +static void AppendToUtf8(std::wstring_view str, std::string& utf8) +{ + utf8.reserve(utf8.size() + Utf8CountBytes(str)); + + char buffer[4]; + for (; str.size(); str.remove_prefix(1)) + { + uint32_t cp; + if (IsUtf16SurrogatePair(str)) { + cp = Utf32FromUtf16Surrogates(str); + str.remove_prefix(1); + } else + cp = str.front(); + + auto bufLength = Utf32ToUtf8(cp, buffer); + utf8.append(buffer, bufLength); + } +} + +std::string ToUtf8(std::wstring_view str) +{ + std::string utf8; + AppendToUtf8(str, utf8); + return utf8; +} + +static bool iswgraph(wchar_t wc) +{ + /* Consider all legal codepoints as graphical except for: + * - whitespace + * - C0 and C1 control characters + * - U+2028 and U+2029 (line/para break) + * - U+FFF9 through U+FFFB (interlinear annotation controls) + * The following code is based on libmusls implementation */ + + if (wc == ' ' || (unsigned)wc - '\t' < 5) + return false; + if (wc < 0xff) + return ((wc + 1) & 0x7f) >= 0x21; + if (wc < 0x2028 || wc - 0x202a < 0xd800 - 0x202a || wc - 0xe000 < 0xfff9 - 0xe000) + return true; + if (wc - 0xfffc > 0x10ffff - 0xfffc || (wc & 0xfffe) == 0xfffe) + return false; + return true; +} + +std::wstring EscapeNonGraphical(std::wstring_view str) +{ + static const char* const ascii_nongraphs[33] = { + "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", + "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", + "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", + "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US", + "DEL", + }; + + std::wostringstream ws; + ws.fill(L'0'); + + for (; str.size(); str.remove_prefix(1)) { + wchar_t wc = str.front(); + if (wc < 32 || wc == 127) // Non-graphical ASCII, excluding space + ws << "<" << ascii_nongraphs[wc == 127 ? 32 : wc] << ">"; + else if (wc < 128) // ASCII + ws << wc; + else if (IsUtf16SurrogatePair(str)) { + ws.write(str.data(), 2); + str.remove_prefix(1); + } + // Exclude unpaired surrogates and NO-BREAK spaces NBSP and NUMSP + else if ((wc < 0xd800 || wc >= 0xe000) && (iswgraph(wc) && wc != 0xA0 && wc != 0x2007 && wc != 0x2000 && wc != 0xfffd)) + ws << wc; + else // Non-graphical Unicode + ws << "(wc) << ">"; + } + + return ws.str(); +} + +std::string EscapeNonGraphical(std::string_view utf8) +{ + return ToUtf8(EscapeNonGraphical(FromUtf8(utf8))); +} + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/Utf.h b/tools/chatgui/zxing-cpp/core/src/Utf.h new file mode 100644 index 00000000..9c8946ad --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/Utf.h @@ -0,0 +1,26 @@ +/* +* Copyright 2022 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Range.h" + +#include +#include + +namespace ZXing { + +bool IsValidUtf8(ByteView bytes); + +std::string ToUtf8(std::wstring_view str); +std::wstring FromUtf8(std::string_view utf8); +#ifdef __cpp_lib_char8_t +std::wstring FromUtf8(std::u8string_view utf8); +#endif + +std::wstring EscapeNonGraphical(std::wstring_view str); +std::string EscapeNonGraphical(std::string_view utf8); + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/WhiteRectDetector.cpp b/tools/chatgui/zxing-cpp/core/src/WhiteRectDetector.cpp new file mode 100644 index 00000000..9644a271 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/WhiteRectDetector.cpp @@ -0,0 +1,276 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "WhiteRectDetector.h" + +#include "BitMatrix.h" +#include "BitMatrixCursor.h" +#include "ResultPoint.h" + +namespace ZXing { + +static const int INIT_SIZE = 10; +static const int CORR = 1; + +bool DetectWhiteRect(const BitMatrix& image, ResultPoint& p0, ResultPoint& p1, ResultPoint& p2, ResultPoint& p3) +{ + return DetectWhiteRect(image, INIT_SIZE, image.width() / 2, image.height() / 2, p0, p1, p2, p3); +} + +/** +* Determines whether a segment contains a black point +* +* @param a min value of the scanned coordinate +* @param b max value of the scanned coordinate +* @param fixed value of fixed coordinate +* @param horizontal set to true if scan must be horizontal, false if vertical +* @return true if a black point has been found, else false. +*/ +static bool ContainsBlackPoint(const BitMatrix& image, int a, int b, int fixed, bool horizontal) { + + a = std::max(a, 0); + if (horizontal) { + if (fixed < 0 || fixed >= image.height()) + return false; + b = std::min(b, image.width() - 1); + for (int x = a; x <= b; x++) { + if (image.get(x, fixed)) { + return true; + } + } + } + else { + if (fixed < 0 || fixed >= image.width()) + return false; + b = std::min(b, image.height() - 1); + for (int y = a; y <= b; y++) { + if (image.get(fixed, y)) { + return true; + } + } + } + + return false; +} + +static bool GetBlackPointOnSegment(const BitMatrix& image, int aX, int aY, int bX, int bY, ResultPoint& result) +{ + PointF a(aX, aY), b(bX, bY); + BitMatrixCursorF cur(image, a, b - a); + + auto dist = std::lround(distance(a, b) / length(cur.d)); + + for (int i = 0; i < dist; i++) { + if (cur.isBlack()) { + result = cur.p; + return true; + } + cur.step(); + } + return false; +} + +/** +* recenters the points of a constant distance towards the center +* +* p0 to p3 describing the corners of the rectangular +* region. The first and last points are opposed on the diagonal, as +* are the second and third. The first point will be the topmost +* point and the last, the bottommost. The second point will be +* leftmost and the third, the rightmost +* +* @param y bottom most point +* @param z left most point +* @param x right most point +* @param t top most point +*/ +static void CenterEdges(const ResultPoint& y, const ResultPoint& z, const ResultPoint& x, const ResultPoint& t, int width, ResultPoint& p0, ResultPoint& p1, ResultPoint& p2, ResultPoint& p3) +{ + // + // t t + // z x + // x OR z + // y y + // + + float yi = y.x(); + float yj = y.y(); + float zi = z.x(); + float zj = z.y(); + float xi = x.x(); + float xj = x.y(); + float ti = t.x(); + float tj = t.y(); + + if (yi < width / 2.0f) { + p0 = ResultPoint(ti - CORR, tj + CORR); + p1 = ResultPoint(zi + CORR, zj + CORR); + p2 = ResultPoint(xi - CORR, xj - CORR); + p3 = ResultPoint(yi + CORR, yj - CORR); + } + else { + p0 = ResultPoint(ti + CORR, tj + CORR); + p1 = ResultPoint(zi + CORR, zj - CORR); + p2 = ResultPoint(xi - CORR, xj + CORR); + p3 = ResultPoint(yi - CORR, yj - CORR); + } +} + +bool DetectWhiteRect(const BitMatrix& image, int initSize, int x, int y, ResultPoint& p0, ResultPoint& p1, + ResultPoint& p2, ResultPoint& p3) +{ + int height = image.height(); + int width = image.width(); + int halfsize = initSize / 2; + int left = x - halfsize; + int right = x + halfsize; + int up = y - halfsize; + int down = y + halfsize; + if (up < 0 || left < 0 || down >= height || right >= width) { + return false; + } + + bool aBlackPointFoundOnBorder = true; + bool atLeastOneBlackPointFoundOnBorder = false; + + bool atLeastOneBlackPointFoundOnRight = false; + bool atLeastOneBlackPointFoundOnBottom = false; + bool atLeastOneBlackPointFoundOnLeft = false; + bool atLeastOneBlackPointFoundOnTop = false; + + while (aBlackPointFoundOnBorder) { + + aBlackPointFoundOnBorder = false; + + // ..... + // . | + // ..... + bool rightBorderNotWhite = true; + while ((rightBorderNotWhite || !atLeastOneBlackPointFoundOnRight) && right < width) { + rightBorderNotWhite = ContainsBlackPoint(image, up, down, right, false); + if (rightBorderNotWhite) { + right++; + aBlackPointFoundOnBorder = true; + atLeastOneBlackPointFoundOnRight = true; + } + else if (!atLeastOneBlackPointFoundOnRight) { + right++; + } + } + + // ..... + // . . + // .___. + bool bottomBorderNotWhite = true; + while ((bottomBorderNotWhite || !atLeastOneBlackPointFoundOnBottom) && down < height) { + bottomBorderNotWhite = ContainsBlackPoint(image, left, right, down, true); + if (bottomBorderNotWhite) { + down++; + aBlackPointFoundOnBorder = true; + atLeastOneBlackPointFoundOnBottom = true; + } + else if (!atLeastOneBlackPointFoundOnBottom) { + down++; + } + } + + // ..... + // | . + // ..... + bool leftBorderNotWhite = true; + while ((leftBorderNotWhite || !atLeastOneBlackPointFoundOnLeft) && left >= 0) { + leftBorderNotWhite = ContainsBlackPoint(image, up, down, left, false); + if (leftBorderNotWhite) { + left--; + aBlackPointFoundOnBorder = true; + atLeastOneBlackPointFoundOnLeft = true; + } + else if (!atLeastOneBlackPointFoundOnLeft) { + left--; + } + } + + // .___. + // . . + // ..... + bool topBorderNotWhite = true; + while ((topBorderNotWhite || !atLeastOneBlackPointFoundOnTop) && up >= 0) { + topBorderNotWhite = ContainsBlackPoint(image, left, right, up, true); + if (topBorderNotWhite) { + up--; + aBlackPointFoundOnBorder = true; + atLeastOneBlackPointFoundOnTop = true; + } + else if (!atLeastOneBlackPointFoundOnTop) { + up--; + } + } + + if (aBlackPointFoundOnBorder) { + atLeastOneBlackPointFoundOnBorder = true; + } + + } + + if (up < 0 || left < 0 || down >= height || right >= width) + return false; + + if (atLeastOneBlackPointFoundOnBorder) { + + int maxSize = right - left; + + ResultPoint z; + bool found = false; + for (int i = 1; !found && i < maxSize; i++) { + found = GetBlackPointOnSegment(image, left, down - i, left + i, down, z); + } + + if (!found) { + return false; + } + + ResultPoint t; + found = false; + //go down right + for (int i = 1; !found && i < maxSize; i++) { + found = GetBlackPointOnSegment(image, left, up + i, left + i, up, t); + } + + if (!found) { + return false; + } + + ResultPoint x; + found = false; + //go down left + for (int i = 1; !found && i < maxSize; i++) { + found = GetBlackPointOnSegment(image, right, up + i, right - i, up, x); + } + + if (!found) { + return false; + } + + ResultPoint y; + found = false; + //go up left + for (int i = 1; !found && i < maxSize; i++) { + found = GetBlackPointOnSegment(image, right, down - i, right - i, down, y); + } + + if (!found) { + return false; + } + + CenterEdges(y, z, x, t, width, p0, p1, p2, p3); + return true; + } + else { + return false; + } +} + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/WhiteRectDetector.h b/tools/chatgui/zxing-cpp/core/src/WhiteRectDetector.h new file mode 100644 index 00000000..db4d11f0 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/WhiteRectDetector.h @@ -0,0 +1,36 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace ZXing { + +class BitMatrix; +class ResultPoint; + +/** + *

+ * Detects a candidate barcode-like rectangular region within an image. It + * starts around the center of the image, increases the size of the candidate + * region until it finds a white rectangular region. + *

+ * + * @param image barcode image to find a rectangle in + * @param initSize initial size of search area around center + * @param x x position of search center + * @param y y position of search center + * @return {@link ResultPoint}[] describing the corners of the rectangular + * region. The first and last points are opposed on the diagonal, as + * are the second and third. The first point will be the topmost + * point and the last, the bottommost. The second point will be + * leftmost and the third, the rightmost + * @return true iff white rect was found + */ +bool DetectWhiteRect(const BitMatrix& image, int initSize, int x, int y, ResultPoint& p0, ResultPoint& p1, + ResultPoint& p2, ResultPoint& p3); +bool DetectWhiteRect(const BitMatrix& image, ResultPoint& p0, ResultPoint& p1, ResultPoint& p2, ResultPoint& p3); + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/WriteBarcode.cpp b/tools/chatgui/zxing-cpp/core/src/WriteBarcode.cpp new file mode 100644 index 00000000..49528d22 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/WriteBarcode.cpp @@ -0,0 +1,213 @@ +/* +* Copyright 2024 Axel Waggershauser +* Copyright 2025 gitlost +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "WriteBarcode.h" + +#include "BarcodeData.h" +#include "BitMatrix.h" +#include "CreateBarcode.h" +#include "Version.h" + +#include + +#ifdef ZXING_USE_ZINT +#include +#endif // ZXING_USE_ZINT + +namespace ZXing { + +struct WriterOptions::Data +{ + int scale = 1; + int rotate = 0; + bool invert = false; + bool addHRT = false; + bool addQuietZones = 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, rotate) +ZX_PROPERTY(bool, invert) +ZX_PROPERTY(bool, addHRT) +ZX_PROPERTY(bool, addQuietZones) + +#undef ZX_PROPERTY + +WriterOptions::WriterOptions() : d(std::make_unique()) {} +WriterOptions::~WriterOptions() = default; +WriterOptions::WriterOptions(WriterOptions&&) noexcept = default; +WriterOptions& WriterOptions::operator=(WriterOptions&&) noexcept = default; + +#ifdef ZXING_USE_ZINT + +struct SetCommonWriterOptions +{ + zint_symbol* zint; + + SetCommonWriterOptions(zint_symbol* zint, const WriterOptions& opts) : zint(zint) + { + zint->show_hrt = opts.addHRT(); + + zint->output_options &= ~(OUT_BUFFER_INTERMEDIATE | BARCODE_NO_QUIET_ZONES); + zint->output_options |= opts.addQuietZones() ? BARCODE_QUIET_ZONES : BARCODE_NO_QUIET_ZONES; + + if (opts.scale() > 0) + zint->scale = opts.scale() / 2.f; + else if (opts.scale() < 0) { + int size = std::max(zint->width, zint->rows); + zint->scale = std::max(1, int(float(-opts.scale()) / size)) / 2.f; + } + + if (opts.invert()) { + strcpy(zint->bgcolour, "000000"); + strcpy(zint->fgcolour, "ffffff"); + } + } + + // reset the defaults such that consecutive write calls don't influence each other + ~SetCommonWriterOptions() + { + zint->scale = 0.5f; + strcpy(zint->fgcolour, "000000"); + strcpy(zint->bgcolour, "ffffff"); + } +}; + +#define CHECK(ZINT_CALL) \ + if (int err = (ZINT_CALL); err >= ZINT_ERROR) \ + throw std::invalid_argument(StrCat(zint->errtxt, " (retval: ", std::to_string(err), ")")); + +#endif // ZXING_USE_ZINT + +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 << R"()" << "\n" + << R"()" << "\n" + << "\n"; + + return res.str(); +} + +static Image ToImage(BitMatrix bits, bool isLinearCode, const WriterOptions& opts) +{ + bits.flipAll(); + int width = opts.scale() > 0 ? bits.width() * opts.scale() : -opts.scale(); + int height = isLinearCode ? std::clamp(width / 2, 50, 300) : opts.scale() > 0 ? bits.height() * opts.scale() : -opts.scale(); + auto symbol = Inflate(std::move(bits), width, height, opts.addQuietZones() ? 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; +} + +std::string WriteBarcodeToSVG(const Barcode& barcode, [[maybe_unused]] const WriterOptions& options) +{ +#if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT) + auto* zint = barcode.d->zint.get(); + + if (!zint) +#endif + return ToSVG(barcode.symbol()); + +#if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT) + auto zintLock = std::lock_guard(*barcode.d->zintMutex); + auto resetOnExit = SetCommonWriterOptions(zint, options); + + zint->output_options |= BARCODE_MEMORY_FILE;// | EMBED_VECTOR_FONT; + strcpy(zint->outfile, "null.svg"); + + CHECK(ZBarcode_Print(zint, options.rotate())); + + return std::string(reinterpret_cast(zint->memfile), zint->memfile_size); +#endif +} + +Image WriteBarcodeToImage(const Barcode& barcode, [[maybe_unused]] const WriterOptions& options) +{ +#if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT) + auto* zint = barcode.d->zint.get(); + + if (!zint) +#endif + return ToImage(barcode.d->symbol.copy(), barcode.format() & BarcodeFormat::AllLinear, options); + +#if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT) + auto zintLock = std::lock_guard(*barcode.d->zintMutex); + auto resetOnExit = SetCommonWriterOptions(zint, options); + + CHECK(ZBarcode_Buffer(zint, options.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; +#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 invert = !options.invert(); + + Image buffer; + if (options.addQuietZones()) { + buffer = Image(iv.width() + 2, iv.height() + 2); + memset(const_cast(buffer.data()), 0xff, buffer.rowStride() * buffer.height()); + for (int y = 0; y < iv.height(); y++) + memcpy(const_cast(buffer.data(1, y + 1)), iv.data(0, y), iv.width()); + iv = barcode.format() & BarcodeFormat::AllLinear ? iv.cropped(0, 1, buffer.width(), buffer.height() - 2) : buffer; + } + + for (int y = 0; y < iv.height(); y += 2) { + // for linear barcodes, only print line pairs that are distinct from the previous one + if (barcode.format() & BarcodeFormat::AllLinear && y > 1 && y < iv.height() - 1 + && memcmp(iv.data(0, y), iv.data(0, y - 2), 2 * iv.rowStride()) == 0) + continue; + + for (int x = 0; x < iv.width(); ++x) { + int tp = bool(*iv.data(x, y)) ^ invert; + int bt = (iv.height() == 1 && tp) || (y + 1 < iv.height() && (bool(*iv.data(x, y + 1)) ^ invert)); + res << map[tp | (bt << 1)]; + } + res << '\n'; + } + + return res.str(); +} + +} // namespace ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/WriteBarcode.h b/tools/chatgui/zxing-cpp/core/src/WriteBarcode.h new file mode 100644 index 00000000..9514452c --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/WriteBarcode.h @@ -0,0 +1,72 @@ +/* +* Copyright 2024 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Barcode.h" +#include "ImageView.h" + +#include + +namespace ZXing { + +/** + * @brief Configuration options for barcode writing/generation. + * + * WriterOptions provides a fluent interface for setting various parameters + * that control how barcodes are generated. + * + * This class supports method chaining for convenient option configuration: + * ```cpp + * auto opts = WriterOptions().scale(5).addHRT(true); + * ``` + */ +class WriterOptions +{ + struct Data; + + std::unique_ptr d; + +public: + WriterOptions(); + ~WriterOptions(); + WriterOptions(WriterOptions&&) noexcept; + WriterOptions& operator=(WriterOptions&&) noexcept; + +#define ZX_PROPERTY(TYPE, NAME) \ + TYPE NAME() const noexcept; \ + WriterOptions& NAME(TYPE v)&; \ + WriterOptions&& NAME(TYPE v)&&; + + /// scale factor for rendering, ie the module size (default: 1). Passing a negative value will choose the scale + /// automatically to fit the size of the barcode to abs(scale) as close as possible. + ZX_PROPERTY(int, scale) + + /// rotate the barcode by given degrees (0, 90, 180, 270) + ZX_PROPERTY(int, rotate) + + /// invert the colors of the barcode + ZX_PROPERTY(bool, invert) + + /// add human readable text (HRI) to the barcode + ZX_PROPERTY(bool, addHRT) + + /// add quiet zones around the barcode (default: true) + ZX_PROPERTY(bool, addQuietZones) + +#undef ZX_PROPERTY +}; + + +/// Write barcode symbol to SVG +std::string WriteBarcodeToSVG(const Barcode& barcode, const WriterOptions& options = {}); + +/// Write barcode symbol to a utf8 string using graphical characters (e.g. '▀') +std::string WriteBarcodeToUtf8(const Barcode& barcode, const WriterOptions& options = {}); + +/// Write barcode symbol to Image (Bitmap) +Image WriteBarcodeToImage(const Barcode& barcode, const WriterOptions& options = {}); + +} // ZXing diff --git a/tools/chatgui/zxing-cpp/core/src/ZXAlgorithms.h b/tools/chatgui/zxing-cpp/core/src/ZXAlgorithms.h new file mode 100644 index 00000000..6aa74091 --- /dev/null +++ b/tools/chatgui/zxing-cpp/core/src/ZXAlgorithms.h @@ -0,0 +1,339 @@ +/* +* Copyright 2017 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Error.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ZXing { + +template +constexpr T narrow_cast(U&& u) noexcept { + return static_cast(std::forward(u)); +} + +inline constexpr auto& Find = std::ranges::find; +inline constexpr auto& FindIf = std::ranges::find_if; + +template requires std::ranges::input_range +auto Contains(const Container& c, const Value& v) { + return std::ranges::find(c, v) != std::ranges::end(c); +} + +template +auto Contains(const std::initializer_list& c, const Value& v) { + return std::ranges::find(c, v) != std::ranges::end(c); +} + +inline bool Contains(const char* str, char c) { + return str && strchr(str, c) != nullptr; +} + +inline bool Contains(std::string_view str, std::string_view substr) { + return str.find(substr) != std::string_view::npos; +} + +template