• 关于cocos2dx 2.x CCLabelBMFont的解析优化


      因为系统字体已经没办法满足项目的需求,需要用一个新的字体,但由于担心字体版权等问题,因为改用通用做法,做一套全字体的BMFont ,全字体9千多个汉字和其他符号,还好2048X2048堆下来了,只用了19号字体,而且万幸经过压缩后,图片也没有多大了。

      但是问题来了,在安卓上,首次引用到这个字体时,字体解析居然花了10多秒。

      打开.fnt文件

    info face="STHeitiSC-Light" size=19 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1
    common lineHeight=26 base=19 scaleW=2048 scaleH=2048 pages=1 packed=0
    page id=0 file="youyuan.png"
    chars count=9237
    char id=12298 x=1 y=1 width=11 height=21 xoffset=10 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
    char id=12299 x=13 y=1 width=11 height=21 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
    char id=12300 x=25 y=1 width=6 height=21 xoffset=12 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
    char id=12301 x=32 y=1 width=6 height=21 xoffset=2 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
    char id=65288 x=39 y=1 width=6 height=21 xoffset=12 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
    char id=65289 x=46 y=1 width=6 height=21 xoffset=2 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
    char id=124 x=53 y=1 width=2 height=21 xoffset=4 yoffset=3 xadvance=9 page=0 chnl=0 letter="|"
    char id=30306 x=77 y=1 width=20 height=20 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0 letter=""
    char id=30221 x=98 y=1 width=20 height=20 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0 letter=""
    char id=23518 x=119 y=1 width=19 height=20 xoffset=1 yoffset=1 xadvance=19 page=0 chnl=0 letter=""
    char id=22815 x=139 y=1 width=19 height=20 xoffset=1 yoffset=1 xadvance=19 page=0 chnl=0 letter=""

    查看CCLabelBMFont.cpp,查看其解析函数

     1 std::set<unsigned int>* CCBMFontConfiguration::parseConfigFile(const char *controlFile)
     2 {    
     3     std::string fullpath = CCFileUtils::sharedFileUtils()->fullPathForFilename(controlFile);
     4     CCString *contents = CCString::createWithContentsOfFile(fullpath.c_str());
     5 
     6     CCAssert(contents, "CCBMFontConfiguration::parseConfigFile | Open file error.");
     7     
     8     set<unsigned int> *validCharsString = new set<unsigned int>();
     9 
    10     if (!contents)
    11     {
    12         CCLOG("cocos2d: Error parsing FNTfile %s", controlFile);
    13         return NULL;
    14     }
    15 
    16     // parse spacing / padding
    17     std::string line;
    18     std::string strLeft = contents->getCString();
    19     while (strLeft.length() > 0)
    20     {
    21         int pos = strLeft.find('
    ');
    22 
    23         if (pos != (int)std::string::npos)
    24         {
    25             // the data is more than a line.get one line
    26             line = strLeft.substr(0, pos);
    27             strLeft = strLeft.substr(pos + 1);
    28         }
    29         else
    30         {
    31             // get the left data
    32             line = strLeft;
    33             strLeft.erase();
    34         }
    35 
    36         if(line.substr(0,strlen("info face")) == "info face") 
    37         {
    38             // XXX: info parsing is incomplete
    39             // Not needed for the Hiero editors, but needed for the AngelCode editor
    40             //            [self parseInfoArguments:line];
    41             this->parseInfoArguments(line);
    42         }
    43         // Check to see if the start of the line is something we are interested in
    44         else if(line.substr(0,strlen("common lineHeight")) == "common lineHeight")
    45         {
    46             this->parseCommonArguments(line);
    47         }
    48         else if(line.substr(0,strlen("page id")) == "page id")
    49         {
    50             this->parseImageFileName(line, controlFile);
    51         }
    52         else if(line.substr(0,strlen("chars c")) == "chars c")
    53         {
    54             // Ignore this line
    55         }
    56         else if(line.substr(0,strlen("char")) == "char")
    57         {
    58             // Parse the current line and create a new CharDef
    59             tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
    60             this->parseCharacterDefinition(line, &element->fontDef);
    61 
    62             element->key = element->fontDef.charID;
    63             HASH_ADD_INT(m_pFontDefDictionary, key, element);
    64             
    65             validCharsString->insert(element->fontDef.charID);
    66         }
    67 //        else if(line.substr(0,strlen("kernings count")) == "kernings count")
    68 //        {
    69 //            this->parseKerningCapacity(line);
    70 //        }
    71         else if(line.substr(0,strlen("kerning first")) == "kerning first")
    72         {
    73             this->parseKerningEntry(line);
    74         }
    75     }
    76     
    77     return validCharsString;
    78 }

    执行最多次数的应该是这里:

     1  else if(line.substr(0,strlen("char")) == "char")
     2         {
     3             // Parse the current line and create a new CharDef
     4             tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
     5             this->parseCharacterDefinition(line, &element->fontDef);
     6 
     7             element->key = element->fontDef.charID;
     8             HASH_ADD_INT(m_pFontDefDictionary, key, element);
     9             
    10             validCharsString->insert(element->fontDef.charID);
    11         }

    继续跟踪:parseCharacterDefinition() 这个函数

     1 void CCBMFontConfiguration::parseCharacterDefinition(std::string line, ccBMFontDef *characterDefinition)
     2 {    
     3     //////////////////////////////////////////////////////////////////////////
     4     // line to parse:
     5     // char id=32   x=0     y=0     width=0     height=0     xoffset=0     yoffset=44    xadvance=14     page=0  chnl=0 
     6     //////////////////////////////////////////////////////////////////////////
     7 
     8     // Character ID
     9     int index = line.find("id=");
    10     int index2 = line.find(' ', index);
    11     std::string value = line.substr(index, index2-index);
    12     sscanf(value.c_str(), "id=%u", &characterDefinition->charID);
    13 
    14     // Character x
    15     index = line.find("x=");
    16     index2 = line.find(' ', index);
    17     value = line.substr(index, index2-index);
    18     sscanf(value.c_str(), "x=%f", &characterDefinition->rect.origin.x);
    19     // Character y
    20     index = line.find("y=");
    21     index2 = line.find(' ', index);
    22     value = line.substr(index, index2-index);
    23     sscanf(value.c_str(), "y=%f", &characterDefinition->rect.origin.y);
    24     // Character width
    25     index = line.find("width=");
    26     index2 = line.find(' ', index);
    27     value = line.substr(index, index2-index);
    28     sscanf(value.c_str(), "width=%f", &characterDefinition->rect.size.width);
    29     // Character height
    30     index = line.find("height=");
    31     index2 = line.find(' ', index);
    32     value = line.substr(index, index2-index);
    33     sscanf(value.c_str(), "height=%f", &characterDefinition->rect.size.height);
    34     // Character xoffset
    35     index = line.find("xoffset=");
    36     index2 = line.find(' ', index);
    37     value = line.substr(index, index2-index);
    38     sscanf(value.c_str(), "xoffset=%hd", &characterDefinition->xOffset);
    39     // Character yoffset
    40     index = line.find("yoffset=");
    41     index2 = line.find(' ', index);
    42     value = line.substr(index, index2-index);
    43     sscanf(value.c_str(), "yoffset=%hd", &characterDefinition->yOffset);
    44     // Character xadvance
    45     index = line.find("xadvance=");
    46     index2 = line.find(' ', index);
    47     value = line.substr(index, index2-index);
    48     sscanf(value.c_str(), "xadvance=%hd", &characterDefinition->xAdvance);
    49 }

    居然全部为字符串一行一行的解析,因为肯定是会很慢的。

    查看.fnt文件 ,其char 解析部分,全部都是固定格式的。因此思路很简单,修改.fnt文件 ,改为二进制解析,这样肯定可以提升很高的效率。

    当然首先要做的是一个转换工具,将.fnt 文件转化为二进制文件。这里,我做的并不是所有的数据都转为二进制,因为时间关系,也没过多的去研究其他的字符。因此,我做的是保留其他所有的符为原本的内容,只修改char id = 这些项的格式

    为了确保原本的.fnt 文件还继续能读,新生成的.fnt 文件中加入了标记“type=sxbmfont”

    修改fnt文件代码,这里只贴出关键代码,其他部分我用的是MFC简单写的,就不贴出来了

    结构体格式定义:

    1 struct BMFontCharNode
    2 {
    3     int id;
    4     int x,y;
    5     int width,height;
    6     int xoffset,yoffset;
    7     int xadvance;
    8 };
    9 typedef struct  BMFontCharNode BMFontNode;

    这是我的MFC点击编码时:

     1 void CtxtToBinDlg::OnBnClickedEncode()
     2 {
     3     // TODO: 在此添加控件通知处理程序代码
     4     if (mOutContent == "")
     5     {
     6         MessageBox("请先选择输出目录");
     7         return;
     8     }
     9     CString path = mOutContent + "/" +  m_fileName;
    10     fstream fin(m_filePath);
    11     const int LINE_LENGTH = 150; 
    12     char str[LINE_LENGTH];  
    13     memset(str,0,LINE_LENGTH);
    14     fstream fout(path,ios::out); 
    15     fout.write("type=sxbmfont
    ",strlen("type=sxbmfont
    "));
    16     for ( int i=0; i < 4 ; i ++)
    17     {
    18         if(fin.getline(str,LINE_LENGTH) )
    19         {   
    20             for (int j=0;j<LINE_LENGTH;j++)
    21                 if (str[j] < 0)
    22                     str[j] = 0;
    23             CString str1 = CString(str) + "
    ";
    24             if (str1.Left(strlen("char id=")) != "char id=")
    25             {
    26                 fout.write(str1.GetBuffer(),str1.GetLength());
    27             }
    28             else
    29             {
    30                 break;
    31             }
    32             memset(str,0,LINE_LENGTH);
    33         }
    34     }
    35     fout.close();
    36     
    37 
    38     fstream binary_file(path,ios::out|ios::binary|ios::app); 
    39     while( fin.getline(str,LINE_LENGTH) )
    40     {   
    41         for (int j=0;j<LINE_LENGTH;j++)
    42             if (str[j] < 0)
    43                 str[j] = 0;
    44         CString str1 = str;
    45         if (str1.Left(strlen("char id=")) == "char id=")
    46         {
    47             BMFontNode t;
    48             sscanf_s(str1.GetBuffer(), "char id=%d x=%d y=%d width=%d height=%d xoffset=%d yoffset=%d xadvance=%d",
    49                 &t.id,&t.x,&t.y,&t.width,&t.height,&t.xoffset,&t.yoffset,&t.xadvance);
    50             // int pase = 1;
    51             binary_file.write(reinterpret_cast<char *>(&t),sizeof(t));
    52         }
    53         memset(str,0,LINE_LENGTH);
    54     }
    55     binary_file.close();
    56     if(!isContent)
    57         MessageBox("生成成功!");
    58 }

    生成新的.fnt文件事,需要修改CCLabelBMFont 来读取

    修改的部分如下:

    在头文件加入新的结构体定义:(与保存时的结构一致)

    CCLabelBMFont.h中加入结构定义

    1 struct BMFontCharNode
    2 {
    3     int id;
    4     int x,y;
    5     int width,height;
    6     int xoffset,yoffset;
    7     int xadvance;
    8 };
    9 typedef struct  BMFontCharNode BMFontNode;

    修改CCLabelBMFont.cpp

    添加重载函数(重载char id = 行的解析)

     1 void CCBMFontConfiguration::parseCharacterDefinition(BMFontNode *node, ccBMFontDef *characterDefinition)
     2 {
     3     characterDefinition->charID = node->id;
     4     characterDefinition->rect.origin.x = node->x;
     5     characterDefinition->rect.origin.y = node->y;
     6     characterDefinition->rect.size.width = node->width;
     7     characterDefinition->rect.size.height = node->height;
     8     characterDefinition->xOffset = node->xoffset;
     9     characterDefinition->yOffset = node->yoffset;
    10     characterDefinition->xAdvance = node->xadvance;
    11 }

    修改解析函数:

      1 std::set<unsigned int>* CCBMFontConfiguration::parseConfigFile(const char *controlFile)
      2 { 
      3     /* 
      4      * 修改数据解析方式  
      5      * 
      6      */
      7     std::string fullpath = CCFileUtils::sharedFileUtils()->fullPathForFilename(controlFile);
      8     
      9     set<unsigned int> *validCharsString = new set<unsigned int>();
     10 
     11 
     12     CCString *contents = CCString::createWithContentsOfFile(fullpath.c_str());
     13     char contentstr[20];
     14     memcpy(contentstr,contents->getCString(),strlen("type=sxbmfont"));
     15     contentstr[strlen("type=sxbmfont")] = 0;
     16     if (strcmp(contentstr,"type=sxbmfont"))
     17     {
     18         CCAssert(contents, "CCBMFontConfiguration::parseConfigFile | Open file error.");
     19         // parse spacing / padding
     20         std::string line;
     21         std::string strLeft = contents->getCString();
     22         while (strLeft.length() > 0)
     23         {
     24             int pos = strLeft.find('
    ');
     25 
     26             if (pos != (int)std::string::npos)
     27             {
     28                 // the data is more than a line.get one line
     29                 line = strLeft.substr(0, pos);
     30                 strLeft = strLeft.substr(pos + 1);
     31             }
     32             else
     33             {
     34                 // get the left data
     35                 line = strLeft;
     36                 strLeft.erase();
     37             }
     38             if(line.substr(0,strlen("info face")) == "info face") 
     39             {
     40                 // XXX: info parsing is incomplete
     41                 // Not needed for the Hiero editors, but needed for the AngelCode editor
     42                 //            [self parseInfoArguments:line];
     43                 this->parseInfoArguments(line);
     44             }
     45             // Check to see if the start of the line is something we are interested in
     46             else if(line.substr(0,strlen("common lineHeight")) == "common lineHeight")
     47             {
     48                 this->parseCommonArguments(line);
     49             }
     50             else if(line.substr(0,strlen("page id")) == "page id")
     51             {
     52                 this->parseImageFileName(line, controlFile);
     53             }
     54             else if(line.substr(0,strlen("chars c")) == "chars c")
     55             {
     56                 // Ignore this line
     57             }
     58             else if(line.substr(0,strlen("char")) == "char")
     59             {
     60                 // Parse the current line and create a new CharDef
     61                 tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
     62                 this->parseCharacterDefinition(line, &element->fontDef);
     63 
     64                 element->key = element->fontDef.charID;
     65                 HASH_ADD_INT(m_pFontDefDictionary, key, element);
     66 
     67                 validCharsString->insert(element->fontDef.charID);
     68             }
     69 
     70             else if(line.substr(0,strlen("kerning first")) == "kerning first")
     71             {
     72                 this->parseKerningEntry(line);
     73             }
     74         }
     75 
     76     }
     77     else
     78     {
     79         unsigned long len = 0;
     80         unsigned char* data = CCFileUtils::sharedFileUtils()->getFileData(fullpath.c_str(), "rb", &len);      
     81         const int LINE_LENGTH = 150; 
     82         char str[LINE_LENGTH];  
     83         int nodeSize = sizeof(BMFontNode);
     84         int index = 0;
     85         int count = 0;
     86         for(int i=0 ; i <  len ; i++)
     87         {
     88             if(data[i] == '
    ')
     89             {
     90                 memcpy(str,data+index,i-index+1);
     91                 str[i-index+1] = 0;
     92                 std::string line = str;
     93                 if(line.substr(0,strlen("info face")) == "info face") 
     94                 {
     95                     this->parseInfoArguments(line);
     96                 }
     97                 // Check to see if the start of the line is something we are interested in
     98                 else if(line.substr(0,strlen("common lineHeight")) == "common lineHeight")
     99                 {
    100                     this->parseCommonArguments(line);
    101                 }
    102                 else if(line.substr(0,strlen("page id")) == "page id")
    103                 {
    104                     this->parseImageFileName(line, controlFile);
    105                 }
    106                 else if(line.substr(0,strlen("chars c")) == "chars c")
    107                 {
    108                     // Ignore this line
    109                 }
    110                 else if(line.substr(0,strlen("char")) == "char")
    111                 {
    112                     // Parse the current line and create a new CharDef
    113                     tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
    114                     this->parseCharacterDefinition(line, &element->fontDef);
    115 
    116                     element->key = element->fontDef.charID;
    117                     HASH_ADD_INT(m_pFontDefDictionary, key, element);
    118 
    119                     validCharsString->insert(element->fontDef.charID);
    120                 }
    121                 else if(line.substr(0,strlen("kerning first")) == "kerning first")
    122                 {
    123                     this->parseKerningEntry(line);
    124                 }
    125                 index = i+1;
    126                 count ++;
    127                 if(count == 5)
    128                     break;
    129             }
    130         
    131         }
    132         while(index < len)
    133         {
    134             BMFontNode t;
    135             memcpy(&t,data+index,nodeSize);
    136             index = index + nodeSize;
    137             tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
    138             this->parseCharacterDefinition(&t, &element->fontDef);
    139 
    140             element->key = element->fontDef.charID;
    141             HASH_ADD_INT(m_pFontDefDictionary, key, element);
    142 
    143             validCharsString->insert(element->fontDef.charID);
    144         }
    145     }
    146 
    147 
    148 
    149     return validCharsString;
    150 }

     至此优化完成。修改后,这样解析过程应该基本上不怎么消耗时间了,因为没有过多的字符解析

    Stay hungry, stay foolish!
  • 相关阅读:
    WPF动态加载3D 放大-旋转-平移
    WPF窗口继承实现统一风格的自定义窗口
    桌面程序的其他实现方式----使用WPF窗体展现网页
    WPF 中自定义控件及其使用
    flask系列三之Jinja2模板
    Python模块学习
    利用Flask-SQLAlchemy提供的paginate()方法实现博客文章的分页显示
    Python利用flask sqlalchemy实现分页效果
    sqlalchemy和flask-sqlalchemy几种分页操作
    Python SQLAlchemy ORM示例
  • 原文地址:https://www.cnblogs.com/JhonKing/p/5141668.html
Copyright © 2020-2023  润新知