q5 2.4.4 → 2.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/q5.d.ts +1425 -0
- package/q5.js +402 -226
- package/q5.min.js +1 -1
- package/src/q5-2d-canvas.js +29 -20
- package/src/q5-2d-image.js +1 -0
- package/src/q5-2d-text.js +188 -142
- package/src/q5-canvas.js +2 -2
- package/src/q5-vector.js +3 -3
- package/src/q5-webgpu-canvas.js +103 -45
- package/src/q5-webgpu-drawing.js +11 -0
- package/src/q5-webgpu-image.js +32 -4
- package/src/q5-webgpu-text.js +30 -7
- package/src/readme.md +3 -11
package/src/q5-2d-text.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
Q5.renderers.q2d.text = ($, q) => {
|
|
2
|
-
$.
|
|
3
|
-
$.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
$._textAlign = 'left';
|
|
3
|
+
$._textBaseline = 'alphabetic';
|
|
4
|
+
|
|
5
|
+
let font = 'sans-serif',
|
|
6
|
+
tSize = 12,
|
|
7
|
+
leading = 15,
|
|
8
|
+
leadDiff = 3,
|
|
9
|
+
emphasis = 'normal',
|
|
10
|
+
fontMod = false,
|
|
11
|
+
styleHash = 0,
|
|
12
|
+
styleHashes = [],
|
|
13
|
+
useCache = false,
|
|
14
|
+
genTextImage = false,
|
|
15
|
+
cacheSize = 0,
|
|
16
|
+
cacheMax = 12000;
|
|
17
|
+
|
|
18
|
+
let cache = ($._textCache = {});
|
|
7
19
|
|
|
8
20
|
$.loadFont = (url, cb) => {
|
|
9
21
|
q._preloadCount++;
|
|
@@ -16,180 +28,214 @@ Q5.renderers.q2d.text = ($, q) => {
|
|
|
16
28
|
});
|
|
17
29
|
return name;
|
|
18
30
|
};
|
|
19
|
-
|
|
31
|
+
|
|
32
|
+
$.textFont = (x) => {
|
|
33
|
+
font = x;
|
|
34
|
+
fontMod = true;
|
|
35
|
+
styleHash = -1;
|
|
36
|
+
};
|
|
20
37
|
$.textSize = (x) => {
|
|
21
|
-
if (x === undefined) return
|
|
38
|
+
if (x === undefined) return tSize;
|
|
22
39
|
if ($._da) x *= $._da;
|
|
23
|
-
|
|
40
|
+
tSize = x;
|
|
41
|
+
fontMod = true;
|
|
42
|
+
styleHash = -1;
|
|
24
43
|
if (!$._leadingSet) {
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
leading = x * 1.25;
|
|
45
|
+
leadDiff = leading - x;
|
|
27
46
|
}
|
|
28
47
|
};
|
|
48
|
+
$.textStyle = (x) => {
|
|
49
|
+
emphasis = x;
|
|
50
|
+
fontMod = true;
|
|
51
|
+
styleHash = -1;
|
|
52
|
+
};
|
|
29
53
|
$.textLeading = (x) => {
|
|
30
|
-
if (x === undefined) return
|
|
54
|
+
if (x === undefined) return leading;
|
|
31
55
|
if ($._da) x *= $._da;
|
|
32
|
-
|
|
33
|
-
|
|
56
|
+
leading = x;
|
|
57
|
+
leadDiff = x - tSize;
|
|
34
58
|
$._leadingSet = true;
|
|
59
|
+
styleHash = -1;
|
|
35
60
|
};
|
|
36
|
-
$.textStyle = (x) => ($._textStyle = x);
|
|
37
61
|
$.textAlign = (horiz, vert) => {
|
|
38
|
-
$.ctx.textAlign = horiz;
|
|
62
|
+
$.ctx.textAlign = $._textAlign = horiz;
|
|
39
63
|
if (vert) {
|
|
40
|
-
$.ctx.textBaseline = vert == $.CENTER ? 'middle' : vert;
|
|
64
|
+
$.ctx.textBaseline = $._textBaseline = vert == $.CENTER ? 'middle' : vert;
|
|
41
65
|
}
|
|
66
|
+
styleHash = -1;
|
|
42
67
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
$.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
|
|
49
|
-
return $.ctx.measureText(str).actualBoundingBoxAscent;
|
|
50
|
-
};
|
|
51
|
-
$.textDescent = (str) => {
|
|
52
|
-
$.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
|
|
53
|
-
return $.ctx.measureText(str).actualBoundingBoxDescent;
|
|
54
|
-
};
|
|
68
|
+
|
|
69
|
+
$.textWidth = (str) => $.ctx.measureText(str).width;
|
|
70
|
+
$.textAscent = (str) => $.ctx.measureText(str).actualBoundingBoxAscent;
|
|
71
|
+
$.textDescent = (str) => $.ctx.measureText(str).actualBoundingBoxDescent;
|
|
72
|
+
|
|
55
73
|
$.textFill = $.fill;
|
|
56
74
|
$.textStroke = $.stroke;
|
|
57
75
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
set(k, v) {
|
|
65
|
-
v.lastAccessed = Date.now();
|
|
66
|
-
super.set(k, v);
|
|
67
|
-
if (this.size > this.maxSize) this.gc();
|
|
68
|
-
}
|
|
69
|
-
get(k) {
|
|
70
|
-
const v = super.get(k);
|
|
71
|
-
if (v) v.lastAccessed = Date.now();
|
|
72
|
-
return v;
|
|
73
|
-
}
|
|
74
|
-
gc() {
|
|
75
|
-
let t = Infinity;
|
|
76
|
-
let oldest;
|
|
77
|
-
let i = 0;
|
|
78
|
-
for (const [k, v] of this.entries()) {
|
|
79
|
-
if (v.lastAccessed < t) {
|
|
80
|
-
t = v.lastAccessed;
|
|
81
|
-
oldest = i;
|
|
82
|
-
}
|
|
83
|
-
i++;
|
|
84
|
-
}
|
|
85
|
-
i = oldest;
|
|
86
|
-
for (const k of this.keys()) {
|
|
87
|
-
if (i == 0) {
|
|
88
|
-
oldest = k;
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
i--;
|
|
92
|
-
}
|
|
93
|
-
this.delete(oldest);
|
|
76
|
+
let updateStyleHash = () => {
|
|
77
|
+
let styleString = font + tSize + emphasis + leading;
|
|
78
|
+
|
|
79
|
+
let hash = 5381;
|
|
80
|
+
for (let i = 0; i < styleString.length; i++) {
|
|
81
|
+
hash = (hash * 33) ^ styleString.charCodeAt(i);
|
|
94
82
|
}
|
|
83
|
+
styleHash = hash >>> 0;
|
|
95
84
|
};
|
|
96
|
-
|
|
97
|
-
$.textCache = (
|
|
98
|
-
if (maxSize)
|
|
99
|
-
if (
|
|
100
|
-
return
|
|
101
|
-
};
|
|
102
|
-
$._genTextImageKey = (str, w, h) => {
|
|
103
|
-
return (
|
|
104
|
-
str.slice(0, 200) +
|
|
105
|
-
$._textStyle +
|
|
106
|
-
$._textSize +
|
|
107
|
-
$._textFont +
|
|
108
|
-
($._doFill ? $.ctx.fillStyle : '') +
|
|
109
|
-
'_' +
|
|
110
|
-
($._doStroke && $._strokeSet ? $.ctx.lineWidth + $.ctx.strokeStyle + '_' : '') +
|
|
111
|
-
(w || '') +
|
|
112
|
-
(h ? 'x' + h : '')
|
|
113
|
-
);
|
|
85
|
+
|
|
86
|
+
$.textCache = (enable, maxSize) => {
|
|
87
|
+
if (maxSize) cacheMax = maxSize;
|
|
88
|
+
if (enable !== undefined) useCache = enable;
|
|
89
|
+
return useCache;
|
|
114
90
|
};
|
|
115
91
|
$.createTextImage = (str, w, h) => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
$._textCache = true;
|
|
121
|
-
$._genTextImage = true;
|
|
122
|
-
$.text(str, 0, 0, w, h);
|
|
123
|
-
$._genTextImage = false;
|
|
124
|
-
$._textCache = og;
|
|
125
|
-
return $._tic.get(k);
|
|
92
|
+
genTextImage = true;
|
|
93
|
+
img = $.text(str, 0, 0, w, h);
|
|
94
|
+
genTextImage = false;
|
|
95
|
+
return img;
|
|
126
96
|
};
|
|
97
|
+
|
|
98
|
+
let lines = [];
|
|
127
99
|
$.text = (str, x, y, w, h) => {
|
|
128
|
-
if (str === undefined) return;
|
|
100
|
+
if (str === undefined || (!$._doFill && !$._doStroke)) return;
|
|
129
101
|
str = str.toString();
|
|
130
102
|
if ($._da) {
|
|
131
103
|
x *= $._da;
|
|
132
104
|
y *= $._da;
|
|
133
105
|
}
|
|
134
|
-
|
|
135
|
-
let
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
106
|
+
let ctx = $.ctx;
|
|
107
|
+
let img, tX, tY;
|
|
108
|
+
|
|
109
|
+
if (fontMod) {
|
|
110
|
+
ctx.font = `${emphasis} ${tSize}px ${font}`;
|
|
111
|
+
fontMod = false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (useCache || genTextImage) {
|
|
115
|
+
if (styleHash == -1) updateStyleHash();
|
|
116
|
+
|
|
117
|
+
img = cache[str];
|
|
118
|
+
if (img) img = img[styleHash];
|
|
119
|
+
|
|
120
|
+
if (img) {
|
|
121
|
+
if (img._fill == $._fill && img._stroke == $._stroke && img._strokeWeight == $._strokeWeight) {
|
|
122
|
+
if (genTextImage) return img;
|
|
123
|
+
return $.textImage(img, x, y);
|
|
124
|
+
} else img.clear();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (str.indexOf('\n') == -1) lines[0] = str;
|
|
129
|
+
else lines = str.split('\n');
|
|
130
|
+
|
|
131
|
+
if (w) {
|
|
132
|
+
let wrapped = [];
|
|
133
|
+
for (let line of lines) {
|
|
134
|
+
let i = 0;
|
|
135
|
+
|
|
136
|
+
while (i < line.length) {
|
|
137
|
+
let max = i + w;
|
|
138
|
+
if (max >= line.length) {
|
|
139
|
+
wrapped.push(line.slice(i));
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
let end = line.lastIndexOf(' ', max);
|
|
143
|
+
if (end === -1 || end < i) {
|
|
144
|
+
end = max;
|
|
145
|
+
}
|
|
146
|
+
wrapped.push(line.slice(i, end));
|
|
147
|
+
i = end;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
lines = wrapped;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!useCache && !genTextImage) {
|
|
154
|
+
tX = x;
|
|
155
|
+
tY = y;
|
|
142
156
|
} else {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
tX = 0;
|
|
158
|
+
tY = leading * lines.length;
|
|
159
|
+
|
|
160
|
+
if (!img) {
|
|
161
|
+
let measure = ctx.measureText(' ');
|
|
162
|
+
let ascent = measure.fontBoundingBoxAscent;
|
|
163
|
+
let descent = measure.fontBoundingBoxDescent;
|
|
164
|
+
h ??= tY + descent;
|
|
165
|
+
|
|
166
|
+
img = $.createImage.call($, Math.ceil(ctx.measureText(str).width), Math.ceil(h), {
|
|
167
|
+
pixelDensity: $._pixelDensity
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
img._ascent = ascent;
|
|
171
|
+
img._descent = descent;
|
|
172
|
+
img._top = descent + leadDiff;
|
|
173
|
+
img._middle = img._top + ascent * 0.5;
|
|
174
|
+
img._bottom = img._top + ascent;
|
|
148
175
|
}
|
|
149
|
-
|
|
150
|
-
|
|
176
|
+
|
|
177
|
+
img._fill = $._fill;
|
|
178
|
+
img._stroke = $._stroke;
|
|
179
|
+
img._strokeWeight = $._strokeWeight;
|
|
180
|
+
img.modified = true;
|
|
181
|
+
|
|
182
|
+
ctx = img.ctx;
|
|
183
|
+
|
|
184
|
+
ctx.font = $.ctx.font;
|
|
185
|
+
ctx.fillStyle = $._fill;
|
|
186
|
+
ctx.strokeStyle = $._stroke;
|
|
187
|
+
ctx.lineWidth = $.ctx.lineWidth;
|
|
151
188
|
}
|
|
152
|
-
|
|
153
|
-
let
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let m = c.measureText(' ');
|
|
158
|
-
_ascent = m.fontBoundingBoxAscent;
|
|
159
|
-
_descent = m.fontBoundingBoxDescent;
|
|
160
|
-
h ??= cY + _descent;
|
|
161
|
-
tg.resizeCanvas(Math.ceil(c.measureText(str).width), Math.ceil(h));
|
|
162
|
-
|
|
163
|
-
c.fillStyle = $.ctx.fillStyle;
|
|
164
|
-
c.strokeStyle = $.ctx.strokeStyle;
|
|
165
|
-
c.lineWidth = $.ctx.lineWidth;
|
|
189
|
+
|
|
190
|
+
let ogFill;
|
|
191
|
+
if (!$._fillSet) {
|
|
192
|
+
ogFill = ctx.fillStyle;
|
|
193
|
+
ctx.fillStyle = 'black';
|
|
166
194
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if ($.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (cY > h) break;
|
|
195
|
+
|
|
196
|
+
for (let line of lines) {
|
|
197
|
+
if ($._doStroke && $._strokeSet) ctx.strokeText(line, tX, tY);
|
|
198
|
+
if ($._doFill) ctx.fillText(line, tX, tY);
|
|
199
|
+
tY += leading;
|
|
200
|
+
if (tY > h) break;
|
|
174
201
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
202
|
+
lines.length = 0;
|
|
203
|
+
|
|
204
|
+
if (!$._fillSet) ctx.fillStyle = ogFill;
|
|
205
|
+
|
|
206
|
+
if (useCache || genTextImage) {
|
|
207
|
+
styleHashes.push(styleHash);
|
|
208
|
+
(cache[str] ??= {})[styleHash] = img;
|
|
209
|
+
|
|
210
|
+
cacheSize++;
|
|
211
|
+
if (cacheSize > cacheMax) {
|
|
212
|
+
let half = Math.ceil(cacheSize / 2);
|
|
213
|
+
let hashes = styleHashes.splice(0, half);
|
|
214
|
+
for (let s in cache) {
|
|
215
|
+
s = cache[s];
|
|
216
|
+
for (let h of hashes) delete s[h];
|
|
217
|
+
}
|
|
218
|
+
cacheSize -= half;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (genTextImage) return img;
|
|
222
|
+
$.textImage(img, x, y);
|
|
182
223
|
}
|
|
183
224
|
};
|
|
184
225
|
$.textImage = (img, x, y) => {
|
|
185
226
|
let og = $._imageMode;
|
|
186
227
|
$._imageMode = 'corner';
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
228
|
+
|
|
229
|
+
let ta = $._textAlign;
|
|
230
|
+
if (ta == 'center') x -= img.canvas.hw;
|
|
231
|
+
else if (ta == 'right') x -= img.width;
|
|
232
|
+
|
|
233
|
+
let bl = $._textBaseline;
|
|
234
|
+
if (bl == 'alphabetic') y -= leading;
|
|
235
|
+
else if (bl == 'middle') y -= img._middle;
|
|
236
|
+
else if (bl == 'bottom') y -= img._bottom;
|
|
237
|
+
else if (bl == 'top') y -= img._top;
|
|
238
|
+
|
|
193
239
|
$.image(img, x, y);
|
|
194
240
|
$._imageMode = og;
|
|
195
241
|
};
|
package/src/q5-canvas.js
CHANGED
|
@@ -272,12 +272,12 @@ Q5.modules.canvas = ($, q) => {
|
|
|
272
272
|
];
|
|
273
273
|
$._styles = [];
|
|
274
274
|
|
|
275
|
-
$.
|
|
275
|
+
$.pushStyles = () => {
|
|
276
276
|
let styles = {};
|
|
277
277
|
for (let s of $._styleNames) styles[s] = $[s];
|
|
278
278
|
$._styles.push(styles);
|
|
279
279
|
};
|
|
280
|
-
$.
|
|
280
|
+
$.popStyles = () => {
|
|
281
281
|
let styles = $._styles.pop();
|
|
282
282
|
for (let s of $._styleNames) $[s] = styles[s];
|
|
283
283
|
};
|
package/src/q5-vector.js
CHANGED
|
@@ -138,9 +138,9 @@ Q5.Vector = class {
|
|
|
138
138
|
return this._$.atan2(this.y, this.x);
|
|
139
139
|
}
|
|
140
140
|
setHeading(ang) {
|
|
141
|
-
let mag = this.mag();
|
|
142
|
-
this.x = mag * this._$.cos(ang);
|
|
143
|
-
this.y = mag * this._$.sin(ang);
|
|
141
|
+
let mag = this.mag();
|
|
142
|
+
this.x = mag * this._$.cos(ang);
|
|
143
|
+
this.y = mag * this._$.sin(ang);
|
|
144
144
|
return this;
|
|
145
145
|
}
|
|
146
146
|
rotate(ang) {
|
package/src/q5-webgpu-canvas.js
CHANGED
|
@@ -60,8 +60,8 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
60
60
|
$._createCanvas = (w, h, opt) => {
|
|
61
61
|
q.ctx = q.drawingContext = c.getContext('webgpu');
|
|
62
62
|
|
|
63
|
-
opt.format
|
|
64
|
-
opt.device
|
|
63
|
+
opt.format ??= navigator.gpu.getPreferredCanvasFormat();
|
|
64
|
+
opt.device ??= Q5.device;
|
|
65
65
|
|
|
66
66
|
$.ctx.configure(opt);
|
|
67
67
|
|
|
@@ -86,6 +86,37 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
86
86
|
$._setCanvasSize(w, h);
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
+
// current color index, used to associate a vertex with a color
|
|
90
|
+
let colorIndex = 0;
|
|
91
|
+
const addColor = (r, g, b, a = 1) => {
|
|
92
|
+
if (typeof r == 'string') r = $.color(r);
|
|
93
|
+
else if (b == undefined) {
|
|
94
|
+
// grayscale mode `fill(1, 0.5)`
|
|
95
|
+
a = g ?? 1;
|
|
96
|
+
g = b = r;
|
|
97
|
+
}
|
|
98
|
+
if (r._q5Color) colorsStack.push(r.r, r.g, r.b, r.a);
|
|
99
|
+
else colorsStack.push(r, g, b, a);
|
|
100
|
+
colorIndex++;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
$.fill = (r, g, b, a) => {
|
|
104
|
+
addColor(r, g, b, a);
|
|
105
|
+
$._doFill = true;
|
|
106
|
+
$._fillIndex = colorIndex;
|
|
107
|
+
};
|
|
108
|
+
$.stroke = (r, g, b, a) => {
|
|
109
|
+
addColor(r, g, b, a);
|
|
110
|
+
$._doStroke = true;
|
|
111
|
+
$._strokeIndex = colorIndex;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
$.noFill = () => ($._doFill = false);
|
|
115
|
+
$.noStroke = () => ($._doStroke = false);
|
|
116
|
+
|
|
117
|
+
$._strokeWeight = 1;
|
|
118
|
+
$.strokeWeight = (v) => ($._strokeWeight = Math.abs(v));
|
|
119
|
+
|
|
89
120
|
$.resetMatrix = () => {
|
|
90
121
|
// Initialize the transformation matrix as 4x4 identity matrix
|
|
91
122
|
|
|
@@ -109,24 +140,6 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
109
140
|
// Stack to keep track of transformation matrix indexes
|
|
110
141
|
$._transformIndexStack = [];
|
|
111
142
|
|
|
112
|
-
$.push = $.pushMatrix = () => {
|
|
113
|
-
// Push the current matrix index onto the stack
|
|
114
|
-
$._transformIndexStack.push($._transformIndex);
|
|
115
|
-
$._pushStyles();
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
$.pop = $.popMatrix = () => {
|
|
119
|
-
if (!$._transformIndexStack.length) {
|
|
120
|
-
return console.warn('Matrix index stack is empty!');
|
|
121
|
-
}
|
|
122
|
-
// Pop the last matrix index from the stack and set it as the current matrix index
|
|
123
|
-
let idx = $._transformIndexStack.pop();
|
|
124
|
-
$._matrix = $.transformStates[idx].slice();
|
|
125
|
-
$._transformIndex = idx;
|
|
126
|
-
$._matrixDirty = false;
|
|
127
|
-
$._popStyles();
|
|
128
|
-
};
|
|
129
|
-
|
|
130
143
|
$.translate = (x, y, z) => {
|
|
131
144
|
if (!x && !y && !z) return;
|
|
132
145
|
// Update the translation values
|
|
@@ -172,6 +185,57 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
172
185
|
$._matrixDirty = true;
|
|
173
186
|
};
|
|
174
187
|
|
|
188
|
+
$.shearX = (ang) => {
|
|
189
|
+
if (!ang) return;
|
|
190
|
+
if ($._angleMode) ang *= $._DEGTORAD;
|
|
191
|
+
|
|
192
|
+
let tanAng = Math.tan(ang);
|
|
193
|
+
|
|
194
|
+
let m0 = $._matrix[0],
|
|
195
|
+
m1 = $._matrix[1],
|
|
196
|
+
m4 = $._matrix[4],
|
|
197
|
+
m5 = $._matrix[5];
|
|
198
|
+
|
|
199
|
+
$._matrix[0] = m0 + m4 * tanAng;
|
|
200
|
+
$._matrix[1] = m1 + m5 * tanAng;
|
|
201
|
+
|
|
202
|
+
$._matrixDirty = true;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
$.shearY = (ang) => {
|
|
206
|
+
if (!ang) return;
|
|
207
|
+
if ($._angleMode) ang *= $._DEGTORAD;
|
|
208
|
+
|
|
209
|
+
let tanAng = Math.tan(ang);
|
|
210
|
+
|
|
211
|
+
let m0 = $._matrix[0],
|
|
212
|
+
m1 = $._matrix[1],
|
|
213
|
+
m4 = $._matrix[4],
|
|
214
|
+
m5 = $._matrix[5];
|
|
215
|
+
|
|
216
|
+
$._matrix[4] = m4 + m0 * tanAng;
|
|
217
|
+
$._matrix[5] = m5 + m1 * tanAng;
|
|
218
|
+
|
|
219
|
+
$._matrixDirty = true;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
$.applyMatrix = (...args) => {
|
|
223
|
+
let m;
|
|
224
|
+
if (args.length == 1) m = args[0];
|
|
225
|
+
else m = args;
|
|
226
|
+
|
|
227
|
+
if (m.length == 9) {
|
|
228
|
+
// Convert 3x3 matrix to 4x4 matrix
|
|
229
|
+
m = [m[0], m[1], 0, m[2], m[3], m[4], 0, m[5], 0, 0, 1, 0, m[6], m[7], 0, m[8]];
|
|
230
|
+
} else if (m.length != 16) {
|
|
231
|
+
throw new Error('Matrix must be a 3x3 or 4x4 array.');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Overwrite the current transformation matrix
|
|
235
|
+
$._matrix = m.slice();
|
|
236
|
+
$._matrixDirty = true;
|
|
237
|
+
};
|
|
238
|
+
|
|
175
239
|
// Function to save the current matrix state if dirty
|
|
176
240
|
$._saveMatrix = () => {
|
|
177
241
|
$.transformStates.push($._matrix.slice());
|
|
@@ -179,37 +243,31 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
179
243
|
$._matrixDirty = false;
|
|
180
244
|
};
|
|
181
245
|
|
|
182
|
-
// current
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
246
|
+
// Push the current matrix index onto the stack
|
|
247
|
+
$.pushMatrix = () => {
|
|
248
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
249
|
+
$._transformIndexStack.push($._transformIndex);
|
|
250
|
+
};
|
|
251
|
+
$.popMatrix = () => {
|
|
252
|
+
if (!$._transformIndexStack.length) {
|
|
253
|
+
return console.warn('Matrix index stack is empty!');
|
|
190
254
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
255
|
+
// Pop the last matrix index from the stack and set it as the current matrix index
|
|
256
|
+
let idx = $._transformIndexStack.pop();
|
|
257
|
+
$._matrix = $.transformStates[idx].slice();
|
|
258
|
+
$._transformIndex = idx;
|
|
259
|
+
$._matrixDirty = false;
|
|
194
260
|
};
|
|
195
261
|
|
|
196
|
-
$.
|
|
197
|
-
|
|
198
|
-
$.
|
|
199
|
-
$._fillIndex = colorIndex;
|
|
262
|
+
$.push = () => {
|
|
263
|
+
$.pushMatrix();
|
|
264
|
+
$.pushStyles();
|
|
200
265
|
};
|
|
201
|
-
$.
|
|
202
|
-
|
|
203
|
-
$.
|
|
204
|
-
$._strokeIndex = colorIndex;
|
|
266
|
+
$.pop = () => {
|
|
267
|
+
$.popMatrix();
|
|
268
|
+
$.popStyles();
|
|
205
269
|
};
|
|
206
270
|
|
|
207
|
-
$.noFill = () => ($._doFill = false);
|
|
208
|
-
$.noStroke = () => ($._doStroke = false);
|
|
209
|
-
|
|
210
|
-
$._strokeWeight = 1;
|
|
211
|
-
$.strokeWeight = (v) => ($._strokeWeight = Math.abs(v));
|
|
212
|
-
|
|
213
271
|
$._calcBox = (x, y, w, h, mode) => {
|
|
214
272
|
let hw = w / 2;
|
|
215
273
|
let hh = h / 2;
|
package/src/q5-webgpu-drawing.js
CHANGED
|
@@ -224,6 +224,15 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
224
224
|
$.endShape(1);
|
|
225
225
|
};
|
|
226
226
|
|
|
227
|
+
$.quad = (x1, y1, x2, y2, x3, y3, x4, y4) => {
|
|
228
|
+
$.beginShape();
|
|
229
|
+
$.vertex(x1, y1);
|
|
230
|
+
$.vertex(x2, y2);
|
|
231
|
+
$.vertex(x3, y3);
|
|
232
|
+
$.vertex(x4, y4);
|
|
233
|
+
$.endShape(1);
|
|
234
|
+
};
|
|
235
|
+
|
|
227
236
|
$.rectMode = (x) => ($._rectMode = x);
|
|
228
237
|
|
|
229
238
|
$.rect = (x, y, w, h) => {
|
|
@@ -245,6 +254,8 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
245
254
|
drawStack.push(0, 6);
|
|
246
255
|
};
|
|
247
256
|
|
|
257
|
+
$.square = (x, y, s) => $.rect(x, y, s, s);
|
|
258
|
+
|
|
248
259
|
$.point = (x, y) => {
|
|
249
260
|
colorIndex = $._strokeIndex;
|
|
250
261
|
let sw = $._strokeWeight;
|
package/src/q5-webgpu-image.js
CHANGED
|
@@ -118,20 +118,30 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
|
|
|
118
118
|
minFilter: 'linear'
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
+
let MAX_TEXTURES = 12000;
|
|
122
|
+
|
|
123
|
+
$._textures = [];
|
|
124
|
+
let tIdx = 0;
|
|
125
|
+
|
|
121
126
|
$._createTexture = (img) => {
|
|
122
127
|
if (img.canvas) img = img.canvas;
|
|
123
128
|
|
|
124
129
|
let textureSize = [img.width, img.height, 1];
|
|
125
130
|
|
|
126
|
-
|
|
131
|
+
let texture = Q5.device.createTexture({
|
|
127
132
|
size: textureSize,
|
|
128
133
|
format: 'bgra8unorm',
|
|
129
134
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
130
135
|
});
|
|
131
136
|
|
|
132
|
-
Q5.device.queue.copyExternalImageToTexture(
|
|
137
|
+
Q5.device.queue.copyExternalImageToTexture(
|
|
138
|
+
{ source: img },
|
|
139
|
+
{ texture, colorSpace: $.canvas.colorSpace },
|
|
140
|
+
textureSize
|
|
141
|
+
);
|
|
133
142
|
|
|
134
|
-
|
|
143
|
+
$._textures[tIdx] = texture;
|
|
144
|
+
img.textureIndex = tIdx;
|
|
135
145
|
|
|
136
146
|
const textureBindGroup = Q5.device.createBindGroup({
|
|
137
147
|
layout: textureLayout,
|
|
@@ -140,7 +150,16 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
|
|
|
140
150
|
{ binding: 1, resource: texture.createView() }
|
|
141
151
|
]
|
|
142
152
|
});
|
|
143
|
-
$._textureBindGroups
|
|
153
|
+
$._textureBindGroups[tIdx] = textureBindGroup;
|
|
154
|
+
|
|
155
|
+
tIdx = (tIdx + 1) % MAX_TEXTURES;
|
|
156
|
+
|
|
157
|
+
// If the texture array is full, destroy the oldest texture
|
|
158
|
+
if ($._textures[tIdx]) {
|
|
159
|
+
$._textures[tIdx].destroy();
|
|
160
|
+
delete $._textures[tIdx];
|
|
161
|
+
delete $._textureBindGroups[tIdx];
|
|
162
|
+
}
|
|
144
163
|
};
|
|
145
164
|
|
|
146
165
|
$.loadImage = $.loadTexture = (src) => {
|
|
@@ -204,3 +223,12 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
|
|
|
204
223
|
verticesStack.length = 0;
|
|
205
224
|
});
|
|
206
225
|
};
|
|
226
|
+
|
|
227
|
+
Q5.THRESHOLD = 1;
|
|
228
|
+
Q5.GRAY = 2;
|
|
229
|
+
Q5.OPAQUE = 3;
|
|
230
|
+
Q5.INVERT = 4;
|
|
231
|
+
Q5.POSTERIZE = 5;
|
|
232
|
+
Q5.DILATE = 6;
|
|
233
|
+
Q5.ERODE = 7;
|
|
234
|
+
Q5.BLUR = 8;
|