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代码

 #ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texCoord;
uniform float u_dH;
uniform float u_dS;
uniform float u_dL;
void main() {
vec4 texColor = texture2D(CC_Texture0, v_texCoord);
float r = texColor.r;
float g = texColor.g;
float b = texColor.b;
float a = texColor.a;
//convert rgb to hsl
float h;
float s;
float l;
{
float max = max(max(r, g), b);
float min = min(min(r, g), b);
//----h
if (max == min){
h = 0.0;
}
else if (max == r&&g >= b){
h = 60.0*(g - b) / (max - min) + 0.0;
}
else if (max == r&&g < b){
h = 60.0*(g - b) / (max - min) + 360.0;
}
else if (max == g){
h = 60.0*(b - r) / (max - min) + 120.0;
}
else if (max == b){
h = 60.0*(r - g) / (max - min) + 240.0;
}
//----l
l = 0.5*(max + min);
//----s
if (l == 0.0 || max == min){
s = 0.0;
}
else if (0.0 <= l&&l <= 0.5){
s = (max - min) / (2.0*l);
}
else if (l > 0.5){
s = (max - min) / (2.0 - 2.0*l);
}
}
//(h,s,l)+(dH,dS,dL) -> (h,s,l)
h = h + u_dH;
s = min(1.0, max(0.0, s + u_dS));
l = l + u_dL;
//convert (h,s,l) to rgb and got final color
vec4 finalColor;
{
float q;
if (l < 0.5){
q = l*(1.0 + s);
}
else if (l >= 0.5){
q = l + s - l*s;
}
float p = 2.0*l - q;
float hk = h / 360.0; float t[];
t[] = hk + 1.0 / 3.0;
t[] = hk;
t[] = hk - 1.0 / 3.0; float c[];
for (int i = ; i < ; i++){
if (t[i] < 0.0)t[i] += 1.0;
if (t[i] > 1.0)t[i] -= 1.0; if (t[i] < 1.0 / 6.0){
c[i] = p + ((q - p)*6.0*t[i]);
}
else if (1.0 / 6.0 <= t[i] && t[i] < 0.5){
c[i] = q;
}
else if (0.5 <= t[i] && t[i] < 2.0 / 3.0){
c[i] = p + ((q - p)*6.0*(2.0 / 3.0 - t[i]));
}
else{
c[i] = p;
}
}
finalColor = vec4(c[], c[], c[], a);
}
finalColor += vec4(u_dL, u_dL, u_dL, 0.0);
gl_FragColor = finalColor;
}

②调用代码

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

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

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

 Image* create_new_image_hsl(Image* image, float u_dH, float u_dS, float u_dL){
u_dH = u_dH;
u_dS = u_dS / 100.0f;
u_dL = u_dL / 100.0f; bool hasAlpha = image->hasAlpha();
Texture2D::PixelFormat pixelFormat = image->getRenderFormat();
int bit_per_pixel = image->getBitPerPixel(); //每像素多少位
unsigned int length = image->getWidth()*image->getHeight(); if (hasAlpha){
//只处理了RGBA8888的格式
if (pixelFormat == Texture2D::PixelFormat::RGBA8888){
unsigned int* inPixel32 = (unsigned int*)image->getData();
for (unsigned int i = ; i < length; ++i, ++inPixel32)
{
unsigned char cr = (*inPixel32 >> ) & 0xFF; // R
unsigned char cg = (*inPixel32 >> ) & 0xFF; // G
unsigned char cb = (*inPixel32 >> ) & 0xFF; // B
unsigned char ca = (*inPixel32 >> ) & 0xFF; // A
//透明图层不做处理
if (ca){
float r = cr*1.0f / 0xFF;
float g = cg*1.0f / 0xFF;
float b = cb*1.0f / 0xFF; float h = ;
float s = ;
float l = ;
{
float max = r > g ? r : g;
max = max > b ? max : b; float min = r < g ? r : g;
min = min < b ? min : b; //----h
if (max == min){
h = ;
}
else if (max == r && g >= b){
h = 60.0f*(g - b) / (max - min);
}
else if (max == r && g < b){
h = 60.0f*(g - b) / (max - min) + 360.0f;
}
else if (max == g){
h = 60.0f*(b - r) / (max - min) + 120.0f;
}
else if (max == b){
h = 60.0f*(r - g) / (max - min) + 240.0f;
}
//----l
l = 0.5f*(max + min);
//----s
if (l == || max == min){
s = ;
}
else if ( <= l&&l <= 0.5f){
s = (max - min) / (2.0f*l);
}
else if (l > 0.5f){
s = (max - min) / (2.0f - 2.0f*l);
}
}
//(h,s,l)+(dH,dS,dL) -> (h,s,l)
h = h + u_dH;
s = > s + u_dS ? : s + u_dS;
s = s < 1.0f ? s : 1.0f; l = l + u_dL;
//convert (h,s,l) to rgb and got final color //vec4 finalColor;
{
float q;
if (l < 0.5f){
q = l*(1.0f + s);
}
else if (l >= 0.5f){
q = l + s - l*s;
}
float p = 2.0f*l - q;
float hk = h / 360.0f; float t[] = {};
t[] = hk + 1.0f / 3.0f;
t[] = hk;
t[] = hk - 1.0f / 3.0f; float c[] = {};
for (int i = ; i < ; i++){
if (t[i] < ){
t[i] += 1.0f;
}
if (t[i] > 1.0f){
t[i] -= 1.0f;
} if (t[i] < 1.0f / 6.0f){
c[i] = p + ((q - p)*6.0f*t[i]);
}
else if (1.0f / 6.0f <= t[i] && t[i] < 0.5f){
c[i] = q;
}
else if (0.5f <= t[i] && t[i] < 2.0f / 3.0f){
c[i] = p + ((q - p)*6.0f*(2.0f / 3.0f - t[i]));
}
else{
c[i] = p;
}
}
//finalColor = vec4(c[0], c[1], c[2], a); r = c[];
g = c[];
b = c[];
} //finalColor += vec4(u_dL, u_dL, u_dL, 0.0);
r += u_dL;
g += u_dL;
b += u_dL;
//限制rgb值的有效范围
if (r > 1.0f) r = 1.0f;
if (g > 1.0f) g = 1.0f;
if (b > 1.0f) b = 1.0f;
if (r < ) r = ;
if (g < ) g = ;
if (b < ) b = ; unsigned char final_r = r * 0xFF;
unsigned char final_g = g * 0xFF;
unsigned char final_b = b * 0xFF;
unsigned char final_a = ca; unsigned int val = final_a;
val = val << ;
val |= final_b;
val = val << ;
val |= final_g;
val = val << ;
val |= final_r; *inPixel32 = val;
}
}
}
} return image;
}

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

 bool create_new_png_and_atlas(const char* atlas_name_no_extension, float u_dH, float u_dS, float u_dL)
{
bool ret = false; std::string atlas_name = atlas_name_no_extension;
atlas_name += ".atlas";
std::string atlas_file_full_name = FileUtils::getInstance()->fullPathForFilename(atlas_name);
Data data = FileUtils::getInstance()->getDataFromFile(atlas_file_full_name); if (!data.isNull()){
std::string writeable_path = FileUtils::getInstance()->getWritablePath(); char* orginal_atlas_content = new char[data.getSize()]();
memcpy(orginal_atlas_content, data.getBytes(), data.getSize()); char sz_new_atlas_path[] = {};
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); //为了保证大小,创建一个两倍大小的内存大小(这里可以适当减少大小)
unsigned char* new_atlas_content = new unsigned char[ * data.getSize()]();
memset(new_atlas_content, , * data.getSize()); //分析得出png文件名字,可能有分为多个png的情况
std::vector<std::string> png_no_extension_list;
int line_start_idx = , line_end_idx = ;
int orginal_content_start_idx = , new_content_idx = , new_content_total_len = ; for (int i = ; i < data.getSize(); ++i){
line_end_idx = i; //每一行判断是否为png
if (orginal_atlas_content[i] == '\n'){
int line_len = line_end_idx - line_start_idx; //处理是图片的情况
if (line_len > && orginal_atlas_content[line_end_idx - ] == '.' &&
(orginal_atlas_content[line_end_idx - ] == 'p' || orginal_atlas_content[line_end_idx - ] == 'P') &&
(orginal_atlas_content[line_end_idx - ] == 'n' || orginal_atlas_content[line_end_idx - ] == 'N') &&
(orginal_atlas_content[line_end_idx - ] == 'g' || orginal_atlas_content[line_end_idx - ] == 'G')){
//将png行前面的数据拷贝进来
int cpy_len = line_start_idx - orginal_content_start_idx;
if (cpy_len > ){
memcpy(new_atlas_content + new_content_idx, orginal_atlas_content + orginal_content_start_idx, cpy_len);
new_content_idx += cpy_len;
new_content_total_len += cpy_len;
} //没有后缀的png的名字
char png_name_no_extension[] = {};
memcpy(png_name_no_extension, orginal_atlas_content + line_start_idx, line_len - );
png_no_extension_list.push_back(png_name_no_extension); //记录列表
char sz_new_png_name[] = {};
sprintf(sz_new_png_name, "%s_%d_%d_%d.png\n", png_name_no_extension, (int)u_dH, (int)u_dS, (int)u_dL); //将图片名字写入新内容
cpy_len = strlen(sz_new_png_name);
memcpy(new_atlas_content + new_content_idx, sz_new_png_name, cpy_len);
new_content_idx += cpy_len;
new_content_total_len += cpy_len; orginal_content_start_idx = line_end_idx + ;
} line_start_idx = line_end_idx + ;
}
} //将最后一个png行到最后那部分数据也拷贝进来
if (data.getSize() > orginal_content_start_idx){
int cpy_len = data.getSize() - orginal_content_start_idx;
memcpy(new_atlas_content + new_content_idx, orginal_atlas_content + orginal_content_start_idx, cpy_len);
new_content_total_len += cpy_len;
} Data data;
data.copy(new_atlas_content, new_content_total_len); if (!FileUtils::getInstance()->writeDataToFile(data, sz_new_atlas_path)){
cocos2d::log("========!!! save file %s error !!!========.", sz_new_atlas_path);
}
else{
ret = true;
} data.clear(); delete[]orginal_atlas_content;
delete[]new_atlas_content; if (png_no_extension_list.size() <= ){
cocos2d::log("========!!! png_name_list is empty error !!!========.");
return false;
} if (!ret){
return false;
} //写入新图片
for (std::vector<std::string>::iterator it = png_no_extension_list.begin(); it != png_no_extension_list.end(); ++it){
if (!ret){
break;
} char sz_new_png_path[] = {};
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); char sz_png_name[] = {};
sprintf(sz_png_name, "%s.png", it->c_str());
std::string png_file_full_name = FileUtils::getInstance()->fullPathForFilename(sz_png_name); Image* image = new Image();
if (image->initWithImageFile(png_file_full_name)){
if (Image* new_image = create_new_image_hsl(image, u_dH, u_dS, u_dL)){
if (!new_image->saveToFile(sz_new_png_path, false)){
cocos2d::log("========!!! save file %s error !!!========.", sz_new_png_path);
ret = false;
}
}
else{
cocos2d::log("========!!! create_new_image_hsl %s error !!!========.", sz_new_png_path);
ret = false;
}
}
else{
cocos2d::log("========!!! initWithImageFile %s error !!!========.", png_file_full_name.c_str());
ret = false;
} delete image;
}
} return ret;
}

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. hibernate validator自定义校验注解以及基于服务(服务组)的校验

    hibernate validator是Bean Validation 1.1 (JSR 349) Reference Implementation,其广泛的应用在mvc的参数校验中,尤其是使用服务端 ...

  2. 维护keepalived与mysql漂移脚本

    环境拓扑 chengAlived #!/bin/bash function checkModelone(){ echo "重新进行获取" wget 192.168.158.147: ...

  3. 如何获取sdcard的总容量

    activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android& ...

  4. 数据库 --- 4 多表查询 ,Navicat工具 , pymysql模块

    一.多表查询 1.笛卡儿积 查询 2.连接 语法: ①inner    显示可构成连接的数据 mysql> select employee.id,employee.name,department ...

  5. Eclipse之maven插件link方式安装

    maven是开发人员要具备的必不可少的技能之一.在使用eclipse进行开发时,我们需要安装maven插件,网上有很多教程,但是有些教程写的太过模糊.在此,我将自己的安装方法总结一下,尽量细致. 前提 ...

  6. strerror函数的总结【转】

    本文转载自:https://www.cnblogs.com/xrcun/p/3210889.html 定义函数:char * strerror(int errnum); 函数说明:strerror() ...

  7. 【做题】Codeforces Round #453 (Div. 1) D. Weighting a Tree——拆环

    前言:结论题似乎是我的硬伤…… 题意是给你一个无向图,已知连接到每一个点的边的权值和(为整数,且属于区间[-n,n]),需要求出每条边权值的一个合法解(都要是在区间[-2*n^2,2*n^2]内的整数 ...

  8. GDOI2018D2T1 谈笑风生

    T1 谈笑风生 [题目描述] [输入] [输出] 一行两个数,所需能量P与在能量最小的前提下最短的到达时间t. [样例输入] 5 7 66 4 3 2 1 5 1 2 1 5 2 3 2 4 2 5 ...

  9. Visual Question Answering with Memory-Augmented Networks

    Visual Question Answering with Memory-Augmented Networks 2018-05-15 20:15:03 Motivation: 虽然 VQA 已经取得 ...

  10. Python 安装与环境变量配置

    一.软件下载 Python安装包下载地址:https://www.python.org/ 二.安装过程(略) 三.环境变量配置: 方法一:使用cmd命令添加path环境变量 在cmd下输入: path ...