npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@paulmillr/qr

v0.1.1

Published

Minimal node.js & browser QR Code Pattern reader and generator. Supports ascii, term, gif and svg formats

Downloads

1,046

Readme

paulmillr-qr

Minimal node.js & browser QR Code Pattern reader and generator.

  • 0-dependency
  • Creating is ~1000 lines, single file. Supports ASCII, term, gif and svg formats
  • Reading is ~800 additional lines in a separate file. Supports camera feed and files
  • Ability to read QR in non-browser environments
  • Extensive tests: cross-testing against 100MB+ of codes
  • ESM support

Other JS libraries:

  • Don't work: jsQR is dead, zxing-js is dead, qr-scanner uses jsQR, doesn't work outside of browser, qcode-decoder broken version of jsQR, doesn't work outside of browser
  • Uncool: instascan is 1MB+ (zxing compiled to js via emscripten), qrcode modern refactor of jsQR (138 stars)

Interactive demo is available at paulmillr.com/demos/qr/.

Usage

Use NPM in node.js / browser:

npm install @paulmillr/qr

import writeQR from '@paulmillr/qr';
const gifBytes = writeQR('Hello world', 'gif');

import readQR from '@paulmillr/qr/decode';
const decoded = readQR({ height: 120, width: 120, data: gifBytes });

console.log(writeQR('Hello world', 'ascii'));
> █████████████████████████████████████
> ██ ▄▄▄▄▄ █  ▀▄▄█ ██▀▄▄▄▄█ ▀█ ▄▄▄▄▄ ██
> ██ █   █ █▀▄▀▄ ▄▄█▄█ ██▀█▀▀█ █   █ ██
> ██ █▄▄▄█ ██ ▄▄█▄▀▀ ▀ ██ ▄ ▄█ █▄▄▄█ ██
> ██▄▄▄▄▄▄▄█ ▀ ▀ █▄▀ ▀ ▀▄█ █ █▄▄▄▄▄▄▄██
> ██ █  ▀ ▄▄▀▀▀ █▀ ▄   ▀▀▄▀ ▄█ ▀█ ▀▄▄██
> ██▀▀▀  ▀▄▄██▄▀▀▄█▀ ▀▄█    ▀▀▀ ▄ █▄▄██
> █████▄▀▀▄▄██ ▀ ▀ ▄▄██▄ ▄▄ ▄ █▀█ █ ███
> ███   ▄▀▄█▄▄▄█   ▀██▄▄▄▀▀█▄▀ ▄█▀ ████
> ██▀▀ ▄ ▀▄ ▄▄██▀▄▀▀████▄▄▄ █▄ █  █▀▀██
> ██▀▀▄ ▄▀▄ ▀▀█▄▀▀▄▄▀▀ █▄▄▀█▀ ▀▄ █▄ ▀██
> ██▀▄▀██ ▄▄ ▀█▄█▀ ▀ ▀█▄▀▀ █▄▀▀ █  █ ██
> ███▀█▄▀▄▄ █  █ ██ ██ ▄ █ ▄▄▄ ▄▀▀▄▄ ██
> ██▄█▄▄▄█▄█ ▄ ▄▀█▀▀ ▄▀ █▀ ▄ ▄▄▄ ▀▄▀▄██
> ██ ▄▄▄▄▄ █ ▄█▄▀▀ ▀█   █▄█  █▄█ ▀▀▄▀██
> ██ █   █ █▀ ▄▀█ ██ ▄▄▀██   ▄▄ ▄█   ██
> ██ █▄▄▄█ █▄  ██▀ ▄▄ ▀█ ▄      ▀▄▄█▀██
> ██▄▄▄▄▄▄▄█▄███▄█▄█▄▄▄▄█▄█▄████▄▄█████
> ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀

Options:

type QrOpts = {
  ecc?: 'low', 'medium', 'quartile', 'high'; // Default: 'medium'. Low: 7%, medium: 15%, quartile: 25%, high: 30%
  encoding?: 'numeric', 'alphanumeric', 'byte', 'kanji', 'eci'; // Force specific encoding. Kanji and ECI are not supported yet
  version?: number; // 1..40, QR code version
  mask?: number; // 0..7, mask number
  border?: number; // Border size, default 2.
  scale?: number; // Scale to this number. Scale=2 -> each block will be 2x2 pixels
}
// - `raw`: 2d boolean array, to use with canvas or other image drawing libraries
// - `ascii`: ASCII symbols, not all fonts will display it properly
// - `term`: terminal color escape sequences. 2x bigger than ASCII, but works with all fonts
// - `gif`: uncompressed gif
// - `svg`: SVG vector image
type Output = 'raw' | 'ascii' | 'term' | 'gif' | 'svg';
function generateQR(text: string, output: 'raw', opts?: QrOpts): boolean[][];
function generateQR(text: string, output: 'ascii' | 'term' | 'svg', opts?: QrOpts): string;
function generateQR(text: string, output: 'gif', opts?: QrOpts): Uint8Array;

Decoding

QR decoding is hard: it is basically computer vision problem. There are two main cases:

  • decoding files. Can be slow, because it is supposed to handle complicated cases such as blur / rotation
  • decoding camera feed. Must be fast; even if one frame fails, next frame can succeed

State-of-the-art is the same as other computer vision problems: neural networks. Using them would make the library hard to audit. Since JS can't access accelerators, it would also likely be very slow. We don't want to use WebGL, it is complex and exposes users to fingerprinting. The implemented reader algorithm is inspired by ZXing:

  1. toBitmap: convert to bitmap, black & white segments. The slowest part and the most important.
  2. detect: find 3 finder patterns and one alignment (for version > 1). This is tricky — they can be rotated and distorted by perspective. Square is not really square — it's quadrilateral, and we have no idea about its size. The best thing we can do is counting runs of a same color and selecting one which looks like pattern; same almost same ratio of runs.
  3. transform: once patterns have been found, try to fix perspective and transform quadrilateral to square
  4. decodeBitmap: after that, execute encoding in reverse: read information via zig-zag pattern, interleave bytes, correct errors, convert to bits and, finally, read segments from bits to create string.
  5. Finished

API

export type Point4 = { x: number; y: number }[];
export type Image = {
  height: number;
  width: number;
  data: Uint8Array | Uint8ClampedArray | number[];
};
export type DecodeOpts = {
  // By default we assume that image has 4 channel per pixel (RGBA). isRGB: true will force to use only one
  isRGB?: boolean;
  // Returns 4 center (3 finder pattern + 1 alignment pattern) points if detected
  detectFn?: (points: Point4) => void;
  // Returns RGBA image of detected QR code
  qrFn?: (img: Image) => void;
};
export default function decode(img: Image, opts: DecodeOpts = {});

Vectors

To test decoding, we use awesome dataset from BoofCV. BoofCV decodes 73% of test cases, zxing decodes 49%. We are almost at parity with zxing (mostly because of ECI stuff not supported). Vectors are preserved in a git repo at github.com/paulmillr/qr-code-vectors.

For testing: accessing camera on iOS Safari requries HTTPS. It means file: protocol or non-encrypted http can't be used.

The spec is available at iso.org for 200 CHF.

Security

There are multiple ways how single text can be encoded:

  • Differences in segmentation: abc123 can become [{type: 'alphanum', 'abc'}, {type: 'num', '123'}], [{type: 'alphanum', 'abc123'}]
  • Differences between mask selection algo in libraries
  • Defaults: error correction level, how many bits are stored before upgrading versions

If an adversary is able to access multiple generated QR codes from a specific library, they can fingerprint a user, which can be then used to exfiltrate data from air-gapped systems. Adversary would then need to create library-specific exploit.

Currently we cross-test against python-qrcode: it is closer to spec than js implementations. We also always use single segment, which is not too optimal, but reduces fingerprinting data.

To improve the behavior, we can cross-test against 3-4 popular libraries.

Speed

Benchmarks measured with Apple M2 on MacOS 13 with node.js 19.

======== encode/ascii ========
encode/noble x 1,794 ops/sec @ 557μs/op
encode/qrcode-generator x 3,128 ops/sec @ 319μs/op ± 1.12% (min: 293μs, max: 3ms)
encode/nuintun x 1,872 ops/sec @ 533μs/op
======== encode/gif ========
encode/noble x 1,771 ops/sec @ 564μs/op
encode/qrcode-generator x 1,773 ops/sec @ 563μs/op
encode/nuintun x 1,883 ops/sec @ 530μs/op
======== encode: big ========
encode/noble x 87 ops/sec @ 11ms/op
encode/qrcode-generator x 124 ops/sec @ 8ms/op
encode/nuintun x 143 ops/sec @ 6ms/op
======== decode ========
decode/noble x 96 ops/sec @ 10ms/op ± 1.39% (min: 9ms, max: 32ms)
decode/jsqr x 34 ops/sec @ 28ms/op
decode/nuintun x 35 ops/sec @ 28ms/op
decode/instascan x 79 ops/sec @ 12ms/op ± 6.73% (min: 9ms, max: 223ms)
======== Decoding quality ========
blurred(45):  noble=12 (26.66%) jsqr=13 (28.88%) nuintun=13 (28.88%) instascan=11 (24.44%)

License

Copyright (c) 2023 Paul Miller (paulmillr.com)

Copyright (c) 2019 ZXing authors

The library @paulmillr/qr is dual-licensed under the Apache 2.0 OR MIT license. You can select a license of your choice.

The library contains code inspired by ZXing, which is licensed under Apache 2.0.

The license to the use of the QR Code stipulated by JIS (Japanese Industrial Standards) and the ISO are not necessary. The specification for QR Code has been made available for use by any person or organization. (Obtaining QR Code Specification) The word “QR Code” is registered trademark of DENSO WAVE INCORPORATED in Japan and other countries. To use the word “QR Code” in your publications or web site, etc, please indicate a sentence QR Code is registered trademark of DENSO WAVE INCORPORATED. This registered trademark applies only for the word “QR Code”, and not for the QR Code pattern (image). (https://www.qrcode.com/en/faq.html)