Text can be horizontally or vertically aligned, or the position can be manually specified. Variants of the Roboto font face are available in any size or colour.";
+ this.description =
+ "Adds text onto an image.
Note: GIF files are supported for input, but cannot be outputted.";
+ this.description =
+ "Converts an image between different formats. Supported formats:
Autocrop Automatically crops same-colour borders from the image.
Autocrop tolerance A percentage value for the tolerance of colour difference between pixels.
Only autocrop frames Only crop real frames (all sides must have the same border)
Symmetric autocrop Force autocrop to be symmetric (top/bottom and left/right are cropped by the same amount)
Autocrop keep border The number of pixels of border to leave around the image.";
+ this.description =
+ "Crops an image to the specified region, or automatically crops edges.
Autocrop Automatically crops same-colour borders from the image.
Autocrop tolerance A percentage value for the tolerance of colour difference between pixels.
Only autocrop frames Only crop real frames (all sides must have the same border)
Symmetric autocrop Force autocrop to be symmetric (top/bottom and left/right are cropped by the same amount)
e.g. The quoted-printable encoded string hello=20world becomes hello world";
+ this.description = "Converts QP-encoded text back to standard text. This format is a content transfer encoding common in email messages.
Normalise Image Attempts to normalise the image before parsing it to improve detection of a QR code.";
+ this.description =
+ "Reads an image file and attempts to detect and read a Quick Response (QR) code from the image.
Normalise Image Attempts to normalise the image before parsing it to improve detection of a QR code.";
this.infoURL = "https://wikipedia.org/wiki/QR_code";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
- "name": "Normalise image",
- "type": "boolean",
- "value": false
- }
+ name: "Normalise image",
+ type: "boolean",
+ value: false,
+ },
];
this.checks = [
{
- "pattern": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)",
- "flags": "",
- "args": [false],
- "useful": true
- }
+ pattern:
+ "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)",
+ flags: "",
+ args: [false],
+ useful: true,
+ },
];
}
@@ -54,9 +55,8 @@ class ParseQRCode extends Operation {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}
- return await parseQrCode(input, normalise);
+ return parseQrCode(input, normalise);
}
-
}
export default ParseQRCode;
diff --git a/src/core/operations/PseudoRandomIntegerGenerator.mjs b/src/core/operations/PseudoRandomIntegerGenerator.mjs
new file mode 100644
index 00000000..a2d83a92
--- /dev/null
+++ b/src/core/operations/PseudoRandomIntegerGenerator.mjs
@@ -0,0 +1,164 @@
+/**
+ * @author cktgh [chankaitung@gmail.com]
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import forge from "node-forge";
+import Utils, { isWorkerEnvironment } from "../Utils.mjs";
+import { DELIM_OPTIONS } from "../lib/Delim.mjs";
+
+/**
+ * Pseudo-Random Integer Generator operation
+ */
+class PseudoRandomIntegerGenerator extends Operation {
+
+ // in theory 2**53 is the max range, but we use Number.MAX_SAFE_INTEGER (2**53 - 1) as it is more consistent.
+ static MAX_RANGE = Number.MAX_SAFE_INTEGER;
+ // arbitrary choice
+ static BUFFER_SIZE = 1024;
+
+ /**
+ * PseudoRandomIntegerGenerator constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Pseudo-Random Integer Generator";
+ this.module = "Ciphers";
+ this.description = "A cryptographically-secure pseudo-random number generator (PRNG).
Generates random integers within a specified range using the browser's built-in crypto.getRandomValues() method if available.
The supported range of integers is from -(2^53 - 1) to (2^53 - 1).";
+ this.infoURL = "https://wikipedia.org/wiki/Pseudorandom_number_generator";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Number of Integers",
+ "type": "number",
+ "value": 1,
+ "min": 1
+ },
+ {
+ "name": "Min Value",
+ "type": "number",
+ "value": 0,
+ "min": Number.MIN_SAFE_INTEGER,
+ "max": Number.MAX_SAFE_INTEGER
+ },
+ {
+ "name": "Max Value",
+ "type": "number",
+ "value": 99,
+ "min": Number.MIN_SAFE_INTEGER,
+ "max": Number.MAX_SAFE_INTEGER
+ },
+ {
+ "name": "Delimiter",
+ "type": "option",
+ "value": DELIM_OPTIONS
+ },
+ {
+ "name": "Output",
+ "type": "option",
+ "value": ["Raw", "Hex", "Decimal"]
+ }
+ ];
+
+ // not using BigUint64Array to avoid BigInt handling overhead
+ this.randomBuffer = new Uint32Array(PseudoRandomIntegerGenerator.BUFFER_SIZE);
+ this.randomBufferOffset = PseudoRandomIntegerGenerator.BUFFER_SIZE;
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [numInts, minInt, maxInt, delimiter, outputType] = args;
+
+ if (minInt === null || maxInt === null) return "";
+
+ const min = Math.ceil(minInt);
+ const max = Math.floor(maxInt);
+ const delim = Utils.charRep(delimiter || "Space");
+
+ if (!Number.isSafeInteger(min) || !Number.isSafeInteger(max)) {
+ throw new OperationError("Min and Max must be between `-(2^53 - 1)` and `2^53 - 1`.");
+ }
+ if (min > max) {
+ throw new OperationError("Min cannot be larger than Max.");
+ }
+ const range = max - min + 1; // inclusive range
+ if (range > PseudoRandomIntegerGenerator.MAX_RANGE) {
+ throw new OperationError("Range between Min and Max cannot be larger than `2^53`");
+ }
+
+ // as large as possible while divisible by range
+ const rejectionThreshold = PseudoRandomIntegerGenerator.MAX_RANGE - (PseudoRandomIntegerGenerator.MAX_RANGE % range);
+ const output = [];
+ for (let i = 0; i < numInts; i++) {
+ const result = this._generateRandomValue(rejectionThreshold);
+ const intValue = min + (result % range);
+
+ switch (outputType) {
+ case "Hex":
+ output.push(intValue.toString(16));
+ break;
+ case "Decimal":
+ output.push(intValue.toString(10));
+ break;
+ case "Raw":
+ default:
+ output.push(Utils.chr(intValue));
+ }
+ }
+
+ if (outputType === "Raw") {
+ return output.join("");
+ }
+ return output.join(delim);
+ }
+
+ /**
+ * Generate a random value, result will be less than the rejection threshold (exclusive).
+ *
+ * @param {number} rejectionThreshold
+ * @returns {number}
+ */
+ _generateRandomValue(rejectionThreshold) {
+ let result;
+ do {
+ if (this.randomBufferOffset + 2 > this.randomBuffer.length) {
+ this._resetRandomBuffer();
+ }
+ // stitching a 53 bit number; not using BigUint64Array to avoid BigInt handling overhead
+ result = (this.randomBuffer[this.randomBufferOffset++] & 0x1f_ffff) * 0x1_0000_0000 +
+ this.randomBuffer[this.randomBufferOffset++];
+ } while (result >= rejectionThreshold);
+
+ return result;
+ }
+
+ /**
+ * Fill random buffer with new random values and rseet the offset.
+ */
+ _resetRandomBuffer() {
+ if (isWorkerEnvironment() && self.crypto) {
+ self.crypto.getRandomValues(this.randomBuffer);
+ } else {
+ const bytes = forge.random.getBytesSync(this.randomBuffer.length * 4);
+ for (let j = 0; j < this.randomBuffer.length; j++) {
+ this.randomBuffer[j] = (bytes.charCodeAt(j * 4) << 24) |
+ (bytes.charCodeAt(j * 4 + 1) << 16) |
+ (bytes.charCodeAt(j * 4 + 2) << 8) |
+ bytes.charCodeAt(j * 4 + 3);
+ }
+ }
+ this.randomBufferOffset = 0;
+ }
+
+}
+
+export default PseudoRandomIntegerGenerator;
diff --git a/src/core/operations/RC6Decrypt.mjs b/src/core/operations/RC6Decrypt.mjs
new file mode 100644
index 00000000..3185b632
--- /dev/null
+++ b/src/core/operations/RC6Decrypt.mjs
@@ -0,0 +1,119 @@
+/**
+ * @author Medjedtxm
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import Utils from "../Utils.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import { toHex } from "../lib/Hex.mjs";
+import { decryptRC6, getBlockSize, getDefaultRounds } from "../lib/RC6.mjs";
+
+/**
+ * RC6 Decrypt operation
+ */
+class RC6Decrypt extends Operation {
+
+ /**
+ * RC6Decrypt constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "RC6 Decrypt";
+ this.module = "Ciphers";
+ this.description = "RC6 is a symmetric key block cipher derived from RC5. It was designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin to meet the requirements of the AES competition, and was one of the five finalists.
RC6 is parameterised as RC6-w/r/b where w is word size in bits (any multiple of 8 from 8-256), r is the number of rounds (1-255), and b is the key length in bytes. The standard AES submission uses w=32, r=20. Common word sizes: 8, 16, 32 (standard), 64, 128.
IV: The Initialisation Vector should be 4*w/8 bytes (e.g. 16 bytes for w=32). If not entered, it will default to null bytes.
Padding: In CBC and ECB mode, the PKCS#7 padding scheme is used.";
+ this.infoURL = "https://wikipedia.org/wiki/RC6";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Key",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+ },
+ {
+ "name": "IV",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+ },
+ {
+ "name": "Mode",
+ "type": "option",
+ "value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
+ },
+ {
+ "name": "Input",
+ "type": "option",
+ "value": ["Hex", "Raw"]
+ },
+ {
+ "name": "Output",
+ "type": "option",
+ "value": ["Raw", "Hex"]
+ },
+ {
+ "name": "Padding",
+ "type": "option",
+ "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"]
+ },
+ {
+ "name": "Word Size",
+ "type": "number",
+ "value": 32,
+ "min": 8,
+ "max": 256,
+ "step": 8
+ },
+ {
+ "name": "Rounds",
+ "type": "number",
+ "value": 20,
+ "min": 1,
+ "max": 255
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const key = Utils.convertToByteArray(args[0].string, args[0].option),
+ iv = Utils.convertToByteArray(args[1].string, args[1].option),
+ [,, mode, inputType, outputType, padding, wordSize, rounds] = args;
+
+ // Validate word size
+ if (!Number.isInteger(wordSize) || wordSize < 8 || wordSize > 256 || wordSize % 8 !== 0)
+ throw new OperationError(`Invalid word size: ${wordSize}. Must be a multiple of 8 between 8 and 256.`);
+
+ const blockSize = getBlockSize(wordSize);
+ const defaultRounds = getDefaultRounds(wordSize);
+
+ if (iv.length !== blockSize && iv.length !== 0 && mode !== "ECB")
+ throw new OperationError(`Invalid IV length: ${iv.length} bytes
+
+RC6-${wordSize} uses an IV length of ${blockSize} bytes (${blockSize * 8} bits).
+Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
+
+ if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255)
+ throw new OperationError(`Invalid number of rounds: ${rounds}
+
+Rounds must be an integer between 1 and 255. Standard for w=${wordSize} is ${defaultRounds}.`);
+
+ // Default IV to null bytes if empty (like AES)
+ const actualIv = iv.length === 0 ? new Array(blockSize).fill(0) : iv;
+
+ input = Utils.convertToByteArray(input, inputType);
+ const output = decryptRC6(input, key, actualIv, mode, padding, rounds, wordSize);
+ return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
+ }
+
+}
+
+export default RC6Decrypt;
diff --git a/src/core/operations/RC6Encrypt.mjs b/src/core/operations/RC6Encrypt.mjs
new file mode 100644
index 00000000..d5ad6d7f
--- /dev/null
+++ b/src/core/operations/RC6Encrypt.mjs
@@ -0,0 +1,119 @@
+/**
+ * @author Medjedtxm
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import Utils from "../Utils.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import { toHex } from "../lib/Hex.mjs";
+import { encryptRC6, getBlockSize, getDefaultRounds } from "../lib/RC6.mjs";
+
+/**
+ * RC6 Encrypt operation
+ */
+class RC6Encrypt extends Operation {
+
+ /**
+ * RC6Encrypt constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "RC6 Encrypt";
+ this.module = "Ciphers";
+ this.description = "RC6 is a symmetric key block cipher derived from RC5. It was designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin to meet the requirements of the AES competition, and was one of the five finalists.
RC6 is parameterised as RC6-w/r/b where w is word size in bits (any multiple of 8 from 8-256), r is the number of rounds (1-255), and b is the key length in bytes. The standard AES submission uses w=32, r=20. Common word sizes: 8, 16, 32 (standard), 64, 128.
IV: The Initialisation Vector should be 4*w/8 bytes (e.g. 16 bytes for w=32). If not entered, it will default to null bytes.
Padding: In CBC and ECB mode, the PKCS#7 padding scheme is used.";
+ this.infoURL = "https://wikipedia.org/wiki/RC6";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Key",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+ },
+ {
+ "name": "IV",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+ },
+ {
+ "name": "Mode",
+ "type": "option",
+ "value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
+ },
+ {
+ "name": "Input",
+ "type": "option",
+ "value": ["Raw", "Hex"]
+ },
+ {
+ "name": "Output",
+ "type": "option",
+ "value": ["Hex", "Raw"]
+ },
+ {
+ "name": "Padding",
+ "type": "option",
+ "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"]
+ },
+ {
+ "name": "Word Size",
+ "type": "number",
+ "value": 32,
+ "min": 8,
+ "max": 256,
+ "step": 8
+ },
+ {
+ "name": "Rounds",
+ "type": "number",
+ "value": 20,
+ "min": 1,
+ "max": 255
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const key = Utils.convertToByteArray(args[0].string, args[0].option),
+ iv = Utils.convertToByteArray(args[1].string, args[1].option),
+ [,, mode, inputType, outputType, padding, wordSize, rounds] = args;
+
+ // Validate word size
+ if (!Number.isInteger(wordSize) || wordSize < 8 || wordSize > 256 || wordSize % 8 !== 0)
+ throw new OperationError(`Invalid word size: ${wordSize}. Must be a multiple of 8 between 8 and 256.`);
+
+ const blockSize = getBlockSize(wordSize);
+ const defaultRounds = getDefaultRounds(wordSize);
+
+ if (iv.length !== blockSize && iv.length !== 0 && mode !== "ECB")
+ throw new OperationError(`Invalid IV length: ${iv.length} bytes
+
+RC6-${wordSize} uses an IV length of ${blockSize} bytes (${blockSize * 8} bits).
+Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
+
+ if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255)
+ throw new OperationError(`Invalid number of rounds: ${rounds}
+
+Rounds must be an integer between 1 and 255. Standard for w=${wordSize} is ${defaultRounds}.`);
+
+ // Default IV to null bytes if empty (like AES)
+ const actualIv = iv.length === 0 ? new Array(blockSize).fill(0) : iv;
+
+ input = Utils.convertToByteArray(input, inputType);
+ const output = encryptRC6(input, key, actualIv, mode, padding, rounds, wordSize);
+ return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
+ }
+
+}
+
+export default RC6Encrypt;
diff --git a/src/core/operations/RandomizeColourPalette.mjs b/src/core/operations/RandomizeColourPalette.mjs
index fa8fa59e..186023c2 100644
--- a/src/core/operations/RandomizeColourPalette.mjs
+++ b/src/core/operations/RandomizeColourPalette.mjs
@@ -10,13 +10,12 @@ import Utils from "../Utils.mjs";
import { isImage } from "../lib/FileType.mjs";
import { runHash } from "../lib/Hash.mjs";
import { toBase64 } from "../lib/Base64.mjs";
-import Jimp from "jimp/es/index.js";
+import { Jimp } from "jimp";
/**
* Randomize Colour Palette operation
*/
class RandomizeColourPalette extends Operation {
-
/**
* RandomizeColourPalette constructor
*/
@@ -25,7 +24,8 @@ class RandomizeColourPalette extends Operation {
this.name = "Randomize Colour Palette";
this.module = "Image";
- this.description = "Randomizes each colour in an image's colour palette. This can often reveal text or symbols that were previously a very similar colour to their surroundings, a technique sometimes used in Steganography.";
+ this.description =
+ "Randomizes each colour in an image's colour palette. This can often reveal text or symbols that were previously a very similar colour to their surroundings, a technique sometimes used in Steganography.";
this.infoURL = "https://wikipedia.org/wiki/Indexed_color";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
@@ -34,8 +34,8 @@ class RandomizeColourPalette extends Operation {
{
name: "Seed",
type: "string",
- value: ""
- }
+ value: "",
+ },
];
}
@@ -45,23 +45,24 @@ class RandomizeColourPalette extends Operation {
* @returns {ArrayBuffer}
*/
async run(input, args) {
- if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
+ if (!isImage(input))
+ throw new OperationError("Please enter a valid image file.");
- const seed = args[0] || (Math.random().toString().substr(2)),
+ const seed = args[0] || Math.random().toString().substr(2),
parsedImage = await Jimp.read(input),
width = parsedImage.bitmap.width,
height = parsedImage.bitmap.height;
let rgbString, rgbHash, rgbHex;
- parsedImage.scan(0, 0, width, height, function(x, y, idx) {
- rgbString = this.bitmap.data.slice(idx, idx+3).join(".");
+ parsedImage.scan(0, 0, width, height, function (x, y, idx) {
+ rgbString = this.bitmap.data.slice(idx, idx + 3).join(".");
rgbHash = runHash("md5", Utils.strToArrayBuffer(seed + rgbString));
rgbHex = rgbHash.substr(0, 6) + "ff";
parsedImage.setPixelColor(parseInt(rgbHex, 16), x, y);
});
- const imageBuffer = await parsedImage.getBufferAsync(Jimp.AUTO);
+ const imageBuffer = await parsedImage.getBuffer(parsedImage.mime);
return new Uint8Array(imageBuffer).buffer;
}
@@ -77,7 +78,6 @@ class RandomizeColourPalette extends Operation {
return ``;
}
-
}
export default RandomizeColourPalette;
diff --git a/src/core/operations/ResizeImage.mjs b/src/core/operations/ResizeImage.mjs
index 2d2af045..bec07c4e 100644
--- a/src/core/operations/ResizeImage.mjs
+++ b/src/core/operations/ResizeImage.mjs
@@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
import { isImage } from "../lib/FileType.mjs";
import { toBase64 } from "../lib/Base64.mjs";
import { isWorkerEnvironment } from "../Utils.mjs";
-import Jimp from "jimp/es/index.js";
+import { Jimp, JimpMime, ResizeStrategy } from "jimp";
/**
* Resize Image operation
*/
class ResizeImage extends Operation {
-
/**
* ResizeImage constructor
*/
@@ -24,7 +23,8 @@ class ResizeImage extends Operation {
this.name = "Resize Image";
this.module = "Image";
- this.description = "Resizes an image to the specified width and height values.";
+ this.description =
+ "Resizes an image to the specified width and height values.";
this.infoURL = "https://wikipedia.org/wiki/Image_scaling";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
@@ -34,23 +34,23 @@ class ResizeImage extends Operation {
name: "Width",
type: "number",
value: 100,
- min: 1
+ min: 1,
},
{
name: "Height",
type: "number",
value: 100,
- min: 1
+ min: 1,
},
{
name: "Unit type",
type: "option",
- value: ["Pixels", "Percent"]
+ value: ["Pixels", "Percent"],
},
{
name: "Maintain aspect ratio",
type: "boolean",
- value: false
+ value: false,
},
{
name: "Resizing algorithm",
@@ -60,10 +60,10 @@ class ResizeImage extends Operation {
"Bilinear",
"Bicubic",
"Hermite",
- "Bezier"
+ "Bezier",
],
- defaultIndex: 1
- }
+ defaultIndex: 1,
+ },
];
}
@@ -80,11 +80,11 @@ class ResizeImage extends Operation {
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
+ "Nearest Neighbour": ResizeStrategy.NEAREST_NEIGHBOR,
+ Bilinear: ResizeStrategy.BILINEAR,
+ Bicubic: ResizeStrategy.BICUBIC,
+ Hermite: ResizeStrategy.HERMITE,
+ Bezier: ResizeStrategy.BEZIER,
};
if (!isImage(input)) {
@@ -99,23 +99,31 @@ class ResizeImage extends Operation {
}
try {
if (unit === "Percent") {
- width = image.getWidth() * (width / 100);
- height = image.getHeight() * (height / 100);
+ width = image.width * (width / 100);
+ height = image.height * (height / 100);
}
if (isWorkerEnvironment())
self.sendStatusMessage("Resizing image...");
if (aspect) {
- image.scaleToFit(width, height, resizeMap[resizeAlg]);
+ image.scaleToFit({
+ w: width,
+ h: height,
+ mode: resizeMap[resizeAlg],
+ });
} else {
- image.resize(width, height, resizeMap[resizeAlg]);
+ image.resize({
+ w: width,
+ h: height,
+ mode: resizeMap[resizeAlg],
+ });
}
let imageBuffer;
- if (image.getMIME() === "image/gif") {
- imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
+ if (image.mime === "image/gif") {
+ imageBuffer = await image.getBuffer(JimpMime.png);
} else {
- imageBuffer = await image.getBufferAsync(Jimp.AUTO);
+ imageBuffer = await image.getBuffer(image.mime);
}
return imageBuffer.buffer;
} catch (err) {
@@ -139,7 +147,6 @@ class ResizeImage extends Operation {
return ``;
}
-
}
export default ResizeImage;
diff --git a/src/core/operations/RotateImage.mjs b/src/core/operations/RotateImage.mjs
index 894ec785..5a96ffab 100644
--- a/src/core/operations/RotateImage.mjs
+++ b/src/core/operations/RotateImage.mjs
@@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
import { isImage } from "../lib/FileType.mjs";
import { toBase64 } from "../lib/Base64.mjs";
import { isWorkerEnvironment } from "../Utils.mjs";
-import Jimp from "jimp/es/index.js";
+import { Jimp, JimpMime } from "jimp";
/**
* Rotate Image operation
*/
class RotateImage extends Operation {
-
/**
* RotateImage constructor
*/
@@ -24,7 +23,8 @@ class RotateImage extends Operation {
this.name = "Rotate Image";
this.module = "Image";
- this.description = "Rotates an image by the specified number of degrees.";
+ this.description =
+ "Rotates an image by the specified number of degrees.";
this.infoURL = "";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
@@ -33,8 +33,8 @@ class RotateImage extends Operation {
{
name: "Rotation amount (degrees)",
type: "number",
- value: 90
- }
+ value: 90,
+ },
];
}
@@ -62,10 +62,10 @@ class RotateImage extends Operation {
image.rotate(degrees);
let imageBuffer;
- if (image.getMIME() === "image/gif") {
- imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
+ if (image.mime === "image/gif") {
+ imageBuffer = await image.getBuffer(JimpMime.png);
} else {
- imageBuffer = await image.getBufferAsync(Jimp.AUTO);
+ imageBuffer = await image.getBuffer(image.mime);
}
return imageBuffer.buffer;
} catch (err) {
@@ -89,7 +89,6 @@ class RotateImage extends Operation {
return ``;
}
-
}
export default RotateImage;
diff --git a/src/core/operations/SQLBeautify.mjs b/src/core/operations/SQLBeautify.mjs
index 0f3d2e3c..2171f7fc 100644
--- a/src/core/operations/SQLBeautify.mjs
+++ b/src/core/operations/SQLBeautify.mjs
@@ -3,8 +3,7 @@
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
-
-import vkbeautify from "vkbeautify";
+import { format } from "sql-formatter";
import Operation from "../Operation.mjs";
/**
@@ -39,7 +38,26 @@ class SQLBeautify extends Operation {
*/
run(input, args) {
const indentStr = args[0];
- return vkbeautify.sql(input, indentStr);
+ // Extract and replace bind variables like :Bind1 with __BIND_0__
+ const bindRegex = /:\w+/g;
+ const bindMap = {};
+ let bindCounter=0;
+ const placeholderInput = input.replace(bindRegex, (match) => {
+ const placeholder = `__BIND_${bindCounter++}__`;
+ bindMap[placeholder] = match;
+ return placeholder;
+ });
+ // Format the SQL with chosen options
+ let formatted= format(placeholderInput, {
+ language: "mysql", // Use MySQL as the default dialect for better compatibility with real-world SQL
+ useTabs: indentStr==="\t", // true if tab, false if spaces
+ tabWidth: indentStr.length || 4, // fallback if empty
+ indentStyle: "standard" // fine for most SQL
+ });
+ // Replace placeholders back with original bind variables
+ formatted = formatted.replace(/__BIND_\d+__/g, match => bindMap[match] || match);
+
+ return formatted;
}
}
diff --git a/src/core/operations/SharpenImage.mjs b/src/core/operations/SharpenImage.mjs
index 5cf6b606..1f5461f2 100644
--- a/src/core/operations/SharpenImage.mjs
+++ b/src/core/operations/SharpenImage.mjs
@@ -8,15 +8,13 @@ import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { isImage } from "../lib/FileType.mjs";
import { toBase64 } from "../lib/Base64.mjs";
-import { gaussianBlur } from "../lib/ImageManipulation.mjs";
import { isWorkerEnvironment } from "../Utils.mjs";
-import Jimp from "jimp/es/index.js";
+import { Jimp, JimpMime } from "jimp";
/**
* Sharpen Image operation
*/
class SharpenImage extends Operation {
-
/**
* SharpenImage constructor
*/
@@ -35,22 +33,22 @@ class SharpenImage extends Operation {
name: "Radius",
type: "number",
value: 2,
- min: 1
+ min: 1,
},
{
name: "Amount",
type: "number",
value: 1,
min: 0,
- step: 0.1
+ step: 0.1,
},
{
name: "Threshold",
type: "number",
value: 10,
min: 0,
- max: 100
- }
+ max: 100,
+ },
];
}
@@ -79,67 +77,102 @@ class SharpenImage extends Operation {
const blurMask = image.clone();
if (isWorkerEnvironment())
- self.sendStatusMessage("Sharpening image... (Blurring cloned image)");
- const blurImage = gaussianBlur(image.clone(), radius);
-
+ self.sendStatusMessage(
+ "Sharpening image... (Blurring cloned image)",
+ );
+ const blurImage = image.clone().gaussian(radius);
if (isWorkerEnvironment())
- self.sendStatusMessage("Sharpening image... (Creating unsharp mask)");
- blurMask.scan(0, 0, blurMask.bitmap.width, blurMask.bitmap.height, function(x, y, idx) {
- const blurRed = blurImage.bitmap.data[idx];
- const blurGreen = blurImage.bitmap.data[idx + 1];
- const blurBlue = blurImage.bitmap.data[idx + 2];
+ self.sendStatusMessage(
+ "Sharpening image... (Creating unsharp mask)",
+ );
+ blurMask.scan(
+ 0,
+ 0,
+ blurMask.bitmap.width,
+ blurMask.bitmap.height,
+ function (x, y, idx) {
+ const blurRed = blurImage.bitmap.data[idx];
+ const blurGreen = blurImage.bitmap.data[idx + 1];
+ const blurBlue = blurImage.bitmap.data[idx + 2];
- const normalRed = this.bitmap.data[idx];
- const normalGreen = this.bitmap.data[idx + 1];
- const normalBlue = this.bitmap.data[idx + 2];
+ const normalRed = this.bitmap.data[idx];
+ const normalGreen = this.bitmap.data[idx + 1];
+ const normalBlue = this.bitmap.data[idx + 2];
- // Subtract blurred pixel value from normal image
- this.bitmap.data[idx] = (normalRed > blurRed) ? normalRed - blurRed : 0;
- this.bitmap.data[idx + 1] = (normalGreen > blurGreen) ? normalGreen - blurGreen : 0;
- this.bitmap.data[idx + 2] = (normalBlue > blurBlue) ? normalBlue - blurBlue : 0;
- });
+ // Subtract blurred pixel value from normal image
+ this.bitmap.data[idx] =
+ normalRed > blurRed ? normalRed - blurRed : 0;
+ this.bitmap.data[idx + 1] =
+ normalGreen > blurGreen ? normalGreen - blurGreen : 0;
+ this.bitmap.data[idx + 2] =
+ normalBlue > blurBlue ? normalBlue - blurBlue : 0;
+ },
+ );
if (isWorkerEnvironment())
- self.sendStatusMessage("Sharpening image... (Merging with unsharp mask)");
- image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y, idx) {
- let maskRed = blurMask.bitmap.data[idx];
- let maskGreen = blurMask.bitmap.data[idx + 1];
- let maskBlue = blurMask.bitmap.data[idx + 2];
+ self.sendStatusMessage(
+ "Sharpening image... (Merging with unsharp mask)",
+ );
+ image.scan(
+ 0,
+ 0,
+ image.bitmap.width,
+ image.bitmap.height,
+ function (x, y, idx) {
+ let maskRed = blurMask.bitmap.data[idx];
+ let maskGreen = blurMask.bitmap.data[idx + 1];
+ let maskBlue = blurMask.bitmap.data[idx + 2];
- const normalRed = this.bitmap.data[idx];
- const normalGreen = this.bitmap.data[idx + 1];
- const normalBlue = this.bitmap.data[idx + 2];
+ const normalRed = this.bitmap.data[idx];
+ const normalGreen = this.bitmap.data[idx + 1];
+ const normalBlue = this.bitmap.data[idx + 2];
- // Calculate luminance
- const maskLuminance = (0.2126 * maskRed + 0.7152 * maskGreen + 0.0722 * maskBlue);
- const normalLuminance = (0.2126 * normalRed + 0.7152 * normalGreen + 0.0722 * normalBlue);
+ // Calculate luminance
+ const maskLuminance =
+ 0.2126 * maskRed +
+ 0.7152 * maskGreen +
+ 0.0722 * maskBlue;
+ const normalLuminance =
+ 0.2126 * normalRed +
+ 0.7152 * normalGreen +
+ 0.0722 * normalBlue;
- let luminanceDiff;
- if (maskLuminance > normalLuminance) {
- luminanceDiff = maskLuminance - normalLuminance;
- } else {
- luminanceDiff = normalLuminance - maskLuminance;
- }
+ let luminanceDiff;
+ if (maskLuminance > normalLuminance) {
+ luminanceDiff = maskLuminance - normalLuminance;
+ } else {
+ luminanceDiff = normalLuminance - maskLuminance;
+ }
- // Scale mask colours by amount
- maskRed = maskRed * amount;
- maskGreen = maskGreen * amount;
- maskBlue = maskBlue * amount;
+ // Scale mask colours by amount
+ maskRed = maskRed * amount;
+ maskGreen = maskGreen * amount;
+ maskBlue = maskBlue * amount;
- // Only change pixel value if the difference is higher than threshold
- if ((luminanceDiff / 255) * 100 >= threshold) {
- this.bitmap.data[idx] = (normalRed + maskRed) <= 255 ? normalRed + maskRed : 255;
- this.bitmap.data[idx + 1] = (normalGreen + maskGreen) <= 255 ? normalGreen + maskGreen : 255;
- this.bitmap.data[idx + 2] = (normalBlue + maskBlue) <= 255 ? normalBlue + maskBlue : 255;
- }
- });
+ // Only change pixel value if the difference is higher than threshold
+ if ((luminanceDiff / 255) * 100 >= threshold) {
+ this.bitmap.data[idx] =
+ normalRed + maskRed <= 255 ?
+ normalRed + maskRed :
+ 255;
+ this.bitmap.data[idx + 1] =
+ normalGreen + maskGreen <= 255 ?
+ normalGreen + maskGreen :
+ 255;
+ this.bitmap.data[idx + 2] =
+ normalBlue + maskBlue <= 255 ?
+ normalBlue + maskBlue :
+ 255;
+ }
+ },
+ );
let imageBuffer;
- if (image.getMIME() === "image/gif") {
- imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
+ if (image.mime === "image/gif") {
+ imageBuffer = await image.getBuffer(JimpMime.png);
} else {
- imageBuffer = await image.getBufferAsync(Jimp.AUTO);
+ imageBuffer = await image.getBuffer(image.mime);
}
return imageBuffer.buffer;
} catch (err) {
@@ -163,7 +196,6 @@ class SharpenImage extends Operation {
return ``;
}
-
}
export default SharpenImage;
diff --git a/src/core/operations/SplitColourChannels.mjs b/src/core/operations/SplitColourChannels.mjs
index d5a26a2d..3a33b78c 100644
--- a/src/core/operations/SplitColourChannels.mjs
+++ b/src/core/operations/SplitColourChannels.mjs
@@ -7,14 +7,13 @@
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
-import {isImage} from "../lib/FileType.mjs";
-import Jimp from "jimp/es/index.js";
+import { isImage } from "../lib/FileType.mjs";
+import { Jimp, JimpMime } from "jimp";
/**
* Split Colour Channels operation
*/
class SplitColourChannels extends Operation {
-
/**
* SplitColourChannels constructor
*/
@@ -23,7 +22,8 @@ class SplitColourChannels extends Operation {
this.name = "Split Colour Channels";
this.module = "Image";
- this.description = "Splits the given image into its red, green and blue colour channels.";
+ this.description =
+ "Splits the given image into its red, green and blue colour channels.";
this.infoURL = "https://wikipedia.org/wiki/Channel_(digital_image)";
this.inputType = "ArrayBuffer";
this.outputType = "List";
@@ -48,26 +48,44 @@ class SplitColourChannels extends Operation {
const split = parsedImage
.clone()
.color([
- {apply: "blue", params: [-255]},
- {apply: "green", params: [-255]}
+ { apply: "blue", params: [-255] },
+ { apply: "green", params: [-255] },
])
- .getBufferAsync(Jimp.MIME_PNG);
- resolve(new File([new Uint8Array((await split).values())], "red.png", {type: "image/png"}));
+ .getBuffer(JimpMime.png);
+ resolve(
+ new File(
+ [new Uint8Array((await split).values())],
+ "red.png",
+ { type: "image/png" },
+ ),
+ );
} catch (err) {
- reject(new OperationError(`Could not split red channel: ${err}`));
+ reject(
+ new OperationError(`Could not split red channel: ${err}`),
+ );
}
});
const green = new Promise(async (resolve, reject) => {
try {
- const split = parsedImage.clone()
+ const split = parsedImage
+ .clone()
.color([
- {apply: "red", params: [-255]},
- {apply: "blue", params: [-255]},
- ]).getBufferAsync(Jimp.MIME_PNG);
- resolve(new File([new Uint8Array((await split).values())], "green.png", {type: "image/png"}));
+ { apply: "red", params: [-255] },
+ { apply: "blue", params: [-255] },
+ ])
+ .getBuffer(JimpMime.png);
+ resolve(
+ new File(
+ [new Uint8Array((await split).values())],
+ "green.png",
+ { type: "image/png" },
+ ),
+ );
} catch (err) {
- reject(new OperationError(`Could not split green channel: ${err}`));
+ reject(
+ new OperationError(`Could not split green channel: ${err}`),
+ );
}
});
@@ -75,12 +93,21 @@ class SplitColourChannels extends Operation {
try {
const split = parsedImage
.color([
- {apply: "red", params: [-255]},
- {apply: "green", params: [-255]},
- ]).getBufferAsync(Jimp.MIME_PNG);
- resolve(new File([new Uint8Array((await split).values())], "blue.png", {type: "image/png"}));
+ { apply: "red", params: [-255] },
+ { apply: "green", params: [-255] },
+ ])
+ .getBuffer(JimpMime.png);
+ resolve(
+ new File(
+ [new Uint8Array((await split).values())],
+ "blue.png",
+ { type: "image/png" },
+ ),
+ );
} catch (err) {
- reject(new OperationError(`Could not split blue channel: ${err}`));
+ reject(
+ new OperationError(`Could not split blue channel: ${err}`),
+ );
}
});
@@ -96,7 +123,6 @@ class SplitColourChannels extends Operation {
async present(files) {
return await Utils.displayFilesAsHTML(files);
}
-
}
export default SplitColourChannels;
diff --git a/src/core/operations/TextIntegerConverter.mjs b/src/core/operations/TextIntegerConverter.mjs
new file mode 100644
index 00000000..4e740100
--- /dev/null
+++ b/src/core/operations/TextIntegerConverter.mjs
@@ -0,0 +1,123 @@
+/**
+ * @author p-leriche [philip.leriche@cantab.net]
+ * @copyright Crown Copyright 2025
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+
+/* ---------- helper functions ---------- */
+
+/**
+ * Convert text to BigInt (big-endian byte interpretation)
+ */
+function textToBigInt(text) {
+ if (text.length === 0) return 0n;
+
+ let result = 0n;
+ for (let i = 0; i < text.length; i++) {
+ const charCode = BigInt(text.charCodeAt(i));
+ if (charCode > 255n) {
+ throw new OperationError(
+ `Character at position ${i} exceeds Latin-1 range (0-255).\n` +
+ "Only ASCII and Latin-1 characters are supported.");
+ }
+ result = (result << 8n) | charCode;
+ }
+ return result;
+}
+
+/**
+ * Convert BigInt to text (big-endian byte interpretation)
+ */
+function bigIntToText(value) {
+ if (value === 0n) return "";
+
+ const bytes = [];
+ let num = value;
+
+ while (num > 0n) {
+ bytes.unshift(Number(num & 0xFFn));
+ num >>= 8n;
+ }
+
+ return String.fromCharCode(...bytes);
+}
+
+/* ---------- operation class ---------- */
+
+/**
+ * Text/Integer Converter operation
+ */
+class TextIntegerConverter extends Operation {
+ /**
+ * TextIntegerConverter constructor
+ */
+ constructor() {
+ super();
+
+ this.description =
+ "Converts between text strings and large integers (decimal or hexadecimal).
" +
+ "Text is interpreted as a big-endian sequence of character codes. For example: " +
+ "ABC is 0x414243 (hex) is 4276803 (decimal) " +
+ "Input format detection: " +
+ "Decimal: digits 0-9 only " +
+ "Hexadecimal: 0x... prefix " +
+ "Quoted or unquoted text: treated as string
" +
+ "Character limitations: " +
+ "Text input may only contain ASCII and Latin-1 characters (code point < 256). " +
+ "Multi-byte Unicode characters will generate an error.
Bech32m (BIP-0350) is an updated version that fixes a weakness in the original Bech32 checksum and is used for Bitcoin Taproot addresses.
The Human-Readable Part (HRP) identifies the network or purpose (e.g., 'bc' for Bitcoin mainnet, 'tb' for testnet, 'age' for AGE encryption keys).
Maximum output length is 90 characters as per specification.";
+ this.infoURL = "https://wikipedia.org/wiki/Bech32";
+ this.inputType = "ArrayBuffer";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Human-Readable Part (HRP)",
+ "type": "string",
+ "value": "bc"
+ },
+ {
+ "name": "Encoding",
+ "type": "option",
+ "value": ["Bech32", "Bech32m"]
+ },
+ {
+ "name": "Input Format",
+ "type": "option",
+ "value": ["Raw bytes", "Hex"]
+ },
+ {
+ "name": "Mode",
+ "type": "option",
+ "value": ["Generic", "Bitcoin SegWit"]
+ },
+ {
+ "name": "Witness Version",
+ "type": "number",
+ "value": 0,
+ "hint": "SegWit witness version (0-16). Only used in Bitcoin SegWit mode."
+ }
+ ];
+ }
+
+ /**
+ * @param {ArrayBuffer} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const hrp = args[0];
+ const encoding = args[1];
+ const inputFormat = args[2];
+ const mode = args[3];
+ const witnessVersion = args[4];
+
+ let inputArray;
+ if (inputFormat === "Hex") {
+ // Convert hex string to bytes
+ const hexStr = new TextDecoder().decode(new Uint8Array(input)).replace(/\s/g, "");
+ inputArray = fromHex(hexStr);
+ } else {
+ inputArray = new Uint8Array(input);
+ }
+
+ if (mode === "Bitcoin SegWit") {
+ // Prepend witness version to the input data
+ const withVersion = new Uint8Array(inputArray.length + 1);
+ withVersion[0] = witnessVersion;
+ withVersion.set(inputArray, 1);
+ return encode(hrp, withVersion, encoding, true);
+ }
+
+ return encode(hrp, inputArray, encoding, false);
+ }
+
+}
+
+export default ToBech32;
diff --git a/src/core/operations/ToQuotedPrintable.mjs b/src/core/operations/ToQuotedPrintable.mjs
index 9db5c5a5..2ea204f9 100644
--- a/src/core/operations/ToQuotedPrintable.mjs
+++ b/src/core/operations/ToQuotedPrintable.mjs
@@ -23,7 +23,7 @@ class ToQuotedPrintable extends Operation {
this.name = "To Quoted Printable";
this.module = "Default";
- this.description = "Quoted-Printable, or QP encoding, is an encoding using printable ASCII characters (alphanumeric and the equals sign '=') to transmit 8-bit data over a 7-bit data path or, generally, over a medium which is not 8-bit clean. It is defined as a MIME content transfer encoding for use in e-mail.
QP works by using the equals sign '=' as an escape character. It also limits line length to 76, as some software has limits on line length.";
+ this.description = "Quoted-Printable, or QP encoding, is an encoding using printable ASCII characters (alphanumeric and the equals sign '=') to transmit 8-bit data over a 7-bit data path or, generally, over a medium which is not 8-bit clean. It is defined as a MIME content transfer encoding for use in email.
QP works by using the equals sign '=' as an escape character. It also limits line length to 76, as some software has limits on line length.";
this.infoURL = "https://wikipedia.org/wiki/Quoted-printable";
this.inputType = "ArrayBuffer";
this.outputType = "string";
diff --git a/src/core/operations/UnescapeUnicodeCharacters.mjs b/src/core/operations/UnescapeUnicodeCharacters.mjs
index 5bb0e5ac..02d16662 100644
--- a/src/core/operations/UnescapeUnicodeCharacters.mjs
+++ b/src/core/operations/UnescapeUnicodeCharacters.mjs
@@ -30,6 +30,23 @@ class UnescapeUnicodeCharacters extends Operation {
"value": ["\\u", "%u", "U+"]
}
];
+ this.checks = [
+ {
+ pattern: "\\\\u(?:[\\da-f]{4,6})",
+ flags: "i",
+ args: ["\\u"]
+ },
+ {
+ pattern: "%u(?:[\\da-f]{4,6})",
+ flags: "i",
+ args: ["%u"]
+ },
+ {
+ pattern: "U\\+(?:[\\da-f]{4,6})",
+ flags: "i",
+ args: ["U+"]
+ }
+ ];
}
/**
diff --git a/src/core/operations/ViewBitPlane.mjs b/src/core/operations/ViewBitPlane.mjs
index 8c93f16c..3740c10d 100644
--- a/src/core/operations/ViewBitPlane.mjs
+++ b/src/core/operations/ViewBitPlane.mjs
@@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import { isImage } from "../lib/FileType.mjs";
import { toBase64 } from "../lib/Base64.mjs";
-import Jimp from "jimp/es/index.js";
+import { Jimp } from "jimp";
/**
* View Bit Plane operation
*/
class ViewBitPlane extends Operation {
-
/**
* ViewBitPlane constructor
*/
@@ -24,7 +23,8 @@ class ViewBitPlane extends Operation {
this.name = "View Bit Plane";
this.module = "Image";
- this.description = "Extracts and displays a bit plane of any given image. These show only a single bit from each pixel, and can be used to hide messages in Steganography.";
+ this.description =
+ "Extracts and displays a bit plane of any given image. These show only a single bit from each pixel, and can be used to hide messages in Steganography.";
this.infoURL = "https://wikipedia.org/wiki/Bit_plane";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
@@ -33,13 +33,13 @@ class ViewBitPlane extends Operation {
{
name: "Colour",
type: "option",
- value: COLOUR_OPTIONS
+ value: COLOUR_OPTIONS,
},
{
name: "Bit",
type: "number",
- value: 0
- }
+ value: 0,
+ },
];
}
@@ -49,36 +49,38 @@ class ViewBitPlane extends Operation {
* @returns {ArrayBuffer}
*/
async run(input, args) {
- if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
+ if (!isImage(input))
+ throw new OperationError("Please enter a valid image file.");
const [colour, bit] = args,
parsedImage = await Jimp.read(input),
width = parsedImage.bitmap.width,
height = parsedImage.bitmap.height,
colourIndex = COLOUR_OPTIONS.indexOf(colour),
- bitIndex = 7-bit;
+ bitIndex = 7 - bit;
if (bit < 0 || bit > 7) {
- throw new OperationError("Error: Bit argument must be between 0 and 7");
+ throw new OperationError(
+ "Error: Bit argument must be between 0 and 7",
+ );
}
let pixel, bin, newPixelValue;
- parsedImage.scan(0, 0, width, height, function(x, y, idx) {
+ parsedImage.scan(0, 0, width, height, function (x, y, idx) {
pixel = this.bitmap.data[idx + colourIndex];
bin = Utils.bin(pixel);
newPixelValue = 255;
if (bin.charAt(bitIndex) === "1") newPixelValue = 0;
- for (let i=0; i < 3; i++) {
+ for (let i = 0; i < 3; i++) {
this.bitmap.data[idx + i] = newPixelValue;
}
this.bitmap.data[idx + 3] = 255;
-
});
- const imageBuffer = await parsedImage.getBufferAsync(Jimp.AUTO);
+ const imageBuffer = await parsedImage.getBuffer(parsedImage.mime);
return new Uint8Array(imageBuffer).buffer;
}
@@ -94,14 +96,8 @@ class ViewBitPlane extends Operation {
return ``;
}
-
}
-const COLOUR_OPTIONS = [
- "Red",
- "Green",
- "Blue",
- "Alpha"
-];
+const COLOUR_OPTIONS = ["Red", "Green", "Blue", "Alpha"];
export default ViewBitPlane;
diff --git a/src/node/apiUtils.mjs b/src/node/apiUtils.mjs
index 64688073..9d1c43cc 100644
--- a/src/node/apiUtils.mjs
+++ b/src/node/apiUtils.mjs
@@ -66,7 +66,7 @@ export function removeSubheadingsFromArray(array) {
* @param str
*/
export function sanitise(str) {
- return str.replace(/ /g, "").toLowerCase();
+ return str.replace(/[/\s.-]/g, "").toLowerCase();
}
diff --git a/src/web/App.mjs b/src/web/App.mjs
index 7071854a..143545d6 100644
--- a/src/web/App.mjs
+++ b/src/web/App.mjs
@@ -650,7 +650,7 @@ class App {
// const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`;
- let compileInfo = `Last build: ${timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1)} ago`;
+ let compileInfo = `Last build: ${timeSinceCompile.substring(0, 1).toUpperCase() + timeSinceCompile.substring(1)} ago`;
if (window.compileMessage !== "") {
compileInfo += " - " + window.compileMessage;
diff --git a/src/web/HTMLIngredient.mjs b/src/web/HTMLIngredient.mjs
index 7eddb32c..91cbed89 100755
--- a/src/web/HTMLIngredient.mjs
+++ b/src/web/HTMLIngredient.mjs
@@ -49,15 +49,14 @@ class HTMLIngredient {
toHtml() {
let html = "",
i, m, eventFn;
+ const hintHtml = this.hint ? `data-toggle="tooltip" title="${this.hint}"` : "";
switch (this.type) {
case "string":
case "binaryString":
case "byteArray":
- html += `