9

First of all, I know this topic has been brought up several times before but I'm posting this question because none of the "solutions" I've used in the past have worked in this specific case. I'm drawing some text to a CALayer that is hosted by a view inside my NSToolbar. Here's what the text look likes:

Font smoothing on

I tried using a suggestion from this StackOverflow post, which is to call CGContextSetShouldSmoothFonts(ctx, false) to turn off subpixel antialiasing before drawing into the context. This is a solution that has given me acceptable results in the past, but in this case it seems to have made the text look even worse:

Font smoothing off

The other solution mentioned in that post is to fill the rect with an opaque background color before drawing, which simply isn't possible in this case because the toolbar background is a gradient. Is there anything I can do to make this text look as nice as text drawn into a plain NSView?

Community
  • 1
  • 1
indragie
  • 18,002
  • 16
  • 95
  • 164
  • 1
    I believe CA renders without subpixel antialiasing because it doesn't know the layer's alignment with screen pixels. But in many cases, the layer is exactly aligned and so subpixel antialiasing would be appropriate. I have never yet found a concrete solution to this — +1 in the hopes that we get a great answer :) – jtbandes Aug 13 '11 at 00:37
  • Also: what is wrong with the first screenshot you posted? What is your desired result? – jtbandes Aug 13 '11 at 00:40
  • It's hard to explain in words, but the text just looks a little too sharp/crisp. It looks almost jagged. Its difficult to see from that one little screenshot, but once you see the overall picture compared to the rest of the app it's fairly obvious – indragie Aug 13 '11 at 01:04
  • I recently asked the same exact question. Take a look at it; perhaps it will help: http://stackoverflow.com/questions/6858205/faking-subpixel-antialiasing-on-text-with-core-animation. Unfortunately, you're going to have to live with it. It's simply not possible... – Itai Ferber Aug 13 '11 at 04:30

3 Answers3

7

The reason sub-pixel antialiasing is not possible is that behind the scenes, CALayers are essentially just OpenGL textures.

This means they are bitmaps handled and rendered directly by the GPU. The GPU knows nothing about text and so can't apply sub-pixel antialiasing.

In order to handle sub-pixel antialiasing, it would have to recalculate the pixel values of the layer content each time the layer was modified, which would be prohibitively expensive and remove the whole point of hosting the layer on the GPU, which is to make compositing and rendering extremely fast.

The reason that text on opaque backgrounds can use SPAA is that the text is pre-rendered with sub-pixel anti-aliasing before the layer texture is stored on the GPU. This is not possible if the background of the text is transparent.

You will have to live with this limitation. Thankfully, it will no longer be an issue when we eventually all get HiDPI displays… have a look at the iPhone, which does not do sub-pixel antialiasing at all. On the iPhone, text looks just fine thanks to the high screen resolution.

Rob Keniger
  • 45,830
  • 6
  • 101
  • 134
  • But at what point is the text (e.g., from `CATextLayer`) rendered? Is there no simple way to cause it to be rendered with subpixel antialiasing, as long as you know it'll always be aligned to screen pixels? – jtbandes Aug 13 '11 at 00:55
  • The text is rendered whenever it changes, and essentially is written as a bitmap to the backing OpenGL texture. I agree that it would be fckn great if somehow this could be worked around for cases where SPAA would be possible to calculate, but unfortunately it isn't. Submit a bug report if you want it to change… – Rob Keniger Aug 13 '11 at 01:02
  • But it is kind of possible. If you have a text field (label) with a solid background, even if its parent view is layer-backed, it seems to use SPAA. What makes that happen? – jtbandes Aug 13 '11 at 01:05
  • 1
    I actually updated the answer with this info. Basically, the text can be pre-rendered onto the opaque background before the bitmap is sent to the GPU. With a transparent background this is not possible. – Rob Keniger Aug 13 '11 at 01:08
  • I pretty much expected this answer, and it bugs me to no end that as of now, there is no way to have both nicely rendered text and powerful animation capabilities together. It's more of a "pick one and run with it" sort of choice. Is there anything I could try that might at least improve it? – indragie Aug 13 '11 at 01:10
  • How does CA know that it can be pre-rendered? Does it just check the alpha of every pixel? And is there any way to trick it into doing SPAA? – jtbandes Aug 13 '11 at 02:03
  • 1
    I believe it uses the `opaque` flag on the layer, but it doesn't matter. If you tricked it into doing SPAA incorrectly, it would look much worse. If you know the background, you can always render the entire thing (text and background) into an opaque layer yourself. If you don't know the background, neither does CA at the point you want to anti-alias. – Rob Napier Aug 13 '11 at 03:10
5

Unfortunately there's no magic fix. At the end of the day, the text must be drawn on to an opaque background to get SPAA. It sucks, but it makes a lot of sense given how layers work. And honestly, I can't see Apple ever "fixing" this. I think this is the reason they've stuck with the old software compositing model for so long. It's a hard problem and they're hoping to ignore it by going high-DPI.

But anyways, there are a few ways to get SPAA in your specific case.

You could try to re-create the gradient and draw it yourself when you draw your text. But obviously that's brittle if the toolbar gradient changes in an OS update, and it could be hard to get the gradient to match exactly right.

Or you could actually try to grab an image of the toolbar background, either at runtime or ahead of time, and draw that as your background.

joshaber
  • 2,465
  • 20
  • 13
  • The last suggestion seems like it might be a good one. I'll try it and let you know how that works out. – indragie Aug 13 '11 at 05:48
  • I ended up just recreating the gradient and then drawing that, it works perfectly. As you said, it might change in a future OS revision but I think I'll have time to prepare for that event ;-). I'm marking this answer as correct because I actually managed to get it working with this method, but Rob's answer does provide a fantastic technical explanation as well. – indragie Aug 14 '11 at 01:55
0

I have a good experience in OpenGL, animation, real-time scrolling text, rendering text on textures using multisample AA. A 4 bytes texture, with alpha, doesn't care about the background is or isn't transparent. OpenGL renders that transparent texture in real time. And remaking a texture at any small change of the glyphs, is a question of microseconds. Apple could fix that.

Leonardo
  • 751
  • 8
  • 15