j433866 6 лет назад
Родитель
Сommit
dfbc1beccd
2 измененных файлов с 163 добавлено и 1 удалено
  1. 2 1
      src/core/config/Categories.json
  2. 161 0
      src/core/operations/SharpenImage.mjs

+ 2 - 1
src/core/config/Categories.json

@@ -374,7 +374,8 @@
             "Image Filter",
             "Contain Image",
             "Cover Image",
-            "Image Hue/Saturation/Lightness"
+            "Image Hue/Saturation/Lightness",
+            "Sharpen Image"
         ]
     },
     {

+ 161 - 0
src/core/operations/SharpenImage.mjs

@@ -0,0 +1,161 @@
+/**
+ * @author j433866 [j433866@gmail.com]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+import { isImage } from "../lib/FileType";
+import { toBase64 } from "../lib/Base64";
+import jimp from "jimp";
+
+/**
+ * Sharpen Image operation
+ */
+class SharpenImage extends Operation {
+
+    /**
+     * SharpenImage constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Sharpen Image";
+        this.module = "Image";
+        this.description = "Sharpens an image (Unsharp mask)";
+        this.infoURL = "https://wikipedia.org/wiki/Unsharp_masking";
+        this.inputType = "byteArray";
+        this.outputType = "byteArray";
+        this.presentType = "html";
+        this.args = [
+            {
+                name: "Radius",
+                type: "number",
+                value: 2,
+                min: 1
+            },
+            {
+                name: "Amount",
+                type: "number",
+                value: 1,
+                min: 0,
+                step: 0.1
+            },
+            {
+                name: "Threshold",
+                type: "number",
+                value: 10,
+                min: 0,
+                max: 100
+            }
+        ];
+    }
+
+    /**
+     * @param {byteArray} input
+     * @param {Object[]} args
+     * @returns {byteArray}
+     */
+    async run(input, args) {
+        const [radius, amount, threshold] = args;
+
+        if (!isImage(input)){
+            throw new OperationError("Invalid file type.");
+        }
+
+        let image;
+        try {
+            image = await jimp.read(Buffer.from(input));
+        } catch (err) {
+            throw new OperationError(`Error loading image. (${err})`);
+        }
+
+        try {
+            if (ENVIRONMENT_IS_WORKER())
+                self.sendStatusMessage("Sharpening image... (Cloning image)");
+            const blurImage = image.clone();
+            const blurMask = image.clone();
+
+            if (ENVIRONMENT_IS_WORKER())
+                self.sendStatusMessage("Sharpening image... (Blurring cloned image)");
+            blurImage.gaussian(radius);
+
+            if (ENVIRONMENT_IS_WORKER())
+                self.sendStatusMessage("Sharpening image... (Creating unsharp mask)");
+            blurMask.scan(0, 0, blurMask.bitmap.width, blurMask.bitmap.height, function(x, y, idx) {
+                const blurRed = blurImage.bitmap.data[idx];
+                const blurGreen = blurImage.bitmap.data[idx + 1];
+                const blurBlue = blurImage.bitmap.data[idx + 2];
+
+                const normalRed = this.bitmap.data[idx];
+                const normalGreen = this.bitmap.data[idx + 1];
+                const normalBlue = this.bitmap.data[idx + 2];
+
+                // Subtract blurred pixel value from normal image
+                this.bitmap.data[idx] = (normalRed > blurRed) ? normalRed - blurRed : 0;
+                this.bitmap.data[idx + 1] = (normalGreen > blurGreen) ? normalGreen - blurGreen : 0;
+                this.bitmap.data[idx + 2] = (normalBlue > blurBlue) ? normalBlue - blurBlue : 0;
+            });
+
+            if (ENVIRONMENT_IS_WORKER())
+                self.sendStatusMessage("Sharpening image... (Merging with unsharp mask)");
+            image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y, idx) {
+                let maskRed = blurMask.bitmap.data[idx];
+                let maskGreen = blurMask.bitmap.data[idx + 1];
+                let maskBlue = blurMask.bitmap.data[idx + 2];
+
+                const normalRed = this.bitmap.data[idx];
+                const normalGreen = this.bitmap.data[idx + 1];
+                const normalBlue = this.bitmap.data[idx + 2];
+
+                // Calculate luminance
+                const maskLuminance = (0.2126 * maskRed + 0.7152 * maskGreen + 0.0722 * maskBlue);
+                const normalLuminance = (0.2126 * normalRed + 0.7152 * normalGreen + 0.0722 * normalBlue);
+
+                let luminanceDiff;
+                if (maskLuminance > normalLuminance) {
+                    luminanceDiff = maskLuminance - normalLuminance;
+                } else {
+                    luminanceDiff = normalLuminance - maskLuminance;
+                }
+
+                // Scale mask colours by amount
+                maskRed = maskRed * amount;
+                maskGreen = maskGreen * amount;
+                maskBlue = maskBlue * amount;
+
+                // Only change pixel value if the difference is higher than threshold
+                if ((luminanceDiff / 255) * 100 >= threshold) {
+                    this.bitmap.data[idx] = (normalRed + maskRed) <= 255 ? normalRed + maskRed : 255;
+                    this.bitmap.data[idx + 1] = (normalGreen + maskGreen) <= 255 ? normalGreen + maskGreen : 255;
+                    this.bitmap.data[idx + 2] = (normalBlue + maskBlue) <= 255 ? normalBlue + maskBlue : 255;
+                }
+            });
+
+            const imageBuffer = await image.getBufferAsync(jimp.AUTO);
+            return [...imageBuffer];
+        } catch (err) {
+            throw new OperationError(`Error sharpening image. (${err})`);
+        }
+    }
+
+    /**
+     * Displays the sharpened image using HTML for web apps
+     * @param {byteArray} data
+     * @returns {html}
+     */
+    present(data) {
+        if (!data.length) return "";
+
+        const type = isImage(data);
+        if (!type) {
+            throw new OperationError("Invalid image type.");
+        }
+
+        return `<img src="data:${type};base64,${toBase64(data)}">`;
+    }
+
+}
+
+export default SharpenImage;