Skip to main content

tapirscan 1.5.0 copy "tapirscan: ^1.5.0" to clipboard
tapirscan: ^1.5.0 copied to clipboard

Barcode scanner for Dart and Flutter: 1D and 2D formats, several barcodes per image, over the native Tapirscan library through dart:ffi.

Tapirscan for Dart and Flutter #

Preview: the API may change in minor releases.

A Dart package over the Tapirscan C library through dart:ffi, for Flutter apps and plain Dart programs. scan returns a ScanResult with values, barcodes and best. Shared semantics (support, geometry, image limits) are in API design.

Setup #

flutter pub add tapirscan   # or: dart pub add tapirscan

Requires Dart 3.10+ (Flutter 3.38+); tested with Flutter 3.44 and 3.47. The dependency ranges also accept the older code_assets 1.x and hooks 2.0 that other packages in an app may pin.

The package's build hook downloads the native library for each target from the GitHub release of the package version, checks it against the SHA-256 checksums in the package's hook/SHA256SUMS, and caches it in .dart_tool. Rust is not needed. The first build of each target needs network access to github.com; http_proxy and https_proxy are respected. Prebuilt libraries cover:

Platform Targets
Android arm64-v8a and x86_64, Android 7.0 (API 24) or later
iOS iOS 13 or later on arm64 devices and x86_64 simulators, iOS 14 on arm64 simulators
macOS macOS 11 or later, arm64 and x86_64
Linux x86_64 and arm64, glibc 2.28 or later
Windows x64

Other 64-bit targets, such as Windows on ARM, need a source_dir (a Tapirscan checkout) or a library_dir. Settings go in the app's pubspec.yaml:

hooks:
  user_defines:
    tapirscan:
      # A mirror of the release assets (https:// or file://), for builds
      # without access to github.com. Downloads are verified either way.
      # release_url: https://mirror.example.com/tapirscan/
      # or prebuilt libraries: <os>-<arch>/libtapirscan.so, such as android-arm64/
      # library_dir: native/
      # or a Tapirscan checkout to build from source:
      # source_dir: ../tapirscan

Building from source #

Inside a Tapirscan checkout, with source_dir, or as a Git dependency, the hook compiles the library with Cargo on the first build of each target:

dependencies:
  tapirscan:
    git:
      url: https://github.com/kleinicke/tapirscan
      path: bindings/flutter

The machine then needs the development prerequisites (rustup, Python 3.10+), the Cargo dependencies fetched once in the checkout (cargo fetch --locked for multiformat/ and bindings/rust/), and for Android the NDK that Flutter uses. Concurrent builds of one checkout wait for each other. scripts/build_flutter_native.py --target <rust-triple> --output <dir> builds one library for library_dir, for example for an F-Droid source build.

Scan #

import 'package:tapirscan/tapirscan.dart';

final scanner = Scanner(
  const ScannerOptions(mode: Mode.low, formats: {Format.ean13, Format.qrCode}),
);
final result = await scanner.scan(
  PixelImage(grayPixels, width: 640, height: 480), // Uint8List, 1 byte per pixel
);
for (final barcode in result.barcodes) {
  print('${barcode.format} ${barcode.text} ${barcode.rect}');
}
scanner.dispose();

scan runs on a background isolate so the UI stays responsive; scanSync runs on the calling isolate. Scans on one scanner run one at a time; create a scanner once and reuse it. Results stay valid after dispose.

ScannerOptions holds mode (default Mode.medium), formats (default Format.retail), eanAddOnPolicy (default EanAddOnPolicy.ignore) and parallel. Format and its presets (retail, common1D, common, linear, matrix, all) are generated from config/formats.json; see the format matrix. New formats may be added in minor releases. scan(image, formats: {...}) replaces the formats for one call; an empty set throws ArgumentError.

PixelImage takes gray (1 channel), RGB (3) or RGBA (4, alpha ignored) bytes and an optional row stride; the last row may be shorter than the stride. Each scan copies the pixels into native memory.

Each Barcode has text, format, polygon, rect, support, payloadBytes, eanAddOn, gs1, readerInitialization, structuredAppend and codabarStartStop (Codabar start and stop characters such as 'AB', which text omits); optional fields are null when unavailable. polygon (PixelPoints) and rect (PixelRect) are in source-image pixels; the names avoid Flutter's Offset and Rect, for example Rect.fromLTWH(r.left.toDouble(), r.top.toDouble(), r.width.toDouble(), r.height.toDouble()).

Images and camera frames #

Decode an image file with the platform codec and scan its RGBA pixels:

final codec = await ui.instantiateImageCodec(encodedBytes); // import 'dart:ui' as ui;
final image = (await codec.getNextFrame()).image;
final rgba = await image.toByteData(format: ui.ImageByteFormat.rawRgba);
final result = await scanner.scan(
  PixelImage(rgba!.buffer.asUint8List(), width: image.width, height: image.height, channels: 4),
);

A package:camera frame in YUV (Android YUV420/NV21, iOS bi-planar) is scanned through its luma plane without conversion; a BGRA frame is converted with PixelImage.bgra. Results are in the frame's sensor orientation.

final plane = image.planes.first;
final pixels = image.format.group == ImageFormatGroup.bgra8888
    ? PixelImage.bgra(plane.bytes, width: image.width, height: image.height, stride: plane.bytesPerRow)
    : PixelImage(plane.bytes, width: image.width, height: image.height, stride: plane.bytesPerRow);
final result = await scanner.scan(pixels);

Scan one frame at a time and drop frames that arrive meanwhile. To scan only part of a frame, pass a view: Uint8List.sublistView(plane.bytes, top * stride + left) with the crop's width and height and the frame's stride.

Coming from flutter_zxing: see the migration guide.

Platforms and errors #

64-bit Android (arm64-v8a, x86_64), iOS, macOS, Linux and Windows. Tested on macOS arm64, Linux x86_64 and an Android arm64 emulator. Native ABI 7 is 64-bit only: on 32-bit Android Tapirscan.isSupported is false and Scanner() throws UnsupportedError, so an app can fall back to another scanner. The app build itself still succeeds.

Failed calls throw ScannerException with a ScannerErrorCode (invalidInput or engine), the C status and the library's message. Calls on a disposed scanner throw StateError, and a pending scan either completes or throws it. No detection is an empty result, not an error.

Not available in Dart: inspect (undecoded regions, timings), structured content parsing, and the cross-language parity tests the other bindings have.

Development #

cd bindings/flutter
dart pub get
dart test                  # builds the host library through the hook
dart analyze --fatal-infos
dart run tool/ffigen.dart  # after changing bindings/c/include/tapirscan.h

lib/src/bindings.g.dart is generated by tool/ffigen.dart and lib/src/format.g.dart by scripts/generate_formats.py.

0
likes
160
points
64
downloads

Documentation

API reference

Publisher

verified publisherf-kleinicke.de

Weekly Downloads

Barcode scanner for Dart and Flutter: 1D and 2D formats, several barcodes per image, over the native Tapirscan library through dart:ffi.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#barcode #qr-code #scanner #ffi

License

Apache-2.0, MIT (license)

Dependencies

code_assets, crypto, ffi, hooks

More

Packages that depend on tapirscan