q5 2.4.4 → 2.4.10

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
@@ -554,12 +554,12 @@ Q5.modules.canvas = ($, q) => {
554
554
  ];
555
555
  $._styles = [];
556
556
 
557
- $._pushStyles = () => {
557
+ $.pushStyles = () => {
558
558
  let styles = {};
559
559
  for (let s of $._styleNames) styles[s] = $[s];
560
560
  $._styles.push(styles);
561
561
  };
562
- $._popStyles = () => {
562
+ $.popStyles = () => {
563
563
  let styles = $._styles.pop();
564
564
  for (let s of $._styleNames) $[s] = styles[s];
565
565
  };
@@ -596,6 +596,13 @@ Q5.renderers.q2d.canvas = ($, q) => {
596
596
  return c;
597
597
  };
598
598
 
599
+ $.clear = () => {
600
+ $.ctx.save();
601
+ $.ctx.resetTransform();
602
+ $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
603
+ $.ctx.restore();
604
+ };
605
+
599
606
  if ($._scope == 'image') return;
600
607
 
601
608
  $._resizeCanvas = (w, h) => {
@@ -605,17 +612,21 @@ Q5.renderers.q2d.canvas = ($, q) => {
605
612
  }
606
613
  delete t.canvas;
607
614
 
608
- let o = new $._OffscreenCanvas(c.width, c.height);
609
- o.w = c.w;
610
- o.h = c.h;
611
- let oCtx = o.getContext('2d');
612
- oCtx.drawImage(c, 0, 0);
615
+ let o;
616
+ if ($.frameCount > 1) {
617
+ o = new $._OffscreenCanvas(c.width, c.height);
618
+ o.w = c.w;
619
+ o.h = c.h;
620
+ let oCtx = o.getContext('2d');
621
+ oCtx.drawImage(c, 0, 0);
622
+ }
613
623
 
614
624
  $._setCanvasSize(w, h);
615
625
 
616
626
  for (let prop in t) $.ctx[prop] = t[prop];
617
627
  $.scale($._pixelDensity);
618
- $.ctx.drawImage(o, 0, 0, o.w, o.h);
628
+
629
+ if (o) $.ctx.drawImage(o, 0, 0, o.w, o.h);
619
630
  };
620
631
 
621
632
  $.fill = function (c) {
@@ -628,7 +639,7 @@ Q5.renderers.q2d.canvas = ($, q) => {
628
639
  }
629
640
  if (c.a <= 0) return ($._doFill = false);
630
641
  }
631
- $.ctx.fillStyle = c.toString();
642
+ $.ctx.fillStyle = $._fill = c.toString();
632
643
  };
633
644
  $.noFill = () => ($._doFill = false);
634
645
  $.stroke = function (c) {
@@ -641,20 +652,16 @@ Q5.renderers.q2d.canvas = ($, q) => {
641
652
  }
642
653
  if (c.a <= 0) return ($._doStroke = false);
643
654
  }
644
- $.ctx.strokeStyle = c.toString();
655
+ $.ctx.strokeStyle = $._stroke = c.toString();
645
656
  };
646
657
  $.strokeWeight = (n) => {
647
658
  if (!n) $._doStroke = false;
648
659
  if ($._da) n *= $._da;
649
- $.ctx.lineWidth = n || 0.0001;
660
+ $.ctx.lineWidth = $._strokeWeight = n || 0.0001;
650
661
  };
651
662
  $.noStroke = () => ($._doStroke = false);
652
- $.clear = () => {
653
- $.ctx.save();
654
- $.ctx.resetTransform();
655
- $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
656
- $.ctx.restore();
657
- };
663
+
664
+ $.opacity = (a) => ($.ctx.globalAlpha = a);
658
665
 
659
666
  // DRAWING MATRIX
660
667
 
@@ -673,7 +680,6 @@ Q5.renderers.q2d.canvas = ($, q) => {
673
680
  y ??= x;
674
681
  $.ctx.scale(x, y);
675
682
  };
676
- $.opacity = (a) => ($.ctx.globalAlpha = a);
677
683
  $.applyMatrix = (a, b, c, d, e, f) => $.ctx.transform(a, b, c, d, e, f);
678
684
  $.shearX = (ang) => $.ctx.transform(1, 0, $.tan(ang), 1, 0, 0);
679
685
  $.shearY = (ang) => $.ctx.transform(1, $.tan(ang), 0, 1, 0, 0);
@@ -682,13 +688,16 @@ Q5.renderers.q2d.canvas = ($, q) => {
682
688
  $.scale($._pixelDensity);
683
689
  };
684
690
 
685
- $.push = $.pushMatrix = () => {
691
+ $.pushMatrix = () => $.ctx.save();
692
+ $.popMatrix = () => $.ctx.restore();
693
+
694
+ $.push = () => {
686
695
  $.ctx.save();
687
- $._pushStyles();
696
+ $.pushStyles();
688
697
  };
689
- $.pop = $.popMatrix = () => {
698
+ $.pop = () => {
690
699
  $.ctx.restore();
691
- $._popStyles();
700
+ $.popStyles();
692
701
  };
693
702
 
694
703
  $.createCapture = (x) => {
@@ -1158,6 +1167,7 @@ Q5.renderers.q2d.image = ($, q) => {
1158
1167
  for (let m of ['canvas', 'image', 'soft_filters']) {
1159
1168
  if (r[m]) r[m]($, $);
1160
1169
  }
1170
+ $._pixelDensity = opt.pixelDensity || 1;
1161
1171
  $.createCanvas(w, h, opt);
1162
1172
  delete $.createCanvas;
1163
1173
  $._loop = false;
@@ -1412,11 +1422,23 @@ Q5.DILATE = 6;
1412
1422
  Q5.ERODE = 7;
1413
1423
  Q5.BLUR = 8;
1414
1424
  Q5.renderers.q2d.text = ($, q) => {
1415
- $._textFont = 'sans-serif';
1416
- $._textSize = 12;
1417
- $._textLeading = 15;
1418
- $._textLeadDiff = 3;
1419
- $._textStyle = 'normal';
1425
+ $._textAlign = 'left';
1426
+ $._textBaseline = 'alphabetic';
1427
+
1428
+ let font = 'sans-serif',
1429
+ tSize = 12,
1430
+ leading = 15,
1431
+ leadDiff = 3,
1432
+ emphasis = 'normal',
1433
+ fontMod = false,
1434
+ styleHash = 0,
1435
+ styleHashes = [],
1436
+ useCache = false,
1437
+ genTextImage = false,
1438
+ cacheSize = 0,
1439
+ cacheMax = 12000;
1440
+
1441
+ let cache = ($._textCache = {});
1420
1442
 
1421
1443
  $.loadFont = (url, cb) => {
1422
1444
  q._preloadCount++;
@@ -1429,180 +1451,214 @@ Q5.renderers.q2d.text = ($, q) => {
1429
1451
  });
1430
1452
  return name;
1431
1453
  };
1432
- $.textFont = (x) => ($._textFont = x);
1454
+
1455
+ $.textFont = (x) => {
1456
+ font = x;
1457
+ fontMod = true;
1458
+ styleHash = -1;
1459
+ };
1433
1460
  $.textSize = (x) => {
1434
- if (x === undefined) return $._textSize;
1461
+ if (x === undefined) return tSize;
1435
1462
  if ($._da) x *= $._da;
1436
- $._textSize = x;
1463
+ tSize = x;
1464
+ fontMod = true;
1465
+ styleHash = -1;
1437
1466
  if (!$._leadingSet) {
1438
- $._textLeading = x * 1.25;
1439
- $._textLeadDiff = $._textLeading - x;
1467
+ leading = x * 1.25;
1468
+ leadDiff = leading - x;
1440
1469
  }
1441
1470
  };
1471
+ $.textStyle = (x) => {
1472
+ emphasis = x;
1473
+ fontMod = true;
1474
+ styleHash = -1;
1475
+ };
1442
1476
  $.textLeading = (x) => {
1443
- if (x === undefined) return $._textLeading;
1477
+ if (x === undefined) return leading;
1444
1478
  if ($._da) x *= $._da;
1445
- $._textLeading = x;
1446
- $._textLeadDiff = x - $._textSize;
1479
+ leading = x;
1480
+ leadDiff = x - tSize;
1447
1481
  $._leadingSet = true;
1482
+ styleHash = -1;
1448
1483
  };
1449
- $.textStyle = (x) => ($._textStyle = x);
1450
1484
  $.textAlign = (horiz, vert) => {
1451
- $.ctx.textAlign = horiz;
1485
+ $.ctx.textAlign = $._textAlign = horiz;
1452
1486
  if (vert) {
1453
- $.ctx.textBaseline = vert == $.CENTER ? 'middle' : vert;
1487
+ $.ctx.textBaseline = $._textBaseline = vert == $.CENTER ? 'middle' : vert;
1454
1488
  }
1489
+ styleHash = -1;
1455
1490
  };
1456
- $.textWidth = (str) => {
1457
- $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
1458
- return $.ctx.measureText(str).width;
1459
- };
1460
- $.textAscent = (str) => {
1461
- $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
1462
- return $.ctx.measureText(str).actualBoundingBoxAscent;
1463
- };
1464
- $.textDescent = (str) => {
1465
- $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
1466
- return $.ctx.measureText(str).actualBoundingBoxDescent;
1467
- };
1491
+
1492
+ $.textWidth = (str) => $.ctx.measureText(str).width;
1493
+ $.textAscent = (str) => $.ctx.measureText(str).actualBoundingBoxAscent;
1494
+ $.textDescent = (str) => $.ctx.measureText(str).actualBoundingBoxDescent;
1495
+
1468
1496
  $.textFill = $.fill;
1469
1497
  $.textStroke = $.stroke;
1470
1498
 
1471
- $._textCache = !!Q5.Image;
1472
- $._TimedCache = class extends Map {
1473
- constructor() {
1474
- super();
1475
- this.maxSize = 500;
1476
- }
1477
- set(k, v) {
1478
- v.lastAccessed = Date.now();
1479
- super.set(k, v);
1480
- if (this.size > this.maxSize) this.gc();
1481
- }
1482
- get(k) {
1483
- const v = super.get(k);
1484
- if (v) v.lastAccessed = Date.now();
1485
- return v;
1486
- }
1487
- gc() {
1488
- let t = Infinity;
1489
- let oldest;
1490
- let i = 0;
1491
- for (const [k, v] of this.entries()) {
1492
- if (v.lastAccessed < t) {
1493
- t = v.lastAccessed;
1494
- oldest = i;
1495
- }
1496
- i++;
1497
- }
1498
- i = oldest;
1499
- for (const k of this.keys()) {
1500
- if (i == 0) {
1501
- oldest = k;
1502
- break;
1503
- }
1504
- i--;
1505
- }
1506
- this.delete(oldest);
1499
+ let updateStyleHash = () => {
1500
+ let styleString = font + tSize + emphasis + leading;
1501
+
1502
+ let hash = 5381;
1503
+ for (let i = 0; i < styleString.length; i++) {
1504
+ hash = (hash * 33) ^ styleString.charCodeAt(i);
1507
1505
  }
1506
+ styleHash = hash >>> 0;
1508
1507
  };
1509
- $._tic = new $._TimedCache();
1510
- $.textCache = (b, maxSize) => {
1511
- if (maxSize) $._tic.maxSize = maxSize;
1512
- if (b !== undefined) $._textCache = b;
1513
- return $._textCache;
1514
- };
1515
- $._genTextImageKey = (str, w, h) => {
1516
- return (
1517
- str.slice(0, 200) +
1518
- $._textStyle +
1519
- $._textSize +
1520
- $._textFont +
1521
- ($._doFill ? $.ctx.fillStyle : '') +
1522
- '_' +
1523
- ($._doStroke && $._strokeSet ? $.ctx.lineWidth + $.ctx.strokeStyle + '_' : '') +
1524
- (w || '') +
1525
- (h ? 'x' + h : '')
1526
- );
1508
+
1509
+ $.textCache = (enable, maxSize) => {
1510
+ if (maxSize) cacheMax = maxSize;
1511
+ if (enable !== undefined) useCache = enable;
1512
+ return useCache;
1527
1513
  };
1528
1514
  $.createTextImage = (str, w, h) => {
1529
- let k = $._genTextImageKey(str, w, h);
1530
- if ($._tic.get(k)) return $._tic.get(k);
1531
-
1532
- let og = $._textCache;
1533
- $._textCache = true;
1534
- $._genTextImage = true;
1535
- $.text(str, 0, 0, w, h);
1536
- $._genTextImage = false;
1537
- $._textCache = og;
1538
- return $._tic.get(k);
1515
+ genTextImage = true;
1516
+ img = $.text(str, 0, 0, w, h);
1517
+ genTextImage = false;
1518
+ return img;
1539
1519
  };
1520
+
1521
+ let lines = [];
1540
1522
  $.text = (str, x, y, w, h) => {
1541
- if (str === undefined) return;
1523
+ if (str === undefined || (!$._doFill && !$._doStroke)) return;
1542
1524
  str = str.toString();
1543
1525
  if ($._da) {
1544
1526
  x *= $._da;
1545
1527
  y *= $._da;
1546
1528
  }
1547
- if (!$._doFill && !$._doStroke) return;
1548
- let c, ti, tg, k, cX, cY, _ascent, _descent;
1549
- let t = $.ctx.getTransform();
1550
- let useCache = $._genTextImage || ($._textCache && (t.b != 0 || t.c != 0));
1551
- if (!useCache) {
1552
- c = $.ctx;
1553
- cX = x;
1554
- cY = y;
1529
+ let ctx = $.ctx;
1530
+ let img, tX, tY;
1531
+
1532
+ if (fontMod) {
1533
+ ctx.font = `${emphasis} ${tSize}px ${font}`;
1534
+ fontMod = false;
1535
+ }
1536
+
1537
+ if (useCache || genTextImage) {
1538
+ if (styleHash == -1) updateStyleHash();
1539
+
1540
+ img = cache[str];
1541
+ if (img) img = img[styleHash];
1542
+
1543
+ if (img) {
1544
+ if (img._fill == $._fill && img._stroke == $._stroke && img._strokeWeight == $._strokeWeight) {
1545
+ if (genTextImage) return img;
1546
+ return $.textImage(img, x, y);
1547
+ } else img.clear();
1548
+ }
1549
+ }
1550
+
1551
+ if (str.indexOf('\n') == -1) lines[0] = str;
1552
+ else lines = str.split('\n');
1553
+
1554
+ if (w) {
1555
+ let wrapped = [];
1556
+ for (let line of lines) {
1557
+ let i = 0;
1558
+
1559
+ while (i < line.length) {
1560
+ let max = i + w;
1561
+ if (max >= line.length) {
1562
+ wrapped.push(line.slice(i));
1563
+ break;
1564
+ }
1565
+ let end = line.lastIndexOf(' ', max);
1566
+ if (end === -1 || end < i) {
1567
+ end = max;
1568
+ }
1569
+ wrapped.push(line.slice(i, end));
1570
+ i = end;
1571
+ }
1572
+ }
1573
+ lines = wrapped;
1574
+ }
1575
+
1576
+ if (!useCache && !genTextImage) {
1577
+ tX = x;
1578
+ tY = y;
1555
1579
  } else {
1556
- k = $._genTextImageKey(str, w, h);
1557
- ti = $._tic.get(k);
1558
- if (ti && !$._genTextImage) {
1559
- $.textImage(ti, x, y);
1560
- return;
1580
+ tX = 0;
1581
+ tY = leading * lines.length;
1582
+
1583
+ if (!img) {
1584
+ let measure = ctx.measureText(' ');
1585
+ let ascent = measure.fontBoundingBoxAscent;
1586
+ let descent = measure.fontBoundingBoxDescent;
1587
+ h ??= tY + descent;
1588
+
1589
+ img = $.createImage.call($, Math.ceil(ctx.measureText(str).width), Math.ceil(h), {
1590
+ pixelDensity: $._pixelDensity
1591
+ });
1592
+
1593
+ img._ascent = ascent;
1594
+ img._descent = descent;
1595
+ img._top = descent + leadDiff;
1596
+ img._middle = img._top + ascent * 0.5;
1597
+ img._bottom = img._top + ascent;
1598
+ }
1599
+
1600
+ img._fill = $._fill;
1601
+ img._stroke = $._stroke;
1602
+ img._strokeWeight = $._strokeWeight;
1603
+ img.modified = true;
1604
+
1605
+ ctx = img.ctx;
1606
+
1607
+ ctx.font = $.ctx.font;
1608
+ ctx.fillStyle = $._fill;
1609
+ ctx.strokeStyle = $._stroke;
1610
+ ctx.lineWidth = $.ctx.lineWidth;
1611
+ }
1612
+
1613
+ let ogFill;
1614
+ if (!$._fillSet) {
1615
+ ogFill = ctx.fillStyle;
1616
+ ctx.fillStyle = 'black';
1617
+ }
1618
+
1619
+ for (let line of lines) {
1620
+ if ($._doStroke && $._strokeSet) ctx.strokeText(line, tX, tY);
1621
+ if ($._doFill) ctx.fillText(line, tX, tY);
1622
+ tY += leading;
1623
+ if (tY > h) break;
1624
+ }
1625
+ lines.length = 0;
1626
+
1627
+ if (!$._fillSet) ctx.fillStyle = ogFill;
1628
+
1629
+ if (useCache || genTextImage) {
1630
+ styleHashes.push(styleHash);
1631
+ (cache[str] ??= {})[styleHash] = img;
1632
+
1633
+ cacheSize++;
1634
+ if (cacheSize > cacheMax) {
1635
+ let half = Math.ceil(cacheSize / 2);
1636
+ let hashes = styleHashes.splice(0, half);
1637
+ for (let s in cache) {
1638
+ s = cache[s];
1639
+ for (let h of hashes) delete s[h];
1640
+ }
1641
+ cacheSize -= half;
1561
1642
  }
1562
- tg = $.createGraphics.call($, 1, 1);
1563
- c = tg.ctx;
1564
- }
1565
- c.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
1566
- let lines = str.split('\n');
1567
- if (useCache) {
1568
- cX = 0;
1569
- cY = $._textLeading * lines.length;
1570
- let m = c.measureText(' ');
1571
- _ascent = m.fontBoundingBoxAscent;
1572
- _descent = m.fontBoundingBoxDescent;
1573
- h ??= cY + _descent;
1574
- tg.resizeCanvas(Math.ceil(c.measureText(str).width), Math.ceil(h));
1575
-
1576
- c.fillStyle = $.ctx.fillStyle;
1577
- c.strokeStyle = $.ctx.strokeStyle;
1578
- c.lineWidth = $.ctx.lineWidth;
1579
- }
1580
- let f = c.fillStyle;
1581
- if (!$._fillSet) c.fillStyle = 'black';
1582
- for (let i = 0; i < lines.length; i++) {
1583
- if ($._doStroke && $._strokeSet) c.strokeText(lines[i], cX, cY);
1584
- if ($._doFill) c.fillText(lines[i], cX, cY);
1585
- cY += $._textLeading;
1586
- if (cY > h) break;
1587
- }
1588
- if (!$._fillSet) c.fillStyle = f;
1589
- if (useCache) {
1590
- ti = tg;
1591
- ti._ascent = _ascent;
1592
- ti._descent = _descent;
1593
- $._tic.set(k, ti);
1594
- if (!$._genTextImage) $.textImage(ti, x, y);
1643
+
1644
+ if (genTextImage) return img;
1645
+ $.textImage(img, x, y);
1595
1646
  }
1596
1647
  };
1597
1648
  $.textImage = (img, x, y) => {
1598
1649
  let og = $._imageMode;
1599
1650
  $._imageMode = 'corner';
1600
- if ($.ctx.textAlign == 'center') x -= img.width * 0.5;
1601
- else if ($.ctx.textAlign == 'right') x -= img.width;
1602
- if ($.ctx.textBaseline == 'alphabetic') y -= $._textLeading;
1603
- if ($.ctx.textBaseline == 'middle') y -= img._descent + img._ascent * 0.5 + $._textLeadDiff;
1604
- else if ($.ctx.textBaseline == 'bottom') y -= img._ascent + img._descent + $._textLeadDiff;
1605
- else if ($.ctx.textBaseline == 'top') y -= img._descent + $._textLeadDiff;
1651
+
1652
+ let ta = $._textAlign;
1653
+ if (ta == 'center') x -= img.canvas.hw;
1654
+ else if (ta == 'right') x -= img.width;
1655
+
1656
+ let bl = $._textBaseline;
1657
+ if (bl == 'alphabetic') y -= leading;
1658
+ else if (bl == 'middle') y -= img._middle;
1659
+ else if (bl == 'bottom') y -= img._bottom;
1660
+ else if (bl == 'top') y -= img._top;
1661
+
1606
1662
  $.image(img, x, y);
1607
1663
  $._imageMode = og;
1608
1664
  };
@@ -2850,9 +2906,9 @@ Q5.Vector = class {
2850
2906
  return this._$.atan2(this.y, this.x);
2851
2907
  }
2852
2908
  setHeading(ang) {
2853
- let mag = this.mag(); // Calculate the magnitude of the vector
2854
- this.x = mag * this._$.cos(ang); // Set the new x component
2855
- this.y = mag * this._$.sin(ang); // Set the new y component
2909
+ let mag = this.mag();
2910
+ this.x = mag * this._$.cos(ang);
2911
+ this.y = mag * this._$.sin(ang);
2856
2912
  return this;
2857
2913
  }
2858
2914
  rotate(ang) {
@@ -3044,8 +3100,8 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3044
3100
  $._createCanvas = (w, h, opt) => {
3045
3101
  q.ctx = q.drawingContext = c.getContext('webgpu');
3046
3102
 
3047
- opt.format = navigator.gpu.getPreferredCanvasFormat();
3048
- opt.device = Q5.device;
3103
+ opt.format ??= navigator.gpu.getPreferredCanvasFormat();
3104
+ opt.device ??= Q5.device;
3049
3105
 
3050
3106
  $.ctx.configure(opt);
3051
3107
 
@@ -3070,6 +3126,37 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3070
3126
  $._setCanvasSize(w, h);
3071
3127
  };
3072
3128
 
3129
+ // current color index, used to associate a vertex with a color
3130
+ let colorIndex = 0;
3131
+ const addColor = (r, g, b, a = 1) => {
3132
+ if (typeof r == 'string') r = $.color(r);
3133
+ else if (b == undefined) {
3134
+ // grayscale mode `fill(1, 0.5)`
3135
+ a = g ?? 1;
3136
+ g = b = r;
3137
+ }
3138
+ if (r._q5Color) colorsStack.push(r.r, r.g, r.b, r.a);
3139
+ else colorsStack.push(r, g, b, a);
3140
+ colorIndex++;
3141
+ };
3142
+
3143
+ $.fill = (r, g, b, a) => {
3144
+ addColor(r, g, b, a);
3145
+ $._doFill = true;
3146
+ $._fillIndex = colorIndex;
3147
+ };
3148
+ $.stroke = (r, g, b, a) => {
3149
+ addColor(r, g, b, a);
3150
+ $._doStroke = true;
3151
+ $._strokeIndex = colorIndex;
3152
+ };
3153
+
3154
+ $.noFill = () => ($._doFill = false);
3155
+ $.noStroke = () => ($._doStroke = false);
3156
+
3157
+ $._strokeWeight = 1;
3158
+ $.strokeWeight = (v) => ($._strokeWeight = Math.abs(v));
3159
+
3073
3160
  $.resetMatrix = () => {
3074
3161
  // Initialize the transformation matrix as 4x4 identity matrix
3075
3162
 
@@ -3093,24 +3180,6 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3093
3180
  // Stack to keep track of transformation matrix indexes
3094
3181
  $._transformIndexStack = [];
3095
3182
 
3096
- $.push = $.pushMatrix = () => {
3097
- // Push the current matrix index onto the stack
3098
- $._transformIndexStack.push($._transformIndex);
3099
- $._pushStyles();
3100
- };
3101
-
3102
- $.pop = $.popMatrix = () => {
3103
- if (!$._transformIndexStack.length) {
3104
- return console.warn('Matrix index stack is empty!');
3105
- }
3106
- // Pop the last matrix index from the stack and set it as the current matrix index
3107
- let idx = $._transformIndexStack.pop();
3108
- $._matrix = $.transformStates[idx].slice();
3109
- $._transformIndex = idx;
3110
- $._matrixDirty = false;
3111
- $._popStyles();
3112
- };
3113
-
3114
3183
  $.translate = (x, y, z) => {
3115
3184
  if (!x && !y && !z) return;
3116
3185
  // Update the translation values
@@ -3156,6 +3225,57 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3156
3225
  $._matrixDirty = true;
3157
3226
  };
3158
3227
 
3228
+ $.shearX = (ang) => {
3229
+ if (!ang) return;
3230
+ if ($._angleMode) ang *= $._DEGTORAD;
3231
+
3232
+ let tanAng = Math.tan(ang);
3233
+
3234
+ let m0 = $._matrix[0],
3235
+ m1 = $._matrix[1],
3236
+ m4 = $._matrix[4],
3237
+ m5 = $._matrix[5];
3238
+
3239
+ $._matrix[0] = m0 + m4 * tanAng;
3240
+ $._matrix[1] = m1 + m5 * tanAng;
3241
+
3242
+ $._matrixDirty = true;
3243
+ };
3244
+
3245
+ $.shearY = (ang) => {
3246
+ if (!ang) return;
3247
+ if ($._angleMode) ang *= $._DEGTORAD;
3248
+
3249
+ let tanAng = Math.tan(ang);
3250
+
3251
+ let m0 = $._matrix[0],
3252
+ m1 = $._matrix[1],
3253
+ m4 = $._matrix[4],
3254
+ m5 = $._matrix[5];
3255
+
3256
+ $._matrix[4] = m4 + m0 * tanAng;
3257
+ $._matrix[5] = m5 + m1 * tanAng;
3258
+
3259
+ $._matrixDirty = true;
3260
+ };
3261
+
3262
+ $.applyMatrix = (...args) => {
3263
+ let m;
3264
+ if (args.length == 1) m = args[0];
3265
+ else m = args;
3266
+
3267
+ if (m.length == 9) {
3268
+ // Convert 3x3 matrix to 4x4 matrix
3269
+ m = [m[0], m[1], 0, m[2], m[3], m[4], 0, m[5], 0, 0, 1, 0, m[6], m[7], 0, m[8]];
3270
+ } else if (m.length != 16) {
3271
+ throw new Error('Matrix must be a 3x3 or 4x4 array.');
3272
+ }
3273
+
3274
+ // Overwrite the current transformation matrix
3275
+ $._matrix = m.slice();
3276
+ $._matrixDirty = true;
3277
+ };
3278
+
3159
3279
  // Function to save the current matrix state if dirty
3160
3280
  $._saveMatrix = () => {
3161
3281
  $.transformStates.push($._matrix.slice());
@@ -3163,37 +3283,31 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3163
3283
  $._matrixDirty = false;
3164
3284
  };
3165
3285
 
3166
- // current color index, used to associate a vertex with a color
3167
- let colorIndex = 0;
3168
- const addColor = (r, g, b, a = 1) => {
3169
- if (typeof r == 'string') r = $.color(r);
3170
- else if (b == undefined) {
3171
- // grayscale mode `fill(1, 0.5)`
3172
- a = g ?? 1;
3173
- g = b = r;
3286
+ // Push the current matrix index onto the stack
3287
+ $.pushMatrix = () => {
3288
+ if ($._matrixDirty) $._saveMatrix();
3289
+ $._transformIndexStack.push($._transformIndex);
3290
+ };
3291
+ $.popMatrix = () => {
3292
+ if (!$._transformIndexStack.length) {
3293
+ return console.warn('Matrix index stack is empty!');
3174
3294
  }
3175
- if (r._q5Color) colorsStack.push(r.r, r.g, r.b, r.a);
3176
- else colorsStack.push(r, g, b, a);
3177
- colorIndex++;
3295
+ // Pop the last matrix index from the stack and set it as the current matrix index
3296
+ let idx = $._transformIndexStack.pop();
3297
+ $._matrix = $.transformStates[idx].slice();
3298
+ $._transformIndex = idx;
3299
+ $._matrixDirty = false;
3178
3300
  };
3179
3301
 
3180
- $.fill = (r, g, b, a) => {
3181
- addColor(r, g, b, a);
3182
- $._doFill = true;
3183
- $._fillIndex = colorIndex;
3302
+ $.push = () => {
3303
+ $.pushMatrix();
3304
+ $.pushStyles();
3184
3305
  };
3185
- $.stroke = (r, g, b, a) => {
3186
- addColor(r, g, b, a);
3187
- $._doStroke = true;
3188
- $._strokeIndex = colorIndex;
3306
+ $.pop = () => {
3307
+ $.popMatrix();
3308
+ $.popStyles();
3189
3309
  };
3190
3310
 
3191
- $.noFill = () => ($._doFill = false);
3192
- $.noStroke = () => ($._doStroke = false);
3193
-
3194
- $._strokeWeight = 1;
3195
- $.strokeWeight = (v) => ($._strokeWeight = Math.abs(v));
3196
-
3197
3311
  $._calcBox = (x, y, w, h, mode) => {
3198
3312
  let hw = w / 2;
3199
3313
  let hh = h / 2;
@@ -3553,6 +3667,15 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
3553
3667
  $.endShape(1);
3554
3668
  };
3555
3669
 
3670
+ $.quad = (x1, y1, x2, y2, x3, y3, x4, y4) => {
3671
+ $.beginShape();
3672
+ $.vertex(x1, y1);
3673
+ $.vertex(x2, y2);
3674
+ $.vertex(x3, y3);
3675
+ $.vertex(x4, y4);
3676
+ $.endShape(1);
3677
+ };
3678
+
3556
3679
  $.rectMode = (x) => ($._rectMode = x);
3557
3680
 
3558
3681
  $.rect = (x, y, w, h) => {
@@ -3574,6 +3697,8 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
3574
3697
  drawStack.push(0, 6);
3575
3698
  };
3576
3699
 
3700
+ $.square = (x, y, s) => $.rect(x, y, s, s);
3701
+
3577
3702
  $.point = (x, y) => {
3578
3703
  colorIndex = $._strokeIndex;
3579
3704
  let sw = $._strokeWeight;
@@ -3839,20 +3964,30 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
3839
3964
  minFilter: 'linear'
3840
3965
  });
3841
3966
 
3967
+ let MAX_TEXTURES = 12000;
3968
+
3969
+ $._textures = [];
3970
+ let tIdx = 0;
3971
+
3842
3972
  $._createTexture = (img) => {
3843
3973
  if (img.canvas) img = img.canvas;
3844
3974
 
3845
3975
  let textureSize = [img.width, img.height, 1];
3846
3976
 
3847
- const texture = Q5.device.createTexture({
3977
+ let texture = Q5.device.createTexture({
3848
3978
  size: textureSize,
3849
3979
  format: 'bgra8unorm',
3850
3980
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
3851
3981
  });
3852
3982
 
3853
- Q5.device.queue.copyExternalImageToTexture({ source: img }, { texture }, textureSize);
3983
+ Q5.device.queue.copyExternalImageToTexture(
3984
+ { source: img },
3985
+ { texture, colorSpace: $.canvas.colorSpace },
3986
+ textureSize
3987
+ );
3854
3988
 
3855
- img.textureIndex = $._textureBindGroups.length;
3989
+ $._textures[tIdx] = texture;
3990
+ img.textureIndex = tIdx;
3856
3991
 
3857
3992
  const textureBindGroup = Q5.device.createBindGroup({
3858
3993
  layout: textureLayout,
@@ -3861,7 +3996,16 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
3861
3996
  { binding: 1, resource: texture.createView() }
3862
3997
  ]
3863
3998
  });
3864
- $._textureBindGroups.push(textureBindGroup);
3999
+ $._textureBindGroups[tIdx] = textureBindGroup;
4000
+
4001
+ tIdx = (tIdx + 1) % MAX_TEXTURES;
4002
+
4003
+ // If the texture array is full, destroy the oldest texture
4004
+ if ($._textures[tIdx]) {
4005
+ $._textures[tIdx].destroy();
4006
+ delete $._textures[tIdx];
4007
+ delete $._textureBindGroups[tIdx];
4008
+ }
3865
4009
  };
3866
4010
 
3867
4011
  $.loadImage = $.loadTexture = (src) => {
@@ -3925,6 +4069,15 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
3925
4069
  verticesStack.length = 0;
3926
4070
  });
3927
4071
  };
4072
+
4073
+ Q5.THRESHOLD = 1;
4074
+ Q5.GRAY = 2;
4075
+ Q5.OPAQUE = 3;
4076
+ Q5.INVERT = 4;
4077
+ Q5.POSTERIZE = 5;
4078
+ Q5.DILATE = 6;
4079
+ Q5.ERODE = 7;
4080
+ Q5.BLUR = 8;
3928
4081
  Q5.renderers.webgpu.text = ($, q) => {
3929
4082
  let t = $.createGraphics(1, 1);
3930
4083
  t.pixelDensity($._pixelDensity);
@@ -3936,6 +4089,8 @@ Q5.renderers.webgpu.text = ($, q) => {
3936
4089
  q._preloadCount--;
3937
4090
  });
3938
4091
  };
4092
+
4093
+ // directly add these text setting functions to the webgpu renderer
3939
4094
  $.textFont = t.textFont;
3940
4095
  $.textSize = t.textSize;
3941
4096
  $.textLeading = t.textLeading;
@@ -3951,7 +4106,20 @@ Q5.renderers.webgpu.text = ($, q) => {
3951
4106
  $.text = (str, x, y, w, h) => {
3952
4107
  let img = t.createTextImage(str, w, h);
3953
4108
 
3954
- if (img.canvas.textureIndex == undefined) $._createTexture(img);
4109
+ if (img.canvas.textureIndex === undefined) {
4110
+ $._createTexture(img);
4111
+ } else if (img.modified) {
4112
+ let cnv = img.canvas;
4113
+ let textureSize = [cnv.width, cnv.height, 1];
4114
+ let texture = $._textures[cnv.textureIndex];
4115
+
4116
+ Q5.device.queue.copyExternalImageToTexture(
4117
+ { source: cnv },
4118
+ { texture, colorSpace: $.canvas.colorSpace },
4119
+ textureSize
4120
+ );
4121
+ img.modified = false;
4122
+ }
3955
4123
 
3956
4124
  $.textImage(img, x, y);
3957
4125
  };
@@ -3959,12 +4127,20 @@ Q5.renderers.webgpu.text = ($, q) => {
3959
4127
  $.createTextImage = t.createTextImage;
3960
4128
 
3961
4129
  $.textImage = (img, x, y) => {
3962
- if (t.ctx.textAlign == 'center') x -= img.width * 0.5;
3963
- else if (t.ctx.textAlign == 'right') x -= img.width;
3964
- if (t.ctx.textBaseline == 'alphabetic') y -= t._textLeading;
3965
- if (t.ctx.textBaseline == 'middle') y -= img._descent + img._ascent * 0.5 + t._textLeadDiff;
3966
- else if (t.ctx.textBaseline == 'bottom') y -= img._ascent + img._descent + t._textLeadDiff;
3967
- else if (t.ctx.textBaseline == 'top') y -= img._descent + t._textLeadDiff;
4130
+ let og = $._imageMode;
4131
+ $._imageMode = 'corner';
4132
+
4133
+ let ta = t._textAlign;
4134
+ if (ta == 'center') x -= img.canvas.hw;
4135
+ else if (ta == 'right') x -= img.width;
4136
+
4137
+ let bl = t._textBaseline;
4138
+ if (bl == 'alphabetic') y -= t._textLeading;
4139
+ else if (bl == 'middle') y -= img._middle;
4140
+ else if (bl == 'bottom') y -= img._bottom;
4141
+ else if (bl == 'top') y -= img._top;
4142
+
3968
4143
  $.image(img, x, y);
4144
+ $._imageMode = og;
3969
4145
  };
3970
4146
  };