q5 1.4.1 → 1.4.3

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