Yomu

pub package license analyze codecov

Pure Dart QR Code & Barcode Reader Library

Yomu is a zero-dependency pure Dart implementation of a QR code and barcode reader library. It works in any Dart environment including Flutter, Dart CLI applications, and server-side Dart.

✨ Why Yomu?

  • 📦 Zero Dependencies: No external package dependencies. Keep your app's dependency graph clean.
  • 🎯 Pure Dart: No C++/Native code. Works instantly on Web (Wasm/JS), Desktop, and Mobile without build issues.
  • 🚀 High Performance: Full HD in ~1.2ms, 4K in ~2.2ms on M4 MacBook Air (AOT). Fast enough for real-time scanning.
  • 🛡️ Robust & Tested: Comprehensive test coverage. Tested against hundreds of distorted, noisy, and unevenly lit images.

🚀 Quick Start

QR Code + All Barcodes

import 'package:yomu/yomu.dart';

void main() {
  // Create a YomuImage container
  final image = YomuImage.rgba(
    bytes: imageBytes,
    width: 300,
    height: 300,
  );

  // Decode QR codes and all barcode formats
  final result = Yomu.all.decode(image);
  print('Decoded: ${result.text}');
}

QR Code Only

// For QR code only scanning
final result = Yomu.qrOnly.decode(YomuImage.rgba(
  bytes: imageBytes,
  width: width,
  height: height,
));

📖 API Reference

Yomu Class

The main entry point class.

Constructor / Static Description
Yomu.all QR codes + all barcode formats
Yomu.qrOnly QR codes only
Yomu.barcodeOnly 1D barcodes only
Yomu.responsive All formats, DecodeEffort.balanced
Yomu.realtime All formats, DecodeEffort.fast (per-frame)
Yomu({enableQRCode, barcodeScanner}) Custom configuration

QR codes printed light on dark (reflectance reversal, ISO/IEC 18004:2015 6.2) are read by default. Every retry stage then reads them the way it reads dark-on-light codes, which roughly doubles what a frame holding no code costs above fast. Pass readLightOnDark: false when every code you read is dark on light: on a textured Full HD frame holding no code, fast goes from 3.6ms to 2.7ms, balanced from 41ms to 15ms and thorough from 130ms to 63ms.

QR codes printed as a mirror image (ISO/IEC 18004:2015 6.2) are read too, at every effort, dark on light or light on dark. A grid that fails to decode is read again transposed only when its format information reads better that way. There is no option: frames holding no code cost the same as without it, and hard or undecodable images about 1% more.

Method Description
decode() Decode the first QR code or barcode in an image
decodeAll() Detect and decode all QR codes in an image

Detection vs Latency (DecodeEffort)

Retries only run on images the fast path cannot decode, so successful scans are never slowed down by this setting. What it trades is detection on hard inputs against latency on inputs holding no code at all — which is every frame of a camera preview pointed at nothing.

The levels split where the cost actually jumps: between stages that reuse the binarized image already in hand and stages that rebuild it from the source pixels.

effort Retries Detection¹ Blank frame² Textured frame²
DecodeEffort.fast none 167/201, 83.1% 1.20ms 3.60ms
DecodeEffort.balanced corner grid search, despeckle, tolerant finder 188/201, 93.5% 1.48ms 40.56ms
DecodeEffort.thorough + full-resolution retry, alternate binarization thresholds 192/201, 95.5% 8.56ms 127.56ms

¹ Fixture corpus (benchmark/tool_detection_rate.dart --matrix). ² Full HD frame containing no code, all formats enabled, AOT (benchmark/tool_bench_seq.dart --frames); the textured frame is uniform random noise from a fixed seed. A textured frame costs more at every level because noise produces false finder patterns, so each stage has candidates to rule out rather than nothing to look at.

Pick by use case:

  • Single images (photos, uploaded pictures): keep the default thorough. A slower failure is better than a missed code.
  • Camera streams that can spend ~41ms on a bad frame (~15ms with readLightOnDark: false): Yomu.responsive (balanced). It recovers 21 of the 25 codes thorough adds over fast, for a third of the cost on a textured frame.
  • Real-time preview: Yomu.realtime (fast). Frames without a code fail as fast as possible; a code missed on one frame is caught on a later one.

The older tryHarder: bool parameter still works — false maps to fast, true to thorough — but it is deprecated in favor of effort.

YomuImage Class

A platform-agnostic container for image data.

Factory Description
YomuImage.rgba() Create from RGBA bytes (4 bytes/pixel)
YomuImage.bgra() Create from BGRA bytes (4 bytes/pixel)
YomuImage.grayscale() Create from grayscale bytes (1 byte/pixel)
YomuImage.yuv420() Create from Y-plane of YUV420 camera image

🔧 Support Status

Supported Image Classes

Yomu targets modern capture sources: printed codes, on-screen codes, and ordinary camera scans. Instead of relying on era-specific photo corpora, the test fixtures are generated to bracket the capability boundary of each distortion axis from both sides (see scripts/generate_stress_qr.py):

Distortion axis Decodes Does not decode
Salt & pepper noise 25% 30%
Low-light (Gaussian) noise σ=120² σ=170²
Gray dirt occlusion 30% 35%
Gaussian blur radius 5.0 radius 6.0
Perspective (top squeeze) 0.3 0.4
Perspective (side squeeze) 0.6 — (saturates)
JPEG artifacts quality 1 — (no boundary)
Specular glare full saturation — (EC absorbs it)
Screen moire amplitude 0.8 amplitude 0.9
Composite casual scan¹ blur 5.5 blur 6.0

¹ Mild perspective (0.2) + lighting gradient + blur. Each component alone is well inside its single-axis boundary.

² The only probabilistic axis. Each σ draws one noise field, so a fixture near the transition reports its own draw rather than the decoder's limit. Measured decode rate over 20 independent draws per σ: 110→100%, 120→100%, 130→75%, 140→70%, 150→50%, 160→40%, 170→5%, 180→5%. The rungs are taken from the flat ends so neither side depends on a lucky draw; the real transition is around σ=150.

Degradations outside these definable classes — arbitrary surface curvature, finder patterns cut out of the frame, damage beyond the error-correction capacity — are out of scope; that long tail is the domain of ML-based detectors.

Encoding Modes (QR)

Mode Support
Numeric ✅
Alphanumeric ✅
Byte (Latin-1/UTF-8) ✅
Kanji ✅
ECI ❌ Not Supported

1D Barcode Support

Format Support Description
EAN-13 ✅ International retail (includes JAN)
EAN-8 ✅ Small products
UPC-A ✅ North American retail
Code 128 ✅ Logistics, high-density
Code 39 ✅ Industrial, alphanumeric
ITF ✅ Interleaved 2 of 5, logistics
Codabar ✅ Libraries, blood banks

🎯 Performance

Run the benchmark suite:

uv run scripts/benchmark_runner.py

Standard Images (≤1000px)

  • Environment: M4 MacBook Air (2024), AOT Compiled
Mode Avg Decode Time
AOT ~0.55ms
JIT ~0.72ms

Large Images (Fused Downsampling)

Images >1MP are automatically processed with a fused conversion step for optimal performance.

Resolution Avg Decode Time
4K (3840×2160) ~4.0ms
Full HD (1920×1080) ~2.4ms

License

MIT License - see LICENSE for details.

Libraries

yomu
Yomu - Pure Dart QR Code and Barcode Reader Library