function quadBlurRGBA (pixels, width, height, radius) {
"use strict";
if (!(pixels && pixels.length && radius > 0 && width > 0 && height > 0)) return;
if (radius <= 1) {
return stackBlurRGBA(pixels, width, height, radius);
}
//
// <──────────> stackLeftIn
//
// stackRightOut <──────────>
//
// stackRightIn <──────────>
//
// ### Stack accumulators
//
// <───────stackLeft──────>
//
// <──────stackRight──────>
//
// ### Quadratic stack accumulator
//
// <──────────────────quadStack──────────────────>
//
// Indices to keep track of and what they are for:
// 1 23 45 67 8
//
// 1) stackLeftOut leftmost pixel
// 2) stackLeftOut rightmost pixel
// 3) stackLeftIn leftmost pixel
// 4) stackRightOut leftmost pixel
// 5) stackLeftIn rightmost pixel,
// equals central pixel position.
// 6) stackRightOut rightmost pixel
// 7) stackRightIn leftmost pixel
// 8) stackRightIn rightmost pixel
const stackWidth = radius + 1;
const accWidth = (stackWidth + 1) / 2 | 0;
const stackLeftOut = [0, 0, 0, 0]
const stackLeftIn = [0, 0, 0, 0]
const stackRightOut = [0, 0, 0, 0]
const stackRightIn = [0, 0, 0, 0]
const stackLeft = [0, 0, 0, 0]
const stackRight = [0, 0, 0, 0]
const quadStack = [0, 0, 0, 0]
const quadBuffer = new Float64Array(pixels.length);
const quadWeight = accWidth * (stackWidth - accWidth) * stackWidth;
// blur rows
for (let y = 0; y < height; y++) {
const yw = y * width * 4;
// Initiate on leftmost pixel.
stackLeftOut[0] = stackLeftIn[0] = stackRightOut[0] = stackRightIn[0] = pixels[yw+0] * accWidth;
stackLeftOut[1] = stackLeftIn[1] = stackRightOut[1] = stackRightIn[1] = pixels[yw+1] * accWidth;
stackLeftOut[2] = stackLeftIn[2] = stackRightOut[2] = stackRightIn[2] = pixels[yw+2] * accWidth;
stackLeftOut[3] = stackLeftIn[3] = stackRightOut[3] = stackRightIn[3] = pixels[yw+3] * accWidth;
// left and right stacks are initually just accumulators times their width
stackLeft[0] = stackRight[0] = stackLeftOut[0] * (stackWidth - accWidth);
stackLeft[1] = stackRight[1] = stackLeftOut[1] * (stackWidth - accWidth);
stackLeft[2] = stackRight[2] = stackLeftOut[2] * (stackWidth - accWidth);
stackLeft[3] = stackRight[3] = stackLeftOut[3] * (stackWidth - accWidth);
quadStack[0] = stackLeft[0] * stackWidth;
quadStack[1] = stackLeft[1] * stackWidth;
quadStack[2] = stackLeft[2] * stackWidth;
quadStack[3] = stackLeft[3] * stackWidth;
const ywMax = yw + width * 4 - 4;
for(let x = 1-radius; x < 0; x++) {
const idx = x*4 + yw;
const idxStackRightOutLP = constrain(idx, yw, ywMax)
const idxStackRightOutRP = constrain(idx + accWidth * 4, yw, ywMax);
const idxStackRightInLP = constrain(idx + (radius - accWidth) * 4, yw, ywMax);
const idxStackRightInRP = constrain(idx + radius * 4, yw, ywMax);
stackRightOut[0] += pixels[idxStackRightOutRP+0] - pixels[idxStackRightOutLP+0];
stackRightOut[1] += pixels[idxStackRightOutRP+1] - pixels[idxStackRightOutLP+1];
stackRightOut[2] += pixels[idxStackRightOutRP+2] - pixels[idxStackRightOutLP+2];
stackRightOut[3] += pixels[idxStackRightOutRP+3] - pixels[idxStackRightOutLP+3];
stackRightIn[0] += pixels[idxStackRightInRP+0] - pixels[idxStackRightInLP+0];
stackRightIn[1] += pixels[idxStackRightInRP+1] - pixels[idxStackRightInLP+1];
stackRightIn[2] += pixels[idxStackRightInRP+2] - pixels[idxStackRightInLP+2];
stackRightIn[3] += pixels[idxStackRightInRP+3] - pixels[idxStackRightInLP+3];
stackRight[0] += stackRightIn[0] - stackRightOut[0];
stackRight[1] += stackRightIn[1] - stackRightOut[1];
stackRight[2] += stackRightIn[2] - stackRightOut[2];
stackRight[3] += stackRightIn[3] - stackRightOut[3];
quadStack[0] += stackRight[0] - stackLeft[0];
quadStack[1] += stackRight[1] - stackLeft[1];
quadStack[2] += stackRight[2] - stackLeft[2];
quadStack[3] += stackRight[3] - stackLeft[3];
}
for (let x = 0; x < width; x++) {
const idx = x*4 + yw;
const idxStackLeftOutLP = constrain(idx - radius * 4, yw, ywMax);
const idxStackLeftOutRP = constrain(idx - (radius - accWidth) * 4, yw, ywMax);
const idxStackLeftInLP = constrain(idx - accWidth * 4, yw, ywMax);
const idxStackLeftInRP = constrain(idx, yw, ywMax);
const idxStackRightOutLP = constrain(idx, yw, ywMax);
const idxStackRightOutRP = constrain(idx + accWidth * 4, yw, ywMax);
const idxStackRightInLP = constrain(idx + (radius - accWidth) * 4, yw, ywMax);
const idxStackRightInRP = constrain(idx + radius * 4, yw, ywMax);
stackLeftOut[0] += pixels[idxStackLeftOutRP + 0] - pixels[idxStackLeftOutLP + 0];
stackLeftOut[1] += pixels[idxStackLeftOutRP + 1] - pixels[idxStackLeftOutLP + 1];
stackLeftOut[2] += pixels[idxStackLeftOutRP + 2] - pixels[idxStackLeftOutLP + 2];
stackLeftOut[3] += pixels[idxStackLeftOutRP + 3] - pixels[idxStackLeftOutLP + 3];
stackLeftIn[0] += pixels[idxStackLeftInRP + 0] - pixels[idxStackLeftInLP + 0];
stackLeftIn[1] += pixels[idxStackLeftInRP + 1] - pixels[idxStackLeftInLP + 1];
stackLeftIn[2] += pixels[idxStackLeftInRP + 2] - pixels[idxStackLeftInLP + 2];
stackLeftIn[3] += pixels[idxStackLeftInRP + 3] - pixels[idxStackLeftInLP + 3];
stackRightOut[0] += pixels[idxStackRightOutRP + 0] - pixels[idxStackRightOutLP + 0];
stackRightOut[1] += pixels[idxStackRightOutRP + 1] - pixels[idxStackRightOutLP + 1];
stackRightOut[2] += pixels[idxStackRightOutRP + 2] - pixels[idxStackRightOutLP + 2];
stackRightOut[3] += pixels[idxStackRightOutRP + 3] - pixels[idxStackRightOutLP + 3];
stackRightIn[0] += pixels[idxStackRightInRP + 0] - pixels[idxStackRightInLP + 0];
stackRightIn[1] += pixels[idxStackRightInRP + 1] - pixels[idxStackRightInLP + 1];
stackRightIn[2] += pixels[idxStackRightInRP + 2] - pixels[idxStackRightInLP + 2];
stackRightIn[3] += pixels[idxStackRightInRP + 3] - pixels[idxStackRightInLP + 3];
stackLeft[0] += stackLeftIn[0] - stackLeftOut[0];
stackLeft[1] += stackLeftIn[1] - stackLeftOut[1];
stackLeft[2] += stackLeftIn[2] - stackLeftOut[2];
stackLeft[3] += stackLeftIn[3] - stackLeftOut[3];
stackRight[0] += stackRightIn[0] - stackRightOut[0];
stackRight[1] += stackRightIn[1] - stackRightOut[1];
stackRight[2] += stackRightIn[2] - stackRightOut[2];
stackRight[3] += stackRightIn[3] - stackRightOut[3];
quadStack[0] += stackRight[0] - stackLeft[0];
quadStack[1] += stackRight[1] - stackLeft[1];
quadStack[2] += stackRight[2] - stackLeft[2];
quadStack[3] += stackRight[3] - stackLeft[3];
quadBuffer[idx + 0] = quadStack[0];
quadBuffer[idx + 1] = quadStack[1];
quadBuffer[idx + 2] = quadStack[2];
quadBuffer[idx + 3] = quadStack[3];
}
}
// blur columns
for (let x = 0; x < width; x++) {
const x4 = x * 4;
// Initiate on top pixel.
stackLeftOut[0] = stackLeftIn[0] = stackRightOut[0] = stackRightIn[0] = quadBuffer[x4] * accWidth;
stackLeftOut[1] = stackLeftIn[1] = stackRightOut[1] = stackRightIn[1] = quadBuffer[x4+1] * accWidth;
stackLeftOut[2] = stackLeftIn[2] = stackRightOut[2] = stackRightIn[2] = quadBuffer[x4+2] * accWidth;
stackLeftOut[3] = stackLeftIn[3] = stackRightOut[3] = stackRightIn[3] = quadBuffer[x4+3] * accWidth;
// left and right stacks are initually just accumulators times their width
stackLeft[0] = stackRight[0] = stackLeftOut[0] * (stackWidth - accWidth);
stackLeft[1] = stackRight[1] = stackLeftOut[1] * (stackWidth - accWidth);
stackLeft[2] = stackRight[2] = stackLeftOut[2] * (stackWidth - accWidth);
stackLeft[3] = stackRight[3] = stackLeftOut[3] * (stackWidth - accWidth);
quadStack[0] = stackLeft[0] * stackWidth;
quadStack[1] = stackLeft[1] * stackWidth;
quadStack[2] = stackLeft[2] * stackWidth;
quadStack[3] = stackLeft[3] * stackWidth;
for(let y = 1-radius; y < 0; y++) {
const idx = x4 + y*4*width;
const idxStackRightOutLP = x4 + constrain(y, 0, height-1)*4*width;
const idxStackRightOutRP = x4 + constrain(y + accWidth, 0, height-1)*4*width;
const idxStackRightInLP = x4 + constrain(y + radius - accWidth, 0, height-1)*4*width;
const idxStackRightInRP = x4 + constrain(y + radius, 0, height-1)*4*width;
stackRightOut[0] += quadBuffer[idxStackRightOutRP+0] - quadBuffer[idxStackRightOutLP+0];
stackRightOut[1] += quadBuffer[idxStackRightOutRP+1] - quadBuffer[idxStackRightOutLP+1];
stackRightOut[2] += quadBuffer[idxStackRightOutRP+2] - quadBuffer[idxStackRightOutLP+2];
stackRightOut[3] += quadBuffer[idxStackRightOutRP+3] - quadBuffer[idxStackRightOutLP+3];
stackRightIn[0] += quadBuffer[idxStackRightInRP+0] - quadBuffer[idxStackRightInLP+0];
stackRightIn[1] += quadBuffer[idxStackRightInRP+1] - quadBuffer[idxStackRightInLP+1];
stackRightIn[2] += quadBuffer[idxStackRightInRP+2] - quadBuffer[idxStackRightInLP+2];
stackRightIn[3] += quadBuffer[idxStackRightInRP+3] - quadBuffer[idxStackRightInLP+3];
stackRight[0] += stackRightIn[0] - stackRightOut[0];
stackRight[1] += stackRightIn[1] - stackRightOut[1];
stackRight[2] += stackRightIn[2] - stackRightOut[2];
stackRight[3] += stackRightIn[3] - stackRightOut[3];
quadStack[0] += stackRight[0] - stackLeft[0];
quadStack[1] += stackRight[1] - stackLeft[1];
quadStack[2] += stackRight[2] - stackLeft[2];
quadStack[3] += stackRight[3] - stackLeft[3];
}
for (let y = 0; y < height; y++) {
const idx = x4 + y*4*width;
const idxStackLeftOutLP = x4 + constrain(y - radius, 0, height-1)*4*width;
const idxStackLeftOutRP = x4 + constrain(y - radius + accWidth, 0, height-1)*4*width;
const idxStackLeftInLP = x4 + constrain(y - accWidth, 0, height-1)*4*width;
const idxStackLeftInRP = x4 + constrain(y, 0, height-1)*4*width;
const idxStackRightOutLP = x4 + constrain(y, 0, height-1)*4*width;
const idxStackRightOutRP = x4 + constrain(y + accWidth, 0, height-1)*4*width;
const idxStackRightInLP = x4 + constrain(y + radius - accWidth, 0, height-1)*4*width;
const idxStackRightInRP = x4 + constrain(y + radius, 0, height-1)*4*width;
stackLeftOut[0] += quadBuffer[idxStackLeftOutRP + 0] - quadBuffer[idxStackLeftOutLP + 0];
stackLeftOut[1] += quadBuffer[idxStackLeftOutRP + 1] - quadBuffer[idxStackLeftOutLP + 1];
stackLeftOut[2] += quadBuffer[idxStackLeftOutRP + 2] - quadBuffer[idxStackLeftOutLP + 2];
stackLeftOut[3] += quadBuffer[idxStackLeftOutRP + 3] - quadBuffer[idxStackLeftOutLP + 3];
stackLeftIn[0] += quadBuffer[idxStackLeftInRP + 0] - quadBuffer[idxStackLeftInLP + 0];
stackLeftIn[1] += quadBuffer[idxStackLeftInRP + 1] - quadBuffer[idxStackLeftInLP + 1];
stackLeftIn[2] += quadBuffer[idxStackLeftInRP + 2] - quadBuffer[idxStackLeftInLP + 2];
stackLeftIn[3] += quadBuffer[idxStackLeftInRP + 3] - quadBuffer[idxStackLeftInLP + 3];
stackRightOut[0] += quadBuffer[idxStackRightOutRP + 0] - quadBuffer[idxStackRightOutLP + 0];
stackRightOut[1] += quadBuffer[idxStackRightOutRP + 1] - quadBuffer[idxStackRightOutLP + 1];
stackRightOut[2] += quadBuffer[idxStackRightOutRP + 2] - quadBuffer[idxStackRightOutLP + 2];
stackRightOut[3] += quadBuffer[idxStackRightOutRP + 3] - quadBuffer[idxStackRightOutLP + 3];
stackRightIn[0] += quadBuffer[idxStackRightInRP + 0] - quadBuffer[idxStackRightInLP + 0];
stackRightIn[1] += quadBuffer[idxStackRightInRP + 1] - quadBuffer[idxStackRightInLP + 1];
stackRightIn[2] += quadBuffer[idxStackRightInRP + 2] - quadBuffer[idxStackRightInLP + 2];
stackRightIn[3] += quadBuffer[idxStackRightInRP + 3] - quadBuffer[idxStackRightInLP + 3];
stackLeft[0] += stackLeftIn[0] - stackLeftOut[0];
stackLeft[1] += stackLeftIn[1] - stackLeftOut[1];
stackLeft[2] += stackLeftIn[2] - stackLeftOut[2];
stackLeft[3] += stackLeftIn[3] - stackLeftOut[3];
stackRight[0] += stackRightIn[0] - stackRightOut[0];
stackRight[1] += stackRightIn[1] - stackRightOut[1];
stackRight[2] += stackRightIn[2] - stackRightOut[2];
stackRight[3] += stackRightIn[3] - stackRightOut[3];
pixels[idx + 0] = (quadStack[0] += stackRight[0] - stackLeft[0]) / (quadWeight * quadWeight) & 0xFF;
pixels[idx + 1] = (quadStack[1] += stackRight[1] - stackLeft[1]) / (quadWeight * quadWeight) & 0xFF;
pixels[idx + 2] = (quadStack[2] += stackRight[2] - stackLeft[2]) / (quadWeight * quadWeight) & 0xFF;
pixels[idx + 3] = (quadStack[3] += stackRight[3] - stackLeft[3]) / (quadWeight * quadWeight) & 0xFF;
}
}
return pixels;
}