RobinSchmidt / RS-MET

Codebase for RS-MET products (Robin Schmidt's Music Engineering Tools)
Other
56 stars 6 forks source link

You could get pixel perfect font that doesn't look pixelly #223

Open elanhickler opened 6 years ago

elanhickler commented 6 years ago

example: This is your font spelling out LFO 2 and my vector recreation:

image

If you draw fonts using a series of vectors instead of bitmaps you can have nice antialiasing while maintaining pixel perfection. It's just a matter of creating the vectors for each character.

Another advantage is being able to resize. To maintain pixel perfection you of course can only use multiples of 1x, 2x, 3x zoom, etc.

image

If you wanted more zoom ratios like 1.5, you'd have to create a new version of the font. Here's one experiment I did for 1.0, 1.5, and 2.0 rations (2.0 is the same as 1.0)

image

Of course after a certain size of font, it really doesn't matter. So creating 2 to 4 small sizes for a font would be plenty.

elanhickler commented 6 years ago

If you work on implementing a vector-based version of your font drawer, I'd agree to recreate all your characters in vector. You interested? This would be more for fun. I'm not sure if I will make use of it... maybe... maybemaybe.

RobinSchmidt commented 6 years ago

oooh - nice! how do you do that? i mean, how do you achieve the font to look pixel-perfect? by drawing the vectors on a pixel grid? what tool do you use for that? in the long run, scalable fonts would certainly be desirable. juce actually does all that already, but the quality is very blurry - especially for bright-on-dark text (which was the reason for me to go through this tedious work of creating my own (pixel) font)

elanhickler commented 6 years ago

Yes, just like you tediously went through creating your own pixel font, the process is very similar to creating the vector font. You put points on the pixel grid. Affinity Designer is perfect for this because it has a curve tool to get perfect curves.

image

RobinSchmidt commented 6 years ago

i just added my prototype pixel fonts to the repo, for reference. the main widget font is RoundedA10D0Bold.fon. i created these fonts back then with a tool called "Fony" and then hardcoded them (and yeah - the big 16 pixel font is still incomplete - some number digits are missing - or actually not missing but rather just copied-and-pasted-and-not-yet-edited from a smaller version). i think, maybe the best strategy would be to pre-render a vector-based font into pixel-fonts for all required sizes (so we don't have to fire up a full blown 2D renderer for each glyph, each time it is needed). i will check out this Affinity Designer myself, too...

elanhickler commented 6 years ago

learn about the corner tool.

RobinSchmidt commented 6 years ago

oh - it's commercial software and there doesn't seem to be a demo available...hmm...i wonder if inkscape can do this, too...to what format do save the output then? svg?

elanhickler commented 6 years ago

there is a demo. https://affinity.store/en-us/checkout/?_ga=2.67930321.1706479119.1536965742-1436673135.1536715951&basket=1008b3e31a4f534d848b9c929cadef77

RobinSchmidt commented 6 years ago

ah - ok - thanks. ...this little text-link is quite hard to find at the bottom of a mile-high page of colorful graphics. haha.

btw. you may have noticed that in my fonts, no character goes below the baseline, such as p,q,g,etc. i did this on purpose to save vertical space which would otherwise go unused most of the time. looks a bit unusual, but that was a sort of pragmatic consideration - how to squeeze the most readable text into the smallest amount of (vertical) space. this is also the reason why i use horizontal sliders. they are most economic spatially, because i can put the text on the slider (rather than below, above or whatever)

elanhickler commented 6 years ago

To maintain perfect accuracy, I am trying to do it by eye and by 1D curve nodes rather than affinity's fancy coner tool with 2D shapes. This allows me to set a stroke size of 3 pixels on the 1D curve's stroke to get perfect 3 pixel width for the whole shape. So I think really any vector program would work for this.

image (I have it offset .5 pixels to make it easier to trace by eye)

Try Gravit Designer, it's free. I might switch to using gravit designer for this. I can also try Inkscape, whichever is the best for making 1D curves, that's the one to use. Oh... but the 2D method is good for simple shapes like O 0 8. Hmm, yeah, I'll just have to use the method that works the best for the given glyph.

elanhickler commented 6 years ago

top: original others: 3 / 2 / 1 pixel stroke image edit: if using 1 pixel stroke or smallest possible font size, better to use original bitmap font edit: the 2 pixel size '2' is fuzzy because I didn't realize I needed to adjust the curve for 2 pixel size. Hmm, looks like arbitrary stroke size won't work so well unless you have a system of quantizing the nodes.

x2 zoom image

I only got interested in this so that you could have pixel perfect font AND when you resize the gui to be bigger, the font doesn't look ugly and pixely. Pixel-perfect vector font only has an advantage over bitmap font when it's not the smallest possible font and when not using large font (then any font looks good). It's only the middle ground that this is useful.

elanhickler commented 6 years ago

OH! If you could disable antialiasing conditionally (for smallest/original sizes) then vector could work in all cases... I think!

elanhickler commented 6 years ago

How do I use these fonts? What settings do I use? Is there a specific font size I needt o use for a given file? There should only be 2 font for the 2 sizes.

RobinSchmidt commented 6 years ago

i have a baseclass BitmapFont in jura and the actual fonts are all subclasses of that. when i need to draw text, i call drawBitmapFontText (a global function defined in GraphicsTools.cpp) which has as one of the parameters a pointer to the BitmapFont baseclass. when i call this for a widget, i pass a pointer to an object that is a static member of the respective font class. ...so basically, like a global object but to be accessed via the class - for example:

class BitmapFontRoundedA7D0 : public BitmapFont
{
  static const BitmapFontRoundedA7D0 instance;
  //... more stuff
};

and can be called like: drawBitmapFontText(g, x, y, text, &BitmapFontRoundedA7D0::instance, color); but for convenience, the RWigdet class stores the pointer &BitmapFontRoundedA7D0::instance in the static font member

elanhickler commented 6 years ago

Vector / Bitmap / Original

1x zoom image (bitmap wins)

2x zoom image (vector wins)

I think if we could just disable antialiasing for this example then the vector would be rendered like the bitmap and we'd have best of all worlds.

RobinSchmidt commented 6 years ago

how did you render these? and what is "original"?

elanhickler commented 6 years ago

I'm doing all this by hand in a vector program, nothing fancy. Original is how that s*** would look rendered in the real world. It's night and day difference between pixel perfect and normal. The 2x zoom... ignore that for the original.

elanhickler commented 6 years ago

Going to need pixel perfect font for this. Do you have a general purpose class for placing letters? I could save each letter as a ~png~ vector file. Would also be nice to have a setting for pixel space between letters or fixed distance for monospace. Hmm, I could make my own class for this that takes vectors, renders images per font per desired size (only integer multiple sizes allowed, x1, x2, x3, etc.), then yeah, paints the text based on the string.

image

RobinSchmidt commented 6 years ago

i have the function

/** Draws a text with a BitmapFont with the given style-settings and returns the x-coordinate where 
the drawn text ends (and subsequent text can be appended, if desired). */
JUCE_API int drawBitmapFontText(Graphics &g, int x, int y, const juce::String& textToDraw, 
  const BitmapFont* fontToUse, const Colour& colourToUse, int kerning = -1, 
  Justification justification = Justification::topLeft);

in jura_GraphicsTools.h. besides othe things, it takes a pointer to a BitmapFont object. subclasses of that class store a (sort of) image for each glyph (actually just a juce grayscale image repurposed to store alpha-values). these glyph images are pre-rendered in the class constructor by the function createGlyphBitmaps (which a subclass can override). in my own subclasses, i just stupidly hardcode these bitmaps, but you could certainly also invoke a vector renderer there

the gui mock up looks good, btw. looking forward to see the finished product