Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Operation: Generate Image #683

Merged
merged 3 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/core/config/Categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@
"name": "Multimedia",
"ops": [
"Render Image",
"Generate Image",
"Play Media",
"Optical Character Recognition",
"Remove EXIF",
Expand Down
185 changes: 185 additions & 0 deletions src/core/operations/GenerateImage.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/**
* @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 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
*/
class GenerateImage extends Operation {

/**
* GenerateImage constructor
*/
constructor() {
super();

this.name = "Generate Image";
this.module = "Image";
this.description = "Generate an 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", "Bits"]
},
{
"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,
"Bits": 1/8,
};

const bytesPerPixel = bytePerPixelMap[mode];

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) => {});

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);
}
}
} 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);
}

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;