q5js / q5.js Goto Github PK
View Code? Open in Web Editor NEWA sequel to p5.js that's smaller, faster, and optimized for interactive art!
Home Page: https://q5js.org
License: GNU Lesser General Public License v3.0
A sequel to p5.js that's smaller, faster, and optimized for interactive art!
Home Page: https://q5js.org
License: GNU Lesser General Public License v3.0
Yeah... this is a bit of a rabbit hole! 🐰
q5.js currently can't render pixel fonts (very small sized fonts made of just a few pixels) at a pixel perfect level (sharp and blocky) because q5 uses native canvas rendering. Every browser's canvas.fillText
implementation seems to do anti-aliasing and the ctx.textRendering
property doesn't seem to change that, at least not in Chromium based browsers. Also Safari does not have this feature. https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textRendering
p5.js can sometimes render pixel fonts correctly, if the font is the right size and at a correct position (not always integer coordinates!). p5 does not use native canvas rendering for true type and open type fonts, it uses OpenType rendering. My guess is that this decision was made because around the time p5.js was created, browser's native canvas implementations for font rendering were not so good. Nowadays though, in any other case besides pixel fonts, q5's native canvas rendering with canvas.fillText
definitely looks better and probably runs faster than p5's OpenType based method. Which I noted here:
https://discourse.processing.org/t/why-is-text-not-centered-properly-with-textalign-center-center/43322/8
However, in the case of pixel fonts, using OpenType to render text as primitive canvas shapes actually works great because pixel fonts are just made out of a few pixels (squares). So OpenType rendering could be used for pixel fonts and native for everything else? Well things are not so simple.
Take a look at this demo with a font called Bit Potion, the creator says it's best to use it in 16px intervals. I made this demo which uses p5.js and shows text displayed with the pixel font at 16px and 32px.
https://editor.p5js.org/quinton-ashley/sketches/fhr7cLd3R
Clicking the mouse in the demo will switch the text from being rendered at integer coordinates to half pixel coordinates (0.5). For some reason, at any even multiple of 16px, such as 32px, text only looks good at integer coordinates. At any odd multiple of 16 such as 48px or 16px, text only looks good at half pixel coordinates and only without letters that hang under the baseline like "p".
Why is that? Well I found this old stack overflow post where a user says "the canvas coordinate system defines the start of a pixel from the pixel's center" https://stackoverflow.com/questions/22976031/html5-canvas-without-anti-aliasing?rq=3
This sucks for users. Displaying pixel fonts shouldn't be a difficult guessing game. Users should just be able to put in any random coordinates and as long as the font is the right size, q5 should be able to draw it with pixel precision (sharpness).
But how could such a generalized solution be achieved? I don't know yet.
One idea is to add a feature to p5 and q5 where users could define the optimal size of a font. Then when the text
function was used with the font, there could be some code that could adjust the position given by the user to make the font display well.
Another totally differnt idea I had was that p5play users could import a pixel font as a spritesheet animation, provided the font offer an image containing all the font's symbols. Then text could be displayed using the Tiles constructor. However, this has many obvious limitations. It'd only work for monospace fonts, not for the "Bit Potion" font. Also this method is untenable for non-English text with accented letters. Also it'd make using the Tiles constructor for anything else pretty much impossible. Yet, this approach could be decent for English text as long as it was implemented as a separate system to not conflict with p5play's Tiles
system.
But I also noticed the Bit Potion font is offered as an old bitmap .fnt windows font file. I wonder if there's any canvas library for rendering them? This could be a good solution. I will research this more.
An async function simply named load
could be used to load any file or multiple files asynchronously and return a promise.
This will be implemented in q5-util.js The q5 setup
function is already async.
This idea was suggested by @mattdesl and iterated on by @mvicky2592 in this p5.js issue discussion. I also think keeping the preload
system for backwards compatibility and ease of use for beginners is the right idea though.
processing/p5.js#6767
Hello. I try to test q5.js in replacement of p5.js for a website (only some small colors image conversion).
On p5js, I manage to do it with a HSL colorMode.
On q5js, there are only RGB mode and HSB mode. It is true?
Needs to run loadPixels
if pixels were not loaded.
When the first frame in a sketch is drawn to the canvas, it's as if the origin of the canvas has been translated to the middle. I tried using canvas.translate(0, 0)
in createCanvas
but it didn't help. This can be tested using noLoop()
in setup.
Sometimes when using q5 instead of p5 the rotateTo
p5play function doesn't work and the sprite just keeps rotating.
Code:
var player;
function preload() {
new Canvas("2:5");
player = new Sprite(100,100);
//player.spriteSheet = loadImage("questKid.png");
player.spriteSheet = "https://p5play.org/learn/assets/questKid.png";
player.w = 32;
player.h = 32;
}
function setup() {
background("blue");
player.addAnis(
{
right:{row:0, frames: 8},
left:{row:1, frames: 6},
down:{row:2, frames: 5},
up:{row:3, frames: 7},
kuv:{row:4, frames: 8},
}
)
}
function draw() {
clear();
background("blue");
if (kb.presses("r")) player.changeAni("right");
if (kb.presses("j")) player.changeAni("left");
if (kb.presses("l")) player.changeAni("down");
if (kb.presses("t")) player.changeAni("up");
if (kb.presses("s")) player.changeAni("kuv");
}
The devs of the WebGL renderer for p5.js, namely Dave Pagurek, have done a great job with it. I have no intention of reinventing the wheel and recreating webgl mode in q5.
WebGPU is the latest and greatest graphics API for the web, built on the low-level GPU APIs Metal and Vulkan.
I've seen some impressive demos of it, like this one that can draw millions of boids!
https://jtsorlinis.github.io/BoidsWebGPU/
I expect Apple to release WebGPU support for iOS in September. Currently this feature is only available for beta testing in Safari Technology Preview. I would love for q5 to have basic WebGPU rendering support before then and ideally by the end of July.
Basic WebGPU support in q5 will consist of drawing shapes and images in two dimensions. Although this can already be done in Babylon.js, it's not as lightweight or as easy to use as I hope q5's implementation will be.
Once all that is complete, more complex rendering functionality like support for text, can be implemented.
Basic 3D drawing would be nice too but personally I'm not going to attempt competing with Babylon.js on providing more advanced 3D capabilities.
resizeCanvas, scale() and .style .parent (probably more HTML DOM p5.js features) could be ported
Co-author @lingdong~ wrote:
curveTightness()
sets the 'alpha' parameter of Catmull-Rom curve, and is NOT identical to p5.js counterpart. As this might change in the future, please call curveAlpha()
directly.
@SableRaf I like this idea!
I did something similar with p5play with the "made with p5play" screen but it fills the window, assuming nearly all usage of p5play is in global mode.
For q5, I'm thinking of doing a loading animation of the q5 logo spinning in css right above the center of the canvas.
In response to the opt-in idea, I think that wouldn't work because developers loading assets locally may never experience long loading times, so they wouldn't see the message suggesting to enable it. Also my assumption is a loading animation would always be preferable to "loading" text.
I want to refactor Q5.Color
and the color
function to add support for HDR colors, make oklch a new color mode, and remove HSB/HSV support.
p5 devs are planning to improve how color is handled in p5.js v2. I'd like them to take a look at my goals for the color improvements I will make in q5 v1.9.3. Hopefully some of these changes can be made in p5 as well!
processing/p5.js#6680
@limzykenneth @lindapaiste @Vishal2002 @JustZambetti @davepagurek @hellonearthis @meodai
q5 is 11x faster than p5 at creating 10,000 random colors: 3ms vs 33ms.
function randomColors() {
for (let i = 0; i < 100000; i++) {
let c = color(random(255), random(255), random(255));
}
}
const start = performance.now();
randomColors();
const end = performance.now();
console.log(`Execution time: ${end - start} ms`);
What kind of magic is q5 using to achieve such results? q5 simply doesn't do any unnecessary color format conversions. Since the default color mode is RGB, q5 simply stores the user's rgb values as properties in a object.
But in p5, the user's legacy rgb 0-255 range input is converted to modern 0-1 range rgba values, both of which are stored in arrays. maxes
arrays for each color format are also copied into every color object, which seems unnecessary. hsba
and hsla
are both defined on the object as null, even if the user doesn't intended to convert the color.
If you view a p5.Color
in the console, you'll probably be confused by the output! Especially if you're a beginner programmer.
{
hsba: null,
hsla: null,
levels: [255, 10, 82, 255],
maxes: {
hsb: [360, 100, 100, 1],
hsl: [360, 100, 100, 1],
rgb: [255, 255, 255, 255]
},
mode: 'rgb',
_array: [1, 0.0392156862745098, 0.3215686274509804, 1]
}
To try to understand why p5.Color
was implemented this way, we have to study its grandaddy: Processing.
In Java Processing, the color
method generated a primitive int
, not an Object, and in Java ints can't have properties or methods. That's why the red
, green
, blue
, alpha
functions were necessary. Those individual color components could only be retrieved from a color int through bitwise operations, not via properties.
https://forum.processing.org/two/discussion/15923/how-to-change-alpha-without-changing-the-whole-color.html
But in p5 these functions should've only been implemented for backwards compatibility. Thankfully color.setRed()
and similar functions were introduced, but why not a color.getRed()
or better yet expose the color components as public properties?
To improve q5 I'm taking inspiration from the popular JS color library culori
represents colors as objects with letter properties, using a single color mode per object, making them easy to view and edit.
// culori.js rgba color data representation
{
r: 255,
g: 10,
b: 82,
a: 1,
mode: 'rgba'
}
But just making red, green, and blue components easier to edit doesn't fix the more fundamental problems with the RGB format. As noted in the article "OKLCH in CSS: why we moved from RGB and HSL":
"RGB, hex and color(display-p3) aren’t convenient for color modifications because, for the vast majority of humans, it’s difficult to intuitively set colors by changing the amount of red, blue and green. Further, RGB and hex also can’t encode P3 colors."
I recommend reading the full article, it's great! I'm going to make separate classes for rgb and oklch that extend Q5.Color
, so that instanceof p5.Color
checks can still work.
Currently Q5.Color
instances internally output color to ctx.fillStyle
or ctx.strokeStyle
via their toString
method. For Q5.ColorRGBA
and Q5.ColorOKLCH
their toString()
functions won't require any calculations.
While I do think oklch is the future, I don't really expect any Intro to Web Design class/section to teach oklch anytime soon. But I want to get people using HDR colors today!
I think the best way to do this is to create a backwards compatibility class, Q5.ColorRGBA_P3
. Users can still edit colors using the default RGB colorMode
but the resulting colors will be internally mapped directly to the HDR "display-p3" color space in toString
using this format: color(display-p3 r g b / a)
.
https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color
To prevent toString
from needing to do the format conversion from the legacy 0-255 range to the modern 0-1 range whenever toString
is used, I'll create getter/setter properties for the purpose of watching for changes. Only when the color is changed will toString
do new calculations.
new Color()
p5.Color
is not a class that belongs to a p5 instance but rather p5 itself. q5 mimics this implementation.
Yet,_colorMode
is an instance variable, so it can't be accessed directly by the p5.Color
constructor. p5 gets around it by passing p5 instances to the p5.Color
constructor. Additionally, in q5 I'll need to access the canvas
to get the canvas' color space. In q5, all of the color parsing code is done in the q5 instance level color
function. I will maintain this structure.
Depending on the instance's current color mode and canvas color space, the q5 instance variable $.Color
will be set to Q5.ColorRGBA
, Q5.ColorRGBA_P3
, or Q5.ColorOKLCH
. These constructors will have no parsing overhead.
Support for the HSV color format will be removed in q5 v1.9.3 because it was rarely used, the amount of code required to convert between RGB and HSV was quite large, and color experts considered HSV to be flawed and outdated way back in 1997!
@Tezumie will expand q5's drawing capabilities with a new addon module: q5-2d-drawing-plus.js
I've been testing the following sketch. I've debugged al the way until normalize()
(line 80), which results in a NaN
. If I comment out the normalize it starts working for a couple of frames but runs in to more issues.
Stopped debugging here but this might be a good test case to find some more discrepancies
I've broken q5 up into the smallest useful modules which are in the src
folder. This is great for organizational purposes and also for extremely lightweight use in which users only need to load the modules necessary for a particular sketch to run.
Modules will load via side effects to keep things simple for students.
My goal is for q5.js to have basic sound support so users don't need to load p5.sound if they don't need to. See loudSound
for the current implementation.
I think setPan
would be a great addition but I couldn't yet figure out how to do it. If anyone wants to implement this let me know!
I was wrong about getters and setters being a good way to sync properties between q5 instances and the global scope.
if (scope == 'global') {
let props = Object.getOwnPropertyNames($);
let t = !Q5._nodejs ? window : global;
for (let p of props) {
if (typeof $[p] == 'function') t[p] = $[p];
else {
Object.defineProperty(t, p, {
get: () => $[p],
set: (v) => ($[p] = v)
});
}
}
}
That's because calling a getter function is much less efficient than simply accessing the value of a prop.
Since q5 properties can commonly be accessed in for loops in the draw loop, significant slowdowns could occur.
p5 solve the problem this way.
this._setProperty = function (prop, value) {
_this[prop] = value;
if (_this._isGlobal) {
window[prop] = value;
}
};
Any time (over 100 times) that one of the synced props is set, the function must be used.
this._setProperty('mouseIsPressed', true);
This is a bit cumbersome and doing it this way may have increased q5's overall size by a kb.
$.mouseIsPressed = true;
// would need to change to
$._setProp('mouseIsPressed', true);
Instead I used a proxy.
let p = new Proxy($, {
set: (t, p, v) => {
$[p] = v;
if ($._isGlobal) globalScope[p] = v;
return true;
}
});
Proxies are nice because they can be used just like objects.
p.mouseIsPressed = true;
Implemented in v2.0-beta12
These changes actually resulted in a slight decrease of q5's overall size!
From the flocking p5 example
this.velocity = p.createVector(p.random(-1, 1), p.random(-1, 1));
let theta = this.velocity.heading() + p.radians(90);
Upon calling this code we get that $ is not defined
you can fix this using
heading() {
let $ = this;
return Math.atan2($.y, $.x);
}
but this is not a pretty solution * And does not really work*
The flexibleCanvas
function will enable dimension agnostic scaling. Could also be accessed via canvas.flexible
prop.
Let users change the opacity of multiple shapes and images without having to change their individual colors or use image tint or filters.
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalAlpha
$.opacity = (a) => (ctx.globalAlpha = a);
Fork the Friendly Error System from p5.js and offer it as an optional q5 module.
p5's FES explain errors in a friendly way. Though beginners can consult AI to get more detailed explanations of errors, sometimes a hint from FES would be enough to guide them to think of a solution.
Somehow I broke tinting in the latest version of q5. Will fix ASAP!
I will implement this tomorrow. Will be case insensitive.
There's an opportunity with q5's upcoming WebGPU renderer to implement friendly p5 style wrappers for batch rendering.
In p5.js every function that draws stuff (like rect
and image
) is run sequentially and layers stuff on top of previously drawn items. This doesn't take advantage of the GPUs strength at drawing in parallel which is faster.
Batch rendering could draw many shapes with the same fill and stroke colors or shader in the same render pass.
Proposed use example:
fill("red");
batchStart();
for (let i = 0; i < 100; i++) {
rect(i*10, random(height), 10, 10);
}
batchEnd();
In between the start and end of a batch, drawing functions like rect
would add vertices to a vertex buffer behind the scenes.
I hope this will give users an easy and performant way to render millions of 2D shapes.
Looks like q5's optimizations don't account for changes to a vector's components.
var vec = new p5.Vector();
vec.x = 2;
vec.normalize();
console.log(vec.x); // gives 1
vec.y = 1;
vec.normalize();
console.log(vec.x) // gives 0.707 in p5, but still 1 in q5
Users should be able to set canvas context attributes.
p5.js currently doesn't expose the ability to change 2d canvas context attributes. They can only be changed when the canvas is created and getContext
is used for the first time.
I'm especially interested in being able to set new defaults for q5.js:
Q5.canvasOptions = {
alpha: false,
desynchronized: true,
colorSpace: 'display-p3'
};
alpha
: most sketches do not take advantage of the canvas element's transparency and disabling it would improve performance
desynchronized
: most sketches would benefit from desynchronization with the DOM, unless the sketch used DOM elements above the canvas.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
https://developer.chrome.com/blog/desynchronized
colorSpace
: If the display supports "display-p3" I want q5 to use it. #21
Related issue:
processing/p5.js#5902
This could be done via a createCanvas
input parameter. For example:
createCanvas(800, 600, '2d', {
alpha: false
});
Users could also change the defaults held in Q5.canvasOptions
which would have an effect on all instances of q5. I will need to do this on p5play.org because I actually do take advantage of the canvas's alpha via the clear
function which makes light and dark mode possible on the site without needing to change the example code.
Looking over the code at school at the moment- tinted images seem to create their offscreen canvas, apply filters, then draw- every frame. If the tint stays the same, it makes sense to cache the canvas with the image, for quick drawing for subsequent frames. This would also help with changing tints, as they could simply change the pre-existing canvas without the need for overhead creating more canvases.
p5 API for dom functions should be implemented as a supplementary module, q5-dom, that won't be included in the default bundle.
I think the p5 dom api could be improved by making it easier for beginners to use CSS flexbox to create better looking UIs with html+css that could overlay the canvas.
If anyone would like to implement this lmk!
I found your project while I was considering whether I should access the canvas directly because p5.js didn't support color space including oklch. To see if it supports wide gamut, I tried to compare the color of p3 color gamut with its fallback through the code below.
function setup() {
createCanvas(400, 400);
background(220);
colorMode('rgb');
let colourRgb = '#ff0c52';
colorMode('oklch');
let colourOklch = color(0.637, 0.28, 16.57, 1);
fill(colourRgb);
rect(0, 0, width / 2, height);
fill(colourOklch);
rect(width / 2, 0, width / 2, height);
}
However, the error phrase below appeared on the console.
TypeError: Cannot read properties of undefined (reading '_q5Color')
at $.fill (q5.js:642:11)
at setup (sketch.js:10:3)
at Q5.$.<computed> [as setup] (q5.js:209:17)
at _start (q5.js:226:11)
at new Q5 (q5.js:236:3)
at HTMLDocument.<anonymous> (q5.js:270:23)
I think the fill function cannot accepts color data created through colorMode('oklch') as a variable, do I have to use a different method when using colorMode('oklch') or is it a feature that hasn't been implemented yet, or is it a bug?
By the way, #ff0c52 appeared pinkish red but little bit saturated as intended, and oklch color was rendered...... gray.
@Tezumie Let me know what you think of the displayMode
function. I put it in a separate module since the code is 2kb and it can work with the webgpu canvas too in the future.
displayMode(mode, renderQuality, displayScale)
It supports three display modes: "normal": no styling to canvas or its parent element
"centered": canvas will be centered horizontally and vertically within its parent and if it's display size is bigger than its parent it will not clip
"maxed": canvas will fill the parent element, same as fullscreen for a global mode canvas inside a main
element
"fullscreen": canvas will fill the screen with letterboxing to persevere its aspect ratio, like css object-fit contain
two render qualities:
"default": pixelDensity set to displayDensity
"pixelated": pixelDensity set to 1 and various css styles are applied to the canvas to make it render without image smoothing
displayScale can be given as a string "x2" or a number. This can be used to make small canvases appear larger.
I love your library! I tested it with some of my sketches but notice that some functions are still missing, like createP. Or am I missing something?
Here's the example using p5play that isn't working.
Strangely this issue only occurs in Safari and not in the iOS app template that I'm working on.
I can't figure out what's wrong compared to p5's implementation.
let boxes = {};
function setup() {
new Canvas();
}
function draw() {
background(16);
renderStats();
textSize(24);
textAlign(CENTER);
fill(200);
text('Tap to create a new sprite', canvas.hw, canvas.hh);
let info = canvas.w + 'x' + canvas.h + ' X' + pixelDensity() + ' ' + deviceOrientation;
text(info, canvas.hw, canvas.hh + 30);
for (let touch of touches) {
if (touch.presses()) {
let box = new Sprite(touch.x, touch.y, 30, 30);
boxes[touch.id] = box;
} else {
boxes[touch.id].moveTowards(touch);
}
}
}
function windowResized() {
canvas.resize();
}
I want users to be able to use more vibrant HDR colors in their q5.js projects.
Check out my HDR web design guide for more info:
https://quinton-ashley.github.io/hdr-web-design-guide/
HTML5 canvas contexts can use the "display-p3" HDR color space but p5.js doesn't support it.
https://developer.mozilla.org/en-US/docs/Web/API/ImageData/colorSpace
https://webkit.org/blog/12058/wide-gamut-2d-graphics-using-html-canvas/
I want to make "display-p3" the default color space in q5.js, as long as the user's device is capable. Most devices made after 2017 are!
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Testing_media_queries
A canvas' color space can be set via canvas context attributes #23
But properly supporting HDR isn't as simple as just changing the color space. I also need to give users a way to use HDR colors. In CSS the format for hex rgba, legacy rgba, srgb, and display-p3 are all different.
I think q5's color
system needs to be rewritten #24
function setup() {
new Canvas(400, 400);
displayMode("maxed", "pixelated");
background(220);
}
function draw() {
background(220);
translate(canvas.hw, canvas.hh);
textAlign(CENTER, CENTER);
textSize(100);
text('hello\nbye', 0, 0);
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.