q5 2.0.17 → 2.2.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/q5.js CHANGED
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * q5.js
3
- * @version 2.0
3
+ * @version 2.2
4
4
  * @author quinton-ashley, Tezumie, and LingDong-
5
5
  * @license LGPL-3.0
6
6
  * @class Q5
7
7
  */
8
- function Q5(scope, parent) {
8
+ function Q5(scope, parent, renderer) {
9
9
  let $ = this;
10
10
  $._q5 = true;
11
- $._scope = scope;
12
11
  $._parent = parent;
12
+ $._renderer = renderer || 'q2d';
13
13
  $._preloadCount = 0;
14
14
 
15
15
  scope ??= 'global';
@@ -17,13 +17,14 @@ function Q5(scope, parent) {
17
17
  if (!(window.setup || window.draw)) return;
18
18
  scope = 'global';
19
19
  }
20
+ $._scope = scope;
20
21
  let globalScope;
21
22
  if (scope == 'global') {
22
23
  Q5._hasGlobal = $._isGlobal = true;
23
24
  globalScope = !Q5._nodejs ? window : global;
24
25
  }
25
26
 
26
- let p = new Proxy($, {
27
+ let q = new Proxy($, {
27
28
  set: (t, p, v) => {
28
29
  $[p] = v;
29
30
  if ($._isGlobal) globalScope[p] = v;
@@ -41,6 +42,10 @@ function Q5(scope, parent) {
41
42
  $._targetFrameDuration = 16.666666666666668;
42
43
  $._frameRate = $._fps = 60;
43
44
  $._loop = true;
45
+ $._hooks = {
46
+ postCanvas: [],
47
+ preRender: []
48
+ };
44
49
 
45
50
  let millisStart = 0;
46
51
  $.millis = () => performance.now() - millisStart;
@@ -48,7 +53,7 @@ function Q5(scope, parent) {
48
53
  $.noCanvas = () => {
49
54
  if ($.canvas?.remove) $.canvas.remove();
50
55
  $.canvas = 0;
51
- p.ctx = p.drawingContext = 0;
56
+ q.ctx = q.drawingContext = 0;
52
57
  };
53
58
 
54
59
  if (window) {
@@ -57,10 +62,10 @@ function Q5(scope, parent) {
57
62
  $.deviceOrientation = window.screen?.orientation?.type;
58
63
  }
59
64
 
60
- $._incrementPreload = () => p._preloadCount++;
61
- $._decrementPreload = () => p._preloadCount--;
65
+ $._incrementPreload = () => q._preloadCount++;
66
+ $._decrementPreload = () => q._preloadCount--;
62
67
 
63
- function _draw(timestamp) {
68
+ $._draw = (timestamp) => {
64
69
  let ts = timestamp || performance.now();
65
70
  $._lastFrameTime ??= ts - $._targetFrameDuration;
66
71
 
@@ -69,44 +74,43 @@ function Q5(scope, parent) {
69
74
  $._shouldResize = false;
70
75
  }
71
76
 
72
- if ($._loop) looper = raf(_draw);
77
+ if ($._loop) looper = raf($._draw);
73
78
  else if ($.frameCount && !$._redraw) return;
74
79
 
75
80
  if (looper && $.frameCount) {
76
81
  let time_since_last = ts - $._lastFrameTime;
77
- if (time_since_last < $._targetFrameDuration - 1) return;
82
+ if (time_since_last < $._targetFrameDuration - 4) return;
78
83
  }
79
- p.deltaTime = ts - $._lastFrameTime;
84
+ q.deltaTime = ts - $._lastFrameTime;
80
85
  $._frameRate = 1000 / $.deltaTime;
81
- p.frameCount++;
86
+ q.frameCount++;
82
87
  let pre = performance.now();
83
- for (let m of Q5.prototype._methods.pre) m.call($);
84
- if ($.ctx) $.ctx.save();
88
+ if ($.ctx) $.resetMatrix();
89
+ if ($._beginRender) $._beginRender();
90
+ for (let m of Q5.methods.pre) m.call($);
85
91
  $.draw();
86
- for (let m of Q5.prototype._methods.post) m.call($);
87
- if ($.ctx) {
88
- $.ctx.restore();
89
- $.resetMatrix();
90
- }
91
- p.pmouseX = $.mouseX;
92
- p.pmouseY = $.mouseY;
92
+ if ($._render) $._render();
93
+ for (let m of Q5.methods.post) m.call($);
94
+ if ($._finishRender) $._finishRender();
95
+ q.pmouseX = $.mouseX;
96
+ q.pmouseY = $.mouseY;
93
97
  $._lastFrameTime = ts;
94
98
  let post = performance.now();
95
99
  $._fps = Math.round(1000 / (post - pre));
96
- }
100
+ };
97
101
  $.noLoop = () => {
98
102
  $._loop = false;
99
103
  looper = null;
100
104
  };
101
105
  $.loop = () => {
102
106
  $._loop = true;
103
- if (looper == null) _draw();
107
+ if (looper == null) $._draw();
104
108
  };
105
109
  $.isLooping = () => $._loop;
106
110
  $.redraw = (n = 1) => {
107
111
  $._redraw = true;
108
112
  for (let i = 0; i < n; i++) {
109
- _draw();
113
+ $._draw();
110
114
  }
111
115
  $._redraw = false;
112
116
  };
@@ -136,7 +140,12 @@ function Q5(scope, parent) {
136
140
  $.describe = () => {};
137
141
 
138
142
  for (let m in Q5.modules) {
139
- Q5.modules[m]($, p);
143
+ Q5.modules[m]($, q);
144
+ }
145
+
146
+ let r = Q5.renderers[$._renderer];
147
+ for (let m in r) {
148
+ r[m]($, q);
140
149
  }
141
150
 
142
151
  // INIT
@@ -147,12 +156,14 @@ function Q5(scope, parent) {
147
156
  }
148
157
  }
149
158
 
159
+ if (scope == 'graphics') return;
160
+
150
161
  if (scope == 'global') {
151
162
  Object.assign(Q5, $);
152
163
  delete Q5.Q5;
153
164
  }
154
165
 
155
- for (let m of Q5.prototype._methods.init) {
166
+ for (let m of Q5.methods.init) {
156
167
  m.call($);
157
168
  }
158
169
 
@@ -169,8 +180,6 @@ function Q5(scope, parent) {
169
180
 
170
181
  if (typeof scope == 'function') scope($);
171
182
 
172
- if (scope == 'graphics') return;
173
-
174
183
  Q5._instanceCount++;
175
184
 
176
185
  let raf =
@@ -218,8 +227,6 @@ function Q5(scope, parent) {
218
227
 
219
228
  if (!($.setup || $.draw)) return;
220
229
 
221
- $._startDone = false;
222
-
223
230
  async function _start() {
224
231
  $._startDone = true;
225
232
  if ($._preloadCount > 0) return raf(_start);
@@ -229,7 +236,7 @@ function Q5(scope, parent) {
229
236
  if ($.ctx === null) $.createCanvas(100, 100);
230
237
  $._setupDone = true;
231
238
  if ($.ctx) $.resetMatrix();
232
- raf(_draw);
239
+ raf($._draw);
233
240
  }
234
241
 
235
242
  if ((arguments.length && scope != 'namespace') || preloadDefined) {
@@ -243,6 +250,7 @@ function Q5(scope, parent) {
243
250
  }
244
251
  }
245
252
 
253
+ Q5.renderers = {};
246
254
  Q5.modules = {};
247
255
 
248
256
  Q5._nodejs = typeof process == 'object';
@@ -253,13 +261,13 @@ Q5._friendlyError = (msg, func) => {
253
261
  };
254
262
  Q5._validateParameters = () => true;
255
263
 
256
- Q5.prototype._methods = {
264
+ Q5.methods = {
257
265
  init: [],
258
266
  pre: [],
259
267
  post: [],
260
268
  remove: []
261
269
  };
262
- Q5.prototype.registerMethod = (m, fn) => Q5.prototype._methods[m].push(fn);
270
+ Q5.prototype.registerMethod = (m, fn) => Q5.methods[m].push(fn);
263
271
  Q5.prototype.registerPreloadMethod = (n, fn) => (Q5.prototype[n] = fn[n]);
264
272
 
265
273
  if (Q5._nodejs) global.p5 ??= global.Q5 = Q5;
@@ -271,7 +279,7 @@ if (typeof document == 'object') {
271
279
  if (!Q5._hasGlobal) new Q5('auto');
272
280
  });
273
281
  }
274
- Q5.modules.q2d_canvas = ($, p) => {
282
+ Q5.modules.canvas = ($, q) => {
275
283
  $._OffscreenCanvas =
276
284
  window.OffscreenCanvas ||
277
285
  function () {
@@ -280,59 +288,28 @@ Q5.modules.q2d_canvas = ($, p) => {
280
288
 
281
289
  if (Q5._nodejs) {
282
290
  if (Q5._createNodeJSCanvas) {
283
- p.canvas = Q5._createNodeJSCanvas(100, 100);
291
+ q.canvas = Q5._createNodeJSCanvas(100, 100);
284
292
  }
285
293
  } else if ($._scope == 'image' || $._scope == 'graphics') {
286
- p.canvas = new $._OffscreenCanvas(100, 100);
294
+ q.canvas = new $._OffscreenCanvas(100, 100);
287
295
  }
296
+
288
297
  if (!$.canvas) {
289
298
  if (typeof document == 'object') {
290
- p.canvas = document.createElement('canvas');
299
+ q.canvas = document.createElement('canvas');
291
300
  $.canvas.id = 'q5Canvas' + Q5._instanceCount;
292
301
  $.canvas.classList.add('q5Canvas');
293
302
  } else $.noCanvas();
294
303
  }
295
304
 
296
305
  let c = $.canvas;
297
-
298
306
  c.width = $.width = 100;
299
307
  c.height = $.height = 100;
300
-
301
- if (c && $._scope != 'graphics' && $._scope != 'image') {
302
- $._setupDone = false;
303
- let parent = $._parent;
304
- if (parent && typeof parent == 'string') {
305
- parent = document.getElementById(parent);
306
- }
307
- c.parent = (el) => {
308
- if (typeof el == 'string') el = document.getElementById(el);
309
- el.append(c);
310
-
311
- function parentResized() {
312
- if ($.frameCount > 1) {
313
- $._shouldResize = true;
314
- $._adjustDisplay();
315
- }
316
- }
317
- if (typeof ResizeObserver == 'function') {
318
- if ($._ro) $._ro.disconnect();
319
- $._ro = new ResizeObserver(parentResized);
320
- $._ro.observe(parent);
321
- } else if (!$.frameCount) {
322
- window.addEventListener('resize', parentResized);
323
- }
324
- };
325
- function appendCanvas() {
326
- parent ??= document.getElementsByTagName('main')[0];
327
- if (!parent) {
328
- parent = document.createElement('main');
329
- document.body.append(parent);
330
- }
331
- c.parent(parent);
332
- }
333
- if (document.body) appendCanvas();
334
- else document.addEventListener('DOMContentLoaded', appendCanvas);
308
+ if ($._scope != 'image') {
309
+ c.renderer = $._renderer;
310
+ c[$._renderer] = true;
335
311
  }
312
+ $._pixelDensity = 1;
336
313
 
337
314
  $._adjustDisplay = () => {
338
315
  if (c.style) {
@@ -341,25 +318,12 @@ Q5.modules.q2d_canvas = ($, p) => {
341
318
  }
342
319
  };
343
320
 
344
- $.createCanvas = function (w, h, renderer, options) {
345
- if (renderer == 'webgl') throw Error(`webgl renderer is not supported in q5, use '2d'`);
346
- if (typeof renderer == 'object') options = renderer;
347
- p.width = c.width = c.w = w || window.innerWidth;
348
- p.height = c.height = c.h = h || window.innerHeight;
349
- c.hw = w / 2;
350
- c.hh = h / 2;
351
- c.renderer = '2d';
321
+ $.createCanvas = function (w, h, options) {
322
+ options ??= arguments[3];
323
+
352
324
  let opt = Object.assign({}, Q5.canvasOptions);
353
- if (options) Object.assign(opt, options);
325
+ if (typeof options == 'object') Object.assign(opt, options);
354
326
 
355
- p.ctx = p.drawingContext = c.getContext('2d', opt);
356
- Object.assign(c, opt);
357
- if ($._colorMode == 'rgb') $.colorMode('rgb');
358
- if ($._scope != 'image') {
359
- $._defaultStyle();
360
- $._da = 0;
361
- }
362
- $.ctx.save();
363
327
  if ($._scope != 'image') {
364
328
  let pd = $.displayDensity();
365
329
  if ($._scope == 'graphics') pd = this._pixelDensity;
@@ -368,96 +332,226 @@ Q5.modules.q2d_canvas = ($, p) => {
368
332
  c.visible = e[0].isIntersecting;
369
333
  }).observe(c);
370
334
  }
371
- $.pixelDensity(Math.ceil(pd));
372
- } else this._pixelDensity = 1;
335
+ $._pixelDensity = Math.ceil(pd);
336
+ }
373
337
 
374
- if ($.displayMode) $.displayMode();
375
- else $._adjustDisplay();
376
- return c;
377
- };
378
- $._createCanvas = $.createCanvas;
338
+ $._setCanvasSize(w, h);
379
339
 
380
- if ($._scope == 'image') return;
340
+ Object.assign(c, opt);
341
+ let rend = $._createCanvas(c.w, c.h, opt);
381
342
 
382
- $._defaultStyle = () => {
383
- $.ctx.fillStyle = 'white';
384
- $.ctx.strokeStyle = 'black';
385
- $.ctx.lineCap = 'round';
386
- $.ctx.lineJoin = 'miter';
387
- $.ctx.textAlign = 'left';
343
+ if ($._hooks) {
344
+ for (let m of $._hooks.postCanvas) m();
345
+ }
346
+ return rend;
388
347
  };
389
348
 
390
- function cloneCtx() {
391
- let t = {};
392
- for (let prop in $.ctx) {
393
- if (typeof $.ctx[prop] != 'function') t[prop] = $.ctx[prop];
349
+ $._save = async (data, name, ext) => {
350
+ name = name || 'untitled';
351
+ ext = ext || 'png';
352
+ if (ext == 'jpg' || ext == 'png' || ext == 'webp') {
353
+ if (data instanceof OffscreenCanvas) {
354
+ const blob = await data.convertToBlob({ type: 'image/' + ext });
355
+ data = await new Promise((resolve) => {
356
+ const reader = new FileReader();
357
+ reader.onloadend = () => resolve(reader.result);
358
+ reader.readAsDataURL(blob);
359
+ });
360
+ } else {
361
+ data = data.toDataURL('image/' + ext);
362
+ }
363
+ } else {
364
+ let type = 'text/plain';
365
+ if (ext == 'json') {
366
+ if (typeof data != 'string') data = JSON.stringify(data);
367
+ type = 'text/json';
368
+ }
369
+ data = new Blob([data], { type });
370
+ data = URL.createObjectURL(data);
394
371
  }
395
- delete t.canvas;
396
- return t;
397
- }
372
+ let a = document.createElement('a');
373
+ a.href = data;
374
+ a.download = name + '.' + ext;
375
+ a.click();
376
+ URL.revokeObjectURL(a.href);
377
+ };
378
+ $.save = (a, b, c) => {
379
+ if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
380
+ c = b;
381
+ b = a;
382
+ a = $.canvas;
383
+ }
384
+ if (c) return $._save(a, b, c);
385
+ if (b) {
386
+ b = b.split('.');
387
+ $._save(a, b[0], b.at(-1));
388
+ } else $._save(a);
389
+ };
398
390
 
399
- function _resizeCanvas(w, h) {
391
+ $._setCanvasSize = (w, h) => {
400
392
  w ??= window.innerWidth;
401
393
  h ??= window.innerHeight;
402
-
403
- let t = cloneCtx();
404
- let o;
405
- if ($.frameCount) {
406
- o = new $._OffscreenCanvas(c.width, c.height);
407
- o.w = c.w;
408
- o.h = c.h;
409
- let oCtx = o.getContext('2d');
410
- oCtx.drawImage(c, 0, 0);
411
- }
412
-
413
- c.width = Math.ceil(w * $._pixelDensity);
414
- c.height = Math.ceil(h * $._pixelDensity);
415
- c.w = w;
416
- c.h = h;
394
+ c.w = w = Math.ceil(w);
395
+ c.h = h = Math.ceil(h);
417
396
  c.hw = w / 2;
418
397
  c.hh = h / 2;
419
- for (let prop in t) $.ctx[prop] = t[prop];
420
- $.ctx.scale($._pixelDensity, $._pixelDensity);
421
-
422
- if ($.frameCount) $.ctx.drawImage(o, 0, 0, o.w, o.h);
398
+ c.width = Math.ceil(w * $._pixelDensity);
399
+ c.height = Math.ceil(h * $._pixelDensity);
423
400
 
424
401
  if (!$._da) {
425
- p.width = w;
426
- p.height = h;
402
+ q.width = w;
403
+ q.height = h;
427
404
  } else $.flexibleCanvas($._dau);
428
405
 
429
- if ($.frameCount != 0) $._adjustDisplay();
406
+ if ($.displayMode && !c.displayMode) $.displayMode();
407
+ else $._adjustDisplay();
408
+ };
409
+
410
+ if ($._scope == 'image') return;
411
+
412
+ if (c && $._scope != 'graphics') {
413
+ c.parent = (el) => {
414
+ if (c.parentElement) c.parentElement.removeChild(c);
415
+
416
+ if (typeof el == 'string') el = document.getElementById(el);
417
+ el.append(c);
418
+
419
+ function parentResized() {
420
+ if ($.frameCount > 1) {
421
+ $._shouldResize = true;
422
+ $._adjustDisplay();
423
+ }
424
+ }
425
+ if (typeof ResizeObserver == 'function') {
426
+ if ($._ro) $._ro.disconnect();
427
+ $._ro = new ResizeObserver(parentResized);
428
+ $._ro.observe(el);
429
+ } else if (!$.frameCount) {
430
+ window.addEventListener('resize', parentResized);
431
+ }
432
+ };
433
+
434
+ function addCanvas() {
435
+ let el = $._parent;
436
+ el ??= document.getElementsByTagName('main')[0];
437
+ if (!el) {
438
+ el = document.createElement('main');
439
+ document.body.append(el);
440
+ }
441
+ c.parent(el);
442
+ }
443
+ if (document.body) addCanvas();
444
+ else document.addEventListener('DOMContentLoaded', addCanvas);
430
445
  }
431
446
 
432
447
  $.resizeCanvas = (w, h) => {
448
+ if (!$.ctx) return $.createCanvas(w, h);
433
449
  if (w == c.w && h == c.h) return;
434
- _resizeCanvas(w, h);
450
+
451
+ $._resizeCanvas(w, h);
435
452
  };
436
453
 
437
- $._pixelDensity = 1;
454
+ $.canvas.resize = $.resizeCanvas;
455
+ $.canvas.save = $.saveCanvas = $.save;
456
+
438
457
  $.displayDensity = () => window.devicePixelRatio;
439
458
  $.pixelDensity = (v) => {
440
459
  if (!v || v == $._pixelDensity) return $._pixelDensity;
441
460
  $._pixelDensity = v;
442
- _resizeCanvas(c.w, c.h);
461
+ $._setCanvasSize(c.w, c.h);
443
462
  return v;
444
463
  };
445
464
 
446
- if ($._scope == 'image') return;
447
-
448
- $.fullscreen = (v) => {
449
- if (v === undefined) return document.fullscreenElement;
450
- if (v) document.body.requestFullscreen();
451
- else document.body.exitFullscreen();
452
- };
453
-
454
465
  $.flexibleCanvas = (unit = 400) => {
455
466
  if (unit) {
456
467
  $._da = c.width / (unit * $._pixelDensity);
457
- p.width = $._dau = unit;
458
- p.height = (c.h / c.w) * unit;
468
+ q.width = $._dau = unit;
469
+ q.height = (c.h / c.w) * unit;
459
470
  } else $._da = 0;
460
471
  };
472
+ };
473
+
474
+ Q5.canvasOptions = {
475
+ alpha: false,
476
+ colorSpace: 'display-p3'
477
+ };
478
+
479
+ if (!window.matchMedia || !matchMedia('(dynamic-range: high) and (color-gamut: p3)').matches) {
480
+ Q5.canvasOptions.colorSpace = 'srgb';
481
+ } else Q5.supportsHDR = true;
482
+ Q5.renderers.q2d = {};
483
+
484
+ Q5.renderers.q2d.canvas = ($, q) => {
485
+ let c = $.canvas;
486
+
487
+ if ($.colorMode) $.colorMode('rgb', 'integer');
488
+
489
+ $._createCanvas = function (w, h, options) {
490
+ q.ctx = q.drawingContext = c.getContext('2d', options);
491
+
492
+ if ($._scope != 'image') {
493
+ // default styles
494
+ $.ctx.fillStyle = 'white';
495
+ $.ctx.strokeStyle = 'black';
496
+ $.ctx.lineCap = 'round';
497
+ $.ctx.lineJoin = 'miter';
498
+ $.ctx.textAlign = 'left';
499
+ }
500
+ $.ctx.scale($._pixelDensity, $._pixelDensity);
501
+ $.ctx.save();
502
+ return c;
503
+ };
504
+
505
+ if ($._scope == 'image') return;
506
+
507
+ $._resizeCanvas = (w, h) => {
508
+ let t = {};
509
+ for (let prop in $.ctx) {
510
+ if (typeof $.ctx[prop] != 'function') t[prop] = $.ctx[prop];
511
+ }
512
+ delete t.canvas;
513
+
514
+ let o = new $._OffscreenCanvas(c.width, c.height);
515
+ o.w = c.w;
516
+ o.h = c.h;
517
+ let oCtx = o.getContext('2d');
518
+ oCtx.drawImage(c, 0, 0);
519
+
520
+ $._setCanvasSize(w, h);
521
+
522
+ for (let prop in t) $.ctx[prop] = t[prop];
523
+ $.scale($._pixelDensity);
524
+ $.ctx.drawImage(o, 0, 0, o.w, o.h);
525
+ };
526
+
527
+ $.fill = function (c) {
528
+ $._doFill = true;
529
+ $._fillSet = true;
530
+ if (Q5.Color) {
531
+ if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
532
+ else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
533
+ if (c.a <= 0) return ($._doFill = false);
534
+ }
535
+ $.ctx.fillStyle = c.toString();
536
+ };
537
+ $.noFill = () => ($._doFill = false);
538
+ $.stroke = function (c) {
539
+ $._doStroke = true;
540
+ $._strokeSet = true;
541
+ if (Q5.Color) {
542
+ if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
543
+ else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
544
+ if (c.a <= 0) return ($._doStroke = false);
545
+ }
546
+ $.ctx.strokeStyle = c.toString();
547
+ };
548
+ $.strokeWeight = (n) => {
549
+ if (!n) $._doStroke = false;
550
+ if ($._da) n *= $._da;
551
+ $.ctx.lineWidth = n || 0.0001;
552
+ };
553
+ $.noStroke = () => ($._doStroke = false);
554
+ $.clear = () => $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
461
555
 
462
556
  // DRAWING MATRIX
463
557
 
@@ -482,7 +576,7 @@ Q5.modules.q2d_canvas = ($, p) => {
482
576
  $.shearY = (ang) => $.ctx.transform(1, $.tan(ang), 0, 1, 0, 0);
483
577
  $.resetMatrix = () => {
484
578
  $.ctx.resetTransform();
485
- $.ctx.scale($._pixelDensity, $._pixelDensity);
579
+ $.scale($._pixelDensity);
486
580
  };
487
581
 
488
582
  $._styleNames = [
@@ -536,29 +630,20 @@ Q5.modules.q2d_canvas = ($, p) => {
536
630
  opt ??= {};
537
631
  opt.alpha ??= true;
538
632
  opt.colorSpace ??= $.canvas.colorSpace;
539
- g._createCanvas.call($, w, h, opt);
633
+ g.createCanvas.call($, w, h, opt);
540
634
  return g;
541
635
  };
542
636
 
543
637
  if (window && $._scope != 'graphics') {
544
638
  window.addEventListener('resize', () => {
545
639
  $._shouldResize = true;
546
- p.windowWidth = window.innerWidth;
547
- p.windowHeight = window.innerHeight;
548
- p.deviceOrientation = window.screen?.orientation?.type;
640
+ q.windowWidth = window.innerWidth;
641
+ q.windowHeight = window.innerHeight;
642
+ q.deviceOrientation = window.screen?.orientation?.type;
549
643
  });
550
644
  }
551
645
  };
552
-
553
- Q5.canvasOptions = {
554
- alpha: false,
555
- colorSpace: 'display-p3'
556
- };
557
-
558
- if (!window.matchMedia || !matchMedia('(dynamic-range: high) and (color-gamut: p3)').matches) {
559
- Q5.canvasOptions.colorSpace = 'srgb';
560
- } else Q5.supportsHDR = true;
561
- Q5.modules.q2d_drawing = ($) => {
646
+ Q5.renderers.q2d.drawing = ($) => {
562
647
  $.CHORD = 0;
563
648
  $.PIE = 1;
564
649
  $.OPEN = 2;
@@ -620,33 +705,6 @@ Q5.modules.q2d_drawing = ($) => {
620
705
 
621
706
  // DRAWING SETTINGS
622
707
 
623
- $.strokeWeight = (n) => {
624
- if (!n) $._doStroke = false;
625
- if ($._da) n *= $._da;
626
- $.ctx.lineWidth = n || 0.0001;
627
- };
628
- $.stroke = function (c) {
629
- $._doStroke = true;
630
- $._strokeSet = true;
631
- if (Q5.Color) {
632
- if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
633
- else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
634
- if (c.a <= 0) return ($._doStroke = false);
635
- }
636
- $.ctx.strokeStyle = c.toString();
637
- };
638
- $.noStroke = () => ($._doStroke = false);
639
- $.fill = function (c) {
640
- $._doFill = true;
641
- $._fillSet = true;
642
- if (Q5.Color) {
643
- if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
644
- else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
645
- if (c.a <= 0) return ($._doFill = false);
646
- }
647
- $.ctx.fillStyle = c.toString();
648
- };
649
- $.noFill = () => ($._doFill = false);
650
708
  $.blendMode = (x) => ($.ctx.globalCompositeOperation = x);
651
709
  $.strokeCap = (x) => ($.ctx.lineCap = x);
652
710
  $.strokeJoin = (x) => ($.ctx.lineJoin = x);
@@ -658,10 +716,6 @@ Q5.modules.q2d_drawing = ($) => {
658
716
 
659
717
  // DRAWING
660
718
 
661
- $.clear = () => {
662
- $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
663
- };
664
-
665
719
  $.background = function (c) {
666
720
  if (c.canvas) return $.image(c, 0, 0, $.width, $.height);
667
721
  $.ctx.save();
@@ -819,18 +873,7 @@ Q5.modules.q2d_drawing = ($) => {
819
873
  bl *= $._da;
820
874
  br *= $._da;
821
875
  }
822
- const hh = Math.min(Math.abs(h), Math.abs(w)) / 2;
823
- tl = Math.min(hh, tl);
824
- tr = Math.min(hh, tr);
825
- bl = Math.min(hh, bl);
826
- br = Math.min(hh, br);
827
- $.ctx.beginPath();
828
- $.ctx.moveTo(x + tl, y);
829
- $.ctx.arcTo(x + w, y, x + w, y + h, tr);
830
- $.ctx.arcTo(x + w, y + h, x, y + h, br);
831
- $.ctx.arcTo(x, y + h, x, y, bl);
832
- $.ctx.arcTo(x, y, x + w, y, tl);
833
- $.ctx.closePath();
876
+ $.ctx.roundRect(x, y, w, h, [tl, tr, br, bl]);
834
877
  ink();
835
878
  }
836
879
 
@@ -849,22 +892,18 @@ Q5.modules.q2d_drawing = ($) => {
849
892
  return $.rect(x, y, s, s, tl, tr, br, bl);
850
893
  };
851
894
 
852
- function clearBuff() {
853
- curveBuff = [];
854
- }
855
-
856
895
  $.beginShape = () => {
857
- clearBuff();
896
+ curveBuff = [];
858
897
  $.ctx.beginPath();
859
898
  firstVertex = true;
860
899
  };
861
900
  $.beginContour = () => {
862
901
  $.ctx.closePath();
863
- clearBuff();
902
+ curveBuff = [];
864
903
  firstVertex = true;
865
904
  };
866
905
  $.endContour = () => {
867
- clearBuff();
906
+ curveBuff = [];
868
907
  firstVertex = true;
869
908
  };
870
909
  $.vertex = (x, y) => {
@@ -872,7 +911,7 @@ Q5.modules.q2d_drawing = ($) => {
872
911
  x *= $._da;
873
912
  y *= $._da;
874
913
  }
875
- clearBuff();
914
+ curveBuff = [];
876
915
  if (firstVertex) {
877
916
  $.ctx.moveTo(x, y);
878
917
  } else {
@@ -889,7 +928,7 @@ Q5.modules.q2d_drawing = ($) => {
889
928
  x *= $._da;
890
929
  y *= $._da;
891
930
  }
892
- clearBuff();
931
+ curveBuff = [];
893
932
  $.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
894
933
  };
895
934
  $.quadraticVertex = (cp1x, cp1y, x, y) => {
@@ -899,7 +938,7 @@ Q5.modules.q2d_drawing = ($) => {
899
938
  x *= $._da;
900
939
  y *= $._da;
901
940
  }
902
- clearBuff();
941
+ curveBuff = [];
903
942
  $.ctx.quadraticCurveTo(cp1x, cp1y, x, y);
904
943
  };
905
944
  $.bezier = (x1, y1, x2, y2, x3, y3, x4, y4) => {
@@ -924,7 +963,7 @@ Q5.modules.q2d_drawing = ($) => {
924
963
  $.endShape($.CLOSE);
925
964
  };
926
965
  $.endShape = (close) => {
927
- clearBuff();
966
+ curveBuff = [];
928
967
  if (close) $.ctx.closePath();
929
968
  ink();
930
969
  };
@@ -1076,7 +1115,32 @@ Q5.modules.q2d_drawing = ($) => {
1076
1115
  return $.ctx.isPointInStroke(x * pd, y * pd);
1077
1116
  };
1078
1117
  };
1079
- Q5.modules.q2d_image = ($, p) => {
1118
+ Q5.renderers.q2d.image = ($, q) => {
1119
+ class Q5Image {
1120
+ constructor(w, h, opt) {
1121
+ let $ = this;
1122
+ $._scope = 'image';
1123
+ $.canvas = $.ctx = $.drawingContext = null;
1124
+ $.pixels = [];
1125
+ Q5.modules.canvas($, $);
1126
+ let r = Q5.renderers.q2d;
1127
+ for (let m of ['canvas', 'image', 'soft_filters']) {
1128
+ if (r[m]) r[m]($, $);
1129
+ }
1130
+ $.createCanvas(w, h, opt);
1131
+ delete $.createCanvas;
1132
+ $._loop = false;
1133
+ }
1134
+ get w() {
1135
+ return this.width;
1136
+ }
1137
+ get h() {
1138
+ return this.height;
1139
+ }
1140
+ }
1141
+
1142
+ Q5.Image ??= Q5Image;
1143
+
1080
1144
  $.createImage = (w, h, opt) => {
1081
1145
  opt ??= {};
1082
1146
  opt.alpha ??= true;
@@ -1084,115 +1148,145 @@ Q5.modules.q2d_image = ($, p) => {
1084
1148
  return new Q5.Image(w, h, opt);
1085
1149
  };
1086
1150
 
1087
- $._tint = null;
1088
- let imgData = null;
1089
- let tmpCtx = null;
1090
- let tmpCt2 = null;
1091
-
1092
- function makeTmpCtx(w, h) {
1093
- h ??= w || $.canvas.height;
1094
- w ??= $.canvas.width;
1095
- if (tmpCtx == null) {
1096
- tmpCtx = new $._OffscreenCanvas(w, h).getContext('2d', {
1097
- colorSpace: $.canvas.colorSpace
1098
- });
1151
+ $.loadImage = function (url, cb, opt) {
1152
+ if (url.canvas) return url;
1153
+ if (url.slice(-3).toLowerCase() == 'gif') {
1154
+ throw new Error(`q5 doesn't support GIFs due to their impact on performance. Use a video or animation instead.`);
1155
+ }
1156
+ q._preloadCount++;
1157
+ let last = [...arguments].at(-1);
1158
+ opt = typeof last == 'object' ? last : null;
1159
+
1160
+ let g = $.createImage(1, 1, opt);
1161
+
1162
+ function loaded(img) {
1163
+ g.resize(img.naturalWidth || img.width, img.naturalHeight || img.height);
1164
+ g.ctx.drawImage(img, 0, 0);
1165
+ q._preloadCount--;
1166
+ if (cb) cb(g);
1099
1167
  }
1100
- if (tmpCtx.canvas.width != w || tmpCtx.canvas.height != h) {
1101
- tmpCtx.canvas.width = w;
1102
- tmpCtx.canvas.height = h;
1168
+
1169
+ if (Q5._nodejs && global.CairoCanvas) {
1170
+ global.CairoCanvas.loadImage(url)
1171
+ .then(loaded)
1172
+ .catch((e) => {
1173
+ q._preloadCount--;
1174
+ throw e;
1175
+ });
1176
+ } else {
1177
+ let img = new window.Image();
1178
+ img.src = url;
1179
+ img.crossOrigin = 'Anonymous';
1180
+ img._pixelDensity = 1;
1181
+ img.onload = () => loaded(img);
1182
+ img.onerror = (e) => {
1183
+ q._preloadCount--;
1184
+ throw e;
1185
+ };
1103
1186
  }
1104
- }
1187
+ return g;
1188
+ };
1105
1189
 
1106
- function makeTmpCt2(w, h) {
1107
- h ??= w || $.canvas.height;
1108
- w ??= $.canvas.width;
1109
- if (tmpCt2 == null) {
1110
- tmpCt2 = new $._OffscreenCanvas(w, h).getContext('2d', {
1111
- colorSpace: $.canvas.colorSpace
1112
- });
1190
+ $.imageMode = (mode) => ($._imageMode = mode);
1191
+ $.image = (img, dx, dy, dw, dh, sx = 0, sy = 0, sw, sh) => {
1192
+ let drawable = img.canvas || img;
1193
+ if (Q5._createNodeJSCanvas) {
1194
+ drawable = drawable.context.canvas;
1113
1195
  }
1114
- if (tmpCt2.canvas.width != w || tmpCt2.canvas.height != h) {
1115
- tmpCt2.canvas.width = w;
1116
- tmpCt2.canvas.height = h;
1196
+ dw ??= img.width || img.videoWidth;
1197
+ dh ??= img.height || img.videoHeight;
1198
+ if ($._imageMode == 'center') {
1199
+ dx -= dw * 0.5;
1200
+ dy -= dh * 0.5;
1117
1201
  }
1118
- }
1202
+ if ($._da) {
1203
+ dx *= $._da;
1204
+ dy *= $._da;
1205
+ dw *= $._da;
1206
+ dh *= $._da;
1207
+ sx *= $._da;
1208
+ sy *= $._da;
1209
+ sw *= $._da;
1210
+ sh *= $._da;
1211
+ }
1212
+ let pd = img._pixelDensity || 1;
1213
+ if (!sw) {
1214
+ sw = drawable.width || drawable.videoWidth;
1215
+ } else sw *= pd;
1216
+ if (!sh) {
1217
+ sh = drawable.height || drawable.videoHeight;
1218
+ } else sh *= pd;
1219
+ $.ctx.drawImage(drawable, sx * pd, sy * pd, sw, sh, dx, dy, dw, dh);
1220
+
1221
+ if ($._tint) {
1222
+ $.ctx.globalCompositeOperation = 'multiply';
1223
+ $.ctx.fillStyle = $._tint.toString();
1224
+ $.ctx.fillRect(dx, dy, dw, dh);
1225
+ $.ctx.globalCompositeOperation = 'source-over';
1226
+ }
1227
+ };
1228
+
1229
+ $._tint = null;
1230
+ let imgData = null;
1119
1231
 
1120
1232
  $._softFilter = () => {
1121
- throw 'Load q5-2d-soft-filters.js to use software filters.';
1233
+ throw new Error('Load q5-2d-soft-filters.js to use software filters.');
1122
1234
  };
1123
1235
 
1124
- function nativeFilter(filt) {
1125
- tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
1126
- tmpCtx.filter = filt;
1127
- tmpCtx.drawImage($.canvas, 0, 0);
1128
- $.ctx.save();
1129
- $.ctx.resetTransform();
1130
- $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
1131
- $.ctx.drawImage(tmpCtx.canvas, 0, 0);
1132
- $.ctx.restore();
1133
- }
1236
+ $.filter = (type, x) => {
1237
+ if (!$.ctx.filter) return $._softFilter(type, x);
1134
1238
 
1135
- $.filter = (typ, x) => {
1136
- if (!$.ctx.filter) return $._softFilter(typ, x);
1137
- makeTmpCtx();
1138
- if (typeof typ == 'string') {
1139
- nativeFilter(typ);
1140
- } else if (typ == Q5.THRESHOLD) {
1239
+ if (typeof type == 'string') f = type;
1240
+ else if (type == Q5.GRAY) f = `saturate(0%)`;
1241
+ else if (type == Q5.INVERT) f = `invert(100%)`;
1242
+ else if (type == Q5.BLUR) {
1243
+ let r = Math.ceil(x * $._pixelDensity) || 1;
1244
+ f = `blur(${r}px)`;
1245
+ } else if (type == Q5.THRESHOLD) {
1141
1246
  x ??= 0.5;
1142
- x = Math.max(x, 0.00001);
1143
- let b = Math.floor((0.5 / x) * 100);
1144
- nativeFilter(`saturate(0%) brightness(${b}%) contrast(1000000%)`);
1145
- } else if (typ == Q5.GRAY) {
1146
- nativeFilter(`saturate(0%)`);
1147
- } else if (typ == Q5.OPAQUE) {
1148
- tmpCtx.fillStyle = 'black';
1149
- tmpCtx.fillRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
1150
- tmpCtx.drawImage($.canvas, 0, 0);
1151
- $.ctx.save();
1152
- $.ctx.resetTransform();
1153
- $.ctx.drawImage(tmpCtx.canvas, 0, 0);
1154
- $.ctx.restore();
1155
- } else if (typ == Q5.INVERT) {
1156
- nativeFilter(`invert(100%)`);
1157
- } else if (typ == Q5.BLUR) {
1158
- nativeFilter(`blur(${Math.ceil((x * $._pixelDensity) / 1) || 1}px)`);
1159
- } else {
1160
- $._softFilter(typ, x);
1161
- }
1162
- };
1247
+ let b = Math.floor((0.5 / Math.max(x, 0.00001)) * 100);
1248
+ f = `saturate(0%) brightness(${b}%) contrast(1000000%)`;
1249
+ } else return $._softFilter(type, x);
1163
1250
 
1164
- $.resize = (w, h) => {
1165
- makeTmpCtx();
1166
- tmpCtx.drawImage($.canvas, 0, 0);
1167
- p.width = w;
1168
- p.height = h;
1169
- $.canvas.width = w * $._pixelDensity;
1170
- $.canvas.height = h * $._pixelDensity;
1171
- $.ctx.save();
1172
- $.ctx.resetTransform();
1173
- $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
1174
- $.ctx.drawImage(tmpCtx.canvas, 0, 0, $.canvas.width, $.canvas.height);
1175
- $.ctx.restore();
1251
+ $.ctx.filter = f;
1252
+ $.ctx.drawImage($.canvas, 0, 0, $.canvas.w, $.canvas.h);
1253
+ $.ctx.filter = 'none';
1176
1254
  };
1177
1255
 
1256
+ if ($._scope == 'image') {
1257
+ $.resize = (w, h) => {
1258
+ let o = new $._OffscreenCanvas($.canvas.width, $.canvas.height);
1259
+ let tmpCtx = o.getContext('2d', {
1260
+ colorSpace: $.canvas.colorSpace
1261
+ });
1262
+ tmpCtx.drawImage($.canvas, 0, 0);
1263
+ $._setCanvasSize(w, h);
1264
+
1265
+ $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
1266
+ $.ctx.drawImage(o, 0, 0, $.canvas.width, $.canvas.height);
1267
+ };
1268
+ }
1269
+
1178
1270
  $.trim = () => {
1179
1271
  let pd = $._pixelDensity || 1;
1180
- let imgData = $.ctx.getImageData(0, 0, $.width * pd, $.height * pd);
1181
- let data = imgData.data;
1182
- let left = $.width,
1272
+ let w = $.canvas.width;
1273
+ let h = $.canvas.height;
1274
+ let data = $.ctx.getImageData(0, 0, w, h).data;
1275
+ let left = w,
1183
1276
  right = 0,
1184
- top = $.height,
1277
+ top = h,
1185
1278
  bottom = 0;
1186
1279
 
1187
- for (let y = 0; y < $.height * pd; y++) {
1188
- for (let x = 0; x < $.width * pd; x++) {
1189
- let index = (y * $.width * pd + x) * 4;
1190
- if (data[index + 3] !== 0) {
1280
+ let i = 3;
1281
+ for (let y = 0; y < h; y++) {
1282
+ for (let x = 0; x < w; x++) {
1283
+ if (data[i] !== 0) {
1191
1284
  if (x < left) left = x;
1192
1285
  if (x > right) right = x;
1193
1286
  if (y < top) top = y;
1194
1287
  if (y > bottom) bottom = y;
1195
1288
  }
1289
+ i += 4;
1196
1290
  }
1197
1291
  }
1198
1292
  top = Math.floor(top / pd);
@@ -1213,48 +1307,6 @@ Q5.modules.q2d_image = ($, p) => {
1213
1307
  $.ctx.restore();
1214
1308
  };
1215
1309
 
1216
- $._save = async (data, name, ext) => {
1217
- name = name || 'untitled';
1218
- ext = ext || 'png';
1219
- if (ext == 'jpg' || ext == 'png' || ext == 'webp') {
1220
- if (data instanceof OffscreenCanvas) {
1221
- const blob = await data.convertToBlob({ type: 'image/' + ext });
1222
- data = await new Promise((resolve) => {
1223
- const reader = new FileReader();
1224
- reader.onloadend = () => resolve(reader.result);
1225
- reader.readAsDataURL(blob);
1226
- });
1227
- } else {
1228
- data = data.toDataURL('image/' + ext);
1229
- }
1230
- } else {
1231
- let type = 'text/plain';
1232
- if (ext == 'json') {
1233
- if (typeof data != 'string') data = JSON.stringify(data);
1234
- type = 'text/json';
1235
- }
1236
- data = new Blob([data], { type });
1237
- data = URL.createObjectURL(data);
1238
- }
1239
- let a = document.createElement('a');
1240
- a.href = data;
1241
- a.download = name + '.' + ext;
1242
- a.click();
1243
- URL.revokeObjectURL(a.href);
1244
- };
1245
- $.save = (a, b, c) => {
1246
- if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
1247
- c = b;
1248
- b = a;
1249
- a = $.canvas;
1250
- }
1251
- if (c) return $._save(a, b, c);
1252
- if (b) {
1253
- b = b.split('.');
1254
- $._save(a, b[0], b.at(-1));
1255
- } else $._save(a);
1256
- };
1257
-
1258
1310
  $.get = (x, y, w, h) => {
1259
1311
  let pd = $._pixelDensity || 1;
1260
1312
  if (x !== undefined && w === undefined) {
@@ -1299,174 +1351,23 @@ Q5.modules.q2d_image = ($, p) => {
1299
1351
 
1300
1352
  $.loadPixels = () => {
1301
1353
  imgData = $.ctx.getImageData(0, 0, $.canvas.width, $.canvas.height);
1302
- p.pixels = imgData.data;
1354
+ q.pixels = imgData.data;
1303
1355
  };
1304
1356
  $.updatePixels = () => {
1305
1357
  if (imgData != null) $.ctx.putImageData(imgData, 0, 0);
1306
1358
  };
1307
1359
 
1308
- $._tinted = function (col) {
1309
- let alpha = col.a;
1310
- col.a = 255;
1311
- makeTmpCtx();
1312
- tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
1313
- tmpCtx.fillStyle = col.toString();
1314
- tmpCtx.fillRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
1315
- tmpCtx.globalCompositeOperation = 'multiply';
1316
- tmpCtx.drawImage($.ctx.canvas, 0, 0);
1317
- tmpCtx.globalCompositeOperation = 'source-over';
1318
-
1319
- $.ctx.save();
1320
- $.ctx.resetTransform();
1321
- let old = $.ctx.globalCompositeOperation;
1322
- $.ctx.globalCompositeOperation = 'source-in';
1323
- $.ctx.drawImage(tmpCtx.canvas, 0, 0);
1324
- $.ctx.globalCompositeOperation = old;
1325
- $.ctx.restore();
1326
-
1327
- tmpCtx.globalAlpha = alpha / 255;
1328
- tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
1329
- tmpCtx.drawImage($.ctx.canvas, 0, 0);
1330
- tmpCtx.globalAlpha = 1;
1331
-
1332
- $.ctx.save();
1333
- $.ctx.resetTransform();
1334
- $.ctx.clearRect(0, 0, $.ctx.canvas.width, $.ctx.canvas.height);
1335
- $.ctx.drawImage(tmpCtx.canvas, 0, 0);
1336
- $.ctx.restore();
1337
- };
1338
-
1339
1360
  $.smooth = () => ($.ctx.imageSmoothingEnabled = true);
1340
1361
  $.noSmooth = () => ($.ctx.imageSmoothingEnabled = false);
1341
1362
 
1342
1363
  if ($._scope == 'image') return;
1343
1364
 
1344
- $.saveCanvas = $.canvas.save = $.save;
1345
-
1346
1365
  $.tint = function (c) {
1347
1366
  $._tint = c._q5Color ? c : $.color(...arguments);
1348
1367
  };
1349
1368
  $.noTint = () => ($._tint = null);
1350
-
1351
- // IMAGING
1352
-
1353
- $.imageMode = (mode) => ($._imageMode = mode);
1354
- $.image = (img, dx, dy, dWidth, dHeight, sx = 0, sy = 0, sWidth, sHeight) => {
1355
- if ($._da) {
1356
- dx *= $._da;
1357
- dy *= $._da;
1358
- dWidth *= $._da;
1359
- dHeight *= $._da;
1360
- sx *= $._da;
1361
- sy *= $._da;
1362
- sWidth *= $._da;
1363
- sHeight *= $._da;
1364
- }
1365
- let drawable = img.canvas || img;
1366
- if (Q5._createNodeJSCanvas) {
1367
- drawable = drawable.context.canvas;
1368
- }
1369
- function reset() {
1370
- if (!img._q5 || !$._tint) return;
1371
- let c = img.ctx;
1372
- c.save();
1373
- c.resetTransform();
1374
- c.clearRect(0, 0, c.canvas.width, c.canvas.height);
1375
- c.drawImage(tmpCt2.canvas, 0, 0);
1376
- c.restore();
1377
- }
1378
- if (img.canvas && $._tint != null) {
1379
- makeTmpCt2(img.canvas.width, img.canvas.height);
1380
- tmpCt2.drawImage(img.canvas, 0, 0);
1381
- img._tinted($._tint);
1382
- }
1383
- dWidth ??= img.width || img.videoWidth;
1384
- dHeight ??= img.height || img.videoHeight;
1385
- if ($._imageMode == 'center') {
1386
- dx -= dWidth * 0.5;
1387
- dy -= dHeight * 0.5;
1388
- }
1389
- let pd = img._pixelDensity || 1;
1390
- if (!sWidth) {
1391
- sWidth = drawable.width || drawable.videoWidth;
1392
- } else sWidth *= pd;
1393
- if (!sHeight) {
1394
- sHeight = drawable.height || drawable.videoHeight;
1395
- } else sHeight *= pd;
1396
- $.ctx.drawImage(drawable, sx * pd, sy * pd, sWidth, sHeight, dx, dy, dWidth, dHeight);
1397
- reset();
1398
- };
1399
-
1400
- $.loadImage = function (url, cb, opt) {
1401
- if (url.canvas) return url;
1402
- if (url.slice(-3).toLowerCase() == 'gif') {
1403
- throw new Error(`q5 doesn't support GIFs due to their impact on performance. Use a video or animation instead.`);
1404
- }
1405
- p._preloadCount++;
1406
- let last = [...arguments].at(-1);
1407
- opt = typeof last == 'object' ? last : null;
1408
-
1409
- let g = $.createImage(1, 1, opt);
1410
-
1411
- function loaded(img) {
1412
- let c = g.ctx;
1413
- g.width = c.canvas.width = img.naturalWidth || img.width;
1414
- g.height = c.canvas.height = img.naturalHeight || img.height;
1415
- c.drawImage(img, 0, 0);
1416
- p._preloadCount--;
1417
- if (cb) cb(g);
1418
- }
1419
-
1420
- if (Q5._nodejs && global.CairoCanvas) {
1421
- global.CairoCanvas.loadImage(url)
1422
- .then(loaded)
1423
- .catch((e) => {
1424
- p._preloadCount--;
1425
- throw e;
1426
- });
1427
- } else {
1428
- let img = new window.Image();
1429
- img.src = url;
1430
- img.crossOrigin = 'Anonymous';
1431
- img._pixelDensity = 1;
1432
- img.onload = () => loaded(img);
1433
- img.onerror = (e) => {
1434
- p._preloadCount--;
1435
- throw e;
1436
- };
1437
- }
1438
- return g;
1439
- };
1440
1369
  };
1441
1370
 
1442
- // IMAGE CLASS
1443
-
1444
- Q5.imageModules = ['q2d_canvas', 'q2d_image'];
1445
-
1446
- class _Q5Image {
1447
- constructor(w, h, opt) {
1448
- let $ = this;
1449
- $._scope = 'image';
1450
- $.canvas = $.ctx = $.drawingContext = null;
1451
- $.pixels = [];
1452
- for (let m of Q5.imageModules) {
1453
- Q5.modules[m]($, $);
1454
- }
1455
- delete this.createCanvas;
1456
-
1457
- this._createCanvas(w, h, '2d', opt);
1458
- this._loop = false;
1459
- }
1460
- get w() {
1461
- return this.width;
1462
- }
1463
- get h() {
1464
- return this.height;
1465
- }
1466
- }
1467
-
1468
- Q5.Image ??= _Q5Image;
1469
-
1470
1371
  Q5.THRESHOLD = 1;
1471
1372
  Q5.GRAY = 2;
1472
1373
  Q5.OPAQUE = 3;
@@ -1476,12 +1377,12 @@ Q5.DILATE = 6;
1476
1377
  Q5.ERODE = 7;
1477
1378
  Q5.BLUR = 8;
1478
1379
  /* software implementation of image filters */
1479
- Q5.modules.q2d_soft_filters = ($) => {
1380
+ Q5.renderers.q2d.soft_filters = ($) => {
1480
1381
  let tmpBuf = null;
1481
1382
 
1482
- function makeTmpBuf() {
1383
+ function ensureTmpBuf() {
1483
1384
  let l = $.canvas.width * $.canvas.height * 4;
1484
- if (!tmpBuf || l != tmpBuf.length) {
1385
+ if (!tmpBuf || tmpBuf.length != l) {
1485
1386
  tmpBuf = new Uint8ClampedArray(l);
1486
1387
  }
1487
1388
  }
@@ -1523,7 +1424,7 @@ Q5.modules.q2d_soft_filters = ($) => {
1523
1424
  }
1524
1425
  };
1525
1426
  $._filters[Q5.DILATE] = (data) => {
1526
- makeTmpBuf();
1427
+ ensureTmpBuf();
1527
1428
  tmpBuf.set(data);
1528
1429
  let [w, h] = [$.canvas.width, $.canvas.height];
1529
1430
  for (let i = 0; i < h; i++) {
@@ -1550,7 +1451,7 @@ Q5.modules.q2d_soft_filters = ($) => {
1550
1451
  }
1551
1452
  };
1552
1453
  $._filters[Q5.ERODE] = (data) => {
1553
- makeTmpBuf();
1454
+ ensureTmpBuf();
1554
1455
  tmpBuf.set(data);
1555
1456
  let [w, h] = [$.canvas.width, $.canvas.height];
1556
1457
  for (let i = 0; i < h; i++) {
@@ -1579,7 +1480,7 @@ Q5.modules.q2d_soft_filters = ($) => {
1579
1480
  $._filters[Q5.BLUR] = (data, rad) => {
1580
1481
  rad = rad || 1;
1581
1482
  rad = Math.floor(rad * $._pixelDensity);
1582
- makeTmpBuf();
1483
+ ensureTmpBuf();
1583
1484
  tmpBuf.set(data);
1584
1485
 
1585
1486
  let ksize = rad * 2 + 1;
@@ -1651,7 +1552,7 @@ Q5.modules.q2d_soft_filters = ($) => {
1651
1552
  $.ctx.putImageData(imgData, 0, 0);
1652
1553
  };
1653
1554
  };
1654
- Q5.modules.q2d_text = ($, p) => {
1555
+ Q5.renderers.q2d.text = ($, q) => {
1655
1556
  $.NORMAL = 'normal';
1656
1557
  $.ITALIC = 'italic';
1657
1558
  $.BOLD = 'bold';
@@ -1671,12 +1572,12 @@ Q5.modules.q2d_text = ($, p) => {
1671
1572
  $._textStyle = 'normal';
1672
1573
 
1673
1574
  $.loadFont = (url, cb) => {
1674
- p._preloadCount++;
1575
+ q._preloadCount++;
1675
1576
  let name = url.split('/').pop().split('.')[0].replace(' ', '');
1676
1577
  let f = new FontFace(name, `url(${url})`);
1677
1578
  document.fonts.add(f);
1678
1579
  f.load().then(() => {
1679
- p._preloadCount--;
1580
+ q._preloadCount--;
1680
1581
  if (cb) cb(name);
1681
1582
  });
1682
1583
  return name;
@@ -1865,8 +1766,8 @@ Q5.modules.q2d_text = ($, p) => {
1865
1766
  };
1866
1767
  };
1867
1768
  Q5.modules.ai = ($) => {
1868
- $.askAI = (q = '') => {
1869
- throw Error('Ask AI ✨ ' + q);
1769
+ $.askAI = (question = '') => {
1770
+ throw Error('Ask AI ✨ ' + question);
1870
1771
  };
1871
1772
 
1872
1773
  $._aiErrorAssistance = async (e) => {
@@ -1929,22 +1830,24 @@ Q5.modules.ai = ($) => {
1929
1830
  } catch (err) {}
1930
1831
  };
1931
1832
  };
1932
- Q5.modules.color = ($, p) => {
1833
+ Q5.modules.color = ($, q) => {
1933
1834
  $.RGB = $.RGBA = $._colorMode = 'rgb';
1934
1835
  $.OKLCH = 'oklch';
1935
1836
 
1936
- if (Q5.supportsHDR) $.Color = Q5.ColorRGBA_P3;
1937
- else $.Color = Q5.ColorRGBA;
1938
-
1939
- $.colorMode = (mode) => {
1837
+ $.colorMode = (mode, format) => {
1940
1838
  $._colorMode = mode;
1839
+ let srgb = $.canvas.colorSpace == 'srgb' || mode == 'srgb';
1840
+ format ??= srgb ? 'integer' : 'float';
1841
+ $._colorFormat = format == 'float' || format == 1 ? 1 : 255;
1941
1842
  if (mode == 'oklch') {
1942
- p.Color = Q5.ColorOKLCH;
1943
- } else if (mode == 'rgb') {
1944
- if ($.canvas.colorSpace == 'srgb') p.Color = Q5.ColorRGBA;
1945
- else p.Color = Q5.ColorRGBA_P3;
1946
- } else if (mode == 'srgb') {
1947
- p.Color = Q5.ColorRGBA;
1843
+ q.Color = Q5.ColorOKLCH;
1844
+ } else {
1845
+ let srgb = $.canvas.colorSpace == 'srgb';
1846
+ if ($._colorFormat == 255) {
1847
+ q.Color = srgb ? Q5.ColorRGBA_8 : Q5.ColorRGBA_P3_8;
1848
+ } else {
1849
+ q.Color = srgb ? Q5.ColorRGBA : Q5.ColorRGBA_P3;
1850
+ }
1948
1851
  $._colorMode = 'rgb';
1949
1852
  }
1950
1853
  };
@@ -1983,42 +1886,49 @@ Q5.modules.color = ($, p) => {
1983
1886
  yellow: [255, 255, 0]
1984
1887
  };
1985
1888
 
1986
- $.color = function (c0, c1, c2, c3) {
1889
+ $.color = (c0, c1, c2, c3) => {
1987
1890
  let C = $.Color;
1988
1891
  if (c0._q5Color) return new C(...c0.levels);
1989
- let args = arguments;
1990
- if (args.length == 1) {
1892
+ if (c1 == undefined) {
1991
1893
  if (typeof c0 == 'string') {
1992
1894
  if (c0[0] == '#') {
1993
1895
  if (c0.length <= 5) {
1994
- return new C(
1995
- parseInt(c0[1] + c0[1], 16),
1996
- parseInt(c0[2] + c0[2], 16),
1997
- parseInt(c0[3] + c0[3], 16),
1998
- c0.length == 4 ? null : parseInt(c0[4] + c0[4], 16)
1999
- );
1896
+ if (c0.length > 4) c3 = parseInt(c0[4] + c0[4], 16);
1897
+ c2 = parseInt(c0[3] + c0[3], 16);
1898
+ c1 = parseInt(c0[2] + c0[2], 16);
1899
+ c0 = parseInt(c0[1] + c0[1], 16);
1900
+ } else {
1901
+ if (c0.length > 7) c3 = parseInt(c0.slice(7, 9), 16);
1902
+ c2 = parseInt(c0.slice(5, 7), 16);
1903
+ c1 = parseInt(c0.slice(3, 5), 16);
1904
+ c0 = parseInt(c0.slice(1, 3), 16);
2000
1905
  }
2001
- return new C(
2002
- parseInt(c0.slice(1, 3), 16),
2003
- parseInt(c0.slice(3, 5), 16),
2004
- parseInt(c0.slice(5, 7), 16),
2005
- c0.length == 7 ? null : parseInt(c0.slice(7, 9), 16)
1906
+ } else if ($._namedColors[c0]) {
1907
+ c0 = $._namedColors[c0];
1908
+ } else {
1909
+ console.error(
1910
+ "q5 can't parse color: " + c0 + '\nOnly numeric input, hex, and common named colors are supported.'
2006
1911
  );
1912
+ return new C(0, 0, 0);
2007
1913
  }
2008
- if ($._namedColors[c0]) return new C(...$._namedColors[c0]);
2009
- console.error(
2010
- "q5 can't parse color: " + c0 + '\nOnly numeric input, hex, and common named colors are supported.'
2011
- );
2012
- return new C(0, 0, 0);
2013
1914
  }
2014
- if (Array.isArray(c0)) return new C(...c0);
1915
+ if (Array.isArray(c0)) {
1916
+ c1 = c0[1];
1917
+ c2 = c0[2];
1918
+ c3 = c0[3];
1919
+ c0 = c0[0];
1920
+ }
2015
1921
  }
2016
- if ($._colorMode == 'rgb') {
2017
- if (args.length == 1) return new C(c0, c0, c0);
2018
- if (args.length == 2) return new C(c0, c0, c0, c1);
1922
+
1923
+ if ($._colorFormat == 1) {
1924
+ c0 /= 255;
1925
+ if (c1) c1 /= 255;
1926
+ if (c2) c2 /= 255;
1927
+ if (c3) c3 /= 255;
2019
1928
  }
2020
- if (args.length == 3) return new C(c0, c1, c2);
2021
- if (args.length == 4) return new C(c0, c1, c2, c3);
1929
+
1930
+ if (c2 == undefined) return new C(c0, c0, c0, c1);
1931
+ return new C(c0, c1, c2, c3);
2022
1932
  };
2023
1933
 
2024
1934
  $.red = (c) => c.r;
@@ -2079,7 +1989,24 @@ Q5.ColorRGBA = class extends Q5.Color {
2079
1989
  this.r = r;
2080
1990
  this.g = g;
2081
1991
  this.b = b;
2082
- this.a = a ?? 255;
1992
+ this.a = a ?? 1;
1993
+ }
1994
+ get levels() {
1995
+ return [this.r, this.g, this.b, this.a];
1996
+ }
1997
+ toString() {
1998
+ return `color(srgb ${this.r} ${this.g} ${this.b} / ${this.a})`;
1999
+ }
2000
+ };
2001
+ Q5.ColorRGBA_P3 = class extends Q5.ColorRGBA {
2002
+ toString() {
2003
+ return `color(display-p3 ${this.r} ${this.g} ${this.b} / ${this.a})`;
2004
+ }
2005
+ };
2006
+ // legacy 8-bit (0-255) integer color format
2007
+ Q5.ColorRGBA_8 = class extends Q5.ColorRGBA {
2008
+ constructor(r, g, b, a) {
2009
+ super(r, g, b, a ?? 255);
2083
2010
  }
2084
2011
  setRed(v) {
2085
2012
  this.r = v;
@@ -2100,9 +2027,10 @@ Q5.ColorRGBA = class extends Q5.Color {
2100
2027
  return `rgb(${this.r} ${this.g} ${this.b} / ${this.a / 255})`;
2101
2028
  }
2102
2029
  };
2103
- Q5.ColorRGBA_P3 = class extends Q5.ColorRGBA {
2030
+ // p3 10-bit color in integer color format, for backwards compatibility
2031
+ Q5.ColorRGBA_P3_8 = class extends Q5.ColorRGBA {
2104
2032
  constructor(r, g, b, a) {
2105
- super(r, g, b, a);
2033
+ super(r, g, b, a ?? 255);
2106
2034
  this._edited = true;
2107
2035
  }
2108
2036
  get r() {
@@ -2231,8 +2159,14 @@ main {
2231
2159
  Object.assign(c, { displayMode, renderQuality, displayScale });
2232
2160
  $._adjustDisplay();
2233
2161
  };
2162
+
2163
+ $.fullscreen = (v) => {
2164
+ if (v === undefined) return document.fullscreenElement;
2165
+ if (v) document.body.requestFullscreen();
2166
+ else document.body.exitFullscreen();
2167
+ };
2234
2168
  };
2235
- Q5.modules.input = ($, p) => {
2169
+ Q5.modules.input = ($, q) => {
2236
2170
  if ($._scope == 'graphics') return;
2237
2171
 
2238
2172
  $.mouseX = 0;
@@ -2276,17 +2210,26 @@ Q5.modules.input = ($, p) => {
2276
2210
 
2277
2211
  $._updateMouse = (e) => {
2278
2212
  if (e.changedTouches) return;
2279
- let rect = $.canvas.getBoundingClientRect();
2280
- let sx = $.canvas.scrollWidth / $.width || 1;
2281
- let sy = $.canvas.scrollHeight / $.height || 1;
2282
- p.mouseX = (e.clientX - rect.left) / sx;
2283
- p.mouseY = (e.clientY - rect.top) / sy;
2213
+ if (c) {
2214
+ let rect = c.getBoundingClientRect();
2215
+ let sx = c.scrollWidth / $.width || 1;
2216
+ let sy = c.scrollHeight / $.height || 1;
2217
+ q.mouseX = (e.clientX - rect.left) / sx;
2218
+ q.mouseY = (e.clientY - rect.top) / sy;
2219
+ if (c.renderer == 'webgpu') {
2220
+ q.mouseX -= c.hw;
2221
+ q.mouseY -= c.hh;
2222
+ }
2223
+ } else {
2224
+ q.mouseX = e.clientX;
2225
+ q.mouseY = e.clientY;
2226
+ }
2284
2227
  };
2285
2228
  $._onmousedown = (e) => {
2286
2229
  $._startAudio();
2287
2230
  $._updateMouse(e);
2288
- p.mouseIsPressed = true;
2289
- p.mouseButton = mouseBtns[e.button];
2231
+ q.mouseIsPressed = true;
2232
+ q.mouseButton = mouseBtns[e.button];
2290
2233
  $.mousePressed(e);
2291
2234
  };
2292
2235
  $._onmousemove = (e) => {
@@ -2296,18 +2239,15 @@ Q5.modules.input = ($, p) => {
2296
2239
  };
2297
2240
  $._onmouseup = (e) => {
2298
2241
  $._updateMouse(e);
2299
- p.mouseIsPressed = false;
2242
+ q.mouseIsPressed = false;
2300
2243
  $.mouseReleased(e);
2301
2244
  };
2302
2245
  $._onclick = (e) => {
2303
2246
  $._updateMouse(e);
2304
- p.mouseIsPressed = true;
2247
+ q.mouseIsPressed = true;
2305
2248
  $.mouseClicked(e);
2306
- p.mouseIsPressed = false;
2249
+ q.mouseIsPressed = false;
2307
2250
  };
2308
- c.addEventListener('mousedown', (e) => $._onmousedown(e));
2309
- c.addEventListener('mouseup', (e) => $._onmouseup(e));
2310
- c.addEventListener('click', (e) => $._onclick(e));
2311
2251
 
2312
2252
  $.cursor = (name, x, y) => {
2313
2253
  let pfx = '';
@@ -2329,17 +2269,17 @@ Q5.modules.input = ($, p) => {
2329
2269
  $._onkeydown = (e) => {
2330
2270
  if (e.repeat) return;
2331
2271
  $._startAudio();
2332
- p.keyIsPressed = true;
2333
- p.key = e.key;
2334
- p.keyCode = e.keyCode;
2272
+ q.keyIsPressed = true;
2273
+ q.key = e.key;
2274
+ q.keyCode = e.keyCode;
2335
2275
  keysHeld[$.keyCode] = keysHeld[$.key.toLowerCase()] = true;
2336
2276
  $.keyPressed(e);
2337
2277
  if (e.key.length == 1) $.keyTyped(e);
2338
2278
  };
2339
2279
  $._onkeyup = (e) => {
2340
- p.keyIsPressed = false;
2341
- p.key = e.key;
2342
- p.keyCode = e.keyCode;
2280
+ q.keyIsPressed = false;
2281
+ q.key = e.key;
2282
+ q.keyCode = e.keyCode;
2343
2283
  keysHeld[$.keyCode] = keysHeld[$.key.toLowerCase()] = false;
2344
2284
  $.keyReleased(e);
2345
2285
  };
@@ -2357,37 +2297,43 @@ Q5.modules.input = ($, p) => {
2357
2297
  }
2358
2298
  $._ontouchstart = (e) => {
2359
2299
  $._startAudio();
2360
- p.touches = [...e.touches].map(getTouchInfo);
2300
+ q.touches = [...e.touches].map(getTouchInfo);
2361
2301
  if (!$._isTouchAware) {
2362
- p.mouseX = $.touches[0].x;
2363
- p.mouseY = $.touches[0].y;
2364
- p.mouseIsPressed = true;
2365
- p.mouseButton = $.LEFT;
2302
+ q.mouseX = $.touches[0].x;
2303
+ q.mouseY = $.touches[0].y;
2304
+ q.mouseIsPressed = true;
2305
+ q.mouseButton = $.LEFT;
2366
2306
  if (!$.mousePressed(e)) e.preventDefault();
2367
2307
  }
2368
2308
  if (!$.touchStarted(e)) e.preventDefault();
2369
2309
  };
2370
2310
  $._ontouchmove = (e) => {
2371
- p.touches = [...e.touches].map(getTouchInfo);
2311
+ q.touches = [...e.touches].map(getTouchInfo);
2372
2312
  if (!$._isTouchAware) {
2373
- p.mouseX = $.touches[0].x;
2374
- p.mouseY = $.touches[0].y;
2313
+ q.mouseX = $.touches[0].x;
2314
+ q.mouseY = $.touches[0].y;
2375
2315
  if (!$.mouseDragged(e)) e.preventDefault();
2376
2316
  }
2377
2317
  if (!$.touchMoved(e)) e.preventDefault();
2378
2318
  };
2379
2319
  $._ontouchend = (e) => {
2380
- p.touches = [...e.touches].map(getTouchInfo);
2320
+ q.touches = [...e.touches].map(getTouchInfo);
2381
2321
  if (!$._isTouchAware && !$.touches.length) {
2382
- p.mouseIsPressed = false;
2322
+ q.mouseIsPressed = false;
2383
2323
  if (!$.mouseReleased(e)) e.preventDefault();
2384
2324
  }
2385
2325
  if (!$.touchEnded(e)) e.preventDefault();
2386
2326
  };
2387
- c.addEventListener('touchstart', (e) => $._ontouchstart(e));
2388
- c.addEventListener('touchmove', (e) => $._ontouchmove(e));
2389
- c.addEventListener('touchcancel', (e) => $._ontouchend(e));
2390
- c.addEventListener('touchend', (e) => $._ontouchend(e));
2327
+
2328
+ if (c) {
2329
+ c.addEventListener('mousedown', (e) => $._onmousedown(e));
2330
+ c.addEventListener('mouseup', (e) => $._onmouseup(e));
2331
+ c.addEventListener('click', (e) => $._onclick(e));
2332
+ c.addEventListener('touchstart', (e) => $._ontouchstart(e));
2333
+ c.addEventListener('touchmove', (e) => $._ontouchmove(e));
2334
+ c.addEventListener('touchcancel', (e) => $._ontouchend(e));
2335
+ c.addEventListener('touchend', (e) => $._ontouchend(e));
2336
+ }
2391
2337
 
2392
2338
  if (window) {
2393
2339
  let l = window.addEventListener;
@@ -2396,7 +2342,7 @@ Q5.modules.input = ($, p) => {
2396
2342
  l('keyup', (e) => $._onkeyup(e), false);
2397
2343
  }
2398
2344
  };
2399
- Q5.modules.math = ($, p) => {
2345
+ Q5.modules.math = ($, q) => {
2400
2346
  $.DEGREES = 'degrees';
2401
2347
  $.RADIANS = 'radians';
2402
2348
 
@@ -2425,7 +2371,7 @@ Q5.modules.math = ($, p) => {
2425
2371
  $.degrees = (x) => x * $._RADTODEG;
2426
2372
  $.radians = (x) => x * $._DEGTORAD;
2427
2373
 
2428
- $.map = (value, istart, istop, ostart, ostop, clamp) => {
2374
+ $.map = Q5.prototype.map = (value, istart, istop, ostart, ostop, clamp) => {
2429
2375
  let val = ostart + (ostop - ostart) * (((value - istart) * 1.0) / (istop - istart));
2430
2376
  if (!clamp) {
2431
2377
  return val;
@@ -2683,7 +2629,7 @@ Q5.modules.math = ($, p) => {
2683
2629
  let _noise;
2684
2630
 
2685
2631
  $.noiseMode = (mode) => {
2686
- p.Noise = Q5[mode[0].toUpperCase() + mode.slice(1) + 'Noise'];
2632
+ q.Noise = Q5[mode[0].toUpperCase() + mode.slice(1) + 'Noise'];
2687
2633
  _noise = null;
2688
2634
  };
2689
2635
  $.noiseSeed = (seed) => {
@@ -2816,14 +2762,15 @@ Q5.PerlinNoise = class extends Q5.Noise {
2816
2762
  return (total / maxAmp + 1) / 2;
2817
2763
  }
2818
2764
  };
2819
- Q5.modules.sound = ($, p) => {
2765
+ Q5.modules.sound = ($, q) => {
2820
2766
  $.Sound = Q5.Sound;
2821
2767
  $.loadSound = (path, cb) => {
2822
- p._preloadCount++;
2768
+ q._preloadCount++;
2823
2769
  Q5.aud ??= new window.AudioContext();
2824
2770
  let a = new Q5.Sound(path, cb);
2825
2771
  a.addEventListener('canplaythrough', () => {
2826
- p._preloadCount--;
2772
+ q._preloadCount--;
2773
+ a.loaded = true;
2827
2774
  if (cb) cb(a);
2828
2775
  });
2829
2776
  return a;
@@ -2855,10 +2802,16 @@ Q5.Sound = class extends Audio {
2855
2802
  setPan(value) {
2856
2803
  this.pan = value;
2857
2804
  }
2805
+ isLoaded() {
2806
+ return this.loaded;
2807
+ }
2808
+ isPlaying() {
2809
+ return !this.paused;
2810
+ }
2858
2811
  };
2859
- Q5.modules.util = ($, p) => {
2812
+ Q5.modules.util = ($, q) => {
2860
2813
  $._loadFile = (path, cb, type) => {
2861
- p._preloadCount++;
2814
+ q._preloadCount++;
2862
2815
  let ret = {};
2863
2816
  fetch(path)
2864
2817
  .then((r) => {
@@ -2866,7 +2819,7 @@ Q5.modules.util = ($, p) => {
2866
2819
  if (type == 'text') return r.text();
2867
2820
  })
2868
2821
  .then((r) => {
2869
- p._preloadCount--;
2822
+ q._preloadCount--;
2870
2823
  Object.assign(ret, r);
2871
2824
  if (cb) cb(r);
2872
2825
  });
@@ -2911,7 +2864,7 @@ Q5.Vector = class {
2911
2864
  return new Q5.Vector(this.x, this.y, this.z);
2912
2865
  }
2913
2866
  _arg2v(x, y, z) {
2914
- if (x.x !== undefined) return x;
2867
+ if (x?.x !== undefined) return x;
2915
2868
  if (y !== undefined) {
2916
2869
  return { x, y, z: z || 0 };
2917
2870
  }