cocos2dx spine之一 :spine变色

cocos2dx版本为3.10

1.具体原理和代码可以参考博文《利用shader改变图片色相Hue》,下面的代码根据该博文进行整理优化。

基本原理就是将RGB值转换为HSL值后加上输入的HSL值,再转换为RGB值。

 

2.spine变色的思路有三种:

①spine::SkeletonAnimation调用shader

②读取spine对应的atlas文件,分析该文件得到所需的png图片,将该图片读入内存,修改内存中像素颜色,然后生成texture赋值给spine中的spAtlas->pages->rendererObject

③读取spine对应的atlas文件,分析该文件得到所需的png图片,将该图片读入内存,修改内存中像素颜色,保存为新的png图片;读取atlas文件,修改里面png名字为新png图片名字,保存为新的atlas文件;spine创建时使用新的png图片和新的atlas文件;

以上三种方法都可行,但在实现过程中我选择使用了第③种方法;

第①种方法简单易用,但是打包到一些低端的android手机时发现掉帧很厉害,所以才会有第②和第③种方法。

第②种方法经过测试也是可行的,但是破坏了spine的正常创建流程,导致对源码的修改过于复杂容易导致各种问题。

第③种方法独立在正常创建spine流程之前,更稳当可控。

 

3.根据资料色相H值的范围在[0,360],饱和度S值的范围在[0,1],明度L值的范围在[0,1];在PhotoShop中(快捷键ctrl+u)可调值范围为色相H[-180,180],饱和度A[-100,100],明度I[-100,100];

所以在代码中,H值不进行转换,S值和L值都除以100;在实际测试过程中发现,PS调试出来的效果和程序得到的效果在S/L不为0的情况下还是有区别的,具体原因不懂,估计可能是算法不一致吧。

 

4.第①种方法shader

①shader代码

1 #ifdef GL_ES
 2         precision mediump float;
 3 #endif
 4         varying vec2 v_texCoord;
 5         uniform float u_dH;
 6         uniform float u_dS;
 7         uniform float u_dL;
 8         void main() { 
 9             vec4 texColor = texture2D(CC_Texture0, v_texCoord); 
10             float r = texColor.r; 
11             float g = texColor.g; 
12             float b = texColor.b; 
13             float a = texColor.a; 
14             //convert rgb to hsl 
15             float h; 
16             float s; 
17             float l; 
18             { 
19                 float max = max(max(r, g), b); 
20                 float min = min(min(r, g), b); 
21                 //----h 
22                 if (max == min){ 
23                     h = 0.0; 
24                 } 
25                 else if (max == r&&g >= b){ 
26                     h = 60.0*(g - b) / (max - min) + 0.0; 
27                 } 
28                 else if (max == r&&g < b){ 
29                     h = 60.0*(g - b) / (max - min) + 360.0; 
30                 } 
31                 else if (max == g){ 
32                     h = 60.0*(b - r) / (max - min) + 120.0; 
33                 } 
34                 else if (max == b){ 
35                     h = 60.0*(r - g) / (max - min) + 240.0; 
36                 } 
37                 //----l 
38                 l = 0.5*(max + min); 
39                 //----s 
40                 if (l == 0.0 || max == min){ 
41                     s = 0.0; 
42                 } 
43                 else if (0.0 <= l&&l <= 0.5){ 
44                     s = (max - min) / (2.0*l); 
45                 } 
46                 else if (l > 0.5){ 
47                     s = (max - min) / (2.0 - 2.0*l); 
48                 } 
49             } 
50             //(h,s,l)+(dH,dS,dL) -> (h,s,l) 
51             h = h + u_dH; 
52             s = min(1.0, max(0.0, s + u_dS)); 
53             l = l + u_dL; 
54             //convert (h,s,l) to rgb and got final color 
55             vec4 finalColor; 
56             { 
57                 float q; 
58                 if (l < 0.5){ 
59                     q = l*(1.0 + s); 
60                 } 
61                 else if (l >= 0.5){ 
62                     q = l + s - l*s; 
63                 } 
64                 float p = 2.0*l - q; 
65                 float hk = h / 360.0; 
66                 
67                 float t[3]; 
68                 t[0] = hk + 1.0 / 3.0; 
69                 t[1] = hk; 
70                 t[2] = hk - 1.0 / 3.0;  
71                 
72                 float c[3]; 
73                 for (int i = 0; i < 3; i++){ 
74                     if (t[i] < 0.0)t[i] += 1.0; 
75                     if (t[i] > 1.0)t[i] -= 1.0; 
76                     
77                     if (t[i] < 1.0 / 6.0){ 
78                         c[i] = p + ((q - p)*6.0*t[i]); 
79                     } 
80                     else if (1.0 / 6.0 <= t[i] && t[i] < 0.5){ 
81                         c[i] = q; 
82                     } 
83                     else if (0.5 <= t[i] && t[i] < 2.0 / 3.0){ 
84                         c[i] = p + ((q - p)*6.0*(2.0 / 3.0 - t[i])); 
85                     } 
86                     else{ 
87                         c[i] = p; 
88                     }
89                 } 
90                 finalColor = vec4(c[0], c[1], c[2], a); 
91             } 
92             finalColor += vec4(u_dL, u_dL, u_dL, 0.0); 
93             gl_FragColor = finalColor; 
94         }

②调用代码

 

1 auto glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_vert, shader_content);//如果是Sprite使用这个Shader,则需要换为ccPositionTextureColor_noMVP_vert
2 GLProgramState* glState = GLProgramState::getOrCreateWithGLProgram(glprogram);
3 
4 glState->setUniformFloat("u_dH", h);
5 glState->setUniformFloat("u_dS", s / 100);
6 glState->setUniformFloat("u_dL", l / 100);
7 
8 skeleton_animation->setGLProgramState(glState);

 

 

 

5.第③种方法创建新的png文件和atlas文件

①创建新png文件函数(这里的函数实现和参考博文《利用shader改变图片色相Hue》的实现不一样,参考博文只传了一个H值,这里是直接根据shader进行的移植修改)

 

1 Image* create_new_image_hsl(Image* image, float u_dH, float u_dS, float u_dL){
  2         u_dH = u_dH;
  3         u_dS = u_dS / 100.0f;
  4         u_dL = u_dL / 100.0f;
  5 
  6         bool hasAlpha = image->hasAlpha();
  7         Texture2D::PixelFormat pixelFormat = image->getRenderFormat();
  8         int bit_per_pixel = image->getBitPerPixel(); //每像素多少位
  9         unsigned int length = image->getWidth()*image->getHeight();
 10 
 11         if (hasAlpha){
 12             //只处理了RGBA8888的格式
 13             if (pixelFormat == Texture2D::PixelFormat::RGBA8888){
 14                 unsigned int* inPixel32 = (unsigned int*)image->getData();
 15                 for (unsigned int i = 0; i < length; ++i, ++inPixel32)
 16                 {
 17                     unsigned char cr = (*inPixel32 >> 0) & 0xFF; // R
 18                     unsigned char cg = (*inPixel32 >> 8) & 0xFF; // G
 19                     unsigned char cb = (*inPixel32 >> 16) & 0xFF; // B
 20                     unsigned char ca = (*inPixel32 >> 24) & 0xFF; // A
 21                     //透明图层不做处理
 22                     if (ca){
 23                         float r = cr*1.0f / 0xFF;
 24                         float g = cg*1.0f / 0xFF;
 25                         float b = cb*1.0f / 0xFF;
 26 
 27                         float h = 0;
 28                         float s = 0;
 29                         float l = 0;
 30                         {
 31                             float max = r > g ? r : g;
 32                             max = max > b ? max : b;
 33 
 34                             float min = r < g ? r : g;
 35                             min = min < b ? min : b;
 36 
 37                             //----h
 38                             if (max == min){
 39                                 h = 0;
 40                             }
 41                             else if (max == r && g >= b){
 42                                 h = 60.0f*(g - b) / (max - min);
 43                             }
 44                             else if (max == r && g < b){
 45                                 h = 60.0f*(g - b) / (max - min) + 360.0f;
 46                             }
 47                             else if (max == g){
 48                                 h = 60.0f*(b - r) / (max - min) + 120.0f;
 49                             }
 50                             else if (max == b){
 51                                 h = 60.0f*(r - g) / (max - min) + 240.0f;
 52                             }
 53                             //----l
 54                             l = 0.5f*(max + min);
 55                             //----s
 56                             if (l == 0 || max == min){
 57                                 s = 0;
 58                             }
 59                             else if (0 <= l&&l <= 0.5f){
 60                                 s = (max - min) / (2.0f*l);
 61                             }
 62                             else if (l > 0.5f){
 63                                 s = (max - min) / (2.0f - 2.0f*l);
 64                             }
 65                         }
 66                         //(h,s,l)+(dH,dS,dL) -> (h,s,l)
 67                         h = h + u_dH;
 68                         s = 0 > s + u_dS ? 0 : s + u_dS;
 69                         s = s < 1.0f ? s : 1.0f;
 70 
 71                         l = l + u_dL;
 72                         //convert (h,s,l) to rgb and got final color
 73 
 74                         //vec4 finalColor;                
 75                         {
 76                             float q;
 77                             if (l < 0.5f){
 78                                 q = l*(1.0f + s);
 79                             }
 80                             else if (l >= 0.5f){
 81                                 q = l + s - l*s;
 82                             }
 83                             float p = 2.0f*l - q;
 84                             float hk = h / 360.0f;
 85 
 86                             float t[3] = {};
 87                             t[0] = hk + 1.0f / 3.0f;
 88                             t[1] = hk;
 89                             t[2] = hk - 1.0f / 3.0f;
 90 
 91                             float c[3] = {};
 92                             for (int i = 0; i < 3; i++){
 93                                 if (t[i] < 0){
 94                                     t[i] += 1.0f;
 95                                 }
 96                                 if (t[i] > 1.0f){
 97                                     t[i] -= 1.0f;
 98                                 }
 99 
100                                 if (t[i] < 1.0f / 6.0f){
101                                     c[i] = p + ((q - p)*6.0f*t[i]);
102                                 }
103                                 else if (1.0f / 6.0f <= t[i] && t[i] < 0.5f){
104                                     c[i] = q;
105                                 }
106                                 else if (0.5f <= t[i] && t[i] < 2.0f / 3.0f){
107                                     c[i] = p + ((q - p)*6.0f*(2.0f / 3.0f - t[i]));
108                                 }
109                                 else{
110                                     c[i] = p;
111                                 }
112                             }
113                             //finalColor = vec4(c[0], c[1], c[2], a);
114 
115                             r = c[0];
116                             g = c[1];
117                             b = c[2];
118                         }
119 
120                         //finalColor += vec4(u_dL, u_dL, u_dL, 0.0);
121                         r += u_dL;
122                         g += u_dL;
123                         b += u_dL;
124                         //限制rgb值的有效范围
125                         if (r > 1.0f) r = 1.0f;
126                         if (g > 1.0f) g = 1.0f;
127                         if (b > 1.0f) b = 1.0f;
128                         if (r < 0) r = 0;
129                         if (g < 0) g = 0;
130                         if (b < 0) b = 0;
131 
132                         unsigned char final_r = r * 0xFF;
133                         unsigned char final_g = g * 0xFF;
134                         unsigned char final_b = b * 0xFF;
135                         unsigned char final_a = ca;
136 
137 
138                         unsigned int val = final_a;
139                         val = val << 8;
140                         val |= final_b;
141                         val = val << 8;
142                         val |= final_g;
143                         val = val << 8;
144                         val |= final_r;
145 
146                         *inPixel32 = val;
147                     }
148                 }
149             }
150         }
151 
152         return image;
153     }

 

 

 

②分析atlas文件,得到对应的png文件,并生成新png文件和atlas文件

 

1 bool create_new_png_and_atlas(const char* atlas_name_no_extension, float u_dH, float u_dS, float u_dL)
  2     {
  3         bool ret = false;
  4 
  5         std::string atlas_name = atlas_name_no_extension;
  6         atlas_name += ".atlas";
  7         std::string atlas_file_full_name = FileUtils::getInstance()->fullPathForFilename(atlas_name);
  8         Data data = FileUtils::getInstance()->getDataFromFile(atlas_file_full_name);
  9 
 10         if (!data.isNull()){
 11             std::string writeable_path = FileUtils::getInstance()->getWritablePath();
 12 
 13             char* orginal_atlas_content = new char[data.getSize()]();
 14             memcpy(orginal_atlas_content, data.getBytes(), data.getSize());
 15 
 16             char sz_new_atlas_path[256] = {};
 17             sprintf(sz_new_atlas_path, "%s%s_%d_%d_%d.atlas", writeable_path.c_str(), atlas_name_no_extension, (int)u_dH, (int)u_dS, (int)u_dL);
 18 
 19             //为了保证大小,创建一个两倍大小的内存大小(这里可以适当减少大小)
 20             unsigned char* new_atlas_content = new unsigned char[2 * data.getSize()]();
 21             memset(new_atlas_content, 0, 2 * data.getSize());
 22 
 23             //分析得出png文件名字,可能有分为多个png的情况
 24             std::vector<std::string> png_no_extension_list;
 25             int line_start_idx = 0, line_end_idx = 0;
 26             int orginal_content_start_idx = 0, new_content_idx = 0, new_content_total_len = 0;
 27 
 28             for (int i = 0; i < data.getSize(); ++i){
 29                 line_end_idx = i;
 30 
 31                 //每一行判断是否为png
 32                 if (orginal_atlas_content[i] == '\n'){
 33                     int line_len = line_end_idx - line_start_idx;
 34 
 35                     //处理是图片的情况
 36                     if (line_len > 4 && orginal_atlas_content[line_end_idx - 4] == '.' &&
 37                         (orginal_atlas_content[line_end_idx - 3] == 'p' || orginal_atlas_content[line_end_idx - 3] == 'P') &&
 38                         (orginal_atlas_content[line_end_idx - 2] == 'n' || orginal_atlas_content[line_end_idx - 2] == 'N') &&
 39                         (orginal_atlas_content[line_end_idx - 1] == 'g' || orginal_atlas_content[line_end_idx - 1] == 'G')){
 40                         //将png行前面的数据拷贝进来
 41                         int cpy_len = line_start_idx - orginal_content_start_idx;
 42                         if (cpy_len > 0){
 43                             memcpy(new_atlas_content + new_content_idx, orginal_atlas_content + orginal_content_start_idx, cpy_len);
 44                             new_content_idx += cpy_len;
 45                             new_content_total_len += cpy_len;
 46                         }
 47 
 48                         //没有后缀的png的名字
 49                         char png_name_no_extension[256] = {};
 50                         memcpy(png_name_no_extension, orginal_atlas_content + line_start_idx, line_len - 4);
 51                         png_no_extension_list.push_back(png_name_no_extension);
 52 
 53                         //记录列表
 54                         char sz_new_png_name[256] = {};
 55                         sprintf(sz_new_png_name, "%s_%d_%d_%d.png\n", png_name_no_extension, (int)u_dH, (int)u_dS, (int)u_dL);
 56 
 57                         //将图片名字写入新内容
 58                         cpy_len = strlen(sz_new_png_name);
 59                         memcpy(new_atlas_content + new_content_idx, sz_new_png_name, cpy_len);
 60                         new_content_idx += cpy_len;
 61                         new_content_total_len += cpy_len;
 62 
 63                         orginal_content_start_idx = line_end_idx + 1;
 64                     }
 65 
 66                     line_start_idx = line_end_idx + 1;
 67                 }
 68             }
 69 
 70             //将最后一个png行到最后那部分数据也拷贝进来
 71             if (data.getSize() > orginal_content_start_idx){
 72                 int cpy_len = data.getSize() - orginal_content_start_idx;
 73                 memcpy(new_atlas_content + new_content_idx, orginal_atlas_content + orginal_content_start_idx, cpy_len);
 74                 new_content_total_len += cpy_len;
 75             }
 76 
 77 
 78             Data data;
 79             data.copy(new_atlas_content, new_content_total_len);
 80 
 81             if (!FileUtils::getInstance()->writeDataToFile(data, sz_new_atlas_path)){
 82                 cocos2d::log("========!!! save file %s error !!!========.", sz_new_atlas_path);
 83             }
 84             else{
 85                 ret = true;
 86             }
 87 
 88             data.clear();
 89 
 90             delete[]orginal_atlas_content;
 91             delete[]new_atlas_content;
 92 
 93             if (png_no_extension_list.size() <= 0){
 94                 cocos2d::log("========!!! png_name_list is empty error !!!========.");
 95                 return false;
 96             }
 97 
 98             if (!ret){
 99                 return false;
100             }
101 
102             //写入新图片
103             for (std::vector<std::string>::iterator it = png_no_extension_list.begin(); it != png_no_extension_list.end(); ++it){
104                 if (!ret){
105                     break;
106                 }
107 
108                 char sz_new_png_path[256] = {};
109                 sprintf(sz_new_png_path, "%s%s_%d_%d_%d.png", writeable_path.c_str(), it->c_str(), (int)u_dH, (int)u_dS, (int)u_dL);
110 
111                 char sz_png_name[256] = {};
112                 sprintf(sz_png_name, "%s.png", it->c_str());
113                 std::string png_file_full_name = FileUtils::getInstance()->fullPathForFilename(sz_png_name);
114 
115                 Image* image = new Image();
116                 if (image->initWithImageFile(png_file_full_name)){
117                     if (Image* new_image = create_new_image_hsl(image, u_dH, u_dS, u_dL)){
118                         if (!new_image->saveToFile(sz_new_png_path, false)){
119                             cocos2d::log("========!!! save file %s error !!!========.", sz_new_png_path);
120                             ret = false;
121                         }
122                     }
123                     else{
124                         cocos2d::log("========!!! create_new_image_hsl %s error !!!========.", sz_new_png_path);
125                         ret = false;
126                     }
127                 }
128                 else{
129                     cocos2d::log("========!!! initWithImageFile %s error !!!========.", png_file_full_name.c_str());
130                     ret = false;
131                 }
132 
133                 delete image;
134             }
135         }
136 
137         return ret;
138     }

 

 

 

6.实现效果

①原图

spine在unity_Image

②PhotoShop中调到115,0,0的效果

spine在unity_i++_02

③传入参数115,0,0后创建新Image的效果

spine在unity_2d_03

 可以看到,使用ps修改的效果和程序输出的效果基本一样。

以上,完。