/** * @author j433866 [j433866@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 { toBase64 } from "../lib/Base64.mjs"; import { gaussianBlur } from "../lib/ImageManipulation.mjs"; import { isWorkerEnvironment } from "../Utils.mjs"; import jimplib from "jimp/es/index.js"; const jimp = jimplib.default ? jimplib.default : jimplib; /** * 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 = "ArrayBuffer"; this.outputType = "ArrayBuffer"; 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 {ArrayBuffer} 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(input); } catch (err) { throw new OperationError(`Error loading image. (${err})`); } try { if (isWorkerEnvironment()) self.sendStatusMessage("Sharpening image... (Cloning image)"); const blurMask = image.clone(); if (isWorkerEnvironment()) self.sendStatusMessage("Sharpening image... (Blurring cloned image)"); const blurImage = gaussianBlur(image.clone(), radius); if (isWorkerEnvironment()) 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 (isWorkerEnvironment()) 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; } }); let imageBuffer; if (image.getMIME() === "image/gif") { imageBuffer = await image.getBufferAsync(jimp.MIME_PNG); } else { imageBuffer = await image.getBufferAsync(jimp.AUTO); } return imageBuffer.buffer; } catch (err) { throw new OperationError(`Error sharpening image. (${err})`); } } /** * Displays the sharpened image using HTML for web apps * @param {ArrayBuffer} data * @returns {html} */ present(data) { if (!data.byteLength) return ""; const dataArray = new Uint8Array(data); const type = isImage(dataArray); if (!type) { throw new OperationError("Invalid file type."); } return ``; } } export default SharpenImage;