q5 1.9.21 → 1.9.46

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.
@@ -0,0 +1,572 @@
1
+ Q5.modules.q2d_image = ($) => {
2
+ $.BLEND = 'source-over';
3
+ $.REMOVE = 'destination-out';
4
+ $.ADD = 'lighter';
5
+ $.DARKEST = 'darken';
6
+ $.LIGHTEST = 'lighten';
7
+ $.DIFFERENCE = 'difference';
8
+ $.SUBTRACT = 'subtract';
9
+ $.EXCLUSION = 'exclusion';
10
+ $.MULTIPLY = 'multiply';
11
+ $.SCREEN = 'screen';
12
+ $.REPLACE = 'copy';
13
+ $.OVERLAY = 'overlay';
14
+ $.HARD_LIGHT = 'hard-light';
15
+ $.SOFT_LIGHT = 'soft-light';
16
+ $.DODGE = 'color-dodge';
17
+ $.BURN = 'color-burn';
18
+
19
+ $._tint = null;
20
+
21
+ let imgData = null;
22
+ let tmpCtx = null;
23
+ let tmpCt2 = null;
24
+ let tmpBuf = null;
25
+
26
+ $.loadPixels = () => {
27
+ imgData = $.ctx.getImageData(0, 0, $.canvas.width, $.canvas.height);
28
+ $.pixels = imgData.data;
29
+ };
30
+ $.updatePixels = () => {
31
+ if (imgData != null) $.ctx.putImageData(imgData, 0, 0);
32
+ };
33
+
34
+ function makeTmpCtx(w, h) {
35
+ h ??= w || $.canvas.height;
36
+ w ??= $.canvas.width;
37
+ if (tmpCtx == null) {
38
+ tmpCtx = new _OffscreenCanvas(w, h).getContext('2d', {
39
+ colorSpace: $.canvas.colorSpace
40
+ });
41
+ }
42
+ if (tmpCtx.canvas.width != w || tmpCtx.canvas.height != h) {
43
+ tmpCtx.canvas.width = w;
44
+ tmpCtx.canvas.height = h;
45
+ }
46
+ }
47
+
48
+ function makeTmpCt2(w, h) {
49
+ h ??= w || $.canvas.height;
50
+ w ??= $.canvas.width;
51
+ if (tmpCt2 == null) {
52
+ tmpCt2 = new _OffscreenCanvas(w, h).getContext('2d', {
53
+ colorSpace: $.canvas.colorSpace
54
+ });
55
+ }
56
+ if (tmpCt2.canvas.width != w || tmpCt2.canvas.height != h) {
57
+ tmpCt2.canvas.width = w;
58
+ tmpCt2.canvas.height = h;
59
+ }
60
+ }
61
+
62
+ function makeTmpBuf() {
63
+ let l = $.canvas.width * $.canvas.height * 4;
64
+ if (!tmpBuf || l != tmpBuf.length) {
65
+ tmpBuf = new Uint8ClampedArray(l);
66
+ }
67
+ }
68
+
69
+ function initSoftFilters() {
70
+ $._filters = [];
71
+ $._filters[$.THRESHOLD] = (data, thresh) => {
72
+ if (thresh === undefined) thresh = 127.5;
73
+ else thresh *= 255;
74
+ for (let i = 0; i < data.length; i += 4) {
75
+ const gray = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
76
+ data[i] = data[i + 1] = data[i + 2] = gray >= thresh ? 255 : 0;
77
+ }
78
+ };
79
+ $._filters[$.GRAY] = (data) => {
80
+ for (let i = 0; i < data.length; i += 4) {
81
+ const gray = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
82
+ data[i] = data[i + 1] = data[i + 2] = gray;
83
+ }
84
+ };
85
+ $._filters[$.OPAQUE] = (data) => {
86
+ for (let i = 0; i < data.length; i += 4) {
87
+ data[i + 3] = 255;
88
+ }
89
+ };
90
+ $._filters[$.INVERT] = (data) => {
91
+ for (let i = 0; i < data.length; i += 4) {
92
+ data[i] = 255 - data[i];
93
+ data[i + 1] = 255 - data[i + 1];
94
+ data[i + 2] = 255 - data[i + 2];
95
+ }
96
+ };
97
+ $._filters[$.POSTERIZE] = (data, lvl = 4) => {
98
+ let lvl1 = lvl - 1;
99
+ for (let i = 0; i < data.length; i += 4) {
100
+ data[i] = (((data[i] * lvl) >> 8) * 255) / lvl1;
101
+ data[i + 1] = (((data[i + 1] * lvl) >> 8) * 255) / lvl1;
102
+ data[i + 2] = (((data[i + 2] * lvl) >> 8) * 255) / lvl1;
103
+ }
104
+ };
105
+ $._filters[$.DILATE] = (data) => {
106
+ makeTmpBuf();
107
+ tmpBuf.set(data);
108
+ let [w, h] = [$.canvas.width, $.canvas.height];
109
+ for (let i = 0; i < h; i++) {
110
+ for (let j = 0; j < w; j++) {
111
+ let l = 4 * Math.max(j - 1, 0);
112
+ let r = 4 * Math.min(j + 1, w - 1);
113
+ let t = 4 * Math.max(i - 1, 0) * w;
114
+ let b = 4 * Math.min(i + 1, h - 1) * w;
115
+ let oi = 4 * i * w;
116
+ let oj = 4 * j;
117
+ for (let k = 0; k < 4; k++) {
118
+ let kt = k + t;
119
+ let kb = k + b;
120
+ let ko = k + oi;
121
+ data[oi + oj + k] = Math.max(
122
+ /*tmpBuf[kt+l],*/ tmpBuf[kt + oj] /*tmpBuf[kt+r],*/,
123
+ tmpBuf[ko + l],
124
+ tmpBuf[ko + oj],
125
+ tmpBuf[ko + r],
126
+ /*tmpBuf[kb+l],*/ tmpBuf[kb + oj] /*tmpBuf[kb+r],*/
127
+ );
128
+ }
129
+ }
130
+ }
131
+ };
132
+ $._filters[$.ERODE] = (data) => {
133
+ makeTmpBuf();
134
+ tmpBuf.set(data);
135
+ let [w, h] = [$.canvas.width, $.canvas.height];
136
+ for (let i = 0; i < h; i++) {
137
+ for (let j = 0; j < w; j++) {
138
+ let l = 4 * Math.max(j - 1, 0);
139
+ let r = 4 * Math.min(j + 1, w - 1);
140
+ let t = 4 * Math.max(i - 1, 0) * w;
141
+ let b = 4 * Math.min(i + 1, h - 1) * w;
142
+ let oi = 4 * i * w;
143
+ let oj = 4 * j;
144
+ for (let k = 0; k < 4; k++) {
145
+ let kt = k + t;
146
+ let kb = k + b;
147
+ let ko = k + oi;
148
+ data[oi + oj + k] = Math.min(
149
+ /*tmpBuf[kt+l],*/ tmpBuf[kt + oj] /*tmpBuf[kt+r],*/,
150
+ tmpBuf[ko + l],
151
+ tmpBuf[ko + oj],
152
+ tmpBuf[ko + r],
153
+ /*tmpBuf[kb+l],*/ tmpBuf[kb + oj] /*tmpBuf[kb+r],*/
154
+ );
155
+ }
156
+ }
157
+ }
158
+ };
159
+ $._filters[$.BLUR] = (data, rad) => {
160
+ rad = rad || 1;
161
+ rad = Math.floor(rad * $._pixelDensity);
162
+ makeTmpBuf();
163
+ tmpBuf.set(data);
164
+
165
+ let ksize = rad * 2 + 1;
166
+
167
+ function gauss1d(ksize) {
168
+ let im = new Float32Array(ksize);
169
+ let sigma = 0.3 * rad + 0.8;
170
+ let ss2 = sigma * sigma * 2;
171
+ for (let i = 0; i < ksize; i++) {
172
+ let x = i - ksize / 2;
173
+ let z = Math.exp(-(x * x) / ss2) / (2.5066282746 * sigma);
174
+ im[i] = z;
175
+ }
176
+ return im;
177
+ }
178
+
179
+ let kern = gauss1d(ksize);
180
+ let [w, h] = [$.canvas.width, $.canvas.height];
181
+ for (let i = 0; i < h; i++) {
182
+ for (let j = 0; j < w; j++) {
183
+ let s0 = 0,
184
+ s1 = 0,
185
+ s2 = 0,
186
+ s3 = 0;
187
+ for (let k = 0; k < ksize; k++) {
188
+ let jk = Math.min(Math.max(j - rad + k, 0), w - 1);
189
+ let idx = 4 * (i * w + jk);
190
+ s0 += tmpBuf[idx] * kern[k];
191
+ s1 += tmpBuf[idx + 1] * kern[k];
192
+ s2 += tmpBuf[idx + 2] * kern[k];
193
+ s3 += tmpBuf[idx + 3] * kern[k];
194
+ }
195
+ let idx = 4 * (i * w + j);
196
+ data[idx] = s0;
197
+ data[idx + 1] = s1;
198
+ data[idx + 2] = s2;
199
+ data[idx + 3] = s3;
200
+ }
201
+ }
202
+ tmpBuf.set(data);
203
+ for (let i = 0; i < h; i++) {
204
+ for (let j = 0; j < w; j++) {
205
+ let s0 = 0,
206
+ s1 = 0,
207
+ s2 = 0,
208
+ s3 = 0;
209
+ for (let k = 0; k < ksize; k++) {
210
+ let ik = Math.min(Math.max(i - rad + k, 0), h - 1);
211
+ let idx = 4 * (ik * w + j);
212
+ s0 += tmpBuf[idx] * kern[k];
213
+ s1 += tmpBuf[idx + 1] * kern[k];
214
+ s2 += tmpBuf[idx + 2] * kern[k];
215
+ s3 += tmpBuf[idx + 3] * kern[k];
216
+ }
217
+ let idx = 4 * (i * w + j);
218
+ data[idx] = s0;
219
+ data[idx + 1] = s1;
220
+ data[idx + 2] = s2;
221
+ data[idx + 3] = s3;
222
+ }
223
+ }
224
+ };
225
+ }
226
+
227
+ function softFilter(typ, x) {
228
+ if (!$._filters) initSoftFilters();
229
+ let imgData = $.ctx.getImageData(0, 0, $.canvas.width, $.canvas.height);
230
+ $._filters[typ](imgData.data, x);
231
+ $.ctx.putImageData(imgData, 0, 0);
232
+ }
233
+
234
+ function nativeFilter(filtstr) {
235
+ tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
236
+ tmpCtx.filter = filtstr;
237
+ tmpCtx.drawImage($.canvas, 0, 0);
238
+ $.ctx.save();
239
+ $.ctx.resetTransform();
240
+ $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
241
+ $.ctx.drawImage(tmpCtx.canvas, 0, 0);
242
+ $.ctx.restore();
243
+ }
244
+
245
+ $.filter = (typ, x) => {
246
+ if (!$.ctx.filter) return softFilter(typ, x);
247
+ makeTmpCtx();
248
+ if (typeof typ == 'string') {
249
+ nativeFilter(typ);
250
+ } else if (typ == $.THRESHOLD) {
251
+ x ??= 0.5;
252
+ x = Math.max(x, 0.00001);
253
+ let b = Math.floor((0.5 / x) * 100);
254
+ nativeFilter(`saturate(0%) brightness(${b}%) contrast(1000000%)`);
255
+ } else if (typ == $.GRAY) {
256
+ nativeFilter(`saturate(0%)`);
257
+ } else if (typ == $.OPAQUE) {
258
+ tmpCtx.fillStyle = 'black';
259
+ tmpCtx.fillRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
260
+ tmpCtx.drawImage($.canvas, 0, 0);
261
+ $.ctx.save();
262
+ $.ctx.resetTransform();
263
+ $.ctx.drawImage(tmpCtx.canvas, 0, 0);
264
+ $.ctx.restore();
265
+ } else if (typ == $.INVERT) {
266
+ nativeFilter(`invert(100%)`);
267
+ } else if (typ == $.BLUR) {
268
+ nativeFilter(`blur(${Math.ceil((x * $._pixelDensity) / 1) || 1}px)`);
269
+ } else {
270
+ softFilter(typ, x);
271
+ }
272
+ };
273
+
274
+ $.resize = (w, h) => {
275
+ makeTmpCtx();
276
+ tmpCtx.drawImage($.canvas, 0, 0);
277
+ $.width = w;
278
+ $.height = h;
279
+ $.canvas.width = w * $._pixelDensity;
280
+ $.canvas.height = h * $._pixelDensity;
281
+ $.ctx.save();
282
+ $.ctx.resetTransform();
283
+ $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
284
+ $.ctx.drawImage(tmpCtx.canvas, 0, 0, $.canvas.width, $.canvas.height);
285
+ $.ctx.restore();
286
+ };
287
+
288
+ $.trim = () => {
289
+ let pd = $._pixelDensity || 1;
290
+ let imgData = $.ctx.getImageData(0, 0, $.width * pd, $.height * pd);
291
+ let data = imgData.data;
292
+ let left = $.width,
293
+ right = 0,
294
+ top = $.height,
295
+ bottom = 0;
296
+
297
+ for (let y = 0; y < $.height * pd; y++) {
298
+ for (let x = 0; x < $.width * pd; x++) {
299
+ let index = (y * $.width * pd + x) * 4;
300
+ if (data[index + 3] !== 0) {
301
+ if (x < left) left = x;
302
+ if (x > right) right = x;
303
+ if (y < top) top = y;
304
+ if (y > bottom) bottom = y;
305
+ }
306
+ }
307
+ }
308
+ top = Math.floor(top / pd);
309
+ bottom = Math.floor(bottom / pd);
310
+ left = Math.floor(left / pd);
311
+ right = Math.floor(right / pd);
312
+
313
+ return $.get(left, top, right - left + 1, bottom - top + 1);
314
+ };
315
+
316
+ $.get = (x, y, w, h) => {
317
+ let pd = $._pixelDensity || 1;
318
+ if (x !== undefined && w === undefined) {
319
+ let c = $.ctx.getImageData(x * pd, y * pd, 1, 1).data;
320
+ return new $.Color(c[0], c[1], c[2], c[3] / 255);
321
+ }
322
+ x = (x || 0) * pd;
323
+ y = (y || 0) * pd;
324
+ let _w = (w = w || $.width);
325
+ let _h = (h = h || $.height);
326
+ w *= pd;
327
+ h *= pd;
328
+ let img = $.createImage(w, h);
329
+ let imgData = $.ctx.getImageData(x, y, w, h);
330
+ img.ctx.putImageData(imgData, 0, 0);
331
+ img._pixelDensity = pd;
332
+ img.width = _w;
333
+ img.height = _h;
334
+ return img;
335
+ };
336
+
337
+ $.set = (x, y, c) => {
338
+ if (c._q5) {
339
+ let old = $._tint;
340
+ $._tint = null;
341
+ $.image(c, x, y);
342
+ $._tint = old;
343
+ return;
344
+ }
345
+ if (!$.pixels.length) $.loadPixels();
346
+ let mod = $._pixelDensity || 1;
347
+ for (let i = 0; i < mod; i++) {
348
+ for (let j = 0; j < mod; j++) {
349
+ let idx = 4 * ((y * mod + i) * $.canvas.width + x * mod + j);
350
+ $.pixels[idx] = c.r ?? c.l;
351
+ $.pixels[idx + 1] = c.g ?? c.c;
352
+ $.pixels[idx + 2] = c.b ?? c.h;
353
+ $.pixels[idx + 3] = c.a;
354
+ }
355
+ }
356
+ };
357
+
358
+ $.tinted = function (col) {
359
+ let alpha = col.a;
360
+ col.a = 255;
361
+ makeTmpCtx();
362
+ tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
363
+ tmpCtx.fillStyle = col.toString();
364
+ tmpCtx.fillRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
365
+ tmpCtx.globalCompositeOperation = 'multiply';
366
+ tmpCtx.drawImage($.ctx.canvas, 0, 0);
367
+ tmpCtx.globalCompositeOperation = 'source-over';
368
+
369
+ $.ctx.save();
370
+ $.ctx.resetTransform();
371
+ let old = $.ctx.globalCompositeOperation;
372
+ $.ctx.globalCompositeOperation = 'source-in';
373
+ $.ctx.drawImage(tmpCtx.canvas, 0, 0);
374
+ $.ctx.globalCompositeOperation = old;
375
+ $.ctx.restore();
376
+
377
+ tmpCtx.globalAlpha = alpha / 255;
378
+ tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
379
+ tmpCtx.drawImage($.ctx.canvas, 0, 0);
380
+ tmpCtx.globalAlpha = 1;
381
+
382
+ $.ctx.save();
383
+ $.ctx.resetTransform();
384
+ $.ctx.clearRect(0, 0, $.ctx.canvas.width, $.ctx.canvas.height);
385
+ $.ctx.drawImage(tmpCtx.canvas, 0, 0);
386
+ $.ctx.restore();
387
+ };
388
+ $.tint = function (c) {
389
+ $._tint = c._q5Color ? c : $.color(...arguments);
390
+ };
391
+ $.noTint = () => ($._tint = null);
392
+
393
+ $.mask = (img) => {
394
+ $.ctx.save();
395
+ $.ctx.resetTransform();
396
+ let old = $.ctx.globalCompositeOperation;
397
+ $.ctx.globalCompositeOperation = 'destination-in';
398
+ $.ctx.drawImage(img.canvas, 0, 0);
399
+ $.ctx.globalCompositeOperation = old;
400
+ $.ctx.restore();
401
+ };
402
+
403
+ $._save = async (data, name, ext) => {
404
+ name = name || 'untitled';
405
+ ext = ext || 'png';
406
+ if (ext == 'jpg' || ext == 'png' || ext == 'webp') {
407
+ if (data instanceof OffscreenCanvas) {
408
+ const blob = await data.convertToBlob({ type: 'image/' + ext });
409
+ data = await new Promise((resolve) => {
410
+ const reader = new FileReader();
411
+ reader.onloadend = () => resolve(reader.result);
412
+ reader.readAsDataURL(blob);
413
+ });
414
+ } else {
415
+ data = data.toDataURL('image/' + ext);
416
+ }
417
+ } else {
418
+ let type = 'text/plain';
419
+ if (ext == 'json') {
420
+ if (typeof data != 'string') data = JSON.stringify(data);
421
+ type = 'text/json';
422
+ }
423
+ data = new Blob([data], { type });
424
+ data = URL.createObjectURL(data);
425
+ }
426
+ let a = document.createElement('a');
427
+ a.href = data;
428
+ a.download = name + '.' + ext;
429
+ document.body.append(a);
430
+ a.click();
431
+ document.body.removeChild(a);
432
+ URL.revokeObjectURL(a.href);
433
+ };
434
+ $.save = (a, b, c) => {
435
+ if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
436
+ c = b;
437
+ b = a;
438
+ a = $.canvas;
439
+ }
440
+ if (c) return $._save(a, b, c);
441
+ if (b) {
442
+ b = b.split('.');
443
+ $._save(a, b[0], b.at(-1));
444
+ } else $._save(a);
445
+ };
446
+ $.canvas.save = $.save;
447
+ $.saveCanvas = $.save;
448
+
449
+ $.createImage = (w, h, opt) => {
450
+ return new Q5.Image(w, h, opt);
451
+ };
452
+
453
+ $._clearTemporaryBuffers = () => {
454
+ tmpCtx = null;
455
+ tmpCt2 = null;
456
+ tmpBuf = null;
457
+ };
458
+
459
+ if ($._scope == 'image') return;
460
+
461
+ // IMAGING
462
+
463
+ $.imageMode = (mode) => ($._imageMode = mode);
464
+ $.image = (img, dx, dy, dWidth, dHeight, sx = 0, sy = 0, sWidth, sHeight) => {
465
+ if ($._da) {
466
+ dx *= $._da;
467
+ dy *= $._da;
468
+ dWidth *= $._da;
469
+ dHeight *= $._da;
470
+ sx *= $._da;
471
+ sy *= $._da;
472
+ sWidth *= $._da;
473
+ sHeight *= $._da;
474
+ }
475
+ let drawable = img._q5 ? img.canvas : img;
476
+ if (Q5._createNodeJSCanvas) {
477
+ drawable = drawable.context.canvas;
478
+ }
479
+ function reset() {
480
+ if (!img._q5 || !$._tint) return;
481
+ let c = img.ctx;
482
+ c.save();
483
+ c.resetTransform();
484
+ c.clearRect(0, 0, c.canvas.width, c.canvas.height);
485
+ c.drawImage(tmpCt2.canvas, 0, 0);
486
+ c.restore();
487
+ }
488
+ if (img._q5 && $._tint != null) {
489
+ makeTmpCt2(img.canvas.width, img.canvas.height);
490
+ tmpCt2.drawImage(img.canvas, 0, 0);
491
+ img.tinted($._tint);
492
+ }
493
+ dWidth ??= img.width || img.videoWidth;
494
+ dHeight ??= img.height || img.videoHeight;
495
+ if ($._imageMode == 'center') {
496
+ dx -= dWidth * 0.5;
497
+ dy -= dHeight * 0.5;
498
+ }
499
+ let pd = img._pixelDensity || 1;
500
+ if (!sWidth) {
501
+ sWidth = drawable.width || drawable.videoWidth;
502
+ } else sWidth *= pd;
503
+ if (!sHeight) {
504
+ sHeight = drawable.height || drawable.videoHeight;
505
+ } else sHeight *= pd;
506
+ $.ctx.drawImage(drawable, sx * pd, sy * pd, sWidth, sHeight, dx, dy, dWidth, dHeight);
507
+ reset();
508
+ };
509
+
510
+ $.loadImage = function (url, cb, opt) {
511
+ $._preloadCount++;
512
+ let last = [...arguments].at(-1);
513
+ opt = typeof last == 'object' ? last : true;
514
+ let g = $.createImage(1, 1, opt.alpha);
515
+ let c = g.ctx;
516
+ if (Q5._nodejs && global.CairoCanvas) {
517
+ CairoCanvas.loadImage(url)
518
+ .then((img) => {
519
+ g.width = c.canvas.width = img.width;
520
+ g.height = c.canvas.height = img.height;
521
+ c.drawImage(img, 0, 0);
522
+ $._preloadCount--;
523
+ if (cb) cb(g);
524
+ })
525
+ .catch((e) => {
526
+ $._preloadCount--;
527
+ throw e;
528
+ });
529
+ } else {
530
+ let img = new window.Image();
531
+ img.src = url;
532
+ img.crossOrigin = 'Anonymous';
533
+ img._pixelDensity = 1;
534
+ img.onload = () => {
535
+ g.width = c.canvas.width = img.naturalWidth;
536
+ g.height = c.canvas.height = img.naturalHeight;
537
+ c.drawImage(img, 0, 0);
538
+ $._preloadCount--;
539
+ if (cb) cb(g);
540
+ };
541
+ img.onerror = (e) => {
542
+ $._preloadCount--;
543
+ throw e;
544
+ };
545
+ }
546
+ return g;
547
+ };
548
+
549
+ $.smooth = () => ($.ctx.imageSmoothingEnabled = true);
550
+ $.noSmooth = () => ($.ctx.imageSmoothingEnabled = false);
551
+ };
552
+
553
+ // IMAGE CLASS
554
+
555
+ class _Q5Image extends Q5 {
556
+ constructor(w, h, opt) {
557
+ super('image');
558
+ delete this.createCanvas;
559
+ opt ??= {};
560
+ opt.alpha ??= true;
561
+ this._createCanvas(w, h, '2d', opt);
562
+ this._loop = false;
563
+ }
564
+ get w() {
565
+ return this.width;
566
+ }
567
+ get h() {
568
+ return this.height;
569
+ }
570
+ }
571
+
572
+ Q5.Image ??= _Q5Image;