multimath
Version:
Core to create fast image math in WebAssembly and JS.
117 lines (99 loc) • 4.08 kB
JavaScript
// 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.
//
;
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;
}
}
};