Test/node_modules/pica/lib/mm_unsharp_mask/unsharp_mask.js
2026-04-09 22:54:00 +07:00

89 lines
2.7 KiB
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 HSV, unsharp mask is applied to the
// brightness channel and then image is converted back to RGB.
//
'use strict';
var glur_mono16 = require('glur/mono16');
function hsv_v16(img, width, height) {
var size = width * height;
var out = new Uint16Array(size);
var r, g, b, max;
for (var i = 0; i < size; i++) {
r = img[4 * i];
g = img[4 * i + 1];
b = img[4 * i + 2];
max = (r >= g && r >= b) ? r : (g >= b && g >= r) ? g : b;
out[i] = max << 8;
}
return out;
}
module.exports = function unsharp(img, width, height, amount, radius, threshold) {
var v1, v2, vmul;
var diff, iTimes4;
if (amount === 0 || radius < 0.5) {
return;
}
if (radius > 2.0) {
radius = 2.0;
}
var brightness = hsv_v16(img, width, height);
var blured = new Uint16Array(brightness); // copy, because blur modify src
glur_mono16(blured, width, height, radius);
var amountFp = (amount / 100 * 0x1000 + 0.5)|0;
var thresholdFp = threshold << 8;
var size = width * height;
/* eslint-disable indent */
for (var i = 0; i < size; i++) {
v1 = brightness[i];
diff = v1 - blured[i];
if (Math.abs(diff) >= thresholdFp) {
// add unsharp mask to the brightness channel
v2 = v1 + ((amountFp * diff + 0x800) >> 12);
// Both v1 and v2 are within [0.0 .. 255.0] (0000-FF00) range, never going into
// [255.003 .. 255.996] (FF01-FFFF). This allows to round this value as (x+.5)|0
// later without overflowing.
v2 = v2 > 0xff00 ? 0xff00 : v2;
v2 = v2 < 0x0000 ? 0x0000 : v2;
// Avoid division by 0. V=0 means rgb(0,0,0), unsharp with unsharpAmount>0 cannot
// change this value (because diff between colors gets inflated), so no need to verify correctness.
v1 = v1 !== 0 ? v1 : 1;
// Multiplying V in HSV model by a constant is equivalent to multiplying each component
// in RGB by the same constant (same for HSL), see also:
// https://beesbuzz.biz/code/16-hsv-color-transforms
vmul = ((v2 << 12) / v1)|0;
// Result will be in [0..255] range because:
// - all numbers are positive
// - r,g,b <= (v1/256)
// - r,g,b,(v1/256),(v2/256) <= 255
// So highest this number can get is X*255/X+0.5=255.5 which is < 256 and rounds down.
iTimes4 = i * 4;
img[iTimes4] = (img[iTimes4] * vmul + 0x800) >> 12; // R
img[iTimes4 + 1] = (img[iTimes4 + 1] * vmul + 0x800) >> 12; // G
img[iTimes4 + 2] = (img[iTimes4 + 2] * vmul + 0x800) >> 12; // B
}
}
};