Skip to content

Commit 719c201

Browse files
Reduce the complexity of the from O(kernelSize^2) to O(kernelSize * 2).
1 parent eac7f68 commit 719c201

File tree

1 file changed

+42
-34
lines changed

1 file changed

+42
-34
lines changed

src/utils/image.js

+42-34
Original file line numberDiff line numberDiff line change
@@ -665,30 +665,19 @@ export class RawImage {
665665

666666
const kernel = [];
667667
const center = Math.floor(kernelSize / 2);
668+
const sigma2 = sigma * sigma;
668669
let sum = 0;
669670

670671
for (let i = 0; i < kernelSize; i++) {
671-
kernel[i] = [];
672-
for (let j = 0; j < kernelSize; j++) {
673-
const x = i - center;
674-
const y = j - center;
675-
676-
// Square the numbers.
677-
const x2 = x * x;
678-
const y2 = y * y;
679-
const sigma2 = sigma * sigma;
680-
681-
const value = Math.exp(-(x2 + y2) / (2 * sigma2));
682-
kernel[i][j] = value;
683-
sum += value;
684-
}
672+
const x = i - center;
673+
const value = Math.exp(-(x * x) / (2 * sigma2));
674+
kernel[i] = value;
675+
sum += value;
685676
}
686677

687-
// Normalise the kernel.
678+
// Normalize the kernel
688679
for (let i = 0; i < kernelSize; i++) {
689-
for (let j = 0; j < kernelSize; j++) {
690-
kernel[i][j] /= sum;
691-
}
680+
kernel[i] /= sum;
692681
}
693682

694683
return kernel;
@@ -702,38 +691,57 @@ export class RawImage {
702691
*/
703692
async gaussianBlur(kernelSize = 3, sigma = 1) {
704693
const kernel = this.generateGaussianKernel(kernelSize, sigma);
705-
const output = new Uint8ClampedArray(this.data.length);
706694
const halfSize = Math.floor(kernelSize / 2);
707-
708-
const height = this.height;
709695
const width = this.width;
696+
const height = this.height;
710697
const channels = this.channels;
711698

699+
// Rather than checking an entire grid of elements, we can instead do
700+
// two separate passes with a 1d array (rather than 2d).
701+
// Consider a 3x3 kernel, instead of calculating each pixel 9 times, we
702+
// can instead calculate two sets of 3 values and then combine them.
703+
const horizontalPass = new Float32Array(this.data.length);
704+
const verticalPass = new Uint8ClampedArray(this.data.length);
705+
706+
// Horizontal pass.
712707
for (let y = 0; y < height; y++) {
713708
for (let x = 0; x < width; x++) {
714709
for (let c = 0; c < channels; c++) {
715710
let sum = 0;
716711

717-
for (let ky = -halfSize; ky <= halfSize; ky++) {
718-
for (let kx = -halfSize; kx <= halfSize; kx++) {
719-
const pixelX = Math.min(Math.max(x + kx, 0), width - 1);
720-
const pixelY = Math.min(Math.max(y + ky, 0), height - 1);
712+
for (let kx = -halfSize; kx <= halfSize; kx++) {
713+
const pixelX = Math.min(Math.max(x + kx, 0), width - 1);
714+
const dataIndex = ((y * width) + pixelX) * channels + c;
715+
const kernelValue = kernel[kx + halfSize];
716+
sum += this.data[dataIndex] * kernelValue;
717+
}
718+
719+
const outputIndex = ((y * width) + x) * channels + c;
720+
horizontalPass[outputIndex] = sum;
721+
}
722+
}
723+
}
721724

722-
const kernelValue = kernel[ky + halfSize][kx + halfSize];
723-
const dataIndex = (((pixelY * width) + pixelX) * channels) + c;
725+
// Vertical pass.
726+
for (let y = 0; y < height; y++) {
727+
for (let x = 0; x < width; x++) {
728+
for (let c = 0; c < channels; c++) {
729+
let sum = 0;
724730

725-
sum += this.data[dataIndex] * kernelValue;
726-
}
731+
for (let ky = -halfSize; ky <= halfSize; ky++) {
732+
const pixelY = Math.min(Math.max(y + ky, 0), height - 1);
733+
const dataIndex = ((pixelY * width) + x) * channels + c;
734+
const kernelValue = kernel[ky + halfSize];
735+
sum += horizontalPass[dataIndex] * kernelValue;
727736
}
728737

729-
const outputIndex = (((y * width) + x) * channels) + c;
730-
output[outputIndex] = sum;
738+
const outputIndex = ((y * width) + x) * channels + c;
739+
verticalPass[outputIndex] = sum;
731740
}
732741
}
733742
}
734743

735-
// Update the image data with the blurred result.
736-
this.data = output;
744+
this.data = verticalPass;
737745
return this;
738746
}
739747

@@ -785,7 +793,7 @@ export class RawImage {
785793
/**
786794
* Split this image into individual bands. This method returns an array of individual image bands from an image.
787795
* For example, splitting an "RGB" image creates three new images each containing a copy of one of the original bands (red, green, blue).
788-
*
796+
*
789797
* Inspired by PIL's `Image.split()` [function](https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.split).
790798
* @returns {RawImage[]} An array containing bands.
791799
*/

0 commit comments

Comments
 (0)