24位位图转化为灰度图

前两天刚刚开始做图像处理的东西,好不容易弄清了位图的基本格式,并尝试着编了一个二十四位位图转化为灰度图的API函数:

HDIB WINAPI DIBToGray(LPSTR lpDIB)
{
 // 获取位图的信息头
 LPBITMAPINFOHEADER lpDIBHdr;             // 指向BITMAPINFOHEADER的指针
 lpDIBHdr=(LPBITMAPINFOHEADER)lpDIB;
 LPSTR lpGray;

 // 计算位图图的信息头、调色板和图形数据的大小, 并给灰度图分配内存
 int dwInfo=lpDIBHdr->biSize;
 int dwPal=::DIBNumColors((LPSTR)lpDIBHdr)*sizeof(RGBQUAD);
 int dwData=lpDIBHdr->biSizeImage;
 int dwGrayPal=dwPal;
 if(dwPal==0)
 {
  dwGrayPal=256*sizeof(RGBQUAD);
  dwData=lpDIBHdr->biHeight*lpDIBHdr->biWidth;
  
 }
 int sizeTotal=dwInfo+dwGrayPal+dwData;
 HGLOBAL hGray=(HGLOBAL)::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeTotal);
 if (hGray==0)
 {
  return NULL;  //内存分配失败则返回NULL
 }
 lpGray = (LPSTR) ::GlobalLock(hGray);

 // 创建灰度图的颜色表  计算每个像素点的灰度值,即求平均即可
 LPLOGPALETTE lpGrayPal=(LPLOGPALETTE)(lpGray+40);

 char * lpBits=FindDIBBits(lpDIB);
 int rowLen=WIDTHBYTES(8*lpDIBHdr->biWidth);
 BYTE* lpGrayBits=(BYTE*)(lpGrayPal)+dwGrayPal;
 int aver=0;
 int i,j,k;

 if(24==lpDIBHdr->biBitCount)
 {
  lpGrayPal->palNumEntries=256;
  lpGrayPal->palVersion=PALVERSION;
  for(i=0;i<256;i++)
  { 
   lpGrayPal->palPalEntry[i].peBlue=i;
   lpGrayPal->palPalEntry[i].peGreen=i;
   lpGrayPal->palPalEntry[i].peRed=i;
   lpGrayPal->palPalEntry[i].peFlags=0;
  }
  for (i=0;i<lpDIBHdr->biHeight;i++)
  {
   for (j=0; j<rowLen; j++)
   {
    k=i*3*rowLen+3*j;
    lpGrayBits[i*rowLen+j]=(lpBits[k]+lpBits[k+1]+lpBits[k+2])/3;
   }
  }

 }

 // 创建灰度图的信息头
 LPBITMAPINFOHEADER lpGrayHdr=(LPBITMAPINFOHEADER)lpGray;
 memcpy(lpGrayHdr, lpDIBHdr, dwInfo);
 if(dwPal==0)
 {
  lpGrayHdr->biSizeImage=sizeTotal;
  lpGrayHdr->biBitCount=8;
  lpGrayHdr->biClrUsed=256;
 }

 ::GlobalUnlock(hGray);
 return (HDIB)hGray;

}

 

在 (BYTE*)(lpGrayPal)+dwGrayPal; 中,最初由于写成了(BYTE*)(lpGrayPal+dwGrayPal);造成了分配的内存不够,一开始是尝试着多分配了一些内存,但是读出来的图像发生了错位,后来发现,dwGrayPal事实上已经是代表了dwGrayPal*sizeof(LOGPALETTE)的大小了,故而发生了错位。由此可以发现,指针增量的类型是十分重要的!

 

 

 

 

 

 

 

      本来以为前面的代码已经算是做完了,但是今天上午换了张图片时,发现得到的灰度图发生了严重的倾斜,并且在第二次灰度化(第二次调用这个函数的时候,发现就是一张纯黑色的图片)。分析原因如下:

纯黑色是因为第二次进入这个函数的时候,只是重新复制了信息头,而调色板和图片数据都没有进行任何操作。而图片倾斜,则是因为位图在存储的时候,如果每一行的像素值所占的字节数为4的倍数时,则正常存储;否则要在后端补零。昨天试验的图片因为其行数恰好是4的倍数,所以没有出现异常,而今天试验的图片不是4的倍数,所以发生了倾斜。

下面给出校正后的代码:

 

 

 

 

 

 

 

 

HDIB WINAPI DIBToGray(LPSTR lpDIB)
{
 // 获取位图的信息头
 LPBITMAPINFOHEADER lpDIBHdr;             // 指向BITMAPINFOHEADER的指针
 lpDIBHdr=(LPBITMAPINFOHEADER)lpDIB;
 LPSTR lpGray;

 // 计算位图图的信息头、调色板和图形数据的大小, 并给灰度图分配内存
 int dwInfo=lpDIBHdr->biSize;
 int dwPal=::DIBNumColors((LPSTR)lpDIBHdr)*sizeof(RGBQUAD);
 int dwData=lpDIBHdr->biSizeImage;
 int dwGrayPal=dwPal;
 int rowLenDes=WIDTHBYTES(8*lpDIBHdr->biWidth);
 if(dwPal==0)
 {
  dwGrayPal=256*sizeof(RGBQUAD);
  dwData=lpDIBHdr->biHeight*rowLenDes;
  
 }
 int sizeTotal=dwInfo+dwGrayPal+dwData;
 HGLOBAL hGray=(HGLOBAL)::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeTotal);
 if (hGray==0)
 {
  return NULL;  //内存分配失败则返回NULL
 }
 lpGray = (LPSTR) ::GlobalLock(hGray);

 // 创建灰度图的颜色表  计算每个像素点的灰度值,即求平均即可
 LPLOGPALETTE lpGrayPal=(LPLOGPALETTE)(lpGray+40);

 char * lpBits=FindDIBBits(lpDIB);
 int rowLenSr=0;
 BYTE* lpGrayBits=(BYTE*)(lpGrayPal)+dwGrayPal;
 int i,j,k;

 if(24==lpDIBHdr->biBitCount)
 {
  lpGrayPal->palNumEntries=256;
  lpGrayPal->palVersion=PALVERSION;
  for(i=0;i<256;i++)
  { 
   lpGrayPal->palPalEntry[i].peBlue=i;
   lpGrayPal->palPalEntry[i].peGreen=i;
   lpGrayPal->palPalEntry[i].peRed=i;
   lpGrayPal->palPalEntry[i].peFlags=0;
  }
  rowLenSr=WIDTHBYTES(24*lpDIBHdr->biWidth);
  for (i=0;i<lpDIBHdr->biHeight;i++)
  {
   for (j=0; j<lpDIBHdr->biWidth; j++)
   {
    k=i*rowLenSr+3*j;
    lpGrayBits[i*rowLenDes+j]=(lpBits[k]+lpBits[k+1]+lpBits[k+2])/3;
   }
   for (j=lpDIBHdr->biWidth;j<rowLenDes;j++)
   {
    lpGrayBits[i*rowLenDes+j]=0;
   }
  }

 }
 else
 {
  memcpy((LPSTR)lpGrayPal,(LPSTR)lpDIBHdr+40,dwGrayPal+dwData);
 }
 // 创建灰度图的信息头
 LPBITMAPINFOHEADER lpGrayHdr=(LPBITMAPINFOHEADER)lpGray;
 memcpy(lpGrayHdr, lpDIBHdr, dwInfo);
 if(dwPal==0)
 {
  lpGrayHdr->biSizeImage=dwData;
  lpGrayHdr->biBitCount=8;
  lpGrayHdr->biClrUsed=256;
 }

 ::GlobalUnlock(hGray);
 return (HDIB)hGray;
 }

 

 

 

 

后来发现前面所给的代码得到的灰度图并不理想,查看相关书籍后才发现灰度图转化并不是简单地将RGB三个分量的值求平均,具体说明如下:除了RGB颜色表示方法外,还有一种称作YUV的表示方法,其中的Y分量是表示亮度,也就是灰度图中的值,而UV表示色差信号。RGB和YUV二者间的转化关系为:

Y=0.299*R+0.587*G+0.114*B

 对于真彩色图则直接对数据进行处理,而对于其他类型的位图,则只要把调色板中的RGB分量全部用Y来替代就可以了,并给出更新后的代码:

 

 

 

 

 

HDIB WINAPI DIBToGray(LPSTR lpDIB)
{
 // 获取位图的信息头
 LPBITMAPINFOHEADER lpDIBHdr;             // 指向BITMAPINFOHEADER的指针
 lpDIBHdr=(LPBITMAPINFOHEADER)lpDIB;
 LPSTR lpGray;

 // 计算位图图的信息头、调色板和图形数据的大小, 并给灰度图分配内存
 int dwInfo=lpDIBHdr->biSize;
 int dwPal=::DIBNumColors((LPSTR)lpDIBHdr)*sizeof(RGBQUAD);
 int dwData=lpDIBHdr->biSizeImage;
 int dwGrayPal=dwPal;
 int rowLenDes=WIDTHBYTES(8*lpDIBHdr->biWidth);
 if(dwPal==0)
 {
  dwGrayPal=256*sizeof(RGBQUAD);
  dwData=lpDIBHdr->biHeight*rowLenDes;
  
 }
 int sizeTotal=dwInfo+dwGrayPal+dwData;
 HGLOBAL hGray=(HGLOBAL)::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeTotal);
 if (hGray==0)
 {
  return NULL;  //内存分配失败则返回NULL
 }
 lpGray = (LPSTR) ::GlobalLock(hGray);

 // 创建灰度图的颜色表  计算每个像素点的灰度值,即求平均即可
 LPLOGPALETTE lpGrayPal=(LPLOGPALETTE)(lpGray+40);

 unsigned char * lpBits=(unsigned char *)FindDIBBits(lpDIB);
 int rowLenSr=0;
 BYTE* lpGrayBits=(BYTE*)(lpGrayPal)+dwGrayPal;
 int i,j,k;

 if(24==lpDIBHdr->biBitCount)
 {
  lpGrayPal->palNumEntries=256;
  lpGrayPal->palVersion=PALVERSION;
  for(i=0;i<256;i++)
  { 
   lpGrayPal->palPalEntry[i].peBlue=i;
   lpGrayPal->palPalEntry[i].peGreen=i;
   lpGrayPal->palPalEntry[i].peRed=i;
   lpGrayPal->palPalEntry[i].peFlags=0;
  }
  rowLenSr=WIDTHBYTES(24*lpDIBHdr->biWidth);
  for (i=0;i<lpDIBHdr->biHeight;i++)
  {
   for (j=0; j<lpDIBHdr->biWidth; j++)
   {
    
    k=i*rowLenSr+3*j;
    lpGrayBits[i*rowLenDes+j]=(BYTE)(0.114*lpBits[k]+0.587*lpBits[k+1]+0.299*lpBits[k+2]);
   }
   for (j=lpDIBHdr->biWidth;j<rowLenDes;j++)
   {
    lpGrayBits[i*rowLenDes+j]=0;
   }
  }

 }
 else
 {
  LPLOGPALETTE lpPal=(LPLOGPALETTE)((LPSTR)lpDIBHdr+40);
  lpGrayPal->palNumEntries=DIBNumColors((LPSTR)lpDIBHdr);
  lpGrayPal->palVersion=PALVERSION;
  unsigned char r,g,b,aver;
  for(i=0;i<lpGrayPal->palNumEntries;i++)
  { 
   b=(unsigned char)lpPal->palPalEntry[i].peBlue;
   g=(unsigned char)lpPal->palPalEntry[i].peGreen;
   r=(unsigned char)lpPal->palPalEntry[i].peRed;
   aver=(unsigned char)(0.114*b+0.587*g+0.299*r);
   lpGrayPal->palPalEntry[i].peBlue=aver;
   lpGrayPal->palPalEntry[i].peGreen=aver;
   lpGrayPal->palPalEntry[i].peRed=aver;
   lpGrayPal->palPalEntry[i].peFlags=0;
  }

  memcpy((LPSTR)lpGrayBits,(LPSTR)lpBits,dwData);
 }

 // 创建灰度图的信息头
 LPBITMAPINFOHEADER lpGrayHdr=(LPBITMAPINFOHEADER)lpGray;
 memcpy(lpGrayHdr, lpDIBHdr, dwInfo);
 if(dwPal==0)
 {
  lpGrayHdr->biSizeImage=dwData;
  lpGrayHdr->biBitCount=8;
  lpGrayHdr->biClrUsed=256;
 }

 ::GlobalUnlock(hGray);
 return (HDIB)hGray;
}

 

当然,若是得到灰度图可以覆盖原来的24位位图,则可以直接在已有的图片存储区操作,不用开辟新的存储区,此时函数的传入参数可以改为引用类型或输出参数改为指针类型。

你可能感兴趣的:(null,存储,byte,图形,图像处理,winapi)