http://blog.csdn.net/maozefa/archive/2010/10/10/5931427.aspx
有关图像的平面几何变换,现有的教程、计算机图书以及网上的资料上介绍理论的偏多,即使有些编程实例,也只是介绍图像几何变换的某些特例,如旋转、缩放、平移等。GDI+倒是有个Matrix类,可完整地实现图像的几何变换,可惜没法得到源码。
本文将完整的实现一个类似GDI+ Matrix的C++几何变换类TransformMatrix,关于几何变换的理论及原理请参考有关书籍或资料。
下面是C++几何变换类TransformMatrix的代码:
- typedef union
- {
- float Elements[6];
- struct
- {
- float m11;
- float m12;
- float m21;
- float m22;
- float dx;
- float dy;
- };
- }MatrixElements, *PMatrixElements;
- class TransformMatrix
- {
- private :
- MatrixElements elements;
- VOID ElementsInit(MatrixElements &e)
- {
- e.m11 = e.m22 = 1.0f;
- e.m12 = e.m21 = e.dx = e.dy = 0.0f;
- }
- VOID ElementsMultiply(MatrixElements &e)
- {
- float m11 = elements.m11;
- float m12 = elements.m12;
- elements.m11 = e.m11 * m11 + e.m12 * elements.m21;
- elements.m12 = e.m11 * m12 + e.m12 * elements.m22;
- elements.m21 = e.m21 * m11 + e.m22 * elements.m21;
- elements.m22 = e.m21 * m12 + e.m22 * elements.m22;
- }
- public :
- // 建立一个新实例,并初始化为单位矩阵 Elements = 1,0,0,1,0,0
- TransformMatrix(VOID )
- {
- Reset();
- }
- // 建立一个新实例,并复制matrix的元素
- TransformMatrix(TransformMatrix *matrix)
- {
- SetElements(matrix->elements);
- }
- TransformMatrix(TransformMatrix &matrix)
- {
- SetElements(matrix.elements);
- }
- // 建立一个按指定的元素初始化的新实例
- TransformMatrix(float m11, float m12, float m21, float m22, float dx, float dy)
- {
- SetElements(m11, m12, m21, m22, dx, dy);
- }
- // 重置对象为单位矩阵
- VOID Reset( VOID )
- {
- ElementsInit(elements);
- }
- // 将对象与matrix相乘
- VOID Multiply(TransformMatrix *matrix)
- {
- // float dx = elements.dx;
- elements.dx += (matrix->elements.dx * elements.m11 + matrix->elements.dy * elements.m21);
- elements.dy += (matrix->elements.dx * elements.m12 + matrix->elements.dy * elements.m22);
- ElementsMultiply(matrix->elements);
- }
- VOID Multiply(TransformMatrix &matrix)
- {
- Multiply(&matrix);
- }
- // 设置平移
- VOID Translate( float offsetX, float offsetY)
- {
- elements.dx += (offsetX * elements.m11 + offsetY * elements.m21);
- elements.dy += (offsetX * elements.m12 + offsetY * elements.m22);
- }
- // 设置缩放
- VOID Scale( float scaleX, float scaleY)
- {
- MatrixElements e;
- ElementsInit(e);
- e.m11 = scaleX;
- e.m22 = scaleY;
- ElementsMultiply(e);
- }
- // 设置剪切
- VOID Shear( float shearX, float shearY)
- {
- MatrixElements e;
- ElementsInit(e);
- e.m21 = shearX;
- e.m12 = shearY;
- ElementsMultiply(e);
- }
- // 设置按角度angle沿原点旋转
- VOID Rotate( float angle)
- {
- MatrixElements e;
- angle = angle * M_PI / 180.0f;
- e.m11 = e.m22 = cos(angle);
- e.m12 = sin(angle);
- e.m21 = -e.m12;
- e.dx = e.dy = 0.0f;
- ElementsMultiply(e);
- }
- // 设置按角度angle沿中心点centerX, centerY旋转
- VOID RotateAt( float angle, float centerX, float centerY)
- {
- Translate(centerX, centerY);
- Rotate(angle);
- Translate(-centerX, -centerY);
- }
- // 如果此对象是可逆转的,则逆转该对象,返回TRUE;否则返回FALSE
- BOOL Invert( VOID )
- {
- double tmp = elements.m11 * elements.m22 - elements.m12 * elements.m21;
- if (( INT )(tmp * 1000.0f) == 0) return FALSE;
- tmp = 1.0f / tmp;
- float m11 = elements.m11;
- float dx = -elements.dx;
- elements.m11 = tmp * elements.m22;
- elements.m12 = tmp * -elements.m12;
- elements.m21 = tmp * -elements.m21;
- elements.m22 = tmp * m11;
- elements.dx = dx * elements.m11 - elements.dy * elements.m21;
- elements.dy = dx * elements.m12 - elements.dy * elements.m22;
- return TRUE;
- }
- // 按给定的大小计算并返回实施变换后的尺寸
- VOID GetTransformSize( INT width, INT height, float &fx, float &fy, float &fwidth, float &fheight)
- {
- float fxs[3], fys[3], v;
- fxs[1] = fys[0] = 0.0f;
- fxs[0] = fxs[2] = width;
- fys[1] = fys[2] = height;
- fx = fy = fwidth = fheight = 0.0f;
- for ( INT i = 0; i < 3; i ++)
- {
- v = fxs[i] * elements.m11 + fys[i] * elements.m21;
- if (v < fx) fx = v;
- else if (v > fwidth) fwidth = v;
- v = fxs[i] * elements.m12 + fys[i] * elements.m22;
- if (v < fy) fy = v;
- else if (v > fheight) fheight = v;
- }
- fwidth -= fx;
- fheight -= fy;
- fx += elements.dx;
- fy += elements.dy;
- }
- // 按给定的大小计算并返回实施变换后整型数矩形
- VOID GetTransformRect( int width, int height, RECT &r)
- {
- float fx, fy, fwidth, fheight;
- GetTransformSize(width, height, fx, fy, fwidth, fheight);
- r.left = (INT )fx;
- r.top = (INT )fy;
- r.right = (INT )(fwidth + fx + 0.999999f);
- r.bottom = (INT )(fheight + fy + 0.999999f);
- }
- // 判断此对象是否是单位矩阵
- BOOL GetIdentity( VOID )
- {
- return (elements.m11 == 1.0f &&
- elements.m22 == 1.0f &&
- elements.m12 == 0.0f &&
- elements.m21 == 0.0f &&
- elements.dx == 0.0f &&
- elements.dy == 0.0f);
- }
- // 获取对象的x偏移量
- float GetOffsetX( VOID )
- {
- return elements.dx;
- }
- // 获取对象的y偏移量
- float GetOffsetY( VOID )
- {
- return elements.dy;
- }
- // 判断对象是否是可逆转的。
- BOOL GetInvertible( VOID )
- {
- return ( INT )(1000.0f * (elements.m11 * elements.m22 - elements.m12 * elements.m21)) != 0;
- }
- // 获取对象元素
- MatrixElements& GetElements(VOID )
- {
- return elements;
- }
- // 设置对象元素。注:设置元素是覆盖形式的
- VOID SetElements(CONST MatrixElements &value)
- {
- SetElements(value.m11, value.m12, value.m21, value.m22, value.dx, value.dy);
- }
- VOID SetElements( float m11, float m12, float m21, float m22, float dx, float dy)
- {
- elements.m11 = m11;
- elements.m12 = m12;
- elements.m21 = m21;
- elements.m22 = m22;
- elements.dx = dx;
- elements.dy = dy;
- }
- };
上面代码中定义了一个几何变换矩阵成员类型MatrixElements,便于实际编程时获取或设置几何变换矩阵成员,TransformMatrix只是简单的对其进行了封装,并通过计算实现有关的几何变换。
TransformMatrix的核心代码是Multiply函数(或ElementsMultiply函数)和Invert函数。
Multiply函数可完成各种复杂的几何变换计算,所有能够实现的具体几何变换都是可以通过其完成的(代码中的平移函数Translate也可以通过其 完成的,当然多了一些不必要的计算)。虽说本文标题是《实现完整的图像平面几何变换》,但TransformMatrix中的几种基础的变换函数并不代表 全部的几何变换,如对称几何变换(镜像),更不用说复杂的组合变换。这倒不是本人要做“标题党”,我所说的“实现完整的图像几何变换”,是指可以通过 Multiply函数或者更直接的变换矩阵成员设置去实现“完整的”图像几何变换,除非其不能使用平面几何变换矩阵进行描述(如梯形变换我就没想到怎么实 现,也许其超出了平面几何变换矩阵范畴?),或者不能进行实际的几何变换(不可逆);“实现完整的图像几何变换”的另一层含义是下面的图像变换执行函数可 实现TransformMatrix所能表示的任意图像几何变换,而不必去写一个个具体的,如缩放、旋转变换函数等。
Invert函数实现了变换矩阵的逆矩阵,通过这个几何变换逆矩阵,可以很方便地实现图形图像几何变换的实际操作。为什么要靠几何变换矩阵的逆矩阵,而不 是直接依据变换矩阵来实现图形图像几何变换的实际操作呢?因为几何变换矩阵表示的意思是,把源图像的任意座标点通过几何变换后投影到目标图像。因为源图像 像素通过几何变换后与目标图像上的像素点有可能不能一一对应,如图像缩放变换后,不是多个源图像像素点对应同一个目标像素点(缩小),就是源图像像素点不 足以填充全部的目标像素点(放大),这就有可能造成目标图像像素点被重复绘制或者被遗漏的现象发生;而几何变换逆矩阵所表示的意思是,对于目标图像任意一 个像素点,如果在几何变换前有源图像像素点与其对应,则进行复制。遍历目标图像像素点就能保证目标图像像素点既不重复、也不遗漏的被复制。
下面是一个图像几何变换函数代码:
- // 获取子图数据
- BOOL GetSubBitmapData(CONST BitmapData *data, INT x, INT y, INT width, INT height, BitmapData *sub)
- {
- if (x < 0)
- {
- width += x;
- x = 0;
- }
- if (x + width > ( INT )data->Width)
- width = (INT )data->Width - x;
- if (width <= 0) return FALSE;
- if (y < 0)
- {
- height += y;
- y = 0;
- }
- if (y + height > ( INT )data->Height)
- height = (INT )data->Height - y;
- if (height <= 0) return FALSE;
- sub->Width = width;
- sub->Height = height;
- sub->Stride = data->Stride;
- sub->Scan0 = (CHAR *)data->Scan0 + y * data->Stride + (x << 2);
- return TRUE;
- }
- // 执行图像数据几何变换
- VOID Transform(BitmapData *dest, INT x, INT y, CONST BitmapData *source, TransformMatrix *matrix)
- {
- // 复制几何变换矩阵对象
- TransformMatrix m(matrix);
- // 几何变换矩阵绝对增加平移量x, y
- m.GetElements().dx += x;
- m.GetElements().dy += y;
- // 按几何变换矩阵计算并获取目标图像数据子数据
- float fx, fy, fwidth, fheight;
- m.GetTransformSize(source->Width, source->Height, fx, fy, fwidth, fheight);
- BitmapData dst;
- if (!GetSubBitmapData(dest, ( INT )fx, ( INT )fy,
- (INT )(fwidth + 0.999999f), ( INT )(fheight + 0.999999f), &dst))
- return ;
- // 获取几何变换逆矩阵
- if (!m.Invert()) return ;
- // 如果子图数据与目标图像原点不一致,几何变换矩阵相对增加平移量fx, fy
- if (fx > 0.0f || fy > 0.0f)
- {
- if (fx < 0.0f) fx = 0.0f;
- else if (fy < 0.0f) fy = 0.0f;
- m.Translate(fx, fy);
- }
- // 设置子图扫描线指针及行偏移宽度
- UINT *pix = ( UINT *)dst.Scan0;
- INT dstOffset = (dst.Stride >> 2) - dst.Width;
- // 几何变换逆矩阵的平移量为与子图原点对应的源图起始坐标点
- MatrixElements e = m.GetElements();
- float xs = e.dx;
- float ys = e.dy;
- // 逐点计算并复制源图几何变换后的数据到目标子图
- for (y = 0; y < ( INT )dst.Height; y ++, pix += dstOffset, xs += e.m21, ys += e.m22)
- {
- float xs0 = xs;
- float ys0 = ys;
- for (x = 0; x < ( INT )dst.Width; x ++, pix ++, xs0 += e.m11, ys0 += e.m12)
- {
- INT x0 = xs0 < 0.0f? ( INT )(xs0 - 0.5f) : ( INT )(xs0 + 0.5f);
- INT y0 = ys0 < 0.0f? ( INT )(ys0 - 0.5f) : ( INT )(ys0 + 0.5f);
- if (y0 >= 0 && y0 < ( INT )source->Height && x0 >= 0 && x0 < ( INT )source->Width)
- *pix = *(UINT *)(( CHAR *)source->Scan0 + y0 * source->Stride + (x0 << 2));
- }
- }
- }
上面图像几何变换函数的几个特点:
1、可以实现任意的图像几何变换(只要TransformMatrix能正确表达的,即变换矩阵可逆);
2、采用了GDI+ 的BitmapData结构(转换为32位ARGB像素格式),而并非任何具体的图像格式,保证了其通用性;
3、函数使用浮点数运算,但在计算像素点位置时避免了通常的浮点数乘除运算,既提高了一定的运算速度,也为以后修改为定点数运算奠定了基础;
4、函数采用临近像素插值,且没有边界像素处理代码,像素复制质量较差。
可以看出,Transform函数的着重点在于特点(1),在实际的实现代码中,可以把它作为一个框架进行扩充和修改。
下面是一个利用Transform函数对GDI+位图进行旋转变换的例子(使用BCB2007):
- // 锁定GDI+位图扫描线到data
- inline VOID LockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data)
- {
- Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());
- bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite,
- PixelFormat32bppARGB, data);
- }
- // GDI+位图扫描线解锁
- inline VOID UnlockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data)
- {
- bmp->UnlockBits(data);
- }
- void __fastcall TForm1::Button1Click(TObject *Sender)
- {
- // 获取源图像扫描线数据
- Gdiplus::Bitmap *bmp = new Gdiplus::Bitmap(L "..//media//001-1.jpg" );
- BitmapData source, dest;
- LockBitmap(bmp, &source);
- // 设置几何变换
- TransformMatrix matrix;
- matrix.Rotate(45);
- // 建立目标位图并获取其扫描线数据
- // r.right和r.bottom分别为几何变换后可见部分的宽度和高度
- RECT r;
- matrix.GetTransformRect(source.Width, source.Height, r);
- if (r.right > 0 && r.bottom > 0)
- {
- Gdiplus::Bitmap *newBmp = new Gdiplus::Bitmap(r.right, r.bottom, PixelFormat32bppARGB);
- LockBitmap(newBmp, &dest);
- Transform(&dest, 0, 0, &source, &matrix);
- UnlockBitmap(newBmp, &dest);
- // 画几何变换后的图像
- Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);
- g->DrawImage(newBmp, 0, 0);
- delete g;
- delete newBmp;
- }
- UnlockBitmap(bmp, &source);
- delete bmp;
- }
下面是图像旋转变换例子运行界面截图:
由于图像几何变换是以源图原点(0,0)为变换原点,所以界面上只能看到原点右下边的图像。还有些几何变换,如旋转90度、180度等,可能会导致几何变 换后的图像完全不可见,为了直观的看到各种几何变换后的完整图像,可以修改一下例子代码,将 TransformMatrix::GetTransformRect函数返回矩形的左上边部分也包括进来:
- void __fastcall TForm1::Button1Click(TObject *Sender)
- {
- // 获取源图像扫描线数据
- Gdiplus::Bitmap *bmp = new Gdiplus::Bitmap(L "..//media//001-1.jpg" );
- BitmapData source, dest;
- LockBitmap(bmp, &source);
- // 设置几何变换
- TransformMatrix matrix;
- matrix.Rotate(45);
- // matrix.RotateAt(45, source.Width / 2, source.Height / 2);
- // matrix.Scale(1.2, 1.2);
- // matrix.Shear(0.2, 0.3);
- // matrix.GetElements().m11 = -1.0f; // 水平镜像
- // matrix.GetElements().m22 = -1.0f; // 垂直镜像
- // matrix.SetElements(0, 1, 1, 0, 0, 0); // x=y镜像
- // 建立目标位图并获取其扫描线数据
- RECT r;
- matrix.GetTransformRect(source.Width, source.Height, r);
- Gdiplus::Bitmap *newBmp = new Gdiplus::Bitmap(
- r.right - r.left, r.bottom - r.top, PixelFormat32bppARGB);
- LockBitmap(newBmp, &dest);
- // 执行图像几何变换
- Transform(&dest, -r.left, -r.top, &source, &matrix);
- // 释放图像扫描线数据(位图解锁)
- UnlockBitmap(newBmp, &dest);
- UnlockBitmap(bmp, &source);
- // 画几何变换后的图像
- Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);
- g->DrawImage(newBmp, 0, 0);
- delete g;
- delete newBmp;
- delete bmp;
- }
运行界面截图如下:
例子代码中,被注释掉的是一些图像常用几何变换,其运行界面就不一一贴图了。
上面的例子使用的是GDI+ 位图,而我们通常使用更多的是GDI位图,下面以BCB2007的VCL位图为例作为本文结束部分:
- // 获取VCL位图扫描线到data
- VOID GetTBitmapData(::Graphics::TBitmap *bmp, BitmapData *data)
- {
- bmp->PixelFormat = pf32bit;
- data->Width = bmp->Width;
- data->Height = bmp->Height;
- data->Scan0 = bmp->ScanLine[0];
- // 因为VCL位图扫描线是倒置的,所以其扫描线字节数设置为负数
- data->Stride = -(bmp->Width << 2);
- }
- void __fastcall TForm1::Button3Click(TObject *Sender)
- {
- TJPEGImage *jpg = new TJPEGImage();
- jpg->LoadFromFile("..//media//001-1.jpg" );
- ::Graphics::TBitmap *bmp = new ::Graphics::TBitmap();
- bmp->Assign(jpg);
- delete jpg;
- BitmapData source, dest;
- GetTBitmapData(bmp, &source);
- // 设置几何变换
- TransformMatrix matrix;
- // matrix.Rotate(45);
- // matrix.RotateAt(45, source.Width / 2, source.Height / 2);
- matrix.Scale(1.2, 1.2);
- matrix.Shear(0.5, 0.5);
- // matrix.GetElements().m11 = -1.0f; // 水平镜像
- // matrix.GetElements().m22 = -1.0f; // 垂直镜像
- // matrix.SetElements(0, 1, 1, 0, 0, 0); // x=y镜像
- // 建立目标位图并获取其扫描线数据
- RECT r;
- matrix.GetTransformRect(source.Width, source.Height, r);
- ::Graphics::TBitmap *newBmp = new ::Graphics::TBitmap();
- newBmp->PixelFormat = pf32bit;
- newBmp->Width = r.right - r.left;
- newBmp->Height = r.bottom - r.top;
- GetTBitmapData(newBmp, &dest);
- // 执行图像几何变换
- Transform(&dest, -r.left, -r.top, &source, &matrix);
- // 画几何变换后的图像
- newBmp->Transparent = true ;
- newBmp->TransparentColor = clWhite;
- Canvas->Draw(0, 0, newBmp);
- delete newBmp;
- delete bmp;
- }
该例子实现了图像缩放与剪切组合变换,其运行界面截图如下:
尽管我十分努力,但水平有限,错误在所难免,欢迎指正和指导。邮箱地址:
怎样实现图像旋转 通过c++ builder
http://topic.csdn.net/u/20110510/18/8887b1a1-7f93-4d27-85c7-e63b053e6968.html?39315