Skip to content

Commit f15bbc9

Browse files
2.28.5
1 parent d4c8449 commit f15bbc9

9 files changed

+171
-189
lines changed

deno.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@q5/q5",
3-
"version": "2.28.0",
3+
"version": "2.28.5",
44
"license": "LGPL-3.0",
55
"description": "Beginner friendly graphics powered by WebGPU and optimized for interactive art!",
66
"author": "quinton-ashley",

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "q5",
3-
"version": "2.28.4",
3+
"version": "2.28.5",
44
"description": "Beginner friendly graphics powered by WebGPU and optimized for interactive art!",
55
"author": "quinton-ashley",
66
"contributors": [

q5.js

+84-93
Original file line numberDiff line numberDiff line change
@@ -399,10 +399,10 @@ Q5.modules.canvas = ($, q) => {
399399

400400
if (Q5._server) {
401401
if (Q5._createServerCanvas) {
402-
q.canvas = Q5._createServerCanvas(100, 100);
402+
q.canvas = Q5._createServerCanvas(200, 200);
403403
}
404404
} else if ($._scope == 'image' || $._scope == 'graphics') {
405-
q.canvas = new $._Canvas(100, 100);
405+
q.canvas = new $._Canvas(200, 200);
406406
}
407407

408408
if (!$.canvas) {
@@ -413,12 +413,13 @@ Q5.modules.canvas = ($, q) => {
413413
} else $.noCanvas();
414414
}
415415

416-
let c = $.canvas;
416+
$.displayDensity = () => window.devicePixelRatio || 1;
417+
417418
$.width = 200;
418419
$.height = 200;
419420
$._pixelDensity = 1;
420421

421-
$.displayDensity = () => window.devicePixelRatio || 1;
422+
let c = $.canvas;
422423

423424
if (c) {
424425
c.width = 200;
@@ -1327,13 +1328,23 @@ Q5.renderers.c2d.shapes = ($) => {
13271328
};
13281329
};
13291330
Q5.renderers.c2d.image = ($, q) => {
1331+
const c = $.canvas;
1332+
1333+
if (c) {
1334+
// polyfill for HTMLCanvasElement
1335+
c.convertToBlob ??= (opt) =>
1336+
new Promise((resolve) => {
1337+
c.toBlob((blob) => resolve(blob), opt.type, opt.quality);
1338+
});
1339+
}
1340+
13301341
$._tint = null;
13311342
let imgData = null;
13321343

13331344
$.createImage = (w, h, opt) => {
13341345
opt ??= {};
13351346
opt.alpha ??= true;
1336-
opt.colorSpace ??= $.canvas.colorSpace || Q5.canvasOptions.colorSpace;
1347+
opt.colorSpace ??= c.colorSpace || Q5.canvasOptions.colorSpace;
13371348
return new Q5.Image($, w, h, opt);
13381349
};
13391350

@@ -1491,14 +1502,13 @@ Q5.renderers.c2d.image = ($, q) => {
14911502
if (!f) $._softFilter(type, value);
14921503

14931504
$.ctx.globalCompositeOperation = 'source-over';
1494-
$.ctx.drawImage($.canvas, 0, 0, $.canvas.w, $.canvas.h);
1505+
$.ctx.drawImage(c, 0, 0, c.w, c.h);
14951506
$.ctx.restore();
14961507
$.modified = $._retint = true;
14971508
};
14981509

14991510
if ($._scope == 'image') {
15001511
$.resize = (w, h) => {
1501-
let c = $.canvas;
15021512
let o = new $._Canvas(c.width, c.height);
15031513
let tmpCtx = o.getContext('2d', {
15041514
colorSpace: c.colorSpace
@@ -1516,13 +1526,13 @@ Q5.renderers.c2d.image = ($, q) => {
15161526
}
15171527

15181528
$._getImageData = (x, y, w, h) => {
1519-
return $.ctx.getImageData(x, y, w, h, { colorSpace: $.canvas.colorSpace });
1529+
return $.ctx.getImageData(x, y, w, h, { colorSpace: c.colorSpace });
15201530
};
15211531

15221532
$.trim = () => {
15231533
let pd = $._pixelDensity || 1;
1524-
let w = $.canvas.width;
1525-
let h = $.canvas.height;
1534+
let w = c.width;
1535+
let h = c.height;
15261536
let data = $._getImageData(0, 0, w, h).data;
15271537
let left = w,
15281538
right = 0,
@@ -1563,7 +1573,7 @@ Q5.renderers.c2d.image = ($, q) => {
15631573

15641574
$.inset = (x, y, w, h, dx, dy, dw, dh) => {
15651575
let pd = $._pixelDensity || 1;
1566-
$.ctx.drawImage($.canvas, x * pd, y * pd, w * pd, h * pd, dx, dy, dw, dh);
1576+
$.ctx.drawImage(c, x * pd, y * pd, w * pd, h * pd, dx, dy, dw, dh);
15671577

15681578
$.modified = $._retint = true;
15691579
};
@@ -1589,7 +1599,7 @@ Q5.renderers.c2d.image = ($, q) => {
15891599
w ??= $.width;
15901600
h ??= $.height;
15911601
let img = $.createImage(w, h, { pixelDensity: pd });
1592-
img.ctx.drawImage($.canvas, x, y, w * pd, h * pd, 0, 0, w, h);
1602+
img.ctx.drawImage(c, x, y, w * pd, h * pd, 0, 0, w, h);
15931603
img.width = w;
15941604
img.height = h;
15951605
return img;
@@ -1610,7 +1620,7 @@ Q5.renderers.c2d.image = ($, q) => {
16101620
let mod = $._pixelDensity || 1;
16111621
for (let i = 0; i < mod; i++) {
16121622
for (let j = 0; j < mod; j++) {
1613-
let idx = 4 * ((y * mod + i) * $.canvas.width + x * mod + j);
1623+
let idx = 4 * ((y * mod + i) * c.width + x * mod + j);
16141624
$.pixels[idx] = c.r;
16151625
$.pixels[idx + 1] = c.g;
16161626
$.pixels[idx + 2] = c.b;
@@ -1620,7 +1630,7 @@ Q5.renderers.c2d.image = ($, q) => {
16201630
};
16211631

16221632
$.loadPixels = () => {
1623-
imgData = $._getImageData(0, 0, $.canvas.width, $.canvas.height);
1633+
imgData = $._getImageData(0, 0, c.width, c.height);
16241634
q.pixels = imgData.data;
16251635
};
16261636
$.updatePixels = () => {
@@ -1635,20 +1645,6 @@ Q5.renderers.c2d.image = ($, q) => {
16351645

16361646
if ($._scope == 'image') return;
16371647

1638-
$._saveCanvas = async (data, ext) => {
1639-
data = data.canvas || data;
1640-
if (data instanceof OffscreenCanvas) {
1641-
const blob = await data.convertToBlob({ type: 'image/' + ext });
1642-
1643-
return await new Promise((resolve) => {
1644-
const reader = new FileReader();
1645-
reader.onloadend = () => resolve(reader.result);
1646-
reader.readAsDataURL(blob);
1647-
});
1648-
}
1649-
return data.toDataURL('image/' + ext);
1650-
};
1651-
16521648
$.tint = function (c) {
16531649
$._tint = (c._q5Color ? c : $.color(...arguments)).toString();
16541650
};
@@ -3370,9 +3366,9 @@ Q5.modules.input = ($, q) => {
33703366
l('touchend', (e) => $._ontouchend(e));
33713367
l('touchcancel', (e) => $._ontouchend(e));
33723368

3373-
c.addEventListener('wheel', (e) => $._onwheel(e));
3369+
if (c) c.addEventListener('wheel', (e) => $._onwheel(e));
33743370

3375-
if (!$._isGlobal) l = c.addEventListener.bind(c);
3371+
if (!$._isGlobal && c) l = c.addEventListener.bind(c);
33763372

33773373
l(pointer + 'down', (e) => $._onmousedown(e));
33783374
l('touchstart', (e) => $._ontouchstart(e));
@@ -4473,23 +4469,22 @@ Q5.modules.util = ($, q) => {
44734469
async function saveFile(data, name, ext) {
44744470
name = name || 'untitled';
44754471
ext = ext || 'png';
4472+
4473+
let blob;
44764474
if (imgRegex.test(ext)) {
4477-
if ($.canvas?.renderer == 'webgpu' && data.canvas?.renderer == 'c2d') {
4478-
data = await $._g._saveCanvas(data, ext);
4479-
} else {
4480-
data = await $._saveCanvas(data, ext);
4481-
}
4475+
let cnv = data.canvas || data;
4476+
blob = await cnv.convertToBlob({ type: 'image/' + ext });
44824477
} else {
44834478
let type = 'text/plain';
44844479
if (ext == 'json') {
44854480
if (typeof data != 'string') data = JSON.stringify(data);
44864481
type = 'text/json';
44874482
}
4488-
data = new Blob([data], { type });
4489-
data = URL.createObjectURL(data);
4483+
blob = new Blob([data], { type });
44904484
}
4485+
44914486
let a = document.createElement('a');
4492-
a.href = data;
4487+
a.href = URL.createObjectURL(blob);
44934488
a.download = name + '.' + ext;
44944489
a.click();
44954490
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
@@ -4501,7 +4496,6 @@ Q5.modules.util = ($, q) => {
45014496
b = a;
45024497
a = $;
45034498
}
4504-
if (a == $.canvas) a = $;
45054499
if (c) saveFile(a, b, c);
45064500
else if (b) {
45074501
let lastDot = b.lastIndexOf('.');
@@ -4870,7 +4864,7 @@ Q5.Vector.fromAngles = (th, ph, l) => new Q5.Vector().fromAngles(th, ph, l);
48704864
Q5.renderers.webgpu = {};
48714865

48724866
Q5.renderers.webgpu.canvas = ($, q) => {
4873-
let c = $.canvas;
4867+
const c = $.canvas;
48744868

48754869
if ($.colorMode) $.colorMode('rgb', 1);
48764870

@@ -5095,9 +5089,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
50955089
}
50965090

50975091
const addColor = (r, g, b, a) => {
5098-
let isColor = r._q5Color;
5099-
5100-
if (usingRGB === false || (g === undefined && !isColor && typeof r !== 'number')) {
5092+
if (usingRGB === false || (g === undefined && !r._q5Color && typeof r !== 'number')) {
51015093
if (usingRGB === false || typeof r == 'string' || !Array.isArray(r)) {
51025094
r = $.color(r, g, b, a);
51035095
} else {
@@ -5110,7 +5102,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
51105102
}
51115103
a ??= _colorFormat;
51125104

5113-
if (isColor === true) {
5105+
if (r._q5Color) {
51145106
let c = r;
51155107
if (usingRGB) ({ r, g, b, a } = c);
51165108
else {
@@ -6671,70 +6663,69 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
66716663

66726664
$._textureBindGroups = [];
66736665

6674-
$._saveCanvas = async (g, ext) => {
6675-
let makeFrame = g._drawStack?.length;
6676-
if (makeFrame) {
6677-
g._render();
6678-
g._finishRender();
6679-
}
6666+
if (c) {
6667+
// polyfill for canvas.convertToBlob
6668+
c.convertToBlob = async (opt) => {
6669+
let makeFrame = $._drawStack?.length;
6670+
if (makeFrame) {
6671+
$._render();
6672+
$._finishRender();
6673+
}
66806674

6681-
let texture = g._texture;
6675+
let texture = $._texture;
66826676

6683-
if (makeFrame) g._beginRender();
6677+
// this changes the value of $._texture
6678+
if (makeFrame) $._beginRender();
66846679

6685-
let w = texture.width,
6686-
h = texture.height,
6687-
bytesPerRow = Math.ceil((w * 4) / 256) * 256;
6680+
let w = texture.width,
6681+
h = texture.height,
6682+
bytesPerRow = Math.ceil((w * 4) / 256) * 256;
66886683

6689-
let buffer = Q5.device.createBuffer({
6690-
size: bytesPerRow * h,
6691-
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
6692-
});
6684+
let buffer = Q5.device.createBuffer({
6685+
size: bytesPerRow * h,
6686+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
6687+
});
66936688

6694-
let en = Q5.device.createCommandEncoder();
6689+
let en = Q5.device.createCommandEncoder();
66956690

6696-
en.copyTextureToBuffer({ texture }, { buffer, bytesPerRow, rowsPerImage: h }, { width: w, height: h });
6691+
en.copyTextureToBuffer({ texture }, { buffer, bytesPerRow, rowsPerImage: h }, { width: w, height: h });
66976692

6698-
Q5.device.queue.submit([en.finish()]);
6693+
Q5.device.queue.submit([en.finish()]);
66996694

6700-
await buffer.mapAsync(GPUMapMode.READ);
6695+
await buffer.mapAsync(GPUMapMode.READ);
67016696

6702-
let pad = new Uint8Array(buffer.getMappedRange());
6703-
let data = new Uint8Array(w * h * 4); // unpadded data
6697+
let pad = new Uint8Array(buffer.getMappedRange());
6698+
let data = new Uint8Array(w * h * 4); // unpadded data
67046699

6705-
// Remove padding from each row and swap BGR to RGB
6706-
for (let y = 0; y < h; y++) {
6707-
const p = y * bytesPerRow; // padded row offset
6708-
const u = y * w * 4; // unpadded row offset
6709-
for (let x = 0; x < w; x++) {
6710-
const pp = p + x * 4; // padded pixel offset
6711-
const up = u + x * 4; // unpadded pixel offset
6712-
data[up + 0] = pad[pp + 2]; // R <- B
6713-
data[up + 1] = pad[pp + 1]; // G <- G
6714-
data[up + 2] = pad[pp + 0]; // B <- R
6715-
data[up + 3] = pad[pp + 3]; // A <- A
6700+
// Remove padding from each row and swap BGR to RGB
6701+
for (let y = 0; y < h; y++) {
6702+
const p = y * bytesPerRow; // padded row offset
6703+
const u = y * w * 4; // unpadded row offset
6704+
for (let x = 0; x < w; x++) {
6705+
const pp = p + x * 4; // padded pixel offset
6706+
const up = u + x * 4; // unpadded pixel offset
6707+
data[up + 0] = pad[pp + 2]; // R <- B
6708+
data[up + 1] = pad[pp + 1]; // G <- G
6709+
data[up + 2] = pad[pp + 0]; // B <- R
6710+
data[up + 3] = pad[pp + 3]; // A <- A
6711+
}
67166712
}
6717-
}
67186713

6719-
buffer.unmap();
6714+
buffer.unmap();
67206715

6721-
let colorSpace = $.canvas.colorSpace;
6722-
data = new Uint8ClampedArray(data.buffer);
6723-
data = new ImageData(data, w, h, { colorSpace });
6724-
let cnv = new $._Canvas(w, h);
6725-
let ctx = cnv.getContext('2d', { colorSpace });
6726-
ctx.putImageData(data, 0, 0);
6716+
let colorSpace = $.canvas.colorSpace;
6717+
data = new Uint8ClampedArray(data.buffer);
6718+
data = new ImageData(data, w, h, { colorSpace });
67276719

6728-
$._buffers.push(buffer);
6720+
let cnv = new OffscreenCanvas(w, h);
6721+
let ctx = cnv.getContext('2d', { colorSpace });
6722+
ctx.putImageData(data, 0, 0);
67296723

6730-
// Convert to blob then data URL
6731-
let blob = await cnv.convertToBlob({ type: 'image/' + ext });
6732-
return await new Promise((resolve) => {
6733-
let r = new FileReader();
6734-
r.onloadend = () => resolve(r.result);
6735-
r.readAsDataURL(blob);
6736-
});
6737-
};
6724+
$._buffers.push(buffer);
6725+
6726+
return await cnv.convertToBlob(opt);
6727+
};
6728+
}
67386729

67396730
let makeSampler = (filter) => {
67406731
$._imageSampler = Q5.device.createSampler({

q5.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)