Skip to content

Commit

Permalink
Add option to disable inverting (cozmo#78)
Browse files Browse the repository at this point in the history
* Add option to disable inverting
  • Loading branch information
cozmo authored Aug 15, 2018
1 parent c8d0ed0 commit 807b073
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 51 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ If you want to use jsQR to scan a webcam stream you'll need to extract the [`Ima

## Usage

jsQR exports a method that takes in 3 arguments representing the image data you wish to decode.
jsQR exports a method that takes in 3 arguments representing the image data you wish to decode. Additionally can take an options object to further configure scanning behavior.

```javascript
const code = jsQR(imageData, width, height);
const code = jsQR(imageData, width, height, options?);

if (code) {
console.log("Found QR code", code);
Expand All @@ -61,7 +61,9 @@ if (code) {
As such the length of this array should be `4 * width * height`.
This data is in the same form as the [`ImageData`](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) interface, and it's also [commonly](https://www.npmjs.com/package/jpeg-js#decoding-jpegs) [returned](https://github.com/lukeapage/pngjs/blob/master/README.md#property-data) by node modules for reading images.
- `width` - The width of the image you wish to decode.
- `height` The height of the image you wish to decode.
- `height` - The height of the image you wish to decode.
- `options` (optional) - Additional options.
- `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.
### Return value
If a QR is able to be decoded the library will return an object with the following keys.
Expand Down
1 change: 0 additions & 1 deletion dist/BitMatrix.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ export declare class BitMatrix {
get(x: number, y: number): boolean;
set(x: number, y: number, v: boolean): void;
setRegion(left: number, top: number, width: number, height: number, v: boolean): void;
getInverted(): BitMatrix;
}
8 changes: 7 additions & 1 deletion dist/binarizer/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
import { BitMatrix } from "../BitMatrix";
export declare function binarize(data: Uint8ClampedArray, width: number, height: number): BitMatrix;
export declare function binarize(data: Uint8ClampedArray, width: number, height: number, returnInverted: boolean): {
binarized: BitMatrix;
inverted: BitMatrix;
} | {
binarized: BitMatrix;
inverted?: undefined;
};
5 changes: 4 additions & 1 deletion dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ export interface QRCode {
bottomRightAlignmentPattern?: Point;
};
}
declare function jsQR(data: Uint8ClampedArray, width: number, height: number): QRCode | null;
export interface Options {
inversionAttempts?: "dontInvert" | "onlyInvert" | "attemptBoth" | "invertFirst";
}
declare function jsQR(data: Uint8ClampedArray, width: number, height: number, providedOptions?: Options): QRCode | null;
export default jsQR;
47 changes: 33 additions & 14 deletions dist/jsQR.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,6 @@ var BitMatrix = /** @class */ (function () {
}
}
};
BitMatrix.prototype.getInverted = function () {
return new BitMatrix(this.data.map(function (d) { return d === 0 ? 1 : 0; }), this.width);
};
return BitMatrix;
}());
exports.BitMatrix = BitMatrix;
Expand Down Expand Up @@ -357,11 +354,21 @@ function scan(matrix) {
},
};
}
function jsQR(data, width, height) {
var binarized = binarizer_1.binarize(data, width, height);
var result = scan(binarized);
if (!result) {
result = scan(binarized.getInverted());
var defaultOptions = {
inversionAttempts: "attemptBoth",
};
function jsQR(data, width, height, providedOptions) {
if (providedOptions === void 0) { providedOptions = {}; }
var options = defaultOptions;
Object.keys(options || {}).forEach(function (opt) {
options[opt] = providedOptions[opt] || options[opt];
});
var shouldInvert = options.inversionAttempts === "attemptBoth" || options.inversionAttempts === "invertFirst";
var tryInvertedFirst = options.inversionAttempts === "onlyInvert" || options.inversionAttempts === "invertFirst";
var _a = binarizer_1.binarize(data, width, height, shouldInvert), binarized = _a.binarized, inverted = _a.inverted;
var result = scan(tryInvertedFirst ? inverted : binarized);
if (!result && (options.inversionAttempts === "attemptBoth" || options.inversionAttempts === "invertFirst")) {
result = scan(tryInvertedFirst ? binarized : inverted);
}
return result;
}
Expand Down Expand Up @@ -396,7 +403,7 @@ var Matrix = /** @class */ (function () {
};
return Matrix;
}());
function binarize(data, width, height) {
function binarize(data, width, height, returnInverted) {
if (data.length !== width * height * 4) {
throw new Error("Malformed data passed to binarizer.");
}
Expand Down Expand Up @@ -453,6 +460,10 @@ function binarize(data, width, height) {
}
}
var binarized = BitMatrix_1.BitMatrix.createEmpty(width, height);
var inverted = null;
if (returnInverted) {
inverted = BitMatrix_1.BitMatrix.createEmpty(width, height);
}
for (var verticalRegion = 0; verticalRegion < verticalRegionCount; verticalRegion++) {
for (var hortizontalRegion = 0; hortizontalRegion < horizontalRegionCount; hortizontalRegion++) {
var left = numBetween(hortizontalRegion, 2, horizontalRegionCount - 3);
Expand All @@ -464,15 +475,23 @@ function binarize(data, width, height) {
}
}
var threshold = sum / 25;
for (var x = 0; x < REGION_SIZE; x++) {
for (var y = 0; y < REGION_SIZE; y++) {
var lum = greyscalePixels.get(hortizontalRegion * REGION_SIZE + x, verticalRegion * REGION_SIZE + y);
binarized.set(hortizontalRegion * REGION_SIZE + x, verticalRegion * REGION_SIZE + y, lum <= threshold);
for (var xRegion = 0; xRegion < REGION_SIZE; xRegion++) {
for (var yRegion = 0; yRegion < REGION_SIZE; yRegion++) {
var x = hortizontalRegion * REGION_SIZE + xRegion;
var y = verticalRegion * REGION_SIZE + yRegion;
var lum = greyscalePixels.get(x, y);
binarized.set(x, y, lum <= threshold);
if (returnInverted) {
inverted.set(x, y, !(lum <= threshold));
}
}
}
}
}
return binarized;
if (returnInverted) {
return { binarized: binarized, inverted: inverted };
}
return { binarized: binarized };
}
exports.binarize = binarize;

Expand Down
4 changes: 3 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ <h1>jsQR Demo</h1>
canvasElement.width = video.videoWidth;
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
var code = jsQR(imageData.data, imageData.width, imageData.height);
var code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: "dontInvert",
});
if (code) {
drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58");
drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58");
Expand Down
47 changes: 33 additions & 14 deletions docs/jsQR.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,6 @@ var BitMatrix = /** @class */ (function () {
}
}
};
BitMatrix.prototype.getInverted = function () {
return new BitMatrix(this.data.map(function (d) { return d === 0 ? 1 : 0; }), this.width);
};
return BitMatrix;
}());
exports.BitMatrix = BitMatrix;
Expand Down Expand Up @@ -357,11 +354,21 @@ function scan(matrix) {
},
};
}
function jsQR(data, width, height) {
var binarized = binarizer_1.binarize(data, width, height);
var result = scan(binarized);
if (!result) {
result = scan(binarized.getInverted());
var defaultOptions = {
inversionAttempts: "attemptBoth",
};
function jsQR(data, width, height, providedOptions) {
if (providedOptions === void 0) { providedOptions = {}; }
var options = defaultOptions;
Object.keys(options || {}).forEach(function (opt) {
options[opt] = providedOptions[opt] || options[opt];
});
var shouldInvert = options.inversionAttempts === "attemptBoth" || options.inversionAttempts === "invertFirst";
var tryInvertedFirst = options.inversionAttempts === "onlyInvert" || options.inversionAttempts === "invertFirst";
var _a = binarizer_1.binarize(data, width, height, shouldInvert), binarized = _a.binarized, inverted = _a.inverted;
var result = scan(tryInvertedFirst ? inverted : binarized);
if (!result && (options.inversionAttempts === "attemptBoth" || options.inversionAttempts === "invertFirst")) {
result = scan(tryInvertedFirst ? binarized : inverted);
}
return result;
}
Expand Down Expand Up @@ -396,7 +403,7 @@ var Matrix = /** @class */ (function () {
};
return Matrix;
}());
function binarize(data, width, height) {
function binarize(data, width, height, returnInverted) {
if (data.length !== width * height * 4) {
throw new Error("Malformed data passed to binarizer.");
}
Expand Down Expand Up @@ -453,6 +460,10 @@ function binarize(data, width, height) {
}
}
var binarized = BitMatrix_1.BitMatrix.createEmpty(width, height);
var inverted = null;
if (returnInverted) {
inverted = BitMatrix_1.BitMatrix.createEmpty(width, height);
}
for (var verticalRegion = 0; verticalRegion < verticalRegionCount; verticalRegion++) {
for (var hortizontalRegion = 0; hortizontalRegion < horizontalRegionCount; hortizontalRegion++) {
var left = numBetween(hortizontalRegion, 2, horizontalRegionCount - 3);
Expand All @@ -464,15 +475,23 @@ function binarize(data, width, height) {
}
}
var threshold = sum / 25;
for (var x = 0; x < REGION_SIZE; x++) {
for (var y = 0; y < REGION_SIZE; y++) {
var lum = greyscalePixels.get(hortizontalRegion * REGION_SIZE + x, verticalRegion * REGION_SIZE + y);
binarized.set(hortizontalRegion * REGION_SIZE + x, verticalRegion * REGION_SIZE + y, lum <= threshold);
for (var xRegion = 0; xRegion < REGION_SIZE; xRegion++) {
for (var yRegion = 0; yRegion < REGION_SIZE; yRegion++) {
var x = hortizontalRegion * REGION_SIZE + xRegion;
var y = verticalRegion * REGION_SIZE + yRegion;
var lum = greyscalePixels.get(x, y);
binarized.set(x, y, lum <= threshold);
if (returnInverted) {
inverted.set(x, y, !(lum <= threshold));
}
}
}
}
}
return binarized;
if (returnInverted) {
return { binarized: binarized, inverted: inverted };
}
return { binarized: binarized };
}
exports.binarize = binarize;

Expand Down
4 changes: 0 additions & 4 deletions src/BitMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,4 @@ export class BitMatrix {
}
}
}

public getInverted() {
return new BitMatrix(this.data.map(d => d === 0 ? 1 : 0), this.width);
}
}
25 changes: 18 additions & 7 deletions src/binarizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Matrix {
}
}

export function binarize(data: Uint8ClampedArray, width: number, height: number): BitMatrix {
export function binarize(data: Uint8ClampedArray, width: number, height: number, returnInverted: boolean) {
if (data.length !== width * height * 4) {
throw new Error("Malformed data passed to binarizer.");
}
Expand Down Expand Up @@ -88,6 +88,10 @@ export function binarize(data: Uint8ClampedArray, width: number, height: number)
}

const binarized = BitMatrix.createEmpty(width, height);
let inverted: BitMatrix = null;
if (returnInverted) {
inverted = BitMatrix.createEmpty(width, height);
}
for (let verticalRegion = 0; verticalRegion < verticalRegionCount; verticalRegion++) {
for (let hortizontalRegion = 0; hortizontalRegion < horizontalRegionCount; hortizontalRegion++) {
const left = numBetween(hortizontalRegion, 2, horizontalRegionCount - 3);
Expand All @@ -99,14 +103,21 @@ export function binarize(data: Uint8ClampedArray, width: number, height: number)
}
}
const threshold = sum / 25;
for (let x = 0; x < REGION_SIZE; x++) {
for (let y = 0; y < REGION_SIZE; y++) {
const lum = greyscalePixels.get(hortizontalRegion * REGION_SIZE + x, verticalRegion * REGION_SIZE + y);
binarized.set(hortizontalRegion * REGION_SIZE + x, verticalRegion * REGION_SIZE + y, lum <= threshold);
for (let xRegion = 0; xRegion < REGION_SIZE; xRegion++) {
for (let yRegion = 0; yRegion < REGION_SIZE; yRegion++) {
const x = hortizontalRegion * REGION_SIZE + xRegion;
const y = verticalRegion * REGION_SIZE + yRegion;
const lum = greyscalePixels.get(x, y);
binarized.set(x, y, lum <= threshold);
if (returnInverted) {
inverted.set(x, y, !(lum <= threshold));
}
}
}
}
}

return binarized;
if (returnInverted) {
return { binarized, inverted };
}
return { binarized };
}
26 changes: 21 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,27 @@ function scan(matrix: BitMatrix): QRCode | null {
};
}

function jsQR(data: Uint8ClampedArray, width: number, height: number): QRCode | null {
const binarized = binarize(data, width, height);
let result = scan(binarized);
if (!result) {
result = scan(binarized.getInverted());
export interface Options {
inversionAttempts?: "dontInvert" | "onlyInvert" | "attemptBoth" | "invertFirst";
}

const defaultOptions: Options = {
inversionAttempts: "attemptBoth",
};

function jsQR(data: Uint8ClampedArray, width: number, height: number, providedOptions: Options = {}): QRCode | null {

const options = defaultOptions;
Object.keys(options || {}).forEach(opt => { // Sad implementation of Object.assign since we target es5 not es6
(options as any)[opt] = (providedOptions as any)[opt] || (options as any)[opt];
});

const shouldInvert = options.inversionAttempts === "attemptBoth" || options.inversionAttempts === "invertFirst";
const tryInvertedFirst = options.inversionAttempts === "onlyInvert" || options.inversionAttempts === "invertFirst";
const {binarized, inverted} = binarize(data, width, height, shouldInvert);
let result = scan(tryInvertedFirst ? inverted : binarized);
if (!result && (options.inversionAttempts === "attemptBoth" || options.inversionAttempts === "invertFirst")) {
result = scan(tryInvertedFirst ? binarized : inverted);
}
return result;
}
Expand Down

0 comments on commit 807b073

Please sign in to comment.