From d025c8bd9a9ee63904acf9fa93791b77c1a208c2 Mon Sep 17 00:00:00 2001 From: Thomas Pointhuber Date: Sat, 12 Oct 2019 17:13:14 +0200 Subject: [PATCH 1/3] Add new operation to generate image from raw data --- src/core/config/Categories.json | 1 + src/core/operations/GenerateImage.mjs | 162 ++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 src/core/operations/GenerateImage.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 94f7fd309..f82f9416b 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -382,6 +382,7 @@ "name": "Multimedia", "ops": [ "Render Image", + "Generate Image", "Play Media", "Optical Character Recognition", "Remove EXIF", diff --git a/src/core/operations/GenerateImage.mjs b/src/core/operations/GenerateImage.mjs new file mode 100644 index 000000000..fbf3d2de6 --- /dev/null +++ b/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 ``; + } + +} + +export default GenerateImage; From a2780ca0560c1ad273c24e34e5348a25f84409d2 Mon Sep 17 00:00:00 2001 From: Thomas Pointhuber Date: Sat, 12 Oct 2019 17:35:46 +0200 Subject: [PATCH 2/3] Add bitwse mode to Generate Image operation --- src/core/operations/GenerateImage.mjs | 113 ++++++++++++++++---------- 1 file changed, 68 insertions(+), 45 deletions(-) diff --git a/src/core/operations/GenerateImage.mjs b/src/core/operations/GenerateImage.mjs index fbf3d2de6..dd780f185 100644 --- a/src/core/operations/GenerateImage.mjs +++ b/src/core/operations/GenerateImage.mjs @@ -6,9 +6,11 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; import {isImage} from "../lib/FileType"; import {toBase64} from "../lib/Base64"; import jimp from "jimp"; +import {isWorkerEnvironment} from "../Utils"; /** * Generate Image operation @@ -32,7 +34,7 @@ class GenerateImage extends Operation { { "name": "Mode", "type": "option", - "value": ["Greyscale", "RG", "RGB", "RGBA"] + "value": ["Greyscale", "RG", "RGB", "RGBA", "Bits"] }, { "name": "Pixel Scale Factor", @@ -70,65 +72,86 @@ class GenerateImage extends Operation { "RG": 2, "RGB": 3, "RGBA": 4, + "Bits": 1/8, }; const bytesPerPixel = bytePerPixelMap[mode]; - if (input.length % bytesPerPixel !== 0) { + if (bytesPerPixel > 0 && 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})`); + if (isWorkerEnvironment()) + self.sendStatusMessage("Generate image from data..."); + + if (mode === "Bits") { + let index = 0; + for (let j = 0; j < input.length; j++) { + const curByte = Utils.bin(input[j]); + for (let k = 0; k < 8; k++, index++) { + const x = index % width; + const y = Math.floor(index / width); + + const value = curByte[k] === "0" ? 0xFF : 0x00; + const pixel = jimp.rgbaToInt(value, value, value, 0xFF); + image.setPixelColor(pixel, x, y); + } } - - 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})`); + } else { + 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) { + if (isWorkerEnvironment()) + self.sendStatusMessage("Scale image..."); + image.scaleToFit(width*scale, height*scale, jimp.RESIZE_NEAREST_NEIGHBOR); } From ef61735f64e712cb3b05fed72e8f076987540691 Mon Sep 17 00:00:00 2001 From: Thomas Pointhuber Date: Sat, 12 Oct 2019 17:52:16 +0200 Subject: [PATCH 3/3] Fix typo --- src/core/operations/GenerateImage.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/GenerateImage.mjs b/src/core/operations/GenerateImage.mjs index dd780f185..b5ad46197 100644 --- a/src/core/operations/GenerateImage.mjs +++ b/src/core/operations/GenerateImage.mjs @@ -25,7 +25,7 @@ class GenerateImage extends Operation { this.name = "Generate Image"; this.module = "Image"; - this.description = "Generate a Image using the input as pixel values."; + this.description = "Generate an Image using the input as pixel values."; this.infoURL = ""; this.inputType = "byteArray"; this.outputType = "ArrayBuffer";