Skip to content

Commit d091c4b

Browse files
Filmbostock
andauthored
d3.blur (#151)
* d3.blur fixes #56 Co-authored-by: Mike Bostock <mbostock@gmail.com>
1 parent 1d1e460 commit d091c4b

File tree

4 files changed

+356
-0
lines changed

4 files changed

+356
-0
lines changed

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,44 @@ Returns an array of arrays, where the *i*th array contains the *i*th element fro
683683
d3.zip([1, 2], [3, 4]); // returns [[1, 3], [2, 4]]
684684
```
685685

686+
#### Blur
687+
688+
<a name="blur" href="#blur">#</a> d3.<b>blur</b>(*data*, *radius*) · [Source](https://github.com/d3/d3-array/blob/main/src/blur.js), [Examples](https://observablehq.com/@d3/d3-blur)
689+
690+
Blurs an array of *data* in-place by applying three iterations of a moving average transform, for a fast approximation of a gaussian kernel of the given *radius*, a non-negative number, and returns the array.
691+
692+
```js
693+
const randomWalk = d3.cumsum({length: 1000}, () => Math.random() - 0.5);
694+
blur(randomWalk, 5);
695+
```
696+
697+
Copy the data if you don’t want to smooth it in-place:
698+
```js
699+
const smoothed = blur(randomWalk.slice(), 5);
700+
```
701+
702+
<a name="blur2" href="#blur2">#</a> d3.<b>blur2</b>({*data*, *width*[, *height*]}, *rx*[, *ry*]) · [Source](https://github.com/d3/d3-array/blob/main/src/blur.js), [Examples](https://observablehq.com/@d3/d3-blur)
703+
704+
Blurs a matrix of the given *width* and *height* in-place, by applying an horizontal blur of radius *rx* and a vertical blur or radius *ry* (which defaults to *rx*). The matrix *data* is stored in a flat array, used to determine the *height* if it is not specified. Returns the blurred {data, width, height}.
705+
706+
```js
707+
data = [
708+
1, 0, 0,
709+
0, 0, 0,
710+
0, 0, 1
711+
];
712+
blur2({data, width: 3}, 1);
713+
```
714+
715+
<a name="blurImage" href="#blurImage">#</a> d3.<b>blurImage</b>(*imageData*, *rx*[, *ry*]) · [Source](https://github.com/d3/d3-array/blob/main/src/blur.js), [Examples](https://observablehq.com/@d3/d3-blurimage)
716+
717+
Blurs an [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) structure in-place, blurring each of the RGBA layers independently by applying an horizontal blur of radius *rx* and a vertical blur or radius *ry* (which defaults to *rx*). Returns the blurred ImageData.
718+
719+
```js
720+
const imData = context.getImageData(0, 0, width, height);
721+
blurImage(imData, 5);
722+
```
723+
686724
### Iterables
687725

688726
These are equivalent to built-in array methods, but work with any iterable including Map, Set, and Generator.

src/blur.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
export function blur(values, r) {
2+
if (!((r = +r) >= 0)) throw new RangeError("invalid r");
3+
let length = values.length;
4+
if (!((length = Math.floor(length)) >= 0)) throw new RangeError("invalid length");
5+
if (!length || !r) return values;
6+
const blur = blurf(r);
7+
const temp = values.slice();
8+
blur(values, temp, 0, length, 1);
9+
blur(temp, values, 0, length, 1);
10+
blur(values, temp, 0, length, 1);
11+
return values;
12+
}
13+
14+
export const blur2 = Blur2(blurf);
15+
16+
export const blurImage = Blur2(blurfImage);
17+
18+
function Blur2(blur) {
19+
return function(data, rx, ry = rx) {
20+
if (!((rx = +rx) >= 0)) throw new RangeError("invalid rx");
21+
if (!((ry = +ry) >= 0)) throw new RangeError("invalid ry");
22+
let {data: values, width, height} = data;
23+
if (!((width = Math.floor(width)) >= 0)) throw new RangeError("invalid width");
24+
if (!((height = Math.floor(height !== undefined ? height : values.length / width)) >= 0)) throw new RangeError("invalid height");
25+
if (!width || !height || (!rx && !ry)) return data;
26+
const blurx = rx && blur(rx);
27+
const blury = ry && blur(ry);
28+
const temp = values.slice();
29+
if (blurx && blury) {
30+
blurh(blurx, temp, values, width, height);
31+
blurh(blurx, values, temp, width, height);
32+
blurh(blurx, temp, values, width, height);
33+
blurv(blury, values, temp, width, height);
34+
blurv(blury, temp, values, width, height);
35+
blurv(blury, values, temp, width, height);
36+
} else if (blurx) {
37+
blurh(blurx, values, temp, width, height);
38+
blurh(blurx, temp, values, width, height);
39+
blurh(blurx, values, temp, width, height);
40+
} else if (blury) {
41+
blurv(blury, values, temp, width, height);
42+
blurv(blury, temp, values, width, height);
43+
blurv(blury, values, temp, width, height);
44+
}
45+
return data;
46+
};
47+
}
48+
49+
function blurh(blur, T, S, w, h) {
50+
for (let y = 0, n = w * h; y < n;) {
51+
blur(T, S, y, y += w, 1);
52+
}
53+
}
54+
55+
function blurv(blur, T, S, w, h) {
56+
for (let x = 0, n = w * h; x < w; ++x) {
57+
blur(T, S, x, x + n, w);
58+
}
59+
}
60+
61+
function blurfImage(radius) {
62+
const blur = blurf(radius);
63+
return (T, S, start, stop, step) => {
64+
start <<= 2, stop <<= 2, step <<= 2;
65+
blur(T, S, start + 0, stop + 0, step);
66+
blur(T, S, start + 1, stop + 1, step);
67+
blur(T, S, start + 2, stop + 2, step);
68+
blur(T, S, start + 3, stop + 3, step);
69+
};
70+
}
71+
72+
// Given a target array T, a source array S, sets each value T[i] to the average
73+
// of {S[i - r], …, S[i], …, S[i + r]}, where r = ⌊radius⌋, start <= i < stop,
74+
// for each i, i + step, i + 2 * step, etc., and where S[j] is clamped between
75+
// S[start] (inclusive) and S[stop] (exclusive). If the given radius is not an
76+
// integer, S[i - r - 1] and S[i + r + 1] are added to the sum, each weighted
77+
// according to r - ⌊radius⌋.
78+
function blurf(radius) {
79+
const radius0 = Math.floor(radius);
80+
if (radius0 === radius) return bluri(radius);
81+
const t = radius - radius0;
82+
const w = 2 * radius + 1;
83+
return (T, S, start, stop, step) => { // stop must be aligned!
84+
if (!((stop -= step) >= start)) return; // inclusive stop
85+
let sum = radius0 * S[start];
86+
const s0 = step * radius0;
87+
const s1 = s0 + step;
88+
for (let i = start, j = start + s0; i < j; i += step) {
89+
sum += S[Math.min(stop, i)];
90+
}
91+
for (let i = start, j = stop; i <= j; i += step) {
92+
sum += S[Math.min(stop, i + s0)];
93+
T[i] = (sum + t * (S[Math.max(start, i - s1)] + S[Math.min(stop, i + s1)])) / w;
94+
sum -= S[Math.max(start, i - s0)];
95+
}
96+
};
97+
}
98+
99+
// Like blurf, but optimized for integer radius.
100+
function bluri(radius) {
101+
const w = 2 * radius + 1;
102+
return (T, S, start, stop, step) => { // stop must be aligned!
103+
if (!((stop -= step) >= start)) return; // inclusive stop
104+
let sum = radius * S[start];
105+
const s = step * radius;
106+
for (let i = start, j = start + s; i < j; i += step) {
107+
sum += S[Math.min(stop, i)];
108+
}
109+
for (let i = start, j = stop; i <= j; i += step) {
110+
sum += S[Math.min(stop, i + s)];
111+
T[i] = sum / w;
112+
sum -= S[Math.max(start, i - s)];
113+
}
114+
};
115+
}

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export {default as bisect, bisectRight, bisectLeft, bisectCenter} from "./bisect.js";
22
export {default as ascending} from "./ascending.js";
33
export {default as bisector} from "./bisector.js";
4+
export {blur, blur2, blurImage} from "./blur.js";
45
export {default as count} from "./count.js";
56
export {default as cross} from "./cross.js";
67
export {default as cumsum} from "./cumsum.js";

test/blur-test.js

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import assert from "assert";
2+
import {blur, blur2} from "../src/index.js";
3+
4+
it("blur(values, r) returns values", () => {
5+
const V = [0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0];
6+
assert.strictEqual(blur(V, 1), V);
7+
assert.deepStrictEqual(V, [0, 0, 0, 1, 3, 6, 7, 6, 3, 1, 0, 0, 0, 0]);
8+
});
9+
10+
it("blur(values, r) observes the expected integer radius r", () => {
11+
assert.deepStrictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 0.0).map(round), [0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 27.00, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000]);
12+
assert.deepStrictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 1.0).map(round), [0.000, 0.000, 0.000, 1.000, 3.000, 6.000, 7.000, 6.000, 3.000, 1.000, 0.000, 0.000, 0.000, 0.000]);
13+
assert.deepStrictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 2.0).map(round), [0.216, 0.648, 1.296, 2.160, 3.240, 3.888, 4.104, 3.888, 3.240, 2.160, 1.296, 0.648, 0.216, 0.000]);
14+
assert.deepStrictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 3.0).map(round), [1.023, 1.338, 1.732, 2.204, 2.598, 2.834, 2.913, 2.834, 2.598, 2.204, 1.653, 1.181, 0.787, 0.472]);
15+
});
16+
17+
it("blur(values, r) observes the expected fractional radius r", () => {
18+
assert.deepStrictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 0.5).map(round), [0.000, 0.000, 0.000, 0.422, 2.531, 6.328, 8.438, 6.328, 2.531, 0.422, 0.000, 0.000, 0.000, 0.000]);
19+
assert.deepStrictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 1.5).map(round), [0.053, 0.316, 0.949, 2.004, 3.322, 4.430, 4.852, 4.430, 3.322, 2.004, 0.949, 0.316, 0.053, 0.000]);
20+
assert.deepStrictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 2.5).map(round), [0.672, 1.078, 1.609, 2.234, 2.813, 3.188, 3.313, 3.188, 2.813, 2.234, 1.594, 1.031, 0.594, 0.281]);
21+
assert.deepStrictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 3.5).map(round), [1.266, 1.503, 1.780, 2.057, 2.294, 2.452, 2.505, 2.452, 2.294, 2.030, 1.701, 1.371, 1.081, 0.844]);
22+
});
23+
24+
it("blur(values, r) repeats starting values before the window", () => {
25+
assert.deepStrictEqual(blur([27, 0, 0, 0, 0, 0, 0, 0], 0.0).map(round), [27.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000]);
26+
assert.deepStrictEqual(blur([27, 0, 0, 0, 0, 0, 0, 0], 1.0).map(round), [13.000, 9.000, 4.000, 1.000, 0.000, 0.000, 0.000, 0.000]);
27+
assert.deepStrictEqual(blur([27, 0, 0, 0, 0, 0, 0, 0], 2.0).map(round), [11.016, 9.072, 6.696, 4.104, 2.160, 0.864, 0.216, 0.000]);
28+
assert.deepStrictEqual(blur([27, 0, 0, 0, 0, 0, 0, 0], 3.0).map(round), [10.233, 8.974, 7.478, 5.825, 4.093, 2.676, 1.574, 0.787]);
29+
});
30+
31+
it("blur(values, r) approximately preserves total value", () => {
32+
assert.strictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 0.0).reduce((p, v) => p + v), 27);
33+
assert.strictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 0.5).reduce((p, v) => p + v), 27);
34+
assert.strictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 1.0).reduce((p, v) => p + v), 27);
35+
assert.strictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 1.5).reduce((p, v) => p + v), 27);
36+
assert.strictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 2.0).reduce((p, v) => p + v), 27.000000000000004);
37+
assert.strictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 2.5).reduce((p, v) => p + v), 26.640625);
38+
assert.strictEqual(blur([0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0], 3.0).reduce((p, v) => p + v), 26.370262390670547);
39+
});
40+
41+
const unit = {
42+
width: 11,
43+
height: 11,
44+
data: [
45+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
46+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
47+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
48+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
50+
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
51+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
52+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
53+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
54+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
55+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
56+
]
57+
};
58+
59+
it("blur2(data, r) modifies in-place", () => {
60+
const copy = copy2(unit);
61+
assert.strictEqual(blur2(copy, 1), copy);
62+
});
63+
64+
it("data.height is redundant for blur2", () => {
65+
const copy = copy2(unit);
66+
delete copy.height;
67+
assert.deepStrictEqual(blur2(copy, 1).data.map(round), [
68+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
69+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
70+
0.000, 0.000, 0.001, 0.004, 0.008, 0.010, 0.008, 0.004, 0.001, 0.000, 0.000,
71+
0.000, 0.000, 0.004, 0.012, 0.025, 0.029, 0.025, 0.012, 0.004, 0.000, 0.000,
72+
0.000, 0.000, 0.008, 0.025, 0.049, 0.058, 0.049, 0.025, 0.008, 0.000, 0.000,
73+
0.000, 0.000, 0.010, 0.029, 0.058, 0.067, 0.058, 0.029, 0.010, 0.000, 0.000,
74+
0.000, 0.000, 0.008, 0.025, 0.049, 0.058, 0.049, 0.025, 0.008, 0.000, 0.000,
75+
0.000, 0.000, 0.004, 0.012, 0.025, 0.029, 0.025, 0.012, 0.004, 0.000, 0.000,
76+
0.000, 0.000, 0.001, 0.004, 0.008, 0.010, 0.008, 0.004, 0.001, 0.000, 0.000,
77+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
78+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000
79+
]);
80+
});
81+
82+
it("blur2(data, r) observes the expected integer radius r", () => {
83+
assert.deepStrictEqual(blur2(copy2(unit), 0), unit);
84+
assert.deepStrictEqual(blur2(copy2(unit), 1).data.map(round), [
85+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
86+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
87+
0.000, 0.000, 0.001, 0.004, 0.008, 0.010, 0.008, 0.004, 0.001, 0.000, 0.000,
88+
0.000, 0.000, 0.004, 0.012, 0.025, 0.029, 0.025, 0.012, 0.004, 0.000, 0.000,
89+
0.000, 0.000, 0.008, 0.025, 0.049, 0.058, 0.049, 0.025, 0.008, 0.000, 0.000,
90+
0.000, 0.000, 0.010, 0.029, 0.058, 0.067, 0.058, 0.029, 0.010, 0.000, 0.000,
91+
0.000, 0.000, 0.008, 0.025, 0.049, 0.058, 0.049, 0.025, 0.008, 0.000, 0.000,
92+
0.000, 0.000, 0.004, 0.012, 0.025, 0.029, 0.025, 0.012, 0.004, 0.000, 0.000,
93+
0.000, 0.000, 0.001, 0.004, 0.008, 0.010, 0.008, 0.004, 0.001, 0.000, 0.000,
94+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
95+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000
96+
]);
97+
assert.deepStrictEqual(blur2(copy2(unit), 2).data.map(round), [
98+
0.001, 0.001, 0.002, 0.003, 0.003, 0.004, 0.003, 0.003, 0.002, 0.001, 0.001,
99+
0.001, 0.002, 0.004, 0.006, 0.007, 0.007, 0.007, 0.006, 0.004, 0.002, 0.001,
100+
0.002, 0.004, 0.006, 0.010, 0.012, 0.012, 0.012, 0.010, 0.006, 0.004, 0.002,
101+
0.003, 0.006, 0.010, 0.014, 0.017, 0.018, 0.017, 0.014, 0.010, 0.006, 0.003,
102+
0.003, 0.007, 0.012, 0.017, 0.021, 0.022, 0.021, 0.017, 0.012, 0.007, 0.003,
103+
0.004, 0.007, 0.012, 0.018, 0.022, 0.023, 0.022, 0.018, 0.012, 0.007, 0.004,
104+
0.003, 0.007, 0.012, 0.017, 0.021, 0.022, 0.021, 0.017, 0.012, 0.007, 0.003,
105+
0.003, 0.006, 0.010, 0.014, 0.017, 0.018, 0.017, 0.014, 0.010, 0.006, 0.003,
106+
0.002, 0.004, 0.006, 0.010, 0.012, 0.012, 0.012, 0.010, 0.006, 0.004, 0.002,
107+
0.001, 0.002, 0.004, 0.006, 0.007, 0.007, 0.007, 0.006, 0.004, 0.002, 0.001,
108+
0.001, 0.001, 0.002, 0.003, 0.003, 0.004, 0.003, 0.003, 0.002, 0.001, 0.001
109+
]);
110+
});
111+
112+
it("blur2(data, rx, 0) does horizontal blurring", () => {
113+
assert.deepStrictEqual(blur2(copy2(unit), 0, 0).data, [
114+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
115+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
116+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
117+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
118+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
119+
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
120+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
121+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
122+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
123+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
124+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
125+
]);
126+
assert.deepStrictEqual(blur2(copy2(unit), 1, 0).data.map(round), [
127+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
128+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
129+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
130+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
131+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
132+
0.000, 0.000, 0.037, 0.111, 0.222, 0.259, 0.222, 0.111, 0.037, 0.000, 0.000,
133+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
134+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
135+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
136+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
137+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000
138+
]);
139+
assert.deepStrictEqual(blur2(copy2(unit), 2, 0).data.map(round), [
140+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
141+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
142+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
143+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
144+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
145+
0.024, 0.048, 0.080, 0.120, 0.144, 0.152, 0.144, 0.120, 0.080, 0.048, 0.024,
146+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
147+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
148+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
149+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
150+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000
151+
]);
152+
});
153+
154+
it("blur2(data, 0, ry) does vertical blurring", () => {
155+
assert.deepStrictEqual(blur2(copy2(unit), 0, 0).data, [
156+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
157+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
158+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
159+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
160+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
161+
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
162+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
163+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
164+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
165+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
166+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
167+
]);
168+
assert.deepStrictEqual(blur2(copy2(unit), 0, 1).data.map(round), [
169+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
170+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
171+
0.000, 0.000, 0.000, 0.000, 0.000, 0.037, 0.000, 0.000, 0.000, 0.000, 0.000,
172+
0.000, 0.000, 0.000, 0.000, 0.000, 0.111, 0.000, 0.000, 0.000, 0.000, 0.000,
173+
0.000, 0.000, 0.000, 0.000, 0.000, 0.222, 0.000, 0.000, 0.000, 0.000, 0.000,
174+
0.000, 0.000, 0.000, 0.000, 0.000, 0.259, 0.000, 0.000, 0.000, 0.000, 0.000,
175+
0.000, 0.000, 0.000, 0.000, 0.000, 0.222, 0.000, 0.000, 0.000, 0.000, 0.000,
176+
0.000, 0.000, 0.000, 0.000, 0.000, 0.111, 0.000, 0.000, 0.000, 0.000, 0.000,
177+
0.000, 0.000, 0.000, 0.000, 0.000, 0.037, 0.000, 0.000, 0.000, 0.000, 0.000,
178+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
179+
0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000
180+
]);
181+
assert.deepStrictEqual(blur2(copy2(unit), 0, 2).data.map(round), [
182+
0.000, 0.000, 0.000, 0.000, 0.000, 0.024, 0.000, 0.000, 0.000, 0.000, 0.000,
183+
0.000, 0.000, 0.000, 0.000, 0.000, 0.048, 0.000, 0.000, 0.000, 0.000, 0.000,
184+
0.000, 0.000, 0.000, 0.000, 0.000, 0.080, 0.000, 0.000, 0.000, 0.000, 0.000,
185+
0.000, 0.000, 0.000, 0.000, 0.000, 0.120, 0.000, 0.000, 0.000, 0.000, 0.000,
186+
0.000, 0.000, 0.000, 0.000, 0.000, 0.144, 0.000, 0.000, 0.000, 0.000, 0.000,
187+
0.000, 0.000, 0.000, 0.000, 0.000, 0.152, 0.000, 0.000, 0.000, 0.000, 0.000,
188+
0.000, 0.000, 0.000, 0.000, 0.000, 0.144, 0.000, 0.000, 0.000, 0.000, 0.000,
189+
0.000, 0.000, 0.000, 0.000, 0.000, 0.120, 0.000, 0.000, 0.000, 0.000, 0.000,
190+
0.000, 0.000, 0.000, 0.000, 0.000, 0.080, 0.000, 0.000, 0.000, 0.000, 0.000,
191+
0.000, 0.000, 0.000, 0.000, 0.000, 0.048, 0.000, 0.000, 0.000, 0.000, 0.000,
192+
0.000, 0.000, 0.000, 0.000, 0.000, 0.024, 0.000, 0.000, 0.000, 0.000, 0.000
193+
]);
194+
});
195+
196+
function copy2({data, width, height}) {
197+
return {data: data.slice(), width, height};
198+
}
199+
200+
function round(x) {
201+
return Math.round(x * 1000) / 1000 || 0;
202+
}

0 commit comments

Comments
 (0)