图形图像平面几何变换类(C++版)

  本文用C++实现一个图形图像平面几何变换类TransformMatrix,布局同《图形图像平面几何变换类(Delphi版)》大致相同,下面是TransformMatrix类的全部代码:

ExpandedBlockStart.gif 代码
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);
    }

    
//  设置剪切,注意不要将shearX, shearY同时设置为1
    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;
    }

};

 

    TransformMatrix的核心代码是Multiply函数(或ElementsMultiply函数)和Invert函数。

    Multiply函数通过2个TransformMatrix的相乘来实现各种复杂的几何变换计算,所有能够实现的具体几何变换都是可以通过其完成的(代码中的平移函数Translate也可以通过其完成的,当然多了一些不必要的计算)。TransformMatrix类所提供的都只是基本的几何变换方法,还有些图形图像几何变换,如对称几何变换(镜像)和各种复杂的组合变换。都只能通过Multiply函数或者更直接的变换矩阵成员设置去实现。

    Invert函数实现了变换矩阵的逆矩阵,通过这个几何变换逆矩阵,可以很方便地实现图形图像几何变换的实际操作。为什么要靠几何变换矩阵的逆矩阵,而不是直接依据变换矩阵来实现图形图像几何变换的实际操作呢?因为几何变换矩阵表示的意思是,把源图像的任意座标点通过几何变换后投影到目标图像。而源图像像素通过几何变换后与目标图像上的像素点有可能不能一一对应,如图像缩放变换后,不是多个源图像像素点对应同一个目标像素点(缩小),就是源图像像素点不足以填充全部的目标像素点(放大),这就有可能造成目标图像像素点被重复绘制或者被遗漏的现象发生;而几何变换逆矩阵所表示的意思是,对于目标图像任意一个像素点,如果在几何变换前有源图像像素点与其对应,则进行复制。遍历目标图像像素点就能保证目标图像像素点既不重复、也不遗漏的被复制。

    同样,为了检验TransformMatrix类,写了2个简单的函数来实现具体图像几何变换:

 

ExpandedBlockStart.gif 代码
//  获取子图数据
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):

 

ExpandedBlockStart.gif 代码
//  锁定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::Button2Click(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  * =   new  Gdiplus::Graphics(Canvas -> Handle);
    g
-> DrawImage(newBmp,  0 0 );

    delete g;
    delete newBmp;
    delete bmp;
}

 

  因为图像几何变换是从源图左上角开始的,所以有的变换后的图像只能有一部分在目标图上,甚至完全不可见,如旋转90度,可检查matrix.GetTransformRect返回矩形的right和bottom(分别为变换后的宽度和高度)是否大于零来确定变换后图像是否可见。本例子使用了r.right - r.left和r.bottom - r.top作为变换后图像的宽度和高度,并在Transform函数中用-r.left和-r.top作为变换偏移,使得源图像无论怎么变换,变换后的图像都是完全可见的。

    运行界面截图:

    如有错误请来信指正:maozefa@hotmail.com

    如果转载,请注明出处。

 

转载于:https://www.cnblogs.com/maozefa/archive/2010/10/27/1862698.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值