zxing-js / library

Multi-format 1D/2D barcode image processing library, usable in JavaScript ecosystem.
https://zxing-js.github.io/library/
Apache License 2.0
2.46k stars 546 forks source link

Wrong characters for Aztec decoder in `Decoder.MIXED_TABLE` #607

Open alexanderpopko opened 3 months ago

alexanderpopko commented 3 months ago

Hi, and many thanks to all contributers for maintaining this library!

I encountered a bug when decoding Aztec codes. Will try to supply a fix and create a pull request.

Describe the bug Decoding Aztec codes in some cases gives wrong results, i.e., '\x01' / '\x02' / '\x03' / '\x04' / '\x05' / '\x06' / '\x07' / '\x0b' / '\x1b' / '\x1c' / '\x1d' / '\x1e' / '\x1f' / '\x7f' get decoded to '\\1' / '\\2' / '\\3' / '\\4' / '\\5' / '\\6' / '\\7' / '\\13' / '\\33' / '\\34' / '\\35' / '\\36' / '\\37' / '\\177'.

To Reproduce Run the following script and see the expected/received output. I ran it using:

import {
  AztecCodeReader,
  AztecCodeWriter,
  BarcodeFormat,
  BinaryBitmap,
  BitMatrix,
  DecodeHintType,
  HybridBinarizer,
  RGBLuminanceSource,
  ZXingStringEncoding,
} from '@zxing/library';
import sharp from 'sharp';

ZXingStringEncoding.customDecoder = (bytes) =>
  Buffer.from(bytes).toString('latin1');

function getPixelArray(bits: BitMatrix): Uint8Array {
  const width = bits.getWidth();
  const height = bits.getHeight();

  const colours: number[] = [];

  for (let x = 0; x < width; x++) {
    for (let y = 0; y < height; y++) {
      colours.push(bits.get(x, y) ? 0 : 255);
    }
  }

  return Uint8Array.from(colours);
}

async function getResizedImage(
  pixels: Uint8Array,
  sourceWidth: number,
  sourceHeight: number,
  targetWidth: number,
  targetHeight: number
) {
  return await sharp(Uint8Array.from(pixels), {
    raw: {
      width: sourceWidth,
      height: sourceHeight,
      channels: 1,
    },
  })
    .toColourspace('b-w')
    .resize({ width: targetWidth, height: targetHeight, kernel: 'nearest' })
    .raw()
    .toBuffer({ resolveWithObject: true });
}

function encodeImage(data: string): BitMatrix {
  return new AztecCodeWriter().encode(data, BarcodeFormat.AZTEC, 0, 0);
}

async function decodeImage(bits: BitMatrix) {
  // It seems to be necessary to increase the image size before decoding,
  // otherwise Detector.getBullsEyeCorners will throw a NotFoundException.
  const { data, info } = await getResizedImage(
    getPixelArray(bits),
    bits.getWidth(),
    bits.getHeight(),
    100,
    100
  );

  const source = new RGBLuminanceSource(
    Uint8ClampedArray.from(data),
    info.width,
    info.height
  );
  const binarizer = new HybridBinarizer(source);
  const image = new BinaryBitmap(binarizer);

  const hints = new Map([
    [DecodeHintType.PURE_BARCODE, true],
    [DecodeHintType.TRY_HARDER, true],
  ]);

  return new AztecCodeReader().decode(image, hints).getText();
}

async function main() {
  const input = '\x01\x02\x03\x04\x05\x06\x07\x0b\x1b\x1c\x1d\x1e\x1f\x7f';
  const output = await decodeImage(encodeImage(input));

  console.log('Expected:', Buffer.from(input, 'latin1').toString('hex'));
  console.log('Received:', Buffer.from(output, 'latin1').toString('hex'));
}

main().then();

Expected behavior '\x01\x02\x03\x04\x05\x06\x07\x0b\x1b\x1c\x1d\x1e\x1f\x7f' gets decoded to '\x01\x02\x03\x04\x05\x06\x07\x0b\x1b\x1c\x1d\x1e\x1f\x7f'.

Desktop (please complete the following information):