Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MR414N-ID
GitHub Repository: MR414N-ID/botku2
Path: blob/master/node_modules/@jimp/plugin-resize/src/modules/resize.js
1126 views
1
// JavaScript Image Resizer (c) 2012 - Grant Galitz
2
// Released to public domain 29 July 2013: https://github.com/grantgalitz/JS-Image-Resizer/issues/4
3
4
function Resize(
5
widthOriginal,
6
heightOriginal,
7
targetWidth,
8
targetHeight,
9
blendAlpha,
10
interpolationPass,
11
resizeCallback
12
) {
13
this.widthOriginal = Math.abs(Math.floor(widthOriginal) || 0);
14
this.heightOriginal = Math.abs(Math.floor(heightOriginal) || 0);
15
this.targetWidth = Math.abs(Math.floor(targetWidth) || 0);
16
this.targetHeight = Math.abs(Math.floor(targetHeight) || 0);
17
this.colorChannels = blendAlpha ? 4 : 3;
18
this.interpolationPass = Boolean(interpolationPass);
19
this.resizeCallback =
20
typeof resizeCallback === 'function' ? resizeCallback : function() {};
21
22
this.targetWidthMultipliedByChannels = this.targetWidth * this.colorChannels;
23
this.originalWidthMultipliedByChannels =
24
this.widthOriginal * this.colorChannels;
25
this.originalHeightMultipliedByChannels =
26
this.heightOriginal * this.colorChannels;
27
this.widthPassResultSize =
28
this.targetWidthMultipliedByChannels * this.heightOriginal;
29
this.finalResultSize =
30
this.targetWidthMultipliedByChannels * this.targetHeight;
31
this.initialize();
32
}
33
34
Resize.prototype.initialize = function() {
35
// Perform some checks:
36
if (
37
this.widthOriginal > 0 &&
38
this.heightOriginal > 0 &&
39
this.targetWidth > 0 &&
40
this.targetHeight > 0
41
) {
42
this.configurePasses();
43
} else {
44
throw new Error('Invalid settings specified for the resizer.');
45
}
46
};
47
48
Resize.prototype.configurePasses = function() {
49
if (this.widthOriginal === this.targetWidth) {
50
// Bypass the width resizer pass:
51
this.resizeWidth = this.bypassResizer;
52
} else {
53
// Setup the width resizer pass:
54
this.ratioWeightWidthPass = this.widthOriginal / this.targetWidth;
55
if (this.ratioWeightWidthPass < 1 && this.interpolationPass) {
56
this.initializeFirstPassBuffers(true);
57
this.resizeWidth =
58
this.colorChannels === 4
59
? this.resizeWidthInterpolatedRGBA
60
: this.resizeWidthInterpolatedRGB;
61
} else {
62
this.initializeFirstPassBuffers(false);
63
this.resizeWidth =
64
this.colorChannels === 4 ? this.resizeWidthRGBA : this.resizeWidthRGB;
65
}
66
}
67
68
if (this.heightOriginal === this.targetHeight) {
69
// Bypass the height resizer pass:
70
this.resizeHeight = this.bypassResizer;
71
} else {
72
// Setup the height resizer pass:
73
this.ratioWeightHeightPass = this.heightOriginal / this.targetHeight;
74
if (this.ratioWeightHeightPass < 1 && this.interpolationPass) {
75
this.initializeSecondPassBuffers(true);
76
this.resizeHeight = this.resizeHeightInterpolated;
77
} else {
78
this.initializeSecondPassBuffers(false);
79
this.resizeHeight =
80
this.colorChannels === 4 ? this.resizeHeightRGBA : this.resizeHeightRGB;
81
}
82
}
83
};
84
85
Resize.prototype._resizeWidthInterpolatedRGBChannels = function(
86
buffer,
87
fourthChannel
88
) {
89
const channelsNum = fourthChannel ? 4 : 3;
90
const ratioWeight = this.ratioWeightWidthPass;
91
const outputBuffer = this.widthBuffer;
92
93
let weight = 0;
94
let finalOffset = 0;
95
let pixelOffset = 0;
96
let firstWeight = 0;
97
let secondWeight = 0;
98
let targetPosition;
99
100
// Handle for only one interpolation input being valid for start calculation:
101
for (
102
targetPosition = 0;
103
weight < 1 / 3;
104
targetPosition += channelsNum, weight += ratioWeight
105
) {
106
for (
107
finalOffset = targetPosition, pixelOffset = 0;
108
finalOffset < this.widthPassResultSize;
109
pixelOffset += this.originalWidthMultipliedByChannels,
110
finalOffset += this.targetWidthMultipliedByChannels
111
) {
112
outputBuffer[finalOffset] = buffer[pixelOffset];
113
outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
114
outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
115
if (fourthChannel)
116
outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];
117
}
118
}
119
120
// Adjust for overshoot of the last pass's counter:
121
weight -= 1 / 3;
122
let interpolationWidthSourceReadStop;
123
124
for (
125
interpolationWidthSourceReadStop = this.widthOriginal - 1;
126
weight < interpolationWidthSourceReadStop;
127
targetPosition += channelsNum, weight += ratioWeight
128
) {
129
// Calculate weightings:
130
secondWeight = weight % 1;
131
firstWeight = 1 - secondWeight;
132
// Interpolate:
133
for (
134
finalOffset = targetPosition,
135
pixelOffset = Math.floor(weight) * channelsNum;
136
finalOffset < this.widthPassResultSize;
137
pixelOffset += this.originalWidthMultipliedByChannels,
138
finalOffset += this.targetWidthMultipliedByChannels
139
) {
140
outputBuffer[finalOffset + 0] =
141
buffer[pixelOffset + 0] * firstWeight +
142
buffer[pixelOffset + channelsNum + 0] * secondWeight;
143
outputBuffer[finalOffset + 1] =
144
buffer[pixelOffset + 1] * firstWeight +
145
buffer[pixelOffset + channelsNum + 1] * secondWeight;
146
outputBuffer[finalOffset + 2] =
147
buffer[pixelOffset + 2] * firstWeight +
148
buffer[pixelOffset + channelsNum + 2] * secondWeight;
149
if (fourthChannel)
150
outputBuffer[finalOffset + 3] =
151
buffer[pixelOffset + 3] * firstWeight +
152
buffer[pixelOffset + channelsNum + 3] * secondWeight;
153
}
154
}
155
156
// Handle for only one interpolation input being valid for end calculation:
157
for (
158
interpolationWidthSourceReadStop =
159
this.originalWidthMultipliedByChannels - channelsNum;
160
targetPosition < this.targetWidthMultipliedByChannels;
161
targetPosition += channelsNum
162
) {
163
for (
164
finalOffset = targetPosition,
165
pixelOffset = interpolationWidthSourceReadStop;
166
finalOffset < this.widthPassResultSize;
167
pixelOffset += this.originalWidthMultipliedByChannels,
168
finalOffset += this.targetWidthMultipliedByChannels
169
) {
170
outputBuffer[finalOffset] = buffer[pixelOffset];
171
outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
172
outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
173
if (fourthChannel)
174
outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];
175
}
176
}
177
178
return outputBuffer;
179
};
180
181
Resize.prototype._resizeWidthRGBChannels = function(buffer, fourthChannel) {
182
const channelsNum = fourthChannel ? 4 : 3;
183
const ratioWeight = this.ratioWeightWidthPass;
184
const ratioWeightDivisor = 1 / ratioWeight;
185
const nextLineOffsetOriginalWidth =
186
this.originalWidthMultipliedByChannels - channelsNum + 1;
187
const nextLineOffsetTargetWidth =
188
this.targetWidthMultipliedByChannels - channelsNum + 1;
189
const output = this.outputWidthWorkBench;
190
const outputBuffer = this.widthBuffer;
191
const trustworthyColorsCount = this.outputWidthWorkBenchOpaquePixelsCount;
192
193
let weight = 0;
194
let amountToNext = 0;
195
let actualPosition = 0;
196
let currentPosition = 0;
197
let line = 0;
198
let pixelOffset = 0;
199
let outputOffset = 0;
200
let multiplier = 1;
201
let r = 0;
202
let g = 0;
203
let b = 0;
204
let a = 0;
205
206
do {
207
for (line = 0; line < this.originalHeightMultipliedByChannels; ) {
208
output[line++] = 0;
209
output[line++] = 0;
210
output[line++] = 0;
211
if (fourthChannel) {
212
output[line++] = 0;
213
trustworthyColorsCount[line / channelsNum - 1] = 0;
214
}
215
}
216
217
weight = ratioWeight;
218
219
do {
220
amountToNext = 1 + actualPosition - currentPosition;
221
multiplier = Math.min(weight, amountToNext);
222
for (
223
line = 0, pixelOffset = actualPosition;
224
line < this.originalHeightMultipliedByChannels;
225
pixelOffset += nextLineOffsetOriginalWidth
226
) {
227
r = buffer[pixelOffset];
228
g = buffer[++pixelOffset];
229
b = buffer[++pixelOffset];
230
a = fourthChannel ? buffer[++pixelOffset] : 255;
231
// Ignore RGB values if pixel is completely transparent
232
output[line++] += (a ? r : 0) * multiplier;
233
output[line++] += (a ? g : 0) * multiplier;
234
output[line++] += (a ? b : 0) * multiplier;
235
if (fourthChannel) {
236
output[line++] += a * multiplier;
237
trustworthyColorsCount[line / channelsNum - 1] += a ? multiplier : 0;
238
}
239
}
240
241
if (weight >= amountToNext) {
242
actualPosition += channelsNum;
243
currentPosition = actualPosition;
244
weight -= amountToNext;
245
} else {
246
currentPosition += weight;
247
break;
248
}
249
} while (
250
weight > 0 &&
251
actualPosition < this.originalWidthMultipliedByChannels
252
);
253
254
for (
255
line = 0, pixelOffset = outputOffset;
256
line < this.originalHeightMultipliedByChannels;
257
pixelOffset += nextLineOffsetTargetWidth
258
) {
259
weight = fourthChannel ? trustworthyColorsCount[line / channelsNum] : 1;
260
multiplier = fourthChannel
261
? weight
262
? 1 / weight
263
: 0
264
: ratioWeightDivisor;
265
outputBuffer[pixelOffset] = output[line++] * multiplier;
266
outputBuffer[++pixelOffset] = output[line++] * multiplier;
267
outputBuffer[++pixelOffset] = output[line++] * multiplier;
268
if (fourthChannel)
269
outputBuffer[++pixelOffset] = output[line++] * ratioWeightDivisor;
270
}
271
272
outputOffset += channelsNum;
273
} while (outputOffset < this.targetWidthMultipliedByChannels);
274
275
return outputBuffer;
276
};
277
278
Resize.prototype._resizeHeightRGBChannels = function(buffer, fourthChannel) {
279
const ratioWeight = this.ratioWeightHeightPass;
280
const ratioWeightDivisor = 1 / ratioWeight;
281
const output = this.outputHeightWorkBench;
282
const outputBuffer = this.heightBuffer;
283
const trustworthyColorsCount = this.outputHeightWorkBenchOpaquePixelsCount;
284
285
let weight = 0;
286
let amountToNext = 0;
287
let actualPosition = 0;
288
let currentPosition = 0;
289
let pixelOffset = 0;
290
let outputOffset = 0;
291
let caret = 0;
292
let multiplier = 1;
293
let r = 0;
294
let g = 0;
295
let b = 0;
296
let a = 0;
297
298
do {
299
for (
300
pixelOffset = 0;
301
pixelOffset < this.targetWidthMultipliedByChannels;
302
303
) {
304
output[pixelOffset++] = 0;
305
output[pixelOffset++] = 0;
306
output[pixelOffset++] = 0;
307
308
if (fourthChannel) {
309
output[pixelOffset++] = 0;
310
trustworthyColorsCount[pixelOffset / 4 - 1] = 0;
311
}
312
}
313
314
weight = ratioWeight;
315
316
do {
317
amountToNext = 1 + actualPosition - currentPosition;
318
multiplier = Math.min(weight, amountToNext);
319
caret = actualPosition;
320
321
for (
322
pixelOffset = 0;
323
pixelOffset < this.targetWidthMultipliedByChannels;
324
325
) {
326
r = buffer[caret++];
327
g = buffer[caret++];
328
b = buffer[caret++];
329
a = fourthChannel ? buffer[caret++] : 255;
330
// Ignore RGB values if pixel is completely transparent
331
output[pixelOffset++] += (a ? r : 0) * multiplier;
332
output[pixelOffset++] += (a ? g : 0) * multiplier;
333
output[pixelOffset++] += (a ? b : 0) * multiplier;
334
335
if (fourthChannel) {
336
output[pixelOffset++] += a * multiplier;
337
trustworthyColorsCount[pixelOffset / 4 - 1] += a ? multiplier : 0;
338
}
339
}
340
341
if (weight >= amountToNext) {
342
actualPosition = caret;
343
currentPosition = actualPosition;
344
weight -= amountToNext;
345
} else {
346
currentPosition += weight;
347
break;
348
}
349
} while (weight > 0 && actualPosition < this.widthPassResultSize);
350
351
for (
352
pixelOffset = 0;
353
pixelOffset < this.targetWidthMultipliedByChannels;
354
355
) {
356
weight = fourthChannel ? trustworthyColorsCount[pixelOffset / 4] : 1;
357
multiplier = fourthChannel
358
? weight
359
? 1 / weight
360
: 0
361
: ratioWeightDivisor;
362
outputBuffer[outputOffset++] = Math.round(
363
output[pixelOffset++] * multiplier
364
);
365
outputBuffer[outputOffset++] = Math.round(
366
output[pixelOffset++] * multiplier
367
);
368
outputBuffer[outputOffset++] = Math.round(
369
output[pixelOffset++] * multiplier
370
);
371
372
if (fourthChannel) {
373
outputBuffer[outputOffset++] = Math.round(
374
output[pixelOffset++] * ratioWeightDivisor
375
);
376
}
377
}
378
} while (outputOffset < this.finalResultSize);
379
380
return outputBuffer;
381
};
382
383
Resize.prototype.resizeWidthInterpolatedRGB = function(buffer) {
384
return this._resizeWidthInterpolatedRGBChannels(buffer, false);
385
};
386
387
Resize.prototype.resizeWidthInterpolatedRGBA = function(buffer) {
388
return this._resizeWidthInterpolatedRGBChannels(buffer, true);
389
};
390
391
Resize.prototype.resizeWidthRGB = function(buffer) {
392
return this._resizeWidthRGBChannels(buffer, false);
393
};
394
395
Resize.prototype.resizeWidthRGBA = function(buffer) {
396
return this._resizeWidthRGBChannels(buffer, true);
397
};
398
399
Resize.prototype.resizeHeightInterpolated = function(buffer) {
400
const ratioWeight = this.ratioWeightHeightPass;
401
const outputBuffer = this.heightBuffer;
402
403
let weight = 0;
404
let finalOffset = 0;
405
let pixelOffset = 0;
406
let pixelOffsetAccumulated = 0;
407
let pixelOffsetAccumulated2 = 0;
408
let firstWeight = 0;
409
let secondWeight = 0;
410
let interpolationHeightSourceReadStop;
411
412
// Handle for only one interpolation input being valid for start calculation:
413
for (; weight < 1 / 3; weight += ratioWeight) {
414
for (
415
pixelOffset = 0;
416
pixelOffset < this.targetWidthMultipliedByChannels;
417
418
) {
419
outputBuffer[finalOffset++] = Math.round(buffer[pixelOffset++]);
420
}
421
}
422
423
// Adjust for overshoot of the last pass's counter:
424
weight -= 1 / 3;
425
426
for (
427
interpolationHeightSourceReadStop = this.heightOriginal - 1;
428
weight < interpolationHeightSourceReadStop;
429
weight += ratioWeight
430
) {
431
// Calculate weightings:
432
secondWeight = weight % 1;
433
firstWeight = 1 - secondWeight;
434
// Interpolate:
435
pixelOffsetAccumulated =
436
Math.floor(weight) * this.targetWidthMultipliedByChannels;
437
pixelOffsetAccumulated2 =
438
pixelOffsetAccumulated + this.targetWidthMultipliedByChannels;
439
for (
440
pixelOffset = 0;
441
pixelOffset < this.targetWidthMultipliedByChannels;
442
++pixelOffset
443
) {
444
outputBuffer[finalOffset++] = Math.round(
445
buffer[pixelOffsetAccumulated++] * firstWeight +
446
buffer[pixelOffsetAccumulated2++] * secondWeight
447
);
448
}
449
}
450
451
// Handle for only one interpolation input being valid for end calculation:
452
while (finalOffset < this.finalResultSize) {
453
for (
454
pixelOffset = 0,
455
pixelOffsetAccumulated =
456
interpolationHeightSourceReadStop *
457
this.targetWidthMultipliedByChannels;
458
pixelOffset < this.targetWidthMultipliedByChannels;
459
++pixelOffset
460
) {
461
outputBuffer[finalOffset++] = Math.round(
462
buffer[pixelOffsetAccumulated++]
463
);
464
}
465
}
466
467
return outputBuffer;
468
};
469
470
Resize.prototype.resizeHeightRGB = function(buffer) {
471
return this._resizeHeightRGBChannels(buffer, false);
472
};
473
474
Resize.prototype.resizeHeightRGBA = function(buffer) {
475
return this._resizeHeightRGBChannels(buffer, true);
476
};
477
478
Resize.prototype.resize = function(buffer) {
479
this.resizeCallback(this.resizeHeight(this.resizeWidth(buffer)));
480
};
481
482
Resize.prototype.bypassResizer = function(buffer) {
483
// Just return the buffer passed:
484
return buffer;
485
};
486
487
Resize.prototype.initializeFirstPassBuffers = function(BILINEARAlgo) {
488
// Initialize the internal width pass buffers:
489
this.widthBuffer = this.generateFloatBuffer(this.widthPassResultSize);
490
491
if (!BILINEARAlgo) {
492
this.outputWidthWorkBench = this.generateFloatBuffer(
493
this.originalHeightMultipliedByChannels
494
);
495
496
if (this.colorChannels > 3) {
497
this.outputWidthWorkBenchOpaquePixelsCount = this.generateFloat64Buffer(
498
this.heightOriginal
499
);
500
}
501
}
502
};
503
504
Resize.prototype.initializeSecondPassBuffers = function(BILINEARAlgo) {
505
// Initialize the internal height pass buffers:
506
this.heightBuffer = this.generateUint8Buffer(this.finalResultSize);
507
508
if (!BILINEARAlgo) {
509
this.outputHeightWorkBench = this.generateFloatBuffer(
510
this.targetWidthMultipliedByChannels
511
);
512
513
if (this.colorChannels > 3) {
514
this.outputHeightWorkBenchOpaquePixelsCount = this.generateFloat64Buffer(
515
this.targetWidth
516
);
517
}
518
}
519
};
520
521
Resize.prototype.generateFloatBuffer = function(bufferLength) {
522
// Generate a float32 typed array buffer:
523
try {
524
return new Float32Array(bufferLength);
525
} catch (error) {
526
return [];
527
}
528
};
529
530
Resize.prototype.generateFloat64Buffer = function(bufferLength) {
531
// Generate a float64 typed array buffer:
532
try {
533
return new Float64Array(bufferLength);
534
} catch (error) {
535
return [];
536
}
537
};
538
539
Resize.prototype.generateUint8Buffer = function(bufferLength) {
540
// Generate a uint8 typed array buffer:
541
try {
542
return new Uint8Array(bufferLength);
543
} catch (error) {
544
return [];
545
}
546
};
547
548
module.exports = Resize;
549
550