mirror of
https://github.com/gchq/CyberChef.git
synced 2025-05-10 00:05:11 -04:00
Add Extract Quantisation Tables operation
This commit is contained in:
parent
f7be8d720b
commit
e156f620b9
3 changed files with 167 additions and 0 deletions
|
@ -379,6 +379,7 @@
|
|||
"Remove EXIF",
|
||||
"Extract EXIF",
|
||||
"Extract RGBA",
|
||||
"Extract Quantisation Tables",
|
||||
"View Bit Plane",
|
||||
"Randomize Colour Palette",
|
||||
"Extract LSB"
|
||||
|
|
68
src/core/lib/QuantisationTable.mjs
Normal file
68
src/core/lib/QuantisationTable.mjs
Normal file
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* Parses a JPEG quantisation table.
|
||||
*
|
||||
* @param {Uint8Array} rawTable
|
||||
* @param {boolean} doublePrec - true if 16-bit precision, false if 8-bit
|
||||
* @returns {number[][]}
|
||||
*
|
||||
*/
|
||||
export function parseQTable(rawTable, doublePrec = false) {
|
||||
// The raw table is flattened in zig-zag order, similar to enumerating a countable set.
|
||||
// This lookup table tells the index in the natural order for each element in the zig-zag order
|
||||
const unZigZagIndex = [
|
||||
0, 1, 8, 16, 9, 2, 3, 10,
|
||||
17, 24, 32, 25, 18, 11, 4, 5,
|
||||
12, 19, 26, 33, 40, 48, 41, 34,
|
||||
27, 20, 13, 6, 7, 14, 21, 28,
|
||||
35, 42, 49, 56, 57, 50, 43, 36,
|
||||
29, 22, 15, 23, 30, 37, 44, 51,
|
||||
58, 59, 52, 45, 38, 31, 39, 46,
|
||||
53, 60, 61, 54, 47, 55, 62, 63];
|
||||
const ret = [
|
||||
[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1]];
|
||||
let i = 0;
|
||||
let elemCount = 0;
|
||||
while (i < rawTable.length) {
|
||||
const row = ~~(unZigZagIndex[elemCount] / 8); // floor
|
||||
const col = unZigZagIndex[elemCount] % 8;
|
||||
if (doublePrec) {
|
||||
ret[row][col] = rawTable[i++] << 8 + rawTable[i++];
|
||||
} else {
|
||||
ret[row][col] = rawTable[i++];
|
||||
}
|
||||
elemCount++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of first appearance of a JPEG marker
|
||||
*
|
||||
* @param {Uint8Array} image
|
||||
* @param {number} markerByte - doesn't include 0xFF
|
||||
* @param {number} fromIndex - the index to start the search at
|
||||
* @returns {number}
|
||||
*
|
||||
*/
|
||||
export function indexOfMarker(image, markerByte, fromIndex = 0) {
|
||||
let ptr = fromIndex;
|
||||
let markerIndex = 0;
|
||||
do {
|
||||
// look for the byte that changes across markers first, as these are less common than 0xFF
|
||||
markerIndex = image.indexOf(markerByte, ptr);
|
||||
if (markerIndex === 0) return -1; // marker byte must follow 0xFF
|
||||
if (image[markerIndex-1] === 0xFF) {
|
||||
return markerIndex - 1;
|
||||
} else {
|
||||
ptr = markerIndex + 1;
|
||||
}
|
||||
} while (markerIndex !== -1);
|
||||
return -1;
|
||||
}
|
98
src/core/operations/ExtractQuantisationTables.mjs
Normal file
98
src/core/operations/ExtractQuantisationTables.mjs
Normal file
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* @author cbeuw [cbeuw.andy@gmail.com]
|
||||
* @copyright Crown Copyright 2020
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import {parseQTable, indexOfMarker} from "../lib/QuantisationTable.mjs";
|
||||
|
||||
/**
|
||||
* Extract Quantisation Tables operation
|
||||
*/
|
||||
class ExtractQuantisationTables extends Operation {
|
||||
|
||||
/**
|
||||
* ExtractQuantisationTables constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Extract Quantisation Tables";
|
||||
this.module = "Default";
|
||||
this.description = "Extracts quantisation tables embedded in a JPEG image.";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/Quantization_(image_processing)";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const image = new Uint8Array(input);
|
||||
// maximally 4 tables are allowed in a JPEG file
|
||||
|
||||
let ptr = indexOfMarker(image, 0xD8);
|
||||
if (ptr === -1) throw new OperationError("Malformed image file: Start of Image not found");
|
||||
|
||||
let ret = "";
|
||||
|
||||
do {
|
||||
const dqtIndex = indexOfMarker(image, 0xDB, ptr);
|
||||
if (dqtIndex === -1) {
|
||||
break;
|
||||
}
|
||||
// -2 here because the length bytes include the two bytes used to record length
|
||||
// -1 to exclude the byte used to record the table's precision and id
|
||||
const tableLength = (image[dqtIndex+2] << 8) + image[dqtIndex+3] - 2 - 1;
|
||||
// if the table is 16-bit precision
|
||||
const doublePrecision = (image[dqtIndex+4] >> 4) === 1;
|
||||
if (tableLength !== (doublePrecision? 128:64)) throw new OperationError(`Invalid table length ${tableLength} at table definition beginning at ${dqtIndex}`);
|
||||
const tableId = image[dqtIndex+4] & 0x0F;
|
||||
if (tableId >= 4) throw new OperationError(`Invalid table identifier ${tableId} at table definition beginning at ${dqtIndex}`);
|
||||
const table = parseQTable(image.slice(dqtIndex + 5, dqtIndex + 5 + tableLength), doublePrecision);
|
||||
|
||||
ret += `Quantisation table at position ${dqtIndex}. ID ${tableId}, ${(doublePrecision? "16":"8")}-bit precision:\n`;
|
||||
ret += this.prettifyMatrix(table);
|
||||
ret += "\n";
|
||||
|
||||
ptr = dqtIndex + 5 + tableLength + 1;
|
||||
} while (ptr < image.length);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[][]} mat
|
||||
* @returns {string}
|
||||
*/
|
||||
prettifyMatrix(mat) {
|
||||
const nRows = mat.length;
|
||||
const nCols = mat[0].length;
|
||||
let maxLen = 0;
|
||||
for (let i = 0; i < nRows; i++) {
|
||||
for (let j = 0; j < nCols; j++) {
|
||||
const elemLen = mat[i][j].toString().length;
|
||||
if (elemLen > maxLen) {
|
||||
maxLen = elemLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ret = "";
|
||||
for (let i = 0; i < nRows; i++) {
|
||||
for (let j = 0; j < nCols; j++) {
|
||||
ret += mat[i][j].toString().padStart(maxLen) + " ";
|
||||
}
|
||||
ret += "\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
export default ExtractQuantisationTables;
|
Loading…
Add table
Add a link
Reference in a new issue