cyberchef 10.22.1 → 10.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +182 -0
- package/CONTRIBUTING.md +40 -0
- package/Dockerfile +2 -0
- package/Gruntfile.js +2 -28
- package/README.md +1 -1
- package/babel.config.js +0 -6
- package/package.json +64 -63
- package/src/core/Chef.mjs +8 -1
- package/src/core/Ingredient.mjs +5 -2
- package/src/core/Operation.mjs +6 -1
- package/src/core/Recipe.mjs +10 -5
- package/src/core/config/Categories.json +20 -4
- package/src/core/config/OperationConfig.json +550 -26
- package/src/core/config/modules/Ciphers.mjs +6 -0
- package/src/core/config/modules/Crypto.mjs +6 -0
- package/src/core/config/modules/Default.mjs +8 -0
- package/src/core/config/modules/Shellcode.mjs +2 -0
- package/src/core/config/scripts/fixCryptoApiImports.mjs +54 -0
- package/src/core/config/scripts/fixSnackBarMarkup.mjs +28 -0
- package/src/core/lib/AudioBytes.mjs +103 -0
- package/src/core/lib/AudioMetaSchema.mjs +82 -0
- package/src/core/lib/AudioParsers.mjs +630 -0
- package/src/core/lib/BigIntUtils.mjs +73 -0
- package/src/core/lib/Extract.mjs +5 -0
- package/src/core/lib/Modhex.mjs +2 -0
- package/src/core/lib/ParityBit.mjs +50 -0
- package/src/core/lib/QRCode.mjs +30 -10
- package/src/core/lib/RC6.mjs +625 -0
- package/src/core/operations/A1Z26CipherDecode.mjs +1 -1
- package/src/core/operations/AddTextToImage.mjs +116 -64
- package/src/core/operations/AnalyseUUID.mjs +109 -6
- package/src/core/operations/BlurImage.mjs +10 -12
- package/src/core/operations/ContainImage.mjs +50 -40
- package/src/core/operations/ConvertImageFormat.mjs +33 -39
- package/src/core/operations/CoverImage.mjs +39 -37
- package/src/core/operations/CropImage.mjs +35 -21
- package/src/core/operations/DisassembleARM.mjs +193 -0
- package/src/core/operations/DitherImage.mjs +8 -8
- package/src/core/operations/EscapeUnicodeCharacters.mjs +0 -17
- package/src/core/operations/ExtractAudioMetadata.mjs +175 -0
- package/src/core/operations/ExtractEmailAddresses.mjs +2 -3
- package/src/core/operations/ExtractLSB.mjs +17 -11
- package/src/core/operations/ExtractRGBA.mjs +12 -10
- package/src/core/operations/FlaskSessionDecode.mjs +80 -0
- package/src/core/operations/FlaskSessionSign.mjs +89 -0
- package/src/core/operations/FlaskSessionVerify.mjs +136 -0
- package/src/core/operations/FlipImage.mjs +14 -10
- package/src/core/operations/GenerateImage.mjs +39 -32
- package/src/core/operations/ImageBrightnessContrast.mjs +10 -10
- package/src/core/operations/ImageFilter.mjs +14 -13
- package/src/core/operations/ImageHueSaturationLightness.mjs +22 -20
- package/src/core/operations/ImageOpacity.mjs +6 -8
- package/src/core/operations/InvertImage.mjs +4 -6
- package/src/core/operations/Jq.mjs +12 -4
- package/src/core/operations/NormaliseImage.mjs +5 -7
- package/src/core/operations/OffsetChecker.mjs +1 -1
- package/src/core/operations/ParityBit.mjs +128 -0
- package/src/core/operations/ParseEthernetFrame.mjs +112 -0
- package/src/core/operations/ParseIPv4Header.mjs +23 -6
- package/src/core/operations/ParseQRCode.mjs +13 -13
- package/src/core/operations/PseudoRandomIntegerGenerator.mjs +164 -0
- package/src/core/operations/RC6Decrypt.mjs +119 -0
- package/src/core/operations/RC6Encrypt.mjs +119 -0
- package/src/core/operations/RandomizeColourPalette.mjs +11 -11
- package/src/core/operations/RegularExpression.mjs +2 -1
- package/src/core/operations/RenderMarkdown.mjs +35 -4
- package/src/core/operations/ResizeImage.mjs +30 -23
- package/src/core/operations/RotateImage.mjs +8 -9
- package/src/core/operations/SQLBeautify.mjs +21 -3
- package/src/core/operations/SharpenImage.mjs +94 -62
- package/src/core/operations/SplitColourChannels.mjs +47 -21
- package/src/core/operations/TextIntegerConverter.mjs +123 -0
- package/src/core/operations/UnescapeUnicodeCharacters.mjs +17 -0
- package/src/core/operations/ViewBitPlane.mjs +16 -20
- package/src/core/operations/index.mjs +22 -0
- package/src/node/index.mjs +55 -0
- package/src/web/HTMLIngredient.mjs +24 -43
- package/src/web/HTMLOperation.mjs +8 -2
- package/src/web/Manager.mjs +1 -0
- package/src/web/html/index.html +6 -6
- package/src/web/static/fonts/bmfonts/Roboto72White.fnt +491 -485
- package/src/web/static/fonts/bmfonts/RobotoBlack72White.fnt +494 -488
- package/src/web/static/fonts/bmfonts/RobotoMono72White.fnt +110 -103
- package/src/web/static/fonts/bmfonts/RobotoSlab72White.fnt +498 -492
- package/src/web/stylesheets/layout/_banner.css +30 -0
- package/src/web/stylesheets/layout/_modals.css +5 -0
- package/src/web/stylesheets/utils/_overrides.css +7 -0
- package/src/web/waiters/ControlsWaiter.mjs +82 -0
- package/src/web/waiters/InputWaiter.mjs +12 -6
- package/src/web/waiters/OperationsWaiter.mjs +30 -13
- package/src/web/waiters/RecipeWaiter.mjs +2 -2
- package/tests/browser/02_ops.js +23 -3
- package/tests/node/index.mjs +1 -0
- package/tests/node/tests/lib/BigIntUtils.mjs +150 -0
- package/tests/node/tests/operations.mjs +11 -10
- package/tests/operations/index.mjs +13 -0
- package/tests/operations/tests/A1Z26CipherDecode.mjs +33 -0
- package/tests/operations/tests/AnalyseUUID.mjs +66 -0
- package/tests/operations/tests/DisassembleARM.mjs +377 -0
- package/tests/operations/tests/ExtractAudioMetadata.mjs +287 -0
- package/tests/operations/tests/ExtractEmailAddresses.mjs +38 -12
- package/tests/operations/tests/Fernet.mjs +18 -3
- package/tests/operations/tests/FlaskSession.mjs +246 -0
- package/tests/operations/tests/GenerateQRCode.mjs +67 -0
- package/tests/operations/tests/JWTSign.mjs +83 -8
- package/tests/operations/tests/Jq.mjs +32 -0
- package/tests/operations/tests/Modhex.mjs +20 -0
- package/tests/operations/tests/ParityBit.mjs +147 -0
- package/tests/operations/tests/ParseEthernetFrame.mjs +45 -0
- package/tests/operations/tests/RC6.mjs +487 -0
- package/tests/operations/tests/RegularExpression.mjs +75 -0
- package/tests/operations/tests/RenderMarkdown.mjs +110 -0
- package/tests/operations/tests/SQLBeautify.mjs +54 -0
- package/tests/operations/tests/TextIntegerConverter.mjs +199 -0
- package/tests/samples/Audio.mjs +73 -0
- package/tests/samples/Images.mjs +0 -12
- package/webpack.config.js +10 -7
- package/src/core/lib/ImageManipulation.mjs +0 -251
|
@@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|
|
9
9
|
import { isImage } from "../lib/FileType.mjs";
|
|
10
10
|
import { toBase64 } from "../lib/Base64.mjs";
|
|
11
11
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
|
12
|
-
import Jimp from "jimp
|
|
12
|
+
import { Jimp, JimpMime, ResizeStrategy } from "jimp";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Resize Image operation
|
|
16
16
|
*/
|
|
17
17
|
class ResizeImage extends Operation {
|
|
18
|
-
|
|
19
18
|
/**
|
|
20
19
|
* ResizeImage constructor
|
|
21
20
|
*/
|
|
@@ -24,7 +23,8 @@ class ResizeImage extends Operation {
|
|
|
24
23
|
|
|
25
24
|
this.name = "Resize Image";
|
|
26
25
|
this.module = "Image";
|
|
27
|
-
this.description =
|
|
26
|
+
this.description =
|
|
27
|
+
"Resizes an image to the specified width and height values.";
|
|
28
28
|
this.infoURL = "https://wikipedia.org/wiki/Image_scaling";
|
|
29
29
|
this.inputType = "ArrayBuffer";
|
|
30
30
|
this.outputType = "ArrayBuffer";
|
|
@@ -34,23 +34,23 @@ class ResizeImage extends Operation {
|
|
|
34
34
|
name: "Width",
|
|
35
35
|
type: "number",
|
|
36
36
|
value: 100,
|
|
37
|
-
min: 1
|
|
37
|
+
min: 1,
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
name: "Height",
|
|
41
41
|
type: "number",
|
|
42
42
|
value: 100,
|
|
43
|
-
min: 1
|
|
43
|
+
min: 1,
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
name: "Unit type",
|
|
47
47
|
type: "option",
|
|
48
|
-
value: ["Pixels", "Percent"]
|
|
48
|
+
value: ["Pixels", "Percent"],
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
name: "Maintain aspect ratio",
|
|
52
52
|
type: "boolean",
|
|
53
|
-
value: false
|
|
53
|
+
value: false,
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
name: "Resizing algorithm",
|
|
@@ -60,10 +60,10 @@ class ResizeImage extends Operation {
|
|
|
60
60
|
"Bilinear",
|
|
61
61
|
"Bicubic",
|
|
62
62
|
"Hermite",
|
|
63
|
-
"Bezier"
|
|
63
|
+
"Bezier",
|
|
64
64
|
],
|
|
65
|
-
defaultIndex: 1
|
|
66
|
-
}
|
|
65
|
+
defaultIndex: 1,
|
|
66
|
+
},
|
|
67
67
|
];
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -80,11 +80,11 @@ class ResizeImage extends Operation {
|
|
|
80
80
|
resizeAlg = args[4];
|
|
81
81
|
|
|
82
82
|
const resizeMap = {
|
|
83
|
-
"Nearest Neighbour":
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
"Nearest Neighbour": ResizeStrategy.NEAREST_NEIGHBOR,
|
|
84
|
+
Bilinear: ResizeStrategy.BILINEAR,
|
|
85
|
+
Bicubic: ResizeStrategy.BICUBIC,
|
|
86
|
+
Hermite: ResizeStrategy.HERMITE,
|
|
87
|
+
Bezier: ResizeStrategy.BEZIER,
|
|
88
88
|
};
|
|
89
89
|
|
|
90
90
|
if (!isImage(input)) {
|
|
@@ -99,23 +99,31 @@ class ResizeImage extends Operation {
|
|
|
99
99
|
}
|
|
100
100
|
try {
|
|
101
101
|
if (unit === "Percent") {
|
|
102
|
-
width = image.
|
|
103
|
-
height = image.
|
|
102
|
+
width = image.width * (width / 100);
|
|
103
|
+
height = image.height * (height / 100);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
if (isWorkerEnvironment())
|
|
107
107
|
self.sendStatusMessage("Resizing image...");
|
|
108
108
|
if (aspect) {
|
|
109
|
-
image.scaleToFit(
|
|
109
|
+
image.scaleToFit({
|
|
110
|
+
w: width,
|
|
111
|
+
h: height,
|
|
112
|
+
mode: resizeMap[resizeAlg],
|
|
113
|
+
});
|
|
110
114
|
} else {
|
|
111
|
-
image.resize(
|
|
115
|
+
image.resize({
|
|
116
|
+
w: width,
|
|
117
|
+
h: height,
|
|
118
|
+
mode: resizeMap[resizeAlg],
|
|
119
|
+
});
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
let imageBuffer;
|
|
115
|
-
if (image.
|
|
116
|
-
imageBuffer = await image.
|
|
123
|
+
if (image.mime === "image/gif") {
|
|
124
|
+
imageBuffer = await image.getBuffer(JimpMime.png);
|
|
117
125
|
} else {
|
|
118
|
-
imageBuffer = await image.
|
|
126
|
+
imageBuffer = await image.getBuffer(image.mime);
|
|
119
127
|
}
|
|
120
128
|
return imageBuffer.buffer;
|
|
121
129
|
} catch (err) {
|
|
@@ -139,7 +147,6 @@ class ResizeImage extends Operation {
|
|
|
139
147
|
|
|
140
148
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
|
141
149
|
}
|
|
142
|
-
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
export default ResizeImage;
|
|
@@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|
|
9
9
|
import { isImage } from "../lib/FileType.mjs";
|
|
10
10
|
import { toBase64 } from "../lib/Base64.mjs";
|
|
11
11
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
|
12
|
-
import Jimp from "jimp
|
|
12
|
+
import { Jimp, JimpMime } from "jimp";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Rotate Image operation
|
|
16
16
|
*/
|
|
17
17
|
class RotateImage extends Operation {
|
|
18
|
-
|
|
19
18
|
/**
|
|
20
19
|
* RotateImage constructor
|
|
21
20
|
*/
|
|
@@ -24,7 +23,8 @@ class RotateImage extends Operation {
|
|
|
24
23
|
|
|
25
24
|
this.name = "Rotate Image";
|
|
26
25
|
this.module = "Image";
|
|
27
|
-
this.description =
|
|
26
|
+
this.description =
|
|
27
|
+
"Rotates an image by the specified number of degrees.";
|
|
28
28
|
this.infoURL = "";
|
|
29
29
|
this.inputType = "ArrayBuffer";
|
|
30
30
|
this.outputType = "ArrayBuffer";
|
|
@@ -33,8 +33,8 @@ class RotateImage extends Operation {
|
|
|
33
33
|
{
|
|
34
34
|
name: "Rotation amount (degrees)",
|
|
35
35
|
type: "number",
|
|
36
|
-
value: 90
|
|
37
|
-
}
|
|
36
|
+
value: 90,
|
|
37
|
+
},
|
|
38
38
|
];
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -62,10 +62,10 @@ class RotateImage extends Operation {
|
|
|
62
62
|
image.rotate(degrees);
|
|
63
63
|
|
|
64
64
|
let imageBuffer;
|
|
65
|
-
if (image.
|
|
66
|
-
imageBuffer = await image.
|
|
65
|
+
if (image.mime === "image/gif") {
|
|
66
|
+
imageBuffer = await image.getBuffer(JimpMime.png);
|
|
67
67
|
} else {
|
|
68
|
-
imageBuffer = await image.
|
|
68
|
+
imageBuffer = await image.getBuffer(image.mime);
|
|
69
69
|
}
|
|
70
70
|
return imageBuffer.buffer;
|
|
71
71
|
} catch (err) {
|
|
@@ -89,7 +89,6 @@ class RotateImage extends Operation {
|
|
|
89
89
|
|
|
90
90
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
|
91
91
|
}
|
|
92
|
-
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
export default RotateImage;
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* @copyright Crown Copyright 2016
|
|
4
4
|
* @license Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
import vkbeautify from "vkbeautify";
|
|
6
|
+
import { format } from "sql-formatter";
|
|
8
7
|
import Operation from "../Operation.mjs";
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -39,7 +38,26 @@ class SQLBeautify extends Operation {
|
|
|
39
38
|
*/
|
|
40
39
|
run(input, args) {
|
|
41
40
|
const indentStr = args[0];
|
|
42
|
-
|
|
41
|
+
// Extract and replace bind variables like :Bind1 with __BIND_0__
|
|
42
|
+
const bindRegex = /:\w+/g;
|
|
43
|
+
const bindMap = {};
|
|
44
|
+
let bindCounter=0;
|
|
45
|
+
const placeholderInput = input.replace(bindRegex, (match) => {
|
|
46
|
+
const placeholder = `__BIND_${bindCounter++}__`;
|
|
47
|
+
bindMap[placeholder] = match;
|
|
48
|
+
return placeholder;
|
|
49
|
+
});
|
|
50
|
+
// Format the SQL with chosen options
|
|
51
|
+
let formatted= format(placeholderInput, {
|
|
52
|
+
language: "mysql", // Use MySQL as the default dialect for better compatibility with real-world SQL
|
|
53
|
+
useTabs: indentStr==="\t", // true if tab, false if spaces
|
|
54
|
+
tabWidth: indentStr.length || 4, // fallback if empty
|
|
55
|
+
indentStyle: "standard" // fine for most SQL
|
|
56
|
+
});
|
|
57
|
+
// Replace placeholders back with original bind variables
|
|
58
|
+
formatted = formatted.replace(/__BIND_\d+__/g, match => bindMap[match] || match);
|
|
59
|
+
|
|
60
|
+
return formatted;
|
|
43
61
|
}
|
|
44
62
|
|
|
45
63
|
}
|
|
@@ -8,15 +8,13 @@ import Operation from "../Operation.mjs";
|
|
|
8
8
|
import OperationError from "../errors/OperationError.mjs";
|
|
9
9
|
import { isImage } from "../lib/FileType.mjs";
|
|
10
10
|
import { toBase64 } from "../lib/Base64.mjs";
|
|
11
|
-
import { gaussianBlur } from "../lib/ImageManipulation.mjs";
|
|
12
11
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
|
13
|
-
import Jimp from "jimp
|
|
12
|
+
import { Jimp, JimpMime } from "jimp";
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Sharpen Image operation
|
|
17
16
|
*/
|
|
18
17
|
class SharpenImage extends Operation {
|
|
19
|
-
|
|
20
18
|
/**
|
|
21
19
|
* SharpenImage constructor
|
|
22
20
|
*/
|
|
@@ -35,22 +33,22 @@ class SharpenImage extends Operation {
|
|
|
35
33
|
name: "Radius",
|
|
36
34
|
type: "number",
|
|
37
35
|
value: 2,
|
|
38
|
-
min: 1
|
|
36
|
+
min: 1,
|
|
39
37
|
},
|
|
40
38
|
{
|
|
41
39
|
name: "Amount",
|
|
42
40
|
type: "number",
|
|
43
41
|
value: 1,
|
|
44
42
|
min: 0,
|
|
45
|
-
step: 0.1
|
|
43
|
+
step: 0.1,
|
|
46
44
|
},
|
|
47
45
|
{
|
|
48
46
|
name: "Threshold",
|
|
49
47
|
type: "number",
|
|
50
48
|
value: 10,
|
|
51
49
|
min: 0,
|
|
52
|
-
max: 100
|
|
53
|
-
}
|
|
50
|
+
max: 100,
|
|
51
|
+
},
|
|
54
52
|
];
|
|
55
53
|
}
|
|
56
54
|
|
|
@@ -79,67 +77,102 @@ class SharpenImage extends Operation {
|
|
|
79
77
|
const blurMask = image.clone();
|
|
80
78
|
|
|
81
79
|
if (isWorkerEnvironment())
|
|
82
|
-
self.sendStatusMessage(
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
self.sendStatusMessage(
|
|
81
|
+
"Sharpening image... (Blurring cloned image)",
|
|
82
|
+
);
|
|
83
|
+
const blurImage = image.clone().gaussian(radius);
|
|
85
84
|
|
|
86
85
|
if (isWorkerEnvironment())
|
|
87
|
-
self.sendStatusMessage(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
86
|
+
self.sendStatusMessage(
|
|
87
|
+
"Sharpening image... (Creating unsharp mask)",
|
|
88
|
+
);
|
|
89
|
+
blurMask.scan(
|
|
90
|
+
0,
|
|
91
|
+
0,
|
|
92
|
+
blurMask.bitmap.width,
|
|
93
|
+
blurMask.bitmap.height,
|
|
94
|
+
function (x, y, idx) {
|
|
95
|
+
const blurRed = blurImage.bitmap.data[idx];
|
|
96
|
+
const blurGreen = blurImage.bitmap.data[idx + 1];
|
|
97
|
+
const blurBlue = blurImage.bitmap.data[idx + 2];
|
|
98
|
+
|
|
99
|
+
const normalRed = this.bitmap.data[idx];
|
|
100
|
+
const normalGreen = this.bitmap.data[idx + 1];
|
|
101
|
+
const normalBlue = this.bitmap.data[idx + 2];
|
|
102
|
+
|
|
103
|
+
// Subtract blurred pixel value from normal image
|
|
104
|
+
this.bitmap.data[idx] =
|
|
105
|
+
normalRed > blurRed ? normalRed - blurRed : 0;
|
|
106
|
+
this.bitmap.data[idx + 1] =
|
|
107
|
+
normalGreen > blurGreen ? normalGreen - blurGreen : 0;
|
|
108
|
+
this.bitmap.data[idx + 2] =
|
|
109
|
+
normalBlue > blurBlue ? normalBlue - blurBlue : 0;
|
|
110
|
+
},
|
|
111
|
+
);
|
|
102
112
|
|
|
103
113
|
if (isWorkerEnvironment())
|
|
104
|
-
self.sendStatusMessage(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
114
|
+
self.sendStatusMessage(
|
|
115
|
+
"Sharpening image... (Merging with unsharp mask)",
|
|
116
|
+
);
|
|
117
|
+
image.scan(
|
|
118
|
+
0,
|
|
119
|
+
0,
|
|
120
|
+
image.bitmap.width,
|
|
121
|
+
image.bitmap.height,
|
|
122
|
+
function (x, y, idx) {
|
|
123
|
+
let maskRed = blurMask.bitmap.data[idx];
|
|
124
|
+
let maskGreen = blurMask.bitmap.data[idx + 1];
|
|
125
|
+
let maskBlue = blurMask.bitmap.data[idx + 2];
|
|
126
|
+
|
|
127
|
+
const normalRed = this.bitmap.data[idx];
|
|
128
|
+
const normalGreen = this.bitmap.data[idx + 1];
|
|
129
|
+
const normalBlue = this.bitmap.data[idx + 2];
|
|
130
|
+
|
|
131
|
+
// Calculate luminance
|
|
132
|
+
const maskLuminance =
|
|
133
|
+
0.2126 * maskRed +
|
|
134
|
+
0.7152 * maskGreen +
|
|
135
|
+
0.0722 * maskBlue;
|
|
136
|
+
const normalLuminance =
|
|
137
|
+
0.2126 * normalRed +
|
|
138
|
+
0.7152 * normalGreen +
|
|
139
|
+
0.0722 * normalBlue;
|
|
140
|
+
|
|
141
|
+
let luminanceDiff;
|
|
142
|
+
if (maskLuminance > normalLuminance) {
|
|
143
|
+
luminanceDiff = maskLuminance - normalLuminance;
|
|
144
|
+
} else {
|
|
145
|
+
luminanceDiff = normalLuminance - maskLuminance;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Scale mask colours by amount
|
|
149
|
+
maskRed = maskRed * amount;
|
|
150
|
+
maskGreen = maskGreen * amount;
|
|
151
|
+
maskBlue = maskBlue * amount;
|
|
152
|
+
|
|
153
|
+
// Only change pixel value if the difference is higher than threshold
|
|
154
|
+
if ((luminanceDiff / 255) * 100 >= threshold) {
|
|
155
|
+
this.bitmap.data[idx] =
|
|
156
|
+
normalRed + maskRed <= 255 ?
|
|
157
|
+
normalRed + maskRed :
|
|
158
|
+
255;
|
|
159
|
+
this.bitmap.data[idx + 1] =
|
|
160
|
+
normalGreen + maskGreen <= 255 ?
|
|
161
|
+
normalGreen + maskGreen :
|
|
162
|
+
255;
|
|
163
|
+
this.bitmap.data[idx + 2] =
|
|
164
|
+
normalBlue + maskBlue <= 255 ?
|
|
165
|
+
normalBlue + maskBlue :
|
|
166
|
+
255;
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
);
|
|
137
170
|
|
|
138
171
|
let imageBuffer;
|
|
139
|
-
if (image.
|
|
140
|
-
imageBuffer = await image.
|
|
172
|
+
if (image.mime === "image/gif") {
|
|
173
|
+
imageBuffer = await image.getBuffer(JimpMime.png);
|
|
141
174
|
} else {
|
|
142
|
-
imageBuffer = await image.
|
|
175
|
+
imageBuffer = await image.getBuffer(image.mime);
|
|
143
176
|
}
|
|
144
177
|
return imageBuffer.buffer;
|
|
145
178
|
} catch (err) {
|
|
@@ -163,7 +196,6 @@ class SharpenImage extends Operation {
|
|
|
163
196
|
|
|
164
197
|
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
|
165
198
|
}
|
|
166
|
-
|
|
167
199
|
}
|
|
168
200
|
|
|
169
201
|
export default SharpenImage;
|
|
@@ -7,14 +7,13 @@
|
|
|
7
7
|
import Operation from "../Operation.mjs";
|
|
8
8
|
import OperationError from "../errors/OperationError.mjs";
|
|
9
9
|
import Utils from "../Utils.mjs";
|
|
10
|
-
import {isImage} from "../lib/FileType.mjs";
|
|
11
|
-
import Jimp from "jimp
|
|
10
|
+
import { isImage } from "../lib/FileType.mjs";
|
|
11
|
+
import { Jimp, JimpMime } from "jimp";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Split Colour Channels operation
|
|
15
15
|
*/
|
|
16
16
|
class SplitColourChannels extends Operation {
|
|
17
|
-
|
|
18
17
|
/**
|
|
19
18
|
* SplitColourChannels constructor
|
|
20
19
|
*/
|
|
@@ -23,7 +22,8 @@ class SplitColourChannels extends Operation {
|
|
|
23
22
|
|
|
24
23
|
this.name = "Split Colour Channels";
|
|
25
24
|
this.module = "Image";
|
|
26
|
-
this.description =
|
|
25
|
+
this.description =
|
|
26
|
+
"Splits the given image into its red, green and blue colour channels.";
|
|
27
27
|
this.infoURL = "https://wikipedia.org/wiki/Channel_(digital_image)";
|
|
28
28
|
this.inputType = "ArrayBuffer";
|
|
29
29
|
this.outputType = "List<File>";
|
|
@@ -48,26 +48,44 @@ class SplitColourChannels extends Operation {
|
|
|
48
48
|
const split = parsedImage
|
|
49
49
|
.clone()
|
|
50
50
|
.color([
|
|
51
|
-
{apply: "blue", params: [-255]},
|
|
52
|
-
{apply: "green", params: [-255]}
|
|
51
|
+
{ apply: "blue", params: [-255] },
|
|
52
|
+
{ apply: "green", params: [-255] },
|
|
53
53
|
])
|
|
54
|
-
.
|
|
55
|
-
resolve(
|
|
54
|
+
.getBuffer(JimpMime.png);
|
|
55
|
+
resolve(
|
|
56
|
+
new File(
|
|
57
|
+
[new Uint8Array((await split).values())],
|
|
58
|
+
"red.png",
|
|
59
|
+
{ type: "image/png" },
|
|
60
|
+
),
|
|
61
|
+
);
|
|
56
62
|
} catch (err) {
|
|
57
|
-
reject(
|
|
63
|
+
reject(
|
|
64
|
+
new OperationError(`Could not split red channel: ${err}`),
|
|
65
|
+
);
|
|
58
66
|
}
|
|
59
67
|
});
|
|
60
68
|
|
|
61
69
|
const green = new Promise(async (resolve, reject) => {
|
|
62
70
|
try {
|
|
63
|
-
const split = parsedImage
|
|
71
|
+
const split = parsedImage
|
|
72
|
+
.clone()
|
|
64
73
|
.color([
|
|
65
|
-
{apply: "red", params: [-255]},
|
|
66
|
-
{apply: "blue", params: [-255]},
|
|
67
|
-
])
|
|
68
|
-
|
|
74
|
+
{ apply: "red", params: [-255] },
|
|
75
|
+
{ apply: "blue", params: [-255] },
|
|
76
|
+
])
|
|
77
|
+
.getBuffer(JimpMime.png);
|
|
78
|
+
resolve(
|
|
79
|
+
new File(
|
|
80
|
+
[new Uint8Array((await split).values())],
|
|
81
|
+
"green.png",
|
|
82
|
+
{ type: "image/png" },
|
|
83
|
+
),
|
|
84
|
+
);
|
|
69
85
|
} catch (err) {
|
|
70
|
-
reject(
|
|
86
|
+
reject(
|
|
87
|
+
new OperationError(`Could not split green channel: ${err}`),
|
|
88
|
+
);
|
|
71
89
|
}
|
|
72
90
|
});
|
|
73
91
|
|
|
@@ -75,12 +93,21 @@ class SplitColourChannels extends Operation {
|
|
|
75
93
|
try {
|
|
76
94
|
const split = parsedImage
|
|
77
95
|
.color([
|
|
78
|
-
{apply: "red", params: [-255]},
|
|
79
|
-
{apply: "green", params: [-255]},
|
|
80
|
-
])
|
|
81
|
-
|
|
96
|
+
{ apply: "red", params: [-255] },
|
|
97
|
+
{ apply: "green", params: [-255] },
|
|
98
|
+
])
|
|
99
|
+
.getBuffer(JimpMime.png);
|
|
100
|
+
resolve(
|
|
101
|
+
new File(
|
|
102
|
+
[new Uint8Array((await split).values())],
|
|
103
|
+
"blue.png",
|
|
104
|
+
{ type: "image/png" },
|
|
105
|
+
),
|
|
106
|
+
);
|
|
82
107
|
} catch (err) {
|
|
83
|
-
reject(
|
|
108
|
+
reject(
|
|
109
|
+
new OperationError(`Could not split blue channel: ${err}`),
|
|
110
|
+
);
|
|
84
111
|
}
|
|
85
112
|
});
|
|
86
113
|
|
|
@@ -96,7 +123,6 @@ class SplitColourChannels extends Operation {
|
|
|
96
123
|
async present(files) {
|
|
97
124
|
return await Utils.displayFilesAsHTML(files);
|
|
98
125
|
}
|
|
99
|
-
|
|
100
126
|
}
|
|
101
127
|
|
|
102
128
|
export default SplitColourChannels;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author p-leriche [philip.leriche@cantab.net]
|
|
3
|
+
* @copyright Crown Copyright 2025
|
|
4
|
+
* @license Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Operation from "../Operation.mjs";
|
|
8
|
+
import OperationError from "../errors/OperationError.mjs";
|
|
9
|
+
|
|
10
|
+
/* ---------- helper functions ---------- */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert text to BigInt (big-endian byte interpretation)
|
|
14
|
+
*/
|
|
15
|
+
function textToBigInt(text) {
|
|
16
|
+
if (text.length === 0) return 0n;
|
|
17
|
+
|
|
18
|
+
let result = 0n;
|
|
19
|
+
for (let i = 0; i < text.length; i++) {
|
|
20
|
+
const charCode = BigInt(text.charCodeAt(i));
|
|
21
|
+
if (charCode > 255n) {
|
|
22
|
+
throw new OperationError(
|
|
23
|
+
`Character at position ${i} exceeds Latin-1 range (0-255).\n` +
|
|
24
|
+
"Only ASCII and Latin-1 characters are supported.");
|
|
25
|
+
}
|
|
26
|
+
result = (result << 8n) | charCode;
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert BigInt to text (big-endian byte interpretation)
|
|
33
|
+
*/
|
|
34
|
+
function bigIntToText(value) {
|
|
35
|
+
if (value === 0n) return "";
|
|
36
|
+
|
|
37
|
+
const bytes = [];
|
|
38
|
+
let num = value;
|
|
39
|
+
|
|
40
|
+
while (num > 0n) {
|
|
41
|
+
bytes.unshift(Number(num & 0xFFn));
|
|
42
|
+
num >>= 8n;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return String.fromCharCode(...bytes);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* ---------- operation class ---------- */
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Text/Integer Converter operation
|
|
52
|
+
*/
|
|
53
|
+
class TextIntegerConverter extends Operation {
|
|
54
|
+
/**
|
|
55
|
+
* TextIntegerConverter constructor
|
|
56
|
+
*/
|
|
57
|
+
constructor() {
|
|
58
|
+
super();
|
|
59
|
+
|
|
60
|
+
this.description =
|
|
61
|
+
"Converts between text strings and large integers (decimal or hexadecimal).<br><br>" +
|
|
62
|
+
"Text is interpreted as a big-endian sequence of character codes. For example:<br>" +
|
|
63
|
+
"ABC is 0x414243 (hex) is 4276803 (decimal)<br>" +
|
|
64
|
+
"<b>Input format detection:</b><br>" +
|
|
65
|
+
"Decimal: digits 0-9 only<br>" +
|
|
66
|
+
"Hexadecimal: 0x... prefix<br>" +
|
|
67
|
+
"Quoted or unquoted text: treated as string<br><br>" +
|
|
68
|
+
"<b>Character limitations:</b><br>" +
|
|
69
|
+
"Text input may only contain ASCII and Latin-1 characters (code point < 256).<br>" +
|
|
70
|
+
"Multi-byte Unicode characters will generate an error.<br><br>." ;
|
|
71
|
+
this.infoURL = "https://wikipedia.org/wiki/Endianness";
|
|
72
|
+
this.inputType = "string";
|
|
73
|
+
this.outputType = "string";
|
|
74
|
+
this.args = [
|
|
75
|
+
{
|
|
76
|
+
name: "Output format",
|
|
77
|
+
type: "option",
|
|
78
|
+
value: ["String", "Decimal", "Hexadecimal"]
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
this.name = "Text-Integer Conversion";
|
|
82
|
+
this.module = "Default";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} input
|
|
87
|
+
* @param {Object[]} args
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
run(input, args) {
|
|
91
|
+
const outputFormat = args[0];
|
|
92
|
+
const trimmed = input.trim();
|
|
93
|
+
|
|
94
|
+
let bigIntValue;
|
|
95
|
+
|
|
96
|
+
if (!trimmed) {
|
|
97
|
+
// Null input - treat as zero
|
|
98
|
+
bigIntValue = 0;
|
|
99
|
+
} else if (/^0x[0-9a-f]+$/i.test(trimmed) ||
|
|
100
|
+
/^[+-]?[0-9]+$/.test(trimmed)) {
|
|
101
|
+
// Hex or decimal integer
|
|
102
|
+
bigIntValue = BigInt(trimmed);
|
|
103
|
+
} else if (/^["'].*["']$/.test(trimmed)) {
|
|
104
|
+
// Quoted string: Remove quotes and convert text to BigInt
|
|
105
|
+
const text = trimmed.slice(1, -1);
|
|
106
|
+
bigIntValue = textToBigInt(text);
|
|
107
|
+
} else {
|
|
108
|
+
// Assume it's unquoted text
|
|
109
|
+
bigIntValue = textToBigInt(trimmed);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Convert to output format
|
|
113
|
+
if (outputFormat === "String") {
|
|
114
|
+
return bigIntToText(bigIntValue);
|
|
115
|
+
} else if (outputFormat === "Decimal") {
|
|
116
|
+
return bigIntValue.toString();
|
|
117
|
+
} else { // Hexadecimal
|
|
118
|
+
return "0x" + bigIntValue.toString(16);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default TextIntegerConverter;
|