Merge with qr-improvements.
Change QR code ops to use ArrayBuffer. Add new function to Utils to convert a string to arraybuffer.
This commit is contained in:
commit
c97e77c765
@ -529,6 +529,22 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string to an ArrayBuffer.
|
||||||
|
*
|
||||||
|
* @param {string} string
|
||||||
|
*/
|
||||||
|
static strToArrayBuffer(string) {
|
||||||
|
const arrayBuffer = new ArrayBuffer(string.length * 2);
|
||||||
|
const arrayBufferView = new Uint8Array(arrayBuffer);
|
||||||
|
for (let i = 0; i < string.length; i++) {
|
||||||
|
arrayBufferView[i] = string.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arrayBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses CSV data and returns it as a two dimensional array or strings.
|
* Parses CSV data and returns it as a two dimensional array or strings.
|
||||||
*
|
*
|
||||||
|
@ -312,6 +312,11 @@ class Magic {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the recipe returned an empty buffer, do not continue
|
||||||
|
if (_buffersEqual(output, new ArrayBuffer())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const magic = new Magic(output, this.opPatterns),
|
const magic = new Magic(output, this.opPatterns),
|
||||||
speculativeResults = await magic.speculativeExecution(
|
speculativeResults = await magic.speculativeExecution(
|
||||||
depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful, crib);
|
depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful, crib);
|
||||||
@ -395,7 +400,12 @@ class Magic {
|
|||||||
const recipe = new Recipe(recipeConfig);
|
const recipe = new Recipe(recipeConfig);
|
||||||
try {
|
try {
|
||||||
await recipe.execute(dish);
|
await recipe.execute(dish);
|
||||||
return dish.get(Dish.ARRAY_BUFFER);
|
// Return an empty buffer if the recipe did not run to completion
|
||||||
|
if (recipe.lastRunOp === recipe.opList[recipe.opList.length - 1]) {
|
||||||
|
return dish.get(Dish.ARRAY_BUFFER);
|
||||||
|
} else {
|
||||||
|
return new ArrayBuffer();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// If there are errors, return an empty buffer
|
// If there are errors, return an empty buffer
|
||||||
return new ArrayBuffer();
|
return new ArrayBuffer();
|
||||||
|
91
src/core/lib/QRCode.mjs
Normal file
91
src/core/lib/QRCode.mjs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* QR code resources
|
||||||
|
*
|
||||||
|
* @author j433866 [j433866@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2019
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import OperationError from "../errors/OperationError";
|
||||||
|
import jsQR from "jsqr";
|
||||||
|
import qr from "qr-image";
|
||||||
|
import jimp from "jimp";
|
||||||
|
import Utils from "../Utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a QR code image from an image
|
||||||
|
*
|
||||||
|
* @param {ArrayBuffer} input
|
||||||
|
* @param {boolean} normalise
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export async function parseQrCode(input, normalise) {
|
||||||
|
let image;
|
||||||
|
try {
|
||||||
|
image = await jimp.read(input);
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(`Error opening image. (${err})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (normalise) {
|
||||||
|
image.rgba(false);
|
||||||
|
image.background(0xFFFFFFFF);
|
||||||
|
image.normalize();
|
||||||
|
image.greyscale();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(`Error normalising iamge. (${err})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const qrData = jsQR(image.bitmap.data, image.getWidth(), image.getHeight());
|
||||||
|
if (qrData) {
|
||||||
|
return qrData.data;
|
||||||
|
} else {
|
||||||
|
throw new OperationError("Could not read a QR code from the image.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a QR code from the input string
|
||||||
|
*
|
||||||
|
* @param {string} input
|
||||||
|
* @param {string} format
|
||||||
|
* @param {number} moduleSize
|
||||||
|
* @param {number} margin
|
||||||
|
* @param {string} errorCorrection
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
export function generateQrCode(input, format, moduleSize, margin, errorCorrection) {
|
||||||
|
const formats = ["SVG", "EPS", "PDF", "PNG"];
|
||||||
|
if (!formats.includes(format.toUpperCase())) {
|
||||||
|
throw new OperationError("Unsupported QR code format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let qrImage;
|
||||||
|
try {
|
||||||
|
qrImage = qr.imageSync(input, {
|
||||||
|
type: format,
|
||||||
|
size: moduleSize,
|
||||||
|
margin: margin,
|
||||||
|
"ec_level": errorCorrection.charAt(0).toUpperCase()
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(`Error generating QR code. (${err})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!qrImage) {
|
||||||
|
throw new OperationError("Error generating QR code.");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case "SVG":
|
||||||
|
case "EPS":
|
||||||
|
case "PDF":
|
||||||
|
return Utils.strToArrayBuffer(qrImage);
|
||||||
|
case "PNG":
|
||||||
|
return qrImage.buffer;
|
||||||
|
default:
|
||||||
|
throw new OperationError("Unsupported QR code format.");
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import Operation from "../Operation";
|
import Operation from "../Operation";
|
||||||
import OperationError from "../errors/OperationError";
|
import OperationError from "../errors/OperationError";
|
||||||
import qr from "qr-image";
|
import { generateQrCode } from "../lib/QRCode";
|
||||||
import { toBase64 } from "../lib/Base64";
|
import { toBase64 } from "../lib/Base64";
|
||||||
import { isImage } from "../lib/FileType";
|
import { isImage } from "../lib/FileType";
|
||||||
import Utils from "../Utils";
|
import Utils from "../Utils";
|
||||||
@ -27,7 +27,7 @@ class GenerateQRCode extends Operation {
|
|||||||
this.description = "Generates a Quick Response (QR) code from the input text.<br><br>A QR code is a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan. A barcode is a machine-readable optical label that contains information about the item to which it is attached.";
|
this.description = "Generates a Quick Response (QR) code from the input text.<br><br>A QR code is a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan. A barcode is a machine-readable optical label that contains information about the item to which it is attached.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/QR_code";
|
this.infoURL = "https://wikipedia.org/wiki/QR_code";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "byteArray";
|
this.outputType = "ArrayBuffer";
|
||||||
this.presentType = "html";
|
this.presentType = "html";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
@ -38,12 +38,14 @@ class GenerateQRCode extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Module size (px)",
|
"name": "Module size (px)",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 5
|
"value": 5,
|
||||||
|
"min": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Margin (num modules)",
|
"name": "Margin (num modules)",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 2
|
"value": 2,
|
||||||
|
"min": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Error correction",
|
"name": "Error correction",
|
||||||
@ -57,61 +59,34 @@ class GenerateQRCode extends Operation {
|
|||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {string} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {byteArray}
|
* @returns {ArrayBuffer}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [format, size, margin, errorCorrection] = args;
|
const [format, size, margin, errorCorrection] = args;
|
||||||
|
|
||||||
// Create new QR image from the input data, and convert it to a buffer
|
return generateQrCode(input, format, size, margin, errorCorrection);
|
||||||
const qrImage = qr.imageSync(input, {
|
|
||||||
type: format,
|
|
||||||
size: size,
|
|
||||||
margin: margin,
|
|
||||||
"ec_level": errorCorrection.charAt(0).toUpperCase()
|
|
||||||
});
|
|
||||||
|
|
||||||
if (qrImage == null) {
|
|
||||||
throw new OperationError("Error generating QR code.");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case "SVG":
|
|
||||||
case "EPS":
|
|
||||||
case "PDF":
|
|
||||||
return [...Buffer.from(qrImage)];
|
|
||||||
case "PNG":
|
|
||||||
// Return the QR image buffer as a byte array
|
|
||||||
return [...qrImage];
|
|
||||||
default:
|
|
||||||
throw new OperationError("Unsupported QR code format.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the QR image using HTML for web apps
|
* Displays the QR image using HTML for web apps
|
||||||
*
|
*
|
||||||
* @param {byteArray} data
|
* @param {ArrayBuffer} data
|
||||||
* @returns {html}
|
* @returns {html}
|
||||||
*/
|
*/
|
||||||
present(data, args) {
|
present(data, args) {
|
||||||
if (!data.length) return "";
|
if (!data.byteLength && !data.length) return "";
|
||||||
|
const dataArray = new Uint8Array(data),
|
||||||
const [format] = args;
|
[format] = args;
|
||||||
|
|
||||||
if (format === "PNG") {
|
if (format === "PNG") {
|
||||||
let dataURI = "data:";
|
const type = isImage(dataArray);
|
||||||
const mime = isImage(data);
|
if (!type) {
|
||||||
if (mime){
|
throw new OperationError("Invalid file type.");
|
||||||
dataURI += mime + ";";
|
|
||||||
} else {
|
|
||||||
throw new OperationError("Invalid PNG file generated by QR image");
|
|
||||||
}
|
}
|
||||||
dataURI += "base64," + toBase64(data);
|
|
||||||
|
|
||||||
return `<img src="${dataURI}">`;
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Utils.byteArrayToChars(data);
|
return Utils.arrayBufferToStr(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,8 @@
|
|||||||
|
|
||||||
import Operation from "../Operation";
|
import Operation from "../Operation";
|
||||||
import OperationError from "../errors/OperationError";
|
import OperationError from "../errors/OperationError";
|
||||||
import { isImage } from "../lib/FileType";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import jsqr from "jsqr";
|
import { parseQrCode } from "../lib/QRCode";
|
||||||
import jimp from "jimp";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse QR Code operation
|
* Parse QR Code operation
|
||||||
@ -34,6 +33,14 @@ class ParseQRCode extends Operation {
|
|||||||
"value": false
|
"value": false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
this.patterns = [
|
||||||
|
{
|
||||||
|
"match": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)",
|
||||||
|
"flags": "",
|
||||||
|
"args": [false],
|
||||||
|
"useful": true
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,59 +51,10 @@ class ParseQRCode extends Operation {
|
|||||||
async run(input, args) {
|
async run(input, args) {
|
||||||
const [normalise] = args;
|
const [normalise] = args;
|
||||||
|
|
||||||
// Make sure that the input is an image
|
if (!isImage(input)) {
|
||||||
if (!isImage(new Uint8Array(input))) throw new OperationError("Invalid file type.");
|
throw new OperationError("Invalid file type.");
|
||||||
|
|
||||||
let image = input;
|
|
||||||
|
|
||||||
if (normalise) {
|
|
||||||
// Process the image to be easier to read by jsqr
|
|
||||||
// Disables the alpha channel
|
|
||||||
// Sets the image default background to white
|
|
||||||
// Normalises the image colours
|
|
||||||
// Makes the image greyscale
|
|
||||||
// Converts image to a JPEG
|
|
||||||
image = await new Promise((resolve, reject) => {
|
|
||||||
jimp.read(input)
|
|
||||||
.then(image => {
|
|
||||||
image
|
|
||||||
.rgba(false)
|
|
||||||
.background(0xFFFFFFFF)
|
|
||||||
.normalize()
|
|
||||||
.greyscale()
|
|
||||||
.getBuffer(jimp.MIME_JPEG, (error, result) => {
|
|
||||||
resolve(result);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
reject(new OperationError("Error reading the image file."));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return await parseQrCode(input, normalise);
|
||||||
if (image instanceof OperationError) {
|
|
||||||
throw image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
jimp.read(Buffer.from(image))
|
|
||||||
.then(image => {
|
|
||||||
if (image.bitmap != null) {
|
|
||||||
const qrData = jsqr(image.bitmap.data, image.getWidth(), image.getHeight());
|
|
||||||
if (qrData != null) {
|
|
||||||
resolve(qrData.data);
|
|
||||||
} else {
|
|
||||||
reject(new OperationError("Couldn't read a QR code from the image."));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reject(new OperationError("Error reading the image file."));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
reject(new OperationError("Error reading the image file."));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user