Merge branch 'Ge0rg3-steganography'

This commit is contained in:
n1474335 2019-09-04 13:55:17 +01:00
commit 5bebd71a44
30 changed files with 489 additions and 27 deletions

View File

@ -177,7 +177,7 @@ class Dish {
this.type = type;
if (!this.valid()) {
const sample = Utils.truncate(JSON.stringify(this.value), 13);
const sample = Utils.truncate(JSON.stringify(this.value), 25);
throw new DishError(`Data is not a valid ${Dish.enumLookup(type)}: ${sample}`);
}
}

View File

@ -369,7 +369,11 @@
"Scan for Embedded Files",
"Extract Files",
"Remove EXIF",
"Extract EXIF"
"Extract EXIF",
"Extract RGBA",
"View Bit Plane",
"Randomize Colour Palette",
"Extract LSB"
]
},
{

View File

@ -12,7 +12,7 @@ import Utils from "../Utils.mjs";
/**
* Base64's the input byte array using the given alphabet, returning a string.
*
* @param {byteArray|Uint8Array|string} data
* @param {byteArray|Uint8Array|ArrayBuffer|string} data
* @param {string} [alphabet="A-Za-z0-9+/="]
* @returns {string}
*
@ -25,6 +25,9 @@ import Utils from "../Utils.mjs";
*/
export function toBase64(data, alphabet="A-Za-z0-9+/=") {
if (!data) return "";
if (data instanceof ArrayBuffer) {
data = new Uint8Array(data);
}
if (typeof data == "string") {
data = Utils.strToByteArray(data);
}

View File

@ -72,3 +72,12 @@ export const JOIN_DELIM_OPTIONS = [
{name: "Nothing (join chars)", value: ""}
];
/**
* RGBA list delimiters.
*/
export const RGBA_DELIM_OPTIONS = [
{name: "Comma", value: ","},
{name: "Space", value: " "},
{name: "CRLF", value: "\\r\\n"},
{name: "Line Feed", value: "\n"}
];

View File

@ -75,7 +75,7 @@ function bytesMatch(sig, buf, offset=0) {
* Given a buffer, detects magic byte sequences at specific positions and returns the
* extension and mime type.
*
* @param {Uint8Array} buf
* @param {Uint8Array|ArrayBuffer} buf
* @param {string[]} [categories=All] - Which categories of file to look for
* @returns {Object[]} types
* @returns {string} type.name - Name of file type
@ -84,6 +84,10 @@ function bytesMatch(sig, buf, offset=0) {
* @returns {string} [type.desc] - Description
*/
export function detectFileType(buf, categories=Object.keys(FILE_SIGNATURES)) {
if (buf instanceof ArrayBuffer) {
buf = new Uint8Array(buf);
}
if (!(buf && buf.length > 1)) {
return [];
}
@ -203,7 +207,7 @@ function locatePotentialSig(buf, sig, offset) {
* Detects whether the given buffer is a file of the type specified.
*
* @param {string|RegExp} type
* @param {Uint8Array} buf
* @param {Uint8Array|ArrayBuffer} buf
* @returns {string|false} The mime type or false if the type does not match
*/
export function isType(type, buf) {
@ -230,7 +234,7 @@ export function isType(type, buf) {
/**
* Detects whether the given buffer contains an image file.
*
* @param {Uint8Array} buf
* @param {Uint8Array|ArrayBuffer} buf
* @returns {string|false} The mime type or false if the type does not match
*/
export function isImage(buf) {

View File

@ -121,7 +121,7 @@ class AddTextToImage extends Operation {
let xPos = args[3],
yPos = args[4];
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -53,7 +53,7 @@ class BlurImage extends Operation {
async run(input, args) {
const [blurAmount, blurType] = args;
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -107,7 +107,7 @@ class ContainImage extends Operation {
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
};
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -93,7 +93,7 @@ class ConvertImageFormat extends Operation {
const mime = formatMap[format];
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file format.");
}
let image;

View File

@ -102,7 +102,7 @@ class CoverImage extends Operation {
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
};
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -93,7 +93,7 @@ class CropImage extends Operation {
*/
async run(input, args) {
const [xPos, yPos, width, height, autocrop, autoTolerance, autoFrames, autoSymmetric, autoBorder] = args;
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -38,7 +38,7 @@ class DitherImage extends Operation {
* @returns {byteArray}
*/
async run(input, args) {
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -0,0 +1,114 @@
/**
* @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import { fromBinary } from "../lib/Binary.mjs";
import { isImage } from "../lib/FileType.mjs";
import jimp from "jimp";
/**
* Extract LSB operation
*/
class ExtractLSB extends Operation {
/**
* ExtractLSB constructor
*/
constructor() {
super();
this.name = "Extract LSB";
this.module = "Image";
this.description = "Extracts the Least Significant Bit data from each pixel in an image. This is a common way to hide data in Steganography.";
this.infoURL = "https://wikipedia.org/wiki/Bit_numbering#Least_significant_bit_in_digital_steganography";
this.inputType = "ArrayBuffer";
this.outputType = "byteArray";
this.args = [
{
name: "Colour Pattern #1",
type: "option",
value: COLOUR_OPTIONS,
},
{
name: "Colour Pattern #2",
type: "option",
value: ["", ...COLOUR_OPTIONS],
},
{
name: "Colour Pattern #3",
type: "option",
value: ["", ...COLOUR_OPTIONS],
},
{
name: "Colour Pattern #4",
type: "option",
value: ["", ...COLOUR_OPTIONS],
},
{
name: "Pixel Order",
type: "option",
value: ["Row", "Column"],
},
{
name: "Bit",
type: "number",
value: 0
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
const bit = 7 - args.pop(),
pixelOrder = args.pop(),
colours = args.filter(option => option !== "").map(option => COLOUR_OPTIONS.indexOf(option)),
parsedImage = await jimp.read(input),
width = parsedImage.bitmap.width,
height = parsedImage.bitmap.height,
rgba = parsedImage.bitmap.data;
if (bit < 0 || bit > 7) {
throw new OperationError("Error: Bit argument must be between 0 and 7");
}
let i, combinedBinary = "";
if (pixelOrder === "Row") {
for (i = 0; i < rgba.length; i += 4) {
for (const colour of colours) {
combinedBinary += Utils.bin(rgba[i + colour])[bit];
}
}
} else {
let rowWidth;
const pixelWidth = width * 4;
for (let col = 0; col < width; col++) {
for (let row = 0; row < height; row++) {
rowWidth = row * pixelWidth;
for (const colour of colours) {
i = rowWidth + (col + colour * 4);
combinedBinary += Utils.bin(rgba[i])[bit];
}
}
}
}
return fromBinary(combinedBinary);
}
}
const COLOUR_OPTIONS = ["R", "G", "B", "A"];
export default ExtractLSB;

View File

@ -0,0 +1,65 @@
/**
* @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { isImage } from "../lib/FileType.mjs";
import jimp from "jimp";
import {RGBA_DELIM_OPTIONS} from "../lib/Delim.mjs";
/**
* Extract RGBA operation
*/
class ExtractRGBA extends Operation {
/**
* ExtractRGBA constructor
*/
constructor() {
super();
this.name = "Extract RGBA";
this.module = "Image";
this.description = "Extracts each pixel's RGBA value in an image. These are sometimes used in Steganography to hide text or data.";
this.infoURL = "https://wikipedia.org/wiki/RGBA_color_space";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
name: "Delimiter",
type: "editableOption",
value: RGBA_DELIM_OPTIONS
},
{
name: "Include Alpha",
type: "boolean",
value: true
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
async run(input, args) {
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
const delimiter = args[0],
includeAlpha = args[1],
parsedImage = await jimp.read(input);
let bitmap = parsedImage.bitmap.data;
bitmap = includeAlpha ? bitmap : bitmap.filter((val, idx) => idx % 4 !== 3);
return bitmap.join(delimiter);
}
}
export default ExtractRGBA;

View File

@ -45,7 +45,7 @@ class FlipImage extends Operation {
*/
async run(input, args) {
const [flipAxis] = args;
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid input file type.");
}

View File

@ -54,7 +54,7 @@ class ImageBrightnessContrast extends Operation {
*/
async run(input, args) {
const [brightness, contrast] = args;
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -48,7 +48,7 @@ class ImageFilter extends Operation {
*/
async run(input, args) {
const [filterType] = args;
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -62,7 +62,7 @@ class ImageHueSaturationLightness extends Operation {
async run(input, args) {
const [hue, saturation, lightness] = args;
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -47,7 +47,7 @@ class ImageOpacity extends Operation {
*/
async run(input, args) {
const [opacity] = args;
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -38,7 +38,7 @@ class InvertImage extends Operation {
* @returns {byteArray}
*/
async run(input, args) {
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid input file format.");
}

View File

@ -37,7 +37,7 @@ class NormaliseImage extends Operation {
* @returns {byteArray}
*/
async run(input, args) {
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -51,7 +51,7 @@ class ParseQRCode extends Operation {
async run(input, args) {
const [normalise] = args;
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}
return await parseQrCode(input, normalise);

View File

@ -0,0 +1,84 @@
/**
* @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import { isImage } from "../lib/FileType.mjs";
import { runHash } from "../lib/Hash.mjs";
import { toBase64 } from "../lib/Base64.mjs";
import jimp from "jimp";
import { toHex } from "../lib/Hex.mjs";
/**
* Randomize Colour Palette operation
*/
class RandomizeColourPalette extends Operation {
/**
* RandomizeColourPalette constructor
*/
constructor() {
super();
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.infoURL = "https://wikipedia.org/wiki/Indexed_color";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Seed",
type: "string",
value: ""
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
async run(input, args) {
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
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(".");
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);
return new Uint8Array(imageBuffer).buffer;
}
/**
* Displays the extracted data as an image for web apps.
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const type = isImage(data);
return `<img src="data:${type};base64,${toBase64(data)}">`;
}
}
export default RandomizeColourPalette;

View File

@ -87,7 +87,7 @@ class ResizeImage extends Operation {
"Bezier": jimp.RESIZE_BEZIER
};
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -46,7 +46,7 @@ class RotateImage extends Operation {
async run(input, args) {
const [degrees] = args;
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -62,7 +62,7 @@ class SharpenImage extends Operation {
async run(input, args) {
const [radius, amount, threshold] = args;
if (!isImage(new Uint8Array(input))) {
if (!isImage(input)) {
throw new OperationError("Invalid file type.");
}

View File

@ -40,7 +40,7 @@ class ToBase64 extends Operation {
*/
run(input, args) {
const alphabet = args[0];
return toBase64(new Uint8Array(input), alphabet);
return toBase64(input, alphabet);
}
/**

View File

@ -0,0 +1,107 @@
/**
* @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import { isImage } from "../lib/FileType.mjs";
import { toBase64 } from "../lib/Base64.mjs";
import jimp from "jimp";
/**
* View Bit Plane operation
*/
class ViewBitPlane extends Operation {
/**
* ViewBitPlane constructor
*/
constructor() {
super();
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.infoURL = "https://wikipedia.org/wiki/Bit_plane";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Colour",
type: "option",
value: COLOUR_OPTIONS
},
{
name: "Bit",
type: "number",
value: 0
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
async run(input, args) {
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;
if (bit < 0 || bit > 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) {
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++) {
this.bitmap.data[idx + i] = newPixelValue;
}
this.bitmap.data[idx + 3] = 255;
});
const imageBuffer = await parsedImage.getBufferAsync(jimp.AUTO);
return new Uint8Array(imageBuffer).buffer;
}
/**
* Displays the extracted data as an image for web apps.
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
const type = isImage(data);
return `<img src="data:${type};base64,${toBase64(data)}">`;
}
}
const COLOUR_OPTIONS = [
"Red",
"Green",
"Blue",
"Alpha"
];
export default ViewBitPlane;

View File

@ -110,4 +110,3 @@ const logOpsTestReport = logTestReport.bind(null, testStatus);
TestRegister.runTests()
.then(logOpsTestReport);

View File

@ -2,9 +2,10 @@
* Image operation tests.
*
* @author tlwr [toby@toby.codes]
* @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]
* @author n1474335 [n1474335@gmail.com]
*
* @copyright Crown Copyright 2017
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
@ -175,4 +176,76 @@ TestRegister.addTests([
},
],
},
{
name: "Extract RGBA",
input: "424d460400000000000036040000280000000400000004000000010008000000000010000000120b0000120b0000000100000001000000c8000000cf000000d7000000df000000e7000000ef000000f7000000ff000083000000ac000000d5000000ff000000000083000000ac000000d5000000ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f070d05030b01090c040e060008020a",
expectedOutput: "0 200 0 0 0 131 0 215 0 0 0 213 131 0 0 0 231 0 213 0 0 0 247 0 0 223 0 0 0 255 0 207 0 0 0 172 255 0 0 0 255 0 172 0 0 0 239 0",
recipeConfig: [
{
op: "From Hex",
args: ["None"]
},
{
op: "Extract RGBA",
args: [" ", false]
}
]
},
{
name: "Extract LSB",
input: "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af400000449494441547801cc9703782b5914c783b56ddbb659db6e9f6ddbb66dd45e6fddc6aecd24cff67b99397b6ebe9d6216d99bcceebefb7dffbaf9ff0e7347e4e491eeddbbf7f5bcbcbc41b9b9b943376cd8f00ef9194afcfb67094ac0d3ed85870d1b764755555592c562616a6a6a18ad560b28b6a0a0203b3d3d7d4e494989fed75f7fdd45fe5e10e38c8c8c0f2a2b2b7737343468ebeaea8a516aabd50a441c00278d46c392cf2a95eaf475d75d7767525252505656565f67332296c9644330d2cbc48c2f3e005f656565ed08c410656666c653bb63e4ef9acd66ce9c0a8027164b430d20aea8a8d84e4c5c05c02c1ebce9a69b6e27af49d5e5f5f5f56a4700b5b5b5ac0300ae1cad3ffdf4d3321a0809365bc93f007068cea9b8b8584e35197abd7e8e5019c032983103aba8ca306fdebc07dadada2c02f4c0a15b6eb9e50ea746313535f5ade6e6e63a57008870170c14397b9e7cf2c97bc838d20294969636e30eb0a1181ce9de2217ce2d087089060037e139b1587ce7fefdfbfd30fa3ef4e9771140a1501cc3ffbb91deea2a05b8991640a9541ec5ffbb5e2800497272f20f7880afad5bb73273e7ce05bea64c99922ee49de07a9d4ed760341a812fbc9030696969c0574a4a8a11ffef5a979d5fbaffe61be604bf3dc698b9d966cade0add555ffa13ab91c96cbb76ed62e7cf9fdf43b367cf3ef7c5175f78ba9cfac5fe2f6cd62e88828a65f13c25c0595d09542b95cceeddbb212020007c7d7d7bc8c7c7e7b2bbbbfbf34ebbc7bcf3e0a33ba25eb992d8f70330ace8df692e9b1703478b7300dadaec00782784799be7815b9c1b676e070a0a0a2a78f7dd77ef7573737b468a871a60b6e733513ba35e818d612fc111b50cce5768a15f983fcc9c311d4a3233e1427d3dfc9c98c8e6e4e4c07d89f7c1dbd96fc3a8ef47c1f6eddb61c78e1dd0b76fdf8b090909c67efdfa31616161b154e6833e7ef4c96d112f1fe5008e6995f688372f5902512121d0a152c1711cb7cc8d1b01b71d4c2a9b64875893b70648461213138fa1794dfffefd01c54645458da102581df8c26a62ce07e0646b6981530a851d60f5ead5c4b48770f7d77b7b7bfb1180e8e8e82db43764c986d017cbfe0ee05c6d2d5cd268ec003366cce00390516c0c0f0f5f4c007af7ee5d4dfa800640bc2ef8c5744719384300366d62478d1a05a40fba03e0430b8bb5070240141717b792aa0433dd9f76db11f98a8d0fd05d6c6b2b54cae56c76763631e503407c7cbc2130303018cd777dfdf5d78f53df8a17fa3cdb7f7bc4cba7ba03301879bbc904c770024e3637c37eec7834204d4622be826fbd2c07803f5b2e72f584bdf1c0fd13be7e32fe54b9e92417f9653437e3fb40c39e3db067c10216f73eb36edd3a06cb6037479155dcfef9e79f3f2cd87361555e5ec4a5c64633bf0cdc22ea2ecc8265e7ce9dfe22a1cfe4a143ef32ab54532e363434f10038e30e9cffdfd650545424c8404bc0c4c4c47664cd1a83c7274ec41fdeb62d0f58192501a3c04c5e5e9e8d1cf30084683c77e1e9adc80000000049454e44ae426082",
expectedOutput: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000208000000000000000000008000000000000000000000000248000000200240000000208908000000200240000000200821000000200240000000061249000000240000000000209b69000001a49b00000000a204a1200001a49b00000009800414000001a49b0000000035db6c00000094924000000086dffc20000df6dec8000001e10014a0000df6dec800002564924b00000df6dec80000009a6db20000007edb4124804177fffba0002fffff69249044e0924bc4002fffff6924905fb2db6d04002fffff692490416d2490040001bfffcc92030dbffffdc00037fffffdb6d302c6db6d700037fffffdb6d327eb6db6148037fffffdb6d30db4000014800dffffeb6d9aefffffff640",
recipeConfig: [
{
op: "From Hex",
args: ["None"]
},
{
op: "Extract LSB",
args: ["B", "G", "A", "", "Column", 2]
},
{
op: "To Hex",
args: ["None"]
}
]
},
{
name: "View Bit Plane",
input: "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af400000449494441547801cc9703782b5914c783b56ddbb659db6e9f6ddbb66dd45e6fddc6aecd24cff67b99397b6ebe9d6216d99bcceebefb7dffbaf9ff0e7347e4e491eeddbbf7f5bcbcbc41b9b9b943376cd8f00ef9194afcfb67094ac0d3ed85870d1b764755555592c562616a6a6a18ad560b28b6a0a0203b3d3d7d4e494989fed75f7fdd45fe5e10e38c8c8c0f2a2b2b7737343468ebeaea8a516aabd50a441c00278d46c392cf2a95eaf475d75d7767525252505656565f67332296c9644330d2cbc48c2f3e005f656565ed08c410656666c653bb63e4ef9acd66ce9c0a8027164b430d20aea8a8d84e4c5c05c02c1ebce9a69b6e27af49d5e5f5f5f56a4700b5b5b5ac0300ae1cad3ffdf4d3321a0809365bc93f007068cea9b8b8584e35197abd7e8e5019c032983103aba8ca306fdebc07dadada2c02f4c0a15b6eb9e50ea746313535f5ade6e6e63a57008870170c14397b9e7cf2c97bc838d20294969636e30eb0a1181ce9de2217ce2d087089060037e139b1587ce7fefdfbfd30fa3ef4e9771140a1501cc3ffbb91deea2a05b8991640a9541ec5ffbb5e2800497272f20f7880afad5bb73273e7ce05bea64c99922ee49de07a9d4ed760341a812fbc9030696969c0574a4a8a11ffef5a979d5fbaffe61be604bf3dc698b9d966cade0add555ffa13ab91c96cbb76ed62e7cf9fdf43b367cf3ef7c5175f78ba9cfac5fe2f6cd62e88828a65f13c25c0595d09542b95cceeddbb212020007c7d7d7bc8c7c7e7b2bbbbfbf34ebbc7bcf3e0a33ba25eb992d8f70330ace8df692e9b1703478b7300dadaec00782784799be7815b9c1b676e070a0a0a2a78f7dd77ef7573737b468a871a60b6e733513ba35e818d612fc111b50cce5768a15f983fcc9c311d4a3233e1427d3dfc9c98c8e6e4e4c07d89f7c1dbd96fc3a8ef47c1f6eddb61c78e1dd0b76fdf8b090909c67efdfa31616161b154e6833e7ef4c96d112f1fe5008e6995f688372f5902512121d0a152c1711cb7cc8d1b01b71d4c2a9b64875893b70648461213138fa1794dfffefd01c54645458da102581df8c26a62ce07e0646b6981530a851d60f5ead5c4b48770f7d77b7b7bfb1180e8e8e82db43764c986d017cbfe0ee05c6d2d5cd268ec003366cce00390516c0c0f0f5f4c007af7ee5d4dfa800640bc2ef8c5744719384300366d62478d1a05a40fba03e0430b8bb5070240141717b792aa0433dd9f76db11f98a8d0fd05d6c6b2b54cae56c76763631e503407c7cbc2130303018cd777dfdf5d78f53df8a17fa3cdb7f7bc4cba7ba03301879bbc904c770024e3637c37eec7834204d4622be826fbd2c07803f5b2e72f584bdf1c0fd13be7e32fe54b9e92417f9653437e3fb40c39e3db067c10216f73eb36edd3a06cb6037479155dcfef9e79f3f2cd87361555e5ec4a5c64633bf0cdc22ea2ecc8265e7ce9dfe22a1cfe4a143ef32ab54532e363434f10038e30e9cffdfd650545424c8404bc0c4c4c47664cd1a83c7274ec41fdeb62d0f58192501a3c04c5e5e9e8d1cf30084683c77e1e9adc80000000049454e44ae426082",
expectedOutput: "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af400000140494441547801c5c1416ea3400000c1ee11ffff726fe6808410186ce26c95fde0432a154f0cdea4b2aa505151519954ee1a5c50995454aea8ac54ae2c5ca8982a3ea132551c199c507942e58ec1898adf50f1cae084ca15952b2a152a47067f40a5e2c8e00f54a81c199ca8b85271a542a5e2c8e005159527542ace0c5ea8a8f844c54ae5ccc217555c197c41c55d83ff6cf0052a772ddca052b1a752b1a772d7c2432a4f2c3c50f1d4c20b2a1593ca918a4965afe2cac29b2a562a93ca56c55d0b2754b62a269555c554b15251a9b8637040e5884ac54a654ba5a2624be5cce040c5918a55c55ec5a4a232a9549c197c48655239523155bc3278a862af624be5ccc2072aaea854a8549c5978834a85ca5ec5918a57ec076f50a958a9546ca94c1557ec071754a68a2d958a270637544c2a2abf69e1a68a95ca54b152a978d73f2e08bd57b6f839a00000000049454e44ae426082",
recipeConfig: [
{
op: "From Hex",
args: ["None"]
},
{
op: "View Bit Plane",
args: ["Green", 3]
},
{
op: "To Hex",
args: ["None"]
}
]
},
{
name: "Randomize Colour Palette",
"input": "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af400000674494441547801c5970394244b1686bf44b9aaddd5fdfcc6330f63af6ddbdea3b56ddbb66ddbdeb16dcf5497d219b137f3d44363ba7b70cede73be74c6fd6f4454e45ffcbfc398ecdef7bef7bd35c562f1c59669de0fc32818467a590ec7bca635bab38fe27823b0278aa29f3cf0810ffc12a02e4680f9fdef7fff1903d5eaa766cd98412e9f2793c91204216118c83e052509b3d96c4aaeb3775d07152b1a8d3a478f1ee5452f7a5101f0384fd88b172f666c6cd8b0215f2e973fb576f56a40e37901be248ce34888514a9192542edcfebcb7a7977a7d844e70e4c891d2a402e401c6c6631ffbd82b92864dd3948ac3b4f1e4bc13c91008a6a0097c1fa7dd927d20421d9acd16beef1fdbba75eb8f5ef9ca577eee6286c010867ef5ab5f1dbfd7bdee858c2569d7cb5ec4488206b55a8d76bb8de7ba64b2198af9025ddddd5c71e5951cd8bf9f37bef18d4f9121fc0f705aa80bc1797b80f1a11392ea034968c8be561be1e4a913d425719ab054a6af7f003b63532a1629e4f3140a058a42d25b3367ce6c02870467aa1eb09938a2fffef7bf6f035e8661d85ac3cd37dfcc82053726e39c56ef380e511c89400bd34ab001b06c9beeee9e00f0264f3eb900f7e52f7ff9e7819f0995bffe6bddcf77ef3f8c8a0fa20123c1209d1771ace47a9cce0b3b63093656363f6a782f4680271c13ce093dcf7ae09d9ef8f47bf77c05a5c964f2686c1a4d9f43474e72e7bbaf65c5da9b3876ec0c9ff8c84fe8abf4b1eff4a118d04c234c260e25381d012756cfee5b15eb1c37ad5c4dbe7b8062ff20f778f0bd78c7c7dfc4fd1e716f8add7dcc98338365775a8c952bb260c6d58f03cc4b1030fa996ac57a96e318acbecb1a9efdba17f28ce73e8585cb1612470aa7ede17b3e9e1771e59503f8a14339977d1490bb1c028c27ace85f6d59e41dd764efcebdd07668b59c74127a92d8f7935531220803dc769b4ac10243171ebf727035605caa804c7fd1783268c2c0e2f73ffb2b5aa9643de82ccb091191ec93c9b86de35e72392bfd290e96cc2701994b1590b550cbb58a29e6234e9d70d9b3f3202a124191240f2479140a8a53c7ce51cae4b06d489eb78c782590bd6401b6a517a838c0204c1bffcd0fff045aa7e31f45b1a0e43466e3bf77808e8845905221b188b81c3d600611bb6215a1421fad7cf6eed8cba986471448a2385d9ef9f7e6bdd4ced688635f0404c46180a8b0a633076c260fe587d131db30e606848045fff09558a6c5f68367f0ddb6ac05a769b49a682d4f784e2a00342d37da04e84bed81e0137f6f3ef7f499c6df5b5ebc1b2b878a5c4c4333dc57c1360d8a390b4bce5bf5b3683343aee70ae7ecb9e696afad77df080497e2880032af7ff3dbeef8d0873dfcb5f3e6cfbb5bc182fffee8ad74570cd0a41f2ad334049346bdc1cd0f78335e0c070f1e3efbef7ffefddd4f7afc63de0f041723c0f8f297bf3c7be1c245df5db850be429d38f09faf523bb20ed3ca25d96fb366a929f1c915079877f797a6823ac6c6dbb973e7ddc45ffc13d0d31d02e3339ff9ccbc952b57ee4a92bb9e475daa6b341d8ce23544dac269d5709da6d0c2711ab49373d7c7ee992bcfd68526c97be2b6f2c2dfa598b58031dd1ec8feec673fdb70bffbdd6fc1b9da48faf93520add8346d4c3bc7b9037fe3e8a6efe33b67b0ed3cc3f3ef4b75eebdc1b2d17188d6f1adcea9afb7873ffce10fe1ddee76b72ec09b8e80ee75ebd68d2c59b2444cc8e9d10ef8162b264252e418345a451d5472065a20d96986aa83ac5fbf8eb7bef56dd701871813f6231ef1083ac177bffb5de3ddef7ef74ab1e2a9058b22459a7f5c244e2915031d630aa374269705952ed9625058b66cd9bc57bdea5547c69a145b928e3aaf56ab6b1301a9ed56112332a67d3d3da831494c6db26bcf1e868786e8aa54c4fd1e1e25a0d56c33343c4cb9524e2ddb8c19331e08fc4150934d42bb542a2defefef97065ae947a53e524f7a6394fd9663418112525714b362c50a962f5f2e7b418ed7ac59239577e3b5db744b01f97c7e39909d6a25cc4af5cb054e9d394bac54cae12347a94895e57209cbb268b7da341a0dec6c8eb3e76af84198625a26ade45ebd91bae96a7520494c75a88a14b614c84c25c0ce66b3c3406a322ccba4bfaf174f8e1bcdc6a83f1c32395314506fd4d37b1dbf28490b527d05dbce10851100b95c2e0fd853fd0a06befded6f7fbfb7b7f78e69825b67fe6d8f1a13bda9c71fca308dfacfa8b46ec9ff8c59c0a9c90454846b8501c1e6f285166ac27e6164b2217085431d9506973702a1cd98f81f11b2640d65786ac70000000049454e44ae426082",
expectedOutput: "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af4000007ed494441547801c5c17b5c0d8602c0f15f9de3d16425a113999296348f3c0b75ea4e2b5be7a05bd46a32c99b5496477266d494ac97508a0e494848c2525e87bc95ccb31145915e3abacb38f7b6cfdde7d3a74faafff6fdaa59c81254fc8384b42179b40501455ee84476617b920eeee33e6742d7c994d685d15c9ac31ff85f5c4386b13ada4b0268a2597c8dc7fa72da23e423ccfca67221fa32f29dfe1c59b08ef4c4459c7a5acc1e130396d5aee2f8d8092c105ee7f9a91a6e6db6a6c4d3893e96b318aa1b41b7aa0822cd156c1aa5c39b2e0f698b7097d36b5af2caec499cc29dd409bd389b578ea66f10569394fc229b89f7fd2f890bbdcbbbeb83c8511b8acbc67b6cad39c7c691f7e81ceb829eb12deb6de740247f912e1c465b84d285c368c92150c68f338e31784e11c3924e522281d50a0156fdf84bf27a1fa2c4de7cb3d79dbdd3ab916f90a2b4ec45e3e6159cb54fc6c8741fa3b48a703e654e0f872ada2230f216cb348cebd030ae43c3b80e0de33aca2a2c30cc37a5d7c89714780a11de84dda24914ae28a5617b209f0f4e22bba03bfd973930a1df22ece73ea1dbe30d8c9ee48455e925a2d39dd861fa968e1088c41219ad1873b89e5137bf66a465029b46f461aaebf79cad7c4e68f0262aabba503ec09123e3b7a319e6cbdefe12385e485d5d0fe4d21e7c2bb047d1e94b3a429d8fb0d64de39cff1172f213581aea8f32eb2ebaf23bc85f6be2e1b5859af4a5680c9d4df046299551c9ec58ed49935d3f9bf04a2d8b8e1288c41219ad383ebe3f771475dc30f88490e1f7e82c8c62f34007be8e53e026b5c4c3a11b03ae6f657abf4944e8fe4efef3293c689c8d87961e11db9da91beb4847084462898c76e48499b243438fead0183e645660133886d3ffaa65acc0971b0113c9add4a3583b1c9f05b5a46c352447bb2f5a867de908753ac03f478bb26b9f52e4f88642e712b47cb7b122f4097ee5d5788cc8e2e8e20a4c94cf5962fa03e9257339635d4e47a9d30195dade841c9273d5408731bdcbf139b983d8de57393d2f908c992ed49b6c6474a101f1c11928dc8ef066ed753a4a483b740f34703fd38c82ada58c7c358f17c77ee2c98aa7acf9598df57ebfd0b93a8dfc3333c82aafc0e1e518968f49e7495e2e6a411fe8ba5e417bd469c79463eef8c54f63d76173a24f9f40b955c989dcb7f867bee7b33c29fd97b8e09c771de5ae5a9c4bd7d15747cae8ea30065ebc4d47086987b6cd34eef4aca56be65bfcc3c6312fab37bb2ec5303c64177d621c18eb25e24ef71ea41e0c216250000d9e525ef7d065d0d628ca3a4da73d02915822a30d1a29c398ebf12be1a5f3d1093a42df9709bc4a6e60d07721945c3d84a47b118a803c0483aff02e3a9eb74356b2283688ec05f6bcf94d8ff6a8d38ebb353ac4fe618ac53d57ee5cbd8874f26d2e9d8ec3eebc2b8ee163f1591bc3a0e25282b639d14bc305a7c652f20b37a06bb8878e50a71d8eff598d4cef2056dd8cd896da8537aebe6cd3b4e3c1706b2a53f5315d1b448d5a363a669fa2f2d442392198f8fe539816ad4e47084462898c36dc2cb4e2d6dc2f181b5241cf47f1147c178db67b19911e4fd1ba3c9e9f728e7241ef393766ca091a974145891ca5d88751920cf21555b44720124b64b44112f135a9053739693e0a3dd3f1684b5c392f4a24cefb027aef0663be3190c64867ba769acc90f43ed8c4a450239b43f5aa069e7656d11e753e62ffd52c94b646f49b7b1fcb3001d6dfbe6375d5778cebfd16cbc453b8669dc66e71144b8dc55cb7fb8a4b71fa1cb55d4279843e36cb2d18aa32e2135b7f027cbc688b402496c868c1cd620259ba4370b9aacec0a06c1cbfe8c2b08614a2b5df5353e14db0db441efa2e4623f92835c92bf18df56464919440d52c56b113b7ec4d686fd1e6b31d6b5813b6946335c51ce86b426b0422b144460be10633a9bff56ff236dee7878bc94c0e7d48a3d10d82baf664f45e3f7627a868541c47f9bb883d665ff15a56c5a36c2f54f16e6cb8a0497d4d29c2a365ecdf2ec2eb81132b0627a0fa7322ad11d28a5986a94c038cc2d7121da5e026ffa380a4862c0c0fc552231f87fcad90facf0229f23ac9dcc2482aee66919462c1a188857caf914813a3f0f1c03e9e8913c8ea398bd6a85d7bd85fc5fff9a4ace19c7e236689d964e858e26d9946c172335ab2d9ff827a83e1bc5fe8c78cf70dbc59684373f1ebeaf009fe940d3eb67cf54087dfb65c2379ea3cfc2ebfa725350b59828a66242bed51793b53f5a29ea84e3d89f2ddc4a4158598e9c969ce35ad8cf9f9fdb8bf3a86dcd35389720ce66fa22d2202a507099b338078fd18cc3ba961305f972901beb424108925329a99b5e021eb96e570f4543051b955ec769dca3c074b164b8d78f4e3532eb9f720b1fb5224a5d6ccd73d4892e609726a63095ee9c39e705b94cf26e3f3a18eb2ea7378561af0f3950f6cb1abe151cc455e5dba474b02915822a319ffd9c188cec8499a2a20c4269ebbb9ae7c1b684556c40786767bc6fdd05b4c1c6a49491739f2dc13d8efb62661531acbc6ebb1ece57ee6ab7499257067e7f2e92cb1d267709f517c73db8d068fde3cb1bc474beab4b0d376274df68d9b89dedd3b0ccc48648fc93374970e42651c878be1af8cd83986cd7602940557386babe0855a19b5ef42e86df788b3227b4e2c7362f3a248a47f1612e1349f26e71f3ca6356a16b20415cd445d7984225d4c695d1887334d386f664f9c7d3ff282dc68f2a7cf019a180d5cc4df7e2f8ee56fc278179a1cb00e67e26fa798e2f48034873f280ed1447cd98596d42c64092afe41eafcc3fe0b9c67148a38c1a5620000000049454e44ae426082",
recipeConfig: [
{
op: "From Hex",
args: ["None"]
},
{
op: "Randomize Colour Palette",
args: ["myseed"]
},
{
op: "To Hex",
args: ["None"]
}
]
}
]);