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

    有关图形图像的平面几何变换,现有的教程、计算机图书以及网上的资料上介绍理论的偏多,即使有些编程实例,也只是介绍图像几何变换的某些特例,如旋转、缩放、平移等。GDI+倒是有个Matrix类,可完整地实现图像的几何变换,可惜没法得到源码。

    本文不准备介绍任何关于平面几何变换的理论或者原理,而是直接用Delphi实现一个图形图像平面几何类TTransformMatrix。

    下面是TransformMatrix类的全部代码:

代码
unit  TransformMatrix;

(* ****************************************************************************
*                                                                            *
* 本单元定义平面几何变换类                                                   *
*                                                                            *
* 编制人: 湖北省公安县统计局  毛泽发  2010.10                                *
*                                                                            *
****************************************************************************
*)

interface

{ $IF RTLVersion >= 17.00 }
{ $inline auto }
{ $IFEND }

uses
  Windows, SysUtils, Gdiplus;

type
  
//  几何变换矩阵结构
  PMatrixElements 
=  Gdiplus.PMatrixElements;
  TMatrixElements 
=  Gdiplus.TMatrixElements;

  
//  平面几何变换类
  TTransformMatrix 
=   class (TObject)
  
private
    FElements: TMatrixElements;
    
function  GetIdentity: Boolean;
    
function  GetInvertible: Boolean;
    
procedure  SetElements( const  Value: TMatrixElements);

    
function  GetIdentityElements: TMatrixElements;
    
procedure  ElementsMultiply( const  e: TMatrixElements);
  
public
    
//  建立一个新实例,并初始化为单位矩阵 Elements  =   1 , 0 , 0 , 1 , 0 , 0
    
constructor  Create;  overload ;
    
//  建立一个新实例,并复制matrix的元素
    
constructor  Create(matrix: TTransformMatrix);  overload ;
    
//  建立一个按指定的元素初始化的新实例
    
constructor  Create(m11, m12, m21, m22, dx, dy: Single);  overload ;
    
//  重置对象为单位矩阵
    
procedure  Reset;
    
//  将对象与matrix相乘
    
procedure  Multiply( const  matrix: TTransformMatrix);
    
//  设置平移
    
procedure  Translate(offsetX, offsetY: Single);
    
//  设置缩放
    
procedure  Scale(scaleX, scaleY: Single);
    
//  设置按角度angle沿原点旋转
    
procedure  Rotate(angle: Single);
    
//  设置按角度angle沿中心点centerX, centerY旋转
    
procedure  RotateAt(angle: Single; centerX, centerY: Single);
    
//  设置剪切,注意不要将shearX, shearY同时设置为1
    
procedure  Shear(shearX, shearY: Single);
    
//  如果此对象是可逆转的,则逆转该对象。
    
procedure  Invert;
    
//  按给定的大小计算并返回实施变换后的尺寸
    
procedure  GetTransformSize(width, height: Integer;  var  fx, fy, fwidth, fheight: Single);
    
//  按给定的大小计算并返回实施变换后的尺寸
    
function  GetTransformRect(width, height: Integer): TRect;

    
//  判断对象是否是可逆转的
    
property  IsInvertible: Boolean  read  GetInvertible;
    
//  判断此对象是否是单位矩阵
    
property  IsIdentity: Boolean  read  GetIdentity;
    
//  获取或设置对象元素
    
property  Elements: TMatrixElements  read  FElements  write  SetElements;
    
//  获取对象的x偏移量
    
property  OffsetX: Single  read  FElements.dx  write  FElements.dx;
    
//  获取对象的y偏移量
    
property  OffsetY: Single  read  FElements.dy  write  FElements.dy;
  
end ;

implementation

{  TTransformMatrix  }

constructor  TTransformMatrix.Create;
begin
  FElements.m11 :
=   1.0 ;
  FElements.m22 :
=   1.0 ;
end ;

constructor  TTransformMatrix.Create(matrix: TTransformMatrix);
begin
  FElements :
=  matrix.Elements;
end ;

constructor  TTransformMatrix.Create(m11, m12, m21, m22, dx, dy: Single);
begin
  FElements.m11 :
=  m11;
  FElements.m12 :
=  m12;
  FElements.m21 :
=  m21;
  FElements.m22 :
=  m22;
  FElements.dx :
=  dx;
  FElements.dy :
=  dy;
end ;

procedure  TTransformMatrix.ElementsMultiply( const  e: TMatrixElements);
var
  m11, m12: Single;
begin
  m11 :
=  FElements.m11;
  m12 :
=  FElements.m12;
  FElements.m11 :
=  e.m11  *  m11  +  e.m12  *  FElements.m21;
  FElements.m12 :
=  e.m11  *  m12  +  e.m12  *  FElements.m22;
  FElements.m21 :
=  e.m21  *  m11  +  e.m22  *  FElements.m21;
  FElements.m22 :
=  e.m21  *  m12  +  e.m22  *  FElements.m22;
end ;

function  TTransformMatrix.GetIdentity: Boolean;
begin
  Result :
=  (FElements.m11  =   1.0 and  (FElements.m12  =   0.0 and
            (FElements.m21 
=   0.0 and  (FElements.m22  =   1.0 and
            (FElements.dx 
=   0.0 )   and  (FElements.dy  =   0.0 );
end ;

function  TTransformMatrix.GetIdentityElements: TMatrixElements;
begin
  FillChar(Result, Sizeof(TMatrixElements), 
0 );
  Result.m11 :
=   1.0 ;
  Result.m22 :
=   1.0 ;
end ;

function  TTransformMatrix.GetInvertible: Boolean;
begin
  Result :
=  Round((FElements.m11  *  FElements.m22  -  FElements.m12  *  FElements.m21)  *   1000.0 <>   0 ;
end ;

function  TTransformMatrix.GetTransformRect(width, height: Integer): TRect;
var
  fx, fy, fwidth, fheight: Single;
begin
  GetTransformSize(width, height, fx, fy, fwidth, fheight);
  Result.Left :
=  Trunc(fx);
  Result.Top :
=  Trunc(fy);
  Result.Right :
=  Trunc(fwidth  +  fx  +   0.999999 );
  Result.Bottom :
=  Trunc(fheight  +  fy  +   0.999999 );
end ;

procedure  TTransformMatrix.GetTransformSize(width, height: Integer;  var  fx, fy,
  fwidth, fheight: Single);
var
  fxs, fys: 
array [ 0 .. 2 of  Single;
  v: Single;
  i: Integer;
begin
  fxs[
0 ] : =  width;
  fxs[
1 ] : =   0.0 ;
  fxs[
2 ] : =  width;
  fys[
0 ] : =   0.0 ;
  fys[
1 ] : =  height;
  fys[
2 ] : =  height;
  fx :
=   0.0 ;
  fy :
=   0.0 ;
  fwidth :
=   0.0 ;
  fheight :
=   0.0 ;
  
for  i : =   0   to   2   do
  
begin
    v :
=  fxs[i]  *  FElements.m11  +  fys[i]  *  FElements.m21;
    
if  v  <  fx  then  fx : =  v
    
else   if  v  >  fwidth  then  fwidth : =  v;
    v :
=  fxs[i]  *  FElements.m12  +  fys[i]  *  FElements.m22;
    
if  v  <  fy  then  fy : =  v
    
else   if  v  >  fheight  then  fheight : =  v;
  
end ;
  fwidth :
=  fwidth  -  fx;
  fheight :
=  fheight  -  fy;
  fx :
=  fx  +  FElements.dx;
  fy :
=  fy  +  FElements.dy;
end ;

procedure  TTransformMatrix.Invert;
var
  tmp: Double;
  m11, dx: Single;
begin
  tmp :
=  FElements.m11  *  FElements.m22  -  FElements.m12  *  FElements.m21;
  
if  Trunc(tmp  *   1000.0 =   0   then
    
raise  Exception.Create( ' Not invertible transformation matrix. ' );
  tmp :
=   1.0   /  tmp;
  m11 :
=  FElements.m11;
  dx :
=   - FElements.dx;
  FElements.m11 :
=  tmp  *  FElements.m22;
  FElements.m12 :
=  tmp  *   - FElements.m12;
  FElements.m21 :
=  tmp  *   - FElements.m21;
  FElements.m22 :
=  tmp  *  m11;
  FElements.dx :
=  dx  *  FElements.m11  -  FElements.dy  *  FElements.m21;
  FElements.dy :
=  dx  *  FElements.m12  -  FElements.dy  *  FElements.m22;
end ;

procedure  TTransformMatrix.Multiply( const  matrix: TTransformMatrix);
begin
  FElements.dx :
=  FElements.dx  +  (matrix.FElements.dx  *  FElements.m11  +
    matrix.FElements.dy 
*  FElements.m21);
  FElements.dy :
=  FElements.dy  +  (matrix.FElements.dx  *  FElements.m12  +
    matrix.FElements.dy 
*  FElements.m22);
  ElementsMultiply(matrix.FElements);
end ;

procedure  TTransformMatrix.Reset;
begin
  FElements :
=  GetIdentityElements;
end ;

procedure  TTransformMatrix.Rotate(angle: Single);
var
  e: TMatrixElements;
begin
  angle :
=  angle  *  PI  /   180.0 ;
  e.m11 :
=  Cos(angle);
  e.m22 :
=  e.m11;
  e.m12 :
=  Sin(angle);
  e.m21 :
=   - e.m12;
  e.dx :
=   0.0 ;
  e.dy :
=   0.0 ;
  ElementsMultiply(e);
end ;

procedure  TTransformMatrix.RotateAt(angle, centerX, centerY: Single);
begin
  Translate(centerX, centerY);
  Rotate(angle);
  Translate(
- centerX,  - centerY);
end ;

procedure  TTransformMatrix.Scale(scaleX, scaleY: Single);
var
  e: TMatrixElements;
begin
  e :
=  GetIdentityElements;
  e.m11 :
=  scaleX;
  e.m22 :
=  scaleY;
  ElementsMultiply(e);
end ;

procedure  TTransformMatrix.SetElements( const  Value: TMatrixElements);
begin
  Move(Value, FElements, Sizeof(TMatrixElements));
end ;

procedure  TTransformMatrix.Shear(shearX, shearY: Single);
var
  e: TMatrixElements;
begin
  e :
=  GetIdentityElements;
  e.m21 :
=  shearX;
  e.m12 :
=  shearY;
  ElementsMultiply(e);
end ;

procedure  TTransformMatrix.Translate(offsetX, offsetY: Single);
begin
  FElements.dx :
=  FElements.dx  +  (offsetX  *  FElements.m11  +  offsetY  *  FElements.m21);
  FElements.dy :
=  FElements.dy  +  (offsetX  *  FElements.m12  +  offsetY  *  FElements.m22);
end ;

end .

 

    TTransformMatrix与GDI+的TGpMatrix布局基本一样,所以关于类的使用方法就不再介绍了,本文的目的在于如何实现自己的平面几何变换类,否则,不如直接用GDI+的TGpMatrix了。

    TTransformMatrix的核心代码是Multiply方法(或ElementsMultiply方法)和Invert方法。

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

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

    为了检验TTransformMatrix类,写了2个简单的过程来实现具体图像几何变换:

代码
//  获取子图数据
function  GetSubBitmapData( const  data: TBitmapData; x, y, width, height: Integer): TBitmapData;
begin
  Result.Scan0 :
=   nil ;
  
if  x  <   0   then
  
begin
    Inc(width, x);
    x :
=   0 ;
  
end ;
  
if  x  +  width  >  Integer(data.Width)  then
    width :
=  Integer(data.Width)  -  x;
  
if  width  <=   0   then  Exit;
  
if  y  <   0   then
  
begin
    Inc(height, y);
    y :
=   0 ;
  
end ;
  
if  y  +  height  >  Integer(data.Height)  then
    height :
=  Integer(data.Height)  -  y;
  
if  height  <=   0   then  Exit;
  Result.Width :
=  width;
  Result.Height :
=  height;
  Result.Stride :
=  data.Stride;
  Result.Scan0 :
=  Pointer(Integer(data.Scan0)  +  y  *  data.Stride  +  (x  shl   2 ));
end ;

procedure  Transform( var  dest: TBitmapData; x, y: Integer;
  
const  source: TBitmapData; matrix: TTransformMatrix);
var
  m: TTransformMatrix;
  e: TMatrixElements;
  fx, fy, fwidth, fheight: Single;
  x0, y0, dstOffset: Integer;
  xs, ys, xs0, ys0: Single;
  pix: PARGB;
  dst: TBitmapData;
begin
  
//  复制几何变换矩阵对象
  m :
=  TTransformMatrix.Create(matrix);
  
try
    
//  几何变换矩阵绝对增加平移量x, y
    m.OffsetX :
=  m.OffsetX  +  x;
    m.OffsetY :
=  m.OffsetY  +  y;
    
//  按几何变换矩阵计算并获取目标图像数据子数据
    m.GetTransformSize(source.Width, source.Height, fx, fy, fwidth, fheight);
    dst :
=  GetSubBitmapData(dest, Trunc(fx), Trunc(fy),
      Trunc(fwidth 
+   0.999999 ), Trunc(fheight  +   0.999999 ));
    
if  dst.Scan0  =   nil   then  Exit;
    
//  获取几何变换逆矩阵
    m.Invert;
    
//  如果子图数据与目标图像原点不一致,几何变换矩阵相对增加平移量fx, fy
    
if  (fx  >   0.0 )   or  (fy  >   0.0 then
    
begin
      
if  fx  <   0.0   then  fx : =   0.0
      
else   if  fy  <   0.0   then  fy : =   0.0 ;
      m.Translate(fx, fy);
    
end ;
    
//  设置子图扫描线指针及行偏移宽度
    pix :
=  dst.Scan0;
    dstOffset :
=  dst.Stride  div   4   -  Integer(dst.Width);
    
//  几何变换逆矩阵的平移量为与子图原点对应的源图起始坐标点
    e :
=  m.Elements;
    xs :
=  e.dx;
    ys :
=  e.dy;
    
//  逐点计算并复制源图几何变换后的数据到目标子图
    
for  y : =   1   to  dst.Height  do
    
begin
      xs0 :
=  xs;
      ys0 :
=  ys;
      
for  x : =   1   to  dst.Width  do
      
begin
        x0 :
=  Round(xs0);
        y0 :
=  Round(ys0);
        
if  (x0  >=   0 and  (x0  <  Integer(source.Width))  and
           (y0 
>=   0 and  (y0  <  Integer(source.Height))  then
          pix^ :
=  PARGB(Integer(source.Scan0)  +  y0  *  source.Stride  +  x0  shl   2 )^;
        Inc(pix);
        xs0 :
=  xs0  +  e.m11;
        ys0 :
=  ys0  +  e.m12;
      
end ;
      Inc(pix, dstOffset);
      xs :
=  xs  +  e.m21;
      ys :
=  ys  +  e.m22;
    
end ;
  
finally
    m.Free;
  
end ;
end ;

 

    这2个过程都使用了GDI+的TBitmapData类型,而非具体的图像类作为参数类型,使得它们具有一定的通用性和扩展性。

    GetSubBitmapData函数用于获取一个界定了范围的子图像数据,减少了像素操作时的计算,而Transform过程则用来实现具体的图像几何变换。上面之所以说“简单”,指的是Transform过程复制像素时使用的是直接临近取值,这样转换出来的图像质量较差;而且计算像素地址时采用了浮点数运算,影响了变换速度。但是这个过程的框架却是较完整的,可在此基础上加入像素插值方式,再改浮点数运算为定点数运算,该过程就比较完善了。

    下面是一个TBitmap类型的图像进行缩放加剪切的例子:

 

代码
procedure  TForm1.Button2Click(Sender: TObject);

  
function  GetTBitmapData(bmp: TBitmap): TBitmapData;
  
begin
    bmp.PixelFormat :
=  pf32bit;
    Result.Width :
=  bmp.Width;
    Result.Height :
=  bmp.Height;
    Result.Stride :
=   - (bmp.Width  *   4 );
    Result.Scan0 :
=  bmp.ScanLine[ 0 ];
  
end ;

var
  bmp, newBmp: TBitmap;
  jpg: TJPEGImage;
  matrix: TTransformMatrix;
  source, dest: TBitmapData;
  r: TRect;
begin
  bmp :
=  TBitmap.Create;
  matrix :
=  TTransformMatrix.Create;
  
try
    jpg :
=  TJPEGImage.Create;
    
try
      jpg.LoadFromFile(
' 001-1.jpg ' );
      bmp.Assign(jpg);
    
finally
      jpg.Free;
    
end ;
    source :
=  GetTBitmapData(bmp);
    matrix.Scale(
1.2 1.2 );
    matrix.Shear(
0.5 0.5 );
    r :
=  matrix.GetTransformRect(source.Width, source.Height);
    
if  (r.Right  <=   0 or  (r.Bottom  <=   0 then  Exit;
    newBmp :
=  TBitmap.Create;
    
try
      newBmp.PixelFormat :
=  pf32bit;
      newBmp.Width :
=  r.Right;
      newBmp.Height :
=  r.Bottom;
      dest :
=  GetTBitmapData(newBmp);
      Transform(dest, 
0 0 , source, matrix);
      Canvas.Draw(
0 0 , newBmp);
    
finally
      newBmp.Free;
    
end ;
  
finally
    matrix.Free;
    bmp.Free;
  
end ;
end ;

 

    原图像和运行界面截图:

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

    说明:本文中使用的GDI+版本下载地址:

    GDI+ for VCL and GDI+ for C语言2010.10.7最新修改版。

    如果使用其它GDI+版本,或者不使用GDI+,可将前面TransformMatrix单元里的:

    // 几何变换矩阵结构
    PMatrixElements = Gdiplus.PMatrixElements;
    TMatrixElements = Gdiplus.TMatrixElements;

    改为:

  TMatrixElements  =   packed   record
    
case  Integer  of
      
0 : (Elements:  array [ 0 .. 5 of   Single);
      
1 : (m11, m12, m21, m22, dx, dy: Single);
  
end ;
  PMatrixElements 
=  ^TMatrixElements;

 

    如有错误请来信指正:[email protected]

    如果转载,请注明出处。

 

你可能感兴趣的:(Delphi)