初始坐标为(x0,y0)的点经过平移(tx,ty)(以向右,向下为正方向)后,坐标变为(x1,y1)。这两点之间的关系是x1=x0+tx ,y1=y0+ty。
以矩阵的形式表示为:
逆变换:
我们想知道的是平移后的图象中每个象素的颜色。例如我们想知道,新图中左上角点的RGB值是多少?很显然,该点是原图的某点经过平移后得到的,这两点的颜色肯定是一样的,所以只要知道了原图那点的RGB值即可。那么到底新图中的左上角点对应原图中的哪一点呢?将左上角点的坐标(0,0)入公式(2.2),得到x0=-tx ,y0=-ty;所以新图中的(0,0)点的颜色和原图中(-tx , -ty)的一样。
如果你用过Photoshop,Corel PhotoPaint等图象处理软件,可能听说过“灰度图”(grayscale)这个词。灰度图是指只含亮度信息,不含色彩信息的图象,就象我们平时看到的黑白照片:亮度由暗到明,变化是连续的。因此,要表示灰度图,就需要把亮度值进行量化。通常划分成0到255共256个级别,其中0最暗(全黑),255最亮(全白)。.bmp格式的文件中,并没有灰度图这个概念,但是,我们可以很容易在.bmp文件中表示灰度图。方法是用256色的调色板,只不过这个调色板有点特殊,每一项的RGB值都是相同的。也就是说RGB值从(0,0,0),(1,1,1)一直到(255,255,255)。(0,0,0)是全黑色,(255,255,255)是全白色,中间的是灰色。这样,灰度图就可以用256色图来表示了。为什么会这样呢?难道是一种巧合?其实并不是。
在表示颜色的方法中,除了RGB外,还有一种叫YUV的表示方法,应用也很多。电视信号中用的就是一种类似于YUV的颜色表示方法。
在这种表示方法中,Y分量的物理含义就是亮度,U和V分量代表了色差信号(你不必了解什么是色差,只要知道有这么一个概念就可以了)。使用这种表示方法有很多好处,最主要的有两点:
(1) 因为Y代表了亮度,所以Y分量包含了灰度图的所有信息,只用Y分量就能完全能够表示出一幅灰度图来。当同时考虑U,V分量时,就能够表示出彩色信息来。这样,用同一种表示方法可以很方便的在灰度和彩色图之间切换,而RGB表示方法就做不到这一点了。
(2) 人眼对于亮度信号非常敏感,而对色差信号的敏感程度相对较弱。也就是说,图象的主要信息包含在Y分量中。这就提示我们:如果在对YUV信号进行量化时,可以“偏心”一点,让Y的量化级别多一些(谁让它重要呢?)而让UV的量化级别少一些,就可以实现图象信息的压缩。这一点将在第9章介绍图象压缩时仔细研究,这里就不深入讨论了。而RGB的表示方法就做不到这一点,因为RGB三个分量同等重要,缺了谁也不行。YUV和RGB之间有着如下的对应关系
先看x方向(width指图象的宽度)
(1) tx≤-width:很显然,图象完全移出了屏幕,不用做任何处理;
(2) -width<tx≤0:容易看出,图象区域的x范围从0到width-|tx|,对应原图的范围从|tx|到width;
(3) 0< tx <width:如图2.6所示。容易看出,图象区域的x范围从tx 到width,对应原图的范围从0到width - tx ;
(4) tx ≥width:很显然,图象完全移出了屏幕,不用做任何处理。
y方向是对应的(height表示图象的高度):
(1) ty≤-height,图象完全移出了屏幕,不用做任何处理;
(2) -height<ty≤0,图象区域的y范围从0到height-|ty|,对应原图的范围从|ty|到height;
(3) 0<ty<height ,图象区域的y范围从ty到height,对应原图的范围从0到height-ty;
(4) ty≥height,图象完全移出了屏幕,不用做任何处理。
这种做法利用了位图存储的连续性,即同一行的象素在内存中是相邻的。利用memcpy函数,从(x0,y0)点开始,一次可以拷贝一整行(宽度为x1-x0),然后将内存指针移到(x0,y0+1)处,拷贝下一行。这样拷贝(y1-y0)行就完成了全部操作,避免了一个一个象素的计算,提高了效率。下面将会贴上代码,编译环境是VC 6.0 (单文档程序)。
首先还是先打开图像,这里需要加个类,表示x方向和y方向平移。
Doc.h:
class CMyDoc : public CDocument
{
public:
BITMAPFILEHEADER m_file;
BITMAPINFOHEADER m_info;
BITMAPINFO* bmpinfo;
BYTE *bmpdata;
BOOL isOpen;
Doc.cpp:
CMyDoc::CMyDoc()
{
// TODO: add one-time construction code here
isOpen=FALSE;
bmpinfo=NULL;
bmpdata=NULL;
}
View.cpp:
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
BYTE* pData = NULL;
pData = pDoc->bmpdata;
if(pDoc->isOpen == TRUE)
{
SetDIBitsToDevice(//12个参数
pDC->m_hDC, //设备环境句柄
0,//绘制目标矩形区域的左上角x坐标
0,//绘制目标矩形区域的左上角y坐标
pDoc->bmpinfo->bmiHeader.biWidth,//绘制目标矩形区域的宽度width
pDoc->bmpinfo->bmiHeader.biHeight,//绘制目标矩形区域的高度height
0,//位图区域的左下角x坐标
0,//位图区域的左下角y坐标
0,//显示图片的起始行
pDoc->bmpinfo->bmiHeader.biHeight,//扫描总行数
pData,//位图数据起首地址
pDoc->bmpinfo,//位图信息头首地址
DIB_RGB_COLORS//颜色参数
);
}
}
void CMyView::OnPingyi()
{
// TODO: Add your command handler code here
CMyDoc* pc=GetDocument();
CPingyi dlg;
dlg.SetDocument(pc);
dlg.DoModal();
Invalidate();
}
CPingYi是新加的类,用来记录x方向和y方向。
Pingyi.h:
#if !defined(AFX_PINGYI_H__304A149B_64E3_4480_ACB5_D3AF19143F29__INCLUDED_)
#define AFX_PINGYI_H__304A149B_64E3_4480_ACB5_D3AF19143F29__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// Pingyi.h : header file
//
#include "平移Doc.h"
/
// CPingyi dialog
class CPingyi : public CDialog
{
public:
int GetX();//获取对话框里的x数据
int GetY();//获取对话框里的Y数据
int m_x;
int m_y;
CMyDoc*poc;
void SetDocument(CMyDoc *m);
CMyDoc* GetDocument();
void Move(BYTE *bmpdata,int width,int height,int xx,int yy);
// Construction
public:
CPingyi(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(CPingyi)
enum { IDD = IDD_MOVE };
// NOTE: the ClassWizard will add data members here
//}}AFX_DATA
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CPingyi)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CPingyi)
afx_msg void OnChangeEdit1();
afx_msg void OnChangeEdit2();
virtual void OnOK();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_PINGYI_H__304A149B_64E3_4480_ACB5_D3AF19143F29__INCLUDED_)
Pingyi.cpp:
// Pingyi.cpp : implementation file
//
#include "stdafx.h"
#include "平移.h"
#include "Pingyi.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/
// CPingyi dialog
CPingyi::CPingyi(CWnd* pParent /*=NULL*/)
: CDialog(CPingyi::IDD, pParent)
{
//{{AFX_DATA_INIT(CPingyi)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
}
void CPingyi::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CPingyi)
// NOTE: the ClassWizard will add DDX and DDV calls here
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CPingyi, CDialog)
//{{AFX_MSG_MAP(CPingyi)
ON_EN_CHANGE(IDC_EDIT1, OnChangeEdit1)
ON_EN_CHANGE(IDC_EDIT2, OnChangeEdit2)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/
// CPingyi message handlers
void CPingyi::OnChangeEdit1()
{
// TODO: If this is a RICHEDIT control, the control will not
// send this notification unless you override the CDialog::OnInitDialog()
// function and call CRichEditCtrl().SetEventMask()
// with the ENM_CHANGE flag ORed into the mask.
m_x=GetDlgItemInt(IDC_EDIT1);
// TODO: Add your control notification handler code here
}
void CPingyi::OnChangeEdit2()
{
// TODO: If this is a RICHEDIT control, the control will not
// send this notification unless you override the CDialog::OnInitDialog()
// function and call CRichEditCtrl().SetEventMask()
// with the ENM_CHANGE flag ORed into the mask.
m_y=GetDlgItemInt(IDC_EDIT2);
// TODO: Add your control notification handler code here
}
int CPingyi::GetX()
{
return m_x;
}
int CPingyi::GetY()
{
return m_y;
}
void CPingyi::OnOK()
{
// TODO: Add extra validation here
Move(GetDocument()->bmpdata,GetDocument()->m_info.biWidth,GetDocument()->m_info.biHeight,GetX(),GetY());
CDialog::OnOK();
}
void CPingyi::SetDocument(CMyDoc *m)
{
poc=m;
}
CMyDoc* CPingyi::GetDocument()
{
return poc;
}
void CPingyi::Move(BYTE *bmpdata,int width,int height,int xx,int yy)
{
BYTE *data;//;临时的存储空间
BYTE *yuan;//原来的数据地址
BYTE *bian;//改变后的图像地址
int x1,y1;//原来的坐标
int x2,y2;//平移后的坐标
LONG zijie;//字节
zijie = WIDTHBYTES(width * 8);
data=(BYTE *)new BYTE[zijie*height];
if(!data) delete[] data;
for(x2=0;x2<height;x2++)
for(y2=0;y2<width;y2++)
{
bian=data+zijie*(height-1-x2)+y2;
x1= x2 - yy;
y1= y2 - xx;
if((x1>0)&&(x1<height)&&(y1>0)&&(y1<width))
{
yuan=bmpdata+zijie*(height-1-x1)+y1;
*bian=*yuan;
}
else
* ((unsigned char*)bian) = 125;
}
memcpy(bmpdata,data,zijie*height);
delete [] data;
}