chart-preview 1.3.0 → 1.3.1
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/.github/workflows/release.yml +7 -3
- package/CHANGELOG.md +6 -0
- package/dist/index.js +58 -13
- package/dist/index.mjs +58 -13
- package/package.json +2 -2
- package/src/ChartPreview.ts +71 -13
|
@@ -22,9 +22,14 @@ jobs:
|
|
|
22
22
|
- name: Setup Node.js
|
|
23
23
|
uses: actions/setup-node@v4
|
|
24
24
|
with:
|
|
25
|
-
node-version:
|
|
25
|
+
node-version: 24
|
|
26
26
|
cache: "npm"
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
# npm trusted publishing needs npm >= 11.5.1; a cached Node 24.x can bundle an older npm.
|
|
29
|
+
- name: Install npm for trusted publishing
|
|
30
|
+
run: |
|
|
31
|
+
npm install -g npm@^11.5.1
|
|
32
|
+
npm --version
|
|
28
33
|
|
|
29
34
|
- name: Install Dependencies
|
|
30
35
|
run: npm ci
|
|
@@ -35,4 +40,3 @@ jobs:
|
|
|
35
40
|
publish: npm run release
|
|
36
41
|
env:
|
|
37
42
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
38
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/CHANGELOG.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -241,6 +241,44 @@ function createPlaceholderTexture() {
|
|
|
241
241
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
242
242
|
return texture;
|
|
243
243
|
}
|
|
244
|
+
function createTapTexture(strumTexture) {
|
|
245
|
+
const rimPx = 8;
|
|
246
|
+
const rimFadePx = 6;
|
|
247
|
+
const image = strumTexture.image;
|
|
248
|
+
const canvas = document.createElement("canvas");
|
|
249
|
+
canvas.width = image.width;
|
|
250
|
+
canvas.height = image.height;
|
|
251
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
252
|
+
ctx.drawImage(image, 0, 0);
|
|
253
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
254
|
+
const pixels = imageData.data;
|
|
255
|
+
const clamp01 = (value) => Math.min(Math.max(value, 0), 1);
|
|
256
|
+
const colorWeight = (i) => {
|
|
257
|
+
const max = Math.max(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
258
|
+
const min = Math.min(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
259
|
+
const saturation = max === 0 ? 0 : (max - min) / max;
|
|
260
|
+
return pixels[i + 3] === 0 ? 0 : clamp01((saturation - 0.2) / 0.3);
|
|
261
|
+
};
|
|
262
|
+
for (let y = 0; y < canvas.height; y++) {
|
|
263
|
+
let runStart = 0;
|
|
264
|
+
for (let x = 0; x <= canvas.width; x++) {
|
|
265
|
+
if (x < canvas.width && colorWeight((y * canvas.width + x) * 4) > 0) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
for (let runX = runStart; runX < x; runX++) {
|
|
269
|
+
const distanceFromEnd = Math.min(runX - runStart, x - 1 - runX) + 1;
|
|
270
|
+
const rim = clamp01((rimPx + rimFadePx - distanceFromEnd) / rimFadePx);
|
|
271
|
+
const i = (y * canvas.width + runX) * 4;
|
|
272
|
+
pixels[i + 3] *= 1 - colorWeight(i) * (1 - rim);
|
|
273
|
+
}
|
|
274
|
+
runStart = x + 1;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
ctx.putImageData(imageData, 0, 0);
|
|
278
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
279
|
+
texture.colorSpace = strumTexture.colorSpace;
|
|
280
|
+
return texture;
|
|
281
|
+
}
|
|
244
282
|
var AnimatedTextureManager = class {
|
|
245
283
|
constructor() {
|
|
246
284
|
this.animatedTextures = [];
|
|
@@ -1121,26 +1159,33 @@ async function loadTextures(instrumentType, options = {}) {
|
|
|
1121
1159
|
[import_scan_chart.noteTypes.blue, "blue"],
|
|
1122
1160
|
[import_scan_chart.noteTypes.orange, "orange"]
|
|
1123
1161
|
]);
|
|
1124
|
-
const modifiers = /* @__PURE__ */ new Map([
|
|
1125
|
-
[import_scan_chart.noteFlags.strum, "-strum"],
|
|
1126
|
-
[import_scan_chart.noteFlags.hopo, "-hopo"],
|
|
1127
|
-
[import_scan_chart.noteFlags.tap, "-tap"]
|
|
1128
|
-
]);
|
|
1129
1162
|
const spFlags = /* @__PURE__ */ new Map([
|
|
1130
1163
|
[import_scan_chart.noteFlags.none, ""],
|
|
1131
1164
|
[SP_FLAG, "-sp"]
|
|
1132
1165
|
]);
|
|
1133
1166
|
for (const [laneKey, laneName] of lanes) {
|
|
1134
|
-
for (
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1167
|
+
for (const [spFlagKey, spFlagName] of spFlags) {
|
|
1168
|
+
const strumTexture = addTexture(
|
|
1169
|
+
laneKey,
|
|
1170
|
+
import_scan_chart.noteFlags.strum | spFlagKey,
|
|
1171
|
+
`5fret-${laneName}-strum${spFlagName}`
|
|
1172
|
+
);
|
|
1173
|
+
addTexture(
|
|
1174
|
+
laneKey,
|
|
1175
|
+
import_scan_chart.noteFlags.hopo | spFlagKey,
|
|
1176
|
+
`5fret-${laneName}-hopo${spFlagName}`
|
|
1177
|
+
);
|
|
1178
|
+
if (laneKey === import_scan_chart.noteTypes.open) {
|
|
1179
|
+
reuseTexture(
|
|
1180
|
+
laneKey,
|
|
1181
|
+
import_scan_chart.noteFlags.tap | spFlagKey,
|
|
1182
|
+
strumTexture.then(createTapTexture)
|
|
1183
|
+
);
|
|
1184
|
+
} else {
|
|
1140
1185
|
addTexture(
|
|
1141
1186
|
laneKey,
|
|
1142
|
-
|
|
1143
|
-
`5fret-${laneName}${
|
|
1187
|
+
import_scan_chart.noteFlags.tap | spFlagKey,
|
|
1188
|
+
`5fret-${laneName}-tap${spFlagName}`
|
|
1144
1189
|
);
|
|
1145
1190
|
}
|
|
1146
1191
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -201,6 +201,44 @@ function createPlaceholderTexture() {
|
|
|
201
201
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
202
202
|
return texture;
|
|
203
203
|
}
|
|
204
|
+
function createTapTexture(strumTexture) {
|
|
205
|
+
const rimPx = 8;
|
|
206
|
+
const rimFadePx = 6;
|
|
207
|
+
const image = strumTexture.image;
|
|
208
|
+
const canvas = document.createElement("canvas");
|
|
209
|
+
canvas.width = image.width;
|
|
210
|
+
canvas.height = image.height;
|
|
211
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
212
|
+
ctx.drawImage(image, 0, 0);
|
|
213
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
214
|
+
const pixels = imageData.data;
|
|
215
|
+
const clamp01 = (value) => Math.min(Math.max(value, 0), 1);
|
|
216
|
+
const colorWeight = (i) => {
|
|
217
|
+
const max = Math.max(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
218
|
+
const min = Math.min(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
219
|
+
const saturation = max === 0 ? 0 : (max - min) / max;
|
|
220
|
+
return pixels[i + 3] === 0 ? 0 : clamp01((saturation - 0.2) / 0.3);
|
|
221
|
+
};
|
|
222
|
+
for (let y = 0; y < canvas.height; y++) {
|
|
223
|
+
let runStart = 0;
|
|
224
|
+
for (let x = 0; x <= canvas.width; x++) {
|
|
225
|
+
if (x < canvas.width && colorWeight((y * canvas.width + x) * 4) > 0) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
for (let runX = runStart; runX < x; runX++) {
|
|
229
|
+
const distanceFromEnd = Math.min(runX - runStart, x - 1 - runX) + 1;
|
|
230
|
+
const rim = clamp01((rimPx + rimFadePx - distanceFromEnd) / rimFadePx);
|
|
231
|
+
const i = (y * canvas.width + runX) * 4;
|
|
232
|
+
pixels[i + 3] *= 1 - colorWeight(i) * (1 - rim);
|
|
233
|
+
}
|
|
234
|
+
runStart = x + 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
ctx.putImageData(imageData, 0, 0);
|
|
238
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
239
|
+
texture.colorSpace = strumTexture.colorSpace;
|
|
240
|
+
return texture;
|
|
241
|
+
}
|
|
204
242
|
var AnimatedTextureManager = class {
|
|
205
243
|
constructor() {
|
|
206
244
|
this.animatedTextures = [];
|
|
@@ -1081,26 +1119,33 @@ async function loadTextures(instrumentType, options = {}) {
|
|
|
1081
1119
|
[noteTypes.blue, "blue"],
|
|
1082
1120
|
[noteTypes.orange, "orange"]
|
|
1083
1121
|
]);
|
|
1084
|
-
const modifiers = /* @__PURE__ */ new Map([
|
|
1085
|
-
[noteFlags.strum, "-strum"],
|
|
1086
|
-
[noteFlags.hopo, "-hopo"],
|
|
1087
|
-
[noteFlags.tap, "-tap"]
|
|
1088
|
-
]);
|
|
1089
1122
|
const spFlags = /* @__PURE__ */ new Map([
|
|
1090
1123
|
[noteFlags.none, ""],
|
|
1091
1124
|
[SP_FLAG, "-sp"]
|
|
1092
1125
|
]);
|
|
1093
1126
|
for (const [laneKey, laneName] of lanes) {
|
|
1094
|
-
for (
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1127
|
+
for (const [spFlagKey, spFlagName] of spFlags) {
|
|
1128
|
+
const strumTexture = addTexture(
|
|
1129
|
+
laneKey,
|
|
1130
|
+
noteFlags.strum | spFlagKey,
|
|
1131
|
+
`5fret-${laneName}-strum${spFlagName}`
|
|
1132
|
+
);
|
|
1133
|
+
addTexture(
|
|
1134
|
+
laneKey,
|
|
1135
|
+
noteFlags.hopo | spFlagKey,
|
|
1136
|
+
`5fret-${laneName}-hopo${spFlagName}`
|
|
1137
|
+
);
|
|
1138
|
+
if (laneKey === noteTypes.open) {
|
|
1139
|
+
reuseTexture(
|
|
1140
|
+
laneKey,
|
|
1141
|
+
noteFlags.tap | spFlagKey,
|
|
1142
|
+
strumTexture.then(createTapTexture)
|
|
1143
|
+
);
|
|
1144
|
+
} else {
|
|
1100
1145
|
addTexture(
|
|
1101
1146
|
laneKey,
|
|
1102
|
-
|
|
1103
|
-
`5fret-${laneName}${
|
|
1147
|
+
noteFlags.tap | spFlagKey,
|
|
1148
|
+
`5fret-${laneName}-tap${spFlagName}`
|
|
1104
1149
|
);
|
|
1105
1150
|
}
|
|
1106
1151
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chart-preview",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"author": "Geo",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
|
-
"url": "git+
|
|
29
|
+
"url": "git+https://github.com/Geomitron/chart-preview.git"
|
|
30
30
|
},
|
|
31
31
|
"bugs": {
|
|
32
32
|
"url": "https://github.com/Geomitron/chart-preview/issues"
|
package/src/ChartPreview.ts
CHANGED
|
@@ -259,6 +259,56 @@ function createPlaceholderTexture(): THREE.Texture {
|
|
|
259
259
|
return texture;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Marks `strumTexture` the way the five-fret tap textures mark tap notes:
|
|
264
|
+
* each row of colored pixels is hollowed out, except for a rim at both ends of
|
|
265
|
+
* the row. With these values, the result closely matches each five-fret tap
|
|
266
|
+
* texture when this is applied to the matching strum texture.
|
|
267
|
+
*/
|
|
268
|
+
function createTapTexture(strumTexture: THREE.Texture): THREE.Texture {
|
|
269
|
+
const rimPx = 8;
|
|
270
|
+
const rimFadePx = 6;
|
|
271
|
+
const image = strumTexture.image as HTMLCanvasElement | HTMLImageElement;
|
|
272
|
+
const canvas = document.createElement("canvas");
|
|
273
|
+
canvas.width = image.width;
|
|
274
|
+
canvas.height = image.height;
|
|
275
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true })!;
|
|
276
|
+
ctx.drawImage(image, 0, 0);
|
|
277
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
278
|
+
const pixels = imageData.data;
|
|
279
|
+
const clamp01 = (value: number) => Math.min(Math.max(value, 0), 1);
|
|
280
|
+
|
|
281
|
+
/** 0 for grey or transparent pixels, rising to 1 for strongly colored pixels. */
|
|
282
|
+
const colorWeight = (i: number) => {
|
|
283
|
+
const max = Math.max(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
284
|
+
const min = Math.min(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
285
|
+
const saturation = max === 0 ? 0 : (max - min) / max;
|
|
286
|
+
return pixels[i + 3] === 0 ? 0 : clamp01((saturation - 0.2) / 0.3);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
for (let y = 0; y < canvas.height; y++) {
|
|
290
|
+
let runStart = 0;
|
|
291
|
+
for (let x = 0; x <= canvas.width; x++) {
|
|
292
|
+
if (x < canvas.width && colorWeight((y * canvas.width + x) * 4) > 0) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
// Pixels runStart to x - 1 are a run of colored pixels
|
|
296
|
+
for (let runX = runStart; runX < x; runX++) {
|
|
297
|
+
const distanceFromEnd = Math.min(runX - runStart, x - 1 - runX) + 1;
|
|
298
|
+
const rim = clamp01((rimPx + rimFadePx - distanceFromEnd) / rimFadePx);
|
|
299
|
+
const i = (y * canvas.width + runX) * 4;
|
|
300
|
+
pixels[i + 3] *= 1 - colorWeight(i) * (1 - rim);
|
|
301
|
+
}
|
|
302
|
+
runStart = x + 1;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
ctx.putImageData(imageData, 0, 0);
|
|
307
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
308
|
+
texture.colorSpace = strumTexture.colorSpace;
|
|
309
|
+
return texture;
|
|
310
|
+
}
|
|
311
|
+
|
|
262
312
|
/** Collection of animated textures that need to be ticked each frame */
|
|
263
313
|
class AnimatedTextureManager {
|
|
264
314
|
private animatedTextures: AnimatedTexture[] = [];
|
|
@@ -1482,27 +1532,35 @@ async function loadTextures(
|
|
|
1482
1532
|
[noteTypes.blue, "blue"],
|
|
1483
1533
|
[noteTypes.orange, "orange"],
|
|
1484
1534
|
]);
|
|
1485
|
-
const modifiers = new Map([
|
|
1486
|
-
[noteFlags.strum, "-strum"],
|
|
1487
|
-
[noteFlags.hopo, "-hopo"],
|
|
1488
|
-
[noteFlags.tap, "-tap"],
|
|
1489
|
-
]);
|
|
1490
1535
|
const spFlags = new Map([
|
|
1491
1536
|
[noteFlags.none, ""],
|
|
1492
1537
|
[SP_FLAG, "-sp"],
|
|
1493
1538
|
]);
|
|
1494
1539
|
|
|
1495
1540
|
for (const [laneKey, laneName] of lanes) {
|
|
1496
|
-
for (
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1541
|
+
for (const [spFlagKey, spFlagName] of spFlags) {
|
|
1542
|
+
const strumTexture = addTexture(
|
|
1543
|
+
laneKey,
|
|
1544
|
+
noteFlags.strum | spFlagKey,
|
|
1545
|
+
`5fret-${laneName}-strum${spFlagName}`
|
|
1546
|
+
);
|
|
1547
|
+
addTexture(
|
|
1548
|
+
laneKey,
|
|
1549
|
+
noteFlags.hopo | spFlagKey,
|
|
1550
|
+
`5fret-${laneName}-hopo${spFlagName}`
|
|
1551
|
+
);
|
|
1552
|
+
if (laneKey === noteTypes.open) {
|
|
1553
|
+
// There is no open tap texture
|
|
1554
|
+
reuseTexture(
|
|
1555
|
+
laneKey,
|
|
1556
|
+
noteFlags.tap | spFlagKey,
|
|
1557
|
+
strumTexture.then(createTapTexture)
|
|
1558
|
+
);
|
|
1559
|
+
} else {
|
|
1502
1560
|
addTexture(
|
|
1503
1561
|
laneKey,
|
|
1504
|
-
|
|
1505
|
-
`5fret-${laneName}${
|
|
1562
|
+
noteFlags.tap | spFlagKey,
|
|
1563
|
+
`5fret-${laneName}-tap${spFlagName}`
|
|
1506
1564
|
);
|
|
1507
1565
|
}
|
|
1508
1566
|
}
|