-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathindex.js
136 lines (120 loc) · 3.89 KB
/
index.js
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
const digitLookup = new Uint8Array(128);
for (let i = 0; i < 83; i++) {
digitLookup[
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~'.charCodeAt(
i,
)
] = i;
}
const decode83 = (str, start, end) => {
let value = 0;
while (start < end) {
value *= 83;
value += digitLookup[str.charCodeAt(start++)];
}
return value;
};
const pow = Math.pow;
const PI = Math.PI;
const PI2 = PI * 2;
const d = 3294.6;
const e = 269.025;
const sRGBToLinear = (value) =>
value > 10.31475 ? pow(value / e + 0.052132, 2.4) : value / d;
const linearTosRGB = (v) =>
~~(v > 0.00001227 ? e * pow(v, 0.416666) - 13.025 : v * d + 1);
const signSqr = (x) => (x < 0 ? -1 : 1) * x * x;
/**
* Fast approximate cosine implementation
* Based on FTrig https://github.com/netcell/FTrig
*/
const fastCos = (x) => {
x += PI / 2;
while (x > PI) {
x -= PI2;
}
const cos = 1.27323954 * x - 0.405284735 * signSqr(x);
return 0.225 * (signSqr(cos) - cos) + cos;
};
/**
* Extracts average color from BlurHash image
* @param {string} blurHash BlurHash image string
* @returns {[number, number, number]}
*/
export function getBlurHashAverageColor(blurHash) {
const val = decode83(blurHash, 2, 6);
return [val >> 16, (val >> 8) & 255, val & 255];
}
/**
* Decodes BlurHash image
* @param {string} blurHash BlurHash image string
* @param {number} width Output image width
* @param {number} height Output image height
* @param {?number} punch
* @returns {Uint8ClampedArray}
*/
export function decodeBlurHash(blurHash, width, height, punch) {
const sizeFlag = decode83(blurHash, 0, 1);
const numX = (sizeFlag % 9) + 1;
const numY = ~~(sizeFlag / 9) + 1;
const size = numX * numY;
let i = 0,
j = 0,
x = 0,
y = 0,
r = 0,
g = 0,
b = 0,
basis = 0,
basisY = 0,
colorIndex = 0,
pixelIndex = 0,
value = 0;
const maximumValue = ((decode83(blurHash, 1, 2) + 1) / 13446) * (punch | 1);
const colors = new Float64Array(size * 3);
const averageColor = getBlurHashAverageColor(blurHash);
for (i = 0; i < 3; i++) {
colors[i] = sRGBToLinear(averageColor[i]);
}
for (i = 1; i < size; i++) {
value = decode83(blurHash, 4 + i * 2, 6 + i * 2);
colors[i * 3] = signSqr(~~(value / 361) - 9) * maximumValue;
colors[i * 3 + 1] = signSqr((~~(value / 19) % 19) - 9) * maximumValue;
colors[i * 3 + 2] = signSqr((value % 19) - 9) * maximumValue;
}
const cosinesY = new Float64Array(numY * height);
const cosinesX = new Float64Array(numX * width);
for (j = 0; j < numY; j++) {
for (y = 0; y < height; y++) {
cosinesY[j * height + y] = fastCos((PI * y * j) / height);
}
}
for (i = 0; i < numX; i++) {
for (x = 0; x < width; x++) {
cosinesX[i * width + x] = fastCos((PI * x * i) / width);
}
}
const bytesPerRow = width * 4;
const pixels = new Uint8ClampedArray(bytesPerRow * height);
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
r = g = b = 0;
for (j = 0; j < numY; j++) {
basisY = cosinesY[j * height + y];
for (i = 0; i < numX; i++) {
basis = cosinesX[i * width + x] * basisY;
colorIndex = (i + j * numX) * 3;
r += colors[colorIndex] * basis;
g += colors[colorIndex + 1] * basis;
b += colors[colorIndex + 2] * basis;
}
}
pixelIndex = 4 * x + y * bytesPerRow;
pixels[pixelIndex] = linearTosRGB(r);
pixels[pixelIndex + 1] = linearTosRGB(g);
pixels[pixelIndex + 2] = linearTosRGB(b);
pixels[pixelIndex + 3] = 255; // alpha
}
}
return pixels;
}