赞助

Duilib 创建不规则窗口(转载)

方法一:

转载:http://blog.csdn.net/chenlycly/article/details/46447297

转载:http://blog.csdn.net/harvic880925/article/details/8925650

方法二:

转载:http://blog.csdn.net/xdrt81y/article/details/22063479

 

搞Windows程序的人尽皆知分层窗口能够实现很多不错的效果,之前看过一些异形窗口的实现,所以就手痒也想自己搞一个玩一玩。自己动手实现过程才发现还是有不少问题的。

基本思路是:
1.将窗口扩展属性设置为分层属性WS_EX_LAYERED。
2.选一张透明的png图片,并将其加载进来。
3.创建与窗口兼容的内存设备上下文,以及兼容位图,将兼容位图选入兼容设备上下文。
4.将png图片绘制到内存设备上下文中。
5.设置BLENDFUNCTION结构,调用UpdateLayeredWindow。

 

第一步设置窗口的分层属性比较简单:

windowStyle = GetWindowLong(hWnd, GWL_EXSTYLE);

windowStyle = windowStyle | WS_EX_LAYERED;

SetWindowLong(hWnd, GWL_EXSTYLE, windowStyle);

 

第二步是将png图片加载到程序中,ATL的CImage和GDI+的Image这两个类比较常用。使用 CImage直接通过Load方法加载绝对路径图片或者内存中的图片,我这里就是使用CImage类实现。代码:

//CImage类方式加载图片
CImage img;
img.Load(TEXT("绝对路径png图片")); //将图片与类关联起来
然后是使用Image类方式:

//Image类方式加载图片
Image* pImage = Image::FromFile(_T("绝对路径png图片"));

 

第三步比较繁琐点。加载了图片后就需要创建一个位图句柄HBITMAP,创建位图句柄有两种方式:CreateCompatibleBitmap和CreateDIBSection这两个函数。先介绍下这两个函数。

HBITMAP CreateCompatibleBitmap( HDC hdc, // handle to DC
int nWidth, // width of bitmap, in pixels
int nHeight // height of bitmap, in pixels);
HBITMAP CreateDIBSection( HDC hdc, // handle to DC
    CONST BITMAPINFO *pbmi, // bitmap data
    UINT iUsage, // data type indicator
    VOID **ppvBits, // bit values
    HANDLE hSection, // handle to file mapping object
    DWORD dwOffset // offset to bitmap bit values
);

首先看看CreateCompatibleBitmap函数的代码:

//CreateCompatibleBitmap函数创建
        hdc = GetDC(hWnd); //hWnd为需要分层窗口的句柄
        hdcMem = CreateCompatibleDC(hdc); //创建与hdc相兼容的内存句柄
        hBitmap = CreateCompatibleBitmap(hdc, sz.cx, sz.cy); //创建与hdc相兼容的位图句柄
        SelectObject(hdcMem,(HGDIOBJ)hBitmap); //将位图选入内存句柄作为画板

然后是使用CreateDIBSection函数的代码:

//CreateDIBSection函数创建
        hdc = GetDC(hWnd);
        hdcMem = CreateCompatibleDC(hdc);

        BITMAPINFO bitmapinfo;
        bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        bitmapinfo.bmiHeader.biBitCount = 32;
        bitmapinfo.bmiHeader.biHeight = sz.cy;
        bitmapinfo.bmiHeader.biWidth = sz.cx;
        bitmapinfo.bmiHeader.biPlanes = 1;
        bitmapinfo.bmiHeader.biCompression=BI_RGB;
        bitmapinfo.bmiHeader.biXPelsPerMeter=0;
        bitmapinfo.bmiHeader.biYPelsPerMeter=0;
        bitmapinfo.bmiHeader.biClrUsed=0;
        bitmapinfo.bmiHeader.biClrImportant=0;
        bitmapinfo.bmiHeader.biSizeImage = bitmapinfo.bmiHeader.biWidth * bitmapinfo.bmiHeader.biHeight * bitmapinfo.bmiHeader.biBitCount / 8;

        hBitmap = ::CreateDIBSection(hdcMem,&bitmapinfo, 0,NULL, 0, 0);
        SelectObject(hdcMem,(HGDIOBJ)hBitmap)

第四步,将png图片绘制到内存设备上下文中。这一步根据前面加载图片的两种方式对应不同的绘制函数。

CImage类方式代码:

//CImage类方式代码
//将img关联的png图片绘制到内存句柄中
img.Draw(hdcMem, 0, 0, sz.cx, sz.cy, 0, 0, sz.cx, sz.cy);
//hdcMem为内存兼容句柄,之后四个参数表明了在内存兼容句柄中绘制的位置,最后四个参数表示了img关联的png图片的位置

然后是Image类方式处理代码:

//Image类方式代码
Graphics g(hMemDC);
g.DrawImage( pImage, 0, 0);

 

第五步:设置BLENDFUNCTION结构,调用UpdateLayeredWindow。

设置BLENDFUNCTION结构代码如下:

BLENDFUNCTION  bf;
bf.AlphaFormat = AC_SRC_ALPHA;//源位图具有Alpha通道
bf.BlendFlags = 0;//必须为0
bf.BlendOp = AC_SRC_OVER;//
bf.SourceConstantAlpha = 255;//设置透明度

然后就是调用UpdateLayeredWindow函数了

UpdateLayeredWindow(hWnd, hdc, NULL, &sz, hdcMem, &pt, NULL, &bf, ULW_ALPHA);

看看MSDN对UpdateLayeredWindow函数的介绍

BOOL UpdateLayeredWindow(
    HWND hwnd,//需要分层的窗口句柄
    HDC hdcDst, //需要分层窗口设备句柄
    POINT *pptDst,//窗口位置不发生变化可以设置为NULL
    SIZE *psize,//窗口大小不发生变化可以设置为NULL
    HDC hdcSrc,//绘制源的设备句柄
    POINT *pptSrc,//
    COLORREF crKey,//指定一个透明色,使用ULW_COLORKEY标志时有效,也就是说crKey为白色时候,那么位图上所有白色的地方均为透明,其他地方不透明
    BLENDFUNCTION *pblend,//之前介绍过了
    DWORD dwFlags//ULW_ALPHA使用Alpha通道,ULW_COLORKEY使用crKey作为透明色,ULW_OPAQUE不透明
);

这样异形窗口就算完成了。

 

整理以下代码如下:

LONG windowStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
windowStyle = windowStyle | WS_EX_LAYERED;
SetWindowLong(hWnd, GWL_EXSTYLE, windowStyle);

CImage img;
img.Load(TEXT("png图片"));

SIZE sz;//图片大小
sz.cx = img.GetWidth();
sz.cy = img.GetHeight();

SetWindowPos(hWnd, NULL, 0, 0, sz.cx, sz.cy, SWP_NOREDRAW);//将窗口大小设置为图片大小使之相互合适

HDC hdc = GetDC(hWnd);//获取窗口设备句柄
HDC hdcMem = CreateCompatibleDC(hdc);//创建一个与hdc相兼容的内存设备句柄
HBITMAP    hBitmap = CreateCompatibleBitmap(hdc, sz.cx, sz.cy);
SelectObject(hdcMem,(HGDIOBJ)hBitmap);
img.Draw(hdcMem, 0, 0, sz.cx, sz.cy, 0, 0, sz.cx, sz.cy);

POINT pt;
pt.x = 0;
pt.y = 0;

BLENDFUNCTION  bf;
bf.AlphaFormat = AC_SRC_ALPHA;
bf.BlendFlags = 0;
bf.BlendOp = AC_SRC_OVER;
bf.SourceConstantAlpha = 255;

UpdateLayeredWindow(hWnd, hdc, NULL, &sz, hdcMem, &pt, NULL, &bf, ULW_ALPHA);
ReleaseDC(hWnd, hdc);

说说遇到的问题,最开始就碰到了一个问题发现png图片没有办法产生异形的效果,然后才发现原来我使用的png图片不是透明png图片。

当png透明图片问题解决后,发现窗口出现后,本来那个应该是透明的地方却是白色不透明的了。这个问题网上也是有介绍的,我在这里得到了答案。下面我把原因重新贴一下:

PNG图片的透明背景总是一片白色,后来才发现这其实是微软GDI+的设计问题,PNG图片是ARGB,使用GDI+载入图片的时候,GDI+会默认已经进行了预剩运算(PARGB),即每象素的实际值是已经和ALPHA值按比例相乘的结果,实际上它根本就没有做预乘,在使用透明图片的象素ALPHA通道的时候,CImage内部正是调用的AlphaBlend,没有预乘的图当作预乘的图片处理的结果就是这相当于一张和纯白背景进行了预剩,所以图象总是出现白色背景。

下面给出解决这个问题的代码,代码来源这里,处理还是比较简单的:

if (pImage->GetBPP() == 32) //确认该图像包含Alpha通道
   {
       for (inti=0; i<pImage->GetWidth();i++)
       {
            for(int j=0; j<pImage->GetHeight(); j++)
            {
                byte*pByte = (byte*)pImage->GetPixelAddress(i, j);
                pByte[0]= pByte[0] * pByte[3]/ 255;
                pByte[1]= pByte[1] * pByte[3]/ 255;
                pByte[2]= pByte[2] * pByte[3]/ 255;
            }
       }
   }

这样异形窗口也算是完成了,但是这样生成的exe文件需要依赖外部的png图片,所以我应该把那张png图片放到资源文件中,然后直接生成的exe就包含了那张png图片。这就涉及到了如何从资源中加载图片的问题。

 

VC中的CImage类的介绍

转载:http://www.cnblogs.com/lemon0823/archive/2013/03/11/2953551.html

VC++)一个比CBitmap更优秀的类    今天查找如何实现DIB位图的背景透明的资料时,发现有论坛上有人问到CImage类的透明问题,于是对其很感兴趣。于是搜集了一些资料,发现以下内容对该类的介绍和使用介绍比较详细,于是就拷贝至此,以备后用。

 

  Visual C++的CBitmap类的功能是比较弱的,它只能显示出在资源中的图标、位图、光标以及图元文件的内容,而不像VB中的Image控件可以显示出绝大多数的外部图像文件(BMP、GIF、JPEG等)。如果想要在对话框或其他窗口中显示外部图像文件则只能借助于第三方提供的控件或代码,未免过于繁琐.

   现在,.net引入了一个功能非常强大的新类 ----- CImage.有了CImage类,Visual C++在图像方面的缺憾将一去不复返。CImage是MFC和ATL共享的新类,它能从外部磁盘中调入一个JPEG、GIF、BMP和PNG格式的图像文件加以显示,而且这些文件格式可以相互转换。例如通过简单的几句,就可以实现CImage类和CBitmap类实例的:

HBITMAP hBitmap=image.Detach();
CBitmap bmp;
bmp.Attach(hBitmap);

这样一来,就又回归到以前操纵CBitmap的方式了.CImage本身封装了DIB(设备无关位图)的功能,因而能够处理每个位图像素。

它具有下列最酷特性:
  1、AlphaBlend支持像素级的颜色混合,从而实现透明和半透明的效果。
  2、PlgBlt能使一个矩形区域的位图映射到一个平行四边形区域中,而且还可能使用位屏蔽操作。
  3、TransparentBlt在目标区域中产生透明图像,SetTransparentColor用来设置某种颜色是透明色。
  4、MaskBlt在目标区域中产生源位图与屏蔽位图合成的效果。

由于CImage在不同的Windows操作系统中其某些性能是不一样的,因此在使用时要特别注意。例如,CImage::PlgBlt和 CImage::MaskBlt只能在 Windows NT 4.0 或更高版本中使用,但不能运行在Windows 95/98 应用程序中。CImage::AlphaBlend和CImage::TransparentBlt也只能在 Windows 2000/98或其更高版本中使用。即使在Windows 2000运行程序还必须将stdafx.h文件中的WINVER和_WIN32_WINNT的预定义修改成0x0500才能正常使用。

使用CImage的一般方法

  使用CImage的一般方法是这样的过程:

  (1) 打开应用程序的stdafx.h文件添加CImage类的包含文件:

#include <atlimage.h>

  (2) 定义一个CImage类对象,然后调用CImage::Load方法装载一个外部图像文件。

  (3) 调用CImage::Draw方法绘制图像。Draw方法具有如下定义:

BOOL Draw( HDC hDestDC, int xDest, int yDest,
int nDestWidth, int nDestHeight, int xSrc, int ySrc,
int nSrcWidth, int nSrcHeight );
BOOL Draw( HDC hDestDC, const RECT& rectDest, const RECT& rectSrc );
BOOL Draw( HDC hDestDC, int xDest, int yDest );
BOOL Draw( HDC hDestDC, const POINT& pointDest );
BOOL Draw( HDC hDestDC, int xDest, int yDest,
int nDestWidth, int nDestHeight );
BOOL Draw( HDC hDestDC, const RECT& rectDest );


  其中,hDestDC用来指定绘制的目标设备环境句柄,(xDest, yDest)和pointDest用来指定图像显示的位置,这个位置和源图像的左上角点相对应。nDestWidth和nDestHeight分别指定图像要显示的高度和宽度,xSrc、ySrc、nSrcWidth和nSrcHeight用来指定要显示的源图像的某个部分所在的位置和大小。 rectDest和rectSrc分别用来指定目标设备环境上和源图像所要显示的某个部分的位置和大小。

  需要说明的是,Draw方法综合了StretchBlt、TransparentBlt和AlphaBlend函数的功能。默认时,Draw的功能和 StretchBlt相同。但当图像含有透明色或Alpha通道时,它的功能又和TransparentBlt、AlphaBlend相同。因此,在一般情况下,我们都应该尽量调用CImage::Draw方法来绘制图像。

  例如,下面的示例Ex_Image是实现这样的功能:当选择"文件"ò"打开"菜单命令后,弹出一个文件打开对话框。当选定一个图像文件后,就会在窗口客户区中显示该图像文件内容。这个示例的具体步骤如下:

  (1) 创建一个默认的单文档程序项目Ex_Image。

  (2) 打开stdafx.h文件中添加CImage类的包含文件atlimage.h。

  (3) 在CEx_ImageView类添加ID_FILE_OPEN的COMMAND事件映射程序,并添加下列代码:

void CEx_ImageView::OnFileOpen()
{
 CString strFilter;
 CSimpleArray<GUID> aguidFileTypes;
 HRESULT hResult;

 // 获取CImage支持的图像文件的过滤字符串
 hResult = m_Image.GetExporterFilterString(strFilter,aguidFileTypes,
_T( "All Image Files") );
 if (FAILED(hResult)) {
  MessageBox("GetExporterFilter调用失败!");
  return;
 }
 CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter);
 if(IDOK != dlg.DoModal()) 
  return;

 m_Image.Destroy();//detach the orignal bitmap of the image if exits and destroy it.
 // 将外部图像文件装载到CImage对象中
 hResult = m_Image.Load(dlg.GetFileName());
 if (FAILED(hResult)) {
  MessageBox("调用图像文件失败!");
  return;
 }

 // 设置主窗口标题栏内容
 CString str;
 str.LoadString(AFX_IDS_APP_TITLE);
 AfxGetMainWnd()->SetWindowText(str + " - " +dlg.GetFileName());

 Invalidate(); // 强制调用OnDraw
}


  (4) 定位到CEx_ImageView::OnDraw函数处,添加下列代码:

void CEx_ImageView::OnDraw(CDC* pDC)
{
 CEx_ImageDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);
 if (!m_Image.IsNull()) {
  m_Image.Draw(pDC->m_hDC,0,0);
 }
}


  (5) 打开Ex_ImageView.h文件,添加一个公共的成员数据m_Image:

public:
CImage m_Image;


  (6) 编译并运行。单击"打开"工具按钮,在弹出的对话框中指定一个图像文件后,单击"打开"按钮,其结果如图7.21所示。

将图片用其它格式保存

  CImage::Save方法能将一个图像文件按另一种格式来保存,它的原型如下: HRESULT Save( LPCTSTR pszFileName, REFGUID guidFileType= GUID_NULL); 
  其中,pszFileName用来指定一个文件名,guidFileType用来指定要保存的图像文件格式,当为GUID_NULL时,其文件格式由文件的扩展名来决定,这也是该函数的默认值。它还可以是GUID_BMPFile(BMP文件格式)、GUID_PNGFile(PNG文件格式)、 GUID_JPEGFile(JPEG文件格式)和GUID_GIFFile(GIF文件格式)。

  例如,下面的过程是在Ex_Image示例基础上进行的,我们在CEx_ImageView类添加ID_FILE_SAVE_AS的COMMAND事件映射程序,并添加下列代码:

void CEx_ImageView::OnFileSaveAs()
{
 if (m_Image.IsNull()) {
  MessageBox("你还没有打开一个要保存的图像文件!");
  return;
 }

 CString strFilter;
 strFilter = "位图文件|*.bmp|JPEG 图像文件|*.jpg| \
GIF 图像文件|*.gif|PNG 图像文件|*.png||";
 CFileDialog dlg(FALSE,NULL,NULL,NULL,strFilter);
 if ( IDOK != dlg.DoModal()) 
  return;

 // 如果用户没有指定文件扩展名,则为其添加一个
 CString strFileName;
 CString strExtension;

 strFileName = dlg.m_ofn.lpstrFile;
 if (dlg.m_ofn.nFileExtension == 0) 
 {
  switch (dlg.m_ofn.nFilterIndex)
  {
   case 1:
    strExtension = "bmp"; break;
   case 2:
    strExtension = "jpg"; break;
   case 3:
    strExtension = "gif"; break;
   case 4:
    strExtension = "png"; break;
   default:
    break;
  }
  strFileName = strFileName + '.' + strExtension;
 }

 // 图像保存
 HRESULT hResult = m_Image.Save(strFileName);
 if (FAILED(hResult)) 
  MessageBox("保存图像文件失败!");

将图片用其它格式保存

  CImage::Save方法能将一个图像文件按另一种格式来保存,它的原型如下: HRESULT Save( LPCTSTR pszFileName, REFGUID guidFileType= GUID_NULL); 
  其中,pszFileName用来指定一个文件名,guidFileType用来指定要保存的图像文件格式,当为GUID_NULL时,其文件格式由文件的扩展名来决定,这也是该函数的默认值。它还可以是GUID_BMPFile(BMP文件格式)、GUID_PNGFile(PNG文件格式)、 GUID_JPEGFile(JPEG文件格式)和GUID_GIFFile(GIF文件格式)。

  例如,下面的过程是在Ex_Image示例基础上进行的,我们在CEx_ImageView类添加ID_FILE_SAVE_AS的COMMAND事件映射程序,并添加下列代码:

void CEx_ImageView::OnFileSaveAs()
{
 if (m_Image.IsNull()) {
  MessageBox("你还没有打开一个要保存的图像文件!");
  return;
 }

 CString strFilter;
 strFilter = "位图文件|*.bmp|JPEG 图像文件|*.jpg| \
GIF 图像文件|*.gif|PNG 图像文件|*.png||";
 CFileDialog dlg(FALSE,NULL,NULL,NULL,strFilter);
 if ( IDOK != dlg.DoModal()) 
  return;

 // 如果用户没有指定文件扩展名,则为其添加一个
 CString strFileName;
 CString strExtension;

 strFileName = dlg.m_ofn.lpstrFile;
 if (dlg.m_ofn.nFileExtension == 0) 
 {
  switch (dlg.m_ofn.nFilterIndex)
  {
   case 1:
    strExtension = "bmp"; break;
   case 2:
    strExtension = "jpg"; break;
   case 3:
    strExtension = "gif"; break;
   case 4:
    strExtension = "png"; break;
   default:
    break;
  }
  strFileName = strFileName + '.' + strExtension;
 }

 // 图像保存
 HRESULT hResult = m_Image.Save(strFileName);
 if (FAILED(hResult)) 
  MessageBox("保存图像文件失败!");
变成黑白图片

  由于许多图像文件使用颜色表来发挥显示设备的色彩显示能力,因而将一张彩色图片变成黑色图片时需要调用CImage::IsIndexed来判断是否使用颜色表,若是则修改颜色表,否则直接将像素进行颜色设置。例如下面的代码: void CEx_ImageView::MakeBlackAndwhite(CImage* image)
{
 if (image->IsNull()) return;

 if (!image->IsIndexed()) {
  // 直接修改像素颜色
  COLORREF pixel;
  int maxY = image->GetHeight(), maxX = image->GetWidth();
  byte r,g,b,avg;
  for (int x=0; x<maxX; x++) {
   for (int y=0; y<maxY; y++) {
    pixel = image->GetPixel(x,y);
    r = GetRValue(pixel);
    g = GetGValue(pixel);
    b = GetBValue(pixel);
    avg = (int)((r + g + b)/3);
    image->SetPixelRGB(x,y,avg,avg,avg);
   }
  }
 } else {
  // 获取并修改颜色表
  int MaxColors = image->GetMaxColorTableEntries();
  RGBQUAD* ColorTable;
  ColorTable = new RGBQUAD[MaxColors];
  image->GetColorTable(0,MaxColors,ColorTable);
  for (int i=0; i<MaxColors; i++)
  {
   int avg = (ColorTable[i].rgbBlue + ColorTable[i].rgbGreen + ColorTable[i].rgbRed)/3;
   ColorTable[i].rgbBlue = avg;
   ColorTable[i].rgbGreen = avg;
   ColorTable[i].rgbRed = avg;
  }
  image->SetColorTable(0,MaxColors,ColorTable);
  delete(ColorTable);
 }
}

       对于CImage这个类,自己也尝试着做了试验,发现在我当前用的系统(Vista系统)下作了BMP、JPG位图的显示以及指定颜色透明的简单测试,发现此类实现这些功能比较简单,使用起来也比较方便。

       另外发现,对图像进行缩放显示时,显示出来的图像存在颜色失真。此时,只需在调用image.TransparentBlt之前,设置将要显示位图的DC的缩放模式就可以解决问题了。即:

       pDC->SetStretchMode(COLORONCOLOR);

       image.TransparentBlt(pDC->GetSafeHdc(),0,0,200,200,RGB(0, 0, 0));

       但是,由于刚刚接触这个类,对它的一些方法的兼容性存在一些怀疑和担心。比如,它的实现使指定颜色透明的方法TransparentBlt能否在Windows2000及以上的Windows系统中都正常实现呢?曾经用过MFC中的CDC的实现指定颜色透明的函数TransparentBlt,在Vista系统下运行正常,可是在XP系统下却出现了问题,结果只好放弃该函数而另找方法。不知道这个类是否也存在这样的问题。

      总之,CImage类功能可能确实是很强大,但是其使用时的系统兼容性问题不知道会怎么样,还需要进一步研究。

 

posted @ 2016-11-26 22:59  车臣  阅读(1428)  评论(0编辑  收藏  举报