The Rombos-Blog

Prevent blurry fonts when using CCLabelTTF


Note: this in an updated blog (August 11th, 2012) to the original version, including a new download!

When using CCLabelTTF to use True Type fonts with Cocos2D, you might have noticed that they sometimes look pretty blurry, while in other places they appear reasonably sharp. What’s wrong here?

The rendering of the fonts tries to strictly consider the exact position of a font, which is given as a floating point number. If this position is not exactly hitting an actual pixel on the screen, the rendering is adjusted accordingly by introducing additional anti-aliasing, which makes the font appear to start exactly between two pixels. In theory. In reality, it simply looks blurry now, and I am sure in favor of a sharp font you would have rather adjusted a correct 0.3 pixel offset to the nearest pixel instead.

Fortunately, it is really not a big deal to do exactly this. We just need to derive a new class from CCLabelTTF and check (and possibly correct) the top left position each time it might change. Considering the complete node structure we are embedded in, we need to round the top left corner coordinates (in world/screen space) to a full pixel, and then recompute the position all the way back to our local coordinate system and fix the position parameter accordingly. Why the top left corner? Because the string is ultimately converted to a Cocos2D texture using the core NSString drawInRect method, which is based on the UIKit coordinate system and has its (0,0) coordinates always top left (rather than Cocos2D bottom left origin).
To make sure that rounding errors don’t sum up and make your label move uncontrolled, we always remember the originally intended position and adjust always referring to that position.

As a reward for reading this blog, you can simply download a ready-made class SharpLabelTTF right away! You can use it instead of CCLabelTTF and integrate it exactly like that. You don’t need to consider anything else.

Using CCLabelTTF:

Using SharpLabelTTF:

And here is the beef:

@implementation SharpLabelTTF
- (void) fixPosition {

    CGSize dim = self.texture.contentSize;    
    [super setPosition:intendedPosition_];
    if (scaleX_ < 0.3 || scaleY_ < 0.3) return;

    // compute world (= screen) coordinate of top left position of label  
    CGPoint worldSpaceTopleft = [self convertToWorldSpace: ccp(0,dim.height)];

    // align origin on a pixel boundary on screen coordinates
    worldSpaceTopleft.x = roundf(worldSpaceTopleft.x * CC_CONTENT_SCALE_FACTOR()) / CC_CONTENT_SCALE_FACTOR();
    worldSpaceTopleft.y = roundf(worldSpaceTopleft.y * CC_CONTENT_SCALE_FACTOR()) / CC_CONTENT_SCALE_FACTOR();

    // transform back into node space
    CGPoint nodeSpaceTopleft = [self convertToNodeSpace:worldSpaceTopleft];

    // adjust position by the computed delta
    CGPoint delta = ccpSub(nodeSpaceTopleft, ccp(0,dim.height));
    CGPoint newPos = ccpAdd(position_, delta);

    // finally set the position data
    [super setPosition:newPos];      

// capture modification calls to adjust position
- (void)onEnter {
    [self fixPosition];
    [super onEnter];

- (void)setParent:(CCNode *)parent {
    [super setParent:parent];
    [self fixPosition];

- (void)setString:(NSString *)str {
    [super setString:str];
    [self fixPosition];

- (void)setPosition:(CGPoint)position {
    intendedPosition_ = position;
    [self fixPosition];

-(void)setRotation:(float)rotation {
    [super setRotation:rotation];
    [self fixPosition];