nodejs-audio-visualizer 5.5.3 → 5.6.3
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/LICENSE +21 -21
- package/README.md +84 -83
- package/dist/audio.d.ts +13 -13
- package/dist/audio.js +128 -128
- package/dist/beats.d.ts +39 -39
- package/dist/beats.js +220 -220
- package/dist/bpmEncoder.d.ts +14 -14
- package/dist/bpmEncoder.js +97 -97
- package/dist/config.d.ts +81 -80
- package/dist/config.js +392 -391
- package/dist/config.js.map +1 -1
- package/dist/dsp.d.ts +16 -16
- package/dist/dsp.js +76 -76
- package/dist/highlight.d.ts +91 -91
- package/dist/highlight.js +606 -606
- package/dist/image.d.ts +107 -97
- package/dist/image.js +614 -545
- package/dist/image.js.map +1 -1
- package/dist/index.d.ts +98 -96
- package/dist/index.js +673 -667
- package/dist/index.js.map +1 -1
- package/dist/subtitleConvert.d.ts +3 -3
- package/dist/subtitleConvert.js +61 -61
- package/dist/video.d.ts +171 -171
- package/dist/video.js +743 -743
- package/dist/waitForEventLoop.d.ts +1 -1
- package/dist/waitForEventLoop.js +5 -5
- package/package.json +62 -62
- package/src/audio.ts +129 -129
- package/src/beats.ts +279 -279
- package/src/bpmEncoder.ts +97 -97
- package/src/config.ts +504 -501
- package/src/dsp.ts +95 -95
- package/src/highlight.ts +617 -617
- package/src/image.ts +791 -707
- package/src/index.ts +896 -885
- package/src/subtitleConvert.ts +63 -63
- package/src/tests/audio.test.ts +102 -102
- package/src/tests/beats.test.ts +134 -134
- package/src/tests/config.test.ts +568 -553
- package/src/tests/dsp.test.ts +24 -24
- package/src/tests/highlight.test.ts +397 -397
- package/src/tests/image.test.ts +371 -319
- package/src/tests/index.test.ts +180 -180
- package/src/tests/subtitleConvert.test.ts +23 -23
- package/src/tests/video.test.ts +652 -652
- package/src/typings.d.ts +28 -28
- package/src/video.ts +947 -947
- package/src/waitForEventLoop.ts +2 -2
package/dist/image.js
CHANGED
|
@@ -1,546 +1,615 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __assign = (this && this.__assign) || function () {
|
|
3
|
-
__assign = Object.assign || function(t) {
|
|
4
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
-
s = arguments[i];
|
|
6
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
-
t[p] = s[p];
|
|
8
|
-
}
|
|
9
|
-
return t;
|
|
10
|
-
};
|
|
11
|
-
return __assign.apply(this, arguments);
|
|
12
|
-
};
|
|
13
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
-
});
|
|
21
|
-
};
|
|
22
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
24
|
-
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
-
function step(op) {
|
|
27
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
-
while (_) try {
|
|
29
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
-
switch (op[0]) {
|
|
32
|
-
case 0: case 1: t = op; break;
|
|
33
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
-
default:
|
|
37
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
-
if (t[2]) _.ops.pop();
|
|
42
|
-
_.trys.pop(); continue;
|
|
43
|
-
}
|
|
44
|
-
op = body.call(thisArg, _);
|
|
45
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
-
};
|
|
52
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
-
var bmp_js_1 = require("bmp-js");
|
|
54
|
-
var jimp_1 = __importDefault(require("jimp"));
|
|
55
|
-
exports.mixValues = function (value1, value1Opacity, value2) {
|
|
56
|
-
return Math.round(value2 * (1 - value1Opacity) + value1 * value1Opacity);
|
|
57
|
-
};
|
|
58
|
-
exports.mixColors = function (_a) {
|
|
59
|
-
var color = _a.color, colorOpacity = _a.colorOpacity, backgroundColor = _a.backgroundColor;
|
|
60
|
-
return {
|
|
61
|
-
red: exports.mixValues(color.red, colorOpacity, backgroundColor.red),
|
|
62
|
-
green: exports.mixValues(color.green, colorOpacity, backgroundColor.green),
|
|
63
|
-
blue: exports.mixValues(color.blue, colorOpacity, backgroundColor.blue),
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
var getRectPixelColor = function (params) {
|
|
67
|
-
var imageDstBuffer = params.imageDstBuffer, pixelIndex = params.pixelIndex, color = params.color, opacity = params.opacity;
|
|
68
|
-
if (opacity === 1) {
|
|
69
|
-
return color;
|
|
70
|
-
}
|
|
71
|
-
var backgroundPixelColor = {
|
|
72
|
-
blue: imageDstBuffer.data[pixelIndex],
|
|
73
|
-
green: imageDstBuffer.data[pixelIndex + 1],
|
|
74
|
-
red: imageDstBuffer.data[pixelIndex + 2],
|
|
75
|
-
};
|
|
76
|
-
var resultColor = exports.mixColors({
|
|
77
|
-
color: color,
|
|
78
|
-
colorOpacity: opacity,
|
|
79
|
-
backgroundColor: backgroundPixelColor,
|
|
80
|
-
});
|
|
81
|
-
return resultColor;
|
|
82
|
-
};
|
|
83
|
-
exports.drawRect = function (_a) {
|
|
84
|
-
var imageDstBuffer = _a.imageDstBuffer, position = _a.position, size = _a.size, color = _a.color, opacity = _a.opacity;
|
|
85
|
-
for (var currY = position.y; currY < position.y + size.height; currY++) {
|
|
86
|
-
for (var currX = position.x; currX < position.x + size.width; currX++) {
|
|
87
|
-
var pixelIndex = imageDstBuffer.shiftPos + currY * imageDstBuffer.rowBytes + currX * 3;
|
|
88
|
-
var pixelColor = getRectPixelColor({
|
|
89
|
-
imageDstBuffer: imageDstBuffer,
|
|
90
|
-
pixelIndex: pixelIndex,
|
|
91
|
-
color: color,
|
|
92
|
-
opacity: opacity,
|
|
93
|
-
});
|
|
94
|
-
imageDstBuffer.data[pixelIndex] = pixelColor.blue;
|
|
95
|
-
imageDstBuffer.data[pixelIndex + 1] = pixelColor.green;
|
|
96
|
-
imageDstBuffer.data[pixelIndex + 2] = pixelColor.red;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
var buildQuadEdgeTable = function (points) {
|
|
101
|
-
var edges = [];
|
|
102
|
-
for (var i = 0; i < points.length; i++) {
|
|
103
|
-
var p1 = points[i];
|
|
104
|
-
var p2 = points[(i + 1) % points.length];
|
|
105
|
-
if (p1.y === p2.y)
|
|
106
|
-
continue; // Skip horizontal edges
|
|
107
|
-
var yMin = Math.min(p1.y, p2.y);
|
|
108
|
-
var yMax = Math.max(p1.y, p2.y);
|
|
109
|
-
var x = p1.y < p2.y ? p1.x : p2.x;
|
|
110
|
-
var dx = (p2.x - p1.x) / (p2.y - p1.y);
|
|
111
|
-
edges.push({ yMin: yMin, yMax: yMax, x: x, dx: dx });
|
|
112
|
-
}
|
|
113
|
-
return edges.sort(function (a, b) { return a.yMin - b.yMin; });
|
|
114
|
-
};
|
|
115
|
-
/**
|
|
116
|
-
* Fills a quadrilateral (4-sided polygon) using scan-line algorithm
|
|
117
|
-
* Only the outer edges are anti-aliased
|
|
118
|
-
*/
|
|
119
|
-
exports.fillRect = function (_a) {
|
|
120
|
-
var imageDstBuffer = _a.imageDstBuffer, points = _a.points, color = _a.color, opacity = _a.opacity;
|
|
121
|
-
// Build edge table from all 4 vertices
|
|
122
|
-
var edgeTable = buildQuadEdgeTable(points);
|
|
123
|
-
if (edgeTable.length === 0)
|
|
124
|
-
return;
|
|
125
|
-
// Find the bounding box
|
|
126
|
-
var yMin = Math.min.apply(Math, points.map(function (p) { return p.y; }));
|
|
127
|
-
var yMax = Math.max.apply(Math, points.map(function (p) { return p.y; }));
|
|
128
|
-
// Active Edge Table for scan line algorithm
|
|
129
|
-
var activeEdges = [];
|
|
130
|
-
// Scan line algorithm: process each horizontal line
|
|
131
|
-
for (var y = Math.floor(yMin); y <= Math.ceil(yMax); y++) {
|
|
132
|
-
// Add edges that start at this scanline to AET
|
|
133
|
-
for (var _i = 0, edgeTable_1 = edgeTable; _i < edgeTable_1.length; _i++) {
|
|
134
|
-
var edge = edgeTable_1[_i];
|
|
135
|
-
if (Math.floor(edge.yMin) === y) {
|
|
136
|
-
activeEdges.push(__assign({}, edge));
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// Remove edges that end at this scanline from AET
|
|
140
|
-
for (var i = activeEdges.length - 1; i >= 0; i--) {
|
|
141
|
-
if (Math.ceil(activeEdges[i].yMax) <= y) {
|
|
142
|
-
activeEdges.splice(i, 1);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
// Sort active edges by x coordinate
|
|
146
|
-
activeEdges.sort(function (a, b) { return a.x - b.x; });
|
|
147
|
-
// Fill pixels between pairs of intersections
|
|
148
|
-
for (var i = 0; i < activeEdges.length; i += 2) {
|
|
149
|
-
if (i + 1 < activeEdges.length) {
|
|
150
|
-
var xStart = activeEdges[i].x;
|
|
151
|
-
var xEnd = activeEdges[i + 1].x;
|
|
152
|
-
// Anti-aliased rendering with sub-pixel precision
|
|
153
|
-
var xStartFloor = Math.floor(xStart);
|
|
154
|
-
var xEndCeil = Math.ceil(xEnd);
|
|
155
|
-
for (var x = xStartFloor; x < xEndCeil; x++) {
|
|
156
|
-
var pixelIndex = imageDstBuffer.shiftPos + y * imageDstBuffer.rowBytes + x * 3;
|
|
157
|
-
// Calculate coverage for anti-aliasing
|
|
158
|
-
var coverage = 1.0;
|
|
159
|
-
if (x === xStartFloor) {
|
|
160
|
-
// Left edge pixel - calculate fractional coverage
|
|
161
|
-
coverage = Math.min(1.0, 1.0 - (xStart - xStartFloor));
|
|
162
|
-
}
|
|
163
|
-
else if (x === xEndCeil - 1) {
|
|
164
|
-
// Right edge pixel - calculate fractional coverage
|
|
165
|
-
coverage = Math.min(1.0, xEnd - Math.floor(xEnd));
|
|
166
|
-
}
|
|
167
|
-
// Apply coverage to opacity for smooth edges
|
|
168
|
-
var effectiveOpacity = opacity * coverage;
|
|
169
|
-
var pixelColor = getRectPixelColor({
|
|
170
|
-
imageDstBuffer: imageDstBuffer,
|
|
171
|
-
pixelIndex: pixelIndex,
|
|
172
|
-
color: color,
|
|
173
|
-
opacity: effectiveOpacity,
|
|
174
|
-
});
|
|
175
|
-
imageDstBuffer.data[pixelIndex] = pixelColor.blue;
|
|
176
|
-
imageDstBuffer.data[pixelIndex + 1] = pixelColor.green;
|
|
177
|
-
imageDstBuffer.data[pixelIndex + 2] = pixelColor.red;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
// Update x coordinates for next scanline (x' = x + dx)
|
|
182
|
-
for (var _b = 0, activeEdges_1 = activeEdges; _b < activeEdges_1.length; _b++) {
|
|
183
|
-
var edge = activeEdges_1[_b];
|
|
184
|
-
edge.x += edge.dx;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
var getRectY = function (rotation, top, height, rectHeight) {
|
|
189
|
-
switch (rotation) {
|
|
190
|
-
case 'up':
|
|
191
|
-
return top + height - rectHeight;
|
|
192
|
-
case 'mirror':
|
|
193
|
-
return top + Math.trunc((height - rectHeight) / 2);
|
|
194
|
-
default:
|
|
195
|
-
return top;
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
var minRectHeight = 2;
|
|
199
|
-
var getRectHeight = function (height, spectrumValue) {
|
|
200
|
-
var rectHeight = Math.trunc(height * spectrumValue);
|
|
201
|
-
if (rectHeight < minRectHeight) {
|
|
202
|
-
return minRectHeight;
|
|
203
|
-
}
|
|
204
|
-
return rectHeight;
|
|
205
|
-
};
|
|
206
|
-
var drawSpectrum = function (_a) {
|
|
207
|
-
var imageDstBuffer = _a.imageDstBuffer, spectrum = _a.spectrum, size = _a.size, position = _a.position, rotation = _a.rotation, margin = _a.margin, color = _a.color, opacity = _a.opacity;
|
|
208
|
-
var busWidth = Math.trunc(size.width / spectrum.length);
|
|
209
|
-
var left = Math.trunc(position.x);
|
|
210
|
-
var top = Math.trunc(position.y);
|
|
211
|
-
var height = Math.trunc(size.height);
|
|
212
|
-
for (var spectrumX = 0; spectrumX < spectrum.length; spectrumX++) {
|
|
213
|
-
var spectrumValue = spectrum[spectrumX];
|
|
214
|
-
if ((spectrumValue > 1) || (spectrumValue < 0)) {
|
|
215
|
-
throw new Error('Spectrum values must be in range from 0 to 1');
|
|
216
|
-
}
|
|
217
|
-
var rectX = left + busWidth * spectrumX;
|
|
218
|
-
var rectHeight = getRectHeight(height, spectrumValue);
|
|
219
|
-
var rectY = getRectY(rotation, top, height, rectHeight);
|
|
220
|
-
exports.drawRect({
|
|
221
|
-
imageDstBuffer: imageDstBuffer,
|
|
222
|
-
position: { x: rectX + margin, y: rectY },
|
|
223
|
-
size: { width: busWidth - margin / 2, height: rectHeight },
|
|
224
|
-
color: color,
|
|
225
|
-
opacity: opacity,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
var calcCirclePoint = function (angleRadians, radius) {
|
|
230
|
-
var circleX = radius * Math.cos(angleRadians);
|
|
231
|
-
var circleY = radius * Math.sin(angleRadians);
|
|
232
|
-
return { x: circleX, y: circleY };
|
|
233
|
-
};
|
|
234
|
-
var lerp = function (start, end, t) {
|
|
235
|
-
return start + (end - start) * t;
|
|
236
|
-
};
|
|
237
|
-
var drawPolarSpectrum = function (_a) {
|
|
238
|
-
var imageDstBuffer = _a.imageDstBuffer, spectrum = _a.spectrum, centerX = _a.centerX, centerY = _a.centerY, innerRadius = _a.innerRadius, maxBarLength = _a.maxBarLength, barWidth = _a.barWidth, color = _a.color, opacity = _a.opacity;
|
|
239
|
-
var angleStep = (2 * Math.PI) / spectrum.length;
|
|
240
|
-
var innerLength = innerRadius * 2 * Math.PI;
|
|
241
|
-
var radiansPerPixel = 2 * Math.PI / innerLength;
|
|
242
|
-
var barWidthInRadians = radiansPerPixel * barWidth;
|
|
243
|
-
var spectrumAverage = spectrum.reduce(function (a, b) { return a + b; }, 0) / spectrum.length;
|
|
244
|
-
var scaleFactor = lerp(0.75, 1, spectrumAverage);
|
|
245
|
-
var scaledMaxOuterRadius = (innerRadius + maxBarLength) * scaleFactor;
|
|
246
|
-
var scaledInnerRadius = innerRadius * scaleFactor;
|
|
247
|
-
for (var i = 0; i < spectrum.length; i++) {
|
|
248
|
-
var currentSpectrumValue = spectrum[i] || 0;
|
|
249
|
-
var busRadius = lerp(scaledInnerRadius, scaledMaxOuterRadius, currentSpectrumValue);
|
|
250
|
-
var angle = i * angleStep;
|
|
251
|
-
var angleRadiansStart = angle;
|
|
252
|
-
var angleRadiansEnd = angle + barWidthInRadians;
|
|
253
|
-
var point1 = calcCirclePoint(angleRadiansStart, scaledInnerRadius);
|
|
254
|
-
var point2 = calcCirclePoint(angleRadiansStart, busRadius);
|
|
255
|
-
var point3 = calcCirclePoint(angleRadiansEnd, scaledInnerRadius);
|
|
256
|
-
var point4 = calcCirclePoint(angleRadiansEnd, busRadius);
|
|
257
|
-
exports.fillRect({
|
|
258
|
-
imageDstBuffer: imageDstBuffer,
|
|
259
|
-
points: [
|
|
260
|
-
{ x: centerX + Math.round(point1.x), y: centerY + Math.round(point1.y) },
|
|
261
|
-
{ x: centerX + Math.round(point2.x), y: centerY + Math.round(point2.y) },
|
|
262
|
-
{ x: centerX + Math.round(point4.x), y: centerY + Math.round(point4.y) },
|
|
263
|
-
{ x: centerX + Math.round(point3.x), y: centerY + Math.round(point3.y) },
|
|
264
|
-
],
|
|
265
|
-
color: color,
|
|
266
|
-
opacity: opacity,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
exports.createSpectrumVisualizerFrameGenerator = function () {
|
|
271
|
-
var prevSpectrum = null;
|
|
272
|
-
return function (_a) {
|
|
273
|
-
var backgroundImageBuffer = _a.backgroundImageBuffer, spectrum = _a.spectrum, size = _a.size, position = _a.position, rotation = _a.rotation, margin = _a.margin, color = _a.color, opacity = _a.opacity, spectrumEffect = _a.spectrumEffect;
|
|
274
|
-
var imageDstBuffer = Object.assign({}, backgroundImageBuffer);
|
|
275
|
-
imageDstBuffer.data = Buffer.from(backgroundImageBuffer.data);
|
|
276
|
-
var rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
|
|
277
|
-
if (spectrumEffect === 'volume') {
|
|
278
|
-
drawSpectrum({
|
|
279
|
-
imageDstBuffer: imageDstBuffer,
|
|
280
|
-
spectrum: spectrum,
|
|
281
|
-
size: size,
|
|
282
|
-
position: { x: position.x + 2, y: position.y + 2 },
|
|
283
|
-
rotation: rotation,
|
|
284
|
-
margin: margin,
|
|
285
|
-
color: rgbSpectrumColor,
|
|
286
|
-
opacity: opacity * 0.5,
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
if (spectrumEffect === 'smooth' && prevSpectrum) {
|
|
290
|
-
drawSpectrum({
|
|
291
|
-
imageDstBuffer: imageDstBuffer,
|
|
292
|
-
spectrum: prevSpectrum,
|
|
293
|
-
size: size,
|
|
294
|
-
position: position,
|
|
295
|
-
rotation: rotation,
|
|
296
|
-
margin: margin,
|
|
297
|
-
color: rgbSpectrumColor,
|
|
298
|
-
opacity: opacity * 0.3,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
drawSpectrum({
|
|
302
|
-
imageDstBuffer: imageDstBuffer,
|
|
303
|
-
spectrum: spectrum,
|
|
304
|
-
size: size,
|
|
305
|
-
position: position,
|
|
306
|
-
rotation: rotation,
|
|
307
|
-
margin: margin,
|
|
308
|
-
color: rgbSpectrumColor,
|
|
309
|
-
opacity: opacity,
|
|
310
|
-
});
|
|
311
|
-
prevSpectrum = spectrum;
|
|
312
|
-
return imageDstBuffer;
|
|
313
|
-
};
|
|
314
|
-
};
|
|
315
|
-
exports.createPolarVisualizerFrameGenerator = function () {
|
|
316
|
-
var prevSpectrum = null;
|
|
317
|
-
return function (_a) {
|
|
318
|
-
var backgroundImageBuffer = _a.backgroundImageBuffer, spectrum = _a.spectrum, centerX = _a.centerX, centerY = _a.centerY, innerRadius = _a.innerRadius, maxBarLength = _a.maxBarLength, barWidth = _a.barWidth, color = _a.color, opacity = _a.opacity, spectrumEffect = _a.spectrumEffect;
|
|
319
|
-
var imageDstBuffer = Object.assign({}, backgroundImageBuffer);
|
|
320
|
-
imageDstBuffer.data = Buffer.from(backgroundImageBuffer.data);
|
|
321
|
-
var rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
|
|
322
|
-
if (spectrumEffect === 'volume') {
|
|
323
|
-
drawPolarSpectrum({
|
|
324
|
-
imageDstBuffer: imageDstBuffer,
|
|
325
|
-
spectrum: spectrum,
|
|
326
|
-
centerX: centerX + 2,
|
|
327
|
-
centerY: centerY + 2,
|
|
328
|
-
innerRadius: innerRadius,
|
|
329
|
-
maxBarLength: maxBarLength,
|
|
330
|
-
barWidth: barWidth,
|
|
331
|
-
color: rgbSpectrumColor,
|
|
332
|
-
opacity: opacity * 0.5,
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
if (spectrumEffect === 'smooth' && prevSpectrum) {
|
|
336
|
-
drawPolarSpectrum({
|
|
337
|
-
imageDstBuffer: imageDstBuffer,
|
|
338
|
-
spectrum: prevSpectrum,
|
|
339
|
-
centerX: centerX,
|
|
340
|
-
centerY: centerY,
|
|
341
|
-
innerRadius: innerRadius,
|
|
342
|
-
maxBarLength: maxBarLength,
|
|
343
|
-
barWidth: barWidth,
|
|
344
|
-
color: rgbSpectrumColor,
|
|
345
|
-
opacity: opacity * 0.3,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
drawPolarSpectrum({
|
|
349
|
-
imageDstBuffer: imageDstBuffer,
|
|
350
|
-
spectrum: spectrum,
|
|
351
|
-
centerX: centerX,
|
|
352
|
-
centerY: centerY,
|
|
353
|
-
innerRadius: innerRadius,
|
|
354
|
-
maxBarLength: maxBarLength,
|
|
355
|
-
barWidth: barWidth,
|
|
356
|
-
color: rgbSpectrumColor,
|
|
357
|
-
opacity: opacity,
|
|
358
|
-
});
|
|
359
|
-
prevSpectrum = spectrum;
|
|
360
|
-
return imageDstBuffer;
|
|
361
|
-
};
|
|
362
|
-
};
|
|
363
|
-
exports.convertToBmp = function (filePath, targetWidth, targetHeight) { return __awaiter(void 0, void 0, void 0, function () {
|
|
364
|
-
return __generator(this, function (_a) {
|
|
365
|
-
return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(void 0, void 0, void 0, function () {
|
|
366
|
-
var image, sw, sh, isOrientationMismatch, targetAspect, cropW, cropH, cropX, cropY, _a;
|
|
367
|
-
return __generator(this, function (_b) {
|
|
368
|
-
switch (_b.label) {
|
|
369
|
-
case 0:
|
|
370
|
-
_b.trys.push([0, 2, , 3]);
|
|
371
|
-
return [4 /*yield*/, jimp_1.default.read(filePath)];
|
|
372
|
-
case 1:
|
|
373
|
-
image = _b.sent();
|
|
374
|
-
if (targetWidth && targetHeight) {
|
|
375
|
-
sw = image.getWidth();
|
|
376
|
-
sh = image.getHeight();
|
|
377
|
-
isOrientationMismatch = (sw > sh && targetWidth < targetHeight) ||
|
|
378
|
-
(sw < sh && targetWidth > targetHeight);
|
|
379
|
-
if (isOrientationMismatch) {
|
|
380
|
-
targetAspect = targetWidth / targetHeight;
|
|
381
|
-
cropW = void 0, cropH = void 0, cropX = void 0, cropY = void 0;
|
|
382
|
-
if (sw / sh > targetAspect) {
|
|
383
|
-
cropW = Math.round(sh * targetAspect);
|
|
384
|
-
cropH = sh;
|
|
385
|
-
cropX = Math.round((sw - cropW) / 2);
|
|
386
|
-
cropY = 0;
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
cropW = sw;
|
|
390
|
-
cropH = Math.round(sw / targetAspect);
|
|
391
|
-
cropX = 0;
|
|
392
|
-
cropY = Math.round((sh - cropH) / 2);
|
|
393
|
-
}
|
|
394
|
-
image.crop(cropX, cropY, cropW, cropH);
|
|
395
|
-
}
|
|
396
|
-
image.resize(targetWidth, targetHeight);
|
|
397
|
-
}
|
|
398
|
-
image.getBuffer("image/bmp", function (err, value) {
|
|
399
|
-
if (err) {
|
|
400
|
-
reject(err);
|
|
401
|
-
}
|
|
402
|
-
resolve(value);
|
|
403
|
-
});
|
|
404
|
-
return [3 /*break*/, 3];
|
|
405
|
-
case 2:
|
|
406
|
-
_a = _b.sent();
|
|
407
|
-
throw new Error("Cannot procces image file from path: " + filePath);
|
|
408
|
-
case 3: return [2 /*return*/];
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
}); })];
|
|
412
|
-
});
|
|
413
|
-
}); };
|
|
414
|
-
exports.parseImage = function (buffer) { return bmp_js_1.decode(buffer); };
|
|
415
|
-
exports.getImageColor = function (image) {
|
|
416
|
-
var blueSum = 0;
|
|
417
|
-
var greenSum = 0;
|
|
418
|
-
var redSum = 0;
|
|
419
|
-
for (var currY = 0; currY < image.height; currY++) {
|
|
420
|
-
for (var currX = 0; currX < image.width; currX++) {
|
|
421
|
-
var idx = (image.width * currY + currX) << 2;
|
|
422
|
-
blueSum += image.data[idx + 1];
|
|
423
|
-
greenSum += image.data[idx + 2];
|
|
424
|
-
redSum += image.data[idx + 3];
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
var pixelsCount = image.width * image.height;
|
|
428
|
-
return {
|
|
429
|
-
red: ~~(redSum / pixelsCount),
|
|
430
|
-
green: ~~(greenSum / pixelsCount),
|
|
431
|
-
blue: ~~(blueSum / pixelsCount)
|
|
432
|
-
};
|
|
433
|
-
};
|
|
434
|
-
exports.getVideoFrameColor = function (bgrBuffer, width, height) {
|
|
435
|
-
var blueSum = 0;
|
|
436
|
-
var greenSum = 0;
|
|
437
|
-
var redSum = 0;
|
|
438
|
-
var pixelsCount = width * height;
|
|
439
|
-
for (var i = 0; i < bgrBuffer.length; i += 3) {
|
|
440
|
-
blueSum += bgrBuffer[i];
|
|
441
|
-
greenSum += bgrBuffer[i + 1];
|
|
442
|
-
redSum += bgrBuffer[i + 2];
|
|
443
|
-
}
|
|
444
|
-
return {
|
|
445
|
-
red: ~~(redSum / pixelsCount),
|
|
446
|
-
green: ~~(greenSum / pixelsCount),
|
|
447
|
-
blue: ~~(blueSum / pixelsCount),
|
|
448
|
-
};
|
|
449
|
-
};
|
|
450
|
-
exports.invertColor = function (color) {
|
|
451
|
-
return ({ red: 255 - color.red, green: 255 - color.green, blue: 255 - color.blue });
|
|
452
|
-
};
|
|
453
|
-
/** Pixel shift strength for the cut frame and the frames after it. */
|
|
454
|
-
exports.CUT_SHAKE_AMPLITUDE_BY_OFFSET = [14, 9, 5, 3, 1];
|
|
455
|
-
exports.CUT_SHAKE_PEAK_AMPLITUDE = 14;
|
|
456
|
-
exports.MIN_CUT_SHAKE_FRAMES = 3;
|
|
457
|
-
exports.SHAKE_DURATION_BEAT_FRACTION = 1 / 3;
|
|
458
|
-
/** Camera shake starts this many seconds after the cut instead of on the cut frame. */
|
|
459
|
-
exports.CAMERA_SHAKE_DELAY_SECONDS = 0.1;
|
|
460
|
-
/**
|
|
461
|
-
* When auto-edit produces no cuts (e.g. a single continuous-shot video with no detected
|
|
462
|
-
* scene changes), camera shake falls back to every Nth beat of the tempo grid instead.
|
|
463
|
-
*/
|
|
464
|
-
exports.CAMERA_SHAKE_EVERY_BEATS = 2;
|
|
465
|
-
exports.buildCutShakeAmplitudes = function (periodFrames) {
|
|
466
|
-
if (!(periodFrames > 0)) {
|
|
467
|
-
return Array.from(exports.CUT_SHAKE_AMPLITUDE_BY_OFFSET);
|
|
468
|
-
}
|
|
469
|
-
var frameCount = Math.max(exports.MIN_CUT_SHAKE_FRAMES, Math.round(periodFrames * exports.SHAKE_DURATION_BEAT_FRACTION));
|
|
470
|
-
if (frameCount === 1) {
|
|
471
|
-
return [exports.CUT_SHAKE_PEAK_AMPLITUDE];
|
|
472
|
-
}
|
|
473
|
-
var decayTo = 1 / exports.CUT_SHAKE_PEAK_AMPLITUDE;
|
|
474
|
-
var amplitudes = [];
|
|
475
|
-
for (var i = 0; i < frameCount; i++) {
|
|
476
|
-
var t = i / (frameCount - 1);
|
|
477
|
-
amplitudes.push(Math.max(1, Math.round(exports.CUT_SHAKE_PEAK_AMPLITUDE * Math.pow(decayTo, t))));
|
|
478
|
-
}
|
|
479
|
-
return amplitudes;
|
|
480
|
-
};
|
|
481
|
-
var clampInt = function (value, min, max) {
|
|
482
|
-
if (value < min) {
|
|
483
|
-
return min;
|
|
484
|
-
}
|
|
485
|
-
if (value > max) {
|
|
486
|
-
return max;
|
|
487
|
-
}
|
|
488
|
-
return value;
|
|
489
|
-
};
|
|
490
|
-
exports.getCutShakeOffset = function (frameIndex, cutFrames, amplitudes, delayFrames) {
|
|
491
|
-
if (amplitudes === void 0) { amplitudes = exports.CUT_SHAKE_AMPLITUDE_BY_OFFSET; }
|
|
492
|
-
if (delayFrames === void 0) { delayFrames = 0; }
|
|
493
|
-
if (amplitudes.length === 0) {
|
|
494
|
-
return { x: 0, y: 0 };
|
|
495
|
-
}
|
|
496
|
-
var delayedFrameIndex = frameIndex - delayFrames;
|
|
497
|
-
var framesAfterCut = -1;
|
|
498
|
-
var originCut = -1;
|
|
499
|
-
for (var offset = 0; offset < amplitudes.length; offset++) {
|
|
500
|
-
var cut = delayedFrameIndex - offset;
|
|
501
|
-
if (cutFrames.has(cut) && (framesAfterCut === -1 || offset < framesAfterCut)) {
|
|
502
|
-
framesAfterCut = offset;
|
|
503
|
-
originCut = cut;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
if (framesAfterCut === -1) {
|
|
507
|
-
return { x: 0, y: 0 };
|
|
508
|
-
}
|
|
509
|
-
var amplitude = amplitudes[framesAfterCut];
|
|
510
|
-
var angle = originCut * 2.399 + framesAfterCut * 2.1;
|
|
511
|
-
return {
|
|
512
|
-
x: Math.round(Math.cos(angle) * amplitude),
|
|
513
|
-
y: Math.round(Math.sin(angle) * amplitude),
|
|
514
|
-
};
|
|
515
|
-
};
|
|
516
|
-
exports.applyCameraShake = function (imageDstBuffer, width, height, offsetX, offsetY) {
|
|
517
|
-
var ox = Math.round(offsetX);
|
|
518
|
-
var oy = Math.round(offsetY);
|
|
519
|
-
if (ox === 0 && oy === 0) {
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
var shiftPos = imageDstBuffer.shiftPos, rowBytes = imageDstBuffer.rowBytes, data = imageDstBuffer.data;
|
|
523
|
-
var pixelBytes = height * rowBytes;
|
|
524
|
-
var sourcePixels = Buffer.allocUnsafe(pixelBytes);
|
|
525
|
-
data.copy(sourcePixels, 0, shiftPos, shiftPos + pixelBytes);
|
|
526
|
-
for (var y = 0; y < height; y++) {
|
|
527
|
-
var srcY = clampInt(y - oy, 0, height - 1);
|
|
528
|
-
var dstRow = shiftPos + y * rowBytes;
|
|
529
|
-
var srcRow = srcY * rowBytes;
|
|
530
|
-
for (var x = 0; x < width; x++) {
|
|
531
|
-
var srcX = clampInt(x - ox, 0, width - 1);
|
|
532
|
-
var dstIndex = dstRow + x * 3;
|
|
533
|
-
var srcIndex = srcRow + srcX * 3;
|
|
534
|
-
data[dstIndex] = sourcePixels[srcIndex];
|
|
535
|
-
data[dstIndex + 1] = sourcePixels[srcIndex + 1];
|
|
536
|
-
data[dstIndex + 2] = sourcePixels[srcIndex + 2];
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
24
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
+
function step(op) {
|
|
27
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
+
while (_) try {
|
|
29
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
+
switch (op[0]) {
|
|
32
|
+
case 0: case 1: t = op; break;
|
|
33
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
+
default:
|
|
37
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
+
if (t[2]) _.ops.pop();
|
|
42
|
+
_.trys.pop(); continue;
|
|
43
|
+
}
|
|
44
|
+
op = body.call(thisArg, _);
|
|
45
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
var bmp_js_1 = require("bmp-js");
|
|
54
|
+
var jimp_1 = __importDefault(require("jimp"));
|
|
55
|
+
exports.mixValues = function (value1, value1Opacity, value2) {
|
|
56
|
+
return Math.round(value2 * (1 - value1Opacity) + value1 * value1Opacity);
|
|
57
|
+
};
|
|
58
|
+
exports.mixColors = function (_a) {
|
|
59
|
+
var color = _a.color, colorOpacity = _a.colorOpacity, backgroundColor = _a.backgroundColor;
|
|
60
|
+
return {
|
|
61
|
+
red: exports.mixValues(color.red, colorOpacity, backgroundColor.red),
|
|
62
|
+
green: exports.mixValues(color.green, colorOpacity, backgroundColor.green),
|
|
63
|
+
blue: exports.mixValues(color.blue, colorOpacity, backgroundColor.blue),
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
var getRectPixelColor = function (params) {
|
|
67
|
+
var imageDstBuffer = params.imageDstBuffer, pixelIndex = params.pixelIndex, color = params.color, opacity = params.opacity;
|
|
68
|
+
if (opacity === 1) {
|
|
69
|
+
return color;
|
|
70
|
+
}
|
|
71
|
+
var backgroundPixelColor = {
|
|
72
|
+
blue: imageDstBuffer.data[pixelIndex],
|
|
73
|
+
green: imageDstBuffer.data[pixelIndex + 1],
|
|
74
|
+
red: imageDstBuffer.data[pixelIndex + 2],
|
|
75
|
+
};
|
|
76
|
+
var resultColor = exports.mixColors({
|
|
77
|
+
color: color,
|
|
78
|
+
colorOpacity: opacity,
|
|
79
|
+
backgroundColor: backgroundPixelColor,
|
|
80
|
+
});
|
|
81
|
+
return resultColor;
|
|
82
|
+
};
|
|
83
|
+
exports.drawRect = function (_a) {
|
|
84
|
+
var imageDstBuffer = _a.imageDstBuffer, position = _a.position, size = _a.size, color = _a.color, opacity = _a.opacity;
|
|
85
|
+
for (var currY = position.y; currY < position.y + size.height; currY++) {
|
|
86
|
+
for (var currX = position.x; currX < position.x + size.width; currX++) {
|
|
87
|
+
var pixelIndex = imageDstBuffer.shiftPos + currY * imageDstBuffer.rowBytes + currX * 3;
|
|
88
|
+
var pixelColor = getRectPixelColor({
|
|
89
|
+
imageDstBuffer: imageDstBuffer,
|
|
90
|
+
pixelIndex: pixelIndex,
|
|
91
|
+
color: color,
|
|
92
|
+
opacity: opacity,
|
|
93
|
+
});
|
|
94
|
+
imageDstBuffer.data[pixelIndex] = pixelColor.blue;
|
|
95
|
+
imageDstBuffer.data[pixelIndex + 1] = pixelColor.green;
|
|
96
|
+
imageDstBuffer.data[pixelIndex + 2] = pixelColor.red;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var buildQuadEdgeTable = function (points) {
|
|
101
|
+
var edges = [];
|
|
102
|
+
for (var i = 0; i < points.length; i++) {
|
|
103
|
+
var p1 = points[i];
|
|
104
|
+
var p2 = points[(i + 1) % points.length];
|
|
105
|
+
if (p1.y === p2.y)
|
|
106
|
+
continue; // Skip horizontal edges
|
|
107
|
+
var yMin = Math.min(p1.y, p2.y);
|
|
108
|
+
var yMax = Math.max(p1.y, p2.y);
|
|
109
|
+
var x = p1.y < p2.y ? p1.x : p2.x;
|
|
110
|
+
var dx = (p2.x - p1.x) / (p2.y - p1.y);
|
|
111
|
+
edges.push({ yMin: yMin, yMax: yMax, x: x, dx: dx });
|
|
112
|
+
}
|
|
113
|
+
return edges.sort(function (a, b) { return a.yMin - b.yMin; });
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Fills a quadrilateral (4-sided polygon) using scan-line algorithm
|
|
117
|
+
* Only the outer edges are anti-aliased
|
|
118
|
+
*/
|
|
119
|
+
exports.fillRect = function (_a) {
|
|
120
|
+
var imageDstBuffer = _a.imageDstBuffer, points = _a.points, color = _a.color, opacity = _a.opacity;
|
|
121
|
+
// Build edge table from all 4 vertices
|
|
122
|
+
var edgeTable = buildQuadEdgeTable(points);
|
|
123
|
+
if (edgeTable.length === 0)
|
|
124
|
+
return;
|
|
125
|
+
// Find the bounding box
|
|
126
|
+
var yMin = Math.min.apply(Math, points.map(function (p) { return p.y; }));
|
|
127
|
+
var yMax = Math.max.apply(Math, points.map(function (p) { return p.y; }));
|
|
128
|
+
// Active Edge Table for scan line algorithm
|
|
129
|
+
var activeEdges = [];
|
|
130
|
+
// Scan line algorithm: process each horizontal line
|
|
131
|
+
for (var y = Math.floor(yMin); y <= Math.ceil(yMax); y++) {
|
|
132
|
+
// Add edges that start at this scanline to AET
|
|
133
|
+
for (var _i = 0, edgeTable_1 = edgeTable; _i < edgeTable_1.length; _i++) {
|
|
134
|
+
var edge = edgeTable_1[_i];
|
|
135
|
+
if (Math.floor(edge.yMin) === y) {
|
|
136
|
+
activeEdges.push(__assign({}, edge));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Remove edges that end at this scanline from AET
|
|
140
|
+
for (var i = activeEdges.length - 1; i >= 0; i--) {
|
|
141
|
+
if (Math.ceil(activeEdges[i].yMax) <= y) {
|
|
142
|
+
activeEdges.splice(i, 1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Sort active edges by x coordinate
|
|
146
|
+
activeEdges.sort(function (a, b) { return a.x - b.x; });
|
|
147
|
+
// Fill pixels between pairs of intersections
|
|
148
|
+
for (var i = 0; i < activeEdges.length; i += 2) {
|
|
149
|
+
if (i + 1 < activeEdges.length) {
|
|
150
|
+
var xStart = activeEdges[i].x;
|
|
151
|
+
var xEnd = activeEdges[i + 1].x;
|
|
152
|
+
// Anti-aliased rendering with sub-pixel precision
|
|
153
|
+
var xStartFloor = Math.floor(xStart);
|
|
154
|
+
var xEndCeil = Math.ceil(xEnd);
|
|
155
|
+
for (var x = xStartFloor; x < xEndCeil; x++) {
|
|
156
|
+
var pixelIndex = imageDstBuffer.shiftPos + y * imageDstBuffer.rowBytes + x * 3;
|
|
157
|
+
// Calculate coverage for anti-aliasing
|
|
158
|
+
var coverage = 1.0;
|
|
159
|
+
if (x === xStartFloor) {
|
|
160
|
+
// Left edge pixel - calculate fractional coverage
|
|
161
|
+
coverage = Math.min(1.0, 1.0 - (xStart - xStartFloor));
|
|
162
|
+
}
|
|
163
|
+
else if (x === xEndCeil - 1) {
|
|
164
|
+
// Right edge pixel - calculate fractional coverage
|
|
165
|
+
coverage = Math.min(1.0, xEnd - Math.floor(xEnd));
|
|
166
|
+
}
|
|
167
|
+
// Apply coverage to opacity for smooth edges
|
|
168
|
+
var effectiveOpacity = opacity * coverage;
|
|
169
|
+
var pixelColor = getRectPixelColor({
|
|
170
|
+
imageDstBuffer: imageDstBuffer,
|
|
171
|
+
pixelIndex: pixelIndex,
|
|
172
|
+
color: color,
|
|
173
|
+
opacity: effectiveOpacity,
|
|
174
|
+
});
|
|
175
|
+
imageDstBuffer.data[pixelIndex] = pixelColor.blue;
|
|
176
|
+
imageDstBuffer.data[pixelIndex + 1] = pixelColor.green;
|
|
177
|
+
imageDstBuffer.data[pixelIndex + 2] = pixelColor.red;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Update x coordinates for next scanline (x' = x + dx)
|
|
182
|
+
for (var _b = 0, activeEdges_1 = activeEdges; _b < activeEdges_1.length; _b++) {
|
|
183
|
+
var edge = activeEdges_1[_b];
|
|
184
|
+
edge.x += edge.dx;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
var getRectY = function (rotation, top, height, rectHeight) {
|
|
189
|
+
switch (rotation) {
|
|
190
|
+
case 'up':
|
|
191
|
+
return top + height - rectHeight;
|
|
192
|
+
case 'mirror':
|
|
193
|
+
return top + Math.trunc((height - rectHeight) / 2);
|
|
194
|
+
default:
|
|
195
|
+
return top;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
var minRectHeight = 2;
|
|
199
|
+
var getRectHeight = function (height, spectrumValue) {
|
|
200
|
+
var rectHeight = Math.trunc(height * spectrumValue);
|
|
201
|
+
if (rectHeight < minRectHeight) {
|
|
202
|
+
return minRectHeight;
|
|
203
|
+
}
|
|
204
|
+
return rectHeight;
|
|
205
|
+
};
|
|
206
|
+
var drawSpectrum = function (_a) {
|
|
207
|
+
var imageDstBuffer = _a.imageDstBuffer, spectrum = _a.spectrum, size = _a.size, position = _a.position, rotation = _a.rotation, margin = _a.margin, color = _a.color, opacity = _a.opacity;
|
|
208
|
+
var busWidth = Math.trunc(size.width / spectrum.length);
|
|
209
|
+
var left = Math.trunc(position.x);
|
|
210
|
+
var top = Math.trunc(position.y);
|
|
211
|
+
var height = Math.trunc(size.height);
|
|
212
|
+
for (var spectrumX = 0; spectrumX < spectrum.length; spectrumX++) {
|
|
213
|
+
var spectrumValue = spectrum[spectrumX];
|
|
214
|
+
if ((spectrumValue > 1) || (spectrumValue < 0)) {
|
|
215
|
+
throw new Error('Spectrum values must be in range from 0 to 1');
|
|
216
|
+
}
|
|
217
|
+
var rectX = left + busWidth * spectrumX;
|
|
218
|
+
var rectHeight = getRectHeight(height, spectrumValue);
|
|
219
|
+
var rectY = getRectY(rotation, top, height, rectHeight);
|
|
220
|
+
exports.drawRect({
|
|
221
|
+
imageDstBuffer: imageDstBuffer,
|
|
222
|
+
position: { x: rectX + margin, y: rectY },
|
|
223
|
+
size: { width: busWidth - margin / 2, height: rectHeight },
|
|
224
|
+
color: color,
|
|
225
|
+
opacity: opacity,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
var calcCirclePoint = function (angleRadians, radius) {
|
|
230
|
+
var circleX = radius * Math.cos(angleRadians);
|
|
231
|
+
var circleY = radius * Math.sin(angleRadians);
|
|
232
|
+
return { x: circleX, y: circleY };
|
|
233
|
+
};
|
|
234
|
+
var lerp = function (start, end, t) {
|
|
235
|
+
return start + (end - start) * t;
|
|
236
|
+
};
|
|
237
|
+
var drawPolarSpectrum = function (_a) {
|
|
238
|
+
var imageDstBuffer = _a.imageDstBuffer, spectrum = _a.spectrum, centerX = _a.centerX, centerY = _a.centerY, innerRadius = _a.innerRadius, maxBarLength = _a.maxBarLength, barWidth = _a.barWidth, color = _a.color, opacity = _a.opacity;
|
|
239
|
+
var angleStep = (2 * Math.PI) / spectrum.length;
|
|
240
|
+
var innerLength = innerRadius * 2 * Math.PI;
|
|
241
|
+
var radiansPerPixel = 2 * Math.PI / innerLength;
|
|
242
|
+
var barWidthInRadians = radiansPerPixel * barWidth;
|
|
243
|
+
var spectrumAverage = spectrum.reduce(function (a, b) { return a + b; }, 0) / spectrum.length;
|
|
244
|
+
var scaleFactor = lerp(0.75, 1, spectrumAverage);
|
|
245
|
+
var scaledMaxOuterRadius = (innerRadius + maxBarLength) * scaleFactor;
|
|
246
|
+
var scaledInnerRadius = innerRadius * scaleFactor;
|
|
247
|
+
for (var i = 0; i < spectrum.length; i++) {
|
|
248
|
+
var currentSpectrumValue = spectrum[i] || 0;
|
|
249
|
+
var busRadius = lerp(scaledInnerRadius, scaledMaxOuterRadius, currentSpectrumValue);
|
|
250
|
+
var angle = i * angleStep;
|
|
251
|
+
var angleRadiansStart = angle;
|
|
252
|
+
var angleRadiansEnd = angle + barWidthInRadians;
|
|
253
|
+
var point1 = calcCirclePoint(angleRadiansStart, scaledInnerRadius);
|
|
254
|
+
var point2 = calcCirclePoint(angleRadiansStart, busRadius);
|
|
255
|
+
var point3 = calcCirclePoint(angleRadiansEnd, scaledInnerRadius);
|
|
256
|
+
var point4 = calcCirclePoint(angleRadiansEnd, busRadius);
|
|
257
|
+
exports.fillRect({
|
|
258
|
+
imageDstBuffer: imageDstBuffer,
|
|
259
|
+
points: [
|
|
260
|
+
{ x: centerX + Math.round(point1.x), y: centerY + Math.round(point1.y) },
|
|
261
|
+
{ x: centerX + Math.round(point2.x), y: centerY + Math.round(point2.y) },
|
|
262
|
+
{ x: centerX + Math.round(point4.x), y: centerY + Math.round(point4.y) },
|
|
263
|
+
{ x: centerX + Math.round(point3.x), y: centerY + Math.round(point3.y) },
|
|
264
|
+
],
|
|
265
|
+
color: color,
|
|
266
|
+
opacity: opacity,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
exports.createSpectrumVisualizerFrameGenerator = function () {
|
|
271
|
+
var prevSpectrum = null;
|
|
272
|
+
return function (_a) {
|
|
273
|
+
var backgroundImageBuffer = _a.backgroundImageBuffer, spectrum = _a.spectrum, size = _a.size, position = _a.position, rotation = _a.rotation, margin = _a.margin, color = _a.color, opacity = _a.opacity, spectrumEffect = _a.spectrumEffect;
|
|
274
|
+
var imageDstBuffer = Object.assign({}, backgroundImageBuffer);
|
|
275
|
+
imageDstBuffer.data = Buffer.from(backgroundImageBuffer.data);
|
|
276
|
+
var rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
|
|
277
|
+
if (spectrumEffect === 'volume') {
|
|
278
|
+
drawSpectrum({
|
|
279
|
+
imageDstBuffer: imageDstBuffer,
|
|
280
|
+
spectrum: spectrum,
|
|
281
|
+
size: size,
|
|
282
|
+
position: { x: position.x + 2, y: position.y + 2 },
|
|
283
|
+
rotation: rotation,
|
|
284
|
+
margin: margin,
|
|
285
|
+
color: rgbSpectrumColor,
|
|
286
|
+
opacity: opacity * 0.5,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (spectrumEffect === 'smooth' && prevSpectrum) {
|
|
290
|
+
drawSpectrum({
|
|
291
|
+
imageDstBuffer: imageDstBuffer,
|
|
292
|
+
spectrum: prevSpectrum,
|
|
293
|
+
size: size,
|
|
294
|
+
position: position,
|
|
295
|
+
rotation: rotation,
|
|
296
|
+
margin: margin,
|
|
297
|
+
color: rgbSpectrumColor,
|
|
298
|
+
opacity: opacity * 0.3,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
drawSpectrum({
|
|
302
|
+
imageDstBuffer: imageDstBuffer,
|
|
303
|
+
spectrum: spectrum,
|
|
304
|
+
size: size,
|
|
305
|
+
position: position,
|
|
306
|
+
rotation: rotation,
|
|
307
|
+
margin: margin,
|
|
308
|
+
color: rgbSpectrumColor,
|
|
309
|
+
opacity: opacity,
|
|
310
|
+
});
|
|
311
|
+
prevSpectrum = spectrum;
|
|
312
|
+
return imageDstBuffer;
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
exports.createPolarVisualizerFrameGenerator = function () {
|
|
316
|
+
var prevSpectrum = null;
|
|
317
|
+
return function (_a) {
|
|
318
|
+
var backgroundImageBuffer = _a.backgroundImageBuffer, spectrum = _a.spectrum, centerX = _a.centerX, centerY = _a.centerY, innerRadius = _a.innerRadius, maxBarLength = _a.maxBarLength, barWidth = _a.barWidth, color = _a.color, opacity = _a.opacity, spectrumEffect = _a.spectrumEffect;
|
|
319
|
+
var imageDstBuffer = Object.assign({}, backgroundImageBuffer);
|
|
320
|
+
imageDstBuffer.data = Buffer.from(backgroundImageBuffer.data);
|
|
321
|
+
var rgbSpectrumColor = (typeof color === 'string') ? hexToRgb(color) : color;
|
|
322
|
+
if (spectrumEffect === 'volume') {
|
|
323
|
+
drawPolarSpectrum({
|
|
324
|
+
imageDstBuffer: imageDstBuffer,
|
|
325
|
+
spectrum: spectrum,
|
|
326
|
+
centerX: centerX + 2,
|
|
327
|
+
centerY: centerY + 2,
|
|
328
|
+
innerRadius: innerRadius,
|
|
329
|
+
maxBarLength: maxBarLength,
|
|
330
|
+
barWidth: barWidth,
|
|
331
|
+
color: rgbSpectrumColor,
|
|
332
|
+
opacity: opacity * 0.5,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (spectrumEffect === 'smooth' && prevSpectrum) {
|
|
336
|
+
drawPolarSpectrum({
|
|
337
|
+
imageDstBuffer: imageDstBuffer,
|
|
338
|
+
spectrum: prevSpectrum,
|
|
339
|
+
centerX: centerX,
|
|
340
|
+
centerY: centerY,
|
|
341
|
+
innerRadius: innerRadius,
|
|
342
|
+
maxBarLength: maxBarLength,
|
|
343
|
+
barWidth: barWidth,
|
|
344
|
+
color: rgbSpectrumColor,
|
|
345
|
+
opacity: opacity * 0.3,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
drawPolarSpectrum({
|
|
349
|
+
imageDstBuffer: imageDstBuffer,
|
|
350
|
+
spectrum: spectrum,
|
|
351
|
+
centerX: centerX,
|
|
352
|
+
centerY: centerY,
|
|
353
|
+
innerRadius: innerRadius,
|
|
354
|
+
maxBarLength: maxBarLength,
|
|
355
|
+
barWidth: barWidth,
|
|
356
|
+
color: rgbSpectrumColor,
|
|
357
|
+
opacity: opacity,
|
|
358
|
+
});
|
|
359
|
+
prevSpectrum = spectrum;
|
|
360
|
+
return imageDstBuffer;
|
|
361
|
+
};
|
|
362
|
+
};
|
|
363
|
+
exports.convertToBmp = function (filePath, targetWidth, targetHeight) { return __awaiter(void 0, void 0, void 0, function () {
|
|
364
|
+
return __generator(this, function (_a) {
|
|
365
|
+
return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(void 0, void 0, void 0, function () {
|
|
366
|
+
var image, sw, sh, isOrientationMismatch, targetAspect, cropW, cropH, cropX, cropY, _a;
|
|
367
|
+
return __generator(this, function (_b) {
|
|
368
|
+
switch (_b.label) {
|
|
369
|
+
case 0:
|
|
370
|
+
_b.trys.push([0, 2, , 3]);
|
|
371
|
+
return [4 /*yield*/, jimp_1.default.read(filePath)];
|
|
372
|
+
case 1:
|
|
373
|
+
image = _b.sent();
|
|
374
|
+
if (targetWidth && targetHeight) {
|
|
375
|
+
sw = image.getWidth();
|
|
376
|
+
sh = image.getHeight();
|
|
377
|
+
isOrientationMismatch = (sw > sh && targetWidth < targetHeight) ||
|
|
378
|
+
(sw < sh && targetWidth > targetHeight);
|
|
379
|
+
if (isOrientationMismatch) {
|
|
380
|
+
targetAspect = targetWidth / targetHeight;
|
|
381
|
+
cropW = void 0, cropH = void 0, cropX = void 0, cropY = void 0;
|
|
382
|
+
if (sw / sh > targetAspect) {
|
|
383
|
+
cropW = Math.round(sh * targetAspect);
|
|
384
|
+
cropH = sh;
|
|
385
|
+
cropX = Math.round((sw - cropW) / 2);
|
|
386
|
+
cropY = 0;
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
cropW = sw;
|
|
390
|
+
cropH = Math.round(sw / targetAspect);
|
|
391
|
+
cropX = 0;
|
|
392
|
+
cropY = Math.round((sh - cropH) / 2);
|
|
393
|
+
}
|
|
394
|
+
image.crop(cropX, cropY, cropW, cropH);
|
|
395
|
+
}
|
|
396
|
+
image.resize(targetWidth, targetHeight);
|
|
397
|
+
}
|
|
398
|
+
image.getBuffer("image/bmp", function (err, value) {
|
|
399
|
+
if (err) {
|
|
400
|
+
reject(err);
|
|
401
|
+
}
|
|
402
|
+
resolve(value);
|
|
403
|
+
});
|
|
404
|
+
return [3 /*break*/, 3];
|
|
405
|
+
case 2:
|
|
406
|
+
_a = _b.sent();
|
|
407
|
+
throw new Error("Cannot procces image file from path: " + filePath);
|
|
408
|
+
case 3: return [2 /*return*/];
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}); })];
|
|
412
|
+
});
|
|
413
|
+
}); };
|
|
414
|
+
exports.parseImage = function (buffer) { return bmp_js_1.decode(buffer); };
|
|
415
|
+
exports.getImageColor = function (image) {
|
|
416
|
+
var blueSum = 0;
|
|
417
|
+
var greenSum = 0;
|
|
418
|
+
var redSum = 0;
|
|
419
|
+
for (var currY = 0; currY < image.height; currY++) {
|
|
420
|
+
for (var currX = 0; currX < image.width; currX++) {
|
|
421
|
+
var idx = (image.width * currY + currX) << 2;
|
|
422
|
+
blueSum += image.data[idx + 1];
|
|
423
|
+
greenSum += image.data[idx + 2];
|
|
424
|
+
redSum += image.data[idx + 3];
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
var pixelsCount = image.width * image.height;
|
|
428
|
+
return {
|
|
429
|
+
red: ~~(redSum / pixelsCount),
|
|
430
|
+
green: ~~(greenSum / pixelsCount),
|
|
431
|
+
blue: ~~(blueSum / pixelsCount)
|
|
432
|
+
};
|
|
433
|
+
};
|
|
434
|
+
exports.getVideoFrameColor = function (bgrBuffer, width, height) {
|
|
435
|
+
var blueSum = 0;
|
|
436
|
+
var greenSum = 0;
|
|
437
|
+
var redSum = 0;
|
|
438
|
+
var pixelsCount = width * height;
|
|
439
|
+
for (var i = 0; i < bgrBuffer.length; i += 3) {
|
|
440
|
+
blueSum += bgrBuffer[i];
|
|
441
|
+
greenSum += bgrBuffer[i + 1];
|
|
442
|
+
redSum += bgrBuffer[i + 2];
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
red: ~~(redSum / pixelsCount),
|
|
446
|
+
green: ~~(greenSum / pixelsCount),
|
|
447
|
+
blue: ~~(blueSum / pixelsCount),
|
|
448
|
+
};
|
|
449
|
+
};
|
|
450
|
+
exports.invertColor = function (color) {
|
|
451
|
+
return ({ red: 255 - color.red, green: 255 - color.green, blue: 255 - color.blue });
|
|
452
|
+
};
|
|
453
|
+
/** Pixel shift strength for the cut frame and the frames after it. */
|
|
454
|
+
exports.CUT_SHAKE_AMPLITUDE_BY_OFFSET = [14, 9, 5, 3, 1];
|
|
455
|
+
exports.CUT_SHAKE_PEAK_AMPLITUDE = 14;
|
|
456
|
+
exports.MIN_CUT_SHAKE_FRAMES = 3;
|
|
457
|
+
exports.SHAKE_DURATION_BEAT_FRACTION = 1 / 3;
|
|
458
|
+
/** Camera shake starts this many seconds after the cut instead of on the cut frame. */
|
|
459
|
+
exports.CAMERA_SHAKE_DELAY_SECONDS = 0.1;
|
|
460
|
+
/**
|
|
461
|
+
* When auto-edit produces no cuts (e.g. a single continuous-shot video with no detected
|
|
462
|
+
* scene changes), camera shake falls back to every Nth beat of the tempo grid instead.
|
|
463
|
+
*/
|
|
464
|
+
exports.CAMERA_SHAKE_EVERY_BEATS = 2;
|
|
465
|
+
exports.buildCutShakeAmplitudes = function (periodFrames) {
|
|
466
|
+
if (!(periodFrames > 0)) {
|
|
467
|
+
return Array.from(exports.CUT_SHAKE_AMPLITUDE_BY_OFFSET);
|
|
468
|
+
}
|
|
469
|
+
var frameCount = Math.max(exports.MIN_CUT_SHAKE_FRAMES, Math.round(periodFrames * exports.SHAKE_DURATION_BEAT_FRACTION));
|
|
470
|
+
if (frameCount === 1) {
|
|
471
|
+
return [exports.CUT_SHAKE_PEAK_AMPLITUDE];
|
|
472
|
+
}
|
|
473
|
+
var decayTo = 1 / exports.CUT_SHAKE_PEAK_AMPLITUDE;
|
|
474
|
+
var amplitudes = [];
|
|
475
|
+
for (var i = 0; i < frameCount; i++) {
|
|
476
|
+
var t = i / (frameCount - 1);
|
|
477
|
+
amplitudes.push(Math.max(1, Math.round(exports.CUT_SHAKE_PEAK_AMPLITUDE * Math.pow(decayTo, t))));
|
|
478
|
+
}
|
|
479
|
+
return amplitudes;
|
|
480
|
+
};
|
|
481
|
+
var clampInt = function (value, min, max) {
|
|
482
|
+
if (value < min) {
|
|
483
|
+
return min;
|
|
484
|
+
}
|
|
485
|
+
if (value > max) {
|
|
486
|
+
return max;
|
|
487
|
+
}
|
|
488
|
+
return value;
|
|
489
|
+
};
|
|
490
|
+
exports.getCutShakeOffset = function (frameIndex, cutFrames, amplitudes, delayFrames) {
|
|
491
|
+
if (amplitudes === void 0) { amplitudes = exports.CUT_SHAKE_AMPLITUDE_BY_OFFSET; }
|
|
492
|
+
if (delayFrames === void 0) { delayFrames = 0; }
|
|
493
|
+
if (amplitudes.length === 0) {
|
|
494
|
+
return { x: 0, y: 0 };
|
|
495
|
+
}
|
|
496
|
+
var delayedFrameIndex = frameIndex - delayFrames;
|
|
497
|
+
var framesAfterCut = -1;
|
|
498
|
+
var originCut = -1;
|
|
499
|
+
for (var offset = 0; offset < amplitudes.length; offset++) {
|
|
500
|
+
var cut = delayedFrameIndex - offset;
|
|
501
|
+
if (cutFrames.has(cut) && (framesAfterCut === -1 || offset < framesAfterCut)) {
|
|
502
|
+
framesAfterCut = offset;
|
|
503
|
+
originCut = cut;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (framesAfterCut === -1) {
|
|
507
|
+
return { x: 0, y: 0 };
|
|
508
|
+
}
|
|
509
|
+
var amplitude = amplitudes[framesAfterCut];
|
|
510
|
+
var angle = originCut * 2.399 + framesAfterCut * 2.1;
|
|
511
|
+
return {
|
|
512
|
+
x: Math.round(Math.cos(angle) * amplitude),
|
|
513
|
+
y: Math.round(Math.sin(angle) * amplitude),
|
|
514
|
+
};
|
|
515
|
+
};
|
|
516
|
+
exports.applyCameraShake = function (imageDstBuffer, width, height, offsetX, offsetY) {
|
|
517
|
+
var ox = Math.round(offsetX);
|
|
518
|
+
var oy = Math.round(offsetY);
|
|
519
|
+
if (ox === 0 && oy === 0) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
var shiftPos = imageDstBuffer.shiftPos, rowBytes = imageDstBuffer.rowBytes, data = imageDstBuffer.data;
|
|
523
|
+
var pixelBytes = height * rowBytes;
|
|
524
|
+
var sourcePixels = Buffer.allocUnsafe(pixelBytes);
|
|
525
|
+
data.copy(sourcePixels, 0, shiftPos, shiftPos + pixelBytes);
|
|
526
|
+
for (var y = 0; y < height; y++) {
|
|
527
|
+
var srcY = clampInt(y - oy, 0, height - 1);
|
|
528
|
+
var dstRow = shiftPos + y * rowBytes;
|
|
529
|
+
var srcRow = srcY * rowBytes;
|
|
530
|
+
for (var x = 0; x < width; x++) {
|
|
531
|
+
var srcX = clampInt(x - ox, 0, width - 1);
|
|
532
|
+
var dstIndex = dstRow + x * 3;
|
|
533
|
+
var srcIndex = srcRow + srcX * 3;
|
|
534
|
+
data[dstIndex] = sourcePixels[srcIndex];
|
|
535
|
+
data[dstIndex + 1] = sourcePixels[srcIndex + 1];
|
|
536
|
+
data[dstIndex + 2] = sourcePixels[srcIndex + 2];
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
/** Scale of the cut frame; the zoom then eases out back to 1 (no zoom). */
|
|
541
|
+
exports.CUT_ZOOM_PEAK_SCALE = 1.15;
|
|
542
|
+
exports.MIN_CUT_ZOOM_FRAMES = 3;
|
|
543
|
+
exports.ZOOM_DURATION_BEAT_FRACTION = 1 / 3;
|
|
544
|
+
/** Frame count used when the tempo is unknown. */
|
|
545
|
+
exports.DEFAULT_CUT_ZOOM_FRAMES = 6;
|
|
546
|
+
exports.buildCutZoomScales = function (periodFrames) {
|
|
547
|
+
var frameCount = periodFrames > 0
|
|
548
|
+
? Math.max(exports.MIN_CUT_ZOOM_FRAMES, Math.round(periodFrames * exports.ZOOM_DURATION_BEAT_FRACTION))
|
|
549
|
+
: exports.DEFAULT_CUT_ZOOM_FRAMES;
|
|
550
|
+
var scales = [];
|
|
551
|
+
for (var i = 0; i < frameCount; i++) {
|
|
552
|
+
var remaining = 1 - i / frameCount;
|
|
553
|
+
scales.push(1 + (exports.CUT_ZOOM_PEAK_SCALE - 1) * remaining * remaining);
|
|
554
|
+
}
|
|
555
|
+
return scales;
|
|
556
|
+
};
|
|
557
|
+
exports.getCutZoomScale = function (frameIndex, cutFrames, scales) {
|
|
558
|
+
for (var offset = 0; offset < scales.length; offset++) {
|
|
559
|
+
if (cutFrames.has(frameIndex - offset)) {
|
|
560
|
+
return scales[offset];
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return 1;
|
|
564
|
+
};
|
|
565
|
+
/** Scales the frame around its center by `scale` (> 1 zooms in), with bilinear sampling. */
|
|
566
|
+
exports.applyCutZoom = function (imageDstBuffer, width, height, scale) {
|
|
567
|
+
if (!(scale > 1)) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
var shiftPos = imageDstBuffer.shiftPos, rowBytes = imageDstBuffer.rowBytes, data = imageDstBuffer.data;
|
|
571
|
+
var pixelBytes = height * rowBytes;
|
|
572
|
+
var sourcePixels = Buffer.allocUnsafe(pixelBytes);
|
|
573
|
+
data.copy(sourcePixels, 0, shiftPos, shiftPos + pixelBytes);
|
|
574
|
+
var centerX = (width - 1) / 2;
|
|
575
|
+
var centerY = (height - 1) / 2;
|
|
576
|
+
var inverseScale = 1 / scale;
|
|
577
|
+
var srcX0 = new Int32Array(width);
|
|
578
|
+
var srcX1 = new Int32Array(width);
|
|
579
|
+
var fracX = new Float32Array(width);
|
|
580
|
+
for (var x = 0; x < width; x++) {
|
|
581
|
+
var srcX = centerX + (x - centerX) * inverseScale;
|
|
582
|
+
var x0 = Math.floor(srcX);
|
|
583
|
+
srcX0[x] = clampInt(x0, 0, width - 1) * 3;
|
|
584
|
+
srcX1[x] = clampInt(x0 + 1, 0, width - 1) * 3;
|
|
585
|
+
fracX[x] = srcX - x0;
|
|
586
|
+
}
|
|
587
|
+
for (var y = 0; y < height; y++) {
|
|
588
|
+
var srcY = centerY + (y - centerY) * inverseScale;
|
|
589
|
+
var y0 = Math.floor(srcY);
|
|
590
|
+
var fy = srcY - y0;
|
|
591
|
+
var row0 = clampInt(y0, 0, height - 1) * rowBytes;
|
|
592
|
+
var row1 = clampInt(y0 + 1, 0, height - 1) * rowBytes;
|
|
593
|
+
var dstRow = shiftPos + y * rowBytes;
|
|
594
|
+
for (var x = 0; x < width; x++) {
|
|
595
|
+
var fx = fracX[x];
|
|
596
|
+
var i00 = row0 + srcX0[x];
|
|
597
|
+
var i01 = row0 + srcX1[x];
|
|
598
|
+
var i10 = row1 + srcX0[x];
|
|
599
|
+
var i11 = row1 + srcX1[x];
|
|
600
|
+
var dstIndex = dstRow + x * 3;
|
|
601
|
+
for (var c = 0; c < 3; c++) {
|
|
602
|
+
var top_1 = sourcePixels[i00 + c] + (sourcePixels[i01 + c] - sourcePixels[i00 + c]) * fx;
|
|
603
|
+
var bottom = sourcePixels[i10 + c] + (sourcePixels[i11 + c] - sourcePixels[i10 + c]) * fx;
|
|
604
|
+
data[dstIndex + c] = Math.round(top_1 + (bottom - top_1) * fy);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
var hexToRgb = function (hex) {
|
|
610
|
+
var red = parseInt(hex.substring(1, 3), 16);
|
|
611
|
+
var green = parseInt(hex.substring(3, 5), 16);
|
|
612
|
+
var blue = parseInt(hex.substring(5, 7), 16);
|
|
613
|
+
return { red: red, green: green, blue: blue };
|
|
614
|
+
};
|
|
546
615
|
//# sourceMappingURL=image.js.map
|