Skip to content

Image pipeline guide

The reader's platform boundary is one deliberately boring object:

{
  data: Uint8ClampedArray | Uint8Array | number[],
  width: number,
  height: number,
}

data is RGBA, four channel values per pixel, row-major, in the order red, green, blue, alpha. A browser ImageData already has this shape. Node, a Web Worker, a canvas, OffscreenCanvas or an application-owned image adapter can all provide the same value without an SDK-specific wrapper.

Validate the image before decoding

The runtime validates the boundary before it starts detection:

Check Rule
width, height Positive safe integers
Each side At most 16,384 pixels
Total pixels At most 16,777,216
Array type Uint8ClampedArray, Uint8Array or number[]
Array length At least width * height * 4 values
number[] values Every required value must be an integer from 0 to 255

The total ceiling is still high—roughly twice the pixel count of a normal 4K image—but it is an allocation guard, not a target resolution. A camera loop usually runs faster and more reliably on a carefully downscaled working frame. Do not “fix” invalid input by silently coercing dimensions or channel values; reject it at your application boundary and log only the non-sensitive context needed to diagnose the issue.

What happens to RGBA data

LuminanceSource.fromImageData() converts the image to one byte of luminance per pixel. It uses a Rec. 601-style integer approximation and composites transparent pixels over white before calculating luminance. This matters for a barcode exported as a transparent PNG: transparent quiet-zone pixels are not mistaken for black ink.

The conversion snapshots the resulting greyscale buffer. Later mutations of the caller's source array do not rewrite a decode that has already crossed the boundary. The SDK does not retain a DOM image, a canvas or a file handle.

import { LuminanceSource } from '@sythos/js_barcode_universal';

const source = LuminanceSource.fromImageData({
  data: new Uint8ClampedArray([
    0, 0, 0, 255,       // dark pixel
    255, 255, 255, 255, // light pixel
  ]),
  width: 2,
  height: 1,
});

console.log(source.width, source.height, source.get(0, 0), source.get(1, 0));

For a pipeline that already owns a validated one-channel raster, use LuminanceSource.fromGrey(grey, width, height). It still checks dimensions and copies the caller-owned bytes. The factor must leave at least one output pixel on each side:

import { LuminanceSource } from '@sythos/js_barcode_universal';

const width = 4;
const height = 4;
const greyscaleBytes = new Uint8Array([
  0, 0, 255, 255,
  0, 0, 255, 255,
  255, 255, 0, 0,
  255, 255, 0, 0,
]);
const source = LuminanceSource.fromGrey(greyscaleBytes, width, height);
const inverted = source.invert();
const quarterTurn = source.rotate90();
const smaller = source.downscale(2);

downscale() uses integer-factor box averaging. It is useful before a camera or batch detector when the original frame has more pixels than the physical module size can use. It is not a substitute for preserving enough pixels per module; a tiny symbol should be cropped or captured closer instead.

Binarization choices

decode() defaults to binarizer: 'auto'. The public choices are:

Strategy Best fit Trade-off
global Clean generated PNGs, screenshots and flat scans Fast and consistent, but one threshold can lose a shadowed symbol
hybrid Uneven camera lighting, glare and page falloff More work, with a local threshold per block
auto General use Uses the normal adaptive path and targeted global fallbacks where the decoder supports them

The low-level functions are public when an application needs to inspect the stages itself:

import {
  LuminanceSource,
  binarizeGlobal,
  binarizeHybrid,
} from '@sythos/js_barcode_universal';

const imageData = {
  data: new Uint8ClampedArray(32 * 32 * 4),
  width: 32,
  height: 32,
};
const source = LuminanceSource.fromImageData(imageData);
const globalBits = binarizeGlobal(source);
const cameraBits = binarizeHybrid(source);

console.log(globalBits.getBounds(), cameraBits.getBounds());

A BitMatrix uses one set bit for a dark module. Low-level detector APIs consume that matrix, but most applications should call root decode() so format selection, validation, de-duplication and camera metadata stay consistent.

Perspective and orientation

Some detectors recover a symbol quadrilateral and sample it through a perspective transform; others use an axis-aligned bounding box or a format- specific scanline. Rotation and perspective are not interchangeable:

  • a quarter-turn can be normalized with a raster rotation;
  • a 45-degree camera-profile pass resamples greyscale input for 2D detectors; linear detectors rotate the binarized matrix;
  • an oblique photograph needs usable module contrast and a detector that can recover its geometry;
  • no option can restore information destroyed by clipping, blur or a missing quiet zone.

For the eight camera-profile angles and the rotation metadata, see Camera reading. Ordinary decoding intentionally avoids arbitrary-angle resampling.

Browser canvas adapter

The safe, direct browser path is:

const context = canvas.getContext('2d', { willReadFrequently: true });
if (!context) throw new Error('A 2D canvas context is required');

context.drawImage(imageOrVideo, 0, 0, canvas.width, canvas.height);
const frame = context.getImageData(0, 0, canvas.width, canvas.height);
const hits = decode(frame, { formats: ['qr'], binarizer: 'auto' });

getImageData() can fail before the SDK sees the bytes when a cross-origin image taints the canvas. Configure CORS or use a same-origin resource. The canonical read example uses a hidden work canvas for both file and camera frames.

Input errors and empty results

Malformed or oversized images can raise a library error at the input boundary. Catch that error around an untrusted file or frame and show a safe status. A valid image with no validated symbol returns []; that is expected for most camera frames and is not an exception.

Do not process partial text, a candidate bounding box or a low-quality visual guess as a barcode result. Only the returned DecodeResult has crossed the format decoder and validation path. If the input is user-controlled, apply application-level checks to result.text and keep it out of HTML sinks.

The resource boundary mirrors SECURITY.md: image data is untrusted, allocations are bounded, and a crash that becomes denial of service, memory abuse or code/data compromise should be treated as a security report.