CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-zxing--library

TypeScript port of ZXing multi-format 1D/2D barcode image processing library

Overview
Eval results
Files

edge-cases.mddocs/examples/

Edge Cases and Advanced Scenarios

Handling challenging situations, edge cases, and advanced barcode processing scenarios.

Handling Poor Image Quality

Multiple Binarization Strategies

import {
  MultiFormatReader,
  BinaryBitmap,
  HybridBinarizer,
  GlobalHistogramBinarizer,
  RGBLuminanceSource,
  LuminanceSource,
  DecodeHintType
} from '@zxing/library';

async function robustDecode(
  imageData: Uint8ClampedArray,
  width: number,
  height: number
): Promise<Result | null> {
  const reader = new MultiFormatReader();
  const source = new RGBLuminanceSource(imageData, width, height);

  // Strategy 1: HybridBinarizer (best for uneven lighting)
  try {
    const bitmap = new BinaryBitmap(new HybridBinarizer(source));
    return reader.decode(bitmap);
  } catch (e1) {}

  // Strategy 2: GlobalHistogramBinarizer (different threshold)
  try {
    const bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
    return reader.decode(bitmap);
  } catch (e2) {}

  // Strategy 3: HybridBinarizer with TRY_HARDER
  try {
    const bitmap = new BinaryBitmap(new HybridBinarizer(source));
    const hints = new Map();
    hints.set(DecodeHintType.TRY_HARDER, true);
    return reader.decode(bitmap, hints);
  } catch (e3) {}

  // Strategy 4: Inverted (white-on-black barcodes)
  try {
    const inverted = source.invert();
    const bitmap = new BinaryBitmap(new HybridBinarizer(inverted));
    const hints = new Map();
    hints.set(DecodeHintType.TRY_HARDER, true);
    return reader.decode(bitmap, hints);
  } catch (e4) {}

  return null;
}

Image Preprocessing

import sharp from 'sharp';

async function preprocessImage(inputPath: string): Promise<{ data: Uint8ClampedArray; width: number; height: number }> {
  // Apply preprocessing: contrast, sharpen, denoise
  const processed = await sharp(inputPath)
    .normalize() // Auto-contrast
    .sharpen()   // Sharpen edges
    .grayscale() // Convert to grayscale
    .raw()
    .toBuffer({ resolveWithObject: true });
  
  return {
    data: new Uint8ClampedArray(processed.data),
    width: processed.info.width,
    height: processed.info.height
  };
}

// Usage
const { data, width, height } = await preprocessImage('./barcode.jpg');
const result = await robustDecode(data, width, height);

Handling Rotated Barcodes

Try Multiple Rotations

import { MultiFormatReader, BinaryBitmap, LuminanceSource } from '@zxing/library';

function decodeWithRotation(source: LuminanceSource): Result | null {
  const reader = new MultiFormatReader();
  
  if (!source.isRotateSupported()) {
    // Try TRY_HARDER which includes rotation
    const bitmap = new BinaryBitmap(new HybridBinarizer(source));
    const hints = new Map();
    hints.set(DecodeHintType.TRY_HARDER, true);
    
    try {
      return reader.decode(bitmap, hints);
    } catch (e) {
      return null;
    }
  }
  
  // Try all 4 orientations
  let current = source;
  for (let i = 0; i < 4; i++) {
    try {
      const bitmap = new BinaryBitmap(new HybridBinarizer(current));
      return reader.decode(bitmap);
    } catch (e) {
      if (i < 3) {
        current = current.rotateCounterClockwise();
      }
    }
  }
  
  return null;
}

Handling Multiple Barcodes

Decode All Barcodes in Image

import { PDF417Reader, MultiFormatReader } from '@zxing/library';

async function decodeAllBarcodes(
  imageData: Uint8ClampedArray,
  width: number,
  height: number
): Promise<Result[]> {
  const results: Result[] = [];
  
  const luminanceSource = new RGBLuminanceSource(imageData, width, height);
  const bitmap = new BinaryBitmap(new HybridBinarizer(luminanceSource));
  
  // Try PDF417 (supports multiple)
  const pdf417Reader = new PDF417Reader();
  try {
    const pdf417Results = pdf417Reader.decodeMultiple(bitmap);
    results.push(...pdf417Results);
  } catch (e) {}
  
  // Try other formats
  const reader = new MultiFormatReader();
  try {
    const result = reader.decode(bitmap);
    results.push(result);
  } catch (e) {}
  
  return results;
}

Very Large Data Encoding

QR Code with Maximum Capacity

import {
  QRCodeWriter,
  BarcodeFormat,
  EncodeHintType,
  QRCodeDecoderErrorCorrectionLevel
} from '@zxing/library';

function encodeLargeData(data: string): BitMatrix | null {
  const writer = new QRCodeWriter();
  
  // Start with version 40, error correction L for maximum capacity
  const hints = new Map();
  hints.set(EncodeHintType.QR_VERSION, 40); // Maximum version
  hints.set(EncodeHintType.ERROR_CORRECTION, QRCodeDecoderErrorCorrectionLevel.L); // Minimum EC
  hints.set(EncodeHintType.CHARACTER_SET, 'UTF-8');
  
  try {
    return writer.encode(data, BarcodeFormat.QR_CODE, 800, 800, hints);
  } catch (error) {
    console.error('Data too large even for QR Code v40-L:', error);
    return null;
  }
}

// QR Code v40-L can hold up to 2,953 bytes

Split Data Across Multiple Codes

function generateMultiPartQR(data: string, maxBytesPerCode: number = 2000): BitMatrix[] {
  const writer = new QRCodeWriter();
  const hints = new Map();
  hints.set(EncodeHintType.ERROR_CORRECTION, 'M');
  
  const chunks: string[] = [];
  for (let i = 0; i < data.length; i += maxBytesPerCode) {
    chunks.push(data.substring(i, i + maxBytesPerCode));
  }
  
  return chunks.map((chunk, index) => {
    const content = `${index + 1}/${chunks.length}: ${chunk}`;
    return writer.encode(content, BarcodeFormat.QR_CODE, 300, 300, hints);
  });
}

// Usage
const largeData = "...very large data...";
const qrCodes = generateMultiPartQR(largeData);
console.log(`Split into ${qrCodes.length} QR codes`);

Damaged Barcode Recovery

Progressive Error Correction

import {
  QRCodeWriter,
  QRCodeReader,
  BarcodeFormat,
  EncodeHintType,
  QRCodeDecoderErrorCorrectionLevel
} from '@zxing/library';

// Generate with high error correction for critical data
function generateRobustQR(text: string): BitMatrix {
  const writer = new QRCodeWriter();
  
  const hints = new Map();
  hints.set(EncodeHintType.ERROR_CORRECTION, QRCodeDecoderErrorCorrectionLevel.H); // 30% recovery
  hints.set(EncodeHintType.MARGIN, 4); // Full quiet zone
  
  return writer.encode(text, BarcodeFormat.QR_CODE, 400, 400, hints);
}

// Decode damaged barcode with retries
async function decodeDamagedBarcode(bitmap: BinaryBitmap): Promise<Result | null> {
  const reader = new QRCodeReader();
  
  // Try with increasing accuracy
  const strategies = [
    new Map(), // No hints
    new Map([[DecodeHintType.TRY_HARDER, true]]), // Try harder
    new Map([[DecodeHintType.PURE_BARCODE, true]]), // Pure barcode
    new Map([
      [DecodeHintType.TRY_HARDER, true],
      [DecodeHintType.PURE_BARCODE, true]
    ]) // Both
  ];
  
  for (const hints of strategies) {
    try {
      return reader.decode(bitmap, hints);
    } catch (e) {
      // Continue to next strategy
    }
  }
  
  return null;
}

Barcode Size Constraints

Minimum Viable QR Code

// Smallest QR code (v1, 21×21 modules) with minimal data
const writer = new QRCodeWriter();
const hints = new Map();
hints.set(EncodeHintType.ERROR_CORRECTION, 'L'); // Minimum EC for maximum data
hints.set(EncodeHintType.MARGIN, 1); // Minimum margin (violates spec but may work)

const matrix = writer.encode('A', BarcodeFormat.QR_CODE, 21, 21, hints);

Space-Constrained Encoding

import {
  DataMatrixWriter,
  AztecCodeWriter,
  BarcodeFormat,
  EncodeHintType
} from '@zxing/library';

function encodeCompact(text: string): { format: string; matrix: BitMatrix; size: number } {
  // Try Aztec compact (smallest for given data)
  try {
    const aztecWriter = new AztecCodeWriter();
    const aztecHints = new Map();
    aztecHints.set(EncodeHintType.AZTEC_LAYERS, -1); // Compact, 1 layer minimum
    aztecHints.set(EncodeHintType.ERROR_CORRECTION, 23); // ISO minimum
    
    const aztecMatrix = aztecWriter.encode(text, BarcodeFormat.AZTEC, 0, 0, aztecHints);
    
    return {
      format: 'Aztec Compact',
      matrix: aztecMatrix,
      size: aztecMatrix.getWidth()
    };
  } catch (e) {}
  
  // Fall back to Data Matrix
  try {
    const dmWriter = new DataMatrixWriter();
    const dmMatrix = dmWriter.encode(text, BarcodeFormat.DATA_MATRIX, 100, 100);
    
    return {
      format: 'Data Matrix',
      matrix: dmMatrix,
      size: dmMatrix.getWidth()
    };
  } catch (e) {}
  
  // Fall back to QR Code
  const qrWriter = new QRCodeWriter();
  const qrMatrix = qrWriter.encode(text, BarcodeFormat.QR_CODE, 100, 100);
  
  return {
    format: 'QR Code',
    matrix: qrMatrix,
    size: qrMatrix.getWidth()
  };
}

Special Character Handling

International Text in QR Codes

import {
  QRCodeWriter,
  BarcodeFormat,
  EncodeHintType
} from '@zxing/library';

// Encode emoji and international characters
function encodeInternationalQR(text: string): BitMatrix {
  const writer = new QRCodeWriter();
  
  const hints = new Map();
  hints.set(EncodeHintType.CHARACTER_SET, 'UTF-8'); // Critical for international text
  hints.set(EncodeHintType.ERROR_CORRECTION, 'M');
  
  return writer.encode(text, BarcodeFormat.QR_CODE, 300, 300, hints);
}

// Examples
const texts = [
  'Hello 世界 🌍',
  'Привет мир',
  'مرحبا بالعالم',
  'こんにちは世界',
  '😀 Emoji support'
];

texts.forEach(text => {
  const matrix = encodeInternationalQR(text);
  console.log(`Encoded "${text}": ${matrix.getWidth()}×${matrix.getHeight()}`);
});

Binary Data Encoding

Encode Binary Data in Aztec

import { AztecEncoder, AztecCode } from '@zxing/library';

// Encode pure binary data (images, encrypted data, etc.)
function encodeBinaryData(data: Uint8Array): AztecCode {
  return AztecEncoder.encode(data, 33, 0); // 33% EC, auto-size
}

// Example: Encode small image
const imageBytes = new Uint8Array([/* PNG header and data */]);
const aztecCode = encodeBinaryData(imageBytes);

console.log('Encoded', imageBytes.length, 'bytes');
console.log('Aztec size:', aztecCode.getSize(), '×', aztecCode.getSize());
console.log('Format:', aztecCode.isCompact() ? 'Compact' : 'Full');
console.log('Layers:', aztecCode.getLayers());

Performance-Critical Scenarios

High-Speed Continuous Scanning

import { BrowserMultiFormatReader, DecodeHintType, BarcodeFormat } from '@zxing/library';

class HighSpeedScanner {
  private reader: BrowserMultiFormatReader;
  private frameCount = 0;
  private successCount = 0;
  private startTime = 0;

  constructor() {
    const hints = new Map();
    hints.set(DecodeHintType.POSSIBLE_FORMATS, [BarcodeFormat.CODE_128]); // Single format
    
    this.reader = new BrowserMultiFormatReader(hints, 50); // 50ms = 20 fps
  }

  async start(videoElement: HTMLVideoElement): Promise<void> {
    this.startTime = Date.now();
    this.frameCount = 0;
    this.successCount = 0;
    
    await this.reader.decodeFromVideoDevice(
      null,
      videoElement,
      (result, error) => {
        this.frameCount++;
        
        if (result) {
          this.successCount++;
          this.processCode(result.getText());
        }
        
        // Log performance every 100 frames
        if (this.frameCount % 100 === 0) {
          this.logPerformance();
        }
      }
    );
  }

  private logPerformance(): void {
    const elapsed = (Date.now() - this.startTime) / 1000;
    const fps = this.frameCount / elapsed;
    const successRate = (this.successCount / this.frameCount * 100).toFixed(1);
    
    console.log(`FPS: ${fps.toFixed(1)}, Success rate: ${successRate}%`);
  }

  private processCode(code: string): void {
    // Process scanned code...
  }

  stop(): void {
    this.reader.stopContinuousDecode();
    this.reader.reset();
  }
}

Version-Constrained QR Codes

Force Specific QR Version

import {
  QRCodeWriter,
  BarcodeFormat,
  EncodeHintType,
  WriterException
} from '@zxing/library';

function encodeFixedVersion(text: string, version: number): BitMatrix | null {
  const writer = new QRCodeWriter();
  
  const hints = new Map();
  hints.set(EncodeHintType.QR_VERSION, version);
  hints.set(EncodeHintType.ERROR_CORRECTION, 'M');
  
  try {
    return writer.encode(text, BarcodeFormat.QR_CODE, 300, 300, hints);
  } catch (error) {
    if (error instanceof WriterException) {
      console.error(`Data doesn't fit in version ${version}`);
      
      // Try with lower error correction
      hints.set(EncodeHintType.ERROR_CORRECTION, 'L');
      try {
        return writer.encode(text, BarcodeFormat.QR_CODE, 300, 300, hints);
      } catch (e) {
        console.error('Data too large even with EC-L');
      }
    }
    return null;
  }
}

// Usage: Force version 5 (37×37 modules)
const matrix = encodeFixedVersion('Data for v5', 5);

GS1 Application Identifiers

Parse GS1-128 Barcodes

import {
  Code128Reader,
  DecodeHintType,
  BarcodeFormat,
  BinaryBitmap
} from '@zxing/library';

interface GS1Data {
  gtin?: string;
  batch?: string;
  expiration?: string;
  serial?: string;
  [key: string]: string | undefined;
}

function decodeGS1_128(bitmap: BinaryBitmap): GS1Data | null {
  const reader = new Code128Reader();
  
  const hints = new Map();
  hints.set(DecodeHintType.ASSUME_GS1, true);
  hints.set(DecodeHintType.POSSIBLE_FORMATS, [BarcodeFormat.CODE_128]);
  
  try {
    const result = reader.decode(bitmap, hints);
    const text = result.getText();
    
    if (!text.startsWith(']C1')) {
      return null; // Not GS1-128
    }
    
    return parseGS1(text.substring(3));
  } catch (error) {
    return null;
  }
}

function parseGS1(data: string): GS1Data {
  const ais = new Map<string, string>();
  const aiPattern = /\((\d{2,4})\)([^\(]+)/g;
  
  let match;
  while ((match = aiPattern.exec(data)) !== null) {
    ais.set(match[1], match[2]);
  }
  
  return {
    gtin: ais.get('01'),
    batch: ais.get('10'),
    expiration: ais.get('17'),
    serial: ais.get('21')
  };
}

// Usage
const gs1Data = decodeGS1_128(bitmap);
if (gs1Data) {
  console.log('GTIN:', gs1Data.gtin);
  console.log('Batch:', gs1Data.batch);
  console.log('Exp:', gs1Data.expiration);
}

Structured Append (Multi-Part QR)

Handle Multi-Part QR Codes

import {
  QRCodeReader,
  Result,
  ResultMetadataType,
  BinaryBitmap
} from '@zxing/library';

interface QRPart {
  sequence: number;
  parity: number;
  text: string;
}

class MultiPartQRHandler {
  private parts = new Map<number, Map<number, string>>(); // parity -> (sequence -> text)

  processResult(result: Result): string | null {
    const metadata = result.getResultMetadata();
    
    if (!metadata || !metadata.has(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) {
      // Single QR code
      return result.getText();
    }
    
    const sequence = metadata.get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE) as number;
    const parity = metadata.get(ResultMetadataType.STRUCTURED_APPEND_PARITY) as number;
    
    if (!this.parts.has(parity)) {
      this.parts.set(parity, new Map());
    }
    
    const parityParts = this.parts.get(parity)!;
    parityParts.set(sequence, result.getText());
    
    // Check if complete (need to know total count somehow)
    // Return concatenated if complete
    return null; // Incomplete
  }

  getStatus(): Map<number, number> {
    const status = new Map<number, number>();
    
    this.parts.forEach((parts, parity) => {
      status.set(parity, parts.size);
    });
    
    return status;
  }
}

Error Recovery Patterns

Retry with Backoff

async function decodeWithRetry(
  getbitmap: () => Promise<BinaryBitmap>,
  maxAttempts: number = 5,
  initialDelay: number = 100
): Promise<Result | null> {
  const reader = new MultiFormatReader();
  
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      const bitmap = await getBitmap();
      
      const hints = new Map();
      if (attempt > 0) {
        hints.set(DecodeHintType.TRY_HARDER, true);
      }
      
      return reader.decode(bitmap, hints);
    } catch (error) {
      if (error instanceof NotFoundException) {
        return null; // No barcode, don't retry
      }
      
      // Exponential backoff
      if (attempt < maxAttempts - 1) {
        const delay = initialDelay * Math.pow(2, attempt);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  return null;
}

Related

  • Real-World Scenarios
  • Quick Start Guide
  • API Reference

Install with Tessl CLI

npx tessl i tessl/npm-zxing--library@0.21.14

docs

examples

edge-cases.md

real-world-scenarios.md

index.md

tile.json