UNPKG

multimath

Version:

Core to create fast image math in WebAssembly and JS.

117 lines (99 loc) 4.08 kB
// Unsharp mask filter // // http://stackoverflow.com/a/23322820/1031804 // USM(O) = O + (2 * (Amount / 100) * (O - GB)) // GB - gaussian blur. // // Image is converted from RGB to HSL, unsharp mask is applied to the // lightness channel and then image is converted back to RGB. // 'use strict'; var glur_mono16 = require('glur/mono16'); var hsl_l16 = require('./hsl_l16'); module.exports = function unsharp(img, width, height, amount, radius, threshold) { var r, g, b; var h, s, l; var min, max; var m1, m2, hShifted; var diff, iTimes4; if (amount === 0 || radius < 0.5) { return; } if (radius > 2.0) { radius = 2.0; } var lightness = hsl_l16(img, width, height); var blured = new Uint16Array(lightness); // copy, because blur modify src glur_mono16(blured, width, height, radius); var amountFp = (amount / 100 * 0x1000 + 0.5)|0; var thresholdFp = (threshold * 257)|0; var size = width * height; /* eslint-disable indent */ for (var i = 0; i < size; i++) { diff = 2 * (lightness[i] - blured[i]); if (Math.abs(diff) >= thresholdFp) { iTimes4 = i * 4; r = img[iTimes4]; g = img[iTimes4 + 1]; b = img[iTimes4 + 2]; // convert RGB to HSL // take RGB, 8-bit unsigned integer per each channel // save HSL, H and L are 16-bit unsigned integers, S is 12-bit unsigned integer // math is taken from here: http://www.easyrgb.com/index.php?X=MATH&H=18 // and adopted to be integer (fixed point in fact) for sake of performance max = (r >= g && r >= b) ? r : (g >= r && g >= b) ? g : b; // min and max are in [0..0xff] min = (r <= g && r <= b) ? r : (g <= r && g <= b) ? g : b; l = (max + min) * 257 >> 1; // l is in [0..0xffff] that is caused by multiplication by 257 if (min === max) { h = s = 0; } else { s = (l <= 0x7fff) ? (((max - min) * 0xfff) / (max + min))|0 : (((max - min) * 0xfff) / (2 * 0xff - max - min))|0; // s is in [0..0xfff] // h could be less 0, it will be fixed in backward conversion to RGB, |h| <= 0xffff / 6 h = (r === max) ? (((g - b) * 0xffff) / (6 * (max - min)))|0 : (g === max) ? 0x5555 + ((((b - r) * 0xffff) / (6 * (max - min)))|0) // 0x5555 == 0xffff / 3 : 0xaaaa + ((((r - g) * 0xffff) / (6 * (max - min)))|0); // 0xaaaa == 0xffff * 2 / 3 } // add unsharp mask mask to the lightness channel l += (amountFp * diff + 0x800) >> 12; if (l > 0xffff) { l = 0xffff; } else if (l < 0) { l = 0; } // convert HSL back to RGB // for information about math look above if (s === 0) { r = g = b = l >> 8; } else { m2 = (l <= 0x7fff) ? (l * (0x1000 + s) + 0x800) >> 12 : l + (((0xffff - l) * s + 0x800) >> 12); m1 = 2 * l - m2 >> 8; m2 >>= 8; // save result to RGB channels // R channel hShifted = (h + 0x5555) & 0xffff; // 0x5555 == 0xffff / 3 r = (hShifted >= 0xaaaa) ? m1 // 0xaaaa == 0xffff * 2 / 3 : (hShifted >= 0x7fff) ? m1 + ((m2 - m1) * 6 * (0xaaaa - hShifted) + 0x8000 >> 16) : (hShifted >= 0x2aaa) ? m2 // 0x2aaa == 0xffff / 6 : m1 + ((m2 - m1) * 6 * hShifted + 0x8000 >> 16); // G channel hShifted = h & 0xffff; g = (hShifted >= 0xaaaa) ? m1 // 0xaaaa == 0xffff * 2 / 3 : (hShifted >= 0x7fff) ? m1 + ((m2 - m1) * 6 * (0xaaaa - hShifted) + 0x8000 >> 16) : (hShifted >= 0x2aaa) ? m2 // 0x2aaa == 0xffff / 6 : m1 + ((m2 - m1) * 6 * hShifted + 0x8000 >> 16); // B channel hShifted = (h - 0x5555) & 0xffff; b = (hShifted >= 0xaaaa) ? m1 // 0xaaaa == 0xffff * 2 / 3 : (hShifted >= 0x7fff) ? m1 + ((m2 - m1) * 6 * (0xaaaa - hShifted) + 0x8000 >> 16) : (hShifted >= 0x2aaa) ? m2 // 0x2aaa == 0xffff / 6 : m1 + ((m2 - m1) * 6 * hShifted + 0x8000 >> 16); } img[iTimes4] = r; img[iTimes4 + 1] = g; img[iTimes4 + 2] = b; } } };