循序渐进实现仿QQ界面(三):界面调色与控件自绘

来源:互联网 发布:淘宝流量高峰期查询 编辑:程序博客网 时间:2024/05/29 06:34

循序渐进实现仿QQ界面(三):界面调色与控件自绘 收藏 此文于2010-01-15被推荐到CSDN首页
如何被推荐?

本篇讲述如何进行界面调色。界面调色一般有两种方法,调色板和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位色图象数据:

view plaincopy to clipboardprint?
//调整色调,参数范围:-180~180(度),=0不作调整   
//调整饱和度,参数范围0~200(建议,最大值可>200),=100不作调整   
//调整亮度,亮度参数范围0~200(建议,最大值可>200),=100不作调整   
BOOL dibFilterEFFECT::AdjustHSL(int degHue,int perSaturation,int perLuminosity)   
{   
 if(!m_rdib->Data())   
    return FALSE;   
  
 if(perSaturation < 0 || perLuminosity < 0)   
  return FALSE;   
  
 if(degHue == 0 && perSaturation == 100 && perLuminosity == 100)   
  return TRUE; //未作调整,直接返回   
     
   LPBYTE pRed, pGrn, pBlu,pBuf=(LPBYTE)m_rdib->Data();   
 UINT loop = m_rdib->Width() * m_rdib->Height();   
 COLORREF *cr = (COLORREF*)m_rdib->Data();   
      
   pRed=pBuf++;pGrn=pBuf++;pBlu=pBuf;    
 float H,S,L;   
  
 for (UINT i=0;i<loop;i++)   
 {   
  RGBtoHSL(*pRed,*pGrn,*pBlu,&H,&S,&L);   
  
  H += degHue;   
      S = (S*perSaturation/100.0f);   
  L = (L*perLuminosity/100.0f);   
  
      *cr = HSLtoRGB(H,S,L);   
  
  pRed+=4;   
      pGrn+=4;   
      pBlu+=4;   
  cr ++;   
 }   
 return TRUE;   
}   
  
void dibFilterEFFECT::RGBtoHSL(BYTE R,BYTE G,BYTE B,float* H,float* S,float* L)   
{   
 BYTE minval = min(R,min(G,B));   
   BYTE maxval = max(R,max(G,B));   
   float mdiff = float(maxval) - float(minval);   
   float msum  = float(maxval) + float(minval);   
      
   *L = msum / 510.0f;   
  
   if (maxval == minval)    
   {   
  *S = 0.0f;   
      *H = 0.0f;    
   }      
   else    
   {    
      float rnorm = (maxval - R) / mdiff;         
      float gnorm = (maxval - G) / mdiff;   
      float bnorm = (maxval - B) / mdiff;      
  
      *S = (*L <= 0.5f) ? (mdiff / msum) : (mdiff / (510.0f - msum));   
  
      if(R == maxval)    
   *H = 60.0f * (6.0f + bnorm - gnorm);   
      if(G == maxval)    
   *H = 60.0f * (2.0f + rnorm - bnorm);   
      if(B == maxval)    
   *H = 60.0f * (4.0f + gnorm - rnorm);   
      if (*H > 360.0f)    
   *H -= 360.0f;   
   }   
}   
  
COLORREF dibFilterEFFECT::HSLtoRGB(float H,float S,float L)   
{   
 BYTE r,g,b;   
    
 L = min(1,L);   
 S = min(1,S);   
  
 if(S == 0.0)   
 {   
  r = g = b = (BYTE)(255 * L);   
 }    
 else    
 {   
  float rm1, rm2;   
            
      if (L <= 0.5f)    
   rm2 = L + L * S;   
      else  
   rm2 = L + S - L * S;   
      rm1 = 2.0f * L - rm2;      
         
  r = HueToRGB(rm1, rm2, H + 120.0f);   
      g = HueToRGB(rm1, rm2, H);   
      b = HueToRGB(rm1, rm2, H - 120.0f);   
   }   
   return RGB(r,g,b);   
}   
  
BYTE dibFilterEFFECT::HueToRGB(float rm1,float rm2,float rh)   
{   
 while(rh > 360.0f)   
  rh -= 360.0f;   
 while(rh < 0.0f)   
  rh += 360.f;   
    
 if(rh < 60.0f)   
  rm1 = rm1 + (rm2 - rm1) * rh / 60.0f;   
 else if(rh < 180.0f)   
  rm1 = rm2;   
 else if(rh < 240.0f)   
  rm1 = rm1 + (rm2 - rm1) * (240.0f - rh) / 60.0f;   
      
 float n = rm1*255;   
 int m = min((int)n,255);   
 m = max(0,m);   
 return (BYTE)m;   
}  
//调整色调,参数范围:-180~180(度),=0不作调整
//调整饱和度,参数范围0~200(建议,最大值可>200),=100不作调整
//调整亮度,亮度参数范围0~200(建议,最大值可>200),=100不作调整
BOOL dibFilterEFFECT::AdjustHSL(int degHue,int perSaturation,int perLuminosity)
{
 if(!m_rdib->Data())
    return FALSE;

 if(perSaturation < 0 || perLuminosity < 0)
  return FALSE;

 if(degHue == 0 && perSaturation == 100 && perLuminosity == 100)
  return TRUE; //未作调整,直接返回
  
   LPBYTE pRed, pGrn, pBlu,pBuf=(LPBYTE)m_rdib->Data();
 UINT loop = m_rdib->Width() * m_rdib->Height();
 COLORREF *cr = (COLORREF*)m_rdib->Data();
   
   pRed=pBuf++;pGrn=pBuf++;pBlu=pBuf; 
 float H,S,L;

 for (UINT i=0;i<loop;i++)
 {
  RGBtoHSL(*pRed,*pGrn,*pBlu,&H,&S,&L);

  H += degHue;
      S = (S*perSaturation/100.0f);
  L = (L*perLuminosity/100.0f);

      *cr = HSLtoRGB(H,S,L);

  pRed+=4;
      pGrn+=4;
      pBlu+=4;
  cr ++;
 }
 return TRUE;
}

void dibFilterEFFECT::RGBtoHSL(BYTE R,BYTE G,BYTE B,float* H,float* S,float* L)
{
 BYTE minval = min(R,min(G,B));
   BYTE maxval = max(R,max(G,B));
   float mdiff = float(maxval) - float(minval);
   float msum  = float(maxval) + float(minval);
   
   *L = msum / 510.0f;

   if (maxval == minval) 
   {
  *S = 0.0f;
      *H = 0.0f; 
   }   
   else 
   { 
      float rnorm = (maxval - R) / mdiff;      
      float gnorm = (maxval - G) / mdiff;
      float bnorm = (maxval - B) / mdiff;  

      *S = (*L <= 0.5f) ? (mdiff / msum) : (mdiff / (510.0f - msum));

      if(R == maxval) 
   *H = 60.0f * (6.0f + bnorm - gnorm);
      if(G == maxval) 
   *H = 60.0f * (2.0f + rnorm - bnorm);
      if(B == maxval) 
   *H = 60.0f * (4.0f + gnorm - rnorm);
      if (*H > 360.0f) 
   *H -= 360.0f;
   }
}

COLORREF dibFilterEFFECT::HSLtoRGB(float H,float S,float L)
{
 BYTE r,g,b;
 
 L = min(1,L);
 S = min(1,S);

 if(S == 0.0)
 {
  r = g = b = (BYTE)(255 * L);
 } 
 else 
 {
  float rm1, rm2;
         
      if (L <= 0.5f) 
   rm2 = L + L * S;
      else
   rm2 = L + S - L * S;
      rm1 = 2.0f * L - rm2;   
      
  r = HueToRGB(rm1, rm2, H + 120.0f);
      g = HueToRGB(rm1, rm2, H);
      b = HueToRGB(rm1, rm2, H - 120.0f);
   }
   return RGB(r,g,b);
}

BYTE dibFilterEFFECT::HueToRGB(float rm1,float rm2,float rh)
{
 while(rh > 360.0f)
  rh -= 360.0f;
 while(rh < 0.0f)
  rh += 360.f;
 
 if(rh < 60.0f)
  rm1 = rm1 + (rm2 - rm1) * rh / 60.0f;
 else if(rh < 180.0f)
  rm1 = rm2;
 else if(rh < 240.0f)
  rm1 = rm1 + (rm2 - rm1) * (240.0f - rh) / 60.0f;
   
 float n = rm1*255;
 int m = min((int)n,255);
 m = max(0,m);
 return (BYTE)m;
}

 

 

实现了界面调色功能,接下来就得实现调色的设置界面,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了,代码很简单:

view plaincopy to clipboardprint?
LRESULT RingTrackBarEx::RingdowProc(HWND hWnd,RINGPARAMS param)   
{   
 switch(param.uMsg)   
 {   
  case WM_PRINTCLIENT:   
  case WM_PAINT:   
  {   
   RECT rc,rcc;   
   HDC hdc,hMemDC;   
   PAINTSTRUCT ps;   
   GetWindowRect(m_hWnd,&rc);   
   OffsetRect(&rc,-rc.left,-rc.top);   
   hdc = param.wParam?(HDC)param.wParam:BeginPaint(hWnd,&ps);   
   FillRect(hdc,&rc,m_brush);   
  
   hMemDC = CreateCompatibleDC(hdc);   
   SelectObject(hMemDC,m_hbmLine);   
   GetChannelRect(&rcc);   
   StretchBlt(hdc,rcc.left,(rc.bottom - m_sizeLine.cy)/2,rcc.right-rcc.left,m_sizeLine.cy,   
       hMemDC,0,0,m_sizeLine.cx,m_sizeLine.cy,SRCCOPY);   
   SelectObject(hMemDC,m_hbmThumb);   
   GetThumbRect(&rc);   
   BitBlt(hdc,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,hMemDC,0,0,SRCCOPY);   
  
   DeleteDC(hMemDC);   
   if(param.wParam == 0)   
    EndPaint(hWnd, &ps);   
   return 0;   
  }   
 }   
 return DefaultProc(param);   
}  
LRESULT RingTrackBarEx::RingdowProc(HWND hWnd,RINGPARAMS param)
{
 switch(param.uMsg)
 {
  case WM_PRINTCLIENT:
  case WM_PAINT:
  {
   RECT rc,rcc;
   HDC hdc,hMemDC;
   PAINTSTRUCT ps;
   GetWindowRect(m_hWnd,&rc);
   OffsetRect(&rc,-rc.left,-rc.top);
   hdc = param.wParam?(HDC)param.wParam:BeginPaint(hWnd,&ps);
   FillRect(hdc,&rc,m_brush);

   hMemDC = CreateCompatibleDC(hdc);
   SelectObject(hMemDC,m_hbmLine);
   GetChannelRect(&rcc);
   StretchBlt(hdc,rcc.left,(rc.bottom - m_sizeLine.cy)/2,rcc.right-rcc.left,m_sizeLine.cy,
       hMemDC,0,0,m_sizeLine.cx,m_sizeLine.cy,SRCCOPY);
   SelectObject(hMemDC,m_hbmThumb);
   GetThumbRect(&rc);
   BitBlt(hdc,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,hMemDC,0,0,SRCCOPY);

   DeleteDC(hMemDC);
   if(param.wParam == 0)
    EndPaint(hWnd, &ps);
   return 0;
  }
 }
 return DefaultProc(param);
}

 

因为调色函数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

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ringphone/archive/2010/01/15/5191809.aspx

0 0
原创粉丝点击