/** * Image manipulation resources * * @author j433866 [j433866@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import OperationError from "../errors/OperationError.mjs"; /** * Gaussian blurs an image. * * @param {jimp} input * @param {number} 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) { throw new OperationError(`Error blurring image. (${err})`); } return input; } /** * * @param {number} radius * @param {number} 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 {number} width * @param {number} height * @param {number} 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; }