Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Text rendering #65

Closed
AngusJohnson opened this issue Apr 10, 2019 · 22 comments
Closed

Text rendering #65

AngusJohnson opened this issue Apr 10, 2019 · 22 comments

Comments

@AngusJohnson
Copy link
Contributor

I don't think Graphics32 text rendering is optimal, and I've attached a sample project that compares the existing text rendering with some code I developed for another project. If this generates significant interest I'd be happy to work up a new text module (though I'd need help with cross-platform support).
Text2

Text2.zip

@Jens01
Copy link

Jens01 commented Apr 10, 2019

Here is my version of lines and text. Perhaps it can help. But I used generics.
I have integrated a miniHTML-Parser (cCommands: array of string = ['B', '/B', 'I', '/I', 'U', '/U', 'S', '/S', 'FONT', '/FONT', 'BR', 'TAB', 'SUB', '/SUB', 'SUP', '/SUP'];) .
https://hidrive.ionos.com/lnk/V8BppuAV

@andersmelander
Copy link
Member

Your screenshot looks good and from a cursory peek through your source I'd say it also looks fine - although I always wish for more comments :-)

Do you yourself have an idea of why the two solutions produces different output?
It's strange because I didn't remember the text output to be that fuzzy when it was first introduced. Maybe that is an error that crept in later.
I have tried with different type faces and sizes and your output is mostly, but not always, thinner or "prettier". Do we have any way of objectively judging which is more "correct"?
I've tried comparing the output to standard Windows GDI and Adobe Acrobat Viewer but I think it's hard to say which is more correct.

A few problems:

  • There's something wrong with the Zoom in your example.
    I was trying to examine the comma in this:
    billede
    to compare it with the Text2 output:
    billede
    but this is what the zoom showed me:
    billede

  • I've noticed that Text2 in some cases chops off the rightmost pixels of a word when LCD is enabled.
    billede

The current text support seems to be using a layered design with:

  • Vectorization of text
  • Rasterization of vectors

while your solution is more of a monolith.
I suspect that a monolithic design makes it easier to optimize, but it will probably also mean that the only way to support other platforms is to implement other monoliths. What I'm saying is that it might be a good idea to try and build on top of, extend or modify the existing design instead if that's possible.

@AngusJohnson
Copy link
Contributor Author

AngusJohnson commented Apr 10, 2019

although I always wish for more comments :-)

I wish I wrote more comments too :)

Do you yourself have an idea of why the two solutions produces different output?

The main difference is that I get the vectors for larger fonts and then down-sample these rasterized glyphs. It seems that this produces clearer and thinner glyphs at smaller font heights.

but this is what the zoom showed me:

I'm confident that what you're seeing in the zoom is an accurate representation of the pixels below your mouse cursor.

Edit: Oops, you're right (again), there is a bug there! It seems that moving the mouse over the top text render show the zoom of the lower text render. The zoom is accurate but just for the wrong image :). I'll fix and update again sometime soonish (when I've finished a few other fixes and embellishments).

I've noticed that Text2 in some cases chops off the rightmost pixels of a word when LCD is enabled.

OK, I haven't noticed that myself but evidently there's a bug somewhere. This code has been cobbled together in the last 24hrs from a small graphics library of mine (that's unrelated to Graphics32). I'm not sure it this bug is related to this recoding or if it also exists in my other library.

GR32_Text2 is monolithic because it's an amalgamation of 2 modules. It's intended as proof of concept (that we can do quite a lot better) as I was disappointed with the quality of text rendering in the TextVPR sample.

Edit2: I've just realised that the bolding and blurring I'm seeing in TextVPR may well be due to an improperly adjusted the Gamma. This may be much ado about nothing after all.

@andersmelander
Copy link
Member

andersmelander commented Apr 10, 2019

The main difference is that I get the vectors for larger fonts

Ah! but, but... that's cheating :-)
If you use that method won't you get the wrong hinting applied to your glyphs? Supposedly the hinting is tailored for size on good quality fonts.
It would be interesting to see what the difference was if the same method was applied to the existing text vectorization. It looks to me that like while it might produce thinner output there's still a difference in the anti-aliasing.

I'm confident that what you're seeing in the zoom is an accurate representation

I provided both the source and the zoomed image, so you can see for yourself that it isn't.
FWIW, at larger font sizes the PolyPolygonFS output wraps onto two lines and the second line isn't visible in the zoom at all.
[edit] Never mind. I see your edit crossed my post.

@andersmelander
Copy link
Member

I've just realised that the bolding and blurring I'm seeing in TextVPR may well be due to an improperly adjusted the Gamma. This may be much ado about nothing after all.

Yes, that would explain why it wasn't there originally. AFAIK the gamma stuff was introduced later.

@AngusJohnson
Copy link
Contributor Author

Yes, that would explain why it wasn't there originally. AFAIK the gamma stuff was introduced later.

Yes, DEFAULT_GAMMA is 2.2 (in GR32_Gamma.pas) which seems very high IMHO.
If I crank this gamma back to 1.0, text rendering with VPR seems to be very good (again).

@andersmelander
Copy link
Member

Reproduced.
I usually disable gamma in GR32.INC since I need to work on the raw uncorrected RGA values, but to run your example i stashed all my changes and thus got the GR32.INC with gamma enabled.

The default gamma was recently changed from 1.6 to to 2.2 by @CWBudde: dc6db6a
Maybe he can comment. I don't understand gamma correction good enough to have an opinion (other than it's problematic).

@AngusJohnson
Copy link
Contributor Author

Just to say that the number of glyphs is not always equal to the length of the string

Thanks, that's helpful information too. (Sorry not to reply earlier.)

@andersmelander
Copy link
Member

So... "Works as designed" or do you still think that we need to fix anything here?

If the existing renderer is good enough, but you know of something that could be done better, you could merge your improvements into the existing?

@CWBudde
Copy link
Contributor

CWBudde commented Apr 10, 2019

Regarding the gamma setting: There is probably some confusion about it. This goes back to the research by Maxim Shemanarev, which has been used as a reference by many others (probably by Mattias as well).
The original research can be found here:
http://www.antigrain.com/research/gamma_correction/index.html

To make it short: Today monitors require a certain GAMMA setting to display colors correctly. Since (good) monitors use mostly sRGB nowadays you should use this for a perfect color representation. If you can't use it, a gamma factor of 2.2 comes very close to this.

Since windows itself does not apply any gamma preprocessing itself, it's up to the user to do it. So far GR32 used a gama factor of 1.6 for drawing of primitives. This means that all bitmaps implicidly contain a gamma factor, so that drawing only mean to blit the image to the graphic card memory.

With an implied gamma setting you need to ensure that the rendering also applies the gamma correction upon drawing at some point. This said, it seems that drawing the fonts with a proper gamma value doesn't seem to work as fonts look to bold.

The reason for this can be:

  • wrong gamma handling (this is/was the case in AGG)
  • double gamma handling (since a factor of 2.2 is close to 2 several functions imply a square function somewhere)
  • issues with blending

In our case the latter (and the first) could be the problem, because not the color values are multiplied as it should, but the alpha values. Since these are multiplied with the color values later, the gamma handling will be done implicitly, but I'm not sure whether it will still be in twice (as it is also in the alpha value as well).

It is on my todo list to look into this issue, but not today or this week, I fear.

@CWBudde
Copy link
Contributor

CWBudde commented Apr 10, 2019

Regarding the gamma value again: We probably really need a way to have an independent gamma setting for each bitmap in a way that rendering will apply the right gamma value for that particular bitmap.

Within the PNG library I already take a certain gamma chunk (containing gamma information) into account in a way that I match it.

The main issue with converting the gamma values is the loss of precision. So sooner or later we should also consider to make 16 bit in the future. But that would also require to have (temporary) bitmaps with 16 bit precision per color channel.

@andersmelander
Copy link
Member

It is on my todo list to look into this issue, but not today or this week, I fear.

That's OK. Just have it done by yesterday :-)

Maybe I haven't understood this properly, but shouldn't correction only be applied at the end stage. I.e. when something is finally displayed on the screen? If this is correct then the only place where gamma should be applied automatically would be in TImage32/TPaintBox32.
I don't think it makes sense in a library such as Graphics32 to assume that the pixel values we're working on have already had some sort of correction applied. The assumption is surely that we are working in RGB color space?

@CWBudde
Copy link
Contributor

CWBudde commented Apr 10, 2019

Basically it would be satisfying to apply it only upon drawing onto the screen. However, if not double buffered you usually draw the result quite often onto the screen, so this drawing code should (in the past) be as fast as possible (just a blit).
If you apply a gamma correction per pixel that it would surely slow down drawing or you need some double buffer for it, which - for large bitmaps - can be a memory limitation.

Another reason about why to use preprocessed images is the precision, especially with only 8 bit per color channel. While operations with a linear scaling will be simple and straight forward, your precision is also equaly spaced accross the entire range. But our eye does not perceive the brightness linearly. it typically perceives darker colors better. Thus the preprocessing makes sense as you can get an even better precision.

Unfortunately - as always - GR32 does not yet use its full potential because the colors are converted by a simple 8 bit table, which has the precision loss already built-in :-(

So in order to make use of the full potential, all drawing code should (optionally!) work with an internal precision of 16 bit per color and apply the gamma factor with a 16bit table. Only than the full brightness range will be used.

The same holds for blending two bitmaps. If done correct, the gamma factors need to be matched before. Per pixel this should be done with 16 bit internal precision. After this operation it can be converted back to 8 bit.

While this is necessary for high quality rendering and good image processing, it might be overkill for simple drawing operations. IMHO Gr32 should offer both, depending on what the user wants.

We just need to implement it and find good defaults (in terms of performance and quality).

@andersmelander
Copy link
Member

I'm currently reading "What every coder should know about gamma" which should clear up some thing for me.

There's even a section on anti-aliasing. Here's a quote:

Antialiasing in γ=2.2 space results in overly dark “smoothing pixels” (right image); the text appears too heavy, almost as if it was bold. Running the algorithm in linear space produces much better results (left image), although in this case the font looks a bit too thin. Interestingly, Photoshop antialiases text using γ=1.42 by default, and this indeed seems to yield the best looking results (middle image).
billede

@CWBudde
Copy link
Contributor

CWBudde commented Apr 10, 2019

Also good to know the sentence after that:

The reason for this is that most fonts have been designed for gamma-incorrect font rasterizers, hence if you use linear space (correctly), then the fonts will look thinner than they should.

This explains why we need an additional (independent!) gamma setting for the font rendering in order to make it look as desired (more or less).

@AngusJohnson
Copy link
Contributor Author

If the existing renderer is good enough, but you know of something that could be done better, you could merge your improvements into the existing?

I don't think we need to make significant changes now, but there are some things that need attention:

  1. add a separate gamma variable as suggested by Christian.
  2. there are significant artifacts/bugs in TTF to vector conversion, with vectors occasionally streaking to the origin (not affecting all fonts).
  3. angled text doesn't seem to be supported.

It would also be ideal if character glyphs (or even just vectors) were cached, as this could improve performance.

I'll try to investigate point 2 above but I don't have the resources to tackle the others, though they are features in GR32_Text2. (GR32_Text2 isn't affected by gamma even though it also uses PolyPolygonFS because I use a larger font for rendering and shrink the result to the expected font size.)

I would recommend a text gamma of 1.0 or even a little less.

@AngusJohnson
Copy link
Contributor Author

the word "الله" has four characters "ا ل ل ه" but only one glyph

I've had a closer look at this and the both GR32_Text_VCL and GR32_Text2 render الله (Char($FDF2)) correctly.

I've noticed that Text2 in some cases chops off the rightmost pixels of a word when LCD is enabled.

I've tried pretty hard but I haven't been able to duplicate this.

Anyhow, just before I close this thread, here an update to GR32_Text2...

Text2.zip

@andersmelander
Copy link
Member

I've tried pretty hard but I haven't been able to duplicate this.

That's because you fixed it :-)
With your changes I can no longer replicate it either.

there are significant artifacts/bugs in TTF to vector conversion, with vectors occasionally streaking to the origin (not affecting all fonts).

Do you have an example of this? I remember seeing something like that in another project I worked on but that was at least 15 years ago and AFAIR it was a bug in the font.

angled text doesn't seem to be supported.

No. It seems to do the escapement of the individual glyphs correctly but fails to also rotate the base line.

@AngusJohnson
Copy link
Contributor Author

Do you have an example of this?

text

@AngusJohnson AngusJohnson reopened this Apr 11, 2019
@andersmelander
Copy link
Member

Not reproduced.
I'm about to commit my changes to TCanvas32 and this looks like it could very well be caused by one of the things I've fixed i TFlattenedPath. Stay tuned...

@AngusJohnson
Copy link
Contributor Author

Not reproduced.

Yep, while I was mucking around with TFlattenedPath, I evidently introduced this bug in some uncommitted code. (I hadn't finished tinkering there when you volunteered to tackle it and I forgot to revert my unfinished changes.)

@andersmelander
Copy link
Member

I have just implemented a cache in the VCL TextToPath functions. It has doubled the performance of the text test in the benchmark examples. Where do I collect my paycheck? :-)

GR32_Text_VCL.zip

I have added it using conditional defines and it is implemented with generics TDictionary<>. I'm using LOGFONT as the primary key into the dictionary. Secondary key is the character value.
I'm caching the font metrics, the individual glyph metrics and the individual TT glyph polygon data. It will be trivial to add kerning data as well.
I have not tried to profile or optimize it in any way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants