Combining Letter Images

At this point in the process we have a directory full of a full alphabet of letter images and we need to recombine them into a sprite sheet for use in Cocos2d-x, other game engines and elsewhere. We will use the TexturePacker program to create a sprite-sheet PNG file as well as a PLIST index file. Then we can change this PLIST file to a FNT file required by Cocos2d-x.

Creating the Sprite-Sheet:

TexturePacker will do most of the work here for us — so this is the easy part.

Start up TexturePacker — the free version will do all we need. Just select all our letter images and drag them to the left-hand-side window in TexturePacker and drop them there. You will see the file list with small images of each file beside their names. In the center window you will see the images arrayed into a sprite sheet.

NOTE: Record the order of the characters as they appear in the left window of TexturePacker. In my case the string is:

“A BCDEFGHIJKLMNOPQRSTUVWXYZ”

Save the sprite-sheet by pressing the “Publish Sprite Sheet” button at the top of the TexturePacker window and give the sprite-sheet a name (no extension). TexturePacker will generate the <filename>.png and <filename>.plist files.

Sprite sheet containing entire alphabet.

The following is the corresponding PLIST file which describes the locations and sizes of the individual images in the above sprite-sheet:

[cc lang=”xml” tabsize=”8″ lines=”20″ width=”600″] frames CharImage_001.png aliases spriteOffset {0,0} spriteSize {214,217} spriteSourceSize {214,217} textureRect {{1396,217},{214,217}} textureRotated CharImage_002.png aliases spriteOffset {0,0} spriteSize {50,217} spriteSourceSize {50,217} textureRect {{0,0},{50,217}} textureRotated CharImage_003.png aliases spriteOffset {0,0} spriteSize {186,217} spriteSourceSize {186,217} textureRect {{0,217},{186,217}} textureRotated CharImage_004.png aliases spriteOffset {0,0} spriteSize {146,217} spriteSourceSize {146,217} textureRect {{185,0},{146,217}} textureRotated CharImage_005.png aliases spriteOffset {0,0} spriteSize {202,217} spriteSourceSize {202,217} textureRect {{572,217},{202,217}} textureRotated CharImage_006.png aliases spriteOffset {0,0} spriteSize {165,217} spriteSourceSize {165,217} textureRect {{958,0},{165,217}} textureRotated CharImage_007.png aliases spriteOffset {0,0} spriteSize {203,217} spriteSourceSize {203,217} textureRect {{774,217},{203,217}} textureRotated CharImage_008.png aliases spriteOffset {0,0} spriteSize {153,217} spriteSourceSize {153,217} textureRect {{331,0},{153,217}} textureRotated CharImage_009.png aliases spriteOffset {0,0} spriteSize {299,217} spriteSourceSize {299,217} textureRect {{1203,434},{299,217}} textureRotated CharImage_010.png aliases spriteOffset {0,0} spriteSize {135,217} spriteSourceSize {135,217} textureRect {{50,0},{135,217}} textureRotated CharImage_011.png aliases spriteOffset {0,0} spriteSize {164,217} spriteSourceSize {164,217} textureRect {{794,0},{164,217}} textureRotated CharImage_012.png aliases spriteOffset {0,0} spriteSize {210,217} spriteSourceSize {210,217} textureRect {{1186,217},{210,217}} textureRotated CharImage_013.png aliases spriteOffset {0,0} spriteSize {182,217} spriteSourceSize {182,217} textureRect {{1296,0},{182,217}} textureRotated CharImage_014.png aliases spriteOffset {0,0} spriteSize {270,217} spriteSourceSize {270,217} textureRect {{933,434},{270,217}} textureRotated CharImage_015.png aliases spriteOffset {0,0} spriteSize {315,217} spriteSourceSize {315,217} textureRect {{1502,434},{315,217}} textureRotated CharImage_016.png aliases spriteOffset {0,0} spriteSize {153,217} spriteSourceSize {153,217} textureRect {{484,0},{153,217}} textureRotated CharImage_017.png aliases spriteOffset {0,0} spriteSize {191,217} spriteSourceSize {191,217} textureRect {{186,217},{191,217}} textureRotated CharImage_018.png aliases spriteOffset {0,0} spriteSize {229,217} spriteSourceSize {229,217} textureRect {{447,434},{229,217}} textureRotated CharImage_019.png aliases spriteOffset {0,0} spriteSize {209,217} spriteSourceSize {209,217} textureRect {{977,217},{209,217}} textureRotated CharImage_020.png aliases spriteOffset {0,0} spriteSize {157,217} spriteSourceSize {157,217} textureRect {{637,0},{157,217}} textureRotated CharImage_021.png aliases spriteOffset {0,0} spriteSize {195,217} spriteSourceSize {195,217} textureRect {{377,217},{195,217}} textureRotated CharImage_022.png aliases spriteOffset {0,0} spriteSize {185,217} spriteSourceSize {185,217} textureRect {{1478,0},{185,217}} textureRotated CharImage_023.png aliases spriteOffset {0,0} spriteSize {218,217} spriteSourceSize {218,217} textureRect {{0,434},{218,217}} textureRotated CharImage_024.png aliases spriteOffset {0,0} spriteSize {257,217} spriteSourceSize {257,217} textureRect {{676,434},{257,217}} textureRotated CharImage_025.png aliases spriteOffset {0,0} spriteSize {229,217} spriteSourceSize {229,217} textureRect {{218,434},{229,217}} textureRotated CharImage_026.png aliases spriteOffset {0,0} spriteSize {173,217} spriteSourceSize {173,217} textureRect {{1123,0},{173,217}} textureRotated CharImage_027.png aliases spriteOffset {0,0} spriteSize {216,217} spriteSourceSize {216,217} textureRect {{1610,217},{216,217}} textureRotated metadata format 3 pixelFormat RGBA8888 premultiplyAlpha realTextureFileName GoldLetterAlphabet.png.png size {1826,651} smartupdate $TexturePacker:SmartUpdate:fe5424a4b480ef76ea4dba00751e813d:e17928c76e957c872b5c8c905f73e949:1f6fb27ef0b0da7790b28c03eb8fa82b$ textureFileName GoldLetterAlphabet.png.png [/cc]

However, Cocos2d-x likes FNT files instead of PLIST files so we have to convert this. I’ve developed a quick and dirty little program (plist2fnt.cpp) to convert PLIST –> FNT file. This program requires the files to be named CharImage_%03d.png”. Here’s the program:

[cc lang=”c++” tabsize=”8″ lines=”20″ width=”600″] #include #include #include #include #include #include “tinyxml2.h” using namespace tinyxml2; using namespace std; typedef long unsigned addr; int chrcnt=0; char chridx[512]; const char* indentstr=” “; int idsz=50; int indent=0; void ind(){ printf(“%s”,indentstr+idsz-indent); }; #define cp(n) printf(“CheckPoint(%d)\n”,n); //—————————————————————————– // BMFont routines. //—————————————————————————– typedef struct INFO{ const char* name; int size; bool bold; bool italic; }t_bmfont_info; typedef struct COMMON{ int lineHeight; int base; int scaleW; int scaleH; }t_bmfont_common; typedef struct PAGE{ const char* name; }t_bmfont_page; typedef struct COUNT{ int chars; }t_bmfont_count; typedef struct CHAR { char chr; int ofsx; int ofsy; int w; int h; int yoffset; }t_bmfont_chr; typedef struct KERNING{ int dummy; }t_bmfont_kern; t_bmfont_info bmfont_info; t_bmfont_common bmfont_common; t_bmfont_page bmfont_page; t_bmfont_count bmfont_count; t_bmfont_chr bmfont_chr; class mycomparison { bool reverse; public: bool operator() (t_bmfont_chr& lhs, t_bmfont_chr& rhs) const { return (lhs.chr>rhs.chr); } }; typedef std::priority_queue,mycomparison> mypq_type; mypq_type myqueue; void init_BMFont(){ bmfont_info.name=”Unknown”; bmfont_info.size=0; bmfont_info.bold=0; bmfont_info.italic=0; bmfont_common.lineHeight=0; bmfont_common.base=0; bmfont_common.scaleW=0; bmfont_common.scaleH=0; bmfont_page.name=”Unknown”; bmfont_count.chars=0; bmfont_chr.chr=0; bmfont_chr.ofsx=0; bmfont_chr.ofsy=0; bmfont_chr.w=0; bmfont_chr.h=0; bmfont_chr.yoffset=0; } void put_BMFont_info(const char *face,int size,bool bold,bool italic) { fprintf(stderr,”info face=\”%s\” size=%d bold=%d italic=%d charset=\”\” unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1\n”, face,size,(bold)?1:0,(italic)?1:0); } void put_BMFont_common(int lineHeight,int base,int scaleW,int scaleH) { fprintf(stderr,”common lineHeight=%d base=%d scaleW=%d scaleH=%d pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0\n”, lineHeight,base,scaleW,scaleH); } void put_BMFont_page(const char* pngName) { fprintf(stderr,”page id=0 file=\”%s\”\n”,pngName); } void put_BMFont_count(int chars) { fprintf(stderr,”chars count=%d\n”,chars); } void put_BMFont_char(char chr,int ofsx,int ofsy,int w,int h,int yoffset) { fprintf(stderr,”char id=%d x=%d y=%d width=%d height=%d xoffset=0 yoffset=%d xadvance=%d page=0 chnl=0 letter=\”%c\”\n”, chr,ofsx,ofsy,w,h,yoffset,w+1,chr); } void put_BMFont_end(int kerns) { fprintf(stderr,”kernings count=0\n”); } bool iskey(XMLNode* node) { return (strncmp(“key”,node->Value(),3)==0)?true:false; }; bool isdict(XMLNode* node) { return (strncmp(“dict”,node->Value(),4)==0)?true:false; }; bool isarray(XMLNode* node) { return (strncmp(“array”,node->Value(),5)==0)?true:false; }; bool isstr(XMLNode* node) { return (strncmp(“string”,node->Value(),6)==0)?true:false; }; bool istrue(XMLNode* node) { return (strncmp(“true”,node->Value(),4)==0)?true:false; }; bool isfalse(XMLNode* node) { return (strncmp(“false”,node->Value(),5)==0)?true:false; }; const char* keytext(XMLNode* node) { auto elem=node->ToElement(); assert((elem!=nullptr)||(!”ERROR: node was not XMLElement”)); return(elem->ToElement()->GetText()); }; const char* get_keytext(XMLNode* node) { const char* rtn=””; if(iskey(node)){ rtn=keytext(node); } return(rtn); }; XMLNode* get_keyvalue(XMLNode* node) { node=node->NextSibling(); return(node); }; void get_dict(XMLNode* dict) { const char* lastkey=”none”; indent+=2; auto child=dict->FirstChildElement(“key”); while(child!=nullptr){ auto keytxt=get_keytext(child); ind(); printf(“key=’%s’\n”,keytxt); if(strncmp(“CharImage”,keytxt,6)==0){ int idx; chrcnt++; char buff[100]; strncpy(buff,keytxt,99); char* token=strtok(buff,”_.”); printf(“token=’%s’\n”,token); token=strtok(nullptr,”_.”); printf(“token=’%s'(%d)\n”,token,atoi(token)); if(atoi(token)==90){ bmfont_chr.chr=’?’; }else{ bmfont_chr.chr=chridx[atoi(token)-1]; } printf(“bmfont_chr.chr=’%c(%d)’\n”,bmfont_chr.chr,bmfont_chr.chr); } lastkey=keytxt; auto keyvalue=get_keyvalue(child); if(isdict(keyvalue)){ ind();printf(“dict\n”); get_dict(keyvalue); }else if(isarray(keyvalue)){ ind();printf(“array\n”); }else if(isstr(keyvalue)){ const char* kv=keytext(keyvalue); ind();printf(“str:’%s’\n”,kv); if(strncmp(“textureRect”,lastkey,11)==0){ char buf[100]; strcpy(buf,kv); const char* flt=”{}, “; int x=0,y=0,w=0,h=0,yoffset=0; char* trp=strtok(buf,flt); x=atoi(trp); trp=strtok(nullptr,flt); y=atoi(trp); trp=strtok(nullptr,flt); w=atoi(trp); trp=strtok(nullptr,flt); h=atoi(trp); bmfont_common.lineHeight = max(bmfont_common.lineHeight,h); bmfont_chr.ofsx=x; bmfont_chr.ofsy=y; bmfont_chr.w=w; bmfont_chr.h=h; bmfont_chr.yoffset=yoffset; myqueue.push(bmfont_chr); } }else if(istrue(keyvalue)){ ind();printf(“true\n”); }else if(isfalse(keyvalue)){ ind();printf(“false\n”); }else{ ind();printf(“unknown\n”); } child=child->NextSiblingElement(“key”); } indent-=2; } void get_alphabet(const char* fn_alphabet) { FILE* fh=fopen(fn_alphabet,”r”); if(fh==nullptr){ perror(“Opening alpahbet file”); exit(1); }else{ int i=0; int chr=0; while((chr=getc(fh))!=EOF){ printf(“chr=0x%02x “,chr); if(chr>=’ ‘){ printf(“chridx[%d]=’%c’\n”,i,chr); chridx[i++]=(char)chr; } } chridx[i]=0; fclose(fh); } } void dump_alphabet() { printf(“Alphabet:”); for(int i=0;(i<511)&&(chridx[i]!=0);i++){ if((i%20)==0) printf("\n "); printf("%c",chridx[i]); } printf("\n"); } int main(int argc,char* argv[]){ const char* FontFile=argv[1]; const int FontSize=atoi(argv[2]); const int FontFileSzW=atoi(argv[3]); const int FontFileSzH=atoi(argv[4]); const char* AlphaFile="alphabet.txt"; char buff[100]; char fontname[30]; char pagename[35]; XMLDocument doc; get_alphabet(AlphaFile); dump_alphabet(); init_BMFont(); strcpy(buff,FontFile); char* token=strtok(buff,"."); sprintf(fontname,"%s",token); sprintf(pagename,"%s.png",token); bmfont_info.name=fontname; bmfont_page.name=pagename; doc.LoadFile(FontFile); //doc.LoadFile( "a.plist" ); //doc.SaveFile( "a.out.xml" ); // Get the documents first child to start things off. // First find the plist node. auto plist = doc.FirstChildElement("plist"); // At line 003 below if(plist!=nullptr){ // Once we have the plist look for the dict child auto dict=plist->FirstChildElement(“dict”); // At line 004 below if(dict!=nullptr){ printf(“Got dict\n”); // Now look at all keys in that dict. auto child=dict->FirstChildElement(“key”); // At line 005 or 023 below while(child!=nullptr){ // The one labelled “frames” is the one we want. if(strncmp(“frames”,child->GetText(),5)==0){ // This is the key at line 005 printf(” Key=’%s’\n”,child->GetText()); auto dict=child->NextSibling(); if(dict!=nullptr){ printf(“Got value=’%s’\n”,dict->Value()); get_dict(dict); // Process the dict(ionary) at line 008 to 022 } } child=child->NextSiblingElement(“key”); } } } put_BMFont_info(fontname,FontSize,false,false); put_BMFont_common(bmfont_common.lineHeight,25,FontFileSzW,FontFileSzH); put_BMFont_page(pagename); put_BMFont_count(chrcnt); while(!myqueue.empty()){ t_bmfont_chr bmfont_chr=myqueue.top(); put_BMFont_char(bmfont_chr.chr,bmfont_chr.ofsx,bmfont_chr.ofsy,bmfont_chr.w,bmfont_chr.h,bmfont_chr.yoffset); myqueue.pop(); } put_BMFont_end(0); } // 000 // 001 // 002 // 003 // 004 // 005 frames // 006 // 007 Letter_001.png // 008 // 009 aliases // 010 // 011 spriteOffset // 012 {0,0} // 013 spriteSize // 014 {89,115} // 015 spriteSourceSize // 016 {89,115} // 017 textureRect // 018 {{82,920},{89,115}} // 019 textureRotated // 020 // 021 // 022 // 023 metadata // 024 // 025 format // 026 3 // 027 pixelFormat // 028 RGBA8888 // 029 premultiplyAlpha // 030 // 031 realTextureFileName // 032 RoyalsGold.png // 033 size // 034 {505,1495} // 035 smartupdate // 036 $TexturePacker:SmartUpdate:bae076a86c994950c23db26de5b903c7:bca835b522c805074e19afa28009f159:13f59389bf20a876b22003b1a56f184c$ // 037 textureFileName // 038 RoyalsGold.png // 039 // 040 // 041 [/cc]

The plist2fnt program can be run with the following grammar:

plist2fnt <filename>.plist <font-size> <pngfile-width> <pngfile-height>

For example for my GoldLetterAlphabet sprite sheet which is 1826 x 621 pixels in dimension (cmd: file GoldLetterAlphabet.png):

plist2fnt GoldLetterAlphabet.plist 200 1826 621 2>GoldLetterAlphabet.fnt

NOTE: The FNT file output is to stderr so that needs to be captured instead of stdout.

And this is the resultant FNT file:

[cc lang=”text” tabsize=”8″ lines=”20″ width=”600″] info face=”GoldLetterAlphabet” size=200 bold=0 italic=0 charset=”” unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 common lineHeight=217 base=25 scaleW=1826 scaleH=651 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0 page id=0 file=”GoldLetterAlphabet.png” chars count=27 char id=32 x=0 y=0 width=50 height=217 xoffset=0 yoffset=0 xadvance=51 page=0 chnl=0 letter=” ” char id=65 x=1396 y=217 width=214 height=217 xoffset=0 yoffset=0 xadvance=215 page=0 chnl=0 letter=”A” char id=66 x=0 y=217 width=186 height=217 xoffset=0 yoffset=0 xadvance=187 page=0 chnl=0 letter=”B” char id=67 x=185 y=0 width=146 height=217 xoffset=0 yoffset=0 xadvance=147 page=0 chnl=0 letter=”C” char id=68 x=572 y=217 width=202 height=217 xoffset=0 yoffset=0 xadvance=203 page=0 chnl=0 letter=”D” char id=69 x=958 y=0 width=165 height=217 xoffset=0 yoffset=0 xadvance=166 page=0 chnl=0 letter=”E” char id=70 x=774 y=217 width=203 height=217 xoffset=0 yoffset=0 xadvance=204 page=0 chnl=0 letter=”F” char id=71 x=331 y=0 width=153 height=217 xoffset=0 yoffset=0 xadvance=154 page=0 chnl=0 letter=”G” char id=72 x=1203 y=434 width=299 height=217 xoffset=0 yoffset=0 xadvance=300 page=0 chnl=0 letter=”H” char id=73 x=50 y=0 width=135 height=217 xoffset=0 yoffset=0 xadvance=136 page=0 chnl=0 letter=”I” char id=74 x=794 y=0 width=164 height=217 xoffset=0 yoffset=0 xadvance=165 page=0 chnl=0 letter=”J” char id=75 x=1186 y=217 width=210 height=217 xoffset=0 yoffset=0 xadvance=211 page=0 chnl=0 letter=”K” char id=76 x=1296 y=0 width=182 height=217 xoffset=0 yoffset=0 xadvance=183 page=0 chnl=0 letter=”L” char id=77 x=933 y=434 width=270 height=217 xoffset=0 yoffset=0 xadvance=271 page=0 chnl=0 letter=”M” char id=78 x=1502 y=434 width=315 height=217 xoffset=0 yoffset=0 xadvance=316 page=0 chnl=0 letter=”N” char id=79 x=484 y=0 width=153 height=217 xoffset=0 yoffset=0 xadvance=154 page=0 chnl=0 letter=”O” char id=80 x=186 y=217 width=191 height=217 xoffset=0 yoffset=0 xadvance=192 page=0 chnl=0 letter=”P” char id=81 x=447 y=434 width=229 height=217 xoffset=0 yoffset=0 xadvance=230 page=0 chnl=0 letter=”Q” char id=82 x=977 y=217 width=209 height=217 xoffset=0 yoffset=0 xadvance=210 page=0 chnl=0 letter=”R” char id=83 x=637 y=0 width=157 height=217 xoffset=0 yoffset=0 xadvance=158 page=0 chnl=0 letter=”S” char id=84 x=377 y=217 width=195 height=217 xoffset=0 yoffset=0 xadvance=196 page=0 chnl=0 letter=”T” char id=85 x=1478 y=0 width=185 height=217 xoffset=0 yoffset=0 xadvance=186 page=0 chnl=0 letter=”U” char id=86 x=0 y=434 width=218 height=217 xoffset=0 yoffset=0 xadvance=219 page=0 chnl=0 letter=”V” char id=87 x=676 y=434 width=257 height=217 xoffset=0 yoffset=0 xadvance=258 page=0 chnl=0 letter=”W” char id=88 x=218 y=434 width=229 height=217 xoffset=0 yoffset=0 xadvance=230 page=0 chnl=0 letter=”X” char id=89 x=1123 y=0 width=173 height=217 xoffset=0 yoffset=0 xadvance=174 page=0 chnl=0 letter=”Y” char id=90 x=1610 y=217 width=216 height=217 xoffset=0 yoffset=0 xadvance=217 page=0 chnl=0 letter=”Z” kernings count=0 [/cc]

Next:

Now that we have the sprite-sheet and it’s corresponding FNT file generated we go on to generating a Cocos2d-x (v3.x) project that uses this bitmap file.

Combining Letter Images

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top