q5 2.18.1 → 2.19.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 +3 -3
- package/deno.json +1 -1
- package/package.json +1 -1
- package/q5.d.ts +282 -78
- package/q5.js +320 -149
- package/q5.min.js +2 -2
package/q5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* q5.js
|
|
3
|
-
* @version 2.
|
|
3
|
+
* @version 2.19
|
|
4
4
|
* @author quinton-ashley, Tezumie, and LingDong-
|
|
5
5
|
* @license LGPL-3.0
|
|
6
6
|
* @class Q5
|
|
@@ -104,9 +104,9 @@ function Q5(scope, parent, renderer) {
|
|
|
104
104
|
throw e;
|
|
105
105
|
}
|
|
106
106
|
for (let m of Q5.methods.post) m.call($);
|
|
107
|
+
$.postProcess();
|
|
107
108
|
if ($._render) $._render();
|
|
108
109
|
if ($._finishRender) $._finishRender();
|
|
109
|
-
$.postProcess();
|
|
110
110
|
q.pmouseX = $.mouseX;
|
|
111
111
|
q.pmouseY = $.mouseY;
|
|
112
112
|
q.moveX = q.moveY = 0;
|
|
@@ -223,6 +223,8 @@ function Q5(scope, parent, renderer) {
|
|
|
223
223
|
$.postProcess ??= () => {};
|
|
224
224
|
|
|
225
225
|
let userFns = [
|
|
226
|
+
'setup',
|
|
227
|
+
'postProcess',
|
|
226
228
|
'mouseMoved',
|
|
227
229
|
'mousePressed',
|
|
228
230
|
'mouseReleased',
|
|
@@ -311,7 +313,7 @@ function createCanvas(w, h, opt) {
|
|
|
311
313
|
}
|
|
312
314
|
}
|
|
313
315
|
|
|
314
|
-
Q5.version = Q5.VERSION = '2.
|
|
316
|
+
Q5.version = Q5.VERSION = '2.19';
|
|
315
317
|
|
|
316
318
|
if (typeof document == 'object') {
|
|
317
319
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -420,49 +422,6 @@ Q5.modules.canvas = ($, q) => {
|
|
|
420
422
|
return g;
|
|
421
423
|
};
|
|
422
424
|
|
|
423
|
-
async function saveFile(data, name, ext) {
|
|
424
|
-
name = name || 'untitled';
|
|
425
|
-
ext = ext || 'png';
|
|
426
|
-
if (ext == 'jpg' || ext == 'png' || ext == 'webp') {
|
|
427
|
-
if (data instanceof OffscreenCanvas) {
|
|
428
|
-
const blob = await data.convertToBlob({ type: 'image/' + ext });
|
|
429
|
-
data = await new Promise((resolve) => {
|
|
430
|
-
const reader = new FileReader();
|
|
431
|
-
reader.onloadend = () => resolve(reader.result);
|
|
432
|
-
reader.readAsDataURL(blob);
|
|
433
|
-
});
|
|
434
|
-
} else {
|
|
435
|
-
data = data.toDataURL('image/' + ext);
|
|
436
|
-
}
|
|
437
|
-
} else {
|
|
438
|
-
let type = 'text/plain';
|
|
439
|
-
if (ext == 'json') {
|
|
440
|
-
if (typeof data != 'string') data = JSON.stringify(data);
|
|
441
|
-
type = 'text/json';
|
|
442
|
-
}
|
|
443
|
-
data = new Blob([data], { type });
|
|
444
|
-
data = URL.createObjectURL(data);
|
|
445
|
-
}
|
|
446
|
-
let a = document.createElement('a');
|
|
447
|
-
a.href = data;
|
|
448
|
-
a.download = name + '.' + ext;
|
|
449
|
-
a.click();
|
|
450
|
-
URL.revokeObjectURL(a.href);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
$.save = (a, b, c) => {
|
|
454
|
-
if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
|
|
455
|
-
c = b;
|
|
456
|
-
b = a;
|
|
457
|
-
a = $.canvas;
|
|
458
|
-
}
|
|
459
|
-
if (c) return saveFile(a, b, c);
|
|
460
|
-
if (b) {
|
|
461
|
-
b = b.split('.');
|
|
462
|
-
saveFile(a, b[0], b.at(-1));
|
|
463
|
-
} else saveFile(a);
|
|
464
|
-
};
|
|
465
|
-
|
|
466
425
|
$._setCanvasSize = (w, h) => {
|
|
467
426
|
if (!w) h ??= window.innerHeight;
|
|
468
427
|
else h ??= w;
|
|
@@ -1322,10 +1281,7 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1322
1281
|
opt ??= {};
|
|
1323
1282
|
opt.alpha ??= true;
|
|
1324
1283
|
opt.colorSpace ??= $.canvas.colorSpace || Q5.canvasOptions.colorSpace;
|
|
1325
|
-
|
|
1326
|
-
img.defaultWidth = w * $._defaultImageScale;
|
|
1327
|
-
img.defaultHeight = h * $._defaultImageScale;
|
|
1328
|
-
return img;
|
|
1284
|
+
return new Q5.Image(w, h, opt);
|
|
1329
1285
|
};
|
|
1330
1286
|
|
|
1331
1287
|
$.loadImage = function (url, cb, opt) {
|
|
@@ -1481,7 +1437,7 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1481
1437
|
$.ctx.globalCompositeOperation = 'source-over';
|
|
1482
1438
|
$.ctx.drawImage($.canvas, 0, 0, $.canvas.w, $.canvas.h);
|
|
1483
1439
|
$.ctx.restore();
|
|
1484
|
-
$._retint = true;
|
|
1440
|
+
$.modified = $._retint = true;
|
|
1485
1441
|
};
|
|
1486
1442
|
|
|
1487
1443
|
if ($._scope == 'image') {
|
|
@@ -1499,7 +1455,7 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1499
1455
|
$.ctx.clearRect(0, 0, c.width, c.height);
|
|
1500
1456
|
$.ctx.drawImage(o, 0, 0, c.width, c.height);
|
|
1501
1457
|
|
|
1502
|
-
$._retint = true;
|
|
1458
|
+
$.modified = $._retint = true;
|
|
1503
1459
|
};
|
|
1504
1460
|
}
|
|
1505
1461
|
|
|
@@ -1546,17 +1502,25 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1546
1502
|
$.ctx.globalCompositeOperation = old;
|
|
1547
1503
|
$.ctx.restore();
|
|
1548
1504
|
|
|
1549
|
-
$._retint = true;
|
|
1505
|
+
$.modified = $._retint = true;
|
|
1550
1506
|
};
|
|
1551
1507
|
|
|
1552
1508
|
$.inset = (x, y, w, h, dx, dy, dw, dh) => {
|
|
1553
1509
|
let pd = $._pixelDensity || 1;
|
|
1554
1510
|
$.ctx.drawImage($.canvas, x * pd, y * pd, w * pd, h * pd, dx, dy, dw, dh);
|
|
1555
1511
|
|
|
1556
|
-
$._retint = true;
|
|
1512
|
+
$.modified = $._retint = true;
|
|
1557
1513
|
};
|
|
1558
1514
|
|
|
1559
|
-
$.copy = () =>
|
|
1515
|
+
$.copy = () => {
|
|
1516
|
+
let img = $.get();
|
|
1517
|
+
for (let prop in $) {
|
|
1518
|
+
if (typeof $[prop] != 'function' && !/(canvas|ctx|texture|textureIndex)/.test(prop)) {
|
|
1519
|
+
img[prop] = $[prop];
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return img;
|
|
1523
|
+
};
|
|
1560
1524
|
|
|
1561
1525
|
$.get = (x, y, w, h) => {
|
|
1562
1526
|
let pd = $._pixelDensity || 1;
|
|
@@ -1566,22 +1530,19 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1566
1530
|
}
|
|
1567
1531
|
x = Math.floor(x || 0) * pd;
|
|
1568
1532
|
y = Math.floor(y || 0) * pd;
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
w
|
|
1572
|
-
h
|
|
1573
|
-
|
|
1574
|
-
img.
|
|
1575
|
-
img._pixelDensity = pd;
|
|
1576
|
-
img.width = _w;
|
|
1577
|
-
img.height = _h;
|
|
1533
|
+
w ??= $.width;
|
|
1534
|
+
h ??= $.height;
|
|
1535
|
+
let img = $.createImage(w, h, { pixelDensity: pd });
|
|
1536
|
+
img.ctx.drawImage($.canvas, x, y, w * pd, h * pd, 0, 0, w, h);
|
|
1537
|
+
img.width = w;
|
|
1538
|
+
img.height = h;
|
|
1578
1539
|
return img;
|
|
1579
1540
|
};
|
|
1580
1541
|
|
|
1581
1542
|
$.set = (x, y, c) => {
|
|
1582
1543
|
x = Math.floor(x);
|
|
1583
1544
|
y = Math.floor(y);
|
|
1584
|
-
$._retint = true;
|
|
1545
|
+
$.modified = $._retint = true;
|
|
1585
1546
|
if (c.canvas) {
|
|
1586
1547
|
let old = $._tint;
|
|
1587
1548
|
$._tint = null;
|
|
@@ -1609,7 +1570,7 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1609
1570
|
$.updatePixels = () => {
|
|
1610
1571
|
if (imgData != null) {
|
|
1611
1572
|
$.ctx.putImageData(imgData, 0, 0);
|
|
1612
|
-
$._retint = true;
|
|
1573
|
+
$.modified = $._retint = true;
|
|
1613
1574
|
}
|
|
1614
1575
|
};
|
|
1615
1576
|
|
|
@@ -1618,6 +1579,20 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1618
1579
|
|
|
1619
1580
|
if ($._scope == 'image') return;
|
|
1620
1581
|
|
|
1582
|
+
$._saveCanvas = async (data, ext) => {
|
|
1583
|
+
data = data.canvas || data;
|
|
1584
|
+
if (data instanceof OffscreenCanvas) {
|
|
1585
|
+
const blob = await data.convertToBlob({ type: 'image/' + ext });
|
|
1586
|
+
|
|
1587
|
+
return await new Promise((resolve) => {
|
|
1588
|
+
const reader = new FileReader();
|
|
1589
|
+
reader.onloadend = () => resolve(reader.result);
|
|
1590
|
+
reader.readAsDataURL(blob);
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
return data.toDataURL('image/' + ext);
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1621
1596
|
$.tint = function (c) {
|
|
1622
1597
|
$._tint = (c._q5Color ? c : $.color(...arguments)).toString();
|
|
1623
1598
|
};
|
|
@@ -1961,10 +1936,15 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1961
1936
|
tY = leading * lines.length;
|
|
1962
1937
|
|
|
1963
1938
|
if (!img) {
|
|
1939
|
+
let ogBaseline = $.ctx.textBaseline;
|
|
1940
|
+
$.ctx.textBaseline = 'alphabetic';
|
|
1941
|
+
|
|
1964
1942
|
let measure = ctx.measureText(' ');
|
|
1965
1943
|
let ascent = measure.fontBoundingBoxAscent;
|
|
1966
1944
|
let descent = measure.fontBoundingBoxDescent;
|
|
1967
1945
|
|
|
1946
|
+
$.ctx.textBaseline = ogBaseline;
|
|
1947
|
+
|
|
1968
1948
|
img = $.createImage.call($, Math.ceil(ctx.measureText(str).width), Math.ceil(tY + descent), {
|
|
1969
1949
|
pixelDensity: $._pixelDensity
|
|
1970
1950
|
});
|
|
@@ -1975,12 +1955,13 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1975
1955
|
img._middle = img._top + ascent * 0.5;
|
|
1976
1956
|
img._bottom = img._top + ascent;
|
|
1977
1957
|
img._leading = leading;
|
|
1958
|
+
} else {
|
|
1959
|
+
img.modified = true;
|
|
1978
1960
|
}
|
|
1979
1961
|
|
|
1980
1962
|
img._fill = $._fill;
|
|
1981
1963
|
img._stroke = $._stroke;
|
|
1982
1964
|
img._strokeWeight = $._strokeWeight;
|
|
1983
|
-
img.modified = true;
|
|
1984
1965
|
|
|
1985
1966
|
ctx = img.ctx;
|
|
1986
1967
|
|
|
@@ -2082,8 +2063,8 @@ Q5.modules.ai = ($) => {
|
|
|
2082
2063
|
let parts = errFile.split(':');
|
|
2083
2064
|
let lineNum = parseInt(parts.at(-2));
|
|
2084
2065
|
if (askAI) lineNum++;
|
|
2085
|
-
parts[
|
|
2086
|
-
let fileUrl = parts.slice(0, 2).join(':');
|
|
2066
|
+
parts[parts.length - 1] = parts.at(-1).split(')')[0];
|
|
2067
|
+
let fileUrl = parts.slice(0, -2).join(':');
|
|
2087
2068
|
let fileBase = fileUrl.split('/').at(-1);
|
|
2088
2069
|
|
|
2089
2070
|
try {
|
|
@@ -2107,7 +2088,7 @@ Q5.modules.ai = ($) => {
|
|
|
2107
2088
|
askAI && e.message.length > 10 ? e.message.slice(10) : 'Whats+wrong+with+this+line%3F+short+answer';
|
|
2108
2089
|
|
|
2109
2090
|
let url =
|
|
2110
|
-
'https://chatgpt.com/?q=q5.js+' +
|
|
2091
|
+
'https://chatgpt.com/?q=using+q5.js+not+p5.js+' +
|
|
2111
2092
|
question +
|
|
2112
2093
|
(askAI ? '' : '%0A%0A' + encodeURIComponent(e.name + ': ' + e.message)) +
|
|
2113
2094
|
'%0A%0ALine%3A+' +
|
|
@@ -2911,7 +2892,7 @@ Q5.modules.input = ($, q) => {
|
|
|
2911
2892
|
$._onwheel = (e) => {
|
|
2912
2893
|
$._updateMouse(e);
|
|
2913
2894
|
e.delta = e.deltaY;
|
|
2914
|
-
if ($.mouseWheel(e) == false) e.preventDefault();
|
|
2895
|
+
if ($.mouseWheel(e) == false || $._noScroll) e.preventDefault();
|
|
2915
2896
|
};
|
|
2916
2897
|
|
|
2917
2898
|
$.cursor = (name, x, y) => {
|
|
@@ -2926,9 +2907,8 @@ Q5.modules.input = ($, q) => {
|
|
|
2926
2907
|
$.canvas.style.cursor = name + pfx;
|
|
2927
2908
|
};
|
|
2928
2909
|
|
|
2929
|
-
$.noCursor = () =>
|
|
2930
|
-
|
|
2931
|
-
};
|
|
2910
|
+
$.noCursor = () => ($.canvas.style.cursor = 'none');
|
|
2911
|
+
$.noScroll = () => ($._noScroll = true);
|
|
2932
2912
|
|
|
2933
2913
|
if (window) {
|
|
2934
2914
|
$.lockMouse = document.body?.requestPointerLock;
|
|
@@ -3167,6 +3147,14 @@ Q5.modules.math = ($, q) => {
|
|
|
3167
3147
|
}
|
|
3168
3148
|
};
|
|
3169
3149
|
|
|
3150
|
+
if ($._renderer == 'c2d' && !$._webgpuFallback) {
|
|
3151
|
+
$.randomX = (v = 0) => $.random(-v, $.canvas.w + v);
|
|
3152
|
+
$.randomY = (v = 0) => $.random(-v, $.canvas.h + v);
|
|
3153
|
+
} else {
|
|
3154
|
+
$.randomX = (v = 0) => $.random(-$.canvas.hw - v, $.canvas.hw + v);
|
|
3155
|
+
$.randomY = (v = 0) => $.random(-$.canvas.hh - v, $.canvas.hh + v);
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3170
3158
|
$.randomGenerator = (method) => {
|
|
3171
3159
|
if (method == $.LCG) rng1 = lcg();
|
|
3172
3160
|
else if (method == $.SHR3) rng1 = shr3();
|
|
@@ -3455,7 +3443,7 @@ Q5.PerlinNoise = class extends Q5.Noise {
|
|
|
3455
3443
|
return (total / maxAmp + 1) / 2;
|
|
3456
3444
|
}
|
|
3457
3445
|
};
|
|
3458
|
-
Q5.modules.record = (
|
|
3446
|
+
Q5.modules.record = ($, q) => {
|
|
3459
3447
|
let rec, btn0, btn1, timer, formatSelect, qualitySelect;
|
|
3460
3448
|
|
|
3461
3449
|
$.recording = false;
|
|
@@ -3549,26 +3537,27 @@ Q5.modules.record = ($) => {
|
|
|
3549
3537
|
}
|
|
3550
3538
|
}
|
|
3551
3539
|
|
|
3552
|
-
formatSelect = $.createSelect();
|
|
3540
|
+
formatSelect = $.createSelect('format');
|
|
3553
3541
|
for (const name in rec.formats) {
|
|
3554
3542
|
formatSelect.option(name, rec.formats[name]);
|
|
3555
3543
|
}
|
|
3544
|
+
formatSelect.title = 'Video Format';
|
|
3556
3545
|
rec.append(formatSelect);
|
|
3557
3546
|
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
'8K': 75000000 // 75 Mbps
|
|
3547
|
+
let qMult = {
|
|
3548
|
+
min: 0.1,
|
|
3549
|
+
low: 0.25,
|
|
3550
|
+
mid: 0.5,
|
|
3551
|
+
high: 0.75,
|
|
3552
|
+
ultra: 0.9,
|
|
3553
|
+
max: 1
|
|
3566
3554
|
};
|
|
3567
3555
|
|
|
3568
|
-
qualitySelect = $.createSelect();
|
|
3569
|
-
for (let name in
|
|
3570
|
-
qualitySelect.option(name);
|
|
3556
|
+
qualitySelect = $.createSelect('quality');
|
|
3557
|
+
for (let name in qMult) {
|
|
3558
|
+
qualitySelect.option(name, qMult[name]);
|
|
3571
3559
|
}
|
|
3560
|
+
qualitySelect.title = 'Video Quality';
|
|
3572
3561
|
rec.append(qualitySelect);
|
|
3573
3562
|
|
|
3574
3563
|
rec.encoderSettings = {};
|
|
@@ -3578,7 +3567,7 @@ Q5.modules.record = ($) => {
|
|
|
3578
3567
|
}
|
|
3579
3568
|
|
|
3580
3569
|
function changeQuality() {
|
|
3581
|
-
rec.encoderSettings.videoBitsPerSecond =
|
|
3570
|
+
rec.encoderSettings.videoBitsPerSecond = maxVideoBitRate * qualitySelect.value;
|
|
3582
3571
|
}
|
|
3583
3572
|
|
|
3584
3573
|
formatSelect.addEventListener('change', changeFormat);
|
|
@@ -3587,8 +3576,8 @@ Q5.modules.record = ($) => {
|
|
|
3587
3576
|
Object.defineProperty(rec, 'quality', {
|
|
3588
3577
|
get: () => qualitySelect.selected,
|
|
3589
3578
|
set: (v) => {
|
|
3590
|
-
v = v.
|
|
3591
|
-
if (
|
|
3579
|
+
v = v.toLowerCase();
|
|
3580
|
+
if (qMult[v]) {
|
|
3592
3581
|
qualitySelect.selected = v;
|
|
3593
3582
|
changeQuality();
|
|
3594
3583
|
}
|
|
@@ -3607,11 +3596,15 @@ Q5.modules.record = ($) => {
|
|
|
3607
3596
|
});
|
|
3608
3597
|
|
|
3609
3598
|
let h = $.canvas.height;
|
|
3610
|
-
rec.quality = h >= 4320 ? '8K' : h >= 2160 ? '4K' : h >= 1440 ? 'QHD' : h >= 1080 ? 'FHD' : h >= 720 ? 'HD' : 'SD';
|
|
3611
3599
|
|
|
3612
3600
|
if (h >= 1440 && rec.formats.VP9) rec.format = 'VP9';
|
|
3613
3601
|
else rec.format = 'H.264';
|
|
3614
3602
|
|
|
3603
|
+
let maxVideoBitRate =
|
|
3604
|
+
(h >= 4320 ? 128 : h >= 2160 ? 75 : h >= 1440 ? 36 : h >= 1080 ? 28 : h >= 720 ? 22 : 16) * 1000000;
|
|
3605
|
+
|
|
3606
|
+
rec.quality = 'high';
|
|
3607
|
+
|
|
3615
3608
|
btn0.addEventListener('click', () => {
|
|
3616
3609
|
if (!$.recording) start();
|
|
3617
3610
|
else if (!rec.paused) $.pauseRecording();
|
|
@@ -3652,7 +3645,7 @@ Q5.modules.record = ($) => {
|
|
|
3652
3645
|
});
|
|
3653
3646
|
|
|
3654
3647
|
rec.mediaRecorder.start();
|
|
3655
|
-
|
|
3648
|
+
q.recording = true;
|
|
3656
3649
|
rec.paused = false;
|
|
3657
3650
|
rec.classList.add('recording');
|
|
3658
3651
|
|
|
@@ -3673,7 +3666,7 @@ Q5.modules.record = ($) => {
|
|
|
3673
3666
|
|
|
3674
3667
|
rec.resetTimer();
|
|
3675
3668
|
rec.mediaRecorder.stop();
|
|
3676
|
-
|
|
3669
|
+
q.recording = false;
|
|
3677
3670
|
rec.paused = false;
|
|
3678
3671
|
rec.classList.remove('recording');
|
|
3679
3672
|
}
|
|
@@ -3745,7 +3738,7 @@ Q5.modules.record = ($) => {
|
|
|
3745
3738
|
$.deleteRecording = () => {
|
|
3746
3739
|
stop();
|
|
3747
3740
|
resetUI();
|
|
3748
|
-
|
|
3741
|
+
q.recording = false;
|
|
3749
3742
|
};
|
|
3750
3743
|
|
|
3751
3744
|
$.saveRecording = async (fileName) => {
|
|
@@ -3770,7 +3763,8 @@ Q5.modules.record = ($) => {
|
|
|
3770
3763
|
a.target = iframe.name;
|
|
3771
3764
|
a.href = dataUrl;
|
|
3772
3765
|
fileName ??=
|
|
3773
|
-
|
|
3766
|
+
document.title +
|
|
3767
|
+
' ' +
|
|
3774
3768
|
new Date()
|
|
3775
3769
|
.toLocaleString(undefined, { hour12: false })
|
|
3776
3770
|
.replace(',', ' at')
|
|
@@ -3788,7 +3782,7 @@ Q5.modules.record = ($) => {
|
|
|
3788
3782
|
|
|
3789
3783
|
setTimeout(() => URL.revokeObjectURL(dataUrl), 1000);
|
|
3790
3784
|
resetUI();
|
|
3791
|
-
|
|
3785
|
+
q.recording = false;
|
|
3792
3786
|
};
|
|
3793
3787
|
};
|
|
3794
3788
|
Q5.modules.sound = ($, q) => {
|
|
@@ -4028,6 +4022,10 @@ Q5.modules.util = ($, q) => {
|
|
|
4028
4022
|
$.loadJSON = (url, cb) => $._loadFile(url, cb, 'json');
|
|
4029
4023
|
$.loadCSV = (url, cb) => $._loadFile(url, cb, 'csv');
|
|
4030
4024
|
|
|
4025
|
+
const imgRegex = /(jpe?g|png|gif|webp|avif|svg)/,
|
|
4026
|
+
fontRegex = /(ttf|otf|woff2?|eot|json)/,
|
|
4027
|
+
audioRegex = /(wav|flac|mp3|ogg|m4a|aac|aiff|weba)/;
|
|
4028
|
+
|
|
4031
4029
|
$.load = function (...urls) {
|
|
4032
4030
|
if (Array.isArray(urls[0])) urls = urls[0];
|
|
4033
4031
|
|
|
@@ -4041,11 +4039,11 @@ Q5.modules.util = ($, q) => {
|
|
|
4041
4039
|
obj = $.loadJSON(url);
|
|
4042
4040
|
} else if (ext == 'csv') {
|
|
4043
4041
|
obj = $.loadCSV(url);
|
|
4044
|
-
} else if (
|
|
4042
|
+
} else if (imgRegex.test(ext)) {
|
|
4045
4043
|
obj = $.loadImage(url);
|
|
4046
|
-
} else if (
|
|
4044
|
+
} else if (fontRegex.test(ext)) {
|
|
4047
4045
|
obj = $.loadFont(url);
|
|
4048
|
-
} else if (
|
|
4046
|
+
} else if (audioRegex.test(ext)) {
|
|
4049
4047
|
obj = $.loadSound(url);
|
|
4050
4048
|
} else {
|
|
4051
4049
|
obj = $.loadText(url);
|
|
@@ -4057,6 +4055,40 @@ Q5.modules.util = ($, q) => {
|
|
|
4057
4055
|
return Promise.all(loaders);
|
|
4058
4056
|
};
|
|
4059
4057
|
|
|
4058
|
+
async function saveFile(data, name, ext) {
|
|
4059
|
+
name = name || 'untitled';
|
|
4060
|
+
ext = ext || 'png';
|
|
4061
|
+
if (imgRegex.test(ext)) {
|
|
4062
|
+
data = await $._saveCanvas(data, ext);
|
|
4063
|
+
} else {
|
|
4064
|
+
let type = 'text/plain';
|
|
4065
|
+
if (ext == 'json') {
|
|
4066
|
+
if (typeof data != 'string') data = JSON.stringify(data);
|
|
4067
|
+
type = 'text/json';
|
|
4068
|
+
}
|
|
4069
|
+
data = new Blob([data], { type });
|
|
4070
|
+
data = URL.createObjectURL(data);
|
|
4071
|
+
}
|
|
4072
|
+
let a = document.createElement('a');
|
|
4073
|
+
a.href = data;
|
|
4074
|
+
a.download = name + '.' + ext;
|
|
4075
|
+
a.click();
|
|
4076
|
+
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
|
|
4077
|
+
}
|
|
4078
|
+
|
|
4079
|
+
$.save = (a, b, c) => {
|
|
4080
|
+
if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
|
|
4081
|
+
c = b;
|
|
4082
|
+
b = a;
|
|
4083
|
+
a = $.canvas;
|
|
4084
|
+
}
|
|
4085
|
+
if (c) saveFile(a, b, c);
|
|
4086
|
+
else if (b) {
|
|
4087
|
+
let lastDot = b.lastIndexOf('.');
|
|
4088
|
+
saveFile(a, b.slice(0, lastDot), b.slice(lastDot + 1));
|
|
4089
|
+
} else saveFile(a);
|
|
4090
|
+
};
|
|
4091
|
+
|
|
4060
4092
|
$.CSV = {};
|
|
4061
4093
|
$.CSV.parse = (csv, sep = ',', lineSep = '\n') => {
|
|
4062
4094
|
if (!csv.length) return [];
|
|
@@ -4390,11 +4422,6 @@ Q5.Vector.sub = (v, u) => v.copy().sub(u);
|
|
|
4390
4422
|
for (let k of ['fromAngle', 'fromAngles', 'random2D', 'random3D']) {
|
|
4391
4423
|
Q5.Vector[k] = (u, v, t) => new Q5.Vector()[k](u, v, t);
|
|
4392
4424
|
}
|
|
4393
|
-
/**
|
|
4394
|
-
* q5-webgpu
|
|
4395
|
-
*
|
|
4396
|
-
* EXPERIMENTAL, for developer testing only!
|
|
4397
|
-
*/
|
|
4398
4425
|
Q5.renderers.webgpu = {};
|
|
4399
4426
|
|
|
4400
4427
|
Q5.renderers.webgpu.canvas = ($, q) => {
|
|
@@ -4410,6 +4437,11 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
4410
4437
|
|
|
4411
4438
|
let pass,
|
|
4412
4439
|
mainView,
|
|
4440
|
+
frameTextureA,
|
|
4441
|
+
frameTextureB,
|
|
4442
|
+
frameSampler,
|
|
4443
|
+
framePipeline,
|
|
4444
|
+
frameBindGroup,
|
|
4413
4445
|
colorIndex = 1,
|
|
4414
4446
|
colorStackIndex = 8;
|
|
4415
4447
|
|
|
@@ -4458,14 +4490,60 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
4458
4490
|
});
|
|
4459
4491
|
|
|
4460
4492
|
let createMainView = () => {
|
|
4493
|
+
let w = $.canvas.width,
|
|
4494
|
+
h = $.canvas.height,
|
|
4495
|
+
size = [w, h],
|
|
4496
|
+
format = 'bgra8unorm';
|
|
4497
|
+
|
|
4461
4498
|
mainView = Q5.device
|
|
4462
4499
|
.createTexture({
|
|
4463
|
-
size
|
|
4500
|
+
size,
|
|
4464
4501
|
sampleCount: 4,
|
|
4465
|
-
format
|
|
4502
|
+
format,
|
|
4466
4503
|
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
4467
4504
|
})
|
|
4468
4505
|
.createView();
|
|
4506
|
+
|
|
4507
|
+
let usage =
|
|
4508
|
+
GPUTextureUsage.COPY_SRC |
|
|
4509
|
+
GPUTextureUsage.COPY_DST |
|
|
4510
|
+
GPUTextureUsage.TEXTURE_BINDING |
|
|
4511
|
+
GPUTextureUsage.RENDER_ATTACHMENT;
|
|
4512
|
+
|
|
4513
|
+
frameTextureA = Q5.device.createTexture({ size, format, usage });
|
|
4514
|
+
frameTextureB = Q5.device.createTexture({ size, format, usage });
|
|
4515
|
+
|
|
4516
|
+
let finalShader = Q5.device.createShaderModule({
|
|
4517
|
+
code: `
|
|
4518
|
+
@vertex fn v(@builtin(vertex_index)i:u32)->@builtin(position)vec4<f32>{
|
|
4519
|
+
const pos=array(vec2(-1f,-1f),vec2(1f,-1f),vec2(-1f,1f),vec2(1f,1f));
|
|
4520
|
+
return vec4(pos[i],0f,1f);
|
|
4521
|
+
}
|
|
4522
|
+
@group(0) @binding(0) var s: sampler;
|
|
4523
|
+
@group(0) @binding(1) var t: texture_2d<f32>;
|
|
4524
|
+
@fragment fn f(@builtin(position)c:vec4<f32>)->@location(0)vec4<f32>{
|
|
4525
|
+
let uv=c.xy/vec2(${w}, ${h});
|
|
4526
|
+
return textureSample(t,s,uv);
|
|
4527
|
+
}`
|
|
4528
|
+
});
|
|
4529
|
+
|
|
4530
|
+
frameSampler = Q5.device.createSampler({
|
|
4531
|
+
magFilter: 'linear',
|
|
4532
|
+
minFilter: 'linear'
|
|
4533
|
+
});
|
|
4534
|
+
|
|
4535
|
+
// Create a pipeline for rendering
|
|
4536
|
+
framePipeline = Q5.device.createRenderPipeline({
|
|
4537
|
+
layout: 'auto',
|
|
4538
|
+
vertex: { module: finalShader, entryPoint: 'v' },
|
|
4539
|
+
fragment: {
|
|
4540
|
+
module: finalShader,
|
|
4541
|
+
entryPoint: 'f',
|
|
4542
|
+
targets: [{ format, writeMask: GPUColorWrite.ALL }]
|
|
4543
|
+
},
|
|
4544
|
+
primitive: { topology: 'triangle-strip' },
|
|
4545
|
+
multisample: { count: 4 }
|
|
4546
|
+
});
|
|
4469
4547
|
};
|
|
4470
4548
|
|
|
4471
4549
|
$._createCanvas = (w, h, opt) => {
|
|
@@ -4831,23 +4909,52 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
4831
4909
|
}
|
|
4832
4910
|
};
|
|
4833
4911
|
|
|
4834
|
-
|
|
4912
|
+
let shouldClear = false;
|
|
4913
|
+
$.clear = () => {
|
|
4914
|
+
shouldClear = true;
|
|
4915
|
+
};
|
|
4916
|
+
|
|
4917
|
+
const _drawFrame = () => {
|
|
4918
|
+
pass.setPipeline(framePipeline);
|
|
4919
|
+
pass.setBindGroup(0, frameBindGroup);
|
|
4920
|
+
pass.draw(4);
|
|
4921
|
+
};
|
|
4835
4922
|
|
|
4836
4923
|
$._beginRender = () => {
|
|
4924
|
+
// swap the frame textures
|
|
4925
|
+
const temp = frameTextureA;
|
|
4926
|
+
frameTextureA = frameTextureB;
|
|
4927
|
+
frameTextureB = temp;
|
|
4928
|
+
$.canvas.texture = frameTextureA;
|
|
4929
|
+
|
|
4837
4930
|
$.encoder = Q5.device.createCommandEncoder();
|
|
4838
4931
|
|
|
4932
|
+
let target = shouldClear ? $.ctx.getCurrentTexture().createView() : frameTextureA.createView();
|
|
4933
|
+
|
|
4839
4934
|
pass = q.pass = $.encoder.beginRenderPass({
|
|
4840
4935
|
label: 'q5-webgpu',
|
|
4841
4936
|
colorAttachments: [
|
|
4842
4937
|
{
|
|
4843
4938
|
view: mainView,
|
|
4844
|
-
resolveTarget:
|
|
4939
|
+
resolveTarget: target,
|
|
4845
4940
|
loadOp: 'clear',
|
|
4846
4941
|
storeOp: 'store',
|
|
4847
4942
|
clearValue: [0, 0, 0, 0]
|
|
4848
4943
|
}
|
|
4849
4944
|
]
|
|
4850
4945
|
});
|
|
4946
|
+
|
|
4947
|
+
if (!shouldClear) {
|
|
4948
|
+
frameBindGroup = Q5.device.createBindGroup({
|
|
4949
|
+
layout: framePipeline.getBindGroupLayout(0),
|
|
4950
|
+
entries: [
|
|
4951
|
+
{ binding: 0, resource: frameSampler },
|
|
4952
|
+
{ binding: 1, resource: frameTextureB.createView() }
|
|
4953
|
+
]
|
|
4954
|
+
});
|
|
4955
|
+
|
|
4956
|
+
_drawFrame();
|
|
4957
|
+
}
|
|
4851
4958
|
};
|
|
4852
4959
|
|
|
4853
4960
|
$._render = () => {
|
|
@@ -4922,8 +5029,33 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
4922
5029
|
|
|
4923
5030
|
$._finishRender = () => {
|
|
4924
5031
|
pass.end();
|
|
4925
|
-
|
|
4926
|
-
|
|
5032
|
+
|
|
5033
|
+
if (!shouldClear) {
|
|
5034
|
+
pass = $.encoder.beginRenderPass({
|
|
5035
|
+
colorAttachments: [
|
|
5036
|
+
{
|
|
5037
|
+
view: mainView,
|
|
5038
|
+
resolveTarget: $.ctx.getCurrentTexture().createView(),
|
|
5039
|
+
loadOp: 'clear',
|
|
5040
|
+
storeOp: 'store',
|
|
5041
|
+
clearValue: [0, 0, 0, 0]
|
|
5042
|
+
}
|
|
5043
|
+
]
|
|
5044
|
+
});
|
|
5045
|
+
|
|
5046
|
+
frameBindGroup = Q5.device.createBindGroup({
|
|
5047
|
+
layout: framePipeline.getBindGroupLayout(0),
|
|
5048
|
+
entries: [
|
|
5049
|
+
{ binding: 0, resource: frameSampler },
|
|
5050
|
+
{ binding: 1, resource: frameTextureA.createView() }
|
|
5051
|
+
]
|
|
5052
|
+
});
|
|
5053
|
+
_drawFrame();
|
|
5054
|
+
pass.end();
|
|
5055
|
+
shouldClear = false;
|
|
5056
|
+
}
|
|
5057
|
+
|
|
5058
|
+
Q5.device.queue.submit([$.encoder.finish()]);
|
|
4927
5059
|
|
|
4928
5060
|
q.pass = $.encoder = null;
|
|
4929
5061
|
|
|
@@ -5718,12 +5850,10 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
|
|
|
5718
5850
|
|
|
5719
5851
|
$.smooth();
|
|
5720
5852
|
|
|
5721
|
-
let MAX_TEXTURES = 12000;
|
|
5722
|
-
|
|
5723
|
-
$._textures = [];
|
|
5724
5853
|
let tIdx = 0;
|
|
5725
5854
|
|
|
5726
5855
|
$._createTexture = (img) => {
|
|
5856
|
+
let g = img;
|
|
5727
5857
|
if (img.canvas) img = img.canvas;
|
|
5728
5858
|
|
|
5729
5859
|
let textureSize = [img.width, img.height, 1];
|
|
@@ -5743,26 +5873,18 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
|
|
|
5743
5873
|
textureSize
|
|
5744
5874
|
);
|
|
5745
5875
|
|
|
5746
|
-
|
|
5747
|
-
|
|
5876
|
+
g.texture = texture;
|
|
5877
|
+
g.textureIndex = tIdx;
|
|
5748
5878
|
|
|
5749
|
-
|
|
5879
|
+
$._textureBindGroups[tIdx] = Q5.device.createBindGroup({
|
|
5750
5880
|
layout: textureLayout,
|
|
5751
5881
|
entries: [
|
|
5752
5882
|
{ binding: 0, resource: sampler },
|
|
5753
5883
|
{ binding: 1, resource: texture.createView() }
|
|
5754
5884
|
]
|
|
5755
5885
|
});
|
|
5756
|
-
$._textureBindGroups[tIdx] = textureBindGroup;
|
|
5757
|
-
|
|
5758
|
-
tIdx = (tIdx + 1) % MAX_TEXTURES;
|
|
5759
5886
|
|
|
5760
|
-
|
|
5761
|
-
if ($._textures[tIdx]) {
|
|
5762
|
-
$._textures[tIdx].destroy();
|
|
5763
|
-
delete $._textures[tIdx];
|
|
5764
|
-
delete $._textureBindGroups[tIdx];
|
|
5765
|
-
}
|
|
5887
|
+
tIdx++;
|
|
5766
5888
|
};
|
|
5767
5889
|
|
|
5768
5890
|
$.loadImage = (src, cb) => {
|
|
@@ -5793,23 +5915,31 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
|
|
|
5793
5915
|
};
|
|
5794
5916
|
|
|
5795
5917
|
$.image = (img, dx = 0, dy = 0, dw, dh, sx = 0, sy = 0, sw, sh) => {
|
|
5796
|
-
let g = img;
|
|
5797
|
-
if (img.canvas) img = img.canvas;
|
|
5798
5918
|
if (img.textureIndex == undefined) return;
|
|
5799
5919
|
|
|
5920
|
+
let cnv = img.canvas || img;
|
|
5921
|
+
|
|
5800
5922
|
if ($._matrixDirty) $._saveMatrix();
|
|
5801
5923
|
|
|
5802
|
-
let w =
|
|
5803
|
-
h =
|
|
5804
|
-
pd =
|
|
5924
|
+
let w = cnv.width,
|
|
5925
|
+
h = cnv.height,
|
|
5926
|
+
pd = img._pixelDensity || 1;
|
|
5805
5927
|
|
|
5806
|
-
|
|
5807
|
-
|
|
5928
|
+
if (img.modified) {
|
|
5929
|
+
Q5.device.queue.copyExternalImageToTexture(
|
|
5930
|
+
{ source: cnv },
|
|
5931
|
+
{ texture: img.texture, colorSpace: $.canvas.colorSpace },
|
|
5932
|
+
[w, h, 1]
|
|
5933
|
+
);
|
|
5934
|
+
img.modified = false;
|
|
5935
|
+
}
|
|
5936
|
+
|
|
5937
|
+
dw ??= img.defaultWidth;
|
|
5938
|
+
dh ??= img.defaultHeight;
|
|
5808
5939
|
sw ??= w;
|
|
5809
5940
|
sh ??= h;
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
dh *= pd;
|
|
5941
|
+
sx *= pd;
|
|
5942
|
+
sy *= pd;
|
|
5813
5943
|
|
|
5814
5944
|
let [l, r, t, b] = $._calcBox(dx, dy, dw, dh, $._imageMode);
|
|
5815
5945
|
|
|
@@ -5829,8 +5959,55 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
|
|
|
5829
5959
|
$.drawStack.push(1, img.textureIndex);
|
|
5830
5960
|
};
|
|
5831
5961
|
|
|
5962
|
+
$._saveCanvas = async (data, ext) => {
|
|
5963
|
+
let texture = data.texture,
|
|
5964
|
+
w = texture.width,
|
|
5965
|
+
h = texture.height,
|
|
5966
|
+
bytesPerRow = Math.ceil((w * 4) / 256) * 256;
|
|
5967
|
+
|
|
5968
|
+
let buffer = Q5.device.createBuffer({
|
|
5969
|
+
size: bytesPerRow * h,
|
|
5970
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
5971
|
+
});
|
|
5972
|
+
|
|
5973
|
+
let en = Q5.device.createCommandEncoder();
|
|
5974
|
+
|
|
5975
|
+
en.copyTextureToBuffer({ texture }, { buffer, bytesPerRow, rowsPerImage: h }, { width: w, height: h });
|
|
5976
|
+
|
|
5977
|
+
Q5.device.queue.submit([en.finish()]);
|
|
5978
|
+
|
|
5979
|
+
await buffer.mapAsync(GPUMapMode.READ);
|
|
5980
|
+
|
|
5981
|
+
let pad = new Uint8Array(buffer.getMappedRange());
|
|
5982
|
+
data = new Uint8Array(w * h * 4); // unpadded data
|
|
5983
|
+
|
|
5984
|
+
// Remove padding from each row
|
|
5985
|
+
for (let y = 0; y < h; y++) {
|
|
5986
|
+
const p = y * bytesPerRow; // padded row offset
|
|
5987
|
+
const u = y * w * 4; // unpadded row offset
|
|
5988
|
+
data.set(pad.subarray(p, p + w * 4), u);
|
|
5989
|
+
}
|
|
5990
|
+
|
|
5991
|
+
buffer.unmap();
|
|
5992
|
+
|
|
5993
|
+
let colorSpace = $.canvas.colorSpace;
|
|
5994
|
+
data = new Uint8ClampedArray(data.buffer);
|
|
5995
|
+
data = new ImageData(data, w, h, { colorSpace });
|
|
5996
|
+
let cnv = new OffscreenCanvas(w, h);
|
|
5997
|
+
let ctx = cnv.getContext('2d', { colorSpace });
|
|
5998
|
+
ctx.putImageData(data, 0, 0);
|
|
5999
|
+
|
|
6000
|
+
// Convert to blob then data URL
|
|
6001
|
+
let blob = await cnv.convertToBlob({ type: 'image/' + ext });
|
|
6002
|
+
return await new Promise((resolve) => {
|
|
6003
|
+
let r = new FileReader();
|
|
6004
|
+
r.onloadend = () => resolve(r.result);
|
|
6005
|
+
r.readAsDataURL(blob);
|
|
6006
|
+
});
|
|
6007
|
+
};
|
|
6008
|
+
|
|
5832
6009
|
$._hooks.preRender.push(() => {
|
|
5833
|
-
if (
|
|
6010
|
+
if (!vertIndex) return;
|
|
5834
6011
|
|
|
5835
6012
|
// Switch to image pipeline
|
|
5836
6013
|
$.pass.setPipeline($._pipelines[1]);
|
|
@@ -6371,20 +6548,14 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
|
|
|
6371
6548
|
}
|
|
6372
6549
|
|
|
6373
6550
|
let img = $._g.createTextImage(str, w, h);
|
|
6374
|
-
|
|
6375
|
-
if (img.canvas.textureIndex == undefined) {
|
|
6551
|
+
if (img.textureIndex == undefined) {
|
|
6376
6552
|
$._createTexture(img);
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
{ source: cnv },
|
|
6384
|
-
{ texture, colorSpace: $.canvas.colorSpace },
|
|
6385
|
-
textureSize
|
|
6386
|
-
);
|
|
6387
|
-
img.modified = false;
|
|
6553
|
+
let _copy = img.copy;
|
|
6554
|
+
img.copy = function () {
|
|
6555
|
+
let copy = _copy();
|
|
6556
|
+
$._createTexture(copy);
|
|
6557
|
+
return copy;
|
|
6558
|
+
};
|
|
6388
6559
|
}
|
|
6389
6560
|
return img;
|
|
6390
6561
|
};
|