q5 2.10.3 → 2.10.6

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
@@ -143,15 +143,16 @@ function Q5(scope, parent, renderer) {
143
143
  $.getTargetFrameRate = () => $._targetFrameRate || 60;
144
144
  $.getFPS = () => $._fps;
145
145
 
146
+ // shims for compatibility with p5.js libraries
146
147
  $.Element = function (a) {
147
148
  this.elt = a;
148
149
  };
149
150
  $._elements = [];
151
+ $.describe = () => {};
150
152
 
151
153
  $.TWO_PI = $.TAU = Math.PI * 2;
152
154
 
153
155
  $.log = $.print = console.log;
154
- $.describe = () => {};
155
156
 
156
157
  for (let m in Q5.modules) {
157
158
  Q5.modules[m]($, q);
@@ -319,60 +320,6 @@ if (typeof document == 'object') {
319
320
  });
320
321
  }
321
322
  Q5.modules.canvas = ($, q) => {
322
- $.CENTER = 'center';
323
- $.LEFT = 'left';
324
- $.RIGHT = 'right';
325
- $.TOP = 'top';
326
- $.BOTTOM = 'bottom';
327
-
328
- $.BASELINE = 'alphabetic';
329
-
330
- $.NORMAL = 'normal';
331
- $.ITALIC = 'italic';
332
- $.BOLD = 'bold';
333
- $.BOLDITALIC = 'italic bold';
334
-
335
- $.ROUND = 'round';
336
- $.SQUARE = 'butt';
337
- $.PROJECT = 'square';
338
- $.MITER = 'miter';
339
- $.BEVEL = 'bevel';
340
-
341
- $.CHORD_OPEN = 0;
342
- $.PIE_OPEN = 1;
343
- $.PIE = 2;
344
- $.CHORD = 3;
345
-
346
- $.RADIUS = 'radius';
347
- $.CORNER = 'corner';
348
- $.CORNERS = 'corners';
349
-
350
- $.OPEN = 0;
351
- $.CLOSE = 1;
352
-
353
- $.LANDSCAPE = 'landscape';
354
- $.PORTRAIT = 'portrait';
355
-
356
- $.BLEND = 'source-over';
357
- $.REMOVE = 'destination-out';
358
- $.ADD = 'lighter';
359
- $.DARKEST = 'darken';
360
- $.LIGHTEST = 'lighten';
361
- $.DIFFERENCE = 'difference';
362
- $.SUBTRACT = 'subtract';
363
- $.EXCLUSION = 'exclusion';
364
- $.MULTIPLY = 'multiply';
365
- $.SCREEN = 'screen';
366
- $.REPLACE = 'copy';
367
- $.OVERLAY = 'overlay';
368
- $.HARD_LIGHT = 'hard-light';
369
- $.SOFT_LIGHT = 'soft-light';
370
- $.DODGE = 'color-dodge';
371
- $.BURN = 'color-burn';
372
-
373
- $.P2D = '2d';
374
- $.WEBGL = 'webgl';
375
-
376
323
  $._OffscreenCanvas =
377
324
  window.OffscreenCanvas ||
378
325
  function () {
@@ -651,6 +598,74 @@ Q5.modules.canvas = ($, q) => {
651
598
  }
652
599
  };
653
600
 
601
+ Q5.CENTER = 'center';
602
+ Q5.LEFT = 'left';
603
+ Q5.RIGHT = 'right';
604
+ Q5.TOP = 'top';
605
+ Q5.BOTTOM = 'bottom';
606
+
607
+ Q5.BASELINE = 'alphabetic';
608
+
609
+ Q5.NORMAL = 'normal';
610
+ Q5.ITALIC = 'italic';
611
+ Q5.BOLD = 'bold';
612
+ Q5.BOLDITALIC = 'italic bold';
613
+
614
+ Q5.ROUND = 'round';
615
+ Q5.SQUARE = 'butt';
616
+ Q5.PROJECT = 'square';
617
+ Q5.MITER = 'miter';
618
+ Q5.BEVEL = 'bevel';
619
+
620
+ Q5.CHORD_OPEN = 0;
621
+ Q5.PIE_OPEN = 1;
622
+ Q5.PIE = 2;
623
+ Q5.CHORD = 3;
624
+
625
+ Q5.RADIUS = 'radius';
626
+ Q5.CORNER = 'corner';
627
+ Q5.CORNERS = 'corners';
628
+
629
+ Q5.OPEN = 0;
630
+ Q5.CLOSE = 1;
631
+
632
+ Q5.LANDSCAPE = 'landscape';
633
+ Q5.PORTRAIT = 'portrait';
634
+
635
+ Q5.BLEND = 'source-over';
636
+ Q5.REMOVE = 'destination-out';
637
+ Q5.ADD = 'lighter';
638
+ Q5.DARKEST = 'darken';
639
+ Q5.LIGHTEST = 'lighten';
640
+ Q5.DIFFERENCE = 'difference';
641
+ Q5.SUBTRACT = 'subtract';
642
+ Q5.EXCLUSION = 'exclusion';
643
+ Q5.MULTIPLY = 'multiply';
644
+ Q5.SCREEN = 'screen';
645
+ Q5.REPLACE = 'copy';
646
+ Q5.OVERLAY = 'overlay';
647
+ Q5.HARD_LIGHT = 'hard-light';
648
+ Q5.SOFT_LIGHT = 'soft-light';
649
+ Q5.DODGE = 'color-dodge';
650
+ Q5.BURN = 'color-burn';
651
+
652
+ Q5.THRESHOLD = 1;
653
+ Q5.GRAY = 2;
654
+ Q5.OPAQUE = 3;
655
+ Q5.INVERT = 4;
656
+ Q5.POSTERIZE = 5;
657
+ Q5.DILATE = 6;
658
+ Q5.ERODE = 7;
659
+ Q5.BLUR = 8;
660
+ Q5.SEPIA = 9;
661
+ Q5.BRIGHTNESS = 10;
662
+ Q5.SATURATION = 11;
663
+ Q5.CONTRAST = 12;
664
+ Q5.HUE_ROTATE = 13;
665
+
666
+ Q5.P2D = '2d';
667
+ Q5.WEBGL = 'webgl';
668
+
654
669
  Q5.canvasOptions = {
655
670
  alpha: false,
656
671
  colorSpace: 'display-p3'
@@ -1270,6 +1285,9 @@ Q5.renderers.q2d.image = ($, q) => {
1270
1285
 
1271
1286
  Q5.Image ??= Q5Image;
1272
1287
 
1288
+ $._tint = null;
1289
+ let imgData = null;
1290
+
1273
1291
  $.createImage = (w, h, opt) => {
1274
1292
  opt ??= {};
1275
1293
  opt.alpha ??= true;
@@ -1364,7 +1382,7 @@ Q5.renderers.q2d.image = ($, q) => {
1364
1382
  } else sh *= pd;
1365
1383
 
1366
1384
  if ($._tint) {
1367
- if (img._tint != $._tint || img._retint) {
1385
+ if (img._retint || img._tint != $._tint) {
1368
1386
  img._tintImg ??= $.createImage(img.w, img.h, { pixelDensity: pd });
1369
1387
 
1370
1388
  if (img._tintImg.width != img.width || img._tintImg.height != img.height) {
@@ -1394,30 +1412,43 @@ Q5.renderers.q2d.image = ($, q) => {
1394
1412
  $.ctx.drawImage(drawable, sx * pd, sy * pd, sw, sh, dx, dy, dw, dh);
1395
1413
  };
1396
1414
 
1397
- $._tint = null;
1398
- let imgData = null;
1399
-
1400
- $._softFilter = () => {
1401
- throw new Error('Load q5-2d-soft-filters.js to use software filters.');
1402
- };
1403
-
1404
- $.filter = (type, x) => {
1405
- if (!$.ctx.filter) return $._softFilter(type, x);
1406
-
1407
- if (typeof type == 'string') f = type;
1408
- else if (type == Q5.GRAY) f = `saturate(0%)`;
1409
- else if (type == Q5.INVERT) f = `invert(100%)`;
1410
- else if (type == Q5.BLUR) {
1411
- let r = Math.ceil(x * $._pixelDensity) || 1;
1412
- f = `blur(${r}px)`;
1413
- } else if (type == Q5.THRESHOLD) {
1414
- x ??= 0.5;
1415
- let b = Math.floor((0.5 / Math.max(x, 0.00001)) * 100);
1416
- f = `saturate(0%) brightness(${b}%) contrast(1000000%)`;
1417
- } else return $._softFilter(type, x);
1415
+ $.filter = (type, value) => {
1416
+ if (!$.ctx.filter) return $._softFilter(type, value);
1417
+ let f = '';
1418
+ if (typeof type === 'string') {
1419
+ f = type;
1420
+ } else if (type === Q5.GRAY) {
1421
+ f = `saturate(0%)`;
1422
+ } else if (type === Q5.INVERT) {
1423
+ f = `invert(100%)`;
1424
+ } else if (type === Q5.BLUR) {
1425
+ const radius = Math.ceil(value * $._pixelDensity) || 1;
1426
+ f = `blur(${radius}px)`;
1427
+ } else if (type === Q5.THRESHOLD) {
1428
+ value ??= 0.5;
1429
+ const brightness = Math.floor((0.5 / Math.max(value, 0.00001)) * 100);
1430
+ f = `saturate(0%) brightness(${brightness}%) contrast(1000000%)`;
1431
+ } else if (type === Q5.SEPIA) {
1432
+ f = `sepia(${value ?? 1})`;
1433
+ } else if (type === Q5.BRIGHTNESS) {
1434
+ f = `brightness(${value ?? 1})`;
1435
+ } else if (type === Q5.SATURATION) {
1436
+ f = `saturate(${value ?? 1})`;
1437
+ } else if (type === Q5.CONTRAST) {
1438
+ f = `contrast(${value ?? 1})`;
1439
+ } else if (type === Q5.HUE_ROTATE) {
1440
+ const unit = $._angleMode === 0 ? 'rad' : 'deg';
1441
+ f = `hue-rotate(${value}${unit})`;
1442
+ } else {
1443
+ $._softFilter(type, value);
1444
+ return;
1445
+ }
1418
1446
 
1419
1447
  $.ctx.filter = f;
1420
- $.ctx.drawImage($.canvas, 0, 0, $.canvas.w, $.canvas.h);
1448
+ if ($.ctx.filter == 'none') {
1449
+ throw new Error(`Invalid filter format: ${type}`);
1450
+ }
1451
+ $.ctx.drawImage($.canvas, 0, 0, $.canvas.width, $.canvas.height);
1421
1452
  $.ctx.filter = 'none';
1422
1453
  $._retint = true;
1423
1454
  };
@@ -1561,15 +1592,151 @@ Q5.renderers.q2d.image = ($, q) => {
1561
1592
  };
1562
1593
  $.noTint = () => ($._tint = null);
1563
1594
  };
1595
+ /* software implementation of image filters */
1596
+ Q5.renderers.q2d.soft_filters = ($) => {
1597
+ let u = null; // uint8 temporary buffer
1564
1598
 
1565
- Q5.THRESHOLD = 1;
1566
- Q5.GRAY = 2;
1567
- Q5.OPAQUE = 3;
1568
- Q5.INVERT = 4;
1569
- Q5.POSTERIZE = 5;
1570
- Q5.DILATE = 6;
1571
- Q5.ERODE = 7;
1572
- Q5.BLUR = 8;
1599
+ function ensureBuf() {
1600
+ let l = $.canvas.width * $.canvas.height * 4;
1601
+ if (!u || u.length != l) u = new Uint8ClampedArray(l);
1602
+ }
1603
+
1604
+ function initSoftFilters() {
1605
+ $._filters = [];
1606
+ $._filters[Q5.THRESHOLD] = (d, thresh) => {
1607
+ if (thresh === undefined) thresh = 127.5;
1608
+ else thresh *= 255;
1609
+ for (let i = 0; i < d.length; i += 4) {
1610
+ const gray = 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2];
1611
+ d[i] = d[i + 1] = d[i + 2] = gray >= thresh ? 255 : 0;
1612
+ }
1613
+ };
1614
+ $._filters[Q5.GRAY] = (d) => {
1615
+ for (let i = 0; i < d.length; i += 4) {
1616
+ const gray = 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2];
1617
+ d[i] = d[i + 1] = d[i + 2] = gray;
1618
+ }
1619
+ };
1620
+ $._filters[Q5.OPAQUE] = (d) => {
1621
+ for (let i = 0; i < d.length; i += 4) {
1622
+ d[i + 3] = 255;
1623
+ }
1624
+ };
1625
+ $._filters[Q5.INVERT] = (d) => {
1626
+ for (let i = 0; i < d.length; i += 4) {
1627
+ d[i] = 255 - d[i];
1628
+ d[i + 1] = 255 - d[i + 1];
1629
+ d[i + 2] = 255 - d[i + 2];
1630
+ }
1631
+ };
1632
+ $._filters[Q5.POSTERIZE] = (d, lvl = 4) => {
1633
+ let lvl1 = lvl - 1;
1634
+ for (let i = 0; i < d.length; i += 4) {
1635
+ d[i] = (((d[i] * lvl) >> 8) * 255) / lvl1;
1636
+ d[i + 1] = (((d[i + 1] * lvl) >> 8) * 255) / lvl1;
1637
+ d[i + 2] = (((d[i + 2] * lvl) >> 8) * 255) / lvl1;
1638
+ }
1639
+ };
1640
+ $._filters[Q5.DILATE] = (d, func) => {
1641
+ func ??= Math.max;
1642
+ ensureBuf();
1643
+ u.set(d);
1644
+ let [w, h] = [$.canvas.width, $.canvas.height];
1645
+ for (let i = 0; i < h; i++) {
1646
+ for (let j = 0; j < w; j++) {
1647
+ let l = 4 * Math.max(j - 1, 0);
1648
+ let r = 4 * Math.min(j + 1, w - 1);
1649
+ let t = 4 * Math.max(i - 1, 0) * w;
1650
+ let b = 4 * Math.min(i + 1, h - 1) * w;
1651
+ let oi = 4 * i * w;
1652
+ let oj = 4 * j;
1653
+ for (let k = 0; k < 4; k++) {
1654
+ let kt = k + t;
1655
+ let kb = k + b;
1656
+ let ko = k + oi;
1657
+ d[oi + oj + k] = func(u[kt + oj], u[ko + l], u[ko + oj], u[ko + r], u[kb + oj]);
1658
+ }
1659
+ }
1660
+ }
1661
+ };
1662
+ $._filters[Q5.ERODE] = (d) => {
1663
+ $._filters[Q5.DILATE](d, Math.min);
1664
+ };
1665
+ $._filters[Q5.BLUR] = (d, r) => {
1666
+ r = r || 1;
1667
+ r = Math.floor(r * $._pixelDensity);
1668
+ ensureBuf();
1669
+ u.set(d);
1670
+
1671
+ let ksize = r * 2 + 1;
1672
+
1673
+ function gauss(ksize) {
1674
+ let im = new Float32Array(ksize);
1675
+ let sigma = 0.3 * r + 0.8;
1676
+ let ss2 = sigma * sigma * 2;
1677
+ for (let i = 0; i < ksize; i++) {
1678
+ let x = i - ksize / 2;
1679
+ let z = Math.exp(-(x * x) / ss2) / (2.5066282746 * sigma);
1680
+ im[i] = z;
1681
+ }
1682
+ return im;
1683
+ }
1684
+
1685
+ let kern = gauss(ksize);
1686
+ let [w, h] = [$.canvas.width, $.canvas.height];
1687
+ for (let i = 0; i < h; i++) {
1688
+ for (let j = 0; j < w; j++) {
1689
+ let s0 = 0,
1690
+ s1 = 0,
1691
+ s2 = 0,
1692
+ s3 = 0;
1693
+ for (let k = 0; k < ksize; k++) {
1694
+ let jk = Math.min(Math.max(j - r + k, 0), w - 1);
1695
+ let idx = 4 * (i * w + jk);
1696
+ s0 += u[idx] * kern[k];
1697
+ s1 += u[idx + 1] * kern[k];
1698
+ s2 += u[idx + 2] * kern[k];
1699
+ s3 += u[idx + 3] * kern[k];
1700
+ }
1701
+ let idx = 4 * (i * w + j);
1702
+ d[idx] = s0;
1703
+ d[idx + 1] = s1;
1704
+ d[idx + 2] = s2;
1705
+ d[idx + 3] = s3;
1706
+ }
1707
+ }
1708
+ u.set(d);
1709
+ for (let i = 0; i < h; i++) {
1710
+ for (let j = 0; j < w; j++) {
1711
+ let s0 = 0,
1712
+ s1 = 0,
1713
+ s2 = 0,
1714
+ s3 = 0;
1715
+ for (let k = 0; k < ksize; k++) {
1716
+ let ik = Math.min(Math.max(i - r + k, 0), h - 1);
1717
+ let idx = 4 * (ik * w + j);
1718
+ s0 += u[idx] * kern[k];
1719
+ s1 += u[idx + 1] * kern[k];
1720
+ s2 += u[idx + 2] * kern[k];
1721
+ s3 += u[idx + 3] * kern[k];
1722
+ }
1723
+ let idx = 4 * (i * w + j);
1724
+ d[idx] = s0;
1725
+ d[idx + 1] = s1;
1726
+ d[idx + 2] = s2;
1727
+ d[idx + 3] = s3;
1728
+ }
1729
+ }
1730
+ };
1731
+ }
1732
+
1733
+ $._softFilter = (typ, x) => {
1734
+ if (!$._filters) initSoftFilters();
1735
+ let imgData = $.ctx._getImageData(0, 0, $.canvas.width, $.canvas.height);
1736
+ $._filters[typ](imgData.data, x);
1737
+ $.ctx.putImageData(imgData, 0, 0);
1738
+ };
1739
+ };
1573
1740
  Q5.renderers.q2d.text = ($, q) => {
1574
1741
  $._textAlign = 'left';
1575
1742
  $._textBaseline = 'alphabetic';
@@ -1899,6 +2066,7 @@ Q5.modules.ai = ($) => {
1899
2066
  };
1900
2067
  Q5.modules.color = ($, q) => {
1901
2068
  $.RGB = $.RGBA = $._colorMode = 'rgb';
2069
+ $.SRGB = 'srgb';
1902
2070
  $.OKLCH = 'oklch';
1903
2071
 
1904
2072
  $.colorMode = (mode, format) => {
@@ -1909,7 +2077,6 @@ Q5.modules.color = ($, q) => {
1909
2077
  if (mode == 'oklch') {
1910
2078
  q.Color = Q5.ColorOKLCH;
1911
2079
  } else {
1912
- let srgb = $.canvas.colorSpace == 'srgb';
1913
2080
  if ($._colorFormat == 255) {
1914
2081
  q.Color = srgb ? Q5.ColorRGBA_8 : Q5.ColorRGBA_P3_8;
1915
2082
  } else {
@@ -1989,14 +2156,19 @@ Q5.modules.color = ($, q) => {
1989
2156
  if (Array.isArray(c0)) [c0, c1, c2, c3] = c0;
1990
2157
  }
1991
2158
 
1992
- if (c2 == undefined) return new C(c0, c0, c0, c1);
2159
+ if (c2 == undefined) {
2160
+ if ($._colorMode == Q5.OKLCH) return new C(c0, 0, 0, c1);
2161
+ return new C(c0, c0, c0, c1);
2162
+ }
1993
2163
  return new C(c0, c1, c2, c3);
1994
2164
  };
1995
2165
 
2166
+ // deprecated
1996
2167
  $.red = (c) => c.r;
1997
2168
  $.green = (c) => c.g;
1998
2169
  $.blue = (c) => c.b;
1999
2170
  $.alpha = (c) => c.a;
2171
+
2000
2172
  $.lightness = (c) => {
2001
2173
  if (c.l) return c.l;
2002
2174
  return ((0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * 100) / 255;
@@ -2054,9 +2226,43 @@ Q5.ColorOKLCH = class extends Q5.Color {
2054
2226
  this.h = h;
2055
2227
  this.a = a ?? 1;
2056
2228
  }
2229
+ get levels() {
2230
+ return [this.l, this.c, this.h, this.a];
2231
+ }
2232
+ equals(c) {
2233
+ return c && this.l == c.l && this.c == c.c && this.h == c.h && this.a == c.a;
2234
+ }
2235
+ isSameColor(c) {
2236
+ return c && this.l == c.l && this.c == c.c && this.h == c.h;
2237
+ }
2057
2238
  toString() {
2058
2239
  return `oklch(${this.l} ${this.c} ${this.h} / ${this.a})`;
2059
2240
  }
2241
+
2242
+ get lightness() {
2243
+ return this.l;
2244
+ }
2245
+ set lightness(v) {
2246
+ this.l = v;
2247
+ }
2248
+ get chroma() {
2249
+ return this.c;
2250
+ }
2251
+ set chroma(v) {
2252
+ this.c = v;
2253
+ }
2254
+ get hue() {
2255
+ return this.h;
2256
+ }
2257
+ set hue(v) {
2258
+ this.h = v;
2259
+ }
2260
+ get alpha() {
2261
+ return this.a;
2262
+ }
2263
+ set alpha(v) {
2264
+ this.a = v;
2265
+ }
2060
2266
  };
2061
2267
 
2062
2268
  Q5.ColorRGBA = class extends Q5.Color {
@@ -2070,9 +2276,39 @@ Q5.ColorRGBA = class extends Q5.Color {
2070
2276
  get levels() {
2071
2277
  return [this.r, this.g, this.b, this.a];
2072
2278
  }
2279
+ equals(c) {
2280
+ return c && this.r == c.r && this.g == c.g && this.b == c.b && this.a == c.a;
2281
+ }
2282
+ isSameColor(c) {
2283
+ return c && this.r == c.r && this.g == c.g && this.b == c.b;
2284
+ }
2073
2285
  toString() {
2074
2286
  return `color(srgb ${this.r} ${this.g} ${this.b} / ${this.a})`;
2075
2287
  }
2288
+ get red() {
2289
+ return this.r;
2290
+ }
2291
+ set red(v) {
2292
+ this.r = v;
2293
+ }
2294
+ get green() {
2295
+ return this.g;
2296
+ }
2297
+ set green(v) {
2298
+ this.g = v;
2299
+ }
2300
+ get blue() {
2301
+ return this.b;
2302
+ }
2303
+ set blue(v) {
2304
+ this.b = v;
2305
+ }
2306
+ get alpha() {
2307
+ return this.a;
2308
+ }
2309
+ set alpha(v) {
2310
+ this.a = v;
2311
+ }
2076
2312
  };
2077
2313
 
2078
2314
  Q5.ColorRGBA_P3 = class extends Q5.ColorRGBA {
@@ -2086,6 +2322,7 @@ Q5.ColorRGBA_8 = class extends Q5.ColorRGBA {
2086
2322
  constructor(r, g, b, a) {
2087
2323
  super(r, g, b, a ?? 255);
2088
2324
  }
2325
+ // deprecated set functions for backwards compatibility
2089
2326
  setRed(v) {
2090
2327
  this.r = v;
2091
2328
  }
@@ -2098,9 +2335,6 @@ Q5.ColorRGBA_8 = class extends Q5.ColorRGBA {
2098
2335
  setAlpha(v) {
2099
2336
  this.a = v;
2100
2337
  }
2101
- get levels() {
2102
- return [this.r, this.g, this.b, this.a];
2103
- }
2104
2338
  toString() {
2105
2339
  return `rgb(${this.r} ${this.g} ${this.b} / ${this.a / 255})`;
2106
2340
  }