Ver Fonte

Merge with qr-improvements.
Change QR code ops to use ArrayBuffer.
Add new function to Utils to convert a string to arraybuffer.

j433866 há 6 anos atrás
pai
commit
c97e77c765

+ 16 - 0
src/core/Utils.mjs

@@ -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.
      *

+ 11 - 1
src/core/lib/Magic.mjs

@@ -312,6 +312,11 @@ class Magic {
                 return;
             }
 
+            // If the recipe returned an empty buffer, do not continue
+            if (_buffersEqual(output, new ArrayBuffer())) {
+                return;
+            }
+
             const magic = new Magic(output, this.opPatterns),
                 speculativeResults = await magic.speculativeExecution(
                     depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful, crib);
@@ -395,7 +400,12 @@ class Magic {
         const recipe = new Recipe(recipeConfig);
         try {
             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) {
             // If there are errors, return an empty buffer
             return new ArrayBuffer();

+ 91 - 0
src/core/lib/QRCode.mjs

@@ -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.");
+    }
+}

+ 17 - 42
src/core/operations/GenerateQRCode.mjs

@@ -6,7 +6,7 @@
 
 import Operation from "../Operation";
 import OperationError from "../errors/OperationError";
-import qr from "qr-image";
+import { generateQrCode } from "../lib/QRCode";
 import { toBase64 } from "../lib/Base64";
 import { isImage } from "../lib/FileType";
 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.infoURL = "https://wikipedia.org/wiki/QR_code";
         this.inputType = "string";
-        this.outputType = "byteArray";
+        this.outputType = "ArrayBuffer";
         this.presentType = "html";
         this.args = [
             {
@@ -38,12 +38,14 @@ class GenerateQRCode extends Operation {
             {
                 "name": "Module size (px)",
                 "type": "number",
-                "value": 5
+                "value": 5,
+                "min": 1
             },
             {
                 "name": "Margin (num modules)",
                 "type": "number",
-                "value": 2
+                "value": 2,
+                "min": 0
             },
             {
                 "name": "Error correction",
@@ -57,61 +59,34 @@ class GenerateQRCode extends Operation {
     /**
      * @param {string} input
      * @param {Object[]} args
-     * @returns {byteArray}
+     * @returns {ArrayBuffer}
      */
     run(input, args) {
         const [format, size, margin, errorCorrection] = args;
 
-        // Create new QR image from the input data, and convert it to a buffer
-        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.");
-        }
+        return generateQrCode(input, format, size, margin, errorCorrection);
     }
 
     /**
      * Displays the QR image using HTML for web apps
      *
-     * @param {byteArray} data
+     * @param {ArrayBuffer} data
      * @returns {html}
      */
     present(data, args) {
-        if (!data.length) return "";
-
-        const [format] = args;
-
+        if (!data.byteLength && !data.length) return "";
+        const dataArray = new Uint8Array(data),
+            [format] = args;
         if (format === "PNG") {
-            let dataURI = "data:";
-            const mime = isImage(data);
-            if (mime){
-                dataURI += mime + ";";
-            } else {
-                throw new OperationError("Invalid PNG file generated by QR image");
+            const type = isImage(dataArray);
+            if (!type) {
+                throw new OperationError("Invalid file type.");
             }
-            dataURI += "base64," + toBase64(data);
 
-            return `<img src="${dataURI}">`;
+            return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
         }
 
-        return Utils.byteArrayToChars(data);
+        return Utils.arrayBufferToStr(data);
     }
 
 }

+ 13 - 55
src/core/operations/ParseQRCode.mjs

@@ -6,9 +6,8 @@
 
 import Operation from "../Operation";
 import OperationError from "../errors/OperationError";
-import { isImage } from "../lib/FileType";
-import jsqr from "jsqr";
-import jimp from "jimp";
+import { isImage } from "../lib/FileType.mjs";
+import { parseQrCode } from "../lib/QRCode";
 
 /**
  * Parse QR Code operation
@@ -34,6 +33,14 @@ class ParseQRCode extends Operation {
                 "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) {
         const [normalise] = args;
 
-        // Make sure that the input is an image
-        if (!isImage(new Uint8Array(input))) 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."));
-                    });
-            });
+        if (!isImage(input)) {
+            throw new OperationError("Invalid file type.");
         }
-
-        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."));
-                });
-        });
-
+        return await parseQrCode(input, normalise);
     }
 
 }