2506 changed files with 57271 additions and 30087 deletions
@ -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 |
||||
@ -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) |
||||
@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue