filetity

One flipped module cannot fake a barcode. Two can.

A barcode reader has one failure that costs real money, and it is not the one people build for. Failing to read a code wastes ten seconds. Reading it as a different number ships the wrong product, prices the wrong item, or books the wrong parcel, and nobody finds out by looking at the label.

What a scan actually is

An EAN-13 is 95 modules wide: a module is the narrowest bar, and every bar and space is some whole number of them. Reading one means walking a single row of pixels, left to right, deciding light or dark, and turning that into the lengths of the runs. One row of our test image gives 59 runs, which is exactly what the format predicts: three guard bars, four runs per digit for twelve digits, five for the centre guard, three to close.

Two details do most of the work, and both are about not trusting the picture.

The threshold comes from the row, not from a constant. A scan of a receipt is not black on white, it is dark grey on light grey. Ink at 90 and paper at 150 both sit below a global cut of 128, so a fixed threshold reads the whole row as bars and finds nothing. Taking the brightest and darkest pixel in that row and splitting the difference reads the same code correctly, which is the measured result below.

Widths come from cumulative edges, not from each run. If you divide every run by an estimated module width and round, the errors add up and by the twelfth digit the arithmetic has drifted out of the tables. Rounding each cumulative edge against the whole symbol instead keeps the error below half a module forever. The same code reads at 1, 2, 3 and 5 pixels per module without changing anything.

Now damage it

Take 9780201379624, which is a real ISBN, and flip one module in the last digit.

one module flipped   ->  null
two modules flipped  ->  { text: "9780201379625", expected: "4" }

The single flip reads as nothing at all, and the reason is a property of the format rather than anything clever in the code. Every digit pattern within one of EAN's number sets has the same parity of dark modules, so changing exactly one lands on a pattern that is in no table, and the read fails at the lookup. A one-bit error cannot produce a wrong-but-plausible digit.

Two flips can. Move the last digit from R4 to R5 and every digit still reads, the guards are intact, the widths are perfect. The code says ...5 and the twelve digits before it add up to 4. That is a barcode which scans beautifully and means something else, and there is nothing in the picture that says so.

Which is why the check digit is the product

The last digit of an EAN is arithmetic on the others: weights of 3 and 1 alternating from the right, summed, subtracted from the next multiple of ten. Code 128 carries a modulo-103 checksum over its symbol values instead. Both are cheap, both are in the spec, and both are the only thing standing between a damaged label and a confident wrong answer.

So our decoder recomputes them and, when they disagree, returns nothing. Not the digits it found, not a best guess, not a confidence score. Nothing. The page says a barcode was found and its check digit does not add up, offers to show the raw digits with the failing one struck through, and refuses to give you a Copy button for them. Seeing the number is fair. Carrying it off as a result is not.

What it will not do, said plainly

It reads a clean image: a screenshot, a flat scan, a label photographed square and in focus. There is no perspective correction, no deblurring, and no reading of a code that is upside down. A photo of a shelf taken at an angle has bars that are wider at one end than the other, the widths stop matching the tables, and it returns nothing, correctly.

That is a decoder rather than a camera pipeline, and the distinction is worth being honest about up front. It is cheaper than letting somebody try four photographs and conclude the tool is broken.

How to check any barcode reader in one minute

Generate an EAN-13, open it in an image editor, and paint one bar slightly wider at the expense of its neighbouring space. Feed it back in. A reader that hands you thirteen digits without complaint is not checking the arithmetic, and the number it gave you is not the number on the label.

The tools this came out of

Both halves read from the same published pattern tables, in your browser, with nothing uploaded.

filetity is built by Adarsh Mishra. The pattern tables come from ISO/IEC 15417 for Code 128 and the GS1 General Specifications for EAN and UPC, and every number above was measured against our own decoder rather than quoted. If your reader disagrees, that is worth knowing: support@filetity.com.