TypeScript port of ZXing multi-format 1D/2D barcode image processing library
Handling challenging situations, edge cases, and advanced barcode processing scenarios.
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;
}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);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;
}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;
}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 bytesfunction 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`);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;
}// 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);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()
};
}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()}`);
});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());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();
}
}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);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);
}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;
}
}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;
}Install with Tessl CLI
npx tessl i tessl/npm-zxing--library@0.21.14