mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-21 07:16:17 -04:00
Add new implementation of gaussian blur.
Changed SharpenImage to use the new algorithm.
This commit is contained in:
parent
ce72acdd61
commit
2cd3e9cacd
3 changed files with 260 additions and 4 deletions
252
src/core/lib/ImageManipulation.mjs
Normal file
252
src/core/lib/ImageManipulation.mjs
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
/**
|
||||||
|
* Image manipulation resources
|
||||||
|
*
|
||||||
|
* @author j433866 [j433866@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2019
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import OperationError from "../errors/OperationError";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gaussian blurs an image.
|
||||||
|
*
|
||||||
|
* @param {jimp} input
|
||||||
|
* @param {int} radius
|
||||||
|
* @param {boolean} fast
|
||||||
|
* @returns {jimp}
|
||||||
|
*/
|
||||||
|
export function gaussianBlur (input, radius) {
|
||||||
|
try {
|
||||||
|
// From http://blog.ivank.net/fastest-gaussian-blur.html
|
||||||
|
const boxes = boxesForGauss(radius, 3);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
input = boxBlur(input, (boxes[i] - 1) / 2);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err);
|
||||||
|
throw new OperationError(`Error blurring image. (${err})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {int} radius
|
||||||
|
* @param {int} numBoxes
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
function boxesForGauss(radius, numBoxes) {
|
||||||
|
const idealWidth = Math.sqrt((12 * radius * radius / numBoxes) + 1);
|
||||||
|
|
||||||
|
let wl = Math.floor(idealWidth);
|
||||||
|
|
||||||
|
if (wl % 2 === 0) {
|
||||||
|
wl--;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wu = wl + 2;
|
||||||
|
|
||||||
|
const mIdeal = (12 * radius * radius - numBoxes * wl * wl - 4 * numBoxes * wl - 3 * numBoxes) / (-4 * wl - 4);
|
||||||
|
const m = Math.round(mIdeal);
|
||||||
|
|
||||||
|
const sizes = [];
|
||||||
|
for (let i = 0; i < numBoxes; i++) {
|
||||||
|
sizes.push(i < m ? wl : wu);
|
||||||
|
}
|
||||||
|
return sizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a box blur effect to the image
|
||||||
|
*
|
||||||
|
* @param {jimp} source
|
||||||
|
* @param {number} radius
|
||||||
|
* @returns {jimp}
|
||||||
|
*/
|
||||||
|
function boxBlur (source, radius) {
|
||||||
|
const width = source.bitmap.width;
|
||||||
|
const height = source.bitmap.height;
|
||||||
|
let output = source.clone();
|
||||||
|
output = boxBlurH(source, output, width, height, radius);
|
||||||
|
source = boxBlurV(output, source, width, height, radius);
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the horizontal blur
|
||||||
|
*
|
||||||
|
* @param {jimp} source
|
||||||
|
* @param {jimp} output
|
||||||
|
* @param {number} width
|
||||||
|
* @param {number} height
|
||||||
|
* @param {number} radius
|
||||||
|
* @returns {jimp}
|
||||||
|
*/
|
||||||
|
function boxBlurH (source, output, width, height, radius) {
|
||||||
|
const iarr = 1 / (radius + radius + 1);
|
||||||
|
for (let i = 0; i < height; i++) {
|
||||||
|
let ti = 0,
|
||||||
|
li = ti,
|
||||||
|
ri = ti + radius;
|
||||||
|
const idx = source.getPixelIndex(ti, i);
|
||||||
|
const firstValRed = source.bitmap.data[idx],
|
||||||
|
firstValGreen = source.bitmap.data[idx + 1],
|
||||||
|
firstValBlue = source.bitmap.data[idx + 2],
|
||||||
|
firstValAlpha = source.bitmap.data[idx + 3];
|
||||||
|
|
||||||
|
const lastIdx = source.getPixelIndex(width - 1, i),
|
||||||
|
lastValRed = source.bitmap.data[lastIdx],
|
||||||
|
lastValGreen = source.bitmap.data[lastIdx + 1],
|
||||||
|
lastValBlue = source.bitmap.data[lastIdx + 2],
|
||||||
|
lastValAlpha = source.bitmap.data[lastIdx + 3];
|
||||||
|
|
||||||
|
let red = (radius + 1) * firstValRed;
|
||||||
|
let green = (radius + 1) * firstValGreen;
|
||||||
|
let blue = (radius + 1) * firstValBlue;
|
||||||
|
let alpha = (radius + 1) * firstValAlpha;
|
||||||
|
|
||||||
|
for (let j = 0; j < radius; j++) {
|
||||||
|
const jIdx = source.getPixelIndex(ti + j, i);
|
||||||
|
red += source.bitmap.data[jIdx];
|
||||||
|
green += source.bitmap.data[jIdx + 1];
|
||||||
|
blue += source.bitmap.data[jIdx + 2];
|
||||||
|
alpha += source.bitmap.data[jIdx + 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j <= radius; j++) {
|
||||||
|
const jIdx = source.getPixelIndex(ri++, i);
|
||||||
|
red += source.bitmap.data[jIdx] - firstValRed;
|
||||||
|
green += source.bitmap.data[jIdx + 1] - firstValGreen;
|
||||||
|
blue += source.bitmap.data[jIdx + 2] - firstValBlue;
|
||||||
|
alpha += source.bitmap.data[jIdx + 3] - firstValAlpha;
|
||||||
|
|
||||||
|
const tiIdx = source.getPixelIndex(ti++, i);
|
||||||
|
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = radius + 1; j < width - radius; j++) {
|
||||||
|
const riIdx = source.getPixelIndex(ri++, i);
|
||||||
|
const liIdx = source.getPixelIndex(li++, i);
|
||||||
|
red += source.bitmap.data[riIdx] - source.bitmap.data[liIdx];
|
||||||
|
green += source.bitmap.data[riIdx + 1] - source.bitmap.data[liIdx + 1];
|
||||||
|
blue += source.bitmap.data[riIdx + 2] - source.bitmap.data[liIdx + 2];
|
||||||
|
alpha += source.bitmap.data[riIdx + 3] - source.bitmap.data[liIdx + 3];
|
||||||
|
|
||||||
|
const tiIdx = source.getPixelIndex(ti++, i);
|
||||||
|
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = width - radius; j < width; j++) {
|
||||||
|
const liIdx = source.getPixelIndex(li++, i);
|
||||||
|
red += lastValRed - source.bitmap.data[liIdx];
|
||||||
|
green += lastValGreen - source.bitmap.data[liIdx + 1];
|
||||||
|
blue += lastValBlue - source.bitmap.data[liIdx + 2];
|
||||||
|
alpha += lastValAlpha - source.bitmap.data[liIdx + 3];
|
||||||
|
|
||||||
|
const tiIdx = source.getPixelIndex(ti++, i);
|
||||||
|
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the vertical blur
|
||||||
|
*
|
||||||
|
* @param {jimp} source
|
||||||
|
* @param {jimp} output
|
||||||
|
* @param {int} width
|
||||||
|
* @param {int} height
|
||||||
|
* @param {int} radius
|
||||||
|
* @returns {jimp}
|
||||||
|
*/
|
||||||
|
function boxBlurV (source, output, width, height, radius) {
|
||||||
|
const iarr = 1 / (radius + radius + 1);
|
||||||
|
for (let i = 0; i < width; i++) {
|
||||||
|
let ti = 0,
|
||||||
|
li = ti,
|
||||||
|
ri = ti + radius;
|
||||||
|
|
||||||
|
const idx = source.getPixelIndex(i, ti);
|
||||||
|
|
||||||
|
const firstValRed = source.bitmap.data[idx],
|
||||||
|
firstValGreen = source.bitmap.data[idx + 1],
|
||||||
|
firstValBlue = source.bitmap.data[idx + 2],
|
||||||
|
firstValAlpha = source.bitmap.data[idx + 3];
|
||||||
|
|
||||||
|
const lastIdx = source.getPixelIndex(i, height - 1),
|
||||||
|
lastValRed = source.bitmap.data[lastIdx],
|
||||||
|
lastValGreen = source.bitmap.data[lastIdx + 1],
|
||||||
|
lastValBlue = source.bitmap.data[lastIdx + 2],
|
||||||
|
lastValAlpha = source.bitmap.data[lastIdx + 3];
|
||||||
|
|
||||||
|
let red = (radius + 1) * firstValRed;
|
||||||
|
let green = (radius + 1) * firstValGreen;
|
||||||
|
let blue = (radius + 1) * firstValBlue;
|
||||||
|
let alpha = (radius + 1) * firstValAlpha;
|
||||||
|
|
||||||
|
for (let j = 0; j < radius; j++) {
|
||||||
|
const jIdx = source.getPixelIndex(i, ti + j);
|
||||||
|
red += source.bitmap.data[jIdx];
|
||||||
|
green += source.bitmap.data[jIdx + 1];
|
||||||
|
blue += source.bitmap.data[jIdx + 2];
|
||||||
|
alpha += source.bitmap.data[jIdx + 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j <= radius; j++) {
|
||||||
|
const riIdx = source.getPixelIndex(i, ri++);
|
||||||
|
red += source.bitmap.data[riIdx] - firstValRed;
|
||||||
|
green += source.bitmap.data[riIdx + 1] - firstValGreen;
|
||||||
|
blue += source.bitmap.data[riIdx + 2] - firstValBlue;
|
||||||
|
alpha += source.bitmap.data[riIdx + 3] - firstValAlpha;
|
||||||
|
|
||||||
|
const tiIdx = source.getPixelIndex(i, ti++);
|
||||||
|
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = radius + 1; j < height - radius; j++) {
|
||||||
|
const riIdx = source.getPixelIndex(i, ri++);
|
||||||
|
const liIdx = source.getPixelIndex(i, li++);
|
||||||
|
red += source.bitmap.data[riIdx] - source.bitmap.data[liIdx];
|
||||||
|
green += source.bitmap.data[riIdx + 1] - source.bitmap.data[liIdx + 1];
|
||||||
|
blue += source.bitmap.data[riIdx + 2] - source.bitmap.data[liIdx + 2];
|
||||||
|
alpha += source.bitmap.data[riIdx + 3] - source.bitmap.data[liIdx + 3];
|
||||||
|
|
||||||
|
const tiIdx = source.getPixelIndex(i, ti++);
|
||||||
|
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = height - radius; j < height; j++) {
|
||||||
|
const liIdx = source.getPixelIndex(i, li++);
|
||||||
|
red += lastValRed - source.bitmap.data[liIdx];
|
||||||
|
green += lastValGreen - source.bitmap.data[liIdx + 1];
|
||||||
|
blue += lastValBlue - source.bitmap.data[liIdx + 2];
|
||||||
|
alpha += lastValAlpha - source.bitmap.data[liIdx + 3];
|
||||||
|
|
||||||
|
const tiIdx = source.getPixelIndex(i, ti++);
|
||||||
|
output.bitmap.data[tiIdx] = Math.round(red * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
|
||||||
|
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import OperationError from "../errors/OperationError";
|
||||||
import { isImage } from "../lib/FileType";
|
import { isImage } from "../lib/FileType";
|
||||||
import { toBase64 } from "../lib/Base64";
|
import { toBase64 } from "../lib/Base64";
|
||||||
import jimp from "jimp";
|
import jimp from "jimp";
|
||||||
|
import { gaussianBlur } from "../lib/ImageManipulation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blur Image operation
|
* Blur Image operation
|
||||||
|
@ -64,12 +65,14 @@ class BlurImage extends Operation {
|
||||||
try {
|
try {
|
||||||
switch (blurType){
|
switch (blurType){
|
||||||
case "Fast":
|
case "Fast":
|
||||||
|
if (ENVIRONMENT_IS_WORKER())
|
||||||
|
self.sendStatusMessage("Fast blurring image...");
|
||||||
image.blur(blurAmount);
|
image.blur(blurAmount);
|
||||||
break;
|
break;
|
||||||
case "Gaussian":
|
case "Gaussian":
|
||||||
if (ENVIRONMENT_IS_WORKER())
|
if (ENVIRONMENT_IS_WORKER())
|
||||||
self.sendStatusMessage("Gaussian blurring image. This may take a while...");
|
self.sendStatusMessage("Gaussian blurring image...");
|
||||||
image.gaussian(blurAmount);
|
image = gaussianBlur(image, blurAmount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Operation from "../Operation";
|
||||||
import OperationError from "../errors/OperationError";
|
import OperationError from "../errors/OperationError";
|
||||||
import { isImage } from "../lib/FileType";
|
import { isImage } from "../lib/FileType";
|
||||||
import { toBase64 } from "../lib/Base64";
|
import { toBase64 } from "../lib/Base64";
|
||||||
|
import { gaussianBlur } from "../lib/ImageManipulation";
|
||||||
import jimp from "jimp";
|
import jimp from "jimp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,12 +75,12 @@ class SharpenImage extends Operation {
|
||||||
try {
|
try {
|
||||||
if (ENVIRONMENT_IS_WORKER())
|
if (ENVIRONMENT_IS_WORKER())
|
||||||
self.sendStatusMessage("Sharpening image... (Cloning image)");
|
self.sendStatusMessage("Sharpening image... (Cloning image)");
|
||||||
const blurImage = image.clone();
|
|
||||||
const blurMask = image.clone();
|
const blurMask = image.clone();
|
||||||
|
|
||||||
if (ENVIRONMENT_IS_WORKER())
|
if (ENVIRONMENT_IS_WORKER())
|
||||||
self.sendStatusMessage("Sharpening image... (Blurring cloned image)");
|
self.sendStatusMessage("Sharpening image... (Blurring cloned image)");
|
||||||
blurImage.gaussian(radius);
|
const blurImage = gaussianBlur(image.clone(), radius, 3);
|
||||||
|
|
||||||
|
|
||||||
if (ENVIRONMENT_IS_WORKER())
|
if (ENVIRONMENT_IS_WORKER())
|
||||||
self.sendStatusMessage("Sharpening image... (Creating unsharp mask)");
|
self.sendStatusMessage("Sharpening image... (Creating unsharp mask)");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue