ondras / rot.js

ROguelike Toolkit in JavaScript. Cool dungeon-related stuff, interactive manual, documentation, tests!
https://ondras.github.io/rot.js/hp/
BSD 3-Clause "New" or "Revised" License
2.33k stars 254 forks source link

rendering is blurry #170

Closed mreinstein closed 4 years ago

mreinstein commented 4 years ago

Here is text rendered on my screen via rot.js [1] : rot

Here is text rendered on my screen via some library called malison [2] [3]:

malison

Both use a <canvas> element to display these grids. Is there a way to get rot.js to render text crisply, without this blurring/anti-aliasing? My window.devicePixelRatio is 2.

[1] https://ondras.github.io/rot.js/manual/#display

[2] https://github.com/munificent/malison

[3] http://munificent.github.io/hauberk/

ondras commented 4 years ago

Hi @mreinstein,

first I suggest deciding whether the issue is caused by the higher DPI (Retina) or not. To perform a Retina test, make your font size double (this will make the double as well) and scale it back to the desired size via CSS, e.g.

fontSize *= 2;
canvas.style.width = (canvas.width/2)+"px";
canvas.style.height = (canvas.height/2)+"px";

...and let us know how does this look like.

However, I believe that pixel density is not the real issue here -- font rendering is. ROT uses vector fonts that are designed to be antialiased (not crispy), Hauberk apparently uses raster fonts. I would suggest looking for suitable TTF/OTF/WOFF fonts that are specifically designed to emulate crispy look (I have no knowledge of these).

Note, however, that roguelike games can look totally okay even with vector/antialiased fonts -- I created several and I was always pretty satisfied with their looks:

TimTCrouch commented 4 years ago

Is it possibly to use raster font images natively with rot.js? I know libtcod uses them as its main ASCII-loading tool. I am trying to get super-crispy looking Code Page 437 ASCII characters and that is really turning into pain in the browser lol. I have one working, but it is extremely blurry like the OP mentioned no matter which font type I seem to use. Thank you!

blinkdog commented 4 years ago

I wanted exactly this look in my 7DRL entry for 2015: https://github.com/blinkdog/7drl-2015

So I borrowed a PNG of the CP437 characters from somebody else's 7DRL 2014 entry: Necromancer Simulator 2014 https://github.com/blinkdog/7drl-2015/blob/master/img/terminal8x8.png

And then used a little bit of code to make a tile map that could behave a lot like text: https://github.com/blinkdog/7drl-2015/blob/master/src/main/coffee/tiles.coffee

If cheating with a tilemap image isn't desirable, then there may be some help in the techniques here, but I'm not really sure if it applies to canvas text commands or not: https://stackoverflow.com/a/49357655

mreinstein commented 4 years ago

@TimTCrouch @blinkdog I've been experimenting with my own renderer, which is heavily inspired from malison/hauberk as a starting point. It uses a raster based sprite font.

https://github.com/mreinstein/ascii-diagrams

Right now the renderer itself is coupled to this ascii diagraming prototype tool (which was the inspiration for starting this in the first place.) It could/should be pulled out into a standalone renderer module.

The code for this is pretty minimal and open source (the raster based font renderer is only about 200 lines so far.) It seems pretty efficient. Would be happy to accept PRs, ideas, etc. If you find this useful.

ondras commented 4 years ago

Hi folks,

rot.js's text rendering power is ultimately based on (and limited by) the underlying HTML <canvas> feature. If you manage to get the <canvas> working the way you like, it is very probable that we can fix rot.js to do the same. Keep in mind, however, that the canvas is primarily designed for vector fonts, so we might be out of luck here - and the only way to get a pixel-perfect rendering is to use image tiles. (Nothing wrong with that approach!)

If you find a way to render TTF/OTF/WOFF in a crispy fashion, let me know what CSS/JS properties need to be applied and I will happily mirror those to rot.js codebase.

mreinstein commented 4 years ago

I deployed a copy of this to the web, you can play with it here if you like. you can draw boxes, connect lines between boxes, label lines and boxes, etc. This looks non-blurry on both retina and non-retina screens:

https://ascii-diagrams-xybsmgbvoj.now.sh/

@ondras I can't speak to rendering ttf/otf/woff, but this demo is the visual behavior I'm after. It uses font sprite images. The code for this is pretty minimal, if there's value in including any of this logic in rot.js I'm all for it! 👍

ondras commented 4 years ago

It uses font sprite images.

Well, image tiles are always an option. They are already supported in rot.js, so anyone is free to use them.

mreinstein commented 4 years ago

is there a working example of using an image tile? I could evaluate if this is producing similar visual results

ondras commented 4 years ago

is there a working example of using an image tile? I could evaluate if this is producing similar visual results

The official interactive manual shows regular graphical tiles; they are always drawn 1:1, WYSIWYG. If you can give me a proper tileset image, I can throw together a trivial demo for you.

mreinstein commented 4 years ago

@ondras here is a tileset image: https://github.com/mreinstein/ascii-diagrams/blob/master/font/font_8_10.png

here is the codepage mapping: https://github.com/mreinstein/ascii-diagrams/blob/master/raster-font/unicode_map.js

here is the code that performs the mapping between character glyph and position within the tileset image (sx, sy): https://github.com/mreinstein/ascii-diagrams/blob/master/raster-font/index.js#L161-L163

ondras commented 4 years ago

Okay @mreinstein, a quick demo using your tileset: https://jsfiddle.net/fzeuy3sc/

mreinstein commented 4 years ago

looks great!

mreinstein commented 4 years ago

@ondras a follow up question: besides browser support, is there any benefit to using the rot canvas renderer over webgl? I would think webgl would be more performant with the same amount of setup code.

ondras commented 4 years ago

I would think webgl would be more performant with the same amount of setup code.

Definitely. That is the reason for the WebGL renderer :) Even though Roguelike games are often not that demanding with respect to rendering.

Its code is much newer and less mature, though. I am pretty certain it does not 100% match the feature set of a regular canvas renderer, with respect to tile stacks, colorization and/or opacity.

mreinstein commented 4 years ago

I am pretty certain it does not 100% match the feature set of a regular canvas renderer, with respect to tile stacks, colorization and/or opacity.

thanks for clarifying! do you have a sense of what the actual feature gap is?

ondras commented 4 years ago

thanks for clarifying! do you have a sense of what the actual feature gap is?

I skimmed the code and there does not seem to be any incompleteness visible or mentioned. I suggest you try it right away, because you can always switch to the regular "tile" anytime you run into issues.

Also it might be worth mentioning that the WebGL renderer requires your textures to be of a same-origin with respect to the calling page.

mreinstein commented 4 years ago

I've updated my diagramming tool to use rot.js with the webgl tileimage renderer. Seems to work pretty well!

https://ascii-diagrams-nlrfivgdbe.now.sh

I found 2 issues with the test fiddle that you provided:

I think writing up this demo in the main documentation would be really helpful; I suspect a lot of people that are using rot.js are after this kind of rendering. If I can help with this in any way, happy to do so.

Thanks again for your help with this!

ondras commented 4 years ago

I found 2 issues with the test fiddle that you provided:

Right, the fiddle was a proof-of-concept, not a full-blown example covering all features and edge cases.

* the `canvas` element needs a css property set, to disable interpolation on images:

Sounds correct, even though only for cases where the canvas has to be scaled. I suppose this covers the devicePixelRatio > 1 cases, e.g. Retina devices.

On the other hand, enabling/forcing nearest neighbor interpolation (as oposed to the default linear interp) might be a matter of personal taste. I would like to leave this option up to the developer.

* special characters don't work in the text field; they're not being mapped correctly

Right, my mapping was a fast hack. Ideally, the author of the raster font image would provide a mapping as well.

I think writing up this demo in the main documentation would be really helpful

I agree that some kind of a code sample specifically for this case might be useful. Not sure if a good fit for the manual, though -- it does not really showcase a particular rot.js feature. Perhaps a blogpost or a fiddle or a roguebasin wiki page...

mreinstein commented 4 years ago

Right, the fiddle was a proof-of-concept, not a full-blown example covering all features and edge cases.

Let me clarify; I'm not picking on you or your solution. It's great! My point is that these 2 changes are needed to render crisp, raster based fonts on rot.js :)

I agree that some kind of a code sample specifically for this case might be useful. Perhaps a blogpost or a fiddle or a roguebasin wiki page.

I think if you were to provide a visual comparison between these 2 rendering strategies (aliased fonts vs crisp raster tileImage fonts) and ask your users "which looks better" you might find that more people than you expect would want the crisp behavior. and it's not that hard to put a note in one of the main documentation pages. We're talking about 3-4 sentences to enable the crisp rendering.

ondras commented 4 years ago

We're talking about 3-4 sentences to enable the crisp rendering.

Right. Can you please confirm that this only applies to retina-like setups with devicePixelRatio > 1? I do not have currently any of these devices available and my current setup does not show any difference with/without the image-rendering property, as the raster tiles are always 1:1 (always crisp).

mreinstein commented 4 years ago

Can you please confirm that this only applies to retina-like setups with devicePixelRatio > 1?

confirmed

ondras commented 4 years ago

I added a paragraph mentioning this into the Manual: https://ondras.github.io/rot.js/manual/#tiles

mreinstein commented 4 years ago

looks good. I'll close this issue, since there is a way to achieve the desired effect and it's somewhat documented in the manual. Thanks for taking the time to work with me on this!