q5 1.4.2 → 1.4.4

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/q5.js +1301 -1286
  3. package/q5.min.js +1 -1
package/q5.js CHANGED
@@ -59,217 +59,19 @@ function Q5(scope, parent) {
59
59
 
60
60
  defaultStyle();
61
61
 
62
- //================================================================
63
- // CONSTANTS
64
- //================================================================
65
62
  $.MAGIC = 0x9a0ce55;
66
-
67
- $.RGB = 0;
68
- $.HSV = 1;
69
- $.HSB = 1;
70
-
71
- $.CHORD = 0;
72
- $.PIE = 1;
73
- $.OPEN = 2;
74
-
75
- $.RADIUS = 'radius';
76
- $.CORNER = 'corner';
77
- $.CORNERS = 'corners';
78
-
79
- $.ROUND = 'round';
80
- $.SQUARE = 'butt';
81
- $.PROJECT = 'square';
82
- $.MITER = 'miter';
83
- $.BEVEL = 'bevel';
84
-
85
- $.CLOSE = 1;
86
-
87
- $.BLEND = 'source-over';
88
- $.REMOVE = 'destination-out';
89
- $.ADD = 'lighter';
90
- $.DARKEST = 'darken';
91
- $.LIGHTEST = 'lighten';
92
- $.DIFFERENCE = 'difference';
93
- $.SUBTRACT = 'subtract';
94
- $.EXCLUSION = 'exclusion';
95
- $.MULTIPLY = 'multiply';
96
- $.SCREEN = 'screen';
97
- $.REPLACE = 'copy';
98
- $.OVERLAY = 'overlay';
99
- $.HARD_LIGHT = 'hard-light';
100
- $.SOFT_LIGHT = 'soft-light';
101
- $.DODGE = 'color-dodge';
102
- $.BURN = 'color-burn';
103
-
104
- $.NORMAL = 'normal';
105
- $.ITALIC = 'italic';
106
- $.BOLD = 'bold';
107
- $.BOLDITALIC = 'italic bold';
108
-
109
- $.CENTER = 'center';
110
- $.LEFT = 'left';
111
- $.RIGHT = 'right';
112
- $.TOP = 'top';
113
- $.BOTTOM = 'bottom';
114
- $.BASELINE = 'alphabetic';
115
-
116
- $.LANDSCAPE = 'landscape';
117
- $.PORTRAIT = 'portrait';
118
-
119
- $.ALT = 18;
120
- $.BACKSPACE = 8;
121
- $.CONTROL = 17;
122
- $.DELETE = 46;
123
- $.DOWN_ARROW = 40;
124
- $.ENTER = 13;
125
- $.ESCAPE = 27;
126
- $.LEFT_ARROW = 37;
127
- $.OPTION = 18;
128
- $.RETURN = 13;
129
- $.RIGHT_ARROW = 39;
130
- $.SHIFT = 16;
131
- $.TAB = 9;
132
- $.UP_ARROW = 38;
133
-
134
- $.DEGREES = 'degrees';
135
- $.RADIANS = 'radians';
136
-
137
- $.HALF_PI = Math.PI / 2;
138
- $.PI = Math.PI;
139
- $.QUARTER_PI = Math.PI / 4;
140
- $.TAU = Math.PI * 2;
141
- $.TWO_PI = Math.PI * 2;
142
-
143
- $.THRESHOLD = 1;
144
- $.GRAY = 2;
145
- $.OPAQUE = 3;
146
- $.INVERT = 4;
147
- $.POSTERIZE = 5;
148
- $.DILATE = 6;
149
- $.ERODE = 7;
150
- $.BLUR = 8;
151
-
152
- $.ARROW = 'default';
153
- $.CROSS = 'crosshair';
154
- $.HAND = 'pointer';
155
- $.MOVE = 'move';
156
- $.TEXT = 'text';
157
-
158
- $.VIDEO = { video: true, audio: false };
159
- $.AUDIO = { video: false, audio: true };
160
-
161
- $.SHR3 = 1;
162
- $.LCG = 2;
163
-
164
- $.HARDWARE_FILTERS = true;
165
- $.hint = (prop, val) => {
166
- $[prop] = val;
167
- };
168
-
169
- //================================================================
170
- // PUBLIC PROPERTIES
171
- //================================================================
172
- $.frameCount = 0;
173
- $.deltaTime = 16;
174
- $.mouseX = 0;
175
- $.mouseY = 0;
176
- $.mouseButton = null;
177
- $.keyIsPressed = false;
178
- $.mouseIsPressed = false;
179
- $.key = null;
180
- $.keyCode = null;
181
63
  $.pixels = [];
182
- $.accelerationX = 0;
183
- $.accelerationY = 0;
184
- $.accelerationZ = 0;
185
- $.rotationX = 0;
186
- $.rotationY = 0;
187
- $.rotationZ = 0;
188
- $.relRotationX = 0;
189
- $.relRotationY = 0;
190
- $.relRotationZ = 0;
191
-
192
- $.pmouseX = 0;
193
- $.pmouseY = 0;
194
- $.pAccelerationX = 0;
195
- $.pAccelerationY = 0;
196
- $.pAccelerationZ = 0;
197
- $.pRotationX = 0;
198
- $.pRotationY = 0;
199
- $.pRotationZ = 0;
200
- $.pRelRotationX = 0;
201
- $.pRelRotationY = 0;
202
- $.pRelRotationZ = 0;
203
-
204
- $.touches = [];
205
-
206
- $._colorMode = $.RGB;
207
- $._doStroke = true;
208
- $._doFill = true;
209
- $._strokeSet = false;
210
- $._fillSet = false;
211
- $._ellipseMode = $.CENTER;
212
- $._rectMode = $.CORNER;
213
- $._curveDetail = 20;
214
- $._curveAlpha = 0.0;
215
- $._loop = true;
216
-
217
- $._textFont = 'sans-serif';
218
- $._textSize = 12;
219
- $._textLeading = 15;
220
- $._textLeadDiff = 3;
221
- $._textStyle = 'normal';
222
-
223
- $._pixelDensity = 1;
224
- $._lastFrameTime = 0;
225
- $._targetFrameRate = null;
226
- $._frameRate = $._fps = 60;
227
-
228
- $._tint = null;
229
-
230
- //================================================================
231
- // PRIVATE VARS
232
- //================================================================
233
- let looper = null;
234
- let firstVertex = true;
235
- let curveBuff = [];
236
64
  let imgData = null;
237
- let preloadCnt = 0;
238
- let keysHeld = {};
239
- let millisStart = 0;
240
- let tmpCtx = null;
241
- let tmpCt2 = null;
242
- let tmpBuf = null;
243
-
244
- //================================================================
245
- // ALIAS PROPERTIES
246
- //================================================================
247
-
248
- Object.defineProperty($, 'deviceOrientation', {
249
- get: () => window.screen?.orientation?.type
250
- });
251
-
252
- Object.defineProperty($, 'windowWidth', {
253
- get: () => window.innerWidth
254
- });
255
-
256
- Object.defineProperty($, 'windowHeight', {
257
- get: () => window.innerHeight
258
- });
259
-
260
- Object.defineProperty($, 'drawingContext', {
261
- get: () => ctx
262
- });
263
-
264
- //================================================================
265
- // CANVAS
266
- //================================================================
267
65
 
268
66
  $.createCanvas = (width, height) => {
269
67
  $.width = width;
270
68
  $.height = height;
271
- $.canvas.width = width * $._pixelDensity;
272
- $.canvas.height = height * $._pixelDensity;
69
+ $.canvas.width = width;
70
+ $.canvas.height = height;
71
+ if (scope != 'image') {
72
+ $.canvas.width *= $._pixelDensity;
73
+ $.canvas.height *= $._pixelDensity;
74
+ }
273
75
  defaultStyle();
274
76
  if (scope != 'graphics' && scope != 'image') {
275
77
  $.pixelDensity(Math.ceil($.displayDensity()));
@@ -277,1230 +79,1437 @@ function Q5(scope, parent) {
277
79
  return $.canvas;
278
80
  };
279
81
 
280
- function cloneCtx() {
281
- let c = {};
282
- for (let prop in ctx) {
283
- if (typeof ctx[prop] != 'function') c[prop] = ctx[prop];
284
- }
285
- delete c.canvas;
286
- return c;
287
- }
82
+ //================================================================
83
+ // IMAGE
84
+ //================================================================
288
85
 
289
- $.resizeCanvas = (width, height) => {
290
- $.width = width;
291
- $.height = height;
292
- let c = cloneCtx();
293
- $.canvas.width = width * $._pixelDensity;
294
- $.canvas.height = height * $._pixelDensity;
295
- ctx = $._ctx = $.canvas.getContext('2d');
296
- for (let prop in c) $._ctx[prop] = c[prop];
297
- if (scope != 'graphics' && scope != 'image') $.pixelDensity($._pixelDensity);
86
+ $.loadPixels = () => {
87
+ imgData = ctx.getImageData(0, 0, $.canvas.width, $.canvas.height);
88
+ $.pixels = imgData.data;
89
+ };
90
+ $.updatePixels = () => {
91
+ if (imgData != null) ctx.putImageData(imgData, 0, 0);
298
92
  };
299
93
 
300
- $.createGraphics = (width, height) => {
301
- let g = new Q5('graphics');
302
- g.createCanvas(width, height);
303
- return g;
94
+ let filterImpl = {};
95
+ filterImpl[$.THRESHOLD] = (data, thresh) => {
96
+ if (thresh === undefined) thresh = 127.5;
97
+ else thresh *= 255;
98
+ for (let i = 0; i < data.length; i += 4) {
99
+ const gray = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
100
+ data[i] = data[i + 1] = data[i + 2] = gray >= thresh ? 255 : 0;
101
+ }
304
102
  };
305
- $.createImage = (width, height) => {
306
- return new Q5.Image(width, height);
103
+ filterImpl[$.GRAY] = (data) => {
104
+ for (let i = 0; i < data.length; i += 4) {
105
+ const gray = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
106
+ data[i] = data[i + 1] = data[i + 2] = gray;
107
+ }
307
108
  };
308
- $.displayDensity = () => window.devicePixelRatio;
309
- $.pixelDensity = (n) => {
310
- if (n === undefined) return $._pixelDensity;
311
- $._pixelDensity = n;
312
-
313
- let c = cloneCtx();
314
- $.canvas.width = Math.ceil($.width * n);
315
- $.canvas.height = Math.ceil($.height * n);
316
- $.canvas.style.width = $.width + 'px';
317
- $.canvas.style.height = $.height + 'px';
318
- ctx = $._ctx = $.canvas.getContext('2d');
319
- for (let prop in c) $._ctx[prop] = c[prop];
320
-
321
- ctx.scale($._pixelDensity, $._pixelDensity);
322
- return $._pixelDensity;
323
- };
324
-
325
- //================================================================
326
- // MATH
327
- //================================================================
328
-
329
- $.map = (value, istart, istop, ostart, ostop, clamp) => {
330
- let val = ostart + (ostop - ostart) * (((value - istart) * 1.0) / (istop - istart));
331
- if (!clamp) {
332
- return val;
333
- }
334
- if (ostart < ostop) {
335
- return Math.min(Math.max(val, ostart), ostop);
336
- } else {
337
- return Math.min(Math.max(val, ostop), ostart);
109
+ filterImpl[$.OPAQUE] = (data) => {
110
+ for (let i = 0; i < data.length; i += 4) {
111
+ data[i + 3] = 255;
338
112
  }
339
113
  };
340
- $.lerp = (a, b, t) => a * (1 - t) + b * t;
341
- $.constrain = (x, lo, hi) => Math.min(Math.max(x, lo), hi);
342
- $.dist = function () {
343
- if (arguments.length == 4) {
344
- return Math.hypot(arguments[0] - arguments[2], arguments[1] - arguments[3]);
345
- } else {
346
- return Math.hypot(arguments[0] - arguments[3], arguments[1] - arguments[4], arguments[2] - arguments[5]);
114
+ filterImpl[$.INVERT] = (data) => {
115
+ for (let i = 0; i < data.length; i += 4) {
116
+ data[i] = 255 - data[i];
117
+ data[i + 1] = 255 - data[i + 1];
118
+ data[i + 2] = 255 - data[i + 2];
347
119
  }
348
120
  };
349
- $.norm = (value, start, stop) => $.map(value, start, stop, 0, 1);
350
- $.sq = (x) => x * x;
351
- $.fract = (x) => x - Math.floor(x);
352
- $.angleMode = (mode) => ($._angleMode = mode);
353
- $._DEGTORAD = Math.PI / 180;
354
- $._RADTODEG = 180 / Math.PI;
355
- $.degrees = (x) => x * $._RADTODEG;
356
- $.radians = (x) => x * $._DEGTORAD;
357
- $.abs = Math.abs;
358
- $.ceil = Math.ceil;
359
- $.exp = Math.exp;
360
- $.floor = Math.floor;
361
- $.log = Math.log;
362
- $.mag = Math.hypot;
363
- $.max = Math.max;
364
- $.min = Math.min;
365
- $.round = Math.round;
366
- $.pow = Math.pow;
367
- $.sqrt = Math.sqrt;
368
- $.sin = (a) => {
369
- if ($._angleMode == 'degrees') a = $.radians(a);
370
- return Math.sin(a);
371
- };
372
- $.cos = (a) => {
373
- if ($._angleMode == 'degrees') a = $.radians(a);
374
- return Math.cos(a);
375
- };
376
- $.tan = (a) => {
377
- if ($._angleMode == 'degrees') a = $.radians(a);
378
- return Math.tan(a);
379
- };
380
- $.asin = (x) => {
381
- let a = Math.asin(x);
382
- if ($._angleMode == 'degrees') a = $.degrees(a);
383
- return a;
384
- };
385
- $.acos = (x) => {
386
- let a = Math.acos(x);
387
- if ($._angleMode == 'degrees') a = $.degrees(a);
388
- return a;
389
- };
390
- $.atan = (x) => {
391
- let a = Math.atan(x);
392
- if ($._angleMode == 'degrees') a = $.degrees(a);
393
- return a;
394
- };
395
- $.atan2 = (y, x) => {
396
- let a = Math.atan2(y, x);
397
- if ($._angleMode == 'degrees') a = $.degrees(a);
398
- return a;
399
- };
400
- $.nf = (n, l, r) => {
401
- let neg = n < 0;
402
- let s = n.toString();
403
- if (neg) s = s.slice(1);
404
- s = s.padStart(l, '0');
405
- if (r > 0) {
406
- if (s.indexOf('.') == -1) s += '.';
407
- s = s.padEnd(l + 1 + r, '0');
121
+ filterImpl[$.POSTERIZE] = (data, lvl) => {
122
+ let lvl1 = lvl - 1;
123
+ for (let i = 0; i < data.length; i += 4) {
124
+ data[i] = (((data[i] * lvl) >> 8) * 255) / lvl1;
125
+ data[i + 1] = (((data[i + 1] * lvl) >> 8) * 255) / lvl1;
126
+ data[i + 2] = (((data[i + 2] * lvl) >> 8) * 255) / lvl1;
408
127
  }
409
- if (neg) s = '-' + s;
410
- return s;
411
128
  };
412
- $.createVector = (x, y, z) => new Q5.Vector(x, y, z, $);
413
-
414
- //================================================================
415
- // CURVE QUERY
416
- //================================================================
417
- //https://github.com/processing/p5.js/blob/1.1.9/src/core/shape/curves.js
418
129
 
419
- $.curvePoint = (a, b, c, d, t) => {
420
- const t3 = t * t * t,
421
- t2 = t * t,
422
- f1 = -0.5 * t3 + t2 - 0.5 * t,
423
- f2 = 1.5 * t3 - 2.5 * t2 + 1.0,
424
- f3 = -1.5 * t3 + 2.0 * t2 + 0.5 * t,
425
- f4 = 0.5 * t3 - 0.5 * t2;
426
- return a * f1 + b * f2 + c * f3 + d * f4;
427
- };
428
- $.bezierPoint = (a, b, c, d, t) => {
429
- const adjustedT = 1 - t;
430
- return (
431
- Math.pow(adjustedT, 3) * a +
432
- 3 * Math.pow(adjustedT, 2) * t * b +
433
- 3 * adjustedT * Math.pow(t, 2) * c +
434
- Math.pow(t, 3) * d
435
- );
436
- };
437
- $.curveTangent = (a, b, c, d, t) => {
438
- const t2 = t * t,
439
- f1 = (-3 * t2) / 2 + 2 * t - 0.5,
440
- f2 = (9 * t2) / 2 - 5 * t,
441
- f3 = (-9 * t2) / 2 + 4 * t + 0.5,
442
- f4 = (3 * t2) / 2 - t;
443
- return a * f1 + b * f2 + c * f3 + d * f4;
130
+ filterImpl[$.DILATE] = (data) => {
131
+ makeTmpBuf();
132
+ tmpBuf.set(data);
133
+ let [w, h] = [ctx.canvas.width, ctx.canvas.height];
134
+ for (let i = 0; i < h; i++) {
135
+ for (let j = 0; j < w; j++) {
136
+ let l = 4 * Math.max(j - 1, 0);
137
+ let r = 4 * Math.min(j + 1, w - 1);
138
+ let t = 4 * Math.max(i - 1, 0) * w;
139
+ let b = 4 * Math.min(i + 1, h - 1) * w;
140
+ let oi = 4 * i * w;
141
+ let oj = 4 * j;
142
+ for (let k = 0; k < 4; k++) {
143
+ let kt = k + t;
144
+ let kb = k + b;
145
+ let ko = k + oi;
146
+ data[oi + oj + k] = Math.max(
147
+ /*tmpBuf[kt+l],*/ tmpBuf[kt + oj] /*tmpBuf[kt+r],*/,
148
+ tmpBuf[ko + l],
149
+ tmpBuf[ko + oj],
150
+ tmpBuf[ko + r],
151
+ /*tmpBuf[kb+l],*/ tmpBuf[kb + oj] /*tmpBuf[kb+r],*/
152
+ );
153
+ }
154
+ }
155
+ }
444
156
  };
445
- $.bezierTangent = (a, b, c, d, t) => {
446
- const adjustedT = 1 - t;
447
- return (
448
- 3 * d * Math.pow(t, 2) -
449
- 3 * c * Math.pow(t, 2) +
450
- 6 * c * adjustedT * t -
451
- 6 * b * adjustedT * t +
452
- 3 * b * Math.pow(adjustedT, 2) -
453
- 3 * a * Math.pow(adjustedT, 2)
454
- );
157
+ filterImpl[$.ERODE] = (data) => {
158
+ makeTmpBuf();
159
+ tmpBuf.set(data);
160
+ let [w, h] = [ctx.canvas.width, ctx.canvas.height];
161
+ for (let i = 0; i < h; i++) {
162
+ for (let j = 0; j < w; j++) {
163
+ let l = 4 * Math.max(j - 1, 0);
164
+ let r = 4 * Math.min(j + 1, w - 1);
165
+ let t = 4 * Math.max(i - 1, 0) * w;
166
+ let b = 4 * Math.min(i + 1, h - 1) * w;
167
+ let oi = 4 * i * w;
168
+ let oj = 4 * j;
169
+ for (let k = 0; k < 4; k++) {
170
+ let kt = k + t;
171
+ let kb = k + b;
172
+ let ko = k + oi;
173
+ data[oi + oj + k] = Math.min(
174
+ /*tmpBuf[kt+l],*/ tmpBuf[kt + oj] /*tmpBuf[kt+r],*/,
175
+ tmpBuf[ko + l],
176
+ tmpBuf[ko + oj],
177
+ tmpBuf[ko + r],
178
+ /*tmpBuf[kb+l],*/ tmpBuf[kb + oj] /*tmpBuf[kb+r],*/
179
+ );
180
+ }
181
+ }
182
+ }
455
183
  };
456
184
 
457
- //================================================================
458
- // COLORS
459
- //================================================================
185
+ filterImpl[$.BLUR] = (data, rad) => {
186
+ rad = rad || 1;
187
+ rad = Math.floor(rad * $._pixelDensity);
188
+ makeTmpBuf();
189
+ tmpBuf.set(data);
460
190
 
461
- $.Color = Q5.Color;
462
- $.colorMode = (mode) => {
463
- $._colorMode = mode;
464
- };
191
+ let ksize = rad * 2 + 1;
465
192
 
466
- let basicColors = {
467
- aqua: [0, 255, 255],
468
- black: [0, 0, 0],
469
- blue: [0, 0, 255],
470
- brown: [165, 42, 42],
471
- crimson: [220, 20, 60],
472
- darkviolet: [148, 0, 211],
473
- gold: [255, 215, 0],
474
- green: [0, 128, 0],
475
- gray: [128, 128, 128],
476
- hotpink: [255, 105, 180],
477
- indigo: [75, 0, 130],
478
- khaki: [240, 230, 140],
479
- lightgreen: [144, 238, 144],
480
- lime: [0, 255, 0],
481
- magenta: [255, 0, 255],
482
- navy: [0, 0, 128],
483
- orange: [255, 165, 0],
484
- olive: [128, 128, 0],
485
- peachpuff: [255, 218, 185],
486
- pink: [255, 192, 203],
487
- purple: [128, 0, 128],
488
- red: [255, 0, 0],
489
- skyblue: [135, 206, 235],
490
- tan: [210, 180, 140],
491
- turquoise: [64, 224, 208],
492
- transparent: [0, 0, 0, 0],
493
- white: [255, 255, 255],
494
- violet: [238, 130, 238],
495
- yellow: [255, 255, 0]
496
- };
193
+ function gauss1d(ksize) {
194
+ let im = new Float32Array(ksize);
195
+ let sigma = 0.3 * rad + 0.8;
196
+ let ss2 = sigma * sigma * 2;
197
+ for (let i = 0; i < ksize; i++) {
198
+ let x = i - ksize / 2;
199
+ let z = Math.exp(-(x * x) / ss2) / (2.5066282746 * sigma);
200
+ im[i] = z;
201
+ }
202
+ return im;
203
+ }
497
204
 
498
- $.color = function () {
499
- let args = arguments;
500
- if (args.length == 1) {
501
- if (typeof args[0] == 'string') {
502
- if (args[0][0] == '#') {
503
- return new Q5.Color(
504
- parseInt(args[0].slice(1, 3), 16),
505
- parseInt(args[0].slice(3, 5), 16),
506
- parseInt(args[0].slice(5, 7), 16),
507
- 1
508
- );
509
- } else {
510
- if (basicColors[args[0]]) {
511
- return new Q5.Color(...basicColors[args[0]], 1);
512
- }
513
- return new Q5.Color(0, 0, 0, 1);
205
+ let kern = gauss1d(ksize);
206
+ let [w, h] = [ctx.canvas.width, ctx.canvas.height];
207
+ for (let i = 0; i < h; i++) {
208
+ for (let j = 0; j < w; j++) {
209
+ let s0 = 0,
210
+ s1 = 0,
211
+ s2 = 0,
212
+ s3 = 0;
213
+ for (let k = 0; k < ksize; k++) {
214
+ let jk = Math.min(Math.max(j - rad + k, 0), w - 1);
215
+ let idx = 4 * (i * w + jk);
216
+ s0 += tmpBuf[idx] * kern[k];
217
+ s1 += tmpBuf[idx + 1] * kern[k];
218
+ s2 += tmpBuf[idx + 2] * kern[k];
219
+ s3 += tmpBuf[idx + 3] * kern[k];
514
220
  }
221
+ let idx = 4 * (i * w + j);
222
+ data[idx] = s0;
223
+ data[idx + 1] = s1;
224
+ data[idx + 2] = s2;
225
+ data[idx + 3] = s3;
515
226
  }
516
- if (typeof args[0] != 'number' && args[0].MAGIC == 0xc010a) {
517
- return args[0];
227
+ }
228
+ tmpBuf.set(data);
229
+ for (let i = 0; i < h; i++) {
230
+ for (let j = 0; j < w; j++) {
231
+ let s0 = 0,
232
+ s1 = 0,
233
+ s2 = 0,
234
+ s3 = 0;
235
+ for (let k = 0; k < ksize; k++) {
236
+ let ik = Math.min(Math.max(i - rad + k, 0), h - 1);
237
+ let idx = 4 * (ik * w + j);
238
+ s0 += tmpBuf[idx] * kern[k];
239
+ s1 += tmpBuf[idx + 1] * kern[k];
240
+ s2 += tmpBuf[idx + 2] * kern[k];
241
+ s3 += tmpBuf[idx + 3] * kern[k];
242
+ }
243
+ let idx = 4 * (i * w + j);
244
+ data[idx] = s0;
245
+ data[idx + 1] = s1;
246
+ data[idx + 2] = s2;
247
+ data[idx + 3] = s3;
518
248
  }
519
249
  }
520
- if ($._colorMode == $.RGB) {
521
- if (args.length == 1) {
522
- return new Q5.Color(args[0], args[0], args[0], 1);
523
- } else if (args.length == 2) {
524
- return new Q5.Color(args[0], args[0], args[0], args[1] / 255);
525
- } else if (args.length == 3) {
526
- return new Q5.Color(args[0], args[1], args[2], 1);
527
- } else if (args.length == 4) {
528
- return new Q5.Color(args[0], args[1], args[2], args[3] / 255);
250
+ };
251
+
252
+ function makeTmpCtx(w, h) {
253
+ if (tmpCtx == null) {
254
+ tmpCtx = document.createElement('canvas').getContext('2d');
255
+ }
256
+ h ??= w || ctx.canvas.height;
257
+ w ??= ctx.canvas.width;
258
+ if (tmpCtx.canvas.width != w || tmpCtx.canvas.height != h) {
259
+ tmpCtx.canvas.width = w;
260
+ tmpCtx.canvas.height = h;
261
+ }
262
+ }
263
+ function makeTmpCt2(w, h) {
264
+ if (tmpCt2 == null) {
265
+ tmpCt2 = document.createElement('canvas').getContext('2d');
266
+ }
267
+ h ??= w || ctx.canvas.height;
268
+ w ??= ctx.canvas.width;
269
+ if (tmpCt2.canvas.width != w || tmpCt2.canvas.height != h) {
270
+ tmpCt2.canvas.width = w;
271
+ tmpCt2.canvas.height = h;
272
+ }
273
+ }
274
+
275
+ function makeTmpBuf() {
276
+ let l = ctx.canvas.width * ctx.canvas.height * 4;
277
+ if (!tmpBuf || l != tmpBuf.length) {
278
+ tmpBuf = new Uint8ClampedArray(l);
279
+ }
280
+ }
281
+
282
+ function nativeFilter(filtstr) {
283
+ tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
284
+ tmpCtx.filter = filtstr;
285
+ tmpCtx.drawImage(ctx.canvas, 0, 0);
286
+ ctx.save();
287
+ ctx.resetTransform();
288
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
289
+ ctx.drawImage(tmpCtx.canvas, 0, 0);
290
+ ctx.restore();
291
+ }
292
+
293
+ $.filter = (typ, x) => {
294
+ let support = $.HARDWARE_FILTERS && ctx.filter != undefined;
295
+ if (support) {
296
+ makeTmpCtx();
297
+ if (typ == $.THRESHOLD) {
298
+ x ??= 0.5;
299
+ x = Math.max(x, 0.00001);
300
+ let b = Math.floor((0.5 / x) * 100);
301
+ nativeFilter(`saturate(0%) brightness(${b}%) contrast(1000000%)`);
302
+ } else if (typ == $.GRAY) {
303
+ nativeFilter(`saturate(0%)`);
304
+ } else if (typ == $.OPAQUE) {
305
+ tmpCtx.fillStyle = 'black';
306
+ tmpCtx.fillRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
307
+ tmpCtx.drawImage(ctx.canvas, 0, 0);
308
+ ctx.save();
309
+ ctx.resetTransform();
310
+ ctx.drawImage(tmpCtx.canvas, 0, 0);
311
+ ctx.restore();
312
+ } else if (typ == $.INVERT) {
313
+ nativeFilter(`invert(100%)`);
314
+ } else if (typ == $.BLUR) {
315
+ nativeFilter(`blur(${Math.ceil((x * $._pixelDensity) / 1) || 1}px)`);
316
+ } else {
317
+ let imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
318
+ filterImpl[typ](imgData.data, x);
319
+ ctx.putImageData(imgData, 0, 0);
529
320
  }
530
321
  } else {
531
- if (args.length == 1) {
532
- return new Q5.Color(...Q5.Color._hsv2rgb(0, 0, args[0] / 100), 1);
533
- } else if (args.length == 2) {
534
- return new Q5.Color(...Q5.Color._hsv2rgb(0, 0, args[0] / 100), args[1] / 255);
535
- } else if (args.length == 3) {
536
- return new Q5.Color(...Q5.Color._hsv2rgb(args[0], args[1] / 100, args[2] / 100), 1);
537
- } else if (args.length == 4) {
538
- return new Q5.Color(...Q5.Color._hsv2rgb(args[0], args[1] / 100, args[2] / 100), args[3]);
539
- }
322
+ let imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
323
+ filterImpl[typ](imgData.data, x);
324
+ ctx.putImageData(imgData, 0, 0);
540
325
  }
541
- return null;
542
326
  };
543
327
 
544
- $.red = (c) => {
545
- return c._r;
328
+ $.resize = (w, h) => {
329
+ makeTmpCtx();
330
+ tmpCtx.drawImage(ctx.canvas, 0, 0);
331
+ $.width = w;
332
+ $.height = h;
333
+ ctx.canvas.width = w * $._pixelDensity;
334
+ ctx.canvas.height = h * $._pixelDensity;
335
+ ctx.save();
336
+ ctx.resetTransform();
337
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
338
+ ctx.drawImage(tmpCtx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height);
339
+ ctx.restore();
546
340
  };
547
- $.green = (c) => {
548
- return c._g;
341
+
342
+ $.get = (x, y, w, h) => {
343
+ if (x !== undefined && w === undefined) {
344
+ let c = ctx.getImageData(x, y, 1, 1).data;
345
+ return new Q5.Color(c[0], c[1], c[2], c[3] / 255);
346
+ }
347
+ x = x || 0;
348
+ y = y || 0;
349
+ w = w || $.width;
350
+ h = h || $.height;
351
+ let img = $.createImage(w, h);
352
+ let mod = $._pixelDensity || 1;
353
+ let imgData = ctx.getImageData(x * mod, y * mod, w * mod, h * mod);
354
+ img.canvas.getContext('2d').putImageData(imgData, 0, 0);
355
+ return img;
549
356
  };
550
- $.blue = (c) => {
551
- return c._b;
357
+
358
+ $.set = (x, y, c) => {
359
+ if (c.MAGIC == $.MAGIC) {
360
+ let old = $._tint;
361
+ $._tint = null;
362
+ $.image(c, x, y);
363
+ $._tint = old;
364
+ return;
365
+ }
366
+ let mod = $._pixelDensity || 1;
367
+ for (let i = 0; i < mod; i++) {
368
+ for (let j = 0; j < mod; j++) {
369
+ let idx = 4 * ((y * mod + i) * ctx.canvas.width + x * mod + j);
370
+ $.pixels[idx] = c._r;
371
+ $.pixels[idx + 1] = c._g;
372
+ $.pixels[idx + 2] = c._b;
373
+ $.pixels[idx + 3] = c._a * 255;
374
+ }
375
+ }
552
376
  };
553
- $.alpha = (c) => {
554
- return c._a * 255;
377
+
378
+ $.tinted = function () {
379
+ let col = $.color(...Array.from(arguments));
380
+ let alpha = col._a;
381
+ col._a = 1;
382
+ makeTmpCtx();
383
+ tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
384
+ tmpCtx.fillStyle = col;
385
+ tmpCtx.fillRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
386
+ tmpCtx.globalCompositeOperation = 'multiply';
387
+ tmpCtx.drawImage(ctx.canvas, 0, 0);
388
+ tmpCtx.globalCompositeOperation = 'source-over';
389
+
390
+ ctx.save();
391
+ ctx.resetTransform();
392
+ let old = ctx.globalCompositeOperation;
393
+ ctx.globalCompositeOperation = 'source-in';
394
+ ctx.drawImage(tmpCtx.canvas, 0, 0);
395
+ ctx.globalCompositeOperation = old;
396
+ ctx.restore();
397
+
398
+ tmpCtx.globalAlpha = alpha;
399
+ tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
400
+ tmpCtx.drawImage(ctx.canvas, 0, 0);
401
+ tmpCtx.globalAlpha = 1;
402
+
403
+ ctx.save();
404
+ ctx.resetTransform();
405
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
406
+ ctx.drawImage(tmpCtx.canvas, 0, 0);
407
+ ctx.restore();
555
408
  };
556
- $.hue = (c) => {
557
- c._inferHSV();
558
- return c._h;
409
+ $.tint = function () {
410
+ $._tint = $.color(...Array.from(arguments));
559
411
  };
560
- $.saturation = (c) => {
561
- c._inferHSV();
562
- return c._s;
412
+ $.noTint = () => ($._tint = null);
413
+
414
+ $.mask = (img) => {
415
+ ctx.save();
416
+ ctx.resetTransform();
417
+ let old = ctx.globalCompositeOperation;
418
+ ctx.globalCompositeOperation = 'destination-in';
419
+ ctx.drawImage(img.canvas, 0, 0);
420
+ ctx.globalCompositeOperation = old;
421
+ ctx.restore();
563
422
  };
564
- $.brightness = (c) => {
565
- c._inferHSV();
566
- return c._v;
423
+
424
+ $._save = (data, name, ext) => {
425
+ name = name || 'untitled';
426
+ ext = ext || 'png';
427
+ if (ext == 'jpg' || ext == 'png') data = data.toDataURL();
428
+ else {
429
+ let type = 'text/plain';
430
+ if (ext == 'json') {
431
+ if (typeof data != 'string') data = JSON.stringify(data);
432
+ type = 'text/json';
433
+ }
434
+ data = new Blob([data], { type });
435
+ data = URL.createObjectURL(data);
436
+ }
437
+ let a = document.createElement('a');
438
+ a.href = data;
439
+ a.download = name + '.' + ext;
440
+ document.body.append(a);
441
+ a.click();
442
+ document.body.removeChild(a);
443
+ URL.revokeObjectURL(a.href);
567
444
  };
568
- $.lightness = (c) => {
569
- return ((0.2126 * c._r + 0.7152 * c._g + 0.0722 * c._b) * 100) / 255;
445
+ $.save = (a, b, c) => {
446
+ if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
447
+ c = b;
448
+ b = a;
449
+ a = ctx.canvas;
450
+ }
451
+ if (c) return $._save(a, b, c);
452
+ if (b) {
453
+ b = b.split('.');
454
+ $._save(a, b[0], b.at(-1));
455
+ } else $._save(a);
570
456
  };
457
+ $.canvas.save = $.save;
458
+ $.saveCanvas = $.save;
571
459
 
572
- function lerpHue(h0, h1, t) {
573
- var methods = [
574
- [Math.abs(h1 - h0), $.map(t, 0, 1, h0, h1)],
575
- [Math.abs(h1 + 360 - h0), $.map(t, 0, 1, h0, h1 + 360)],
576
- [Math.abs(h1 - 360 - h0), $.map(t, 0, 1, h0, h1 - 360)]
577
- ];
578
- methods.sort((x, y) => x[0] - y[0]);
579
- return (methods[0][1] + 720) % 360;
580
- }
460
+ if (scope == 'image') return;
581
461
 
582
- $.lerpColor = (a, b, t) => {
583
- if ($._colorMode == $.RGB) {
584
- return new Q5.Color(
585
- $.constrain($.lerp(a._r, b._r, t), 0, 255),
586
- $.constrain($.lerp(a._g, b._g, t), 0, 255),
587
- $.constrain($.lerp(a._b, b._b, t), 0, 255),
588
- $.constrain($.lerp(a._a, b._a, t), 0, 1)
589
- );
590
- } else {
591
- a._inferHSV();
592
- b._inferHSV();
593
- return new Q5.Color(
594
- $.constrain(lerpHue(a._h, b._h, t), 0, 360),
595
- $.constrain($.lerp(a._s, b._s, t), 0, 100),
596
- $.constrain($.lerp(a._v, b._v, t), 0, 100),
597
- $.constrain($.lerp(a._a, b._a, t), 0, 1)
598
- );
599
- }
462
+ $.remove = () => {
463
+ $.noLoop();
464
+ $.canvas.remove();
600
465
  };
601
466
 
602
467
  //================================================================
603
- // DRAWING SETTING
468
+ // CONSTANTS
469
+ //================================================================
470
+
471
+ $.RGB = 0;
472
+ $.HSV = 1;
473
+ $.HSB = 1;
474
+
475
+ $.CHORD = 0;
476
+ $.PIE = 1;
477
+ $.OPEN = 2;
478
+
479
+ $.RADIUS = 'radius';
480
+ $.CORNER = 'corner';
481
+ $.CORNERS = 'corners';
482
+
483
+ $.ROUND = 'round';
484
+ $.SQUARE = 'butt';
485
+ $.PROJECT = 'square';
486
+ $.MITER = 'miter';
487
+ $.BEVEL = 'bevel';
488
+
489
+ $.CLOSE = 1;
490
+
491
+ $.BLEND = 'source-over';
492
+ $.REMOVE = 'destination-out';
493
+ $.ADD = 'lighter';
494
+ $.DARKEST = 'darken';
495
+ $.LIGHTEST = 'lighten';
496
+ $.DIFFERENCE = 'difference';
497
+ $.SUBTRACT = 'subtract';
498
+ $.EXCLUSION = 'exclusion';
499
+ $.MULTIPLY = 'multiply';
500
+ $.SCREEN = 'screen';
501
+ $.REPLACE = 'copy';
502
+ $.OVERLAY = 'overlay';
503
+ $.HARD_LIGHT = 'hard-light';
504
+ $.SOFT_LIGHT = 'soft-light';
505
+ $.DODGE = 'color-dodge';
506
+ $.BURN = 'color-burn';
507
+
508
+ $.NORMAL = 'normal';
509
+ $.ITALIC = 'italic';
510
+ $.BOLD = 'bold';
511
+ $.BOLDITALIC = 'italic bold';
512
+
513
+ $.CENTER = 'center';
514
+ $.LEFT = 'left';
515
+ $.RIGHT = 'right';
516
+ $.TOP = 'top';
517
+ $.BOTTOM = 'bottom';
518
+ $.BASELINE = 'alphabetic';
519
+
520
+ $.LANDSCAPE = 'landscape';
521
+ $.PORTRAIT = 'portrait';
522
+
523
+ $.ALT = 18;
524
+ $.BACKSPACE = 8;
525
+ $.CONTROL = 17;
526
+ $.DELETE = 46;
527
+ $.DOWN_ARROW = 40;
528
+ $.ENTER = 13;
529
+ $.ESCAPE = 27;
530
+ $.LEFT_ARROW = 37;
531
+ $.OPTION = 18;
532
+ $.RETURN = 13;
533
+ $.RIGHT_ARROW = 39;
534
+ $.SHIFT = 16;
535
+ $.TAB = 9;
536
+ $.UP_ARROW = 38;
537
+
538
+ $.DEGREES = 'degrees';
539
+ $.RADIANS = 'radians';
540
+
541
+ $.HALF_PI = Math.PI / 2;
542
+ $.PI = Math.PI;
543
+ $.QUARTER_PI = Math.PI / 4;
544
+ $.TAU = Math.PI * 2;
545
+ $.TWO_PI = Math.PI * 2;
546
+
547
+ $.THRESHOLD = 1;
548
+ $.GRAY = 2;
549
+ $.OPAQUE = 3;
550
+ $.INVERT = 4;
551
+ $.POSTERIZE = 5;
552
+ $.DILATE = 6;
553
+ $.ERODE = 7;
554
+ $.BLUR = 8;
555
+
556
+ $.ARROW = 'default';
557
+ $.CROSS = 'crosshair';
558
+ $.HAND = 'pointer';
559
+ $.MOVE = 'move';
560
+ $.TEXT = 'text';
561
+
562
+ $.VIDEO = { video: true, audio: false };
563
+ $.AUDIO = { video: false, audio: true };
564
+
565
+ $.SHR3 = 1;
566
+ $.LCG = 2;
567
+
568
+ $.HARDWARE_FILTERS = true;
569
+ $.hint = (prop, val) => {
570
+ $[prop] = val;
571
+ };
572
+
573
+ //================================================================
574
+ // PUBLIC PROPERTIES
575
+ //================================================================
576
+ $.frameCount = 0;
577
+ $.deltaTime = 16;
578
+ $.mouseX = 0;
579
+ $.mouseY = 0;
580
+ $.mouseButton = null;
581
+ $.keyIsPressed = false;
582
+ $.mouseIsPressed = false;
583
+ $.key = null;
584
+ $.keyCode = null;
585
+ $.accelerationX = 0;
586
+ $.accelerationY = 0;
587
+ $.accelerationZ = 0;
588
+ $.rotationX = 0;
589
+ $.rotationY = 0;
590
+ $.rotationZ = 0;
591
+ $.relRotationX = 0;
592
+ $.relRotationY = 0;
593
+ $.relRotationZ = 0;
594
+
595
+ $.pmouseX = 0;
596
+ $.pmouseY = 0;
597
+ $.pAccelerationX = 0;
598
+ $.pAccelerationY = 0;
599
+ $.pAccelerationZ = 0;
600
+ $.pRotationX = 0;
601
+ $.pRotationY = 0;
602
+ $.pRotationZ = 0;
603
+ $.pRelRotationX = 0;
604
+ $.pRelRotationY = 0;
605
+ $.pRelRotationZ = 0;
606
+
607
+ $.touches = [];
608
+
609
+ $._colorMode = $.RGB;
610
+ $._doStroke = true;
611
+ $._doFill = true;
612
+ $._strokeSet = false;
613
+ $._fillSet = false;
614
+ $._ellipseMode = $.CENTER;
615
+ $._rectMode = $.CORNER;
616
+ $._curveDetail = 20;
617
+ $._curveAlpha = 0.0;
618
+ $._loop = true;
619
+
620
+ $._textFont = 'sans-serif';
621
+ $._textSize = 12;
622
+ $._textLeading = 15;
623
+ $._textLeadDiff = 3;
624
+ $._textStyle = 'normal';
625
+
626
+ $._pixelDensity = 1;
627
+ $._lastFrameTime = 0;
628
+ $._targetFrameRate = null;
629
+ $._frameRate = $._fps = 60;
630
+
631
+ $._tint = null;
632
+
633
+ //================================================================
634
+ // PRIVATE VARS
635
+ //================================================================
636
+ let looper = null;
637
+ let firstVertex = true;
638
+ let curveBuff = [];
639
+ let preloadCnt = 0;
640
+ let keysHeld = {};
641
+ let millisStart = 0;
642
+ let tmpCtx = null;
643
+ let tmpCt2 = null;
644
+ let tmpBuf = null;
645
+
646
+ //================================================================
647
+ // ALIAS PROPERTIES
604
648
  //================================================================
605
649
 
606
- function defaultStyle() {
607
- ctx.fillStyle = 'white';
608
- ctx.strokeStyle = 'black';
609
- ctx.lineCap = 'round';
610
- ctx.lineJoin = 'miter';
611
- ctx.textAlign = 'left';
612
- }
613
-
614
- $.strokeWeight = (n) => {
615
- if (n > 0) $._doStroke = true;
616
- else $._doStroke = false;
617
- ctx.lineWidth = n;
618
- };
619
- $.stroke = function () {
620
- $._doStroke = true;
621
- $._strokeSet = true;
622
- if (typeof arguments[0] == 'string') {
623
- ctx.strokeStyle = arguments[0];
624
- return;
625
- }
626
- let col = $.color(...arguments);
627
- if (col._a <= 0) {
628
- $._doStroke = false;
629
- return;
630
- }
631
- ctx.strokeStyle = col;
632
- };
633
- $.noStroke = () => ($._doStroke = false);
634
- $.fill = function () {
635
- $._doFill = true;
636
- $._fillSet = true;
637
- if (typeof arguments[0] == 'string') {
638
- ctx.fillStyle = arguments[0];
639
- return;
640
- }
641
- let col = $.color(...arguments);
642
- if (col._a <= 0) {
643
- $._doFill = false;
644
- return;
645
- }
646
- ctx.fillStyle = col;
647
- };
648
- $.noFill = () => ($._doFill = false);
649
- $.smooth = () => ($._smooth = true);
650
- $.noSmooth = () => ($._smooth = false);
651
- $.blendMode = (x) => (ctx.globalCompositeOperation = x);
652
- $.strokeCap = (x) => (ctx.lineCap = x);
653
- $.strokeJoin = (x) => (ctx.lineJoin = x);
654
- $.ellipseMode = (x) => ($._ellipseMode = x);
655
- $.rectMode = (x) => ($._rectMode = x);
656
- $.curveDetail = (x) => ($._curveDetail = x);
657
- $.curveAlpha = (x) => ($._curveAlpha = x);
658
- $.curveTightness = (x) => ($._curveAlpha = x);
650
+ Object.defineProperty($, 'deviceOrientation', {
651
+ get: () => window.screen?.orientation?.type
652
+ });
659
653
 
660
- //================================================================
661
- // DRAWING
662
- //================================================================
654
+ Object.defineProperty($, 'windowWidth', {
655
+ get: () => window.innerWidth
656
+ });
663
657
 
664
- $.clear = () => {
665
- ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
666
- };
658
+ Object.defineProperty($, 'windowHeight', {
659
+ get: () => window.innerHeight
660
+ });
667
661
 
668
- $.background = function () {
669
- if (arguments[0] && arguments[0].MAGIC == $.MAGIC) {
670
- return $.image(arguments[0], 0, 0, $.width, $.height);
671
- }
672
- ctx.save();
673
- ctx.resetTransform();
674
- if (typeof arguments[0] == 'string') {
675
- ctx.fillStyle = arguments[0];
676
- } else {
677
- ctx.fillStyle = $.color(...Array.from(arguments));
678
- }
679
- ctx.fillRect(0, 0, $.canvas.width, $.canvas.height);
680
- ctx.restore();
681
- };
662
+ Object.defineProperty($, 'drawingContext', {
663
+ get: () => ctx
664
+ });
682
665
 
683
- $.line = (x0, y0, x1, y1) => {
684
- if ($._doStroke) {
685
- ctx.beginPath();
686
- ctx.moveTo(x0, y0);
687
- ctx.lineTo(x1, y1);
688
- ctx.stroke();
689
- }
690
- };
666
+ //================================================================
667
+ // CANVAS
668
+ //================================================================
691
669
 
692
- function normAng(x) {
693
- let mid = $._angleMode == $.DEGREES ? 180 : Math.PI;
694
- let full = mid * 2;
695
- if (0 <= x && x <= full) return x;
696
- while (x < 0) {
697
- x += full;
698
- }
699
- while (x >= mid) {
700
- x -= full;
670
+ function cloneCtx() {
671
+ let c = {};
672
+ for (let prop in ctx) {
673
+ if (typeof ctx[prop] != 'function') c[prop] = ctx[prop];
701
674
  }
702
- return x;
675
+ delete c.canvas;
676
+ return c;
703
677
  }
704
678
 
705
- function arcImpl(x, y, w, h, start, stop, mode, detail) {
706
- if (!$._doFill && !$._doStroke) return;
707
- let lo = normAng(start);
708
- let hi = normAng(stop);
709
- if (lo > hi) [lo, hi] = [hi, lo];
710
- if (lo == 0) {
711
- if (hi == 0) return;
712
- if (($._angleMode == $.DEGREES && hi == 360) || hi == $.TAU) {
713
- return $.ellipse(x, y, w, h);
714
- }
715
- }
716
- ctx.beginPath();
717
- for (let i = 0; i < detail + 1; i++) {
718
- let t = i / detail;
719
- let a = $.lerp(lo, hi, t);
720
- let dx = ($.cos(a) * w) / 2;
721
- let dy = ($.sin(a) * h) / 2;
722
- ctx[i ? 'lineTo' : 'moveTo'](x + dx, y + dy);
723
- }
724
- if (mode == $.CHORD) {
725
- ctx.closePath();
726
- } else if (mode == $.PIE) {
727
- ctx.lineTo(x, y);
728
- ctx.closePath();
729
- }
730
- if ($._doFill) ctx.fill();
731
- if ($._doStroke) ctx.stroke();
732
- }
733
- $.arc = (x, y, w, h, start, stop, mode, detail) => {
734
- if (start == stop) return $.ellipse(x, y, w, h);
735
- detail ??= 25;
736
- mode ??= $.PIE;
737
- if ($._ellipseMode == $.CENTER) {
738
- arcImpl(x, y, w, h, start, stop, mode, detail);
739
- } else if ($._ellipseMode == $.RADIUS) {
740
- arcImpl(x, y, w * 2, h * 2, start, stop, mode, detail);
741
- } else if ($._ellipseMode == $.CORNER) {
742
- arcImpl(x + w / 2, y + h / 2, w, h, start, stop, mode, detail);
743
- } else if ($._ellipseMode == $.CORNERS) {
744
- arcImpl((x + w) / 2, (y + h) / 2, w - x, h - y, start, stop, mode, detail);
745
- }
679
+ $.resizeCanvas = (width, height) => {
680
+ $.width = width;
681
+ $.height = height;
682
+ let c = cloneCtx();
683
+ $.canvas.width = width * $._pixelDensity;
684
+ $.canvas.height = height * $._pixelDensity;
685
+ ctx = $._ctx = $.canvas.getContext('2d');
686
+ for (let prop in c) $._ctx[prop] = c[prop];
687
+ if (scope != 'graphics' && scope != 'image') $.pixelDensity($._pixelDensity);
746
688
  };
747
689
 
748
- function ellipseImpl(x, y, w, h) {
749
- if (!$._doFill && !$._doStroke) return;
750
- ctx.beginPath();
751
- ctx.ellipse(x, y, w / 2, h / 2, 0, 0, $.TAU);
752
- if ($._doFill) ctx.fill();
753
- if ($._doStroke) ctx.stroke();
754
- }
755
- $.ellipse = (x, y, w, h) => {
756
- h ??= w;
757
- if ($._ellipseMode == $.CENTER) {
758
- ellipseImpl(x, y, w, h);
759
- } else if ($._ellipseMode == $.RADIUS) {
760
- ellipseImpl(x, y, w * 2, h * 2);
761
- } else if ($._ellipseMode == $.CORNER) {
762
- ellipseImpl(x + w / 2, y + h / 2, w, h);
763
- } else if ($._ellipseMode == $.CORNERS) {
764
- ellipseImpl((x + w) / 2, (y + h) / 2, w - x, h - y);
765
- }
766
- };
767
- $.circle = (x, y, r) => {
768
- return $.ellipse(x, y, r, r);
690
+ $.createGraphics = (width, height) => {
691
+ let g = new Q5('graphics');
692
+ g.createCanvas(width, height);
693
+ return g;
769
694
  };
770
- $.point = (x, y) => {
771
- if (x.x) {
772
- y = x.y;
773
- x = x.x;
774
- }
775
- ctx.beginPath();
776
- ctx.ellipse(x, y, 0.4, 0.4, 0, 0, $.TAU);
777
- ctx.stroke();
695
+ $.createImage = (width, height) => {
696
+ return new Q5.Image(width, height);
778
697
  };
779
- function rectImpl(x, y, w, h) {
780
- if ($._doFill) ctx.fillRect(x, y, w, h);
781
- if ($._doStroke) ctx.strokeRect(x, y, w, h);
782
- }
783
- function roundedRectImpl(x, y, w, h, tl, tr, br, bl) {
784
- if (!$._doFill && !$._doStroke) return;
785
- if (tl === undefined) {
786
- return rectImpl(x, y, w, h);
787
- }
788
- if (tr === undefined) {
789
- return roundedRectImpl(x, y, w, h, tl, tl, tl, tl);
790
- }
791
- const hh = Math.min(Math.abs(h), Math.abs(w)) / 2;
792
- tl = Math.min(hh, tl);
793
- tr = Math.min(hh, tr);
794
- bl = Math.min(hh, bl);
795
- br = Math.min(hh, br);
796
- ctx.beginPath();
797
- ctx.moveTo(x + tl, y);
798
- ctx.arcTo(x + w, y, x + w, y + h, tr);
799
- ctx.arcTo(x + w, y + h, x, y + h, br);
800
- ctx.arcTo(x, y + h, x, y, bl);
801
- ctx.arcTo(x, y, x + w, y, tl);
802
- ctx.closePath();
803
- if ($._doFill) ctx.fill();
804
- if ($._doStroke) ctx.stroke();
805
- }
698
+ $.displayDensity = () => window.devicePixelRatio;
699
+ $.pixelDensity = (n) => {
700
+ if (n === undefined) return $._pixelDensity;
701
+ $._pixelDensity = n;
806
702
 
807
- $.rect = (x, y, w, h, tl, tr, br, bl) => {
808
- if ($._rectMode == $.CENTER) {
809
- roundedRectImpl(x - w / 2, y - h / 2, w, h, tl, tr, br, bl);
810
- } else if ($._rectMode == $.RADIUS) {
811
- roundedRectImpl(x - w, y - h, w * 2, h * 2, tl, tr, br, bl);
812
- } else if ($._rectMode == $.CORNER) {
813
- roundedRectImpl(x, y, w, h, tl, tr, br, bl);
814
- } else if ($._rectMode == $.CORNERS) {
815
- roundedRectImpl(x, y, w - x, h - y, tl, tr, br, bl);
816
- }
817
- };
818
- $.square = (x, y, s, tl, tr, br, bl) => {
819
- return $.rect(x, y, s, s, tl, tr, br, bl);
703
+ let c = cloneCtx();
704
+ $.canvas.width = Math.ceil($.width * n);
705
+ $.canvas.height = Math.ceil($.height * n);
706
+ $.canvas.style.width = $.width + 'px';
707
+ $.canvas.style.height = $.height + 'px';
708
+ ctx = $._ctx = $.canvas.getContext('2d');
709
+ for (let prop in c) $._ctx[prop] = c[prop];
710
+
711
+ ctx.scale($._pixelDensity, $._pixelDensity);
712
+ return $._pixelDensity;
820
713
  };
821
714
 
822
- function clearBuff() {
823
- curveBuff = [];
824
- }
715
+ //================================================================
716
+ // MATH
717
+ //================================================================
825
718
 
826
- $.beginShape = () => {
827
- clearBuff();
828
- ctx.beginPath();
829
- firstVertex = true;
830
- };
831
- $.beginContour = () => {
832
- ctx.closePath();
833
- clearBuff();
834
- firstVertex = true;
835
- };
836
- $.endContour = () => {
837
- clearBuff();
838
- firstVertex = true;
719
+ $.map = (value, istart, istop, ostart, ostop, clamp) => {
720
+ let val = ostart + (ostop - ostart) * (((value - istart) * 1.0) / (istop - istart));
721
+ if (!clamp) {
722
+ return val;
723
+ }
724
+ if (ostart < ostop) {
725
+ return Math.min(Math.max(val, ostart), ostop);
726
+ } else {
727
+ return Math.min(Math.max(val, ostop), ostart);
728
+ }
839
729
  };
840
- $.vertex = (x, y) => {
841
- clearBuff();
842
- if (firstVertex) {
843
- ctx.moveTo(x, y);
730
+ $.lerp = (a, b, t) => a * (1 - t) + b * t;
731
+ $.constrain = (x, lo, hi) => Math.min(Math.max(x, lo), hi);
732
+ $.dist = function () {
733
+ if (arguments.length == 4) {
734
+ return Math.hypot(arguments[0] - arguments[2], arguments[1] - arguments[3]);
844
735
  } else {
845
- ctx.lineTo(x, y);
736
+ return Math.hypot(arguments[0] - arguments[3], arguments[1] - arguments[4], arguments[2] - arguments[5]);
846
737
  }
847
- firstVertex = false;
848
738
  };
849
- $.bezierVertex = (cp1x, cp1y, cp2x, cp2y, x, y) => {
850
- clearBuff();
851
- ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
739
+ $.norm = (value, start, stop) => $.map(value, start, stop, 0, 1);
740
+ $.sq = (x) => x * x;
741
+ $.fract = (x) => x - Math.floor(x);
742
+ $.angleMode = (mode) => ($._angleMode = mode);
743
+ $._DEGTORAD = Math.PI / 180;
744
+ $._RADTODEG = 180 / Math.PI;
745
+ $.degrees = (x) => x * $._RADTODEG;
746
+ $.radians = (x) => x * $._DEGTORAD;
747
+ $.abs = Math.abs;
748
+ $.ceil = Math.ceil;
749
+ $.exp = Math.exp;
750
+ $.floor = Math.floor;
751
+ $.log = Math.log;
752
+ $.mag = Math.hypot;
753
+ $.max = Math.max;
754
+ $.min = Math.min;
755
+ $.round = Math.round;
756
+ $.pow = Math.pow;
757
+ $.sqrt = Math.sqrt;
758
+ $.sin = (a) => {
759
+ if ($._angleMode == 'degrees') a = $.radians(a);
760
+ return Math.sin(a);
852
761
  };
853
- $.quadraticVertex = (cp1x, cp1y, x, y) => {
854
- clearBuff();
855
- ctx.quadraticCurveTo(cp1x, cp1y, x, y);
762
+ $.cos = (a) => {
763
+ if ($._angleMode == 'degrees') a = $.radians(a);
764
+ return Math.cos(a);
856
765
  };
857
- $.bezier = (x1, y1, x2, y2, x3, y3, x4, y4) => {
858
- $.beginShape();
859
- $.vertex(x1, y1);
860
- $.bezierVertex(x2, y2, x3, y3, x4, y4);
861
- $.endShape();
766
+ $.tan = (a) => {
767
+ if ($._angleMode == 'degrees') a = $.radians(a);
768
+ return Math.tan(a);
862
769
  };
863
- $.triangle = (x1, y1, x2, y2, x3, y3) => {
864
- $.beginShape();
865
- $.vertex(x1, y1);
866
- $.vertex(x2, y2);
867
- $.vertex(x3, y3);
868
- $.endShape($.CLOSE);
770
+ $.asin = (x) => {
771
+ let a = Math.asin(x);
772
+ if ($._angleMode == 'degrees') a = $.degrees(a);
773
+ return a;
869
774
  };
870
- $.quad = (x1, y1, x2, y2, x3, y3, x4, y4) => {
871
- $.beginShape();
872
- $.vertex(x1, y1);
873
- $.vertex(x2, y2);
874
- $.vertex(x3, y3);
875
- $.vertex(x4, y4);
876
- $.endShape($.CLOSE);
775
+ $.acos = (x) => {
776
+ let a = Math.acos(x);
777
+ if ($._angleMode == 'degrees') a = $.degrees(a);
778
+ return a;
877
779
  };
878
- $.endShape = (close) => {
879
- clearBuff();
880
- if (close) {
881
- ctx.closePath();
882
- }
883
- if ($._doFill) ctx.fill();
884
- if ($._doStroke) ctx.stroke();
885
- if (!$._doFill && !$._doStroke) {
886
- // eh.
887
- ctx.save();
888
- ctx.fillStyle = 'none';
889
- ctx.fill();
890
- ctx.restore();
891
- }
780
+ $.atan = (x) => {
781
+ let a = Math.atan(x);
782
+ if ($._angleMode == 'degrees') a = $.degrees(a);
783
+ return a;
892
784
  };
893
- function catmullRomSpline(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, numPts, alpha) {
894
- //https://en.wikipedia.org/wiki/Centripetal_Catmull–Rom_spline
895
- function catmullromSplineGetT(t, p0x, p0y, p1x, p1y, alpha) {
896
- let a = Math.pow(p1x - p0x, 2.0) + Math.pow(p1y - p0y, 2.0);
897
- let b = Math.pow(a, alpha * 0.5);
898
- return b + t;
785
+ $.atan2 = (y, x) => {
786
+ let a = Math.atan2(y, x);
787
+ if ($._angleMode == 'degrees') a = $.degrees(a);
788
+ return a;
789
+ };
790
+ $.nf = (n, l, r) => {
791
+ let neg = n < 0;
792
+ let s = n.toString();
793
+ if (neg) s = s.slice(1);
794
+ s = s.padStart(l, '0');
795
+ if (r > 0) {
796
+ if (s.indexOf('.') == -1) s += '.';
797
+ s = s.padEnd(l + 1 + r, '0');
899
798
  }
900
- let pts = [];
799
+ if (neg) s = '-' + s;
800
+ return s;
801
+ };
802
+ $.createVector = (x, y, z) => new Q5.Vector(x, y, z, $);
901
803
 
902
- let t0 = 0.0;
903
- let t1 = catmullromSplineGetT(t0, p0x, p0y, p1x, p1y, alpha);
904
- let t2 = catmullromSplineGetT(t1, p1x, p1y, p2x, p2y, alpha);
905
- let t3 = catmullromSplineGetT(t2, p2x, p2y, p3x, p3y, alpha);
804
+ //================================================================
805
+ // CURVE QUERY
806
+ //================================================================
807
+ //https://github.com/processing/p5.js/blob/1.1.9/src/core/shape/curves.js
906
808
 
907
- for (let i = 0; i < numPts; i++) {
908
- let t = t1 + (i / (numPts - 1)) * (t2 - t1);
909
- let s = [
910
- (t1 - t) / (t1 - t0),
911
- (t - t0) / (t1 - t0),
912
- (t2 - t) / (t2 - t1),
913
- (t - t1) / (t2 - t1),
914
- (t3 - t) / (t3 - t2),
915
- (t - t2) / (t3 - t2),
916
- (t2 - t) / (t2 - t0),
917
- (t - t0) / (t2 - t0),
918
- (t3 - t) / (t3 - t1),
919
- (t - t1) / (t3 - t1)
920
- ];
921
- for (let j = 0; j < s.length; j += 2) {
922
- if (isNaN(s[j])) {
923
- s[j] = 1;
924
- s[j + 1] = 0;
925
- }
926
- if (!isFinite(s[j])) {
927
- if (s[j] > 0) {
928
- s[j] = 1;
929
- s[j + 1] = 0;
930
- } else {
931
- s[j] = 0;
932
- s[j + 1] = 1;
809
+ $.curvePoint = (a, b, c, d, t) => {
810
+ const t3 = t * t * t,
811
+ t2 = t * t,
812
+ f1 = -0.5 * t3 + t2 - 0.5 * t,
813
+ f2 = 1.5 * t3 - 2.5 * t2 + 1.0,
814
+ f3 = -1.5 * t3 + 2.0 * t2 + 0.5 * t,
815
+ f4 = 0.5 * t3 - 0.5 * t2;
816
+ return a * f1 + b * f2 + c * f3 + d * f4;
817
+ };
818
+ $.bezierPoint = (a, b, c, d, t) => {
819
+ const adjustedT = 1 - t;
820
+ return (
821
+ Math.pow(adjustedT, 3) * a +
822
+ 3 * Math.pow(adjustedT, 2) * t * b +
823
+ 3 * adjustedT * Math.pow(t, 2) * c +
824
+ Math.pow(t, 3) * d
825
+ );
826
+ };
827
+ $.curveTangent = (a, b, c, d, t) => {
828
+ const t2 = t * t,
829
+ f1 = (-3 * t2) / 2 + 2 * t - 0.5,
830
+ f2 = (9 * t2) / 2 - 5 * t,
831
+ f3 = (-9 * t2) / 2 + 4 * t + 0.5,
832
+ f4 = (3 * t2) / 2 - t;
833
+ return a * f1 + b * f2 + c * f3 + d * f4;
834
+ };
835
+ $.bezierTangent = (a, b, c, d, t) => {
836
+ const adjustedT = 1 - t;
837
+ return (
838
+ 3 * d * Math.pow(t, 2) -
839
+ 3 * c * Math.pow(t, 2) +
840
+ 6 * c * adjustedT * t -
841
+ 6 * b * adjustedT * t +
842
+ 3 * b * Math.pow(adjustedT, 2) -
843
+ 3 * a * Math.pow(adjustedT, 2)
844
+ );
845
+ };
846
+
847
+ //================================================================
848
+ // COLORS
849
+ //================================================================
850
+
851
+ $.Color = Q5.Color;
852
+ $.colorMode = (mode) => {
853
+ $._colorMode = mode;
854
+ };
855
+
856
+ let basicColors = {
857
+ aqua: [0, 255, 255],
858
+ black: [0, 0, 0],
859
+ blue: [0, 0, 255],
860
+ brown: [165, 42, 42],
861
+ crimson: [220, 20, 60],
862
+ darkviolet: [148, 0, 211],
863
+ gold: [255, 215, 0],
864
+ green: [0, 128, 0],
865
+ gray: [128, 128, 128],
866
+ hotpink: [255, 105, 180],
867
+ indigo: [75, 0, 130],
868
+ khaki: [240, 230, 140],
869
+ lightgreen: [144, 238, 144],
870
+ lime: [0, 255, 0],
871
+ magenta: [255, 0, 255],
872
+ navy: [0, 0, 128],
873
+ orange: [255, 165, 0],
874
+ olive: [128, 128, 0],
875
+ peachpuff: [255, 218, 185],
876
+ pink: [255, 192, 203],
877
+ purple: [128, 0, 128],
878
+ red: [255, 0, 0],
879
+ skyblue: [135, 206, 235],
880
+ tan: [210, 180, 140],
881
+ turquoise: [64, 224, 208],
882
+ transparent: [0, 0, 0, 0],
883
+ white: [255, 255, 255],
884
+ violet: [238, 130, 238],
885
+ yellow: [255, 255, 0]
886
+ };
887
+
888
+ $.color = function () {
889
+ let args = arguments;
890
+ if (args.length == 1) {
891
+ if (typeof args[0] == 'string') {
892
+ if (args[0][0] == '#') {
893
+ return new Q5.Color(
894
+ parseInt(args[0].slice(1, 3), 16),
895
+ parseInt(args[0].slice(3, 5), 16),
896
+ parseInt(args[0].slice(5, 7), 16),
897
+ 1
898
+ );
899
+ } else {
900
+ if (basicColors[args[0]]) {
901
+ return new Q5.Color(...basicColors[args[0]], 1);
933
902
  }
903
+ return new Q5.Color(0, 0, 0, 1);
934
904
  }
935
905
  }
936
- let a1x = p0x * s[0] + p1x * s[1];
937
- let a1y = p0y * s[0] + p1y * s[1];
938
- let a2x = p1x * s[2] + p2x * s[3];
939
- let a2y = p1y * s[2] + p2y * s[3];
940
- let a3x = p2x * s[4] + p3x * s[5];
941
- let a3y = p2y * s[4] + p3y * s[5];
942
- let b1x = a1x * s[6] + a2x * s[7];
943
- let b1y = a1y * s[6] + a2y * s[7];
944
- let b2x = a2x * s[8] + a3x * s[9];
945
- let b2y = a2y * s[8] + a3y * s[9];
946
- let cx = b1x * s[2] + b2x * s[3];
947
- let cy = b1y * s[2] + b2y * s[3];
948
- pts.push([cx, cy]);
949
- }
950
- return pts;
951
- }
952
-
953
- $.curveVertex = (x, y) => {
954
- curveBuff.push([x, y]);
955
- if (curveBuff.length < 4) {
956
- return;
906
+ if (typeof args[0] != 'number' && args[0].MAGIC == 0xc010a) {
907
+ return args[0];
908
+ }
957
909
  }
958
- let p0 = curveBuff[curveBuff.length - 4];
959
- let p1 = curveBuff[curveBuff.length - 3];
960
- let p2 = curveBuff[curveBuff.length - 2];
961
- let p3 = curveBuff[curveBuff.length - 1];
962
- let pts = catmullRomSpline(...p0, ...p1, ...p2, ...p3, $._curveDetail, $._curveAlpha);
963
- for (let i = 0; i < pts.length; i++) {
964
- if (firstVertex) {
965
- ctx.moveTo(...pts[i]);
966
- } else {
967
- ctx.lineTo(...pts[i]);
910
+ if ($._colorMode == $.RGB) {
911
+ if (args.length == 1) {
912
+ return new Q5.Color(args[0], args[0], args[0], 1);
913
+ } else if (args.length == 2) {
914
+ return new Q5.Color(args[0], args[0], args[0], args[1] / 255);
915
+ } else if (args.length == 3) {
916
+ return new Q5.Color(args[0], args[1], args[2], 1);
917
+ } else if (args.length == 4) {
918
+ return new Q5.Color(args[0], args[1], args[2], args[3] / 255);
919
+ }
920
+ } else {
921
+ if (args.length == 1) {
922
+ return new Q5.Color(...Q5.Color._hsv2rgb(0, 0, args[0] / 100), 1);
923
+ } else if (args.length == 2) {
924
+ return new Q5.Color(...Q5.Color._hsv2rgb(0, 0, args[0] / 100), args[1] / 255);
925
+ } else if (args.length == 3) {
926
+ return new Q5.Color(...Q5.Color._hsv2rgb(args[0], args[1] / 100, args[2] / 100), 1);
927
+ } else if (args.length == 4) {
928
+ return new Q5.Color(...Q5.Color._hsv2rgb(args[0], args[1] / 100, args[2] / 100), args[3]);
968
929
  }
969
- firstVertex = false;
970
930
  }
971
- };
972
- $.curve = (x1, y1, x2, y2, x3, y3, x4, y4) => {
973
- $.beginShape();
974
- $.curveVertex(x1, y1);
975
- $.curveVertex(x2, y2);
976
- $.curveVertex(x3, y3);
977
- $.curveVertex(x4, y4);
978
- $.endShape();
931
+ return null;
979
932
  };
980
933
 
981
- //================================================================
982
- // DRAWING MATRIX
983
- //================================================================
984
- $.translate = (x, y) => ctx.translate(x, y);
985
- $.rotate = (r) => {
986
- if ($._angleMode == 'degrees') r = $.radians(r);
987
- ctx.rotate(r);
934
+ $.red = (c) => {
935
+ return c._r;
988
936
  };
989
-
990
- $.scale = (x, y) => {
991
- y ??= x;
992
- ctx.scale(x, y);
937
+ $.green = (c) => {
938
+ return c._g;
993
939
  };
994
- $.applyMatrix = (a, b, c, d, e, f) => {
995
- ctx.transform(a, b, c, d, e, f);
940
+ $.blue = (c) => {
941
+ return c._b;
996
942
  };
997
- $.shearX = (ang) => {
998
- ctx.transform(1, 0, $.tan(ang), 1, 0, 0);
943
+ $.alpha = (c) => {
944
+ return c._a * 255;
999
945
  };
1000
- $.shearY = (ang) => {
1001
- ctx.transform(1, $.tan(ang), 0, 1, 0, 0);
946
+ $.hue = (c) => {
947
+ c._inferHSV();
948
+ return c._h;
1002
949
  };
1003
-
1004
- $.resetMatrix = () => {
1005
- ctx.resetTransform();
1006
- ctx.scale($._pixelDensity, $._pixelDensity);
950
+ $.saturation = (c) => {
951
+ c._inferHSV();
952
+ return c._s;
953
+ };
954
+ $.brightness = (c) => {
955
+ c._inferHSV();
956
+ return c._v;
957
+ };
958
+ $.lightness = (c) => {
959
+ return ((0.2126 * c._r + 0.7152 * c._g + 0.0722 * c._b) * 100) / 255;
1007
960
  };
1008
961
 
1009
- $._styleNames = [
1010
- '_doStroke',
1011
- '_doFill',
1012
- '_strokeSet',
1013
- '_fillSet',
1014
- '_tint',
1015
- '_imageMode',
1016
- '_rectMode',
1017
- '_ellipseMode',
1018
- '_textFont',
1019
- '_textLeading',
1020
- '_leadingSet',
1021
- '_textSize',
1022
- '_textAlign',
1023
- '_textBaseline',
1024
- '_textStyle',
1025
- '_textWrap'
1026
- ];
1027
-
1028
- $._ctxStyleNames = ['strokeStyle', 'fillStyle', 'lineWidth', 'lineCap', 'lineJoin'];
1029
-
1030
- $._styles = [];
1031
- $._ctxStyles = [];
962
+ function lerpHue(h0, h1, t) {
963
+ var methods = [
964
+ [Math.abs(h1 - h0), $.map(t, 0, 1, h0, h1)],
965
+ [Math.abs(h1 + 360 - h0), $.map(t, 0, 1, h0, h1 + 360)],
966
+ [Math.abs(h1 - 360 - h0), $.map(t, 0, 1, h0, h1 - 360)]
967
+ ];
968
+ methods.sort((x, y) => x[0] - y[0]);
969
+ return (methods[0][1] + 720) % 360;
970
+ }
1032
971
 
1033
- $.pushMatrix = $.push = () => {
1034
- ctx.save();
1035
- let styles = {};
1036
- for (let s of $._styleNames) styles[s] = $[s];
1037
- $._styles.push(styles);
1038
- let ctxStyles = {};
1039
- for (let s of $._ctxStyleNames) ctxStyles[s] = ctx[s];
1040
- $._ctxStyles.push(ctxStyles);
1041
- };
1042
- $.popMatrix = $.pop = () => {
1043
- ctx.restore();
1044
- let styles = $._styles.pop();
1045
- for (let s of $._styleNames) $[s] = styles[s];
1046
- let ctxStyles = $._ctxStyles.pop();
1047
- for (let s of $._ctxStyleNames) ctx[s] = ctxStyles[s];
972
+ $.lerpColor = (a, b, t) => {
973
+ if ($._colorMode == $.RGB) {
974
+ return new Q5.Color(
975
+ $.constrain($.lerp(a._r, b._r, t), 0, 255),
976
+ $.constrain($.lerp(a._g, b._g, t), 0, 255),
977
+ $.constrain($.lerp(a._b, b._b, t), 0, 255),
978
+ $.constrain($.lerp(a._a, b._a, t), 0, 1)
979
+ );
980
+ } else {
981
+ a._inferHSV();
982
+ b._inferHSV();
983
+ return new Q5.Color(
984
+ $.constrain(lerpHue(a._h, b._h, t), 0, 360),
985
+ $.constrain($.lerp(a._s, b._s, t), 0, 100),
986
+ $.constrain($.lerp(a._v, b._v, t), 0, 100),
987
+ $.constrain($.lerp(a._a, b._a, t), 0, 1)
988
+ );
989
+ }
1048
990
  };
1049
991
 
1050
992
  //================================================================
1051
- // IMAGING
993
+ // DRAWING SETTING
1052
994
  //================================================================
1053
- $.imageMode = (mode) => ($._imageMode = mode); // TODO
1054
- $.image = (img, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight) => {
1055
- let drawable = img.MAGIC == $.MAGIC ? img.canvas : img;
1056
- function reset() {
1057
- if (img.MAGIC != $.MAGIC || !$._tint) return;
1058
- let c = img.canvas.getContext('2d');
1059
- c.save();
1060
- c.resetTransform();
1061
- c.clearRect(0, 0, c.canvas.width, c.canvas.height);
1062
- c.drawImage(tmpCt2.canvas, 0, 0);
1063
- c.restore();
1064
- }
1065
- if (img.MAGIC == $.MAGIC && $._tint != null) {
1066
- makeTmpCt2(img.canvas.width, img.canvas.height);
1067
- tmpCt2.drawImage(img.canvas, 0, 0);
1068
- img.tinted($._tint);
995
+
996
+ function defaultStyle() {
997
+ ctx.fillStyle = 'white';
998
+ ctx.strokeStyle = 'black';
999
+ ctx.lineCap = 'round';
1000
+ ctx.lineJoin = 'miter';
1001
+ ctx.textAlign = 'left';
1002
+ }
1003
+
1004
+ $.strokeWeight = (n) => {
1005
+ if (n > 0) $._doStroke = true;
1006
+ else $._doStroke = false;
1007
+ ctx.lineWidth = n;
1008
+ };
1009
+ $.stroke = function () {
1010
+ $._doStroke = true;
1011
+ $._strokeSet = true;
1012
+ if (typeof arguments[0] == 'string') {
1013
+ ctx.strokeStyle = arguments[0];
1014
+ return;
1069
1015
  }
1070
- if (!dWidth) {
1071
- if (img.MAGIC == $.MAGIC || img.width) {
1072
- dWidth = img.width;
1073
- dHeight = img.height;
1074
- } else {
1075
- dWidth = img.videoWidth;
1076
- dHeight = img.videoHeight;
1077
- }
1016
+ let col = $.color(...arguments);
1017
+ if (col._a <= 0) {
1018
+ $._doStroke = false;
1019
+ return;
1078
1020
  }
1079
- if ($._imageMode == 'center') {
1080
- dx -= dWidth * 0.5;
1081
- dy -= dHeight * 0.5;
1021
+ ctx.strokeStyle = col;
1022
+ };
1023
+ $.noStroke = () => ($._doStroke = false);
1024
+ $.fill = function () {
1025
+ $._doFill = true;
1026
+ $._fillSet = true;
1027
+ if (typeof arguments[0] == 'string') {
1028
+ ctx.fillStyle = arguments[0];
1029
+ return;
1082
1030
  }
1083
- if (sx === undefined) {
1084
- ctx.drawImage(drawable, dx, dy, dWidth, dHeight);
1085
- reset();
1031
+ let col = $.color(...arguments);
1032
+ if (col._a <= 0) {
1033
+ $._doFill = false;
1086
1034
  return;
1087
1035
  }
1088
- sWidth ??= drawable.width;
1089
- sHeight ??= drawable.height;
1090
- ctx.drawImage(drawable, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
1091
- reset();
1092
- };
1093
-
1094
- $.loadPixels = () => {
1095
- imgData = ctx.getImageData(0, 0, $.canvas.width, $.canvas.height);
1096
- $.pixels = imgData.data;
1097
- };
1098
- $.updatePixels = () => {
1099
- if (imgData != null) ctx.putImageData(imgData, 0, 0);
1036
+ ctx.fillStyle = col;
1100
1037
  };
1038
+ $.noFill = () => ($._doFill = false);
1039
+ $.smooth = () => ($._smooth = true);
1040
+ $.noSmooth = () => ($._smooth = false);
1041
+ $.blendMode = (x) => (ctx.globalCompositeOperation = x);
1042
+ $.strokeCap = (x) => (ctx.lineCap = x);
1043
+ $.strokeJoin = (x) => (ctx.lineJoin = x);
1044
+ $.ellipseMode = (x) => ($._ellipseMode = x);
1045
+ $.rectMode = (x) => ($._rectMode = x);
1046
+ $.curveDetail = (x) => ($._curveDetail = x);
1047
+ $.curveAlpha = (x) => ($._curveAlpha = x);
1048
+ $.curveTightness = (x) => ($._curveAlpha = x);
1101
1049
 
1102
- $._incrementPreload = () => preloadCnt++;
1103
- $._decrementPreload = () => preloadCnt--;
1050
+ //================================================================
1051
+ // DRAWING
1052
+ //================================================================
1104
1053
 
1105
- $.loadImage = (url, cb) => {
1106
- preloadCnt++;
1107
- let g = $.createImage(100, 100);
1108
- let c = g.canvas.getContext('2d');
1109
- let img = new window.Image();
1110
- img.src = url;
1111
- img.crossOrigin = 'Anonymous';
1112
- img.onload = () => {
1113
- g.width = c.canvas.width = img.naturalWidth;
1114
- g.height = c.canvas.height = img.naturalHeight;
1115
- c.drawImage(img, 0, 0);
1116
- preloadCnt--;
1117
- if (cb) cb(g);
1118
- };
1119
- img.onerror = (e) => {
1120
- preloadCnt--;
1121
- throw e;
1122
- };
1123
- return g;
1054
+ $.clear = () => {
1055
+ ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
1124
1056
  };
1125
1057
 
1126
- let filterImpl = {};
1127
- filterImpl[$.THRESHOLD] = (data, thresh) => {
1128
- if (thresh === undefined) thresh = 127.5;
1129
- else thresh *= 255;
1130
- for (let i = 0; i < data.length; i += 4) {
1131
- const gray = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
1132
- data[i] = data[i + 1] = data[i + 2] = gray >= thresh ? 255 : 0;
1133
- }
1134
- };
1135
- filterImpl[$.GRAY] = (data) => {
1136
- for (let i = 0; i < data.length; i += 4) {
1137
- const gray = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
1138
- data[i] = data[i + 1] = data[i + 2] = gray;
1139
- }
1140
- };
1141
- filterImpl[$.OPAQUE] = (data) => {
1142
- for (let i = 0; i < data.length; i += 4) {
1143
- data[i + 3] = 255;
1058
+ $.background = function () {
1059
+ if (arguments[0] && arguments[0].MAGIC == $.MAGIC) {
1060
+ return $.image(arguments[0], 0, 0, $.width, $.height);
1144
1061
  }
1145
- };
1146
- filterImpl[$.INVERT] = (data) => {
1147
- for (let i = 0; i < data.length; i += 4) {
1148
- data[i] = 255 - data[i];
1149
- data[i + 1] = 255 - data[i + 1];
1150
- data[i + 2] = 255 - data[i + 2];
1062
+ ctx.save();
1063
+ ctx.resetTransform();
1064
+ if (typeof arguments[0] == 'string') {
1065
+ ctx.fillStyle = arguments[0];
1066
+ } else {
1067
+ ctx.fillStyle = $.color(...Array.from(arguments));
1151
1068
  }
1069
+ ctx.fillRect(0, 0, $.canvas.width, $.canvas.height);
1070
+ ctx.restore();
1152
1071
  };
1153
- filterImpl[$.POSTERIZE] = (data, lvl) => {
1154
- let lvl1 = lvl - 1;
1155
- for (let i = 0; i < data.length; i += 4) {
1156
- data[i] = (((data[i] * lvl) >> 8) * 255) / lvl1;
1157
- data[i + 1] = (((data[i + 1] * lvl) >> 8) * 255) / lvl1;
1158
- data[i + 2] = (((data[i + 2] * lvl) >> 8) * 255) / lvl1;
1072
+
1073
+ $.line = (x0, y0, x1, y1) => {
1074
+ if ($._doStroke) {
1075
+ ctx.beginPath();
1076
+ ctx.moveTo(x0, y0);
1077
+ ctx.lineTo(x1, y1);
1078
+ ctx.stroke();
1159
1079
  }
1160
1080
  };
1161
1081
 
1162
- filterImpl[$.DILATE] = (data) => {
1163
- makeTmpBuf();
1164
- tmpBuf.set(data);
1165
- let [w, h] = [ctx.canvas.width, ctx.canvas.height];
1166
- for (let i = 0; i < h; i++) {
1167
- for (let j = 0; j < w; j++) {
1168
- let l = 4 * Math.max(j - 1, 0);
1169
- let r = 4 * Math.min(j + 1, w - 1);
1170
- let t = 4 * Math.max(i - 1, 0) * w;
1171
- let b = 4 * Math.min(i + 1, h - 1) * w;
1172
- let oi = 4 * i * w;
1173
- let oj = 4 * j;
1174
- for (let k = 0; k < 4; k++) {
1175
- let kt = k + t;
1176
- let kb = k + b;
1177
- let ko = k + oi;
1178
- data[oi + oj + k] = Math.max(
1179
- /*tmpBuf[kt+l],*/ tmpBuf[kt + oj] /*tmpBuf[kt+r],*/,
1180
- tmpBuf[ko + l],
1181
- tmpBuf[ko + oj],
1182
- tmpBuf[ko + r],
1183
- /*tmpBuf[kb+l],*/ tmpBuf[kb + oj] /*tmpBuf[kb+r],*/
1184
- );
1185
- }
1186
- }
1082
+ function normAng(x) {
1083
+ let mid = $._angleMode == $.DEGREES ? 180 : Math.PI;
1084
+ let full = mid * 2;
1085
+ if (0 <= x && x <= full) return x;
1086
+ while (x < 0) {
1087
+ x += full;
1187
1088
  }
1188
- };
1189
- filterImpl[$.ERODE] = (data) => {
1190
- makeTmpBuf();
1191
- tmpBuf.set(data);
1192
- let [w, h] = [ctx.canvas.width, ctx.canvas.height];
1193
- for (let i = 0; i < h; i++) {
1194
- for (let j = 0; j < w; j++) {
1195
- let l = 4 * Math.max(j - 1, 0);
1196
- let r = 4 * Math.min(j + 1, w - 1);
1197
- let t = 4 * Math.max(i - 1, 0) * w;
1198
- let b = 4 * Math.min(i + 1, h - 1) * w;
1199
- let oi = 4 * i * w;
1200
- let oj = 4 * j;
1201
- for (let k = 0; k < 4; k++) {
1202
- let kt = k + t;
1203
- let kb = k + b;
1204
- let ko = k + oi;
1205
- data[oi + oj + k] = Math.min(
1206
- /*tmpBuf[kt+l],*/ tmpBuf[kt + oj] /*tmpBuf[kt+r],*/,
1207
- tmpBuf[ko + l],
1208
- tmpBuf[ko + oj],
1209
- tmpBuf[ko + r],
1210
- /*tmpBuf[kb+l],*/ tmpBuf[kb + oj] /*tmpBuf[kb+r],*/
1211
- );
1212
- }
1213
- }
1089
+ while (x >= mid) {
1090
+ x -= full;
1214
1091
  }
1215
- };
1216
-
1217
- filterImpl[$.BLUR] = (data, rad) => {
1218
- rad = rad || 1;
1219
- rad = Math.floor(rad * $._pixelDensity);
1220
- makeTmpBuf();
1221
- tmpBuf.set(data);
1222
-
1223
- let ksize = rad * 2 + 1;
1092
+ return x;
1093
+ }
1224
1094
 
1225
- function gauss1d(ksize) {
1226
- let im = new Float32Array(ksize);
1227
- let sigma = 0.3 * rad + 0.8;
1228
- let ss2 = sigma * sigma * 2;
1229
- for (let i = 0; i < ksize; i++) {
1230
- let x = i - ksize / 2;
1231
- let z = Math.exp(-(x * x) / ss2) / (2.5066282746 * sigma);
1232
- im[i] = z;
1095
+ function arcImpl(x, y, w, h, start, stop, mode, detail) {
1096
+ if (!$._doFill && !$._doStroke) return;
1097
+ let lo = normAng(start);
1098
+ let hi = normAng(stop);
1099
+ if (lo > hi) [lo, hi] = [hi, lo];
1100
+ if (lo == 0) {
1101
+ if (hi == 0) return;
1102
+ if (($._angleMode == $.DEGREES && hi == 360) || hi == $.TAU) {
1103
+ return $.ellipse(x, y, w, h);
1233
1104
  }
1234
- return im;
1235
1105
  }
1236
-
1237
- let kern = gauss1d(ksize);
1238
- let [w, h] = [ctx.canvas.width, ctx.canvas.height];
1239
- for (let i = 0; i < h; i++) {
1240
- for (let j = 0; j < w; j++) {
1241
- let s0 = 0,
1242
- s1 = 0,
1243
- s2 = 0,
1244
- s3 = 0;
1245
- for (let k = 0; k < ksize; k++) {
1246
- let jk = Math.min(Math.max(j - rad + k, 0), w - 1);
1247
- let idx = 4 * (i * w + jk);
1248
- s0 += tmpBuf[idx] * kern[k];
1249
- s1 += tmpBuf[idx + 1] * kern[k];
1250
- s2 += tmpBuf[idx + 2] * kern[k];
1251
- s3 += tmpBuf[idx + 3] * kern[k];
1252
- }
1253
- let idx = 4 * (i * w + j);
1254
- data[idx] = s0;
1255
- data[idx + 1] = s1;
1256
- data[idx + 2] = s2;
1257
- data[idx + 3] = s3;
1258
- }
1106
+ ctx.beginPath();
1107
+ for (let i = 0; i < detail + 1; i++) {
1108
+ let t = i / detail;
1109
+ let a = $.lerp(lo, hi, t);
1110
+ let dx = ($.cos(a) * w) / 2;
1111
+ let dy = ($.sin(a) * h) / 2;
1112
+ ctx[i ? 'lineTo' : 'moveTo'](x + dx, y + dy);
1259
1113
  }
1260
- tmpBuf.set(data);
1261
- for (let i = 0; i < h; i++) {
1262
- for (let j = 0; j < w; j++) {
1263
- let s0 = 0,
1264
- s1 = 0,
1265
- s2 = 0,
1266
- s3 = 0;
1267
- for (let k = 0; k < ksize; k++) {
1268
- let ik = Math.min(Math.max(i - rad + k, 0), h - 1);
1269
- let idx = 4 * (ik * w + j);
1270
- s0 += tmpBuf[idx] * kern[k];
1271
- s1 += tmpBuf[idx + 1] * kern[k];
1272
- s2 += tmpBuf[idx + 2] * kern[k];
1273
- s3 += tmpBuf[idx + 3] * kern[k];
1274
- }
1275
- let idx = 4 * (i * w + j);
1276
- data[idx] = s0;
1277
- data[idx + 1] = s1;
1278
- data[idx + 2] = s2;
1279
- data[idx + 3] = s3;
1280
- }
1114
+ if (mode == $.CHORD) {
1115
+ ctx.closePath();
1116
+ } else if (mode == $.PIE) {
1117
+ ctx.lineTo(x, y);
1118
+ ctx.closePath();
1119
+ }
1120
+ if ($._doFill) ctx.fill();
1121
+ if ($._doStroke) ctx.stroke();
1122
+ }
1123
+ $.arc = (x, y, w, h, start, stop, mode, detail) => {
1124
+ if (start == stop) return $.ellipse(x, y, w, h);
1125
+ detail ??= 25;
1126
+ mode ??= $.PIE;
1127
+ if ($._ellipseMode == $.CENTER) {
1128
+ arcImpl(x, y, w, h, start, stop, mode, detail);
1129
+ } else if ($._ellipseMode == $.RADIUS) {
1130
+ arcImpl(x, y, w * 2, h * 2, start, stop, mode, detail);
1131
+ } else if ($._ellipseMode == $.CORNER) {
1132
+ arcImpl(x + w / 2, y + h / 2, w, h, start, stop, mode, detail);
1133
+ } else if ($._ellipseMode == $.CORNERS) {
1134
+ arcImpl((x + w) / 2, (y + h) / 2, w - x, h - y, start, stop, mode, detail);
1281
1135
  }
1282
1136
  };
1283
1137
 
1284
- function makeTmpCtx(w, h) {
1285
- if (tmpCtx == null) {
1286
- tmpCtx = document.createElement('canvas').getContext('2d');
1138
+ function ellipseImpl(x, y, w, h) {
1139
+ if (!$._doFill && !$._doStroke) return;
1140
+ ctx.beginPath();
1141
+ ctx.ellipse(x, y, w / 2, h / 2, 0, 0, $.TAU);
1142
+ if ($._doFill) ctx.fill();
1143
+ if ($._doStroke) ctx.stroke();
1144
+ }
1145
+ $.ellipse = (x, y, w, h) => {
1146
+ h ??= w;
1147
+ if ($._ellipseMode == $.CENTER) {
1148
+ ellipseImpl(x, y, w, h);
1149
+ } else if ($._ellipseMode == $.RADIUS) {
1150
+ ellipseImpl(x, y, w * 2, h * 2);
1151
+ } else if ($._ellipseMode == $.CORNER) {
1152
+ ellipseImpl(x + w / 2, y + h / 2, w, h);
1153
+ } else if ($._ellipseMode == $.CORNERS) {
1154
+ ellipseImpl((x + w) / 2, (y + h) / 2, w - x, h - y);
1287
1155
  }
1288
- h ??= w || ctx.canvas.height;
1289
- w ??= ctx.canvas.width;
1290
- if (tmpCtx.canvas.width != w || tmpCtx.canvas.height != h) {
1291
- tmpCtx.canvas.width = w;
1292
- tmpCtx.canvas.height = h;
1156
+ };
1157
+ $.circle = (x, y, r) => {
1158
+ return $.ellipse(x, y, r, r);
1159
+ };
1160
+ $.point = (x, y) => {
1161
+ if (x.x) {
1162
+ y = x.y;
1163
+ x = x.x;
1293
1164
  }
1165
+ ctx.beginPath();
1166
+ ctx.ellipse(x, y, 0.4, 0.4, 0, 0, $.TAU);
1167
+ ctx.stroke();
1168
+ };
1169
+ function rectImpl(x, y, w, h) {
1170
+ if ($._doFill) ctx.fillRect(x, y, w, h);
1171
+ if ($._doStroke) ctx.strokeRect(x, y, w, h);
1294
1172
  }
1295
- function makeTmpCt2(w, h) {
1296
- if (tmpCt2 == null) {
1297
- tmpCt2 = document.createElement('canvas').getContext('2d');
1173
+ function roundedRectImpl(x, y, w, h, tl, tr, br, bl) {
1174
+ if (!$._doFill && !$._doStroke) return;
1175
+ if (tl === undefined) {
1176
+ return rectImpl(x, y, w, h);
1298
1177
  }
1299
- h ??= w || ctx.canvas.height;
1300
- w ??= ctx.canvas.width;
1301
- if (tmpCt2.canvas.width != w || tmpCt2.canvas.height != h) {
1302
- tmpCt2.canvas.width = w;
1303
- tmpCt2.canvas.height = h;
1178
+ if (tr === undefined) {
1179
+ return roundedRectImpl(x, y, w, h, tl, tl, tl, tl);
1304
1180
  }
1181
+ const hh = Math.min(Math.abs(h), Math.abs(w)) / 2;
1182
+ tl = Math.min(hh, tl);
1183
+ tr = Math.min(hh, tr);
1184
+ bl = Math.min(hh, bl);
1185
+ br = Math.min(hh, br);
1186
+ ctx.beginPath();
1187
+ ctx.moveTo(x + tl, y);
1188
+ ctx.arcTo(x + w, y, x + w, y + h, tr);
1189
+ ctx.arcTo(x + w, y + h, x, y + h, br);
1190
+ ctx.arcTo(x, y + h, x, y, bl);
1191
+ ctx.arcTo(x, y, x + w, y, tl);
1192
+ ctx.closePath();
1193
+ if ($._doFill) ctx.fill();
1194
+ if ($._doStroke) ctx.stroke();
1305
1195
  }
1306
1196
 
1307
- function makeTmpBuf() {
1308
- let l = ctx.canvas.width * ctx.canvas.height * 4;
1309
- if (!tmpBuf || l != tmpBuf.length) {
1310
- tmpBuf = new Uint8ClampedArray(l);
1197
+ $.rect = (x, y, w, h, tl, tr, br, bl) => {
1198
+ if ($._rectMode == $.CENTER) {
1199
+ roundedRectImpl(x - w / 2, y - h / 2, w, h, tl, tr, br, bl);
1200
+ } else if ($._rectMode == $.RADIUS) {
1201
+ roundedRectImpl(x - w, y - h, w * 2, h * 2, tl, tr, br, bl);
1202
+ } else if ($._rectMode == $.CORNER) {
1203
+ roundedRectImpl(x, y, w, h, tl, tr, br, bl);
1204
+ } else if ($._rectMode == $.CORNERS) {
1205
+ roundedRectImpl(x, y, w - x, h - y, tl, tr, br, bl);
1311
1206
  }
1207
+ };
1208
+ $.square = (x, y, s, tl, tr, br, bl) => {
1209
+ return $.rect(x, y, s, s, tl, tr, br, bl);
1210
+ };
1211
+
1212
+ function clearBuff() {
1213
+ curveBuff = [];
1312
1214
  }
1313
1215
 
1314
- function nativeFilter(filtstr) {
1315
- tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
1316
- tmpCtx.filter = filtstr;
1317
- tmpCtx.drawImage(ctx.canvas, 0, 0);
1318
- ctx.save();
1319
- ctx.resetTransform();
1320
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1321
- ctx.drawImage(tmpCtx.canvas, 0, 0);
1322
- ctx.restore();
1216
+ $.beginShape = () => {
1217
+ clearBuff();
1218
+ ctx.beginPath();
1219
+ firstVertex = true;
1220
+ };
1221
+ $.beginContour = () => {
1222
+ ctx.closePath();
1223
+ clearBuff();
1224
+ firstVertex = true;
1225
+ };
1226
+ $.endContour = () => {
1227
+ clearBuff();
1228
+ firstVertex = true;
1229
+ };
1230
+ $.vertex = (x, y) => {
1231
+ clearBuff();
1232
+ if (firstVertex) {
1233
+ ctx.moveTo(x, y);
1234
+ } else {
1235
+ ctx.lineTo(x, y);
1236
+ }
1237
+ firstVertex = false;
1238
+ };
1239
+ $.bezierVertex = (cp1x, cp1y, cp2x, cp2y, x, y) => {
1240
+ clearBuff();
1241
+ ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
1242
+ };
1243
+ $.quadraticVertex = (cp1x, cp1y, x, y) => {
1244
+ clearBuff();
1245
+ ctx.quadraticCurveTo(cp1x, cp1y, x, y);
1246
+ };
1247
+ $.bezier = (x1, y1, x2, y2, x3, y3, x4, y4) => {
1248
+ $.beginShape();
1249
+ $.vertex(x1, y1);
1250
+ $.bezierVertex(x2, y2, x3, y3, x4, y4);
1251
+ $.endShape();
1252
+ };
1253
+ $.triangle = (x1, y1, x2, y2, x3, y3) => {
1254
+ $.beginShape();
1255
+ $.vertex(x1, y1);
1256
+ $.vertex(x2, y2);
1257
+ $.vertex(x3, y3);
1258
+ $.endShape($.CLOSE);
1259
+ };
1260
+ $.quad = (x1, y1, x2, y2, x3, y3, x4, y4) => {
1261
+ $.beginShape();
1262
+ $.vertex(x1, y1);
1263
+ $.vertex(x2, y2);
1264
+ $.vertex(x3, y3);
1265
+ $.vertex(x4, y4);
1266
+ $.endShape($.CLOSE);
1267
+ };
1268
+ $.endShape = (close) => {
1269
+ clearBuff();
1270
+ if (close) {
1271
+ ctx.closePath();
1272
+ }
1273
+ if ($._doFill) ctx.fill();
1274
+ if ($._doStroke) ctx.stroke();
1275
+ if (!$._doFill && !$._doStroke) {
1276
+ // eh.
1277
+ ctx.save();
1278
+ ctx.fillStyle = 'none';
1279
+ ctx.fill();
1280
+ ctx.restore();
1281
+ }
1282
+ };
1283
+ function catmullRomSpline(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, numPts, alpha) {
1284
+ //https://en.wikipedia.org/wiki/Centripetal_Catmull–Rom_spline
1285
+ function catmullromSplineGetT(t, p0x, p0y, p1x, p1y, alpha) {
1286
+ let a = Math.pow(p1x - p0x, 2.0) + Math.pow(p1y - p0y, 2.0);
1287
+ let b = Math.pow(a, alpha * 0.5);
1288
+ return b + t;
1289
+ }
1290
+ let pts = [];
1291
+
1292
+ let t0 = 0.0;
1293
+ let t1 = catmullromSplineGetT(t0, p0x, p0y, p1x, p1y, alpha);
1294
+ let t2 = catmullromSplineGetT(t1, p1x, p1y, p2x, p2y, alpha);
1295
+ let t3 = catmullromSplineGetT(t2, p2x, p2y, p3x, p3y, alpha);
1296
+
1297
+ for (let i = 0; i < numPts; i++) {
1298
+ let t = t1 + (i / (numPts - 1)) * (t2 - t1);
1299
+ let s = [
1300
+ (t1 - t) / (t1 - t0),
1301
+ (t - t0) / (t1 - t0),
1302
+ (t2 - t) / (t2 - t1),
1303
+ (t - t1) / (t2 - t1),
1304
+ (t3 - t) / (t3 - t2),
1305
+ (t - t2) / (t3 - t2),
1306
+ (t2 - t) / (t2 - t0),
1307
+ (t - t0) / (t2 - t0),
1308
+ (t3 - t) / (t3 - t1),
1309
+ (t - t1) / (t3 - t1)
1310
+ ];
1311
+ for (let j = 0; j < s.length; j += 2) {
1312
+ if (isNaN(s[j])) {
1313
+ s[j] = 1;
1314
+ s[j + 1] = 0;
1315
+ }
1316
+ if (!isFinite(s[j])) {
1317
+ if (s[j] > 0) {
1318
+ s[j] = 1;
1319
+ s[j + 1] = 0;
1320
+ } else {
1321
+ s[j] = 0;
1322
+ s[j + 1] = 1;
1323
+ }
1324
+ }
1325
+ }
1326
+ let a1x = p0x * s[0] + p1x * s[1];
1327
+ let a1y = p0y * s[0] + p1y * s[1];
1328
+ let a2x = p1x * s[2] + p2x * s[3];
1329
+ let a2y = p1y * s[2] + p2y * s[3];
1330
+ let a3x = p2x * s[4] + p3x * s[5];
1331
+ let a3y = p2y * s[4] + p3y * s[5];
1332
+ let b1x = a1x * s[6] + a2x * s[7];
1333
+ let b1y = a1y * s[6] + a2y * s[7];
1334
+ let b2x = a2x * s[8] + a3x * s[9];
1335
+ let b2y = a2y * s[8] + a3y * s[9];
1336
+ let cx = b1x * s[2] + b2x * s[3];
1337
+ let cy = b1y * s[2] + b2y * s[3];
1338
+ pts.push([cx, cy]);
1339
+ }
1340
+ return pts;
1323
1341
  }
1324
1342
 
1325
- $.filter = (typ, x) => {
1326
- let support = $.HARDWARE_FILTERS && ctx.filter != undefined;
1327
- if (support) {
1328
- makeTmpCtx();
1329
- if (typ == $.THRESHOLD) {
1330
- x ??= 0.5;
1331
- x = Math.max(x, 0.00001);
1332
- let b = Math.floor((0.5 / x) * 100);
1333
- nativeFilter(`saturate(0%) brightness(${b}%) contrast(1000000%)`);
1334
- } else if (typ == $.GRAY) {
1335
- nativeFilter(`saturate(0%)`);
1336
- } else if (typ == $.OPAQUE) {
1337
- tmpCtx.fillStyle = 'black';
1338
- tmpCtx.fillRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
1339
- tmpCtx.drawImage(ctx.canvas, 0, 0);
1340
- ctx.save();
1341
- ctx.resetTransform();
1342
- ctx.drawImage(tmpCtx.canvas, 0, 0);
1343
- ctx.restore();
1344
- } else if (typ == $.INVERT) {
1345
- nativeFilter(`invert(100%)`);
1346
- } else if (typ == $.BLUR) {
1347
- nativeFilter(`blur(${Math.ceil((x * $._pixelDensity) / 1) || 1}px)`);
1343
+ $.curveVertex = (x, y) => {
1344
+ curveBuff.push([x, y]);
1345
+ if (curveBuff.length < 4) {
1346
+ return;
1347
+ }
1348
+ let p0 = curveBuff[curveBuff.length - 4];
1349
+ let p1 = curveBuff[curveBuff.length - 3];
1350
+ let p2 = curveBuff[curveBuff.length - 2];
1351
+ let p3 = curveBuff[curveBuff.length - 1];
1352
+ let pts = catmullRomSpline(...p0, ...p1, ...p2, ...p3, $._curveDetail, $._curveAlpha);
1353
+ for (let i = 0; i < pts.length; i++) {
1354
+ if (firstVertex) {
1355
+ ctx.moveTo(...pts[i]);
1348
1356
  } else {
1349
- let imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
1350
- filterImpl[typ](imgData.data, x);
1351
- ctx.putImageData(imgData, 0, 0);
1357
+ ctx.lineTo(...pts[i]);
1352
1358
  }
1353
- } else {
1354
- let imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
1355
- filterImpl[typ](imgData.data, x);
1356
- ctx.putImageData(imgData, 0, 0);
1359
+ firstVertex = false;
1357
1360
  }
1358
1361
  };
1362
+ $.curve = (x1, y1, x2, y2, x3, y3, x4, y4) => {
1363
+ $.beginShape();
1364
+ $.curveVertex(x1, y1);
1365
+ $.curveVertex(x2, y2);
1366
+ $.curveVertex(x3, y3);
1367
+ $.curveVertex(x4, y4);
1368
+ $.endShape();
1369
+ };
1359
1370
 
1360
- $.resize = (w, h) => {
1361
- makeTmpCtx();
1362
- tmpCtx.drawImage(ctx.canvas, 0, 0);
1363
- $.width = w;
1364
- $.height = h;
1365
- ctx.canvas.width = w * $._pixelDensity;
1366
- ctx.canvas.height = h * $._pixelDensity;
1367
- ctx.save();
1368
- ctx.resetTransform();
1369
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1370
- ctx.drawImage(tmpCtx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height);
1371
- ctx.restore();
1371
+ //================================================================
1372
+ // DRAWING MATRIX
1373
+ //================================================================
1374
+ $.translate = (x, y) => ctx.translate(x, y);
1375
+ $.rotate = (r) => {
1376
+ if ($._angleMode == 'degrees') r = $.radians(r);
1377
+ ctx.rotate(r);
1372
1378
  };
1373
1379
 
1374
- $.get = (x, y, w, h) => {
1375
- if (x !== undefined && w === undefined) {
1376
- let c = ctx.getImageData(x, y, 1, 1).data;
1377
- return new Q5.Color(c[0], c[1], c[2], c[3] / 255);
1378
- }
1379
- x = x || 0;
1380
- y = y || 0;
1381
- w = w || $.width;
1382
- h = h || $.height;
1383
- let g = $.createGraphics(w, h);
1384
- g.pixelDensity($._pixelDensity);
1385
- let imgData = ctx.getImageData(x * $._pixelDensity, y * $._pixelDensity, w * $._pixelDensity, h * $._pixelDensity);
1386
- g.canvas.getContext('2d').putImageData(imgData, 0, 0);
1387
- return g;
1380
+ $.scale = (x, y) => {
1381
+ y ??= x;
1382
+ ctx.scale(x, y);
1383
+ };
1384
+ $.applyMatrix = (a, b, c, d, e, f) => {
1385
+ ctx.transform(a, b, c, d, e, f);
1386
+ };
1387
+ $.shearX = (ang) => {
1388
+ ctx.transform(1, 0, $.tan(ang), 1, 0, 0);
1389
+ };
1390
+ $.shearY = (ang) => {
1391
+ ctx.transform(1, $.tan(ang), 0, 1, 0, 0);
1388
1392
  };
1389
1393
 
1390
- $.set = (x, y, c) => {
1391
- if (c.MAGIC == $.MAGIC) {
1392
- let old = $._tint;
1393
- $._tint = null;
1394
- $.image(c, x, y);
1395
- $._tint = old;
1396
- return;
1397
- }
1398
- for (let i = 0; i < $._pixelDensity; i++) {
1399
- for (let j = 0; j < $._pixelDensity; j++) {
1400
- let idx = 4 * ((y * $._pixelDensity + i) * ctx.canvas.width + x * $._pixelDensity + j);
1401
- $.pixels[idx] = c._r;
1402
- $.pixels[idx + 1] = c._g;
1403
- $.pixels[idx + 2] = c._b;
1404
- $.pixels[idx + 3] = c._a * 255;
1405
- }
1406
- }
1394
+ $.resetMatrix = () => {
1395
+ ctx.resetTransform();
1396
+ ctx.scale($._pixelDensity, $._pixelDensity);
1407
1397
  };
1408
1398
 
1409
- $.tinted = function () {
1410
- let col = $.color(...Array.from(arguments));
1411
- let alpha = col._a;
1412
- col._a = 1;
1413
- makeTmpCtx();
1414
- tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
1415
- tmpCtx.fillStyle = col;
1416
- tmpCtx.fillRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
1417
- tmpCtx.globalCompositeOperation = 'multiply';
1418
- tmpCtx.drawImage(ctx.canvas, 0, 0);
1419
- tmpCtx.globalCompositeOperation = 'source-over';
1399
+ $._styleNames = [
1400
+ '_doStroke',
1401
+ '_doFill',
1402
+ '_strokeSet',
1403
+ '_fillSet',
1404
+ '_tint',
1405
+ '_imageMode',
1406
+ '_rectMode',
1407
+ '_ellipseMode',
1408
+ '_textFont',
1409
+ '_textLeading',
1410
+ '_leadingSet',
1411
+ '_textSize',
1412
+ '_textAlign',
1413
+ '_textBaseline',
1414
+ '_textStyle',
1415
+ '_textWrap'
1416
+ ];
1420
1417
 
1421
- ctx.save();
1422
- ctx.resetTransform();
1423
- let old = ctx.globalCompositeOperation;
1424
- ctx.globalCompositeOperation = 'source-in';
1425
- ctx.drawImage(tmpCtx.canvas, 0, 0);
1426
- ctx.globalCompositeOperation = old;
1427
- ctx.restore();
1418
+ $._ctxStyleNames = ['strokeStyle', 'fillStyle', 'lineWidth', 'lineCap', 'lineJoin'];
1428
1419
 
1429
- tmpCtx.globalAlpha = alpha;
1430
- tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
1431
- tmpCtx.drawImage(ctx.canvas, 0, 0);
1432
- tmpCtx.globalAlpha = 1;
1420
+ $._styles = [];
1421
+ $._ctxStyles = [];
1433
1422
 
1423
+ $.pushMatrix = $.push = () => {
1434
1424
  ctx.save();
1435
- ctx.resetTransform();
1436
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1437
- ctx.drawImage(tmpCtx.canvas, 0, 0);
1425
+ let styles = {};
1426
+ for (let s of $._styleNames) styles[s] = $[s];
1427
+ $._styles.push(styles);
1428
+ let ctxStyles = {};
1429
+ for (let s of $._ctxStyleNames) ctxStyles[s] = ctx[s];
1430
+ $._ctxStyles.push(ctxStyles);
1431
+ };
1432
+ $.popMatrix = $.pop = () => {
1438
1433
  ctx.restore();
1434
+ let styles = $._styles.pop();
1435
+ for (let s of $._styleNames) $[s] = styles[s];
1436
+ let ctxStyles = $._ctxStyles.pop();
1437
+ for (let s of $._ctxStyleNames) ctx[s] = ctxStyles[s];
1439
1438
  };
1440
1439
 
1441
- $.tint = function () {
1442
- $._tint = $.color(...Array.from(arguments));
1440
+ //================================================================
1441
+ // IMAGING
1442
+ //================================================================
1443
+ $.imageMode = (mode) => ($._imageMode = mode); // TODO
1444
+ $.image = (img, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight) => {
1445
+ let drawable = img.MAGIC == $.MAGIC ? img.canvas : img;
1446
+ function reset() {
1447
+ if (img.MAGIC != $.MAGIC || !$._tint) return;
1448
+ let c = img.canvas.getContext('2d');
1449
+ c.save();
1450
+ c.resetTransform();
1451
+ c.clearRect(0, 0, c.canvas.width, c.canvas.height);
1452
+ c.drawImage(tmpCt2.canvas, 0, 0);
1453
+ c.restore();
1454
+ }
1455
+ if (img.MAGIC == $.MAGIC && $._tint != null) {
1456
+ makeTmpCt2(img.canvas.width, img.canvas.height);
1457
+ tmpCt2.drawImage(img.canvas, 0, 0);
1458
+ img.tinted($._tint);
1459
+ }
1460
+ if (!dWidth) {
1461
+ if (img.MAGIC == $.MAGIC || img.width) {
1462
+ dWidth = img.width;
1463
+ dHeight = img.height;
1464
+ } else {
1465
+ dWidth = img.videoWidth;
1466
+ dHeight = img.videoHeight;
1467
+ }
1468
+ }
1469
+ if ($._imageMode == 'center') {
1470
+ dx -= dWidth * 0.5;
1471
+ dy -= dHeight * 0.5;
1472
+ }
1473
+ if (sx === undefined) {
1474
+ ctx.drawImage(drawable, dx, dy, dWidth, dHeight);
1475
+ reset();
1476
+ return;
1477
+ }
1478
+ sWidth ??= drawable.width;
1479
+ sHeight ??= drawable.height;
1480
+ ctx.drawImage(drawable, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
1481
+ reset();
1443
1482
  };
1444
1483
 
1445
- $.noTint = () => ($._tint = null);
1484
+ $._incrementPreload = () => preloadCnt++;
1485
+ $._decrementPreload = () => preloadCnt--;
1446
1486
 
1447
- $.mask = (img) => {
1448
- ctx.save();
1449
- ctx.resetTransform();
1450
- let old = ctx.globalCompositeOperation;
1451
- ctx.globalCompositeOperation = 'destination-in';
1452
- ctx.drawImage(img.canvas, 0, 0);
1453
- ctx.globalCompositeOperation = old;
1454
- ctx.restore();
1487
+ $.loadImage = (url, cb) => {
1488
+ preloadCnt++;
1489
+ let g = $.createImage(100, 100);
1490
+ let c = g.canvas.getContext('2d');
1491
+ let img = new window.Image();
1492
+ img.src = url;
1493
+ img.crossOrigin = 'Anonymous';
1494
+ img.onload = () => {
1495
+ g.width = c.canvas.width = img.naturalWidth;
1496
+ g.height = c.canvas.height = img.naturalHeight;
1497
+ c.drawImage(img, 0, 0);
1498
+ preloadCnt--;
1499
+ if (cb) cb(g);
1500
+ };
1501
+ img.onerror = (e) => {
1502
+ preloadCnt--;
1503
+ throw e;
1504
+ };
1505
+ return g;
1455
1506
  };
1456
1507
 
1457
- $.clearTemporaryBuffers = () => {
1508
+ $._clearTemporaryBuffers = () => {
1458
1509
  tmpCtx = null;
1459
1510
  tmpCt2 = null;
1460
1511
  tmpBuf = null;
1461
1512
  };
1462
- $._save = (data, name, ext) => {
1463
- name = name || 'untitled';
1464
- ext = ext || 'png';
1465
- if (ext == 'jpg' || ext == 'png') data = data.toDataURL();
1466
- else {
1467
- let type = 'text/plain';
1468
- if (ext == 'json') {
1469
- if (typeof data != 'string') data = JSON.stringify(data);
1470
- type = 'text/json';
1471
- }
1472
- data = new Blob([data], { type });
1473
- data = URL.createObjectURL(data);
1474
- }
1475
- let a = document.createElement('a');
1476
- a.href = data;
1477
- a.download = name + '.' + ext;
1478
- document.body.append(a);
1479
- a.click();
1480
- document.body.removeChild(a);
1481
- URL.revokeObjectURL(a.href);
1482
- };
1483
- $.save = (a, b, c) => {
1484
- if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
1485
- c = b;
1486
- b = a;
1487
- a = ctx.canvas;
1488
- }
1489
- if (c) return $._save(a, b, c);
1490
- if (b) {
1491
- b = b.split('.');
1492
- $._save(a, b[0], b.at(-1));
1493
- } else $._save(a);
1494
- };
1495
- $.canvas.save = $.save;
1496
- $.saveCanvas = $.save;
1497
-
1498
- $.remove = () => {
1499
- $.noLoop();
1500
- $.canvas.remove();
1501
- };
1502
-
1503
- if (scope == 'image') return;
1504
1513
 
1505
1514
  //================================================================
1506
1515
  // TYPOGRAPHY
@@ -1623,7 +1632,8 @@ function Q5(scope, parent) {
1623
1632
  if (str === undefined) return;
1624
1633
  str = str.toString();
1625
1634
  if (!$._doFill && !$._doStroke) return;
1626
- let c, ti, k, cX, cY;
1635
+ let c, ti, tg, k, cX, cY;
1636
+ let mod = 1;
1627
1637
  let t = ctx.getTransform();
1628
1638
  let useCache = $._useCache || ($._textCache && (t.b != 0 || t.c != 0));
1629
1639
  if (!useCache) {
@@ -1637,34 +1647,38 @@ function Q5(scope, parent) {
1637
1647
  $.textImage(ti, x, y);
1638
1648
  return;
1639
1649
  }
1640
- ti = $.createImage(1, 1);
1641
- c = ti._ctx;
1650
+ tg = $.createGraphics(1, 1);
1651
+ c = tg._ctx;
1652
+ mod = $._pixelDensity;
1642
1653
  }
1643
- c.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
1654
+ c.font = `${$._textStyle} ${$._textSize * mod}px ${$._textFont}`;
1644
1655
  let lines = str.split('\n');
1645
1656
  if (useCache) {
1646
1657
  cX = 0;
1647
- cY = $._textLeading * lines.length;
1658
+ cY = $._textLeading * mod * lines.length;
1648
1659
  let m = c.measureText(' ');
1649
- ti._ascent = m.fontBoundingBoxAscent;
1650
- ti._descent = m.fontBoundingBoxDescent;
1651
- h ??= cY + ti._descent;
1652
- ti.resizeCanvas(Math.ceil($.textWidth(str)), Math.ceil(h));
1653
- ti.pixelDensity($._pixelDensity);
1660
+ tg._ascent = m.fontBoundingBoxAscent;
1661
+ tg._descent = m.fontBoundingBoxDescent;
1662
+ h ??= cY + tg._descent;
1663
+ tg.resizeCanvas(Math.ceil(c.measureText(str).width), Math.ceil(h));
1654
1664
 
1655
1665
  c.fillStyle = ctx.fillStyle;
1656
1666
  c.strokeStyle = ctx.strokeStyle;
1667
+ c.lineWidth = ctx.lineWidth * mod;
1657
1668
  }
1669
+ let f = c.fillStyle;
1670
+ if (!$._fillSet) c.fillStyle = 'black';
1658
1671
  for (let i = 0; i < lines.length; i++) {
1659
1672
  if ($._doStroke && $._strokeSet) c.strokeText(lines[i], cX, cY);
1660
- let f = c.fillStyle;
1661
- if (!$._fillSet) c.fillStyle = 'black';
1662
1673
  if ($._doFill) c.fillText(lines[i], cX, cY);
1663
- if (!$._fillSet) c.fillStyle = f;
1664
1674
  cY += $._textLeading;
1665
1675
  if (cY > h) break;
1666
1676
  }
1677
+ if (!$._fillSet) c.fillStyle = f;
1667
1678
  if (useCache) {
1679
+ ti = tg.get();
1680
+ ti.width /= $._pixelDensity;
1681
+ ti.height /= $._pixelDensity;
1668
1682
  $._tic.set(k, ti);
1669
1683
  $.textImage(ti, x, y);
1670
1684
  }
@@ -2765,6 +2779,7 @@ class _Q5Image extends Q5 {
2765
2779
  constructor(width, height) {
2766
2780
  super('image');
2767
2781
  this.createCanvas(width, height);
2782
+ delete this.createCanvas;
2768
2783
  this._loop = false;
2769
2784
  }
2770
2785
  }