pica 7.0.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -16
- package/dist/pica.js +475 -230
- package/dist/pica.min.js +2 -2
- package/index.js +86 -40
- package/lib/mathlib.js +2 -2
- package/lib/mm_resize/convolve.c +193 -17
- package/lib/mm_resize/convolve.js +160 -33
- package/lib/mm_resize/convolve.wasm +0 -0
- package/lib/mm_resize/convolve_wasm_base64.js +1 -1
- package/lib/mm_resize/resize.js +25 -21
- package/lib/mm_resize/resize_filter_gen.js +3 -3
- package/lib/mm_resize/resize_filter_info.js +44 -17
- package/lib/mm_resize/resize_wasm.js +24 -14
- package/lib/utils.js +79 -1
- package/lib/worker.js +5 -3
- package/package.json +7 -8
- package/CHANGELOG.md +0 -334
package/lib/mm_resize/convolve.c
CHANGED
|
@@ -4,6 +4,10 @@ inline uint8_t clampTo8(int32_t i) {
|
|
|
4
4
|
return i < 0 ? 0 : (i > 255 ? 255 : i);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
inline uint32_t clampNegative(int32_t i) {
|
|
8
|
+
return i >= 0 ? i : 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
#define R(x) ((uint8_t)(x))
|
|
9
13
|
#define G(x) ((uint8_t)((x) >> 8))
|
|
@@ -13,10 +17,9 @@ inline uint8_t clampTo8(int32_t i) {
|
|
|
13
17
|
#define RGBA(r, g, b, a) ((r) | ((g) << 8) | ((b) << 16) | ((a) << 24))
|
|
14
18
|
|
|
15
19
|
|
|
16
|
-
void
|
|
20
|
+
void convolveHor(uint32_t *src, uint16_t *dest, uint32_t srcW, uint32_t srcH, uint32_t destW, int16_t *filters)
|
|
17
21
|
{
|
|
18
22
|
int32_t r, g, b, a;
|
|
19
|
-
uint32_t rgba;
|
|
20
23
|
uint32_t filterPtr, filterShift, filterSize;
|
|
21
24
|
uint32_t srcPtr, srcY, destX;
|
|
22
25
|
int32_t filterVal;
|
|
@@ -28,53 +31,226 @@ void convolve(uint32_t *src, uint32_t *dest, uint32_t srcW, uint32_t srcH, uint3
|
|
|
28
31
|
for (destX = 0; destX < destW; destX++) {
|
|
29
32
|
// Get the filter that determines the current output pixel.
|
|
30
33
|
filterShift = filters[filterPtr++];
|
|
31
|
-
srcPtr = srcOffset + filterShift;
|
|
32
34
|
filterSize = filters[filterPtr++];
|
|
33
35
|
|
|
36
|
+
srcPtr = srcOffset + filterShift;
|
|
37
|
+
|
|
34
38
|
r = g = b = a = 0;
|
|
35
39
|
// Apply the filter to the row to get the destination pixel r, g, b, a
|
|
36
40
|
for (; filterSize > 0; filterSize--) {
|
|
37
41
|
filterVal = filters[filterPtr++];
|
|
38
|
-
rgba = src[srcPtr++];
|
|
42
|
+
uint32_t rgba = src[srcPtr++];
|
|
39
43
|
|
|
40
44
|
r += filterVal * R(rgba);
|
|
41
45
|
g += filterVal * G(rgba);
|
|
42
46
|
b += filterVal * B(rgba);
|
|
43
47
|
a += filterVal * A(rgba);
|
|
44
48
|
};
|
|
45
|
-
|
|
46
|
-
//
|
|
49
|
+
|
|
50
|
+
// Store 15 bits between passes for better precision
|
|
51
|
+
// Instead of shift to 14 (FIXED_FRAC_BITS), shift to 7 only
|
|
52
|
+
//
|
|
53
|
+
dest[destOffset++] = clampNegative(r >> 7);
|
|
54
|
+
dest[destOffset++] = clampNegative(g >> 7);
|
|
55
|
+
dest[destOffset++] = clampNegative(b >> 7);
|
|
56
|
+
dest[destOffset++] = clampNegative(a >> 7);
|
|
57
|
+
destOffset += srcH * 4 - 4;
|
|
58
|
+
};
|
|
59
|
+
destOffset = (srcY + 1) * 4;
|
|
60
|
+
srcOffset = (srcY + 1) * srcW;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
void convolveVert(uint16_t *src, uint32_t *dest, uint32_t srcW, uint32_t srcH, uint32_t destW, int16_t *filters)
|
|
66
|
+
{
|
|
67
|
+
int32_t r, g, b, a;
|
|
68
|
+
uint32_t filterPtr, filterShift, filterSize;
|
|
69
|
+
uint32_t srcPtr, srcY, destX;
|
|
70
|
+
int32_t filterVal;
|
|
71
|
+
uint32_t srcOffset = 0, destOffset = 0;
|
|
72
|
+
//
|
|
73
|
+
for (srcY=0; srcY < srcH; srcY++) {
|
|
74
|
+
filterPtr = 0;
|
|
75
|
+
|
|
76
|
+
// Apply precomputed filters to each destination row point
|
|
77
|
+
for (destX = 0; destX < destW; destX++) {
|
|
78
|
+
// Get the filter that determines the current output pixel.
|
|
79
|
+
filterShift = filters[filterPtr++];
|
|
80
|
+
filterSize = filters[filterPtr++];
|
|
81
|
+
|
|
82
|
+
srcPtr = srcOffset + filterShift*4;
|
|
83
|
+
|
|
84
|
+
r = g = b = a = 0;
|
|
85
|
+
|
|
86
|
+
// Apply the filter to the row to get the destination pixel r, g, b, a
|
|
87
|
+
for (; filterSize > 0; filterSize--) {
|
|
88
|
+
filterVal = filters[filterPtr++];
|
|
89
|
+
|
|
90
|
+
r += filterVal * src[srcPtr++];
|
|
91
|
+
g += filterVal * src[srcPtr++];
|
|
92
|
+
b += filterVal * src[srcPtr++];
|
|
93
|
+
a += filterVal * src[srcPtr++];
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Sync with premultiplied version for exact result match
|
|
97
|
+
r >>= 7;
|
|
98
|
+
g >>= 7;
|
|
99
|
+
b >>= 7;
|
|
100
|
+
a >>= 7;
|
|
101
|
+
|
|
102
|
+
// Bring this value back in range + round result.
|
|
47
103
|
//
|
|
48
|
-
// (!) Add 1/2 of value before clamping to get proper rounding. In other
|
|
49
|
-
// case brightness loss will be noticeable if you resize image with white
|
|
50
|
-
// border and place it on white background.
|
|
51
104
|
dest[destOffset] = RGBA(
|
|
52
|
-
clampTo8((r + (1<<13))>>14
|
|
53
|
-
clampTo8((g + (1<<13))>>14
|
|
54
|
-
clampTo8((b + (1<<13))>>14
|
|
55
|
-
clampTo8((a + (1<<13))>>14
|
|
105
|
+
clampTo8((r + (1 << 13)) >> 14),
|
|
106
|
+
clampTo8((g + (1 << 13)) >> 14),
|
|
107
|
+
clampTo8((b + (1 << 13)) >> 14),
|
|
108
|
+
clampTo8((a + (1 << 13)) >> 14)
|
|
109
|
+
);
|
|
56
110
|
destOffset += srcH;
|
|
57
111
|
};
|
|
58
112
|
destOffset = srcY + 1;
|
|
113
|
+
srcOffset = (srcY + 1) * srcW * 4;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
void convolveHorWithPre(uint32_t *src, uint16_t *dest, uint32_t srcW, uint32_t srcH, uint32_t destW, int16_t *filters)
|
|
119
|
+
{
|
|
120
|
+
int32_t r, g, b, a;
|
|
121
|
+
uint32_t filterPtr, filterShift, filterSize;
|
|
122
|
+
uint32_t srcPtr, srcY, destX;
|
|
123
|
+
int32_t filterVal;
|
|
124
|
+
uint32_t srcOffset = 0, destOffset = 0;
|
|
125
|
+
//
|
|
126
|
+
for (srcY=0; srcY < srcH; srcY++) {
|
|
127
|
+
filterPtr = 0;
|
|
128
|
+
// Apply precomputed filters to each destination row point
|
|
129
|
+
for (destX = 0; destX < destW; destX++) {
|
|
130
|
+
// Get the filter that determines the current output pixel.
|
|
131
|
+
filterShift = filters[filterPtr++];
|
|
132
|
+
filterSize = filters[filterPtr++];
|
|
133
|
+
|
|
134
|
+
srcPtr = srcOffset + filterShift;
|
|
135
|
+
|
|
136
|
+
r = g = b = a = 0;
|
|
137
|
+
// Apply the filter to the row to get the destination pixel r, g, b, a
|
|
138
|
+
for (; filterSize > 0; filterSize--) {
|
|
139
|
+
filterVal = filters[filterPtr++];
|
|
140
|
+
uint32_t rgba = src[srcPtr++];
|
|
141
|
+
|
|
142
|
+
uint8_t alpha = A(rgba);
|
|
143
|
+
|
|
144
|
+
r += filterVal * alpha * R(rgba);
|
|
145
|
+
g += filterVal * alpha * G(rgba);
|
|
146
|
+
b += filterVal * alpha * B(rgba);
|
|
147
|
+
a += filterVal * alpha;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Premultiply is (* alpha / 255).
|
|
151
|
+
// Postpone division for better performance
|
|
152
|
+
r /= 255;
|
|
153
|
+
g /= 255;
|
|
154
|
+
b /= 255;
|
|
155
|
+
|
|
156
|
+
// Store 15 bits between passes for better precision
|
|
157
|
+
// Instead of shift to 14 (FIXED_FRAC_BITS), shift to 7 only
|
|
158
|
+
//
|
|
159
|
+
dest[destOffset++] = clampNegative(r >> 7);
|
|
160
|
+
dest[destOffset++] = clampNegative(g >> 7);
|
|
161
|
+
dest[destOffset++] = clampNegative(b >> 7);
|
|
162
|
+
dest[destOffset++] = clampNegative(a >> 7);
|
|
163
|
+
destOffset += srcH * 4 - 4;
|
|
164
|
+
};
|
|
165
|
+
destOffset = (srcY + 1) * 4;
|
|
59
166
|
srcOffset = (srcY + 1) * srcW;
|
|
60
167
|
};
|
|
61
168
|
}
|
|
62
169
|
|
|
170
|
+
|
|
171
|
+
void convolveVertWithPre(uint16_t *src, uint32_t *dest, uint32_t srcW, uint32_t srcH, uint32_t destW, int16_t *filters)
|
|
172
|
+
{
|
|
173
|
+
int32_t r, g, b, a;
|
|
174
|
+
uint32_t filterPtr, filterShift, filterSize;
|
|
175
|
+
uint32_t srcPtr, srcY, destX;
|
|
176
|
+
int32_t filterVal;
|
|
177
|
+
uint32_t srcOffset = 0, destOffset = 0;
|
|
178
|
+
//
|
|
179
|
+
for (srcY=0; srcY < srcH; srcY++) {
|
|
180
|
+
filterPtr = 0;
|
|
181
|
+
|
|
182
|
+
// Apply precomputed filters to each destination row point
|
|
183
|
+
for (destX = 0; destX < destW; destX++) {
|
|
184
|
+
// Get the filter that determines the current output pixel.
|
|
185
|
+
filterShift = filters[filterPtr++];
|
|
186
|
+
filterSize = filters[filterPtr++];
|
|
187
|
+
|
|
188
|
+
srcPtr = srcOffset + filterShift*4;
|
|
189
|
+
|
|
190
|
+
r = g = b = a = 0;
|
|
191
|
+
|
|
192
|
+
// Apply the filter to the row to get the destination pixel r, g, b, a
|
|
193
|
+
for (; filterSize > 0; filterSize--) {
|
|
194
|
+
filterVal = filters[filterPtr++];
|
|
195
|
+
|
|
196
|
+
r += filterVal * src[srcPtr++];
|
|
197
|
+
g += filterVal * src[srcPtr++];
|
|
198
|
+
b += filterVal * src[srcPtr++];
|
|
199
|
+
a += filterVal * src[srcPtr++];
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Downscale to leave room for un-premultiply
|
|
203
|
+
r >>= 7;
|
|
204
|
+
g >>= 7;
|
|
205
|
+
b >>= 7;
|
|
206
|
+
a >>= 7;
|
|
207
|
+
|
|
208
|
+
// Un-premultiply
|
|
209
|
+
a = clampTo8((a + (1 << 13)) >> 14);
|
|
210
|
+
if (a > 0) {
|
|
211
|
+
r = r * 255 / a;
|
|
212
|
+
g = g * 255 / a;
|
|
213
|
+
b = b * 255 / a;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Bring this value back in range + round result.
|
|
217
|
+
// Shift value = FIXED_FRAC_BITS + 7
|
|
218
|
+
//
|
|
219
|
+
dest[destOffset] = RGBA(
|
|
220
|
+
clampTo8((r + (1 << 13)) >> 14),
|
|
221
|
+
clampTo8((g + (1 << 13)) >> 14),
|
|
222
|
+
clampTo8((b + (1 << 13)) >> 14),
|
|
223
|
+
a
|
|
224
|
+
);
|
|
225
|
+
destOffset += srcH;
|
|
226
|
+
};
|
|
227
|
+
destOffset = srcY + 1;
|
|
228
|
+
srcOffset = (srcY + 1) * srcW * 4;
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
63
233
|
void convolveHV(uint32_t filtersX_offset,
|
|
64
234
|
uint32_t filtersY_offset,
|
|
65
235
|
uint32_t tmp_offset,
|
|
66
236
|
uint32_t srcW,
|
|
67
237
|
uint32_t srcH,
|
|
68
238
|
uint32_t destW,
|
|
69
|
-
uint32_t destH
|
|
239
|
+
uint32_t destH,
|
|
240
|
+
uint8_t has_alpha)
|
|
70
241
|
{
|
|
71
242
|
uint8_t *memory = 0;
|
|
72
243
|
|
|
73
244
|
uint32_t *src = (uint32_t *)memory;
|
|
245
|
+
uint16_t *tmp = (uint16_t *)(memory + tmp_offset);
|
|
74
246
|
int16_t *filterX = (int16_t *)(memory + filtersX_offset);
|
|
75
247
|
int16_t *filterY = (int16_t *)(memory + filtersY_offset);
|
|
76
|
-
uint32_t *tmp = (uint32_t *)(memory + tmp_offset);
|
|
77
248
|
|
|
78
|
-
|
|
79
|
-
|
|
249
|
+
if (has_alpha) {
|
|
250
|
+
convolveHorWithPre(src, tmp, srcW, srcH, destW, filterX);
|
|
251
|
+
convolveVertWithPre(tmp, src, srcH, destW, destH, filterY);
|
|
252
|
+
} else {
|
|
253
|
+
convolveHor(src, tmp, srcW, srcH, destW, filterX);
|
|
254
|
+
convolveVert(tmp, src, srcH, destW, destH, filterY);
|
|
255
|
+
}
|
|
80
256
|
}
|
|
@@ -8,18 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
function clampTo8(i) { return i < 0 ? 0 : (i > 255 ? 255 : i); }
|
|
11
|
+
function clampNegative(i) { return i >= 0 ? i : 0; }
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
//
|
|
13
|
+
// Convolve image data in horizontal direction. Can be used for:
|
|
14
|
+
//
|
|
15
|
+
// 1. bitmap with premultiplied alpha
|
|
16
|
+
// 2. bitmap without alpha (all values 255)
|
|
15
17
|
//
|
|
16
|
-
//
|
|
17
|
-
// types of input array and temporary buffer)
|
|
18
|
-
// - making vertical pass by horisonltal lines inprove CPU cache use.
|
|
18
|
+
// Notes:
|
|
19
19
|
//
|
|
20
|
-
//
|
|
20
|
+
// - output is transposed
|
|
21
|
+
// - output resolution is ~15 bits per channel(for better precision).
|
|
21
22
|
//
|
|
22
|
-
function
|
|
23
|
+
function convolveHor(src, dest, srcW, srcH, destW, filters) {
|
|
23
24
|
|
|
24
25
|
var r, g, b, a;
|
|
25
26
|
var filterPtr, filterShift, filterSize;
|
|
@@ -53,17 +54,69 @@ function convolveHorizontally(src, dest, srcW, srcH, destW, filters) {
|
|
|
53
54
|
srcPtr = (srcPtr + 4)|0;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
//
|
|
57
|
-
//
|
|
57
|
+
// Store 15 bits between passes for better precision
|
|
58
|
+
// Instead of shift to 14 (FIXED_FRAC_BITS), shift to 7 only
|
|
58
59
|
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
dest[destOffset + 3] = clampNegative(a >> 7);
|
|
61
|
+
dest[destOffset + 2] = clampNegative(b >> 7);
|
|
62
|
+
dest[destOffset + 1] = clampNegative(g >> 7);
|
|
63
|
+
dest[destOffset] = clampNegative(r >> 7);
|
|
64
|
+
destOffset = (destOffset + srcH * 4)|0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
destOffset = ((srcY + 1) * 4)|0;
|
|
68
|
+
srcOffset = ((srcY + 1) * srcW * 4)|0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Supplementary method for `convolveHor()`
|
|
73
|
+
//
|
|
74
|
+
function convolveVert(src, dest, srcW, srcH, destW, filters) {
|
|
75
|
+
|
|
76
|
+
var r, g, b, a;
|
|
77
|
+
var filterPtr, filterShift, filterSize;
|
|
78
|
+
var srcPtr, srcY, destX, filterVal;
|
|
79
|
+
var srcOffset = 0, destOffset = 0;
|
|
80
|
+
|
|
81
|
+
// For each row
|
|
82
|
+
for (srcY = 0; srcY < srcH; srcY++) {
|
|
83
|
+
filterPtr = 0;
|
|
84
|
+
|
|
85
|
+
// Apply precomputed filters to each destination row point
|
|
86
|
+
for (destX = 0; destX < destW; destX++) {
|
|
87
|
+
// Get the filter that determines the current output pixel.
|
|
88
|
+
filterShift = filters[filterPtr++];
|
|
89
|
+
filterSize = filters[filterPtr++];
|
|
90
|
+
|
|
91
|
+
srcPtr = (srcOffset + (filterShift * 4))|0;
|
|
92
|
+
|
|
93
|
+
r = g = b = a = 0;
|
|
94
|
+
|
|
95
|
+
// Apply the filter to the row to get the destination pixel r, g, b, a
|
|
96
|
+
for (; filterSize > 0; filterSize--) {
|
|
97
|
+
filterVal = filters[filterPtr++];
|
|
98
|
+
|
|
99
|
+
// Use reverse order to workaround deopts in old v8 (node v.10)
|
|
100
|
+
// Big thanks to @mraleph (Vyacheslav Egorov) for the tip.
|
|
101
|
+
a = (a + filterVal * src[srcPtr + 3])|0;
|
|
102
|
+
b = (b + filterVal * src[srcPtr + 2])|0;
|
|
103
|
+
g = (g + filterVal * src[srcPtr + 1])|0;
|
|
104
|
+
r = (r + filterVal * src[srcPtr])|0;
|
|
105
|
+
srcPtr = (srcPtr + 4)|0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Sync with premultiplied version for exact result match
|
|
109
|
+
r >>= 7;
|
|
110
|
+
g >>= 7;
|
|
111
|
+
b >>= 7;
|
|
112
|
+
a >>= 7;
|
|
113
|
+
|
|
114
|
+
// Bring this value back in range + round result.
|
|
62
115
|
//
|
|
63
|
-
dest[destOffset + 3] = clampTo8((a + (1 << 13)) >> 14
|
|
64
|
-
dest[destOffset + 2] = clampTo8((b + (1 << 13)) >> 14
|
|
65
|
-
dest[destOffset + 1] = clampTo8((g + (1 << 13)) >> 14
|
|
66
|
-
dest[destOffset] = clampTo8((r + (1 << 13)) >> 14
|
|
116
|
+
dest[destOffset + 3] = clampTo8((a + (1 << 13)) >> 14);
|
|
117
|
+
dest[destOffset + 2] = clampTo8((b + (1 << 13)) >> 14);
|
|
118
|
+
dest[destOffset + 1] = clampTo8((g + (1 << 13)) >> 14);
|
|
119
|
+
dest[destOffset] = clampTo8((r + (1 << 13)) >> 14);
|
|
67
120
|
destOffset = (destOffset + srcH * 4)|0;
|
|
68
121
|
}
|
|
69
122
|
|
|
@@ -72,11 +125,73 @@ function convolveHorizontally(src, dest, srcW, srcH, destW, filters) {
|
|
|
72
125
|
}
|
|
73
126
|
}
|
|
74
127
|
|
|
75
|
-
// Technically, convolvers are the same. But input array and temporary
|
|
76
|
-
// buffer can be of different type (especially, in old browsers). So,
|
|
77
|
-
// keep code in separate functions to avoid deoptimizations & speed loss.
|
|
78
128
|
|
|
79
|
-
|
|
129
|
+
// Premultiply & convolve image data in horizontal direction. Can be used for:
|
|
130
|
+
//
|
|
131
|
+
// - Any bitmap data, extracted with `.getImageData()` method (with
|
|
132
|
+
// non-premultiplied alpha)
|
|
133
|
+
//
|
|
134
|
+
// For images without alpha channel this method is slower than `convolveHor()`
|
|
135
|
+
//
|
|
136
|
+
function convolveHorWithPre(src, dest, srcW, srcH, destW, filters) {
|
|
137
|
+
|
|
138
|
+
var r, g, b, a, alpha;
|
|
139
|
+
var filterPtr, filterShift, filterSize;
|
|
140
|
+
var srcPtr, srcY, destX, filterVal;
|
|
141
|
+
var srcOffset = 0, destOffset = 0;
|
|
142
|
+
|
|
143
|
+
// For each row
|
|
144
|
+
for (srcY = 0; srcY < srcH; srcY++) {
|
|
145
|
+
filterPtr = 0;
|
|
146
|
+
|
|
147
|
+
// Apply precomputed filters to each destination row point
|
|
148
|
+
for (destX = 0; destX < destW; destX++) {
|
|
149
|
+
// Get the filter that determines the current output pixel.
|
|
150
|
+
filterShift = filters[filterPtr++];
|
|
151
|
+
filterSize = filters[filterPtr++];
|
|
152
|
+
|
|
153
|
+
srcPtr = (srcOffset + (filterShift * 4))|0;
|
|
154
|
+
|
|
155
|
+
r = g = b = a = 0;
|
|
156
|
+
|
|
157
|
+
// Apply the filter to the row to get the destination pixel r, g, b, a
|
|
158
|
+
for (; filterSize > 0; filterSize--) {
|
|
159
|
+
filterVal = filters[filterPtr++];
|
|
160
|
+
|
|
161
|
+
// Use reverse order to workaround deopts in old v8 (node v.10)
|
|
162
|
+
// Big thanks to @mraleph (Vyacheslav Egorov) for the tip.
|
|
163
|
+
alpha = src[srcPtr + 3];
|
|
164
|
+
a = (a + filterVal * alpha)|0;
|
|
165
|
+
b = (b + filterVal * src[srcPtr + 2] * alpha)|0;
|
|
166
|
+
g = (g + filterVal * src[srcPtr + 1] * alpha)|0;
|
|
167
|
+
r = (r + filterVal * src[srcPtr] * alpha)|0;
|
|
168
|
+
srcPtr = (srcPtr + 4)|0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Premultiply is (* alpha / 255).
|
|
172
|
+
// Postpone division for better performance
|
|
173
|
+
b = (b / 255)|0;
|
|
174
|
+
g = (g / 255)|0;
|
|
175
|
+
r = (r / 255)|0;
|
|
176
|
+
|
|
177
|
+
// Store 15 bits between passes for better precision
|
|
178
|
+
// Instead of shift to 14 (FIXED_FRAC_BITS), shift to 7 only
|
|
179
|
+
//
|
|
180
|
+
dest[destOffset + 3] = clampNegative(a >> 7);
|
|
181
|
+
dest[destOffset + 2] = clampNegative(b >> 7);
|
|
182
|
+
dest[destOffset + 1] = clampNegative(g >> 7);
|
|
183
|
+
dest[destOffset] = clampNegative(r >> 7);
|
|
184
|
+
destOffset = (destOffset + srcH * 4)|0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
destOffset = ((srcY + 1) * 4)|0;
|
|
188
|
+
srcOffset = ((srcY + 1) * srcW * 4)|0;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Supplementary method for `convolveHorWithPre()`
|
|
193
|
+
//
|
|
194
|
+
function convolveVertWithPre(src, dest, srcW, srcH, destW, filters) {
|
|
80
195
|
|
|
81
196
|
var r, g, b, a;
|
|
82
197
|
var filterPtr, filterShift, filterSize;
|
|
@@ -110,17 +225,27 @@ function convolveVertically(src, dest, srcW, srcH, destW, filters) {
|
|
|
110
225
|
srcPtr = (srcPtr + 4)|0;
|
|
111
226
|
}
|
|
112
227
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
228
|
+
// Downscale to leave room for un-premultiply
|
|
229
|
+
r >>= 7;
|
|
230
|
+
g >>= 7;
|
|
231
|
+
b >>= 7;
|
|
232
|
+
a >>= 7;
|
|
233
|
+
|
|
234
|
+
// Un-premultiply
|
|
235
|
+
a = clampTo8((a + (1 << 13)) >> 14);
|
|
236
|
+
if (a > 0) {
|
|
237
|
+
r = (r * 255 / a)|0;
|
|
238
|
+
g = (g * 255 / a)|0;
|
|
239
|
+
b = (b * 255 / a)|0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Bring this value back in range + round result.
|
|
243
|
+
// Shift value = FIXED_FRAC_BITS + 7
|
|
119
244
|
//
|
|
120
|
-
dest[destOffset + 3] =
|
|
121
|
-
dest[destOffset + 2] = clampTo8((b + (1 << 13)) >> 14
|
|
122
|
-
dest[destOffset + 1] = clampTo8((g + (1 << 13)) >> 14
|
|
123
|
-
dest[destOffset] = clampTo8((r + (1 << 13)) >> 14
|
|
245
|
+
dest[destOffset + 3] = a;
|
|
246
|
+
dest[destOffset + 2] = clampTo8((b + (1 << 13)) >> 14);
|
|
247
|
+
dest[destOffset + 1] = clampTo8((g + (1 << 13)) >> 14);
|
|
248
|
+
dest[destOffset] = clampTo8((r + (1 << 13)) >> 14);
|
|
124
249
|
destOffset = (destOffset + srcH * 4)|0;
|
|
125
250
|
}
|
|
126
251
|
|
|
@@ -131,6 +256,8 @@ function convolveVertically(src, dest, srcW, srcH, destW, filters) {
|
|
|
131
256
|
|
|
132
257
|
|
|
133
258
|
module.exports = {
|
|
134
|
-
|
|
135
|
-
|
|
259
|
+
convolveHor,
|
|
260
|
+
convolveVert,
|
|
261
|
+
convolveHorWithPre,
|
|
262
|
+
convolveVertWithPre
|
|
136
263
|
};
|
|
Binary file
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
//
|
|
3
3
|
'use strict';
|
|
4
4
|
/* eslint-disable max-len */
|
|
5
|
-
module.exports = '
|
|
5
|
+
module.exports = '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';
|
package/lib/mm_resize/resize.js
CHANGED
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
const createFilters = require('./resize_filter_gen');
|
|
5
|
-
const
|
|
6
|
-
const convolveVertically = require('./convolve').convolveVertically;
|
|
5
|
+
const { convolveHor, convolveVert, convolveHorWithPre, convolveVertWithPre } = require('./convolve');
|
|
7
6
|
|
|
8
7
|
|
|
8
|
+
function hasAlpha(src, width, height) {
|
|
9
|
+
let ptr = 3, len = (width * height * 4)|0;
|
|
10
|
+
while (ptr < len) {
|
|
11
|
+
if (src[ptr] !== 255) return true;
|
|
12
|
+
ptr = (ptr + 4)|0;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
function resetAlpha(dst, width, height) {
|
|
10
18
|
let ptr = 3, len = (width * height * 4)|0;
|
|
11
19
|
while (ptr < len) { dst[ptr] = 0xFF; ptr = (ptr + 4)|0; }
|
|
@@ -23,26 +31,22 @@ module.exports = function resize(options) {
|
|
|
23
31
|
const offsetX = options.offsetX || 0;
|
|
24
32
|
const offsetY = options.offsetY || 0;
|
|
25
33
|
const dest = options.dest || new Uint8Array(destW * destH * 4);
|
|
26
|
-
const quality = typeof options.quality === 'undefined' ? 3 : options.quality;
|
|
27
|
-
const alpha = options.alpha || false;
|
|
28
|
-
|
|
29
|
-
const filtersX = createFilters(quality, srcW, destW, scaleX, offsetX),
|
|
30
|
-
filtersY = createFilters(quality, srcH, destH, scaleY, offsetY);
|
|
31
|
-
|
|
32
|
-
const tmp = new Uint8Array(destW * srcH * 4);
|
|
33
|
-
|
|
34
|
-
// To use single function we need src & tmp of the same type.
|
|
35
|
-
// But src can be CanvasPixelArray, and tmp - Uint8Array. So, keep
|
|
36
|
-
// vertical and horizontal passes separately to avoid deoptimization.
|
|
37
|
-
|
|
38
|
-
convolveHorizontally(src, tmp, srcW, srcH, destW, filtersX);
|
|
39
|
-
convolveVertically(tmp, dest, srcH, destW, destH, filtersY);
|
|
40
|
-
|
|
41
|
-
// That's faster than doing checks in convolver.
|
|
42
|
-
// !!! Note, canvas data is not premultipled. We don't need other
|
|
43
|
-
// alpha corrections.
|
|
44
34
|
|
|
45
|
-
|
|
35
|
+
const filter = typeof options.filter === 'undefined' ? 'mks2013' : options.filter;
|
|
36
|
+
const filtersX = createFilters(filter, srcW, destW, scaleX, offsetX),
|
|
37
|
+
filtersY = createFilters(filter, srcH, destH, scaleY, offsetY);
|
|
38
|
+
|
|
39
|
+
const tmp = new Uint16Array(destW * srcH * 4);
|
|
40
|
+
|
|
41
|
+
// Autodetect if alpha channel exists, and use appropriate method
|
|
42
|
+
if (hasAlpha(src, srcW, srcH)) {
|
|
43
|
+
convolveHorWithPre(src, tmp, srcW, srcH, destW, filtersX);
|
|
44
|
+
convolveVertWithPre(tmp, dest, srcH, destW, destH, filtersY);
|
|
45
|
+
} else {
|
|
46
|
+
convolveHor(src, tmp, srcW, srcH, destW, filtersX);
|
|
47
|
+
convolveVert(tmp, dest, srcH, destW, destH, filtersY);
|
|
48
|
+
resetAlpha(dest, destW, destH);
|
|
49
|
+
}
|
|
46
50
|
|
|
47
51
|
return dest;
|
|
48
52
|
};
|
|
@@ -21,15 +21,15 @@ function toFixedPoint(num) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
module.exports = function resizeFilterGen(
|
|
24
|
+
module.exports = function resizeFilterGen(filter, srcSize, destSize, scale, offset) {
|
|
25
25
|
|
|
26
|
-
var filterFunction = FILTER_INFO[
|
|
26
|
+
var filterFunction = FILTER_INFO.filter[filter].fn;
|
|
27
27
|
|
|
28
28
|
var scaleInverted = 1.0 / scale;
|
|
29
29
|
var scaleClamped = Math.min(1.0, scale); // For upscale
|
|
30
30
|
|
|
31
31
|
// Filter window (averaging interval), scaled to src image
|
|
32
|
-
var srcWindow = FILTER_INFO[
|
|
32
|
+
var srcWindow = FILTER_INFO.filter[filter].win / scaleClamped;
|
|
33
33
|
|
|
34
34
|
var destPixel, srcPixel, srcFirst, srcLast, filterElementSize,
|
|
35
35
|
floatFilter, fxpFilter, total, pxl, idx, floatVal, filterTotal, filterVal;
|
|
@@ -6,38 +6,65 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const filter = {
|
|
10
|
+
// Nearest neibor
|
|
11
|
+
box: {
|
|
11
12
|
win: 0.5,
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
fn: function (x) {
|
|
14
|
+
if (x < 0) x = -x;
|
|
15
|
+
return (x < 0.5) ? 1.0 : 0.0;
|
|
14
16
|
}
|
|
15
17
|
},
|
|
16
|
-
|
|
18
|
+
// // Hamming
|
|
19
|
+
hamming: {
|
|
17
20
|
win: 1.0,
|
|
18
|
-
|
|
19
|
-
if (x
|
|
20
|
-
if (x
|
|
21
|
+
fn: function (x) {
|
|
22
|
+
if (x < 0) x = -x;
|
|
23
|
+
if (x >= 1.0) { return 0.0; }
|
|
24
|
+
if (x < 1.19209290E-07) { return 1.0; }
|
|
21
25
|
var xpi = x * Math.PI;
|
|
22
26
|
return ((Math.sin(xpi) / xpi) * (0.54 + 0.46 * Math.cos(xpi / 1.0)));
|
|
23
27
|
}
|
|
24
28
|
},
|
|
25
|
-
|
|
29
|
+
// Lanczos, win = 2
|
|
30
|
+
lanczos2: {
|
|
26
31
|
win: 2.0,
|
|
27
|
-
|
|
28
|
-
if (x
|
|
29
|
-
if (x
|
|
32
|
+
fn: function (x) {
|
|
33
|
+
if (x < 0) x = -x;
|
|
34
|
+
if (x >= 2.0) { return 0.0; }
|
|
35
|
+
if (x < 1.19209290E-07) { return 1.0; }
|
|
30
36
|
var xpi = x * Math.PI;
|
|
31
37
|
return (Math.sin(xpi) / xpi) * Math.sin(xpi / 2.0) / (xpi / 2.0);
|
|
32
38
|
}
|
|
33
39
|
},
|
|
34
|
-
|
|
40
|
+
// Lanczos, win = 3
|
|
41
|
+
lanczos3: {
|
|
35
42
|
win: 3.0,
|
|
36
|
-
|
|
37
|
-
if (x
|
|
38
|
-
if (x
|
|
43
|
+
fn: function (x) {
|
|
44
|
+
if (x < 0) x = -x;
|
|
45
|
+
if (x >= 3.0) { return 0.0; }
|
|
46
|
+
if (x < 1.19209290E-07) { return 1.0; }
|
|
39
47
|
var xpi = x * Math.PI;
|
|
40
48
|
return (Math.sin(xpi) / xpi) * Math.sin(xpi / 3.0) / (xpi / 3.0);
|
|
41
49
|
}
|
|
50
|
+
},
|
|
51
|
+
// Magic Kernel Sharp 2013, win = 2.5
|
|
52
|
+
// http://johncostella.com/magic/
|
|
53
|
+
mks2013: {
|
|
54
|
+
win: 2.5,
|
|
55
|
+
fn: function (x) {
|
|
56
|
+
if (x < 0) x = -x;
|
|
57
|
+
if (x >= 2.5) { return 0.0; }
|
|
58
|
+
if (x >= 1.5) { return -0.125 * (x - 2.5) * (x - 2.5); }
|
|
59
|
+
if (x >= 0.5) { return 0.25 * (4 * x * x - 11 * x + 7); }
|
|
60
|
+
return 1.0625 - 1.75 * x * x;
|
|
61
|
+
}
|
|
42
62
|
}
|
|
43
|
-
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
filter: filter,
|
|
67
|
+
// Legacy mapping
|
|
68
|
+
f2q: { box: 0, hamming: 1, lanczos2: 2, lanczos3: 3 },
|
|
69
|
+
q2f: [ 'box', 'hamming', 'lanczos2', 'lanczos3' ]
|
|
70
|
+
};
|