136 lines
4.7 KiB
JavaScript
136 lines
4.7 KiB
JavaScript
// Resize convolvers, pure JS implementation
|
|
//
|
|
'use strict';
|
|
|
|
|
|
// Precision of fixed FP values
|
|
//var FIXED_FRAC_BITS = 14;
|
|
|
|
|
|
function clampTo8(i) { return i < 0 ? 0 : (i > 255 ? 255 : i); }
|
|
|
|
|
|
// Convolve image in horizontal directions and transpose output. In theory,
|
|
// transpose allow:
|
|
//
|
|
// - use the same convolver for both passes (this fails due different
|
|
// types of input array and temporary buffer)
|
|
// - making vertical pass by horisonltal lines inprove CPU cache use.
|
|
//
|
|
// But in real life this doesn't work :)
|
|
//
|
|
function convolveHorizontally(src, dest, srcW, srcH, destW, filters) {
|
|
|
|
var r, g, b, a;
|
|
var filterPtr, filterShift, filterSize;
|
|
var srcPtr, srcY, destX, filterVal;
|
|
var srcOffset = 0, destOffset = 0;
|
|
|
|
// For each row
|
|
for (srcY = 0; srcY < srcH; srcY++) {
|
|
filterPtr = 0;
|
|
|
|
// Apply precomputed filters to each destination row point
|
|
for (destX = 0; destX < destW; destX++) {
|
|
// Get the filter that determines the current output pixel.
|
|
filterShift = filters[filterPtr++];
|
|
filterSize = filters[filterPtr++];
|
|
|
|
srcPtr = (srcOffset + (filterShift * 4))|0;
|
|
|
|
r = g = b = a = 0;
|
|
|
|
// Apply the filter to the row to get the destination pixel r, g, b, a
|
|
for (; filterSize > 0; filterSize--) {
|
|
filterVal = filters[filterPtr++];
|
|
|
|
// Use reverse order to workaround deopts in old v8 (node v.10)
|
|
// Big thanks to @mraleph (Vyacheslav Egorov) for the tip.
|
|
a = (a + filterVal * src[srcPtr + 3])|0;
|
|
b = (b + filterVal * src[srcPtr + 2])|0;
|
|
g = (g + filterVal * src[srcPtr + 1])|0;
|
|
r = (r + filterVal * src[srcPtr])|0;
|
|
srcPtr = (srcPtr + 4)|0;
|
|
}
|
|
|
|
// Bring this value back in range. All of the filter scaling factors
|
|
// are in fixed point with FIXED_FRAC_BITS bits of fractional part.
|
|
//
|
|
// (!) Add 1/2 of value before clamping to get proper rounding. In other
|
|
// case brightness loss will be noticeable if you resize image with white
|
|
// border and place it on white background.
|
|
//
|
|
dest[destOffset + 3] = clampTo8((a + (1 << 13)) >> 14/*FIXED_FRAC_BITS*/);
|
|
dest[destOffset + 2] = clampTo8((b + (1 << 13)) >> 14/*FIXED_FRAC_BITS*/);
|
|
dest[destOffset + 1] = clampTo8((g + (1 << 13)) >> 14/*FIXED_FRAC_BITS*/);
|
|
dest[destOffset] = clampTo8((r + (1 << 13)) >> 14/*FIXED_FRAC_BITS*/);
|
|
destOffset = (destOffset + srcH * 4)|0;
|
|
}
|
|
|
|
destOffset = ((srcY + 1) * 4)|0;
|
|
srcOffset = ((srcY + 1) * srcW * 4)|0;
|
|
}
|
|
}
|
|
|
|
// Technically, convolvers are the same. But input array and temporary
|
|
// buffer can be of different type (especially, in old browsers). So,
|
|
// keep code in separate functions to avoid deoptimizations & speed loss.
|
|
|
|
function convolveVertically(src, dest, srcW, srcH, destW, filters) {
|
|
|
|
var r, g, b, a;
|
|
var filterPtr, filterShift, filterSize;
|
|
var srcPtr, srcY, destX, filterVal;
|
|
var srcOffset = 0, destOffset = 0;
|
|
|
|
// For each row
|
|
for (srcY = 0; srcY < srcH; srcY++) {
|
|
filterPtr = 0;
|
|
|
|
// Apply precomputed filters to each destination row point
|
|
for (destX = 0; destX < destW; destX++) {
|
|
// Get the filter that determines the current output pixel.
|
|
filterShift = filters[filterPtr++];
|
|
filterSize = filters[filterPtr++];
|
|
|
|
srcPtr = (srcOffset + (filterShift * 4))|0;
|
|
|
|
r = g = b = a = 0;
|
|
|
|
// Apply the filter to the row to get the destination pixel r, g, b, a
|
|
for (; filterSize > 0; filterSize--) {
|
|
filterVal = filters[filterPtr++];
|
|
|
|
// Use reverse order to workaround deopts in old v8 (node v.10)
|
|
// Big thanks to @mraleph (Vyacheslav Egorov) for the tip.
|
|
a = (a + filterVal * src[srcPtr + 3])|0;
|
|
b = (b + filterVal * src[srcPtr + 2])|0;
|
|
g = (g + filterVal * src[srcPtr + 1])|0;
|
|
r = (r + filterVal * src[srcPtr])|0;
|
|
srcPtr = (srcPtr + 4)|0;
|
|
}
|
|
|
|
// Bring this value back in range. All of the filter scaling factors
|
|
// are in fixed point with FIXED_FRAC_BITS bits of fractional part.
|
|
//
|
|
// (!) Add 1/2 of value before clamping to get proper rounding. In other
|
|
// case brightness loss will be noticeable if you resize image with white
|
|
// border and place it on white background.
|
|
//
|
|
dest[destOffset + 3] = clampTo8((a + (1 << 13)) >> 14/*FIXED_FRAC_BITS*/);
|
|
dest[destOffset + 2] = clampTo8((b + (1 << 13)) >> 14/*FIXED_FRAC_BITS*/);
|
|
dest[destOffset + 1] = clampTo8((g + (1 << 13)) >> 14/*FIXED_FRAC_BITS*/);
|
|
dest[destOffset] = clampTo8((r + (1 << 13)) >> 14/*FIXED_FRAC_BITS*/);
|
|
destOffset = (destOffset + srcH * 4)|0;
|
|
}
|
|
|
|
destOffset = ((srcY + 1) * 4)|0;
|
|
srcOffset = ((srcY + 1) * srcW * 4)|0;
|
|
}
|
|
}
|
|
|
|
|
|
module.exports = {
|
|
convolveHorizontally,
|
|
convolveVertically
|
|
};
|