q5js / q5.js

A sequel to p5.js that's smaller, faster, and optimized for interactive art!
https://q5js.org
GNU Lesser General Public License v3.0
104 stars 8 forks source link

pixel fonts not rendered properly #18

Open quinton-ashley opened 9 months ago

quinton-ashley commented 9 months ago

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.

quinton-ashley commented 9 months ago

Found this stack overflow post: https://gamedev.stackexchange.com/questions/183179/completely-pixelated-font-in-javascript-canvas/183207#183207?newreg=cab3501f3c1144d9b582acb17c458634

Setting this CSS property -webkit-font-smoothing to "none" solves the problem in q5! But only on macOS in Safari (so sad) https://developer.mozilla.org/en-US/docs/Web/CSS/font-smooth