瀏覽代碼

Add new operation to generate image from raw data

Thomas Pointhuber 5 年之前
父節點
當前提交
d025c8bd9a
共有 2 個文件被更改,包括 163 次插入0 次删除
  1. 1 0
      src/core/config/Categories.json
  2. 162 0
      src/core/operations/GenerateImage.mjs

+ 1 - 0
src/core/config/Categories.json

@@ -382,6 +382,7 @@
         "name": "Multimedia",
         "ops": [
             "Render Image",
+            "Generate Image",
             "Play Media",
             "Optical Character Recognition",
             "Remove EXIF",

+ 162 - 0
src/core/operations/GenerateImage.mjs

@@ -0,0 +1,162 @@
+/**
+ * @author pointhi [thomas.pointhuber@gmx.at]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import {isImage} from "../lib/FileType";
+import {toBase64} from "../lib/Base64";
+import jimp from "jimp";
+
+/**
+ * Generate Image operation
+ */
+class GenerateImage extends Operation {
+
+    /**
+     * GenerateImage constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Generate Image";
+        this.module = "Image";
+        this.description = "Generate a Image using the input as pixel values.";
+        this.infoURL = "";
+        this.inputType = "byteArray";
+        this.outputType = "ArrayBuffer";
+        this.presentType = "html";
+        this.args = [
+            {
+                "name": "Mode",
+                "type": "option",
+                "value": ["Greyscale", "RG", "RGB", "RGBA"]
+            },
+            {
+                "name": "Pixel Scale Factor",
+                "type": "number",
+                "value": 8,
+            },
+            {
+                "name": "Pixels per Row",
+                "type": "number",
+                "value": 64,
+            }
+        ];
+    }
+
+    /**
+     * @param {byteArray} input
+     * @param {Object[]} args
+     * @returns {ArrayBuffer}
+     */
+    async run(input, args) {
+        const mode = args[0];
+        const scale = args[1];
+        const width = args[2];
+
+        if (scale  <= 0) {
+            throw new OperationError("Pixel Scale Factor needs to be > 0");
+        }
+
+        if (width  <= 0) {
+            throw new OperationError("Pixels per Row needs to be > 0");
+        }
+
+        const bytePerPixelMap = {
+            "Greyscale": 1,
+            "RG": 2,
+            "RGB": 3,
+            "RGBA": 4,
+        };
+
+        const bytesPerPixel = bytePerPixelMap[mode];
+
+        if (input.length % bytesPerPixel  !== 0) {
+            throw new OperationError(`Number of bytes is not a divisor of ${bytesPerPixel}`);
+        }
+
+        const height = Math.ceil(input.length / bytesPerPixel / width);
+        const image = await new jimp(width, height, (err, image) => {});
+
+
+        let i = 0;
+        while (i < input.length) {
+            const index = i/bytesPerPixel;
+            const x = index % width;
+            const y = Math.floor(index / width);
+
+            let red = 0x00;
+            let green = 0x00;
+            let blue = 0x00;
+            let alpha = 0xFF;
+
+            switch (mode) {
+                case "Greyscale":
+                    red = green = blue = input[i++];
+                    break;
+
+                case "RG":
+                    red = input[i++];
+                    green = input[i++];
+                    break;
+
+                case "RGB":
+                    red = input[i++];
+                    green = input[i++];
+                    blue = input[i++];
+                    break;
+
+                case "RGBA":
+                    red = input[i++];
+                    green = input[i++];
+                    blue = input[i++];
+                    alpha = input[i++];
+                    break;
+
+                default:
+                    throw new OperationError(`Unsupported Mode: (${mode})`);
+            }
+
+            try {
+                const pixel = jimp.rgbaToInt(red, green, blue, alpha);
+                image.setPixelColor(pixel, x, y);
+            } catch (err) {
+                throw new OperationError(`Error while generating image from pixel values. (${err})`);
+            }
+        }
+
+        if (scale !== 1) {
+            image.scaleToFit(width*scale, height*scale, jimp.RESIZE_NEAREST_NEIGHBOR);
+        }
+
+        try {
+            const imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
+            return imageBuffer.buffer;
+        } catch (err) {
+            throw new OperationError(`Error generating image. (${err})`);
+        }
+    }
+
+    /**
+     * Displays the generated image using HTML for web apps
+     * @param {ArrayBuffer} data
+     * @returns {html}
+     */
+    present(data) {
+        if (!data.byteLength) return "";
+        const dataArray = new Uint8Array(data);
+
+        const type = isImage(dataArray);
+        if (!type) {
+            throw new OperationError("Invalid file type.");
+        }
+
+        return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
+    }
+
+}
+
+export default GenerateImage;