Skip to content

Commit 8352ae7

Browse files
authored
Merge pull request #1 from stefanorie/improve-binarizer
Improve binarizer (danimoh)
2 parents 01d3b0a + 82cd6e6 commit 8352ae7

3 files changed

Lines changed: 67 additions & 26 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ This data is in the same form as the [`ImageData`](https://developer.mozilla.org
6464
- `height` - The height of the image you wish to decode.
6565
- `options` (optional) - Additional options.
6666
- `inversionAttempts` - (`attemptBoth` (default), `dontInvert`, `onlyInvert`, or `invertFirst`) - Should jsQR attempt to invert the image to find QR codes with white modules on black backgrounds instead of the black modules on white background. This option defaults to `attemptBoth` for backwards compatibility but causes a ~50% performance hit, and will probably be default to `dontInvert` in future versions.
67+
- `canOverwriteImage` - (`true` (default) or `false`) - Specifies whether the image data can be overwritten for performance improvements or whether it should be kept untouched. If `true` the image buffer will be used internally to reduce additional memory allocation.
6768
6869
### Return value
6970
If a QR is able to be decoded the library will return an object with the following keys.

src/binarizer/index.ts

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {BitMatrix} from "../BitMatrix";
1+
import { BitMatrix } from "../BitMatrix";
22

33
const REGION_SIZE = 8;
44
const MIN_DYNAMIC_RANGE = 24;
@@ -11,9 +11,13 @@ function numBetween(value: number, min: number, max: number): number {
1111
class Matrix {
1212
private data: Uint8ClampedArray;
1313
private width: number;
14-
constructor(width: number, height: number) {
14+
constructor(width: number, height: number, buffer?: Uint8ClampedArray) {
1515
this.width = width;
16-
this.data = new Uint8ClampedArray(width * height);
16+
const bufferSize = width * height;
17+
if (buffer && buffer.length !== bufferSize) {
18+
throw new Error("Wrong buffer size");
19+
}
20+
this.data = buffer || new Uint8ClampedArray(bufferSize);
1721
}
1822
public get(x: number, y: number) {
1923
return this.data[y * this.width + x];
@@ -23,24 +27,40 @@ class Matrix {
2327
}
2428
}
2529

26-
export function binarize(data: Uint8ClampedArray, width: number, height: number, returnInverted: boolean) {
27-
if (data.length !== width * height * 4) {
30+
export function binarize(data: Uint8ClampedArray, width: number, height: number, returnInverted: boolean,
31+
canOverwriteImage: boolean) {
32+
const pixelCount = width * height;
33+
if (data.length !== pixelCount * 4) {
2834
throw new Error("Malformed data passed to binarizer.");
2935
}
36+
// assign the greyscale and binary image within the rgba buffer as the rgba image will not be needed after conversion
37+
let bufferOffset = 0;
3038
// Convert image to greyscale
31-
const greyscalePixels = new Matrix(width, height);
32-
for (let x = 0; x < width; x++) {
33-
for (let y = 0; y < height; y++) {
34-
const r = data[((y * width + x) * 4) + 0];
35-
const g = data[((y * width + x) * 4) + 1];
36-
const b = data[((y * width + x) * 4) + 2];
39+
let greyscaleBuffer: Uint8ClampedArray;
40+
if (canOverwriteImage) {
41+
greyscaleBuffer = new Uint8ClampedArray(data.buffer, bufferOffset, pixelCount);
42+
bufferOffset += pixelCount;
43+
}
44+
const greyscalePixels = new Matrix(width, height, greyscaleBuffer);
45+
for (let y = 0; y < height; y++) {
46+
for (let x = 0; x < width; x++) {
47+
const pixelPosition = (y * width + x) * 4;
48+
const r = data[pixelPosition];
49+
const g = data[pixelPosition + 1];
50+
const b = data[pixelPosition + 2];
3751
greyscalePixels.set(x, y, 0.2126 * r + 0.7152 * g + 0.0722 * b);
3852
}
3953
}
4054
const horizontalRegionCount = Math.ceil(width / REGION_SIZE);
4155
const verticalRegionCount = Math.ceil(height / REGION_SIZE);
56+
const blackPointsCount = horizontalRegionCount * verticalRegionCount;
4257

43-
const blackPoints = new Matrix(horizontalRegionCount, verticalRegionCount);
58+
let blackPointsBuffer: Uint8ClampedArray;
59+
if (canOverwriteImage) {
60+
blackPointsBuffer = new Uint8ClampedArray(data.buffer, bufferOffset, blackPointsCount);
61+
bufferOffset += blackPointsCount;
62+
}
63+
const blackPoints = new Matrix(horizontalRegionCount, verticalRegionCount, blackPointsBuffer);
4464
for (let verticalRegion = 0; verticalRegion < verticalRegionCount; verticalRegion++) {
4565
for (let hortizontalRegion = 0; hortizontalRegion < horizontalRegionCount; hortizontalRegion++) {
4666
let sum = 0;
@@ -87,11 +107,25 @@ export function binarize(data: Uint8ClampedArray, width: number, height: number,
87107
}
88108
}
89109

90-
const binarized = BitMatrix.createEmpty(width, height);
110+
let binarized: BitMatrix;
111+
if (canOverwriteImage) {
112+
const binarizedBuffer = new Uint8ClampedArray(data.buffer, bufferOffset, pixelCount);
113+
bufferOffset += pixelCount;
114+
binarized = new BitMatrix(binarizedBuffer, width);
115+
} else {
116+
binarized = BitMatrix.createEmpty(width, height);
117+
}
118+
91119
let inverted: BitMatrix = null;
92120
if (returnInverted) {
93-
inverted = BitMatrix.createEmpty(width, height);
121+
if (canOverwriteImage) {
122+
const invertedBuffer = new Uint8ClampedArray(data.buffer, bufferOffset, pixelCount);
123+
inverted = new BitMatrix(invertedBuffer, width);
124+
} else {
125+
inverted = BitMatrix.createEmpty(width, height);
126+
}
94127
}
128+
95129
for (let verticalRegion = 0; verticalRegion < verticalRegionCount; verticalRegion++) {
96130
for (let hortizontalRegion = 0; hortizontalRegion < horizontalRegionCount; hortizontalRegion++) {
97131
const left = numBetween(hortizontalRegion, 2, horizontalRegionCount - 3);

src/index.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import {binarize} from "./binarizer";
2-
import {BitMatrix} from "./BitMatrix";
3-
import {Chunks} from "./decoder/decodeData";
4-
import {decode} from "./decoder/decoder";
5-
import {extract} from "./extractor";
6-
import {locate, Point} from "./locator";
1+
import { binarize } from "./binarizer";
2+
import { BitMatrix } from "./BitMatrix";
3+
import { Chunks } from "./decoder/decodeData";
4+
import { decode } from "./decoder/decoder";
5+
import { extract } from "./extractor";
6+
import { locate, Point } from "./locator";
77

88
export interface QRCode {
99
binaryData: number[];
@@ -56,22 +56,28 @@ function scan(matrix: BitMatrix): QRCode | null {
5656

5757
export interface Options {
5858
inversionAttempts?: "dontInvert" | "onlyInvert" | "attemptBoth" | "invertFirst";
59+
canOverwriteImage?: boolean;
5960
}
6061

6162
const defaultOptions: Options = {
6263
inversionAttempts: "attemptBoth",
64+
canOverwriteImage: true,
6365
};
6466

65-
function jsQR(data: Uint8ClampedArray, width: number, height: number, providedOptions: Options = {}): QRCode | null {
66-
67-
const options = defaultOptions;
68-
Object.keys(options || {}).forEach(opt => { // Sad implementation of Object.assign since we target es5 not es6
69-
(options as any)[opt] = (providedOptions as any)[opt] || (options as any)[opt];
67+
function mergeObject(target: any, src: any) {
68+
Object.keys(src).forEach(opt => { // Sad implementation of Object.assign since we target es5 not es6
69+
target[opt] = src[opt];
7070
});
71+
}
72+
73+
function jsQR(data: Uint8ClampedArray, width: number, height: number, providedOptions: Options = {}): QRCode | null {
74+
const options = Object.create(null);
75+
mergeObject(options, defaultOptions);
76+
mergeObject(options, providedOptions);
7177

7278
const shouldInvert = options.inversionAttempts === "attemptBoth" || options.inversionAttempts === "invertFirst";
7379
const tryInvertedFirst = options.inversionAttempts === "onlyInvert" || options.inversionAttempts === "invertFirst";
74-
const {binarized, inverted} = binarize(data, width, height, shouldInvert);
80+
const { binarized, inverted } = binarize(data, width, height, shouldInvert, options.canOverwriteImage);
7581
let result = scan(tryInvertedFirst ? inverted : binarized);
7682
if (!result && (options.inversionAttempts === "attemptBoth" || options.inversionAttempts === "invertFirst")) {
7783
result = scan(tryInvertedFirst ? binarized : inverted);

0 commit comments

Comments
 (0)