Repository navigation
Expand file tree
/
Copy pathmain.js
More file actions
190 lines (159 loc) · 5.97 KB
/
Copy pathmain.js
File metadata and controls
190 lines (159 loc) · 5.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// default canvas dimensions
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 800;
// set up the canvas
const canvas = document.getElementById("canvas");
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
const ctx = canvas.getContext("2d");
// load the image
const img = new Image();
img.crossOrigin = "anonymous";
// Markus Trienke, CC BY-SA 2.0 <https://creativecommons.org/licenses/by-sa/2.0>, via Wikimedia Commons
img.src =
"https://upload.wikimedia.org/wikipedia/commons/d/d5/Greenland_467_%2835130903436%29_%28cropped%29.jpg";
img.onload = () => {
original();
};
// listen to the changes made to the input field
document.getElementById("image_source").addEventListener("change", (event) => {
img.src = event.target.value;
});
/**
* The pixel buffer is a one-dimension unsigned integer array.
* We need to convert the (x, y) position to a singular index on that array.
* In addition to that, each 4 adjacent entry represents the RGBA value for a single pixel (4 array elements per pixel)
* @returns the index on the flat array
*/
const cartesianToFlat = (x, y) => 4 * (x + CANVAS_WIDTH * y);
/**
* Given the indices, finds the weighted average for each color.
* Returns a Uint8Array of RGB
*/
const findNewRGBValue = (pixelBuffer, ...indices) => {
let r = 0;
let g = 0;
let b = 0;
let weightSum = 0;
for (let i = 0; i < indices.length; i++) {
weightSum += indices[i].weight;
r += pixelBuffer[indices[i].pos + 0] * indices[i].weight;
g += pixelBuffer[indices[i].pos + 1] * indices[i].weight;
b += pixelBuffer[indices[i].pos + 2] * indices[i].weight;
}
r = Math.round(r / weightSum);
g = Math.round(g / weightSum);
b = Math.round(b / weightSum);
return new Uint8Array([r, g, b]);
};
// Reference: https://en.wikipedia.org/wiki/Box_blur
const boxBlur = () => {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const newImageData = ctx.createImageData(imageData);
const start = performance.now();
const KERNEL_RADIUS = 1;
for (let x = 0; x < canvas.width; x++) {
for (let y = 0; y < canvas.height; y++) {
const fitsKernel = x >= KERNEL_RADIUS &&
x < canvas.width - KERNEL_RADIUS && y >= KERNEL_RADIUS &&
y < canvas.height - KERNEL_RADIUS;
let r, g, b;
const pos = cartesianToFlat(x, y);
if (fitsKernel) {
const indices = [];
// takes all the relevant indices into account
for (let xx = -KERNEL_RADIUS; xx <= KERNEL_RADIUS; xx++) {
for (let yy = -KERNEL_RADIUS; yy <= KERNEL_RADIUS; yy++) {
indices.push(
{
pos: cartesianToFlat(x + xx, y + yy),
weight: 1,
},
);
}
}
[r, g, b] = findNewRGBValue(data, ...indices);
} else {
r = data[pos];
g = data[pos + 1];
b = data[pos + 2];
}
newImageData.data[pos] = r;
newImageData.data[pos + 1] = g;
newImageData.data[pos + 2] = b;
newImageData.data[pos + 3] = data[pos + 3];
}
}
const total = performance.now() - start;
console.log("BoxBlur:", total, "ms");
ctx.putImageData(newImageData, 0, 0);
};
// https://en.wikipedia.org/wiki/Gaussian_blur
const gaussian = () => {
// the gaussian function
const gaussianWeight = (x, y) => {
const SIGMA = 1.85089642; // no real reason for this
const stDevSq = Math.pow(SIGMA, 2);
const A = 1 / (2 * Math.PI * stDevSq);
const B = (x * x + y * y) / (2 * stDevSq);
return A * Math.pow(Math.E, -B);
};
const KERNEL_RADIUS = 2;
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const newImageData = ctx.createImageData(imageData);
const start = performance.now();
for (let x = 0; x < canvas.width; x++) {
for (let y = 0; y < canvas.height; y++) {
const fitsKernel = x >= KERNEL_RADIUS &&
x < canvas.width - KERNEL_RADIUS && y >= KERNEL_RADIUS &&
y < canvas.height - KERNEL_RADIUS;
let r, g, b;
const pos = cartesianToFlat(x, y);
if (fitsKernel) {
const indices = [];
// this ensures that the currently processed pixel is centered at position (0, 0)
// and the neighbors are also adjusted appropriately
for (let xx = -KERNEL_RADIUS; xx <= KERNEL_RADIUS; xx++) {
for (let yy = -KERNEL_RADIUS; yy <= KERNEL_RADIUS; yy++) {
indices.push(
{
pos: cartesianToFlat(x + xx, y + yy),
weight: gaussianWeight(xx, yy),
},
);
}
}
[r, g, b] = findNewRGBValue(data, ...indices);
} else {
r = data[pos];
g = data[pos + 1];
b = data[pos + 2];
}
newImageData.data[pos] = r;
newImageData.data[pos + 1] = g;
newImageData.data[pos + 2] = b;
newImageData.data[pos + 3] = data[pos + 3];
}
}
const total = performance.now() - start;
console.log("GaussianBlur:", total, "ms");
ctx.putImageData(newImageData, 0, 0);
};
const original = () => {
const adjustedCanvasHeight = (img.naturalHeight / img.naturalWidth) *
CANVAS_WIDTH;
canvas.height = adjustedCanvasHeight;
ctx.drawImage(
img,
0,
0,
img.naturalWidth,
img.naturalHeight,
0,
0,
CANVAS_WIDTH,
adjustedCanvasHeight,
);
};