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代码
#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变色的更多相关文章
- cocos2dx spine之一 :spine缓存 (c++ & lua)
cocos2dx版本为3.10 1.在使用spine的过程中,发现了一个比较严重的问题:每次创建SkeletonAnimation的时候都会很卡,即使是使用同一个骨骼数据skeletonData. 跟 ...
- Spine学习二 -播放Spine动画
要想播放一个Spine动画,必须要在一个物体上绑定一个Spine播放的组件,这里暂时使用SkeletonAnimation组件. 然后就是编写动画的控制脚本. 这里提一个特性: [SpineAnima ...
- Spine应用--使用Spine动画制作动作游戏
在前面的文章中,已经陆陆续续的讲解了一些使用Spine动画的细节,有了这些细节,我们已经满足了在unity中使用Spine动画制作动作游戏的技术基础. 那么,要使用Spine动画在unity中制作一款 ...
- Spine学习七 - spine动画资源+ Unity Mecanim动画系统
前面已经讲过 Spine自己动画状态机的动画融合,但是万一有哥们就是想要使用Unity的动画系统,那有没有办法呢?答案是肯定的,接下来,就说说如何实现: 1. 在project面板找打你导入的Spin ...
- Spine学习五- spine动画融合
在许多地方,都需要用到动画融合,unity的新版动画系统已经能够很方便的进行动画融合,那么使用spine的动画状态机的情况下,如何来进行动画融合呢? 官方有两种方案,一种是使用混合动作实现,另一种是使 ...
- Cocos2d-x PluginX (二)增加新的Plugin
创建Plugin目录 第一步,在plugin/plugins下,目录需要严格按照如下规范实现: plugin/plugins/alipay/proj.android /proj.ios 因为publi ...
- HelloWorld——Cocos2d-x学习历程(二)
HelloWorld分析: 1."resource"文件夹 该文件夹主要用于存放游戏中需要的图片.音频和配置等资源文件. 2."include"和"s ...
- Cocos2dx项目启程二 之 封装属于我的按钮类
不知道为什么,很讨厌cocos2dx的 各菜单类,比如按钮:如果一张图片上就已经有按钮的几个状态了,我还是要创建多张资源图片, 最起码要指定这张图片上哪块区域是这个普通状态,哪块区域是那个选中状态.. ...
- 从零开始のcocos2dx生活(二)Node
节点 Node 文章目录 节点 Node 前言 变量初始化 创建一个节点对象 获取节点依赖的计数器 获取节点的描述(获取节点的Tag) 节点的局部层顺序值(LocalZOrder) 设置节点的Loca ...
随机推荐
- 基础_模型迁移_CBIR_augmentation
在之前我们做过这样的研究:5图分类CBIR问题 各不相同的 5类的图形,每类100张 import numpy as npfrom keras.datasets import mnistimport ...
- 20155201 李卓雯 《网络对抗技术》实验一 逆向及Bof基础
20155201 李卓雯 <网络对抗技术>实验一 逆向及Bof基础 一.实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,f ...
- 我的QML
1.键盘加Text import QtQuick 2.7 import QtGraphicalEffects 1.0 Rectangle{ width:; height:; color:"# ...
- 深刻理解Python中的元类(metaclass)以及元类实现单例模式
在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Python中这一点仍 ...
- Vue学习【第二篇】:ES6简单介绍
ECMAScript 6简介 ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了.它的目标,是使得 JavaScript ...
- Android O HIDL的实现对接【转】
本文转载自:https://blog.csdn.net/gh201030460222/article/details/80551897 Android O HIDL的实现对接1. HIDL的定义1.1 ...
- php中的正则函数:正则匹配,正则替换,正则分割 所有的操作都不会影响原来的字符串.
有一个长期的误解, 如果要分组, 必须用 小括号 和 |, 而不能用 中括号 和 |. [ab|AB]表示的不是 匹配 ab或 AB, 而是表示 匹配 a,b, |, A, B 这5个字符中 的任意 ...
- 彻底了解 suid, sgid ,sticky权限
sticky: 粘性的, 如 : sticky tape: 粘胶带 /tmp, /var/tmp: 位 sticky: 表示: 第一, 任何用户都可以在该目录下创建文件(编辑自己的文件),第二, 但是 ...
- 【安装】Matlab7.0简介及安装
一.简介 Matlab下载官方版是美国MathWorks公司出品的商业数学软件,Matlab7.0下载官方版用于算法开发.数据可视化.数据分析以及数值计算的高级技术计算语言和交互式环境,主要包括MAT ...
- SQL 多行合并一行
select stuff((select ',' + CONVERT(VARCHAR(50),id)+'' from tab_menu group by id for xml path('')), ...