Przeglądaj źródła

Add new implementation of gaussian blur.
Changed SharpenImage to use the new algorithm.

j433866 6 lat temu
rodzic
commit
2cd3e9cacd

+ 252 - 0
src/core/lib/ImageManipulation.mjs

@@ -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;
+}

+ 5 - 2
src/core/operations/BlurImage.mjs

@@ -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...");
-                    image.gaussian(blurAmount);
+                        self.sendStatusMessage("Gaussian blurring image...");
+                    image = gaussianBlur(image, blurAmount);
                     break;
                     break;
             }
             }
 
 

+ 3 - 2
src/core/operations/SharpenImage.mjs

@@ -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)");