zxing-cpp: strip to core library only (remove tests/wrappers/examples/docs); .gitignore: fix /core to allow zxing-cpp/core/ dir
This commit is contained in:
1 parent
22c24d135e
commit
8051dfe4e5
2506 files changed
+57271
-30087
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
test/samples export-ignore
|
||||
zint export-ignore
|
||||
.git* export-ignore
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
github: axxel
|
||||
custom: 'www.paypal.com/donate/?hosted_button_id=8LDF4NNQF8Z3L'
|
||||
@@ -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)
|
||||
-435
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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/
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
[submodule "zint"]
|
||||
path = zint
|
||||
url = https://github.com/zint/zint.git
|
||||
shallow = true
|
||||
@@ -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
|
||||
)
|
||||
@@ -1,154 +0,0 @@
|
||||
[](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: |
|
||||
|:-:|:-|
|
||||
| [](https://www.alliedvision.com/) | [Allied Vision Konstanz GmbH](https://www.alliedvision.com/) |
|
||||
| [](https://github.com/jknlsn) | [Jake Nelson](https://github.com/jknlsn) |
|
||||
| [](https://github.com/kurzdigital) | [KURZ Digital Solutions GmbH & Co. KG](https://github.com/kurzdigital) |
|
||||
| [](https://github.com/moonshine-ai) | [Moonshine AI](https://github.com/moonshine-ai) |
|
||||
| [](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) |
|
||||
| [](https://github.com/somcosoftware) | [Somco Software](https://somcosoftware.com/en) |
|
||||
| [](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 <iostream>
|
||||
|
||||
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 <iostream>
|
||||
|
||||
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).]
|
||||
@@ -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
|
||||
$<$<BOOL:${ZXING_UNIT_TESTS}>:-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
|
||||
$<$<BOOL:${ZXING_C_API}>:src/ZXingC.h>
|
||||
$<$<BOOL:${ZXING_C_API}>: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
|
||||
$<$<BOOL:${ZXING_C_API}>:${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 "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>"
|
||||
INTERFACE "$<INSTALL_INTERFACE:include>"
|
||||
)
|
||||
|
||||
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 "$<$<NOT:$<BOOL:${ZXING_READERS}>>:-DZUECI_EMBED_NO_TO_UTF> $<$<NOT:$<BOOL:${ZXING_WRITERS_OLD}>>:-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 "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/libzint>")
|
||||
else()
|
||||
include(../zxing.cmake)
|
||||
zxing_add_package(zint zint https://github.com/zint/zint.git 2.16.0)
|
||||
target_link_libraries (ZXing PRIVATE $<IF:$<BOOL:${ZINT_STATIC}>,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
|
||||
"$<$<BOOL:${ZXING_C_API}>:${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}
|
||||
)
|
||||
@@ -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@"
|
||||
@@ -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()
|
||||
@@ -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 <cmath>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <numbers>
|
||||
#include <utility>
|
||||
|
||||
#ifdef ZXING_USE_ZINT
|
||||
#include <zint.h>
|
||||
void zint_symbol_deleter::operator()(zint_symbol* p) const noexcept
|
||||
{
|
||||
ZBarcode_Delete(p);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
Barcode::Barcode() : d(std::make_shared<Data>()) {}
|
||||
|
||||
Barcode::Barcode(Data&& data) : d(std::make_shared<Data>(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<uint8_t>& Barcode::bytes() const
|
||||
{
|
||||
return d->content.bytes;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> 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<int>(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<std::string>(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<Barcode> 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<std::string, Barcodes> 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
|
||||
@@ -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 <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<Barcode>;
|
||||
|
||||
/**
|
||||
* @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<Data> 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<uint8_t>& bytes() const;
|
||||
|
||||
/// Returns the raw / standard content following the ECI protocol.
|
||||
std::vector<uint8_t> 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
|
||||
@@ -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 <memory>
|
||||
#include <mutex>
|
||||
#include <numbers>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<zint_symbol, zint_symbol_deleter>;
|
||||
#else
|
||||
struct zint_symbol {};
|
||||
using unique_zint_symbol = std::unique_ptr<zint_symbol>;
|
||||
#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<std::mutex> 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<int>(std::lround(position.orientation() * 180 / std::numbers::pi)); }
|
||||
};
|
||||
|
||||
using BarcodesData = std::vector<BarcodeData>;
|
||||
|
||||
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
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
|
||||
#include "Version.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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<BarcodeFormat> 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<BarcodeFormat> 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 <iterator>
|
||||
#include <stdexcept>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Version.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// 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 <array>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
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 <std::size_t N>
|
||||
using BarcodeFormatArray = std::array<BarcodeFormat, N>;
|
||||
|
||||
constexpr BarcodeFormatArray<2> operator|(BarcodeFormat a, BarcodeFormat b)
|
||||
{
|
||||
return {a, b};
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
constexpr BarcodeFormatArray<N + 1> operator|(BarcodeFormatArray<N> bts, BarcodeFormat bt)
|
||||
{
|
||||
BarcodeFormatArray<N + 1> ret{};
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
ret[i] = bts[i];
|
||||
ret[N] = bt;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
constexpr bool operator&(BarcodeFormat lhs, const BarcodeFormatArray<N>& 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<BarcodeFormat> 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 <std::size_t N>
|
||||
BarcodeFormats(BarcodeFormatArray<N> formats) : formats_{formats.begin(), formats.end()}
|
||||
{
|
||||
normalize();
|
||||
}
|
||||
|
||||
BarcodeFormats(std::vector<BarcodeFormat>&& 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<BarcodeFormat>::value_type;
|
||||
using pointer = typename std::vector<BarcodeFormat>::pointer;
|
||||
auto data() const noexcept { return formats_.data(); }
|
||||
|
||||
bool empty() const noexcept { return formats_.empty(); }
|
||||
int size() const noexcept { return static_cast<int>(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
|
||||
@@ -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<const BitMatrix> 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<BitMatrix>(_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<BitMatrix*>(_cache->matrix.get())->flipAll();
|
||||
|
||||
if (_cache->transposed)
|
||||
const_cast<BitMatrix*>(_cache->transposed.get())->flipAll();
|
||||
|
||||
_inverted = !_inverted;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
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<BitMatrix*>(_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
|
||||
@@ -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 <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class BitMatrix;
|
||||
|
||||
using PatternRow = std::vector<uint16_t>;
|
||||
|
||||
/**
|
||||
* 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> _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<const BitMatrix> 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
|
||||
@@ -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 <cstddef>
|
||||
#include <stdexcept>
|
||||
|
||||
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
|
||||
@@ -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 <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class ByteArray;
|
||||
|
||||
/**
|
||||
* A simple, fast array of bits.
|
||||
*/
|
||||
class BitArray
|
||||
{
|
||||
std::vector<uint8_t> _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<uint8_t>::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<int>(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<Iterator>;
|
||||
Range range() const { return {begin(), end()}; }
|
||||
|
||||
friend bool operator==(const BitArray& a, const BitArray& b) { return a._bits == b._bits; }
|
||||
};
|
||||
|
||||
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
constexpr T& AppendBit(T& val, bool bit)
|
||||
{
|
||||
return (val <<= 1) |= static_cast<T>(bit);
|
||||
}
|
||||
|
||||
template <typename ARRAY, typename = std::enable_if_t<std::is_integral_v<typename ARRAY::value_type>>>
|
||||
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 <typename T = int, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
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 <typename T = int, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
std::vector<T> ToInts(const BitArray& bits, int wordSize, int totalWords, int offset = 0)
|
||||
{
|
||||
assert(totalWords >= bits.size() / wordSize);
|
||||
assert(wordSize <= 8 * (int)sizeof(T));
|
||||
|
||||
std::vector<T> 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<int>(bits.end() - cur);
|
||||
}
|
||||
|
||||
explicit operator bool() const { return size(); }
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -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 <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
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<uint16_t>& 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
|
||||
@@ -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 <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
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<data_t> _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<data_t&>(static_cast<const BitMatrix*>(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<data_t*> row(int y) { return {_bits.data() + y * _width, _bits.data() + (y + 1) * _width}; }
|
||||
Range<const data_t*> row(int y) const { return {_bits.data() + y * _width, _bits.data() + (y + 1) * _width}; }
|
||||
|
||||
Range<StrideIter<const data_t*>> 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; }
|
||||
|
||||
/**
|
||||
* <p>Flips the given bit.</p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets a square region of the bit matrix to true.</p>
|
||||
*
|
||||
* @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 <typename T>
|
||||
bool isIn(PointT<T> 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<uint16_t>& 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<typename T>
|
||||
BitMatrix ToBitMatrix(const Matrix<T>& 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<typename T>
|
||||
Matrix<T> ToMatrix(const BitMatrix& in, T black = 0, T white = ~0)
|
||||
{
|
||||
Matrix<T> 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
|
||||
@@ -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 <climits>
|
||||
|
||||
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<T>. 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<typename POINT>
|
||||
class BitMatrixCursor
|
||||
{
|
||||
using this_t = BitMatrixCursor<POINT>;
|
||||
|
||||
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 <typename T>
|
||||
Value testAt(PointT<T> 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<int>(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<typename ARRAY>
|
||||
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<typename ARRAY>
|
||||
ARRAY readPatternFromBlack(int maxWhitePrefix, int range = 0)
|
||||
{
|
||||
if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix))
|
||||
return {};
|
||||
return readPattern<ARRAY>(range);
|
||||
}
|
||||
};
|
||||
|
||||
using BitMatrixCursorF = BitMatrixCursor<PointF>;
|
||||
using BitMatrixCursorI = BitMatrixCursor<PointI>;
|
||||
|
||||
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<PointI> 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
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2017 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BitMatrixIO.h"
|
||||
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
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"(<?xml version="1.0" encoding="UTF-8"?>)" << "\n"
|
||||
<< R"(<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 )" << width << ' ' << height << R"(" stroke="none">)"
|
||||
<< "\n<path d=\"";
|
||||
|
||||
for (int y = 0; y < height; ++y)
|
||||
for (int x = 0; x < width; ++x)
|
||||
if (matrix.get(x, y))
|
||||
out << "M" << x << "," << y << "h1v1h-1z";
|
||||
|
||||
out << "\"/>\n</svg>";
|
||||
|
||||
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<int>(str.length() / (lineLength + 1));
|
||||
int width = narrow_cast<int>(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<uint8_t>(Inflate(matrix.copy(), 0, 0, quietZone));
|
||||
std::ofstream file(filename);
|
||||
file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n";
|
||||
file.write(reinterpret_cast<const char*>(out.data()), out.size());
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2017 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#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
|
||||
@@ -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 <stdexcept>
|
||||
|
||||
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
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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
|
||||
@@ -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 <cstdint>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
ByteArray is an extension of std::vector<uint8_t>.
|
||||
*/
|
||||
class ByteArray : public std::vector<uint8_t>
|
||||
{
|
||||
public:
|
||||
ByteArray() = default;
|
||||
ByteArray(std::initializer_list<uint8_t> list) : std::vector<uint8_t>(list) {}
|
||||
explicit ByteArray(int len) : std::vector<uint8_t>(len, 0) {}
|
||||
explicit ByteArray(std::string_view str) : std::vector<uint8_t>(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<const char*>(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
|
||||
@@ -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 <algorithm>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
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
|
||||
@@ -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<PointF> 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<PointF> 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<PointF> 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<PointF> 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<PointF> 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<PointF> 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<QuadrilateralF> FitQadrilateralToPoints(PointF center, std::vector<PointF>& 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<const PointF*, 4> 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<const PointF*, 4> beg = {corners[0] + 1, corners[1] + 1, corners[2] + 1, corners[3] + 1};
|
||||
std::array<const PointF*, 4> 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<QuadrilateralF> 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<QuadrilateralF> 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<PointF> 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
|
||||
@@ -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 <optional>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
template <typename T, size_t N>
|
||||
static float CenterFromEnd(const std::array<T, N>& 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<int N, typename Cursor>
|
||||
std::optional<Pattern<N>> ReadSymmetricPattern(Cursor& cur, int range)
|
||||
{
|
||||
static_assert(N % 2 == 1);
|
||||
assert(range > 0);
|
||||
Pattern<N> 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<bool RELAXED_THRESHOLD = false, typename PATTERN>
|
||||
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<pattern.size()> 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<RELAXED_THRESHOLD>(res, pattern))
|
||||
return 0;
|
||||
|
||||
if (updatePosition)
|
||||
cur.step(res[s_2] / 2 - (centerBwd - 1));
|
||||
|
||||
return Reduce(res);
|
||||
}
|
||||
|
||||
std::optional<PointF> CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle = true);
|
||||
|
||||
std::optional<PointF> FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize);
|
||||
|
||||
std::optional<QuadrilateralF> FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup);
|
||||
|
||||
std::optional<QuadrilateralF> FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int ringIndex);
|
||||
|
||||
struct ConcentricPattern : public PointF
|
||||
{
|
||||
int size = 0;
|
||||
};
|
||||
|
||||
template <bool E2E = false, typename PATTERN>
|
||||
std::optional<ConcentricPattern> 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<E2E>(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<true>(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
|
||||
|
||||
@@ -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 <cassert>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string ToString(ContentType type)
|
||||
{
|
||||
const char* t2s[] = {"Text", "Binary", "Mixed", "GS1", "ISO15434", "UnknownECI"};
|
||||
int idx = static_cast<int>(type);
|
||||
if (idx < 0 || idx >= Size(t2s))
|
||||
return "InvalidContentType";
|
||||
return t2s[idx];
|
||||
}
|
||||
|
||||
template <typename FUNC>
|
||||
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<std::string>) * 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<bool> 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
|
||||
@@ -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 <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class Content
|
||||
{
|
||||
template <typename FUNC>
|
||||
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<Encoding> encodings;
|
||||
#if !defined(ZXING_READERS) && defined(ZXING_USE_ZINT)
|
||||
std::vector<std::string> 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<uint8_t>(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
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
||||
@@ -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 <zint.h>
|
||||
#else
|
||||
#include "MultiFormatWriter.h"
|
||||
#endif // ZXING_USE_ZINT
|
||||
|
||||
#include <charconv>
|
||||
#include <optional>
|
||||
|
||||
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<TYPE> CreatorOptions::NAME() const noexcept { return JsonGet<TYPE>(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<Data>(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<int>& 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<const char*>(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<int>(ToECI(cs));
|
||||
} else if (std::all_of(eci->begin(), eci->end(), IsDigit<char>)) {
|
||||
zint->eci = std::stoi(*eci);
|
||||
}
|
||||
} else if (mode == DATA_MODE) {
|
||||
zint->eci = static_cast<int>(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_t>(size)}))
|
||||
// zint->eci = static_cast<int>(ECI::UTF8);
|
||||
}
|
||||
|
||||
if (opts.format() == BarcodeFormat::Telepen && (std::all_of((const char*)data, (const char*)data + size, IsDigit<char>)))
|
||||
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<uint8_t>(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<size_t>(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<const char *>(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<PointI>(left, top, width, height)}, opts.format());
|
||||
#endif
|
||||
|
||||
res.zint = std::move(opts.d->zint);
|
||||
res.zintMutex = std::make_unique<std::mutex>();
|
||||
|
||||
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<PointI>(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<const char*>(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
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2024 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Barcode.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
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<Data> 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<TYPE> 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 <typename R>
|
||||
requires std::ranges::contiguous_range<R> && std::ranges::sized_range<R> && (sizeof(std::ranges::range_value_t<R>) == 1)
|
||||
Barcode CreateBarcodeFromBytes(const R& contents, const CreatorOptions& options)
|
||||
{
|
||||
return CreateBarcodeFromBytes(std::ranges::data(contents), std::ranges::size(contents), options);
|
||||
}
|
||||
#else
|
||||
template <typename R>
|
||||
Barcode CreateBarcodeFromBytes(const R& contents, const CreatorOptions& options)
|
||||
{
|
||||
return CreateBarcodeFromBytes(std::data(contents), std::size(contents), options);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // ZXing
|
||||
@@ -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 <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
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> _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>, customData, setCustomData)
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
template<typename T>
|
||||
DecoderResult&& addExtra(std::string_view key, T val, T ignore = {}) { _json += JsonProp(key, val, ignore); return std::move(*this); }
|
||||
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -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 <utility>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ECI.h"
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
static const std::map<ECI, CharacterSet> 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
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CharacterSet.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
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<int>(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
|
||||
@@ -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<int>(e.type())];
|
||||
if (!e.msg().empty())
|
||||
ret += " (" + e.msg() + ")";
|
||||
if (auto location = e.location(); !location.empty())
|
||||
ret += " @ " + e.location();
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <version>
|
||||
#ifdef __cpp_lib_bitops
|
||||
#include <bit>
|
||||
#else
|
||||
namespace std {
|
||||
template <class T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
|
||||
constexpr int popcount(T x) noexcept
|
||||
{
|
||||
int n = 0;
|
||||
for (; x; ++n)
|
||||
x &= x - 1; // clear lowest bit
|
||||
return n;
|
||||
}
|
||||
|
||||
template <class T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
|
||||
constexpr int countr_zero(T x) noexcept
|
||||
{
|
||||
if (x == 0)
|
||||
return std::numeric_limits<T>::digits;
|
||||
|
||||
int n = 0;
|
||||
while ((x & T(1)) == 0) {
|
||||
x >>= 1;
|
||||
++n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
template <class T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
|
||||
constexpr int countl_zero(T x) noexcept
|
||||
{
|
||||
if (x == 0)
|
||||
return std::numeric_limits<T>::digits;
|
||||
|
||||
int n = 0;
|
||||
for (int i = std::numeric_limits<T>::digits; i-- && !(x & (T(1) << i)); ++n)
|
||||
;
|
||||
return n;
|
||||
}
|
||||
} // namespace std
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
template<typename Enum>
|
||||
class Flags
|
||||
{
|
||||
static_assert(std::is_enum<Enum>::value, "Flags is only usable on enumeration types.");
|
||||
|
||||
using Int = std::make_unsigned_t<typename std::underlying_type<Enum>::type>;
|
||||
Int i = 0;
|
||||
|
||||
constexpr inline Flags(Int other) : i(other) {}
|
||||
|
||||
constexpr static inline auto highestBitSet(Int x) noexcept
|
||||
{
|
||||
return std::numeric_limits<Int>::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<Enum> 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<Enum>::const_iterator it,
|
||||
// typename std::initializer_list<Enum>::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<ENUM>; \
|
||||
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
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "GTIN.h"
|
||||
|
||||
#include <algorithm>
|
||||
#ifdef __cpp_lib_format // not available on gcc 12
|
||||
#include <format>
|
||||
#endif
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
|
||||
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<int>(GTIN.substr(first, 7 - implicitZero));
|
||||
if (prefix >= 0 && prefix <= 99)
|
||||
return {};
|
||||
|
||||
// 00001-00009 US
|
||||
prefix = FromString<int>(GTIN.substr(first, 5 - implicitZero));
|
||||
if (prefix >= 1 && prefix <= 9)
|
||||
return "US";
|
||||
|
||||
// 0001-0009 US
|
||||
prefix = FromString<int>(GTIN.substr(first, 4 - implicitZero));
|
||||
if (prefix >= 1 && prefix <= 9)
|
||||
return "US";
|
||||
}
|
||||
|
||||
const int prefix = FromString<int>(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<uint16_t>(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
|
||||
@@ -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 <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ZXing::GTIN {
|
||||
|
||||
#ifdef ZXING_INTERNAL
|
||||
template <typename T>
|
||||
T ComputeCheckDigit(const std::basic_string<T>& 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<T>((10 - (sum % 10)) % 10);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsCheckDigitValid(const std::basic_string<T>& s)
|
||||
{
|
||||
return ComputeCheckDigit(s, true) == s.back();
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Evaluate the prefix of the GTIN to estimate the country of origin. See
|
||||
* <a href="https://www.gs1.org/standards/id-keys/company-prefix">
|
||||
* https://www.gs1.org/standards/id-keys/company-prefix</a> and
|
||||
* <a href="https://en.wikipedia.org/wiki/List_of_GS1_country_codes">
|
||||
* https://en.wikipedia.org/wiki/List_of_GS1_country_codes</a>.
|
||||
*
|
||||
* `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
|
||||
@@ -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 <algorithm>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
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<uint16_t, LUMINANCE_BUCKETS>;
|
||||
|
||||
GlobalHistogramBinarizer::GlobalHistogramBinarizer(const ImageView& buffer) : BinaryBitmap(buffer) {}
|
||||
|
||||
GlobalHistogramBinarizer::~GlobalHistogramBinarizer() = default;
|
||||
|
||||
using ImageLineView = Range<StrideIter<const uint8_t*>>;
|
||||
|
||||
inline ImageLineView RowView(const ImageView& iv, int row)
|
||||
{
|
||||
return {{iv.data(0, row), iv.pixStride()}, {iv.data(iv.width(), row), iv.pixStride()}};
|
||||
}
|
||||
|
||||
template <typename I>
|
||||
static void ThresholdSharpened(Range<I> in, int threshold, std::vector<uint8_t>& 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 <typename I>
|
||||
static auto GenHistogram(Range<I> 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<int>(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<uint8_t> 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<const uint8_t*>(nullptr, nullptr);
|
||||
auto histogram = packedLineView ? GenHistogram(packedLineView) : GenHistogram(lineView);
|
||||
auto threshold = EstimateBlackPoint(histogram) - 1;
|
||||
if (threshold <= 0)
|
||||
return false;
|
||||
|
||||
ZX_THREAD_LOCAL std::vector<uint8_t> 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<const BitMatrix>
|
||||
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<const BitMatrix>(binarize(blackPoint));
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -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<const BitMatrix> getBlackMatrix() const override;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -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<std::optional<PointF>>&& apP, const std::vector<int>& apMX, const std::vector<int>& 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
|
||||
@@ -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 <optional>
|
||||
#include <vector>
|
||||
|
||||
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<ROI>;
|
||||
|
||||
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<std::optional<PointF>>&& apP, const std::vector<int>& apMX, const std::vector<int>& apMY);
|
||||
|
||||
} // ZXing
|
||||
@@ -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 <cmath>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
|
||||
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<int>(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<int>(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<int>(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
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string HRIFromGS1(std::string_view gs1);
|
||||
std::string HRIFromISO15434(std::string_view str);
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -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 <algorithm>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#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<T_t> CalculateBlackPoints(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height,
|
||||
int rowStride)
|
||||
{
|
||||
Matrix<T_t> 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<BitMatrix> CalculateMatrix(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width,
|
||||
int height, int rowStride, const Matrix<T_t>& blackPoints)
|
||||
{
|
||||
auto matrix = std::make_shared<BitMatrix>(width, height);
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
Matrix<uint8_t> out(width, height);
|
||||
Matrix<uint8_t> 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<const char*>(out.data()), out.size());
|
||||
std::ofstream file2("thresholds_avg.pnm");
|
||||
file2 << "P5\n" << out.width() << ' ' << out.height() << "\n255\n";
|
||||
file2.write(reinterpret_cast<const char*>(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<T_t> 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<T_t> 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<T_t> SmoothThresholds(Matrix<T_t>&& in)
|
||||
{
|
||||
Matrix<T_t> 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<BitMatrix> ThresholdImage(const ImageView iv, const Matrix<T_t>& thresholds)
|
||||
{
|
||||
auto matrix = std::make_shared<BitMatrix>(iv.width(), iv.height());
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
Matrix<uint8_t> 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<const char*>(out.data()), out.size());
|
||||
#endif
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::shared_ptr<const BitMatrix> 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
|
||||
@@ -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<const BitMatrix> getBlackMatrix() const override;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2019 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
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<uint32_t>(format) >> 3*8) & 0xFF; }
|
||||
constexpr inline int RedIndex(ImageFormat format) { return (static_cast<uint32_t>(format) >> 2*8) & 0xFF; }
|
||||
constexpr inline int GreenIndex(ImageFormat format) { return (static_cast<uint32_t>(format) >> 1*8) & 0xFF; }
|
||||
constexpr inline int BlueIndex(ImageFormat format) { return (static_cast<uint32_t>(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<uint8_t>((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<uint8_t[]> _memory;
|
||||
Image(std::unique_ptr<uint8_t[]>&& 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<uint8_t[]>(w * h * PixStride(f)), w, h, f) {}
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2025 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "JSON.h"
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
// 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<PATTERN>(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<char>(code);
|
||||
i += 4;
|
||||
break;
|
||||
}
|
||||
default: throw std::runtime_error("Unknown escape sequence");
|
||||
}
|
||||
} else {
|
||||
res += c;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2025 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <cstring>
|
||||
#ifdef __cpp_lib_format // not available on gcc 12
|
||||
#include <format>
|
||||
#endif
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string JsonEscapeStr(std::string_view str);
|
||||
std::string JsonUnEscapeStr(std::string_view str);
|
||||
|
||||
template<typename T>
|
||||
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<T, bool>)
|
||||
return val != ignore ? ZX_JSON_KEY_VAL(val ? "true" : "false") : "";
|
||||
else if constexpr (std::is_floating_point_v<T>)
|
||||
return val != ignore ? ZX_JSON_KEY_VAL(ZX_JSON_FMT_FLOAT(val)) : "";
|
||||
else if constexpr (std::is_integral_v<T>)
|
||||
return val != ignore ? ZX_JSON_KEY_VAL(std::to_string(val)) : "";
|
||||
else if constexpr (std::is_convertible_v<T, std::string_view>)
|
||||
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 <typename T>
|
||||
inline std::optional<T> 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<bool, T>)
|
||||
return str.empty() || Contains("1tT", str.front()) ? std::optional(true) : std::nullopt;
|
||||
else if constexpr (std::is_arithmetic_v<T>)
|
||||
return str.empty() ? std::nullopt : std::optional(FromString<T>(str));
|
||||
else if constexpr (std::is_same_v<std::string, T>)
|
||||
return JsonUnEscapeStr(str);
|
||||
else
|
||||
static_assert("unsupported JSON value type");
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -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 <algorithm>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#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<const PointF> 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;
|
||||
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<double> 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<PointF> 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<PointF> 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
|
||||
@@ -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 <optional>
|
||||
#include <span>
|
||||
|
||||
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<const PointF> 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<PointF> findTimingPatternCross(bool isBlack, int radius);
|
||||
|
||||
using Directions = std::span<const PointI>;
|
||||
|
||||
/**
|
||||
* @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
|
||||
@@ -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 <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
|
||||
class LogMatrix
|
||||
{
|
||||
using LogBuffer = Matrix<uint8_t>;
|
||||
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 <typename T>
|
||||
void operator()(const PointT<T>& p, int color = 1)
|
||||
{
|
||||
if (_image && _image->isIn(p))
|
||||
_log.set(static_cast<int>(p.x * _scale), static_cast<int>(p.y * _scale), color);
|
||||
}
|
||||
|
||||
void operator()(const PointT<int>& p, int color)
|
||||
{
|
||||
operator()(centered(p), color);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void operator()(const std::vector<PointT<T>>& 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<typename T> void log(PointT<T>, int = 0) {}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -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 <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
template <class T>
|
||||
class Matrix
|
||||
{
|
||||
public:
|
||||
using value_t = T;
|
||||
|
||||
private:
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
std::vector<value_t> _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
|
||||
@@ -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 <memory>
|
||||
|
||||
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
|
||||
@@ -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 <vector>
|
||||
#include <memory>
|
||||
|
||||
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<std::unique_ptr<Reader>> _readers;
|
||||
const ReaderOptions& _opts;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -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 <stdexcept>
|
||||
|
||||
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<QRCode::ErrorCorrectionLevel>(--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<decltype(writer)>(writer));
|
||||
};
|
||||
|
||||
[[maybe_unused]] auto exec2 = [&](auto&& writer) {
|
||||
if (_encoding != CharacterSet::Unknown)
|
||||
writer.setEncoding(_encoding);
|
||||
return exec0(std::forward<decltype(writer)>(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
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
#include "CharacterSet.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Range.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
using PatternType = uint16_t;
|
||||
template<int N> using Pattern = std::array<PatternType, N>;
|
||||
using PatternRow = std::vector<PatternType>;
|
||||
|
||||
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 <size_t N>
|
||||
PatternView(const Pattern<N>& 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<int>(_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 acceptIfAtFirstBar = false>
|
||||
bool hasQuietZoneBefore(float scale) const
|
||||
{
|
||||
return (acceptIfAtFirstBar && isAtFirstBar()) || _data[-1] >= sum() * scale;
|
||||
}
|
||||
|
||||
template<bool acceptIfAtLastBar = true>
|
||||
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<int>(_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 <typename T>
|
||||
struct BarAndSpace
|
||||
{
|
||||
using value_type = T;
|
||||
T bar = {}, space = {};
|
||||
// even index -> bar, odd index -> space
|
||||
constexpr T& operator[](int i) noexcept { return reinterpret_cast<T*>(this)[i & 1]; }
|
||||
constexpr T operator[](int i) const noexcept { return reinterpret_cast<const T*>(this)[i & 1]; }
|
||||
bool isValid() const { return bar != T{} && space != T{}; }
|
||||
};
|
||||
|
||||
using BarAndSpaceI = BarAndSpace<PatternType>;
|
||||
|
||||
template <int LEN, typename RT, typename T>
|
||||
constexpr auto BarAndSpaceSum(const T* view) noexcept
|
||||
{
|
||||
BarAndSpace<RT> 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 <int N, int SUM, bool IS_SPARSE = false>
|
||||
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<value_type> sums() const noexcept { return BarAndSpaceSum<N, value_type>(_data); }
|
||||
};
|
||||
|
||||
template <int N, int SUM>
|
||||
using FixedSparsePattern = FixedPattern<N, SUM, true>;
|
||||
|
||||
template <bool E2E = false, int LEN, int SUM>
|
||||
double IsPattern(const PatternView& view, const FixedPattern<LEN, SUM, false>& pattern, int spaceInPixel = 0,
|
||||
double minQuietZone = 0, double moduleSizeRef = 0)
|
||||
{
|
||||
if constexpr (E2E) {
|
||||
auto widths = BarAndSpaceSum<LEN, double>(view.data());
|
||||
auto sums = pattern.sums();
|
||||
BarAndSpace<double> 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<double> 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 <bool RELAXED_THRESHOLD = false, int N, int SUM>
|
||||
double IsPattern(const PatternView& view, const FixedPattern<N, SUM, true>& 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 E2E = false, int N, int SUM, bool IS_SPARSE>
|
||||
bool IsRightGuard(const PatternView& view, const FixedPattern<N, SUM, IS_SPARSE>& pattern, double minQuietZone,
|
||||
double moduleSizeRef = 0)
|
||||
{
|
||||
assert(view.size() == pattern.size());
|
||||
if (!view.isValid())
|
||||
return false;
|
||||
int spaceInPixel = view.isAtLastBar() ? std::numeric_limits<int>::max() : *view.end();
|
||||
return IsPattern<E2E>(view, pattern, spaceInPixel, minQuietZone, moduleSizeRef) != 0;
|
||||
}
|
||||
|
||||
template<int LEN, typename Pred>
|
||||
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<int>::max()))
|
||||
return window;
|
||||
for (auto end = view.end() - minSize; window.data() < end; window.skipPair())
|
||||
if (isGuard(window, window[-1]))
|
||||
return window;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template <bool E2E = false, int LEN, int SUM, bool IS_SPARSE>
|
||||
PatternView FindLeftGuard(const PatternView& view, int minSize, const FixedPattern<LEN, SUM, IS_SPARSE>& pattern,
|
||||
double minQuietZone)
|
||||
{
|
||||
return FindLeftGuard<LEN>(view, std::max(minSize, LEN),
|
||||
[&pattern, minQuietZone](const PatternView& window, int spaceInPixel) {
|
||||
return IsPattern<E2E>(window, pattern, spaceInPixel, minQuietZone);
|
||||
});
|
||||
}
|
||||
|
||||
template <int LEN>
|
||||
std::array<int, LEN - 2> NormalizedE2EPattern(const PatternView& view, int mods, bool reverse = false)
|
||||
{
|
||||
double moduleSize = static_cast<double>(view.sum(LEN)) / mods;
|
||||
std::array<int, LEN - 2> 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 <int LEN, int SUM>
|
||||
std::array<int, LEN> NormalizedPattern(const PatternView& view)
|
||||
{
|
||||
double moduleSize = static_cast<double>(view.sum(LEN)) / SUM;
|
||||
#if 1
|
||||
int err = SUM;
|
||||
std::array<int, LEN> is;
|
||||
std::array<double, LEN> 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<int, LEN> 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<typename I>
|
||||
void GetPatternRow(Range<I> 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<I> && sizeof(std::remove_cv_t<std::iter_value_t<I>>) == 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<simd_t>(bitPtr);
|
||||
auto asSimd1 = LoadU<simd_t>(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
|
||||
@@ -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 <array>
|
||||
|
||||
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<const std::array<PointF::value_t, 8>&>(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
|
||||
@@ -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
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
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 <typename T>
|
||||
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 <typename U>
|
||||
constexpr explicit PointT(const PointT<U>& p) : x(static_cast<T>(p.x)), y(static_cast<T>(p.y))
|
||||
{}
|
||||
|
||||
template <typename U>
|
||||
PointT& operator+=(const PointT<U>& b)
|
||||
{
|
||||
x += b.x;
|
||||
y += b.y;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
using PointI = PointT<int>;
|
||||
using PointF = PointT<double>;
|
||||
|
||||
/// @cond INTERNAL
|
||||
|
||||
template <typename T>
|
||||
bool operator==(const PointT<T>& a, const PointT<T>& b)
|
||||
{
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator!=(const PointT<T>& a, const PointT<T>& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto operator-(const PointT<T>& a) -> PointT<T>
|
||||
{
|
||||
return {-a.x, -a.y};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
auto operator+(const PointT<T>& a, const PointT<U>& b) -> PointT<decltype(a.x + b.x)>
|
||||
{
|
||||
return {a.x + b.x, a.y + b.y};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
auto operator-(const PointT<T>& a, const PointT<U>& b) -> PointT<decltype(a.x - b.x)>
|
||||
{
|
||||
return {a.x - b.x, a.y - b.y};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
auto operator*(const PointT<T>& a, const PointT<U>& b) -> PointT<decltype(a.x * b.x)>
|
||||
{
|
||||
return {a.x * b.x, a.y * b.y};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
PointT<T> operator*(U s, const PointT<T>& a)
|
||||
{
|
||||
return {s * a.x, s * a.y};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
PointT<T> operator/(const PointT<T>& a, U d)
|
||||
{
|
||||
return {a.x / d, a.y / d};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
auto dot(const PointT<T>& a, const PointT<U>& b) -> decltype (a.x * b.x)
|
||||
{
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto cross(PointT<T> a, PointT<T> b) -> decltype(a.x * b.x)
|
||||
{
|
||||
return a.x * b.y - b.x * a.y;
|
||||
}
|
||||
|
||||
/// L1 norm
|
||||
template <typename T>
|
||||
T sumAbsComponent(PointT<T> p)
|
||||
{
|
||||
return std::abs(p.x) + std::abs(p.y);
|
||||
}
|
||||
|
||||
/// L2 norm
|
||||
template <typename T>
|
||||
auto length(PointT<T> p) -> decltype(std::sqrt(dot(p, p)))
|
||||
{
|
||||
return std::sqrt(dot(p, p));
|
||||
}
|
||||
|
||||
/// L-inf norm
|
||||
template <typename T>
|
||||
T maxAbsComponent(PointT<T> p)
|
||||
{
|
||||
return std::max(std::abs(p.x), std::abs(p.y));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto distance(PointT<T> a, PointT<T> b) -> decltype(length(a - b))
|
||||
{
|
||||
return length(a - b);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
PointF normalized(PointT<T> d)
|
||||
{
|
||||
return PointF(d) / length(PointF(d));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
PointT<T> bresenhamDirection(PointT<T> d)
|
||||
{
|
||||
return d / maxAbsComponent(d);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
PointT<T> mainDirection(PointT<T> d)
|
||||
{
|
||||
return std::abs(d.x) > std::abs(d.y) ? PointT<T>(d.x, 0) : PointT<T>(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 <typename T>
|
||||
std::string ToString(const PointT<T>& 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
|
||||
|
||||
@@ -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 <array>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
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 <typename T>
|
||||
class Quadrilateral : public std::array<T, 4>
|
||||
{
|
||||
using Base = std::array<T, 4>;
|
||||
using Base::at;
|
||||
public:
|
||||
using Point = T;
|
||||
|
||||
Quadrilateral() = default;
|
||||
Quadrilateral(T tl, T tr, T br, T bl) : Base{tl, tr, br, bl} {}
|
||||
template <typename U>
|
||||
Quadrilateral(PointT<U> tl, PointT<U> tr, PointT<U> br, PointT<U> 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<PointF>;
|
||||
using QuadrilateralI = Quadrilateral<PointI>;
|
||||
|
||||
template <typename T>
|
||||
std::string ToString(const Quadrilateral<PointT<T>>& 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 <typename PointT = PointF>
|
||||
Quadrilateral<PointT> 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 <typename PointT = PointF>
|
||||
Quadrilateral<PointT> 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 <typename PointT = PointF>
|
||||
Quadrilateral<PointT> 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 <typename PointT = PointF>
|
||||
Quadrilateral<PointT> CenteredSquare(int size)
|
||||
{
|
||||
return Scale(Quadrilateral(PointT{-1, -1}, {1, -1}, {1, 1}, {-1, 1}), size / 2);
|
||||
}
|
||||
|
||||
template <typename PointT = PointI>
|
||||
Quadrilateral<PointT> Line(int y, int xStart, int xStop)
|
||||
{
|
||||
return {PointT{xStart, y}, {xStop, y}, {xStop, y}, {xStart, y}};
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
bool IsConvex(const Quadrilateral<PointT>& 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 <typename PointT>
|
||||
Quadrilateral<PointT> Scale(const Quadrilateral<PointT>& q, int factor)
|
||||
{
|
||||
return {factor * q[0], factor * q[1], factor * q[2], factor * q[3]};
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
Quadrilateral<PointT> Move(const Quadrilateral<PointT>& q, PointT offset)
|
||||
{
|
||||
return {q[0] + offset, q[1] + offset, q[2] + offset, q[3] + offset};
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
PointT Center(const Quadrilateral<PointT>& q)
|
||||
{
|
||||
return Reduce(q) / Size(q);
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
Quadrilateral<PointT> RotatedCorners(const Quadrilateral<PointT>& q, int n = 1, bool mirror = false)
|
||||
{
|
||||
Quadrilateral<PointT> 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 <typename PointT>
|
||||
bool IsInside(const PointT& p, const Quadrilateral<PointT>& 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 <typename PointT>
|
||||
Quadrilateral<PointT> BoundingBox(const Quadrilateral<PointT>& 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 <typename PointT>
|
||||
bool HaveIntersectingBoundingBoxes(const Quadrilateral<PointT>& a, const Quadrilateral<PointT>& 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 <typename PointT>
|
||||
Quadrilateral<PointT> Blend(const Quadrilateral<PointT>& a, const Quadrilateral<PointT>& 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<PointT> res;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
res[i] = (a[i] + b[(i + offset) % 4]) / 2;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // ZXing
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
template <typename Iterator>
|
||||
struct StrideIter
|
||||
{
|
||||
Iterator pos;
|
||||
int stride;
|
||||
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
using difference_type = typename std::iterator_traits<Iterator>::difference_type;
|
||||
using value_type = typename std::iterator_traits<Iterator>::value_type;
|
||||
using pointer = Iterator;
|
||||
using reference = typename std::iterator_traits<Iterator>::reference;
|
||||
|
||||
auto operator*() const { return *pos; }
|
||||
auto operator[](int i) const { return *(pos + i * stride); }
|
||||
StrideIter<Iterator>& operator++() { return pos += stride, *this; }
|
||||
StrideIter<Iterator> operator++(int) { auto temp = *this; ++*this; return temp; }
|
||||
bool operator==(const StrideIter<Iterator>& rhs) const { return pos == rhs.pos; }
|
||||
StrideIter<Iterator> operator+(int i) const { return {pos + i * stride, stride}; }
|
||||
StrideIter<Iterator> operator-(int i) const { return {pos - i * stride, stride}; }
|
||||
int operator-(const StrideIter<Iterator>& rhs) const { return narrow_cast<int>((pos - rhs.pos) / stride); }
|
||||
};
|
||||
|
||||
template <typename Iterator>
|
||||
StrideIter(const Iterator&, int) -> StrideIter<Iterator>;
|
||||
|
||||
|
||||
template <typename Iterator>
|
||||
struct Range
|
||||
{
|
||||
Iterator _begin, _end;
|
||||
|
||||
Range(Iterator b, Iterator e) : _begin(b), _end(e) {}
|
||||
|
||||
template <typename C>
|
||||
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<int>(end() - begin()); }
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
Range(const C&) -> Range<typename C::const_iterator>;
|
||||
|
||||
/**
|
||||
* ArrayView is a lightweight, non-owning, non-mutable view over a contiguous sequence of elements.
|
||||
* Similar to std::span<const T>. See also Range template for general iterator use case.
|
||||
*/
|
||||
template <typename T>
|
||||
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 <typename P, typename U = T,
|
||||
typename = std::enable_if_t<
|
||||
sizeof(U) == 1 && std::is_same_v<void, std::remove_cv_t<std::remove_reference_t<std::remove_pointer_t<P>>>>>>
|
||||
constexpr ArrayView(P data, size_type size) noexcept : _data(reinterpret_cast<pointer>(data)), _size(size)
|
||||
{}
|
||||
|
||||
template <typename Container,
|
||||
typename = std::enable_if_t<std::is_convertible_v<decltype(std::data(std::declval<Container&>())), 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<T> 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<uint8_t>;
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -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 <utility>
|
||||
|
||||
|
||||
#ifdef ZXING_READERS
|
||||
#include "GlobalHistogramBinarizer.h"
|
||||
#include "HybridBinarizer.h"
|
||||
#include "MultiFormatReader.h"
|
||||
#include "Pattern.h"
|
||||
#include "ThresholdBinarizer.h"
|
||||
#endif
|
||||
|
||||
#include <climits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
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<Data>()) {}
|
||||
ReaderOptions::~ReaderOptions() = default;
|
||||
|
||||
// copy
|
||||
ReaderOptions::ReaderOptions(const ReaderOptions& other) : d(std::make_unique<Data>(*other.d)) {}
|
||||
ReaderOptions& ReaderOptions::operator=(const ReaderOptions& other)
|
||||
{
|
||||
if (this != &other)
|
||||
d = std::make_unique<Data>(*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<uint8_t*>(Image::data()); }
|
||||
};
|
||||
|
||||
template<typename P>
|
||||
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<LumImage> buffers;
|
||||
|
||||
template<int N>
|
||||
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<ImageView> 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<BinaryBitmap> CreateBitmap(ZXing::Binarizer binarizer, const ImageView& iv)
|
||||
{
|
||||
switch (binarizer) {
|
||||
case Binarizer::BoolCast: return std::make_unique<ThresholdBinarizer>(iv, 0);
|
||||
case Binarizer::FixedThreshold: return std::make_unique<ThresholdBinarizer>(iv, 127);
|
||||
case Binarizer::GlobalHistogram: return std::make_unique<GlobalHistogramBinarizer>(iv);
|
||||
case Binarizer::LocalAverage: return std::make_unique<HybridBinarizer>(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<MultiFormatReader> 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<MultiFormatReader>(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<int>(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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 <string_view>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
|
||||
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 "<GS>")
|
||||
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<Data> 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
|
||||
@@ -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 <algorithm>
|
||||
#include <type_traits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
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<double> UEC(std::optional<int> usedECC, int numECC)
|
||||
{
|
||||
return usedECC ? std::optional(std::clamp(1.0 - static_cast<double>(*usedECC) / numECC, 0.0, 1.0)) : std::nullopt;
|
||||
}
|
||||
|
||||
#ifdef ZXING_READERS
|
||||
std::optional<double> ReedSolomonDecode(RSField field, std::span<uint8_t> codeword, int numECC, std::span<const int> erasures)
|
||||
{
|
||||
return UEC(rs::decode(GetGF2n(field), codeword, numECC, erasures), numECC);
|
||||
}
|
||||
|
||||
std::optional<double> ReedSolomonDecode(RSField field, std::span<int> codeword, int numECC, std::span<const int> 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<const uint8_t> data, std::span<uint8_t> parity)
|
||||
{
|
||||
rs::encode(GetGF2n(field), data, parity);
|
||||
}
|
||||
|
||||
void ReedSolomonEncode(RSField field, std::span<uint8_t> codeword, int numECC)
|
||||
{
|
||||
rs::encode_inplace(GetGF2n(field), codeword, numECC);
|
||||
}
|
||||
|
||||
void ReedSolomonEncode(RSField field, std::span<int> 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
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2026 Axel Waggershauser
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
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<double> ReedSolomonDecode(RSField field, std::span<int> codeword, int numECC, std::span<const int> erasures = {});
|
||||
|
||||
std::optional<double> ReedSolomonDecode(RSField field, std::span<uint8_t> codeword, int numECC,
|
||||
std::span<const int> 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<const uint8_t> data, std::span<uint8_t> parity);
|
||||
|
||||
/// @brief ReedSolomonEncode replaces the last numECC symbols in codeword with parity symbols
|
||||
void ReedSolomonEncode(RSField field, std::span<uint8_t> codeword, int numECC);
|
||||
void ReedSolomonEncode(RSField field, std::span<int> codeword, int numECC);
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Point.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
#include <cstdio>
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class RegressionLine
|
||||
{
|
||||
protected:
|
||||
std::vector<PointF> _points;
|
||||
PointF _directionInward;
|
||||
PointF::value_t a = NAN, b = NAN, c = NAN;
|
||||
|
||||
friend PointF intersect(const RegressionLine& l1, const RegressionLine& l2);
|
||||
|
||||
template<typename T> bool evaluate(const PointT<T>* begin, const PointT<T>* 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 <typename T> bool evaluate(const std::vector<PointT<T>>& points) { return evaluate(&points.front(), &points.back() + 1); }
|
||||
|
||||
template <typename T> static auto distance(PointT<T> a, PointT<T> b) { return ZXing::distance(a, b); }
|
||||
|
||||
public:
|
||||
RegressionLine() { _points.reserve(16); } // arbitrary but plausible start size (tiny performance improvement)
|
||||
|
||||
template<typename T> RegressionLine(PointT<T> a, PointT<T> b)
|
||||
{
|
||||
evaluate(std::vector{a, b});
|
||||
}
|
||||
|
||||
template<typename T> RegressionLine(const PointT<T>* b, const PointT<T>* 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 <typename T> auto distance(PointT<T> 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
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ResultPoint.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
float ResultPoint::Distance(int aX, int aY, int bX, int bY)
|
||||
{
|
||||
auto dx = static_cast<float>(aX - bX);
|
||||
auto dy = static_cast<float>(aY - bY);
|
||||
return std::sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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 <typename T> ResultPoint(PointT<T> p) : PointF(p) {}
|
||||
|
||||
float x() const { return static_cast<float>(PointF::x); }
|
||||
float y() const { return static_cast<float>(PointF::y); }
|
||||
|
||||
void set(float x, float y) { *this = PointF(x, y); }
|
||||
|
||||
static float Distance(int aX, int aY, int bX, int bY);
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <version>
|
||||
|
||||
#if defined(__cpp_lib_generator)
|
||||
|
||||
#include <generator>
|
||||
|
||||
#else
|
||||
|
||||
#include <concepts>
|
||||
#include <coroutine>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
|
||||
namespace std {
|
||||
|
||||
template <std::movable T>
|
||||
class generator
|
||||
{
|
||||
public:
|
||||
struct promise_type
|
||||
{
|
||||
std::optional<T> 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<promise_type>;
|
||||
|
||||
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::integral T>
|
||||
std::generator<T> 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("");
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <version>
|
||||
|
||||
#if defined(__cpp_lib_print) && __cpp_lib_print >= 202207L
|
||||
|
||||
#include <print>
|
||||
|
||||
#else
|
||||
|
||||
#include <format>
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
|
||||
namespace std {
|
||||
|
||||
template <class... Args>
|
||||
inline void print(std::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
auto s = std::format(fmt, std::forward<Args>(args)...);
|
||||
std::fwrite(s.data(), 1, s.size(), stdout);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
inline void println(std::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
std::print(fmt, std::forward<Args>(args)...);
|
||||
std::fputc('\n', stdout);
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
// idea from <experimental/scope> (library fundamentals TS v3)
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace std {
|
||||
|
||||
template <class F>
|
||||
class scope_exit
|
||||
{
|
||||
public:
|
||||
explicit scope_exit(F&& f) noexcept(std::is_nothrow_move_constructible_v<F>) : func_(std::forward<F>(f)), active_(true) {}
|
||||
|
||||
scope_exit(scope_exit&& other) noexcept(std::is_nothrow_move_constructible_v<F>)
|
||||
: 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<F&>()()))
|
||||
{
|
||||
if (active_)
|
||||
func_();
|
||||
}
|
||||
|
||||
void release() noexcept { active_ = false; }
|
||||
|
||||
private:
|
||||
F func_;
|
||||
bool active_;
|
||||
};
|
||||
|
||||
template <class F>
|
||||
scope_exit(F) -> scope_exit<F>;
|
||||
|
||||
} // 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
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
struct StructuredAppendInfo
|
||||
{
|
||||
int index = -1;
|
||||
int count = -1;
|
||||
std::string id;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
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<char>((modVal >= 10 ? 'A' - 10 : '0') + modVal)) : std::string();
|
||||
}
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -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 <cassert>
|
||||
#include <stdexcept>
|
||||
#include <cstdio>
|
||||
|
||||
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<uint8_t*>(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
|
||||
@@ -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 <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string BytesToUtf8(ByteView bytes, ECI eci);
|
||||
|
||||
inline std::string BytesToUtf8(ByteView bytes, CharacterSet cs)
|
||||
{
|
||||
return BytesToUtf8(bytes, ToECI(cs));
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -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 <zint.h>
|
||||
#else
|
||||
#include "libzueci/zueci.h"
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
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<int>(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<const unsigned char *>(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<const unsigned char *>(str.data()), str_len,
|
||||
reinterpret_cast<unsigned char *>(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<const unsigned char *>(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<const unsigned char *>(str.data()), str_len,
|
||||
reinterpret_cast<unsigned char *>(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
|
||||
Loaded 100 of 2506 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user