阅读提示:
《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。
《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。
尽可能保持二者内容一致,可相互对照。
本文代码必须包括文章《Delphi图像处理 -- 数据类型及公用过程》中的ImageData.pas单元。
有关图形图像的平面几何变换,现有的教程、计算机图书以及网上的资料上介绍理论的偏多,即使有些编程实例,也只是介绍图像几何变换的某些特例,如旋转、缩放、平移等。GDI+倒是有个Matrix类,可完整地实现图像的几何变换,可惜没法得到源码。
本文不准备介绍任何关于平面几何变换的理论或者原理,而是直接用Delphi实现一个图形图像平面几何类TTransformMatrix。(C++版请见:http://blog.csdn.net/maozefa/archive/2010/10/10/5931427.aspx)
下面是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;
// 设置双立方插值的斜率。缺省值为-0.75。返回设置前的值
function SetBicubicSlope(const Value: Single): Single;
// 临近插值过程。汇编调用
procedure _GetNearColor;
// 线性插值过程。汇编调用
procedure _GetBilinearColor;
// 双立方插值过程。汇编调用
procedure _GetBicubicColor;
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;
(***************************************************************************
* | m11' m12' | 1 | m22 -m12 |
* | | = ----------------- * | |
* | m21' m22' | m11*m22 - m12*m21 |-m21 m11 |
*
* dx' = dx * m11' - dy * m21'
* dy' = dx * m12' - dy * m22'
***************************************************************************)
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;
{ 插值过程 }
type
PXMMType = ^TXMMType;
TXMMType = array[0..7] of Word;
var
BilinearTab: PXMMType; // emm 线形插值用表
BicubicTab: PXMMType; // emm 双立方插值用表
// <-->xmm7 8 * word=0
// <-->ebx stride
// --> edx x * 4096
// --> ecx y * 4096
// <-->esi = data->Scan0 + y * data->Stride + x * 4
// <-- xmm0 BilinearColor
procedure _GetBilinearColor;
asm
push ecx
push edx
and edx, 0ff0h
and ecx, 0ff0h
mov eax, BilinearTab
movq xmm0, [esi]
movq xmm1, [esi+ebx]
punpcklbw xmm0, xmm7 // xmm0 = pixel2, pixel0
punpcklbw xmm1, xmm7 // xmm1 = pixel3, pixel1
pmulhuw xmm0, [eax+ecx] // xmm0 *= vr
pmulhuw xmm1, [eax+ecx+256*16]// xmm1 *= v
paddw xmm0, xmm1
pmulhuw xmm0, [eax+edx+512*16]// xmm0 *= u, ur
movdqa xmm1, xmm0
psrldq xmm0, 8 // xmm0 >>= 64
paddw xmm0, xmm1
packuswb xmm0, xmm7
pop edx
pop ecx
end;
// <--> xmm7 8 * word = 0
// <--> xmm6 8 * word = 4
// <--> ebx stride
// --> edx x * 4096
// --> ecx y * 4096
// <--> esi = data->Scan0 + y * data->Stride + x * 4
// <-- xmm0 BicubicColor
procedure _GetBicubicColor;
asm
push edi
push ecx
push edx
mov edi, BicubicTab
and edx, 0ff0h // u = x & 4095
and ecx, 0ff0h // v = y & 4096
mov eax, edx // eax = -u
neg eax
movq xmm1, [esi]
movq xmm2, [esi+8]
movq xmm3, [esi+ebx]
movq xmm4, [esi+ebx+8]
punpcklbw xmm1, xmm7
punpcklbw xmm2, xmm7
punpcklbw xmm3, xmm7
punpcklbw xmm4, xmm7
psllw xmm1, 7
psllw xmm2, 7
psllw xmm3, 7
psllw xmm4, 7
pmulhw xmm1, [edi+edx]
pmulhw xmm2, [edi+eax+512*16]
pmulhw xmm3, [edi+edx]
pmulhw xmm4, [edi+eax+512*16]
paddsw xmm1, xmm2
paddsw xmm3, xmm4
movdqa xmm0, xmm1
movdqa xmm4, xmm3
psrldq xmm0, 8
psrldq xmm3, 8
paddsw xmm0, xmm1
paddsw xmm3, xmm4
punpcklqdq xmm0, xmm3
pmulhw xmm0, [edi+ecx]
neg ecx
add esi, ebx
movq xmm1, [esi+ebx]
movq xmm2, [esi+ebx+8]
movq xmm3, [esi+ebx*2]
movq xmm4, [esi+ebx*2+8]
punpcklbw xmm1, xmm7
punpcklbw xmm2, xmm7
punpcklbw xmm3, xmm7
punpcklbw xmm4, xmm7
psllw xmm1, 7
psllw xmm2, 7
psllw xmm3, 7
psllw xmm4, 7
pmulhw xmm1, [edi+edx]
pmulhw xmm2, [edi+eax+512*16]
pmulhw xmm3, [edi+edx]
pmulhw xmm4, [edi+eax+512*16]
paddsw xmm1, xmm2
paddsw xmm3, xmm4
movdqa xmm2, xmm1
movdqa xmm4, xmm3
psrldq xmm1, 8
psrldq xmm3, 8
paddsw xmm1, xmm2
paddsw xmm3, xmm4
punpcklqdq xmm1, xmm3
pmulhw xmm1, [edi+ecx+512*16]
paddsw xmm0, xmm1
movdqa xmm1, xmm0
psrldq xmm0, 8
paddsw xmm0, xmm1
paddsw xmm0, xmm6
psraw xmm0, 3
packuswb xmm0, xmm7
pop edx
pop ecx
pop edi
end;
procedure _GetNearColor;
asm
movd xmm0, [esi]
end;
var
PMMXTable: Pointer;
BicubicSlope: Single = 0;
function SetBicubicSlope(const Value: Single): Single;
function BicubicFunc(x : double): double;
var
x2, x3: double;
begin
if x < 0.0 then x := -x;
x2 := x * x;
x3 := x2 * x;
if x <= 1.0 then
Result := (Value + 2.0) * x3 - (Value + 3.0) * x2 + 1.0
else if x <= 2.0 then
Result := Value * x3 - (5.0 * Value) * x2 + (8.0 * Value) * x - (4.0 * Value)
else Result := 0.0;
end;
var
I: Integer;
P: PXMMType;
begin
Result := BicubicSlope;
if (BicubicSlope <> Value) and (Value < 0.0) {and (Value >= -2.0)} then
begin
BicubicSlope := Value;
P := BicubicTab;
for I := 0 to 255 do
begin
p^[0] := Round(16384 * BicubicFunc((I + 256) * (1.0 / 256)));
p^[1] := p^[0];
p^[2] := p^[0];
p^[3] := p^[0];
p^[4] := Round(16384 * BicubicFunc(I * (1.0 / 256)));
p^[5] := p^[4];
p^[6] := p^[4];
p^[7] := p^[4];
Inc(p);
end;
for I := 256 to 512 do
begin
p^[0] := Round(16384 * BicubicFunc((I - 256) * (1.0 / 256)));
p^[1] := p^[0];
p^[2] := p^[0];
p^[3] := p^[0];
p^[4] := Round(16384 * BicubicFunc(I * (1.0 / 256)));
p^[5] := p^[4];
p^[6] := p^[4];
p^[7] := p^[4];
Inc(p);
end;
end;
end;
procedure InitBilinearTab;
asm
mov edx, BilinearTab
mov eax, 1000100h
movd xmm7, eax
pshufd xmm7, xmm7, 0
mov eax, 10001h
movd xmm6, eax
pshufd xmm6, xmm6, 0
mov eax, 0ff00ffh
movd xmm0, eax
pshufd xmm0, xmm0, 0
movdqa xmm1, xmm6
xor ecx, ecx
jmp @@1
@@sumLoop:
mov eax, ecx
shl eax, 16
or eax, ecx
movd xmm1, eax
pshufd xmm1, xmm1, 0
movdqa xmm0, xmm7
psubw xmm0, xmm1
cmp ecx, 255
je @@1
paddw xmm1, xmm6
@@1:
psllw xmm0, 8
psllw xmm1, 8
movdqa xmm2, xmm0
movlhps xmm2, xmm1
movdqa [edx], xmm0 // vr
movdqa [edx+256*16], xmm1 // v
movdqa [edx+512*16], xmm2 // high 64 u, lower 64 ur
add edx, 16
inc ecx
cmp ecx, 256
jb @@sumLoop
end;
const
BilinearTabSize = 256 * 3 * 16;
BicubicTabSize = 513 * 16;
initialization
begin
GetMem(PMMXTable, BilinearTabSize + BicubicTabSize + 12);
BilinearTab := PXMMType((Integer(PMMXTable) + 12) and (not 15));
BicubicTab := PXMMType(Integer(BilinearTab) + BilinearTabSize);
InitBilinearTab;
SetBicubicSlope(-0.75);
end;
finalization
begin
FreeMem(PMMXTable);
end;
end.
TTransformMatrix与GDI+的TGpMatrix布局基本一样,所以关于类的使用方法就不再介绍了,本文的目的在于如何实现自己的平面几何变换类,否则,不如直接用GDI+的TGpMatrix了。
TTransformMatrix的核心代码是Multiply方法(或ElementsMultiply方法)和Invert方法。
Multiply方法通过2个TTransformMatrix的相乘来实现各种复杂的几何变换计算,所有能够实现的具体几何变换都是可以通过其完成的(代码中的平移函数Translate也可以通过其完成的,当然多了一些不必要的计算)。无论是TTransformMatrix类还是GDI+的TGpMatrix类,所提供的都只是基本的几何变换方法,还有些图形图像几何变换,如对称几何变换(镜像)和各种复杂的组合变换。都只能通过Multiply方法或者更直接的变换矩阵成员设置去实现。
Invert方法实现了变换矩阵的逆矩阵,通过这个几何变换逆矩阵,可以很方便地实现图形图像几何变换的实际操作。为什么要靠几何变换矩阵的逆矩阵,而不是直接依据变换矩阵来实现图形图像几何变换的实际操作呢?因为几何变换矩阵表示的意思是,把源图像的任意座标点通过几何变换后投影到目标图像。而源图像像素通过几何变换后与目标图像上的像素点有可能不能一一对应,如图像缩放变换后,不是多个源图像像素点对应同一个目标像素点(缩小),就是源图像像素点不足以填充全部的目标像素点(放大),这就有可能造成目标图像像素点被重复绘制或者被遗漏的现象发生;而几何变换逆矩阵所表示的意思是,对于目标图像任意一个像素点,如果在几何变换前有源图像像素点与其对应,则进行复制。遍历目标图像像素点就能保证目标图像像素点既不重复、也不遗漏的被复制。
上面TransformMatrix.pas单元还提供了几个插值过程,供具体的图像几何变换过程使用,限于篇幅,本文只介绍TTransformMatrix,没有用到这几个过程,也就没作介绍,放在这里,只是为了方便,因为具体的图像几何变换过程必须包括TransformMatrix.pas单元,这样就不会找不到这几个过程了。当然,为了检验TTransformMatrix类,还是写了个简单的过程来实现图像几何变换,下面是这个过程的代码:
procedure Transform(var dest: TImageData; x, y: Integer;
const source: TImageData; matrix: TTransformMatrix);
var
m: TTransformMatrix;
e: TMatrixElements;
fx, fy, fwidth, fheight: Single;
x0, y0, dstOffset: Integer;
xs, ys, xs0, ys0: Single;
pix: PARGB;
dst: TImageData;
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 := GetSubImageData(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;
GetSubBitmapData函数(《Delphi图像处理 -- 数据类型及公用过程》中的ImageData.pas单元)用于获取一个界定了范围的子图像数据,减少了像素操作时的计算,而Transform过程则用来实现具体的图像几何变换。上面之所以说“简单”,指的是Transform过程复制像素时使用的是直接临近取值,这样转换出来的图像质量较差;而且计算像素地址时采用了浮点数运算,影响了变换速度。但是这个过程的框架却是较完整的,可在此基础上加入像素插值方式,再改浮点数运算为定点数运算,该过程就比较完善了。
下面是分别使用Delpha的TBitmap类型和GDI+的TGpBitmap类型进行图像缩放加剪切的例子:
procedure TForm1.Button1Click(Sender: TObject);
var
bmp, newBmp: TBitmap;
jpg: TJPEGImage;
matrix: TTransformMatrix;
source, dest, exp: TImageData;
r: TRect;
begin
bmp := TBitmap.Create;
matrix := TTransformMatrix.Create;
try
jpg := TJPEGImage.Create;
try
jpg.LoadFromFile('..\..\media\IMG_9440_mf.jpg');
bmp.Assign(jpg);
finally
jpg.Free;
end;
matrix.Scale(1.2, 1.2);
matrix.Shear(0.5, 0.5);
r := matrix.GetTransformRect(bmp.Width, bmp.Height);
if (r.Right <= 0) or (r.Bottom <= 0) then Exit;
source := GetBitmapData(bmp);
newBmp := TBitmap.Create;
try
newBmp.PixelFormat := pf32bit;
newBmp.Width := r.Right;
newBmp.Height := r.Bottom;
dest := GetBitmapData(newBmp);
Transform(dest, 0, 0, source, matrix);
Canvas.Draw(0, 0, newBmp);
finally
newBmp.Free;
end;
finally
matrix.Free;
bmp.Free;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
bmp, newBmp: TGpBitmap;
matrix: TTransformMatrix;
source, dest: TImageData;
g: TGpGraphics;
r: TRect;
begin
bmp := TGpBitmap.Create('..\..\media\IMG_9440_mf.jpg');
matrix := TTransformMatrix.Create;
try
matrix.Scale(1.2, 1.2);
matrix.Shear(0.5, 0.5);
r := matrix.GetTransformRect(bmp.Width, bmp.Height);
if (r.Right <= 0) or (r.Bottom <= 0) then Exit;
newBmp := TGpBitmap.Create(r.Right, r.Bottom, pf32bppARGB);
g := TGpGraphics.Create(Canvas.Handle);
try
source := LockGpBitmap(bmp);
dest := LockGpBitmap(newBmp);
Transform(dest, 0, 0, source, matrix);
UnlockGpBitmap(newBmp, dest);
UnlockGpBitmap(bmp, source);
g.DrawImage(newBmp, 0, 0);
finally
g.Free;
newBmp.Free;
end;
finally
matrix.Free;
bmp.Free;
end;
end;
2个例子运行界面类似。下面是原图像和运行界面截图:
说明:本文中使用的GDI+版本下载地址和说明请见《GDI+ for VCL基础 -- GDI+ 与 VCL》。
如果使用与本文不同的GDI+版本,或者不使用GDI+,可将前面TransformMatrix单元里的:
// 几何变换矩阵结构
PMatrixElements = Gdiplus.PMatrixElements;
TMatrixElements = Gdiplus.TMatrixElements;
改为:
case Integer of
0 : (Elements: array [ 0 .. 5 ] of Single);
1 : (m11, m12, m21, m22, dx, dy: Single);
end ;
PMatrixElements = ^TMatrixElements;
《Delphi图像处理》系列使用GDI+单元下载地址和说明见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com
这里可访问《Delphi图像处理 -- 文章索引》。