仿酷狗音乐播放器开发日志二十一 开发动态调色板控件(附源码)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 转载请说明原出处,谢谢~~           上一篇仿酷狗日志结束后,整个换肤功能就只剩下调色板功能没有做了,我本以为会很简单,但是研究了酷狗的调色板功能后发现不是那么简单的事情。

转载请说明原出处,谢谢~~

 

        上一篇仿酷狗日志结束后,整个换肤功能就只剩下调色板功能没有做了,我本以为会很简单,但是研究了酷狗的调色板功能后发现不是那么简单的事情。首先看一下酷狗的调色板的样子:


        我原本以为酷狗的主界面只是一张图片,然后通过鼠标坐标来选择颜色,简单粗暴。等我开始做这部分时发现情况不一样。

        可以看到,酷狗的调色板分为两部分,上半部分是调色板的主界面,下半部分是调整亮度的工具栏,我这里分别给他们起名为Pallet和Bar方便说明。这个调色板的Pallet部分的整体亮度可以根据Bar的值而改变,而Bar的颜色会根据Pallet的选择的颜色而改变,这就加大了难度。

        这个调色板动态生成了颜色值,如果要做出一样的功能,首先要解决的就是得知这个调色板的绘制算法,这个着实让我头疼,因为我以前从来没有研究过调色板,也不喜欢搞这种算法。

       我首先对不同亮度下的酷狗调色板截图,然后放到PS中。打开ps的调色板后对图片上的各个颜色取色来研究他们的规律。总算有些发现,这个大家最好自己实践一下:


      在PS中有多重绘图模式:RGB、HSB、CMYK、LAB。测试了几个图片后,发现这个调色板的图片可以从HSB模式发现规律。大致的规律是:在一行中向右取色,HSB三个值中只有H的值变化;在一列中,从上往下取色,S值变大、B值变小,而且S值在达到图片的中间部分后再往下就不会增大,我把它称作S的临界值,而图片亮度越低,S的临界值就越小!而B值变小的速度是整体越来越快。

      在CodeProject中查了几个资料和demo后,对绘图的方式有了一些了解。

      首先我补充一下HSB的知识,详细资料请自行百度:

      在HSB模式中,H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度。 HSB模式对应的媒介是人眼。

HSB与 HSV相同。
色相(H, hue):在0~360°的标准色轮上,色相是按位置度量的。在通常的使用中,色相是由颜色名称标识的,比如红、绿或橙色。黑色和白色无色相。
饱和度(S, saturation):表示色彩的纯度,为0时为灰色。白、黑和其他灰色色彩都没有饱和度的。在最大饱和度时,每一 色相具有最纯的 色光。取值范围0~100%。
亮度(B, brightness或V, value):是色彩的明亮度。为0时即为黑色。最大亮度是色彩最鲜明的状态。取值范围0~100%。
HSB模式中S和B呈现的数值越高,饱和度明度越高,页面色彩强烈艳丽,对视觉刺激是迅速的,醒目的效果,但不易于长时间的观看。以上两种颜色的S数值接近,是强烈的状态。H显示的度是代表在色轮表里某个角度所呈现的 色相 状态,相对于SB来说,意义不大。

        了解了HSB的意思后,首先要知道RGB与HSB的转换算法,因为在duilib中才用RGB的绘图模式。这里给出主要的转换算法(部分算法并非我写的而是摘自网络):

struct RGBColor{
	int r;
	int g;
	int b;
	RGBColor():r(0),g(0),b(0){}
};

struct HSBColor{
	int h;
	int s;
	int b;
	HSBColor():h(0),s(0),b(0){}
};


COLORREF CPalletUI::GetColorFromHSB(int h,int s, int b)
{
	COLORREF color;
	double H = h;
	double B = (double)b / 100;
	double S = (double)s / 100;
	double rgbR,rgbG,rgbB;

	H = (H  >= 360) ? 0 : H ;  

	if(S == 0) {  
		rgbR = B * 255;  
		rgbG = B * 255;  
		rgbB = B * 255;  
	} else 
	{  
		int i = ((int)floor((double)H / 60)) % 6;  
		double f = H / 60 - i;  
		double p = B * (1 - S);  
		double q = B * (1 - S * f);  
		double t = B * (1 - S * (1 - f));  

		switch(i) {  
		case 0:  
			rgbR = B, rgbG = t, rgbB = p;  
			break;  
		case 1:  
			rgbR = q; rgbG = B; rgbB = p;  
			break;  
		case 2:  
			rgbR = p; rgbG = B; rgbB = t;  
			break;  
		case 3:  
			rgbR = p; rgbG = q; rgbB = B;  
			break;  
		case 4:  
			rgbR = t; rgbG = p; rgbB = B;  
			break;  
		case 5:  
			rgbR = B; rgbG = p; rgbB = q;  
			break;  
		}  
		rgbR = rgbR * 255;  
		rgbG = rgbG * 255;  
		rgbB = rgbB * 255;  
	}  

	color = RGB(rgbR + 0.5,rgbG + 0.5,rgbB + 0.5); 
	return color;
}

HSBColor CPalletUI::GetHSBFromColor(COLORREF color)
{
	HSBColor hsb;
	RGBColor rgb = GetRGBFromColor(color);
	double R = rgb.r, G = rgb.g, B = rgb.b;

	double var_Min = min(min(R, G), B);  
	double var_Max = max(max(R, G), B);  

	double hsbH,hsbS,hsbB;

	if(var_Min == var_Max) {  
		hsbH = 0;  
	} else if(var_Max == R && G >= B) {  
		hsbH = 60 * ( (G - B) / (var_Max - var_Min) );  
	} else if(var_Max == R && G < B) {  
		hsbH = 60 * ( (G - B) / (var_Max - var_Min) ) + 360;  
	} else if(var_Max == G) {  
		hsbH = 60 * ( (B - R) / (var_Max - var_Min) ) + 120;  
	} else if(var_Max == B) {  
		hsbH = 60 * ( (R - G) / (var_Max - var_Min) ) + 240;  
	}  

	if(var_Max == 0) {  
		hsbS = 0;  
	} else {  
		hsbS = 1 - (var_Min / var_Max);  
	}  

	double var_R = (R / 255);  
	double var_G = (G / 255);  
	double var_B = (B / 255);  

	hsbB = max(max(var_R, var_G), var_B);  
	hsbH = (hsbH >= 360) ? 0 : hsbH;  

	hsb.h = hsbH + 0.5;
	hsb.s = hsbS * 100 + 0.5;
	hsb.b = hsbB * 100 + 0.5;

	return hsb;  

}

 算法描述:

        1)根据HSB的概念,我在设计这个动态调色板的绘制算法时,H取值范围为0~360、S和B取值范围0~100。

        2)整张图片从左到右的H值从0到360均匀变化。

        3)S的初值为0,取S的临界值为整体图片的亮度值(这个值由下方的Bar来确定)。在每一列中,取中间位置为S的临界值所在位置;在上半部分中,S的值均匀增大;下半部分中,S的值一直取临界值

        4)B的初值为100。在每一列中,B的值总是趁减小趋势,减小的速度越来越快。原酷狗的B的临界值会根据图片亮度的变化而改变位置,我这里做了简化,让B的临界值位置在中间,而取B的临界值的算法为 100 - (100 - 整体亮度)/ 2。

       通过观察得知整张图片分为上下两部分,去其高度为200,所以每部分高度为100。

        c++的描述代码为:

//m_nCurB为图片整体的亮度值,由Bar决定
int SValue = m_nCurB;						//取S的临界值为图片整体的亮度
int BValueTop = (100 - SValue)/2;			//取B的临界值为100 - (100 - 整体亮度)/ 2
int BValueBottom = 100 - BValueTop;
	

for(int i = 0; i < 100; ++i){				//i表示行数,由于整体高度为200,所以一次性绘制上下两部分的两行
	for(int j = 0; j < 360; ++j){			//j表示列数
		int BValue = (100 - i ) * BValueTop / 100 + BValueBottom;
		COLORREF color = GetColorFromHSB(j,(int)((double)(i * SValue / 100)),BValue);
		SetPixel(m_MemDc, m_rcItem.left + j,m_rcItem.top + i,   color); 

		BValue = i * BValueBottom / 100;
		color = GetColorFromHSB(j,SValue,BValue);
		SetPixel(m_MemDc, m_rcItem.left + j,m_rcItem.top + 199 - i,   color); 

	}
}
        以上是调色板主界面的绘制算法,还有剩下的Bar的绘制算法,这个比较简单。Bar的整体颜色的H和S值是通过Pallet部分获取的,剩下的就是从左到右指定不同的B值就可以了,详见源码。

       这个算法弄好后,剩下的就是将其封装为duilib的调色板控件,我继承了CControlUI控件,重写了DoInit()、DoEvent()、DoPaint()函数。

       为了让控件在用户点击了Pallet或者Bar的一部分后可以动态变化,需要在DoEvent函数中处理WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE消息。先设置两个标记变量,表示鼠标是否在Pallet或者Bar中

1)当用户单击控件的界面时,判断鼠标是否在Pallet或者Bar中。如果在,就设置相应的标记变量为真,用于WM_MOUSEMOVE中继续处理。

        2)当用户松开鼠标时,设置标记变量都为假。

        3)当用户在控件上移动时,判断是否在Pallet或者Bar中,然后处理相应的消息。如果在Pallet中,根据鼠标坐标动态算出鼠标所在位置的颜色值,用来设置bar的颜色。如果在Bar中,根据鼠标坐标计算整张图片的亮度值,用来设置Pallet的亮度值。


调色板控件的封装:        

        核心的算法我米描述完了,剩下只是用c++代码描述一下。我将封装控件命名为CPalletUI,为了设置了两个属性,名为palletheight和barheight,用于设置Pallet的高度和Bar的高度,建议将Pallet的高度设置为200的整数,否则最终效果图容易出现失真。Pallet和Bar的宽度为整个控件的宽度。

        另外为控件封装了名为GetSelectColor的函数,用来获取用户最终选择的颜色。

        给出一个使用控件的xml描述代码:

<VerticalLayout height="230"><!-- 主体 -->
   	<Pallet name="Pallet" width="506" height="220" palletheight="200" barheight="14" padding="7,5,0,0" bkcolor="#FFFFFFFF" />   
</VerticalLayout> 
       在头文件中我写好了常量来表示控件的类名和接口名,方便使用者编写CreateControl函数。

       最后给出我模仿酷狗调色板的效果图:





 总结:

         我个人不擅长算法和图像处理,所以最终的控件的代码质量可能不好,如果哪位朋友有更好的算法、资料或、建议或者发现什么错误,请联系我,不胜感激。

         在这个控件编写后几天,网友“风之羽翼”在我原代码基础上,使用新的更合理的代码重写了控件,大幅提升了新控件的性能,在此表示感谢。新控件的博文地址为:仿酷狗音乐播放器开发日志二十二 动态调色板控件第二版(性能大幅提升附源码),原控件下载地址就不提供了,大家直接用新的。


     Redrain  2014.8.16

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
3月前
|
存储 监控 数据库
Django 后端架构开发:高效日志规范与实践
Django 后端架构开发:高效日志规范与实践
71 1
|
1月前
|
Rust 前端开发 JavaScript
Tauri 开发实践 — Tauri 日志记录功能开发
本文介绍了如何为 Tauri 应用配置日志记录。Tauri 是一个利用 Web 技术构建桌面应用的框架。文章详细说明了如何在 Rust 和 JavaScript 代码中设置和集成日志记录,并控制日志输出。通过添加 `log` crate 和 Tauri 日志插件,可以轻松实现多平台日志记录,包括控制台输出、Webview 控制台和日志文件。文章还展示了如何调整日志级别以优化输出内容。配置完成后,日志记录功能将显著提升开发体验和程序稳定性。
71 1
Tauri 开发实践 — Tauri 日志记录功能开发
|
3月前
|
SQL 关系型数据库 MySQL
【MySQL】根据binlog日志获取回滚sql的一个开发思路
【MySQL】根据binlog日志获取回滚sql的一个开发思路
|
13天前
|
监控 开发者
鸿蒙5.0版开发:使用HiLog打印日志(ArkTS)
在HarmonyOS 5.0中,HiLog是系统提供的日志系统,支持DEBUG、INFO、WARN、ERROR、FATAL五种日志级别。本文介绍如何在ArkTS中使用HiLog打印日志,并提供示例代码。通过合理使用HiLog,开发者可以更好地调试和监控应用。
56 16
|
1月前
|
开发框架 缓存 安全
开发日志:IIS安全配置
开发日志:IIS安全配置
开发日志:IIS安全配置
|
1月前
|
开发工具 git
git显示开发日志+WinSW——将.exe文件注册为服务的一个工具+图床PicGo+kubeconfig 多个集群配置 如何切换
git显示开发日志+WinSW——将.exe文件注册为服务的一个工具+图床PicGo+kubeconfig 多个集群配置 如何切换
39 1
|
3月前
|
人工智能 Java Spring
Spring框架下,如何让你的日志管理像‘AI’一样智能,提升开发效率的秘密武器!
【8月更文挑战第31天】日志管理在软件开发中至关重要,不仅能帮助开发者追踪问题和调试程序,还是系统监控和运维的重要工具。在Spring框架下,通过合理配置Logback等日志框架,可大幅提升日志管理效率。本文将介绍如何引入日志框架、配置日志级别、在代码中使用Logger,以及利用ELK等工具进行日志聚合和分析,帮助你构建高效、可靠的日志管理系统,为开发和运维提供支持。
68 0
|
15天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
128 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
1月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
232 3
|
1月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1637 14