我逐步写一些图像处理方面的经验作为技术研究或讨论吧。
DELPHI中,所有的图像需要对图像的内容进行处理之前,都应该先转换成TBitmap类。除非只是简单地显示一下图像,或者事先已经有类把相应的功能进行打包。
TBitmap类是DELPHI已经封装好的,访问的速度也相当快。处理前一般把BMP转换成24位的图像深度
PixelFormat := pf24bit;
这么做的好处是,此后的代码处理比较简单化了,可以避免由于颜色深度不同而引起的错位或内容错乱等莫名其妙的问题。
接下来说一下如何访问图像的内容。请耐心看,可能要看好几篇这系列的文章才能开始真正地编写程序。
访问图像的内容,可以直接利用bmp的画面属性Canvas。Canvas是由一个一个像素组成的,可以直接引用这些像素,就可以获得这些点的颜色值。例如左上角的第一个点是
bmp.Canvas.Pixels[0, 0];
它的值是TColor。我写了一个函数可以直接把TColor中R(红)、G(绿)、B(蓝)三个分量的值分离出来:
procedure GetRGBFromColor(color: TColor; var r, g, b: Integer);
begin
r := color and $000000FF;
g := (color shr 8) and $000000FF;
b := (color shr 16) and $000000FF;
end;
用Pixels去访问bmp是最基本的,也是最直接最简单的,只要给出坐标值就可以获利颜色。但这种方法的缺点是,运算的速度比较慢。如果我们要对整张照片的所有像素值都进行遍历和读取,速度是极慢的。
接下来介绍bmp访问中最常用的方法吧。为了克服pixels访问太慢的问题,Borland为TBitmap增加了一个Scanline的方法,利用它可以快速定位图像的某一行,利用指针的移动对图像内容进行读写。可以说,绝大部分图像处理代码,都是利用这各方法进行访问的。
在使用Scanline之前,请在代码最前面interface加入以下代码
interface
{$DEFINE USE_SCANLINE}
让我们用一个令图像内容变成灰度图的代码例子看一下scanline的使用吧
procedure GrayScale(var clip: tbitmap);
var
p0:pbytearray;
Gray,x,y: Integer;
begin
for y:=0 to clip.Height-1 do
begin
p0:=clip.scanline[y];
for x:=0 to clip.Width-1 do
begin
Gray:= Round(p0[x*3] * 0.3 + p0[x*3 + 1] * 0.59 + p0[x*3 + 2] * 0.11);
p0[x*3]:=Gray;
p0[x*3+1]:=Gray;
p0[x*3+2]:=Gray;
end;
end;
end;
p0是一个指针,y值是需要读取的图像的行数,p0:=clip.scanline[y]即表示读取第y行的内容。
x是表示列数,p0[x*3]、p0[x*3+1]、p0[x*3+2]分别表示第x列的像素的BGR三个分量的值。只要直接修改p0[x*3]、p0[x*3+1]、p0[x*3+2],就可以让图像内容发生改变。
上面的函数中,我们先读取了某一个点的红绿蓝的值,然后进行运算得到一个灰度值,再把灰度值赋给红绿蓝分量。当某一个点的RGB三值相同时,这个点显示的颜色就是灰色的了。
可以有人对Gray的运算有疑问,后面的三个系数是如何得到的,这很简单,我们可以修改这三个系数为其他值的,只不过根据大部分人的经验,当三个系数为这样取值时,彩色转换为灰度得到的效果最接近人类的视觉感受而已。
这里对Gray的输出还少了一步溢出值的检验,凡是赋给RGB的值,必须是0-255之间的(不难理解,RGB各点8位,共24位。8位的最大值是255)。