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.实现效果

①原图

cocos2dx spine之二 :spine变色-LMLPHP

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

cocos2dx spine之二 :spine变色-LMLPHP

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

cocos2dx spine之二 :spine变色-LMLPHP

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

以上,完。

05-11 17:45