Font Stroke

This topic contains 35 replies, has 23 voices, and was last updated by  MDKG 1 month, 1 week ago.

Viewing 25 posts - 1 through 25 (of 36 total)
Author Posts
Author Posts
December 25, 2010 at 9:08 pm #227122

leocck
Participant
@leocck

A long time ago I was thinking about a code to create a stroke around the letters to achieve better results than Hiero stroke.

So I did it and I like to share with you others. I didn’t search on cocos2d forum if there’s another method already, if so please excuse me.

The code can create a stroke on a CCLabel given a color and a size, and returns a CCRenderTexture to put behind the original CCLabel, giving this result:

I’m not using the latest version of Cocos2D, and haven’t test its performance with dynamic labels (since I’m using it on static hints). That been said, here’s the code:

+(CCRenderTexture*) createStroke: (CCLabel*) label   size:(float)size   color:(ccColor3B)cor
{
CCRenderTexture* rt = [CCRenderTexture renderTextureWithWidth:label.texture.contentSize.width+size*2 height:label.texture.contentSize.height+size*2];
CGPoint originalPos = [label position];
ccColor3B originalColor = [label color];
[label setColor:cor];
ccBlendFunc originalBlend = [label blendFunc];
[label setBlendFunc:(ccBlendFunc) { GL_SRC_ALPHA, GL_ONE }];
CGPoint center = ccp(label.texture.contentSize.width/2+size, label.texture.contentSize.height/2+size);
[rt begin];
for (int i=0; i<360; i+=15)
{
[label setPosition:ccp(center.x + sin(CC_DEGREES_TO_RADIANS(i))*size, center.y + cos(CC_DEGREES_TO_RADIANS(i))*size)];
[label visit];
}
[rt end];
[label setPosition:originalPos];
[label setColor:originalColor];
[label setBlendFunc:originalBlend];
[rt setPosition:originalPos];
return rt;
}

And here’s an example of how to use it:

CCLabel* label = [CCLabel labelWithString: @"Some Text"
dimensions:CGSizeMake(305,179) alignment:UITextAlignmentLeft
fontName:@"SomeFont" fontSize:23];
[label setPosition:ccp(167,150)];
[label setColor:ccWHITE];
CCRenderTexture* stroke = [SomeUtilityClass createStroke:label size:3 color:ccBLACK];
[self addChild:stroke];
[self addChild:label];

December 26, 2010 at 12:33 am #308465

hactar
Moderator
@hactar

Thanks for sharing this. Nice results on your screen shot there. Bookmarking for possible future use.

December 26, 2010 at 2:42 am #308466

cocos
Participant
@cocos

@leocck Great! Thank you! Bookmarking for reference.

December 30, 2010 at 11:13 am #308467

carribus
Participant
@carribus

This really does look nice.

My concern would be about the 360 iterations per text label.

Although I don’t have a better solution for the type of stroke quality you achieve, you could probably get away with an iteration of 8 loops, each loop changing the direction by 45 degrees.

December 30, 2010 at 12:41 pm #308468

leocck
Participant
@leocck

@carribus

In fact it doesn’t have 360 iterations, since I’m adding 15 on each iteration, it’ll have 24 iterations.

But you can optimize the number of iterations, since thinner strokes with small fonts needs less iterations to look good ;)

January 25, 2011 at 9:22 pm #308469

leocck
Participant
@leocck

I have updated the code, ’cause it had a visibility bug. Here’s the code:

+(CCRenderTexture*) createStroke: (CCLabel*) label   size:(float)size   color:(ccColor3B)cor
{
CCRenderTexture* rt = [CCRenderTexture renderTextureWithWidth:label.texture.contentSize.width+size*2 height:label.texture.contentSize.height+size*2];
CGPoint originalPos = [label position];
ccColor3B originalColor = [label color];
BOOL originalVisibility = [label visible];
[label setColor:cor];
[label setVisible:YES];
ccBlendFunc originalBlend = [label blendFunc];
[label setBlendFunc:(ccBlendFunc) { GL_SRC_ALPHA, GL_ONE }];
CGPoint meio = ccp(label.texture.contentSize.width/2+size, label.texture.contentSize.height/2+size);
[rt begin];
for (int i=0; i<360; i+=30) // you should optimize that for your needs
{
[label setPosition:ccp(meio.x + sin(CC_DEGREES_TO_RADIANS(i))*size, meio.y + cos(CC_DEGREES_TO_RADIANS(i))*size)];
[label visit];
}
[rt end];
[label setPosition:originalPos];
[label setColor:originalColor];
[label setBlendFunc:originalBlend];
[label setVisible:originalVisibility];
[rt setPosition:originalPos];
return rt;
}

January 25, 2011 at 9:26 pm #308470

jd
Participant
@jd

Sweet. Adding to favorites.

February 7, 2011 at 7:00 am #308471

bobertperry
Participant
@bobertperry

I updated your code so the anchor point does not have to be .5,.5.

+(CCRenderTexture*) createStroke: (CCLabelTTF*) label   size:(float)size   color:(ccColor3B)cor
{
CCRenderTexture* rt = [CCRenderTexture renderTextureWithWidth:label.texture.contentSize.width+size*2 height:label.texture.contentSize.height+size*2];
CGPoint originalPos = [label position];
ccColor3B originalColor = [label color];
BOOL originalVisibility = [label visible];
[label setColor:cor];
[label setVisible:YES];
ccBlendFunc originalBlend = [label blendFunc];
[label setBlendFunc:(ccBlendFunc) { GL_SRC_ALPHA, GL_ONE }];
CGPoint bottomLeft = ccp(label.texture.contentSize.width * label.anchorPoint.x + size, label.texture.contentSize.height * label.anchorPoint.y + size);
CGPoint positionOffset = ccp(label.texture.contentSize.width * label.anchorPoint.x - label.texture.contentSize.width/2,label.texture.contentSize.height * label.anchorPoint.y - label.texture.contentSize.height/2);
CGPoint position = ccpSub(originalPos, positionOffset);

[rt begin];
for (int i=0; i<360; i+=30) // you should optimize that for your needs
{
[label setPosition:ccp(bottomLeft.x + sin(CC_DEGREES_TO_RADIANS(i))*size, bottomLeft.y + cos(CC_DEGREES_TO_RADIANS(i))*size)];
[label visit];
}
[rt end];
[label setPosition:originalPos];
[label setColor:originalColor];
[label setBlendFunc:originalBlend];
[label setVisible:originalVisibility];
[rt setPosition:position];
return rt;
}

and made a wrapper for CCMenuItemFont to include your stroke thingy:

@implementation CCMenuItemFontWithStroke

-(id) initFromString: (NSString*) value target:(id) rec selector:(SEL) cb
{
NSAssert( [value length] != 0, @"Value length must be greater than 0");

CCLabelTTF *label = [CCLabelTTF labelWithString:value fontName:[CCMenuItemFontWithStroke fontName] fontSize:[CCMenuItemFontWithStroke fontSize]];

if((self=[super initWithLabel:label target:rec selector:cb]) ) {
// do something ?
}

CCRenderTexture * stroke = [WhateverClassItsIn createStroke:label size:3 color:strokeColor];
[self addChild:stroke z:-1];

return self;
}

@end

Works freeking awesome~

February 8, 2011 at 5:59 am #308472

bobertperry
Participant
@bobertperry

The wrapper I mentioned before did not update when you update a menu’s string. I updated the code to fix this if anyone is interested.

@implementation CCMenuItemFontWithStroke

#define kTagStroke 1029384756

-(id) initFromString: (NSString*) value target:(id) rec selector:(SEL) cb
{
self = [super initFromString:value target:rec selector:cb];

if ([label_ isKindOfClass: [CCLabelTTF class]]) {
CCRenderTexture * stroke = [GameSingleton createStroke:(CCLabelTTF*)label_ size:3 color:strokeColor];
[self addChild:stroke z:-1 tag:kTagStroke];
}else{
NSLog(@"Error adding stroke in menu, label_ is not a CCLabelTTF. This has only been tested on cocos2d 99.5");
}

return self;
}

-(void) setString:(NSString *)string
{
[super setString:string];

if ([label_ isKindOfClass: [CCLabelTTF class]]) {
[self removeChildByTag:kTagStroke cleanup:YES];
CCRenderTexture * stroke = [GameSingleton createStroke:(CCLabelTTF*)label_ size:3 color:strokeColor];
[self addChild:stroke z:-1 tag:kTagStroke];

}else{
NSLog(@"Error adding stroke in menu, label_ is not a CCLabelTTF. This has only been tested on cocos2d 99.5");
}

}

@end

February 8, 2011 at 8:07 am #308473

leocck
Participant
@leocck

Many thanks bobertperry !

March 9, 2011 at 6:42 am #308474

FiberCore
Participant
@fibercore

Thank you @leocck ,thanks for your wonderful work. I use CCLabelBMFont, if anyone use it like me, you should change

CCRenderTexture* rt = [CCRenderTexture renderTextureWithWidth:label.texture.contentSize.width+size*2  height:label.texture.contentSize.height+size*2];

to

CCRenderTexture* rt = [CCRenderTexture renderTextureWithWidth:label.contentSize.width+size*2  height:label.contentSize.height+size*2];

March 9, 2011 at 12:55 pm #308475

leocck
Participant
@leocck

Thanks @FiberCore

It seems imageshack don’t works anymore. Here’s a new image to see the result.

March 9, 2011 at 1:38 pm #308476

FiberCore
Participant
@fibercore

Hi @leocck, I met a problem with stroke color, I created a label with stroke like this:

static const ccColor3B ccGreenOutline = {209,255,130};
static const ccColor3B ccCoffeeColor = {102,59,49};
......
CCLabelBMFont *timerLabel = [CCLabelBMFont labelWithString:@"Your" fntFile:@"SomeFont.fnt"];
timerLabel.positionInPixels = ccp(200, 200);
timerLabel.color = ccCoffeeColor;
CCRenderTexture* stroke = [CCLabelBMFont createStroke:timerLabel size:3 color:ccGreenOutline];
[self addChild:stroke z:30];
[self addChild:timerLabel z:30];

But I just got wrong label with white stroke color, which I expect should be green.

The correct color is :

I tested

static const ccColor3B ccWHITE = {255,255,255};
static const ccColor3B ccYELLOW = {255,255,0};
static const ccColor3B ccBLUE = {0,0,255};
static const ccColor3B ccGREEN = {0,255,0};
static const ccColor3B ccRED = {255,0,0};

It works fine. But I cannot got right ccGreenOutline (r = 209, g = 255, b = 130)

March 9, 2011 at 1:57 pm #308477

leocck
Participant
@leocck

This seems like a blend problem… Try some blending parameters on this line to see if it works better:

[label setBlendFunc:(ccBlendFunc) { GL_SRC_ALPHA, GL_ONE }];

March 10, 2011 at 1:39 am #308478

FiberCore
Participant
@fibercore

Thanks, GL_SRC_COLOR works fine :)

March 10, 2011 at 1:47 am #308479

leocck
Participant
@leocck

Wow that’s great, thanks @FiberCore

March 10, 2011 at 9:37 am #308480

varedis
Moderator
@varedis

I have a slight problem trying to use this, using a dark color works fine but I am trying to add a white stroke to some of my labels and the color gets blended with the original color in places, I have tried changing blending modes but this is the best I can get it to look:

This is using:

[label setBlendFunc:(ccBlendFunc) { GL_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR }];

March 10, 2011 at 1:07 pm #308481

wybielacz
Participant
@wybielacz

Could someone point me how to add the font stroke to a CCMenuItemFont ?

April 5, 2011 at 7:56 pm #308482

JesseJames
@jessejames

Hi,

I have troubles updating the stroke when the label changes. I tried this but it doesn’t work:

// update label
[labelTime setString: time];
// update stroke ?!
labelTimeStroke = [SomeUtilityClass createStroke:labelTime size:1 color:ccBLACK];

cheers,

Tex

April 6, 2011 at 3:28 pm #308483

wybielacz
Participant
@wybielacz

@JesseJames You have to remove the stroke and add it again at each update in order to make it work.

Anyone can tell me how to add the font stroke to a CCMenuItemFont ?

April 7, 2011 at 6:44 am #308484

timTheMystic
@timthemystic

You could do something like this:

CCLabel* aLabel = [CCLabel labelWithString: @"Trip Up!"
dimensions: CGSizeMake(200, 40)
alignment: UITextAlignmentCenter
fontName: @"Marker Felt"
fontSize:36];

aLabel.color = ccc3(255, 255, 10);
aLabelItem = [CCMenuItemLabel itemWithLabel:aLabel target:self selector:@selector(gotoMainMenu:)];

CCRenderTexture* strokeT = [GenUtils createStroke:aLabel size:2 color:ccc3(255,240,255) withBlend:1];
aLabelItem.position = ccp(0, 0);
strokeT.position = ccp(240, 160);
[_menuLayer addChild:strokeT];

April 27, 2011 at 11:24 am #308485

Zhenmuron
Participant
@zhenmuron

Is there any simple way to change the stroke alpha/opacity after its creation, if I would like to fade in/out the text? Or do I basically have to redraw the stroke each update with a different alpha?

May 10, 2011 at 2:27 am #308486

bobertperry
Participant
@bobertperry

I know its old but I ended up doing this too:

Could someone point me how to add the font stroke to a CCMenuItemFont ?

.h

//
// CCMenuItemFontWithStroke.h
// test2
//
// Created by Robert Perry on 2/5/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "cocos2d.h"

@interface CCMenuItemFontWithStroke : CCMenuItemFont {
int stokeSize;
ccColor3B strokeColor;

}

@property (nonatomic) int stokeSize;
@property (nonatomic)ccColor3B strokeColor;

-(id) initFromString: (NSString*) value target:(id) rec selector:(SEL) cb strokeSize:(int)strokeSize stokeColor:(ccColor3B)color;
@end

.m

//
// CCMenuItemFontWithStroke.m
// test2
//
// Created by Robert Perry on 2/5/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "CCMenuItemFontWithStroke.h"

@implementation CCMenuItemFontWithStroke

@synthesize stokeSize;
@synthesize strokeColor;

#define kTagStroke 1029384756

+(CCRenderTexture*) createStroke: (CCLabelTTF*) label size:(float)size color:(ccColor3B)cor
{
CCRenderTexture* rt = [CCRenderTexture renderTextureWithWidth:label.texture.contentSize.width+size*2 height:label.texture.contentSize.height+size*2];
CGPoint originalPos = [label position];
ccColor3B originalColor = [label color];
BOOL originalVisibility = [label visible];
[label setColor:cor];
[label setVisible:YES];
ccBlendFunc originalBlend = [label blendFunc];
[label setBlendFunc:(ccBlendFunc) { GL_SRC_ALPHA, GL_ONE }];
CGPoint bottomLeft = ccp(label.texture.contentSize.width * label.anchorPoint.x + size, label.texture.contentSize.height * label.anchorPoint.y + size);
//CGPoint positionOffset = ccp(label.texture.contentSize.width * label.anchorPoint.x - label.texture.contentSize.width/2,label.texture.contentSize.height * label.anchorPoint.y - label.texture.contentSize.height/2);
//use this for adding stoke to its self...
CGPoint positionOffset= ccp(-label.contentSize.width/2,-label.contentSize.height/2);

CGPoint position = ccpSub(originalPos, positionOffset);

[rt begin];
for (int i=0; i<360; i+=30) // you should optimize that for your needs
{
[label setPosition:ccp(bottomLeft.x + sin(CC_DEGREES_TO_RADIANS(i))*size, bottomLeft.y + cos(CC_DEGREES_TO_RADIANS(i))*size)];
[label visit];
}
[rt end];
[label setPosition:originalPos];
[label setColor:originalColor];
[label setBlendFunc:originalBlend];
[label setVisible:originalVisibility];
[rt setPosition:position];
return rt;
}

-(id) initFromString: (NSString*) value target:(id) rec selector:(SEL) cb strokeSize:(int)strokeSize stokeColor:(ccColor3B)color
{

self = [super initFromString:value target:rec selector:cb];

self.strokeColor = color;
self.stokeSize = strokeSize;

if ([label_ isKindOfClass: [CCLabelTTF class]]) {
CCRenderTexture * stroke = [CCMenuItemFontWithStroke createStroke:(CCLabelTTF*)label_ size:strokeSize color:strokeColor];
[self addChild:stroke z:-1 tag:kTagStroke];
}else{
NSLog(@"Error adding stroke in menu, label_ is not a CCLabelTTF. This has only been tested on cocos2d 99.5");
}

return self;
}

//default 1 pixel, black
-(id) initFromString: (NSString*) value target:(id) rec selector:(SEL) cb {
return [self initFromString:value target:rec selector:cb strokeSize:3 stokeColor:ccBLACK];
}

-(void) setString:(NSString *)string
{
[super setString:string];

if ([label_ isKindOfClass: [CCLabelTTF class]]) {
[self removeChildByTag:kTagStroke cleanup:YES];
CCRenderTexture * stroke = [CCMenuItemFontWithStroke createStroke:(CCLabelTTF*)label_ size:stokeSize color:strokeColor];
[self addChild:stroke z:-1 tag:kTagStroke];

}else{
NSLog(@"Error adding stroke in menu, label_ is not a CCLabelTTF. This has only been tested on cocos2d 99.5");
}

}

@end

May 23, 2011 at 8:32 pm #308487

betzerra
Participant
@betzerra

I couldn’t make it work… no font stroke displayed. Any thoughts on what I’m doing wrong?

Sorry about my dumb question, I’m new on cocos2d.

January 27, 2012 at 2:38 pm #308488

praveencastelino
@praveencastelino

Its not working when the fill color is white and fill color is red(can be anything).

I guess there is some problem with blend function.

Any idea on what should be the blending function?

Viewing 25 posts - 1 through 25 (of 36 total)

You must be logged in to reply to this topic.