图像处理 - 打开图片

图像世界是五彩缤纷的,首先需要介绍一下位图和调色板的概念,普通的显示器屏幕是由许许多多点构成的,我们称之为象素。显示时采用扫描的方法:电子枪每次从左到右扫描一行,为每个象素着色,然后从上到下这样扫描若干行,就扫过了一屏。为了防止闪烁,每秒要重复上述过程几十次。例如我们常说的屏幕分辨率为640×480,刷新频率为70Hz,意思是说每行要扫描640个象素,一共有480行,每秒重复扫描屏幕70次。

  我们知道,自然界中的所有颜色都可以由红、绿、蓝(RGB)组合而成。有的颜色含有红色成分多一些,如深红;有的含有红色成分少一些,如浅红。针对含有红色成分的多少,可以分成0255256个等级,0级表示不含红色成分;255级表示含有100%的红色成分。同样,绿色和蓝色也被分成256级。这种分级概念称为量化。

  这样,根据红、绿、蓝各种不同的组合我们就能表示出256×256×256,约1600万种颜色。这么多颜色对于我们人眼来说已经足够丰富了。


  这张RGB的表,就是我们常说的调色板(Palette),另一种叫法是颜色查找表LUT(Look Up Table),似乎更确切一些。Windows位图中便用到了调色板技术。其实不光是Windows位图,许多图象文件格式如pcxtifgif等都用到了。所以很好地掌握调色板的概念是十分有用的。

  有一种图,它的颜色数高达256×256×256种,也就是说包含我们上述提到的RGB颜色表示方法中所有的颜色,这种图叫做真彩色图(true color)。真彩色图并不是说一幅图包含了所有的颜色,而是说它具有显示所有颜色的能力,即最多可以包含所有的颜色。表示真彩色图时,每个象素直接用RGB三个分量字节表示,而不采用调色板技术。原因很明显:如果用调色板,表示一个象素也要用24位,这是因为每种颜色的索引要用24(因为总共有224种颜色,即调色板有224),和直接用RGB三个分量表示用的字节数一样,不但没有任何便宜,还要加上一个256×256×256×3个字节的大调色板。所以真彩色图直接用RGB三个分量表示,它又叫做24位色图。

  bmp文件大体上分成四个部分:


  文件头结构的长度是固定的,为14个字节(WORD为无符号16位整数,DWORD为无符号32位整数)。

  信息头结构的长度是固定的,为40个字节(LONG32位整数)。

  调色板其实是个数组,共有biClrUsed个元素(如果该值为零,则有2biBitCount个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节。

  对于用到调色板的位图,图象数据就是该象素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的RGB值。

  要注意两点:

  (1)   每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍biSizeImage时已经提到了。

  (2)   一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图象最下面一行的左边第一个象素,然后是左边第二个象素……接下来是倒数第二行左边第一个象素,左边第二个象素……依次类推,最后得到的是最上面一行的最右一个象素。

  接下来就是打开bmp位图了,这里我选择了VC里面的单文档程序,通过打开按钮打开bmp图像。在Doc里面加载图像,在View里面显示图像,下面贴代码:

  Doc.h :

class CMyDoc : public CDocument
{
public:
	//bpm文件的格式:1 文件头  2信息头 (1:真彩色,没有调色板;2:索引图像:有调色板);
	//3 数据
	//调色板:
	BITMAPFILEHEADER *m_FileHeader;//文件头
     BITMAPINFOHEADER *m_InfoHeader;//信息头
	RGBQUAD *pallete;//调色板
	BYTE * bmpdata;      //图像数据的缓冲区
  Doc.cpp :

CMyDoc::CMyDoc()
{
	// TODO: add one-time construction code here
	//指针初始化
	m_FileHeader=NULL;
	m_InfoHeader=NULL;
	pallete=NULL;
	bmpdata=NULL;
}
void CMyDoc::OnFileOpen() 
{
	// TODO: Add your command handler code here

	CString FileName;//保存文件的文件名
	static char szFilter[]="Bitmap Files (*.bmp)|*.bmp|All files(*.*)|*.*||";	
	
	CFileDialog m_FileDialog(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,("Bitmap File(*.bmp)|*.bmp"),NULL);//文件对话框
	//加上异常处理
				
	CFile m_File;//定义文件类对象
	try
	{
		if(m_FileDialog.DoModal()==IDOK)
		{
			FileName=m_FileDialog.GetPathName();//获取文件路径
			//获取文件路径,路径保存在字符串变量里
		//	CFile m_File;//定义文件类对象
			m_File.Open(FileName,CFile::modeRead,NULL);//文件已打开
			if(m_FileHeader) delete m_FileHeader;//判断是否存在文件头  存在则删除
			
			m_FileHeader=(BITMAPFILEHEADER*)new BYTE[sizeof(BITMAPFILEHEADER)];//给文件头分配内存
			m_File.Read(m_FileHeader,sizeof(BITMAPFILEHEADER));//读取文件头
			
			if(m_InfoHeader) delete m_InfoHeader;//判断是否存在信息头 存在就删除
			m_InfoHeader=(BITMAPINFOHEADER*)new BYTE[sizeof(BITMAPINFOHEADER)];//给信息头分配内存
			m_File.Read(m_InfoHeader,sizeof(BITMAPINFOHEADER));//读取信息头
			/
			if(m_FileHeader->bfType!=0x4D42)  //判断所打开的文件是否为bmp格式的文件
			{
				AfxMessageBox("this is not a bitmap!");
				m_File.Close(); //如果不是bmp格式的文件关闭文件
			}
			/
			if(m_InfoHeader->biBitCount==8)//8位
			{	
				if(m_InfoHeader!=NULL)//判断信息头是否为空
					delete [] m_InfoHeader;//删除信息头指针
				m_InfoHeader=(BITMAPINFOHEADER*) new BYTE [sizeof (BITMAPINFOHEADER)+sizeof(RGBQUAD)*256];	//分配内存缓冲区
				m_File.Seek(-sizeof(BITMAPINFOHEADER),CFile::current);//计算当前走过的字节数  
				m_File.Read(m_InfoHeader,sizeof(BITMAPINFOHEADER));//读取信息头	
				
				if(pallete!=NULL)//判断调色板
					delete []pallete;//删除调色板指针
				pallete=(RGBQUAD*)new BYTE[sizeof(RGBQUAD)*256];//给调色板分配内存
				m_File.Read(pallete,sizeof(RGBQUAD)*256);//读取调色板数据
				
				memcpy((BYTE*)m_InfoHeader+sizeof(BITMAPINFOHEADER),pallete,sizeof(RGBQUAD)*256);//给信息头分配原来的信息头字节数和调色板的字节数
				
				int size = 0;//定义变量size用于计算图像数据大小
				if(bmpdata)//判读是否存在图像数据
					delete []bmpdata;//删除数据指针
				
				size = m_FileHeader->bfSize - m_FileHeader->bfOffBits;//图像数据的字节大小
				
				bmpdata=(BYTE*) new BYTE[size];//给数据指针分配内存
				m_File.Read(bmpdata,size);	//读取数据
			}
			else if(m_InfoHeader->biBitCount==24)//24位真彩色图像
			{
				if(bmpdata)  delete  bmpdata;//判读是否存在图像数据
				bmpdata=(BYTE*)new BYTE[m_InfoHeader->biSizeImage];//给图像数据分配内存
				m_File.Read(bmpdata,m_InfoHeader->biSizeImage);//读取图像数据
			}
			
			m_File.Close();//文件关闭
			UpdateAllViews(NULL,0,NULL);//刷新
		}

	}
	catch (CMemoryException* e)
	{
		AfxMessageBox("e->m_szMessage");
		e->Delete();
	}
	catch (CFileException* e)
	{
		CString str;
		str.Format("读取数据失败的原因是:%d",e->m_cause);
		AfxMessageBox("str");
		m_File.Abort();
		e->Delete(); 
	}
	catch (CUserException* e)
	{
		CString str;
		str.Format("资源无法找到!");
		AfxMessageBox("str");
		e->Delete(); 
	}
}
  View.h :

class CMyView : public CView
{
public:
	int Height;
	int Width;
	BYTE* m_pImgDib;       //定义一个指向图像数据的指针 
	BITMAPINFO* m_pImgBmi; //文件头
	CDC *pMemDC1,*pMemDC2; //内存DC
	CBitmap* pOldBmp;      //封装bitmap的类
	HBITMAP hBmp;          //是bitmap的指针  ?
	BITMAP  bmp;           //定义一个BITMAP对象
	CPoint  point;         //是一个结构体,定义了逻辑位图的一些属性
  View.cpp :

CMyView::CMyView()
{
	// TODO: add construction code here
	//构造函数中初始化
	m_pImgDib=NULL;
	m_pImgBmi=NULL;
	pMemDC1=NULL;
	pMemDC2=NULL;
	pOldBmp=NULL;
	hBmp=NULL;
	Height=600;
	Width=600;
	point.x=0;
	point.y=0;
}
CMyView::~CMyView()
{
	//析构函数是最后执行的  在此函数里删除指针
	if(m_pImgDib) delete  m_pImgDib;
	if(m_pImgBmi) delete  m_pImgBmi;
	if(pMemDC1)   delete pMemDC1;
	if(pMemDC2)   delete pMemDC2;
	if(pOldBmp)   delete  pOldBmp;
}
void CMyView::OnDraw(CDC* pDC)
{
	CMyDoc* pDoc = GetDocument();//创建文档类的指针
		if(pDoc->m_InfoHeader)//判断信息头是否存在
		m_pImgBmi=(BITMAPINFO*)(pDoc->m_InfoHeader);//赋值强制类型转换
	if(pDoc->bmpdata)//判断图像信息是否存在
		m_pImgDib=pDoc->bmpdata;
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here
   if(m_pImgBmi&&m_pImgDib)
	{
		if(pMemDC1==NULL)     //内存DC1
		{
			pMemDC1=new CDC;  //CDC包含传输数据的设备和成员函数
			pMemDC1->CreateCompatibleDC(pDC);//使得pMemDC和pDC相兼容
		}
		CRect rc;                 //存储矩形的左上角和右下角的坐标
		GetClientRect(&rc);      //得到客户区的屏幕尺寸
		//
		if(pOldBmp!=NULL)//判断原图像数据是否存在
			pMemDC1->SelectObject(pOldBmp);//将图像数据对象选入内存设备环境中
		if(hBmp!=NULL)
			DeleteObject(hBmp);
		hBmp=CreateDIBitmap(pDC->GetSafeHdc(),      //CreateDIBitmap返回值是句柄
			(BITMAPINFOHEADER*)m_pImgBmi,CBM_INIT,
			m_pImgDib,m_pImgBmi,DIB_RGB_COLORS);

		if(hBmp==NULL)return ;
		CBitmap *pBmp=CBitmap::FromHandle(hBmp);  //得到位图的句柄
		pOldBmp=pMemDC1->SelectObject(pBmp);     //把位图选中到画布一上
		pBmp->GetBitmap(&bmp);
		if(hBmp==NULL)return;
		
		float wid,hei;
		wid=(float)(rc.right-rc.left);//bm.bmWidth://*2;
		hei=(float)(rc.bottom-rc.top);//bm.bmHeight://*2;
		int x,y;
		float hrate,wrate,rate;//保持图像输出比例
		hrate=(float)hei/(float)bmp.bmHeight;
		wrate=(float)wid/(float)bmp.bmPlanes;
		if(hrate<1.0 && wrate<1.0)
		{
			rate=(hrate<wrate)?hrate:wrate;
		}
		else 
			if(hrate>1.0 && wrate>1.0)
			{
				rate=(hrate<wrate)?hrate:wrate;
			}
			if(hrate>1.0 && wrate<1.0)
			{
				rate=wrate;
			}
			else
			{
				rate=hrate;
			}
			x=(int)abs((int)(wid-bmp.bmWidth*rate)/2);
			y=(int)abs((int)(hei-bmp.bmHeight*rate)/2);
			//
			if(pMemDC2==NULL) //第二个内存DC(直接与pDC交换数据)
			{
			  pMemDC2=new CDC;
			  pMemDC2->CreateCompatibleDC(pMemDC1);//pMemDC1相匹配	创建与屏幕兼容pMemDC1
			}

	    CBitmap myBmp;
	    myBmp.CreateCompatibleBitmap(pDC,rc.right,rc.bottom);//创建与指定的设备环境相关的设备兼容的位图
		CBitmap* pmyBmp=pMemDC2->SelectObject(&myBmp);//把位图选中到画布中

		pMemDC2->SetStretchBltMode(HALFTONE);
	//	pMemDC2->FillSolidRect(&rc,RGB(20,20,20));
		pMemDC2->StretchBlt(0,0,bmp.bmWidth,bmp.bmHeight,pMemDC1,
			0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);//将设备环境1放到设备环境2上
       // pMemDC2->FillSolidRect(&rc,RGB(20,20,20));
		pDC->SetStretchBltMode(HALFTONE);
		pDC->StretchBlt(300,50,bmp.bmWidth,bmp.bmHeight,pMemDC2,
			0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);//将设备环境2放到显示器中

	}
	// TODO: add draw code for native data here
}
  

  编译运行,结果如图:

 

里面的文字描述是借鉴于其他的图像处理资料,代码是亲自编写调试过的,所以才贴出来分享!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值