mirror of
https://github.com/gchq/CyberChef
synced 2024-11-15 00:57:08 +00:00
Merge branch 'j433866-image-operations'
This commit is contained in:
commit
45c1c23e09
18 changed files with 1523 additions and 1 deletions
|
@ -27,6 +27,9 @@ class Ingredient {
|
|||
this.toggleValues = [];
|
||||
this.target = null;
|
||||
this.defaultIndex = 0;
|
||||
this.min = null;
|
||||
this.max = null;
|
||||
this.step = 1;
|
||||
|
||||
if (ingredientConfig) {
|
||||
this._parseConfig(ingredientConfig);
|
||||
|
@ -50,6 +53,9 @@ class Ingredient {
|
|||
this.toggleValues = ingredientConfig.toggleValues;
|
||||
this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null;
|
||||
this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0;
|
||||
this.min = ingredientConfig.min;
|
||||
this.max = ingredientConfig.max;
|
||||
this.step = ingredientConfig.step;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -184,6 +184,9 @@ class Operation {
|
|||
if (ing.disabled) conf.disabled = ing.disabled;
|
||||
if (ing.target) conf.target = ing.target;
|
||||
if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex;
|
||||
if (typeof ing.min === "number") conf.min = ing.min;
|
||||
if (typeof ing.max === "number") conf.max = ing.max;
|
||||
if (ing.step) conf.step = ing.step;
|
||||
return conf;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -361,7 +361,20 @@
|
|||
"Play Media",
|
||||
"Remove EXIF",
|
||||
"Extract EXIF",
|
||||
"Split Colour Channels"
|
||||
"Split Colour Channels",
|
||||
"Rotate Image",
|
||||
"Resize Image",
|
||||
"Blur Image",
|
||||
"Dither Image",
|
||||
"Invert Image",
|
||||
"Flip Image",
|
||||
"Crop Image",
|
||||
"Image Brightness / Contrast",
|
||||
"Image Opacity",
|
||||
"Image Filter",
|
||||
"Contain Image",
|
||||
"Cover Image",
|
||||
"Image Hue/Saturation/Lightness"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
102
src/core/operations/BlurImage.mjs
Normal file
102
src/core/operations/BlurImage.mjs
Normal file
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Blur Image operation
|
||||
*/
|
||||
class BlurImage extends Operation {
|
||||
|
||||
/**
|
||||
* BlurImage constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Blur Image";
|
||||
this.module = "Image";
|
||||
this.description = "Applies a blur effect to the image.<br><br>Gaussian blur is much slower than fast blur, but produces better results.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Gaussian_blur";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Amount",
|
||||
type: "number",
|
||||
value: 5,
|
||||
min: 1
|
||||
},
|
||||
{
|
||||
name: "Type",
|
||||
type: "option",
|
||||
value: ["Fast", "Gaussian"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [blurAmount, blurType] = args;
|
||||
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
switch (blurType){
|
||||
case "Fast":
|
||||
image.blur(blurAmount);
|
||||
break;
|
||||
case "Gaussian":
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Gaussian blurring image. This may take a while...");
|
||||
image.gaussian(blurAmount);
|
||||
break;
|
||||
}
|
||||
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error blurring image. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the blurred image using HTML for web apps
|
||||
*
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BlurImage;
|
143
src/core/operations/ContainImage.mjs
Normal file
143
src/core/operations/ContainImage.mjs
Normal file
|
@ -0,0 +1,143 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Contain Image operation
|
||||
*/
|
||||
class ContainImage extends Operation {
|
||||
|
||||
/**
|
||||
* ContainImage constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Contain Image";
|
||||
this.module = "Image";
|
||||
this.description = "Scales an image to the specified width and height, maintaining the aspect ratio. The image may be letterboxed.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Width",
|
||||
type: "number",
|
||||
value: 100,
|
||||
min: 1
|
||||
},
|
||||
{
|
||||
name: "Height",
|
||||
type: "number",
|
||||
value: 100,
|
||||
min: 1
|
||||
},
|
||||
{
|
||||
name: "Horizontal align",
|
||||
type: "option",
|
||||
value: [
|
||||
"Left",
|
||||
"Center",
|
||||
"Right"
|
||||
],
|
||||
defaultIndex: 1
|
||||
},
|
||||
{
|
||||
name: "Vertical align",
|
||||
type: "option",
|
||||
value: [
|
||||
"Top",
|
||||
"Middle",
|
||||
"Bottom"
|
||||
],
|
||||
defaultIndex: 1
|
||||
},
|
||||
{
|
||||
name: "Resizing algorithm",
|
||||
type: "option",
|
||||
value: [
|
||||
"Nearest Neighbour",
|
||||
"Bilinear",
|
||||
"Bicubic",
|
||||
"Hermite",
|
||||
"Bezier"
|
||||
],
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [width, height, hAlign, vAlign, alg] = args;
|
||||
|
||||
const resizeMap = {
|
||||
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": jimp.RESIZE_BICUBIC,
|
||||
"Hermite": jimp.RESIZE_HERMITE,
|
||||
"Bezier": jimp.RESIZE_BEZIER
|
||||
};
|
||||
|
||||
const alignMap = {
|
||||
"Left": jimp.HORIZONTAL_ALIGN_LEFT,
|
||||
"Center": jimp.HORIZONTAL_ALIGN_CENTER,
|
||||
"Right": jimp.HORIZONTAL_ALIGN_RIGHT,
|
||||
"Top": jimp.VERTICAL_ALIGN_TOP,
|
||||
"Middle": jimp.VERTICAL_ALIGN_MIDDLE,
|
||||
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
|
||||
};
|
||||
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Containing image...");
|
||||
image.contain(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error containing image. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the contained image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ContainImage;
|
143
src/core/operations/CoverImage.mjs
Normal file
143
src/core/operations/CoverImage.mjs
Normal file
|
@ -0,0 +1,143 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Cover Image operation
|
||||
*/
|
||||
class CoverImage extends Operation {
|
||||
|
||||
/**
|
||||
* CoverImage constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Cover Image";
|
||||
this.module = "Image";
|
||||
this.description = "Scales the image to the given width and height, keeping the aspect ratio. The image may be clipped.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Width",
|
||||
type: "number",
|
||||
value: 100,
|
||||
min: 1
|
||||
},
|
||||
{
|
||||
name: "Height",
|
||||
type: "number",
|
||||
value: 100,
|
||||
min: 1
|
||||
},
|
||||
{
|
||||
name: "Horizontal align",
|
||||
type: "option",
|
||||
value: [
|
||||
"Left",
|
||||
"Center",
|
||||
"Right"
|
||||
],
|
||||
defaultIndex: 1
|
||||
},
|
||||
{
|
||||
name: "Vertical align",
|
||||
type: "option",
|
||||
value: [
|
||||
"Top",
|
||||
"Middle",
|
||||
"Bottom"
|
||||
],
|
||||
defaultIndex: 1
|
||||
},
|
||||
{
|
||||
name: "Resizing algorithm",
|
||||
type: "option",
|
||||
value: [
|
||||
"Nearest Neighbour",
|
||||
"Bilinear",
|
||||
"Bicubic",
|
||||
"Hermite",
|
||||
"Bezier"
|
||||
],
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [width, height, hAlign, vAlign, alg] = args;
|
||||
|
||||
const resizeMap = {
|
||||
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": jimp.RESIZE_BICUBIC,
|
||||
"Hermite": jimp.RESIZE_HERMITE,
|
||||
"Bezier": jimp.RESIZE_BEZIER
|
||||
};
|
||||
|
||||
const alignMap = {
|
||||
"Left": jimp.HORIZONTAL_ALIGN_LEFT,
|
||||
"Center": jimp.HORIZONTAL_ALIGN_CENTER,
|
||||
"Right": jimp.HORIZONTAL_ALIGN_RIGHT,
|
||||
"Top": jimp.VERTICAL_ALIGN_TOP,
|
||||
"Middle": jimp.VERTICAL_ALIGN_MIDDLE,
|
||||
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
|
||||
};
|
||||
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Covering image...");
|
||||
image.cover(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error covering image. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the covered image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CoverImage;
|
144
src/core/operations/CropImage.mjs
Normal file
144
src/core/operations/CropImage.mjs
Normal file
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Crop Image operation
|
||||
*/
|
||||
class CropImage extends Operation {
|
||||
|
||||
/**
|
||||
* CropImage constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Crop Image";
|
||||
this.module = "Image";
|
||||
this.description = "Crops an image to the specified region, or automatically crops edges.<br><br><b><u>Autocrop</u></b><br>Automatically crops same-colour borders from the image.<br><br><u>Autocrop tolerance</u><br>A percentage value for the tolerance of colour difference between pixels.<br><br><u>Only autocrop frames</u><br>Only crop real frames (all sides must have the same border)<br><br><u>Symmetric autocrop</u><br>Force autocrop to be symmetric (top/bottom and left/right are cropped by the same amount)<br><br><u>Autocrop keep border</u><br>The number of pixels of border to leave around the image.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Cropping_(image)";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "X Position",
|
||||
type: "number",
|
||||
value: 0,
|
||||
min: 0
|
||||
},
|
||||
{
|
||||
name: "Y Position",
|
||||
type: "number",
|
||||
value: 0,
|
||||
min: 0
|
||||
},
|
||||
{
|
||||
name: "Width",
|
||||
type: "number",
|
||||
value: 10,
|
||||
min: 1
|
||||
},
|
||||
{
|
||||
name: "Height",
|
||||
type: "number",
|
||||
value: 10,
|
||||
min: 1
|
||||
},
|
||||
{
|
||||
name: "Autocrop",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Autocrop tolerance (%)",
|
||||
type: "number",
|
||||
value: 0.02,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 0.01
|
||||
},
|
||||
{
|
||||
name: "Only autocrop frames",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "Symmetric autocrop",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Autocrop keep border (px)",
|
||||
type: "number",
|
||||
value: 0,
|
||||
min: 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [xPos, yPos, width, height, autocrop, autoTolerance, autoFrames, autoSymmetric, autoBorder] = args;
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Cropping image...");
|
||||
if (autocrop) {
|
||||
image.autocrop({
|
||||
tolerance: (autoTolerance / 100),
|
||||
cropOnlyFrames: autoFrames,
|
||||
cropSymmetric: autoSymmetric,
|
||||
leaveBorder: autoBorder
|
||||
});
|
||||
} else {
|
||||
image.crop(xPos, yPos, width, height);
|
||||
}
|
||||
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error cropping image. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the cropped image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CropImage;
|
79
src/core/operations/DitherImage.mjs
Normal file
79
src/core/operations/DitherImage.mjs
Normal file
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Image Dither operation
|
||||
*/
|
||||
class DitherImage extends Operation {
|
||||
|
||||
/**
|
||||
* DitherImage constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Dither Image";
|
||||
this.module = "Image";
|
||||
this.description = "Apply a dither effect to an image.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Dither";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Applying dither to image...");
|
||||
image.dither565();
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error applying dither to image. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the dithered image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DitherImage;
|
94
src/core/operations/FlipImage.mjs
Normal file
94
src/core/operations/FlipImage.mjs
Normal file
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Flip Image operation
|
||||
*/
|
||||
class FlipImage extends Operation {
|
||||
|
||||
/**
|
||||
* FlipImage constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Flip Image";
|
||||
this.module = "Image";
|
||||
this.description = "Flips an image along its X or Y axis.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Axis",
|
||||
type: "option",
|
||||
value: ["Horizontal", "Vertical"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [flipAxis] = args;
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid input file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Flipping image...");
|
||||
switch (flipAxis){
|
||||
case "Horizontal":
|
||||
image.flip(true, false);
|
||||
break;
|
||||
case "Vertical":
|
||||
image.flip(false, true);
|
||||
break;
|
||||
}
|
||||
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error flipping image. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the flipped image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default FlipImage;
|
103
src/core/operations/ImageBrightnessContrast.mjs
Normal file
103
src/core/operations/ImageBrightnessContrast.mjs
Normal file
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Image Brightness / Contrast operation
|
||||
*/
|
||||
class ImageBrightnessContrast extends Operation {
|
||||
|
||||
/**
|
||||
* ImageBrightnessContrast constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Image Brightness / Contrast";
|
||||
this.module = "Image";
|
||||
this.description = "Adjust the brightness or contrast of an image.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Brightness",
|
||||
type: "number",
|
||||
value: 0,
|
||||
min: -100,
|
||||
max: 100
|
||||
},
|
||||
{
|
||||
name: "Contrast",
|
||||
type: "number",
|
||||
value: 0,
|
||||
min: -100,
|
||||
max: 100
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [brightness, contrast] = args;
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (brightness !== 0) {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Changing image brightness...");
|
||||
image.brightness(brightness / 100);
|
||||
}
|
||||
if (contrast !== 0) {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Changing image contrast...");
|
||||
image.contrast(contrast / 100);
|
||||
}
|
||||
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error adjusting image brightness or contrast. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ImageBrightnessContrast;
|
94
src/core/operations/ImageFilter.mjs
Normal file
94
src/core/operations/ImageFilter.mjs
Normal file
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Image Filter operation
|
||||
*/
|
||||
class ImageFilter extends Operation {
|
||||
|
||||
/**
|
||||
* ImageFilter constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Image Filter";
|
||||
this.module = "Image";
|
||||
this.description = "Applies a greyscale or sepia filter to an image.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Filter type",
|
||||
type: "option",
|
||||
value: [
|
||||
"Greyscale",
|
||||
"Sepia"
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [filterType] = args;
|
||||
if (!isImage(input)){
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Applying " + filterType.toLowerCase() + " filter to image...");
|
||||
if (filterType === "Greyscale") {
|
||||
image.greyscale();
|
||||
} else {
|
||||
image.sepia();
|
||||
}
|
||||
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error applying filter to image. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the blurred image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ImageFilter;
|
129
src/core/operations/ImageHueSaturationLightness.mjs
Normal file
129
src/core/operations/ImageHueSaturationLightness.mjs
Normal file
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Image Hue/Saturation/Lightness operation
|
||||
*/
|
||||
class ImageHueSaturationLightness extends Operation {
|
||||
|
||||
/**
|
||||
* ImageHueSaturationLightness constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Image Hue/Saturation/Lightness";
|
||||
this.module = "Image";
|
||||
this.description = "Adjusts the hue / saturation / lightness (HSL) values of an image.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Hue",
|
||||
type: "number",
|
||||
value: 0,
|
||||
min: -360,
|
||||
max: 360
|
||||
},
|
||||
{
|
||||
name: "Saturation",
|
||||
type: "number",
|
||||
value: 0,
|
||||
min: -100,
|
||||
max: 100
|
||||
},
|
||||
{
|
||||
name: "Lightness",
|
||||
type: "number",
|
||||
value: 0,
|
||||
min: -100,
|
||||
max: 100
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [hue, saturation, lightness] = args;
|
||||
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (hue !== 0) {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Changing image hue...");
|
||||
image.colour([
|
||||
{
|
||||
apply: "hue",
|
||||
params: [hue]
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (saturation !== 0) {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Changing image saturation...");
|
||||
image.colour([
|
||||
{
|
||||
apply: "saturate",
|
||||
params: [saturation]
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (lightness !== 0) {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Changing image lightness...");
|
||||
image.colour([
|
||||
{
|
||||
apply: "lighten",
|
||||
params: [lightness]
|
||||
}
|
||||
]);
|
||||
}
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error adjusting image hue / saturation / lightness. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
}
|
||||
|
||||
export default ImageHueSaturationLightness;
|
89
src/core/operations/ImageOpacity.mjs
Normal file
89
src/core/operations/ImageOpacity.mjs
Normal file
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Image Opacity operation
|
||||
*/
|
||||
class ImageOpacity extends Operation {
|
||||
|
||||
/**
|
||||
* ImageOpacity constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Image Opacity";
|
||||
this.module = "Image";
|
||||
this.description = "Adjust the opacity of an image.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Opacity (%)",
|
||||
type: "number",
|
||||
value: 100,
|
||||
min: 0,
|
||||
max: 100
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [opacity] = args;
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Changing image opacity...");
|
||||
image.opacity(opacity / 100);
|
||||
|
||||
const imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error changing image opacity. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ImageOpacity;
|
79
src/core/operations/InvertImage.mjs
Normal file
79
src/core/operations/InvertImage.mjs
Normal file
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Invert Image operation
|
||||
*/
|
||||
class InvertImage extends Operation {
|
||||
|
||||
/**
|
||||
* InvertImage constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Invert Image";
|
||||
this.module = "Image";
|
||||
this.description = "Invert the colours of an image.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid input file format.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Inverting image...");
|
||||
image.invert();
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error inverting image. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the inverted image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default InvertImage;
|
70
src/core/operations/NormaliseImage.mjs
Normal file
70
src/core/operations/NormaliseImage.mjs
Normal file
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Normalise Image operation
|
||||
*/
|
||||
class NormaliseImage extends Operation {
|
||||
|
||||
/**
|
||||
* NormaliseImage constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Normalise Image";
|
||||
this.module = "Image";
|
||||
this.description = "Normalise the image colours.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType= "html";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
const image = await jimp.read(Buffer.from(input));
|
||||
|
||||
image.normalize();
|
||||
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the normalised image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default NormaliseImage;
|
138
src/core/operations/ResizeImage.mjs
Normal file
138
src/core/operations/ResizeImage.mjs
Normal file
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Resize Image operation
|
||||
*/
|
||||
class ResizeImage extends Operation {
|
||||
|
||||
/**
|
||||
* ResizeImage constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Resize Image";
|
||||
this.module = "Image";
|
||||
this.description = "Resizes an image to the specified width and height values.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Image_scaling";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Width",
|
||||
type: "number",
|
||||
value: 100,
|
||||
min: 1
|
||||
},
|
||||
{
|
||||
name: "Height",
|
||||
type: "number",
|
||||
value: 100,
|
||||
min: 1
|
||||
},
|
||||
{
|
||||
name: "Unit type",
|
||||
type: "option",
|
||||
value: ["Pixels", "Percent"]
|
||||
},
|
||||
{
|
||||
name: "Maintain aspect ratio",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Resizing algorithm",
|
||||
type: "option",
|
||||
value: [
|
||||
"Nearest Neighbour",
|
||||
"Bilinear",
|
||||
"Bicubic",
|
||||
"Hermite",
|
||||
"Bezier"
|
||||
],
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
let width = args[0],
|
||||
height = args[1];
|
||||
const unit = args[2],
|
||||
aspect = args[3],
|
||||
resizeAlg = args[4];
|
||||
|
||||
const resizeMap = {
|
||||
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": jimp.RESIZE_BICUBIC,
|
||||
"Hermite": jimp.RESIZE_HERMITE,
|
||||
"Bezier": jimp.RESIZE_BEZIER
|
||||
};
|
||||
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (unit === "Percent") {
|
||||
width = image.getWidth() * (width / 100);
|
||||
height = image.getHeight() * (height / 100);
|
||||
}
|
||||
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Resizing image...");
|
||||
if (aspect) {
|
||||
image.scaleToFit(width, height, resizeMap[resizeAlg]);
|
||||
} else {
|
||||
image.resize(width, height, resizeMap[resizeAlg]);
|
||||
}
|
||||
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error resizing image. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the resized image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ResizeImage;
|
87
src/core/operations/RotateImage.mjs
Normal file
87
src/core/operations/RotateImage.mjs
Normal file
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import { toBase64 } from "../lib/Base64";
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Rotate Image operation
|
||||
*/
|
||||
class RotateImage extends Operation {
|
||||
|
||||
/**
|
||||
* RotateImage constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Rotate Image";
|
||||
this.module = "Image";
|
||||
this.description = "Rotates an image by the specified number of degrees.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Rotation amount (degrees)",
|
||||
type: "number",
|
||||
value: 90
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [degrees] = args;
|
||||
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(Buffer.from(input));
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
try {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Rotating image...");
|
||||
image.rotate(degrees);
|
||||
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
return [...imageBuffer];
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error rotating image. (${err})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the rotated image using HTML for web apps
|
||||
* @param {byteArray} data
|
||||
* @returns {html}
|
||||
*/
|
||||
present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = isImage(data);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
return `<img src="data:${type};base64,${toBase64(data)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default RotateImage;
|
|
@ -32,6 +32,9 @@ class HTMLIngredient {
|
|||
this.defaultIndex = config.defaultIndex || 0;
|
||||
this.toggleValues = config.toggleValues;
|
||||
this.id = "ing-" + this.app.nextIngId();
|
||||
this.min = (typeof config.min === "number") ? config.min : "";
|
||||
this.max = (typeof config.max === "number") ? config.max : "";
|
||||
this.step = config.step || 1;
|
||||
}
|
||||
|
||||
|
||||
|
@ -103,6 +106,9 @@ class HTMLIngredient {
|
|||
id="${this.id}"
|
||||
arg-name="${this.name}"
|
||||
value="${this.value}"
|
||||
min="${this.min}"
|
||||
max="${this.max}"
|
||||
step="${this.step}"
|
||||
${this.disabled ? "disabled" : ""}>
|
||||
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
|
||||
</div>`;
|
||||
|
|
Loading…
Reference in a new issue