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[];
  68. t[] = hk + 1.0 / 3.0;
  69. t[] = hk;
  70. t[] = hk - 1.0 / 3.0;
  71.  
  72. float c[];
  73. for (int i = ; i < ; 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[], c[], c[], 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 / );
  6. glState->setUniformFloat("u_dL", l / );
  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 = ; i < length; ++i, ++inPixel32)
  16. {
  17. unsigned char cr = (*inPixel32 >> ) & 0xFF; // R
  18. unsigned char cg = (*inPixel32 >> ) & 0xFF; // G
  19. unsigned char cb = (*inPixel32 >> ) & 0xFF; // B
  20. unsigned char ca = (*inPixel32 >> ) & 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 = ;
  28. float s = ;
  29. float l = ;
  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 = ;
  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 == || max == min){
  57. s = ;
  58. }
  59. else if ( <= 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 = > s + u_dS ? : 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[] = {};
  87. t[] = hk + 1.0f / 3.0f;
  88. t[] = hk;
  89. t[] = hk - 1.0f / 3.0f;
  90.  
  91. float c[] = {};
  92. for (int i = ; i < ; i++){
  93. if (t[i] < ){
  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[];
  116. g = c[];
  117. b = c[];
  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 < ) r = ;
  129. if (g < ) g = ;
  130. if (b < ) b = ;
  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. unsigned int val = final_a;
  138. val = val << ;
  139. val |= final_b;
  140. val = val << ;
  141. val |= final_g;
  142. val = val << ;
  143. val |= final_r;
  144.  
  145. *inPixel32 = val;
  146. }
  147. }
  148. }
  149. }
  150.  
  151. return image;
  152. }

②分析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[] = {};
  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[ * data.getSize()]();
  21. memset(new_atlas_content, , * data.getSize());
  22.  
  23. //分析得出png文件名字,可能有分为多个png的情况
  24. std::vector<std::string> png_no_extension_list;
  25. int line_start_idx = , line_end_idx = ;
  26. int orginal_content_start_idx = , new_content_idx = , new_content_total_len = ;
  27.  
  28. for (int i = ; 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 > && orginal_atlas_content[line_end_idx - ] == '.' &&
  37. (orginal_atlas_content[line_end_idx - ] == 'p' || orginal_atlas_content[line_end_idx - ] == 'P') &&
  38. (orginal_atlas_content[line_end_idx - ] == 'n' || orginal_atlas_content[line_end_idx - ] == 'N') &&
  39. (orginal_atlas_content[line_end_idx - ] == 'g' || orginal_atlas_content[line_end_idx - ] == 'G')){
  40. //将png行前面的数据拷贝进来
  41. int cpy_len = line_start_idx - orginal_content_start_idx;
  42. if (cpy_len > ){
  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[] = {};
  50. memcpy(png_name_no_extension, orginal_atlas_content + line_start_idx, line_len - );
  51. png_no_extension_list.push_back(png_name_no_extension);
  52.  
  53. //记录列表
  54. char sz_new_png_name[] = {};
  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 + ;
  64. }
  65.  
  66. line_start_idx = line_end_idx + ;
  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. Data data;
  78. data.copy(new_atlas_content, new_content_total_len);
  79.  
  80. if (!FileUtils::getInstance()->writeDataToFile(data, sz_new_atlas_path)){
  81. cocos2d::log("========!!! save file %s error !!!========.", sz_new_atlas_path);
  82. }
  83. else{
  84. ret = true;
  85. }
  86.  
  87. data.clear();
  88.  
  89. delete[]orginal_atlas_content;
  90. delete[]new_atlas_content;
  91.  
  92. if (png_no_extension_list.size() <= ){
  93. cocos2d::log("========!!! png_name_list is empty error !!!========.");
  94. return false;
  95. }
  96.  
  97. if (!ret){
  98. return false;
  99. }
  100.  
  101. //写入新图片
  102. for (std::vector<std::string>::iterator it = png_no_extension_list.begin(); it != png_no_extension_list.end(); ++it){
  103. if (!ret){
  104. break;
  105. }
  106.  
  107. char sz_new_png_path[] = {};
  108. 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);
  109.  
  110. char sz_png_name[] = {};
  111. sprintf(sz_png_name, "%s.png", it->c_str());
  112. std::string png_file_full_name = FileUtils::getInstance()->fullPathForFilename(sz_png_name);
  113.  
  114. Image* image = new Image();
  115. if (image->initWithImageFile(png_file_full_name)){
  116. if (Image* new_image = create_new_image_hsl(image, u_dH, u_dS, u_dL)){
  117. if (!new_image->saveToFile(sz_new_png_path, false)){
  118. cocos2d::log("========!!! save file %s error !!!========.", sz_new_png_path);
  119. ret = false;
  120. }
  121. }
  122. else{
  123. cocos2d::log("========!!! create_new_image_hsl %s error !!!========.", sz_new_png_path);
  124. ret = false;
  125. }
  126. }
  127. else{
  128. cocos2d::log("========!!! initWithImageFile %s error !!!========.", png_file_full_name.c_str());
  129. ret = false;
  130. }
  131.  
  132. delete image;
  133. }
  134. }
  135.  
  136. return ret;
  137. }

6.实现效果

①原图

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

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

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

以上,完。

cocos2dx spine之二 :spine变色的更多相关文章

  1. cocos2dx spine之一 :spine缓存 (c++ & lua)

    cocos2dx版本为3.10 1.在使用spine的过程中,发现了一个比较严重的问题:每次创建SkeletonAnimation的时候都会很卡,即使是使用同一个骨骼数据skeletonData. 跟 ...

  2. Spine学习二 -播放Spine动画

    要想播放一个Spine动画,必须要在一个物体上绑定一个Spine播放的组件,这里暂时使用SkeletonAnimation组件. 然后就是编写动画的控制脚本. 这里提一个特性: [SpineAnima ...

  3. Spine应用--使用Spine动画制作动作游戏

    在前面的文章中,已经陆陆续续的讲解了一些使用Spine动画的细节,有了这些细节,我们已经满足了在unity中使用Spine动画制作动作游戏的技术基础. 那么,要使用Spine动画在unity中制作一款 ...

  4. Spine学习七 - spine动画资源+ Unity Mecanim动画系统

    前面已经讲过 Spine自己动画状态机的动画融合,但是万一有哥们就是想要使用Unity的动画系统,那有没有办法呢?答案是肯定的,接下来,就说说如何实现: 1. 在project面板找打你导入的Spin ...

  5. Spine学习五- spine动画融合

    在许多地方,都需要用到动画融合,unity的新版动画系统已经能够很方便的进行动画融合,那么使用spine的动画状态机的情况下,如何来进行动画融合呢? 官方有两种方案,一种是使用混合动作实现,另一种是使 ...

  6. Cocos2d-x PluginX (二)增加新的Plugin

    创建Plugin目录 第一步,在plugin/plugins下,目录需要严格按照如下规范实现: plugin/plugins/alipay/proj.android /proj.ios 因为publi ...

  7. HelloWorld——Cocos2d-x学习历程(二)

    HelloWorld分析: 1."resource"文件夹 该文件夹主要用于存放游戏中需要的图片.音频和配置等资源文件. 2."include"和"s ...

  8. Cocos2dx项目启程二 之 封装属于我的按钮类

    不知道为什么,很讨厌cocos2dx的 各菜单类,比如按钮:如果一张图片上就已经有按钮的几个状态了,我还是要创建多张资源图片, 最起码要指定这张图片上哪块区域是这个普通状态,哪块区域是那个选中状态.. ...

  9. 从零开始のcocos2dx生活(二)Node

    节点 Node 文章目录 节点 Node 前言 变量初始化 创建一个节点对象 获取节点依赖的计数器 获取节点的描述(获取节点的Tag) 节点的局部层顺序值(LocalZOrder) 设置节点的Loca ...

随机推荐

  1. Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源

    Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源 在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等.今天就给大家介绍一个 Spri ...

  2. Python3 freetds.conf odbcinst.ini odbc.ini 之间的关系

    Python3 freetds.conf odbcinst.ini odbc.ini 之间的关系 三者分别是FreeTDS和UnixODBC的配置文件: 1,FreeTDS中的freetds.conf ...

  3. opencv学习之路(5)、鼠标和滑动条操作

    一.鼠标事件 #include<opencv2/opencv.hpp> #include<iostream> using namespace cv; using namespa ...

  4. topcoder srm 445 div1

    problem1 link 这个的结论是只需要考虑坐标是整数或者是整数.5,比如(2.5,3),(4,3.5),(1.5,4.5)这样的时候.这个详细证明起来应该挺麻烦的.这里有一些讨论. probl ...

  5. Restful framework【第十二篇】版本控制

    简单使用 -drf版本控制 -在setting中配置 'DEFAULT_VERSION': 'v1', # 默认版本(从request对象里取不到,显示的默认值) 'ALLOWED_VERSIONS' ...

  6. linux如何使make输出makefile中所有的规则和变量

    答: make -p (会执行makefile,加入-q可以阻止makefile的执行)

  7. Java内存模型原理总结(转自51CTO)

    转载地址:http://developer.51cto.com/art/201811/587220.htm [51CTO.com原创稿件]这篇文章主要介绍模型产生的问题背景,解决的问题,处理思路,相关 ...

  8. hihoCoder week3 KMP算法

    题目链接 https://hihocoder.com/contest/hiho3/problems kmp算法 #include <bits/stdc++.h> using namespa ...

  9. How to Install Apache Tomcat 8.5 on CentOS 7.3

    How to Install Apache Tomcat 8.5 on CentOS 7.3 From: https://www.howtoforge.com/tutorial/how-to-inst ...

  10. Sql Ado.net 学习笔记之连接字符串

    https://www.cnblogs.com/heng95/p/5902019.html 连接字符串 SQL Client .net数据提供程序在连接到数据库时极其灵活,它提供了多种用以生成连接字符 ...