循序渐进实现仿QQ界面(三):界面调色与控件自绘
本篇讲述如何进行界面调色。界面调色一般有两种方法,调色板和HSL色彩变换。调色板局限于256色,这里不采用,因此用HSL色彩变换实现。首先要了解一下什么是HSL色彩空间,完整且详尽的知识请到维基百科去看,链接地址:http://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4,这里简单讲一下(摘自维基百科):
HSL 和 HSV(也叫做 HSB)是对RGB 色彩空间中点的两种有关系的表示,它们尝试描述比 RGB 更准确的感知颜色联系,并仍保持在计算上简单。HSL 表示 hue(色相)、saturation(饱和度)、lightness(亮度),HSV 表示 hue、 saturation、value 而 HSB 表示 hue、saturation、brightness(明度)。如下图:
HSL 和 HSV 二者都把颜色描述在圆柱体内的点,这个圆柱的中心轴取值为自底部的黑色到顶部的白色而在它们中间是的灰色,绕这个轴的角度对应于“色相”,到这个轴的距离对应于“饱和度”,而沿着这个轴的距离对应于“亮度”,“value”或“明度”。
把RGB颜色转换到HSL色系,进行调色就很简单了,改变色彩只要转个角度,改变亮度就是沿轴升降进行“切片”,饱和度就是改变到中心轴的距离,3点定位到一个颜色,再转回RGB颜色就完成了界面调色。对需要调色的贴图进行这么一个变换,再重新贴图刷新一下界面就完成了调色功能。
并不是所有图片都需要调色,象标题栏右边的沙滩图案,如果调色就很难看了,会象照片的负片效果,因此用户头像,底纹图案,文字,图标都不需要调色,只需要把背景图案,按钮的高亮和按下状态图片,菜单背景和高亮图案进行色彩变换就行了。系统按钮需要局部调色,把最小化和最大化,还原按钮的高亮和按下状态的背景进行调色,因此准备了另外一张图片,一共5张图片需要调色:
最后一张图片是调色后透明绘制到系统按钮源图象上,实现了局部调色。
HSL色彩变换的实现是通过挂接图象库的滤镜插件类实现,实现原理请看RingSDK的帮助文档,调用代码极其简单,m_dibBkg.GETFILTER(dibFilterEFFECT)->AdjustHSL(h,s,l);就完成了背景的调色。图象库实现了两个滤镜插件类,dibFilterALPHA和dibFilterEFFECT,实现图象的各种ALPHA混合效果和亮度对比度,色度调整等。这里把关键的调色代码贴出来,其中m_rdib->Data()为已加载的32位色图象数据:
[cpp] view plaincopy
1. //调整色调,参数范围:-180~180(度),=0不作调整
2. //调整饱和度,参数范围0~200(建议,最大值可>200),=100不作调整
3. //调整亮度,亮度参数范围0~200(建议,最大值可>200),=100不作调整
4. BOOL dibFilterEFFECT::AdjustHSL(int degHue,int perSaturation,int perLuminosity)
5. {
6. if(!m_rdib->Data())
7. return FALSE;
8.
9. if(perSaturation < 0 || perLuminosity < 0)
10. return FALSE;
11.
12. if(degHue == 0 && perSaturation == 100 && perLuminosity == 100)
13. return TRUE; //未作调整,直接返回
14.
15. LPBYTE pRed, pGrn, pBlu,pBuf=(LPBYTE)m_rdib->Data();
16. UINT loop = m_rdib->Width() * m_rdib->Height();
17. COLORREF *cr = (COLORREF*)m_rdib->Data();
18.
19. pRed=pBuf++;pGrn=pBuf++;pBlu=pBuf;
20. float H,S,L;
21.
22. for (UINT i=0;i<loop;i++)
23. {
24. RGBtoHSL(*pRed,*pGrn,*pBlu,&H,&S,&L);
25.
26. H += degHue;
27. S = (S*perSaturation/100.0f);
28. L = (L*perLuminosity/100.0f);
29.
30. *cr = HSLtoRGB(H,S,L);
31.
32. pRed+=4;
33. pGrn+=4;
34. pBlu+=4;
35. cr ++;
36. }
37. return TRUE;
38. }
39.
40. void dibFilterEFFECT::RGBtoHSL(BYTE R,BYTE G,BYTE B,float* H,float* S,float* L)
41. {
42. BYTE minval = min(R,min(G,B));
43. BYTE maxval = max(R,max(G,B));
44. float mdiff = float(maxval) - float(minval);
45. float msum = float(maxval) + float(minval);
46.
47. *L = msum / 510.0f;
48.
49. if (maxval == minval)
50. {
51. *S = 0.0f;
52. *H = 0.0f;
53. }
54. else
55. {
56. float rnorm = (maxval - R) / mdiff;
57. float gnorm = (maxval - G) / mdiff;
58. float bnorm = (maxval - B) / mdiff;
59.
60. *S = (*L <= 0.5f) ? (mdiff / msum) : (mdiff / (510.0f - msum));
61.
62. if(R == maxval)
63. *H = 60.0f * (6.0f + bnorm - gnorm);
64. if(G == maxval)
65. *H = 60.0f * (2.0f + rnorm - bnorm);
66. if(B == maxval)
67. *H = 60.0f * (4.0f + gnorm - rnorm);
68. if (*H > 360.0f)
69. *H -= 360.0f;
70. }
71. }
72.
73. COLORREF dibFilterEFFECT::HSLtoRGB(float H,float S,float L)
74. {
75. BYTE r,g,b;
76.
77. L = min(1,L);
78. S = min(1,S);
79.
80. if(S == 0.0)
81. {
82. r = g = b = (BYTE)(255 * L);
83. }
84. else
85. {
86. float rm1, rm2;
87.
88. if (L <= 0.5f)
89. rm2 = L + L * S;
90. else
91. rm2 = L + S - L * S;
92. rm1 = 2.0f * L - rm2;
93.
94. r = HueToRGB(rm1, rm2, H + 120.0f);
95. g = HueToRGB(rm1, rm2, H);
96. b = HueToRGB(rm1, rm2, H - 120.0f);
97. }
98. return RGB(r,g,b);
99. }
100.
101. BYTE dibFilterEFFECT::HueToRGB(float rm1,float rm2,float rh)
102. {
103. while(rh > 360.0f)
104. rh -= 360.0f;
105. while(rh < 0.0f)
106. rh += 360.f;
107.
108. if(rh < 60.0f)
109. rm1 = rm1 + (rm2 - rm1) * rh / 60.0f;
110. else if(rh < 180.0f)
111. rm1 = rm2;
112. else if(rh < 240.0f)
113. rm1 = rm1 + (rm2 - rm1) * (240.0f - rh) / 60.0f;
114.
115. float n = rm1*255;
116. int m = min((int)n,255);
117. m = max(0,m);
118. return (BYTE)m;
119. }
实现了界面调色功能,接下来就得实现调色的设置界面,QQ2009的调色设置是预置了8种基色,可以选择一种基色,也可以对HSL分别调色,但是这8种基色是随着底纹和皮肤的选择变动的,皮肤的选择影响可选择的底纹和基色,一开始没怎么搞懂,后来下了一个QQ2009的主题包,结果是一个自定义格式的打包文件,没办法解开看里面图片,就没研究了,再下了一个2008版的主题包,可以看到所用的图片,大致清楚其为什么这么做了,里面的底纹图片是BMP格式,用紫红做透明色,由于没有ALPHA混合通道,这样的图片透明绘制到背景,边缘是有锯齿的,除非背景颜色跟图象边缘颜色相近才看不出来,因此选择的皮肤会限定底纹图片和背景颜色,选择底纹会同时变动背景颜色,再调色则对背景和底纹一起调色,底纹图案尽量采用RGB色差不大的图案,使调色效果不会太明显,这样整个界面调色底纹图案就没有了锯齿,变化也不怎么看得出来,是一种比较巧妙的做法。当然这是QQ以前版本的做法,2009应该已经改进了。这里演示的界面调色功能由于底纹图案是采用了带ALPHA通道的PNG格式图片,因此不存在这样的限制,同时为了简单起见,代码易于理解,就不学QQ的做法了,取消皮肤的设定,调色和底纹的选择互不影响,不做联动。在资源里预置了5张底纹图片可供选择,有兴趣的可以自己加。这里只是为简单起见而将底纹加到了资源里面,实际为了实现换肤并可扩展应该是自己实现一个换肤的配置文件和资源包,这里就不演示了。
现在实现调色的设置界面,首先响应“更改外观”按钮的弹起事件(WM_LBUTTONUP消息里面),弹出设置窗口,这个窗口是资源里面调色和底纹对话框的大小,不可拖动和调整大小,需要在失去焦点时自动隐藏,因此需要在其WM_ACTIVATE消息里面判断一下,如果是WA_INACTIVE状态就发送一个退出消息把自己关闭。加载调色和底纹两个对话框,显示一个,把另一个隐藏,调色和底纹的选择看起来应该是TAB控件,不过得自绘,这里用图片控件模拟TAB反而简单,反正只有两个选择,判断一下鼠标位置,换个图片,切换一下两个对话框的显示状态,看代码就知道了。
这里主要讲解一下调色对话框上4个Slider控件的自绘。控件自绘需要先子类化,RingSDK界面库已经封装好了,继承相应的控件类,子类化是自动的,可以重载其RingdowProc函数,即控件的窗口过程,想做什么都可以,给了你最大的自由度,未处理的消息返回DefaultProc或基类的RingdowProc就可以。自绘的Slider控件是继承自RingTrackBar,只要响应其WM_PRINTCLIENT和WM_PAINT消息,把背景图案和滑块图案画上去就OK了,代码很简单:
[cpp] view plaincopy
1. LRESULT RingTrackBarEx::RingdowProc(HWND hWnd,RINGPARAMS param)
2. {
3. switch(param.uMsg)
4. {
5. case WM_PRINTCLIENT:
6. case WM_PAINT:
7. {
8. RECT rc,rcc;
9. HDC hdc,hMemDC;
10. PAINTSTRUCT ps;
11. GetWindowRect(m_hWnd,&rc);
12. OffsetRect(&rc,-rc.left,-rc.top);
13. hdc = param.wParam?(HDC)param.wParam:BeginPaint(hWnd,&ps);
14. FillRect(hdc,&rc,m_brush);
15.
16. hMemDC = CreateCompatibleDC(hdc);
17. SelectObject(hMemDC,m_hbmLine);
18. GetChannelRect(&rcc);
19. StretchBlt(hdc,rcc.left,(rc.bottom - m_sizeLine.cy)/2,rcc.right-rcc.left,m_sizeLine.cy,
20. hMemDC,0,0,m_sizeLine.cx,m_sizeLine.cy,SRCCOPY);
21. SelectObject(hMemDC,m_hbmThumb);
22. GetThumbRect(&rc);
23. BitBlt(hdc,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,hMemDC,0,0,SRCCOPY);
24.
25. DeleteDC(hMemDC);
26. if(param.wParam == 0)
27. EndPaint(hWnd, &ps);
28. return 0;
29. }
30. }
31. return DefaultProc(param);
32. }
因为调色函数AdjustHSL的参数关系,设置调整色调的控件调整范围值为-180 ~ 180,初始值为0,饱和度和亮度的范围都是0 ~ 200,初始值为100。关于透明度的调整,怎么使窗口半透明网上介绍文章有很多,这里就不罗嗦了,界面库对此做了封装,调用SetLayeredAlpha就可以了,不过范围值是设成了25 ~ 255,不能0 ~ 255,全透明的话窗口就不能操作了。
调整色调的图片是展开的HSL色环,如下图:
注意因为默认的背景是兰色,因此兰色是在图案的中间,如果默认背景是别的颜色,就需要循环平移这些色彩,使背景色处在中间位置,否则拖动滑块调色,实际的效果跟滑块所在的颜色就对不上号了。
底纹图案的选择很简单,重新加载选择的图案就OK,看代码就知道了。
至此完成了界面调色功能,界面中间部分因为以后会有控件遮盖,因此不需要调色。现在看看程序的截图:
最后说一下怎么做带ALPHA通道的PNG图片和加到程序里面:
首先找一张自己满意的图片,调整到高度95,QQ标题栏的高度,宽度随意,只要图案不怎么变形就可以,然后加一个蒙板,如图,按一下图中的按纽:
要使图片透明,需要去除背景,如果是新建图片并复制了图象过来,把背景层删掉,如果是直接打开的图片,需要复制一层然后把背景层删掉。加了蒙板后选择渐变工具,从右往左一拉,然后把图片保存为PNG格式就可以了,如图:
做完PNG图片还要做一张31*31的预览BMP图片,按资源里的格式做就可以了,把PNG加入为“PNG”类型的资源,ID值必须与resource.h里IDP_SEA等5张预置图片的ID值连续,预览图片的ID值必须与IDB_TATOO1~IDB_TATOO5的值连续,在wnduioption.cpp里面找到gszTatooInfo的初始化代码,把图片说明加到里面,编译一下程序就OK了。
现在这个仿QQ界面的程序已经完成了标题栏部分的功能,接下来要完成客户区和底栏部分的功能,留待下一篇再讲了。
程序代码下载地址:http://download.csdn.net/source/1995651
循序渐进实现仿QQ界面(三):界面调色与控件自绘的更多相关文章
- C#仿QQ皮肤-Label与ListBox 控件实现----寻求滚动条的解决方案
大家还是先来看看效果吧 这次之所以一次写两个控件,其实主要是因为Label控件实在是太简单了没有必要放放一个文章里写,所以就一次性来了.Label控件我就不再多说了,我直接把代码贴一下吧因为就几行代码 ...
- WPFS数据绑定(要是后台类对象的属性值发生改变,通知在“client界面与之绑定的控件值”也发生改变须要实现INotitypropertyChanged接口)
WPFS数据绑定(要是后台类对象的属性值发生改变,通知在"client界面与之绑定的控件值"也发生改变须要实现INotitypropertyChanged接口) MainWindo ...
- Android群英传笔记——第三章:Android控件架构与自定义控件讲解
Android群英传笔记--第三章:Android控件架构与自定义控件讲解 真的很久没有更新博客了,三四天了吧,搬家干嘛的,心累,事件又很紧,抽时间把第三章大致的看完了,当然,我还是有一点View的基 ...
- Android自定义View(三、深入解析控件测量onMeasure)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51490283 本文出自:[openXu的博客] 目录: onMeasure什么时候会被调用 ...
- Silverlight 2学习笔记二:三个基本布局控件(Canvas、StackPanel、Grid )
这篇文章主要是翻译了ScottGu博客的文章:Silverlight Tutorial Part 2: Using Layout Management.虽然是翻译,但通过笔记记录,我发现对这三个布局控 ...
- 循序渐进实现仿QQ界面(一):园角矩形与双缓冲贴图窗口
印象里仿QQ界面的程序应该有很多,搜了一下,虽然出来一大堆,排除了重复的,却只有两三个,没我想象的好.经常看到CSDN上有人问,QQ这个功能怎么实现,那个界面怎么实现,归纳了一下,决定写这么一个仿QQ ...
- 循序渐进实现仿QQ界面(二):贴图按钮的三态模拟
开始之前先说一下RingSDK的编译问题,这里演示的程序需要用到最新版本的RingSDK,请务必用SVN到svn://svnhost.cn/RingSDK更新到最新版本,推荐用TortoiseSVN. ...
- 仿QQ空间动态界面分享
先看看效果: 用极少的代码实现了 动态详情 及 二级评论 的 数据获取与处理 和 UI显示与交互,并且高解耦.高复用.高灵活. 动态列表界面MomentListFragment支持 下拉刷新与上拉加载 ...
- RDIFramework.NET ━ .NET快速信息化系统开发框架 V3.2->Web版本“产品管理”事例编辑界面新增KindEditor复文本编辑控件
KindEditor是一套开源的HTML可视化编辑器,主要用于让用户在网站上获得所见即所得编辑效果,兼容IE.Firefox.Chrome.Safari.Opera等主流浏览器.KindEditor使 ...
随机推荐
- Vue中解决路由切换,页面不更新的实用方法
前言:vue-router的切换不同于传统的页面的切换.路由之间的切换,其实就是组件之间的切换,不是真正的页面切换.这也会导致一个问题,就是引用相同组件的时候,会导致该组件无法更新,也就是我们口中的页 ...
- 类InetAddress
如果一个类没有构造方法:A:成员全部是静态的(Math,Arrays,Collections)B:单例设计模式(Runtime)C:类中有静态方法返回该类的对象(InetAddress) public ...
- spring mvc 接受数组
@RequestParam(value = "customerIds[]")Integer[] customerIds 加上 requestParam value设置为 &qu ...
- A heavy dew refreshed the earth at night
brightness.n.明亮 endless.adj.无止境的 degradation.n.堕落 superiority.n.优越性 valuable.adj.有价值的 flake.n.小薄片 cu ...
- 微信小程序---》分包加载
[小程序]---分包加载 一.分包加载 某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载 在构建小程序分包项目时,构建会输出一个或多个分包.每个使 ...
- 在使用spring中的ContextConfiguration、test注解时出现的错误
错误: 在使用测试注解时出现ContextConfiguration注解和test注解无法正常导包使用的编译异常,如图: 解决办法: 将pom.xml文件中以下依赖管理 中的<scope> ...
- PTA第二题
#include<string.h> #include<stdio.h> #include<malloc.h> ]; ][]={"ling",& ...
- S5PV210刷机
一. 刷机初识 1.1. 什么是刷机 a. 刷机就是通过各种手段把相应镜像烧录到设备中,让设备可以运行起来.常见的刷机如:安卓刷机,QT刷机 1.2. S5PV210刷机镜像 1.2.1. 刷安卓所需 ...
- [CQOI2009]dance跳舞(最大流+二分)
[CQOI2009]dance跳舞 每个人拆成$2$个点,表示是否与喜欢的人跳舞 跳$m$首舞曲时,满足最大流为$n*m$ 二分$m$,跑最大流即可 #include<cstdio> #i ...
- redis-Nosql
Nosql: CAP:C(Consistency):强一致性.A(Availability):可用性.P(Partitio Tolerance):分区容错性 CAP 理论的核心是: 一个分布式系统,不 ...