注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

yeye55的博客

编程就象品一杯清茶,清淡却又深厚。

 
 
 

日志

 
 
关于我

宅男一只,喜欢编程。没事的时候就喜欢抱着电脑看看电影、看看动漫、听听音乐、打打网游、逛逛论坛。清静的时候喜欢专研编程技术,但是发现自己至今仍然只是一只三脚猫。

网易考拉推荐

Delphi下实现全屏快速找图找色  

2011-11-04 15:34:29|  分类: 辅助程序设计 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

  本文最早于2009年5月29日在编程论坛(programbbs.com)上发表,页面地址:http://programbbs.com/bbs/view12-20778-1.htm 。

1 前言

  最近有好几个朋友都在问我找图找色的问题,奇怪?于是乎写了一个专门用于找图找色的单元文件“BitmapData.pas”。在这个单元文件中我实现了从文件中导入位图、屏幕截图、鼠标指针截图、在图片上查找子图、在图片上查找颜色等功能。在查找过程中可以设定颜色变化范围、可以从左到右从上到下查找、也可以从指定点向四周查找。关于这个文件的下载和使用,可以参考本文的第四节。下面详细说说这些功能的实现。

2 数据提取

  位图其实可以看成是一个由象素组成的矩阵,找图找色可以看成是象素值的比对。很多新手在设计这类的程序时喜欢使用TBitmap.Canvas.Pixels属性,这个属性其实是对API函数GetPixel的封装,这个函数执行速度是很慢的,主要用来对位图象素进行偶尔的访问。而比对过程中需要对象素进行频繁的访问,造成程序运行缓慢。另外一种方法是使用TBitmap.ScanLine属性,利用它可以直接访问位图的数据。但是这些数据和当前位图的格式有关,主要是色深方面的问题,不同的色深会有不同格式的数据。另外比对过程中也需要对该属性进行频繁的调用。由于比对过程完全是数据的比较,不需要进行绘制操作。所以可以一次性将位图的数据提取出来放置到一个缓冲区中再进行比对,这样程序的性能会更高,也便于查找算法的实现。这时可以调用API函数GetDIBits获得设备无关位图的RGB数据,其实ScanLine属性也是调用这个函数实现的。GetDIBits函数格式声明如下:

function GetDIBits(
    DC: HDC; //设备上下文句柄;
    Bitmap: HBitmap; //位图句柄,注意不是TBitmap对象;
    StartScan, //开始检索的第一条扫描线;
    NumScans: UINT; //共检索的扫描线数;
    Bits: Pointer; //数据缓冲区指针;
    var BitInfo:  TBitmapInfo; //位图信息结构,此结构确定了设备无关位图的数据格式;
    Usage: UINT //指定TBitmapInfo结构的bmiColors成员的格式。
    ): Integer; stdcall;

  其中TBitmapInfo结构的格式如下:

tagBITMAPINFO = packed record
  bmiHeader: TBitmapInfoHeader; //位图信息头,该结构用于说明位图的格式;
  bmiColors: array[0..0] of  TRGBQuad; //颜色表,给出调色板数据。
end;

  在上述结构中主要使用bmiHeader成员,TBitmapInfoHeader结构的格式如下:

tagBITMAPINFOHEADER = packed record
  biSize: DWORD; //当前结构的大小;
  biWidth: Longint; //以像素为单位,给出该结构所描述位图的宽度;
  biHeight: Longint; //以像素为单位,给出该结构所描述位图的高度;
  biPlanes: Word; //目标设备的平面数,必须为1;
  biBitCount: Word; //每个像素所需要的位数,当图像为真彩色时,
//该成员的取值为24;
  biCompression: DWORD; //位图的压缩类型,若该成员的取值为BI_RGB,
//则图像数据没有经过压缩处理;
  biSizeImage: DWORD; //以字节为单位,给出图像数据的大小,若图像为
//BI_RGB位图,则该成员的值必须设为0;
  biXPelsPerMeter: Longint; //以每米像素数为单位,给出位图水平方向的分辨率;
  biYPelsPerMeter: Longint; //以每米像素数为单位,给出位图垂直方向的分辨率;
  biClrUsed: DWORD; //位图实际使用的颜色表中的颜色变址数;
  biClrImportant: DWORD; //位图显示过程中重要颜色的变址数。
end;

  在上面两个结构中,bmiColours成员指向一个颜色表,它包含多少个表项是由bmiHeader.biBitCount成员定义。当该成员的取值为24时,则颜色表中的表项为空。当biBitCount取值24同时biCompression取值BI_RGB时表示当前位图为24位真彩色无压缩位图。这时可以将位图数据缓冲区看成是一个一维的字节数组。其中每3个字节代表1个像素。这3个字节以蓝(B)、绿(G)、红(R)为顺序,直接定义了像素颜色。这里要注意一个字节顺序,一般我们使用的TColor颜色格式是以红(R)、绿(G)、蓝(B)为顺序的RGB颜色,而缓冲区中使用的是顺序相反的BGR颜色。另外利用GetDIBits提取的位图数据是自下而上从左到右保存到缓冲区中的,即先保存位图最后一行从左到右的象素数据,再保存倒数第二行的数据,以此类推第一行最后保存。除了数据反相保存外,每行数据都以4字节(32位)对齐,一行数据的长度不能被4整除时就在每行的末尾填充值为0的字节使之能被4整除。例如:对于宽5象素的位图每行数据占16个字节,前15个字节每3个字节保存1个象素颜色,最后填充1个字节。对于宽10象素的位图每行数据占32个字节,前30个字节每3个字节保存1个象素颜色,最后填充2个字节。

  知道了缓冲区数据的格式,就可以对缓冲区中的数据进行访问。现在给出相关访问的示范代码:首先位图数据缓冲区是一个一维的字节数组,那么这个数组Bits可以按以下代码进行定义:

type
    TByteAry = array  [0..0] of Byte;
    PByteAry = ^TByteAry;
var
    Bits : PByteAry;

  接着假设有一个位图,高Height象素,宽Width象素。那么对齐后每行数据长度LineWidth字节可以用以下的代码计算出来:

LineWidth:=(((Width*24)+31) and  ($7FFFFFFF-31)) shr 3;

  于是前面数组Bits的大小Size就为:LineWidth*Height。对于任意一个象素在位图上的位置Left,Top(二维)可以用以下代码换算出该象素数据在数组Bits中的位置Off(一维):

Off:=((Height-Top-1)*LineWidth)+(Left*3);

  假设一个BGR格式的颜色值Color,以下代码可以从数组Bits的Off位置读取一个象素颜色值:

Color:=((PInteger(@(Bits[Off])))^ and  $FFFFFF);

  使用GetDIBits函数后就可以不再使用TBitmap对象。以下的示范代码实现对当前屏幕的全屏截图,并将截图后的位图数据提取到缓冲区中返回:

procedure CopyScreen(var Bits : PByteAry;  var Size : Integer);
var
    Width,Height,LineWidth  : Integer;
    Wnd : HWND;
    DC,MemDC : HDC;
    Bitmap,OldBitmap :  HBITMAP;
    BitInfo : TBitmapInfo;
begin
    //数据初始化
     Width:=GetSystemMetrics(SM_CXSCREEN);
    Height:=GetSystemMetrics(SM_CYSCREEN);
     LineWidth:=(((Width*24)+31) and ($7FFFFFFF-31)) shr 3;
     Size:=LineWidth*Height;
    GetMem(Bits,Size);
    //截图
     Wnd:=GetDesktopWindow();
    DC:=GetWindowDC(Wnd);
     MemDC:=CreateCompatibleDC(DC);
    Bitmap:=CreateCompatibleBitmap(DC,Width,Height);
     OldBitmap:=SelectObject(MemDC,Bitmap);
     BitBlt(MemDC,0,0,Width,Height,DC,0,0,SRCCOPY);
     Bitmap:=SelectObject(MemDC,OldBitmap);
    //位图信息初始化
    with BitInfo.bmiHeader  do
    begin
         biSize:=SizeOf(TBitmapInfoHeader);
         biWidth:=Width;
         biHeight:=Height;
         biPlanes:=1;
         biBitCount:=24;
         biCompression:=BI_RGB;
         biSizeImage:=0;
         biXPelsPerMeter:=0;
         biYPelsPerMeter:=0;
         biClrUsed:=0;
         biClrImportant:=0;
    end;
    //提取数据
     GetDIBits(DC,Bitmap,0,Height,Pointer(Bits),BitInfo,DIB_RGB_COLORS);
    DeleteDC(MemDC);
    DeleteObject(Bitmap);
     DeleteObject(OldBitmap);
    ReleaseDC(Wnd,DC);
end;

  对于标准的24位BMP位图文件,其中的位图数据也是以上述格式保存的。有的24位BMP文件并不标准,所以文件最好使用Windows自带的画图程序保存。以下的示范代码实现从标准的24位BMP格式文件中导入位图数据到缓冲区中返回:

procedure LoadFile(const FileName :  string; var Bits : PByteAry; var Size : Integer);
var
    Stream : TFileStream;
    FileHeader :  TBitmapFileHeader;
    InfoHeader :  TBitmapInfoHeader;
    LineWidth : Integer;
begin
     Stream:=TFileStream.Create(FileName,fmOpenRead);
    //读取文件头
     Stream.Read(FileHeader,SizeOf(TBitmapFileHeader));
     Stream.Read(InfoHeader,SizeOf(TBitmapInfoHeader));
    with  FileHeader,InfoHeader do
    begin
         //确定图片格式
         if (bfType<>$4D42) or (biSize<>SizeOf(TBitmapInfoHeader)) or
            (biBitCount<>24) or (biCompression<>BI_RGB) then
         begin
             Bits:=nil;
             Size:=0;
             exit;
         end;
         //数据初始化
         LineWidth:=(((biWidth*24)+31) and ($7FFFFFFF-31)) shr 3;
         Size:=LineWidth*biHeight;
         GetMem(Bits,Size);
    end;
    //读入数据
     Stream.Read(Bits^,Size);
    Stream.Free;
end;

  综上所述,当位图数据提取到一个缓冲区中,找图找色就是对这个缓冲区中的数据进行访问的过程。而这个缓冲区可以作为一个矩阵来进行访问。只要对矩阵进行遍历就可以实现找图找色的算法。

3 矩阵遍历

  矩阵遍历是一个数据结构方面的问题。假设有一个矩阵Matrix,它共有RowCount行,每行有ColCount列,当利用y表示行数,x表示列数,那么利用Matrix[y,x]就可以访问矩阵中的任意元素。假设有一个10×10大小的矩阵,它的遍历方法有以下三种:

  在上图中矩阵中的数字表示遍历到元素的先后次序,箭头表示遍历的方向。第一种的一般遍历法在很多编程书上都有介绍,而且经常作为循环代码的示范程序使用。这种遍历方法稍加修改就可以做到从右上角开始、从左下角开始、从右下角开始。这种遍历方法很简单,这里就不多说了。与一般遍历相反,螺旋遍历在所有的编程书和数据结构书上都没有讲到。现在详细的说明一下螺旋遍历。

  螺旋遍历可以做到以一个基点为中心向四周遍历,这个基点可以不是矩阵的中心点,实际上基点可以是矩阵上的任意一点,甚至可以是矩阵外的点。注意:这里所说的“点”是指可以用(y,x)访问的元素,当(y,x)坐标超出矩阵范围,例如(-1,-1),这就是矩阵外的点。可以看出螺旋遍历对于找图找色非常有用。螺旋遍历实现起来并不难,仔细观察图1中的螺旋遍历就会发现遍历可以由遍历方向和遍历步数组成。从(3,2)点开始向上遍历一步,再向右遍历一步,再向下遍历二步,再向左遍历二步,这时完成一轮,遍历方向又开始向上、向右、向下、向左一轮又一轮,同时遍历步数逐步加大。当向上遍历时y总是减1;当向右遍历时x总是加1;当向下遍历时y总是加1;当向左遍历时x总是减1,这样可以根据遍历方向计算出坐标的变化。另外螺旋遍历有可能会访问到矩阵外的点,在访问时要进行判断。正是由于螺旋遍历会访问矩阵外的点,遍历循环将无法停止从而出现死循环。这时要设定一个访问计数VisitCount,当遍历循环访问了矩阵中的所有点后退出循环。综上所述,螺旋遍历的示范代码如下:

type
    //遍历方向
    TAspect = (asLeft,  asRight, asUp, asDown);
const
    //移动坐标差
    MoveVal : array  [asLeft..asDown] of TPoint = (
         (X : -1; Y :  0), //asLeft
         (X :  1; Y :  0), //asRight
        (X  :  0; Y : -1), //asUp
         (X :  0; Y :  1)  //asDown
    );
    //矩阵大小
    RowCount = 10;
    ColCount = 10;
var
    //矩阵
    Matrix : array  [0..RowCount-1,0..ColCount-1] of Integer;
//螺旋遍历(不支持步长)
procedure MatrixOrder1_(y,x : Integer);
var
    Aspect : TAspect;
    VisitCount,Count,i :  Integer;
begin
    VisitCount:=0;
    Aspect:=asUp;
    Count:=1;
    while  VisitCount<(RowCount*ColCount) do
    begin
         for i:=0 to Count-1 do
         begin
             if (x>=0) and (x<ColCount) and
                (y>=0) and (y<RowCount) then
             begin
                 //访问矩阵元素
                 Matrix[y,x]:=VisitCount;
                 VisitCount:=VisitCount+1;
             end;
             x:=x+MoveVal[Aspect].X;
             y:=y+MoveVal[Aspect].Y;
         end;
         case Aspect of
             asLeft  : begin Aspect:=asUp;   Count:=Count+1; end;
             asRight : begin Aspect:=asDown; Count:=Count+1; end;
             asUp    : begin Aspect:=asRight; end;
             asDown  : begin Aspect:=asLeft;  end;
         end;
    end;
end;

  这里还有一个步长的问题,所谓步长就是指在遍历的时候跳过一些点,只平均访问矩阵中的某些点。例如以下数据就是步长为2以(3,2)为基点的螺旋遍历后的矩阵,其中“-”表示遍历时没有访问到的点。

输出矩阵:
+ 0 1 2 3 4 5 6 7 8 9
0 : - - - - - - - - - -
1 : 8 - 1 - 2 - 9 - 16 -
2 : - - - - - - - - - -
3 : 7 - 0 - 3 - 10 - 17 -
4 : - - - - - - - - - -
5 : 6 - 5 - 4 - 11 - 18 -
6 : - - - - - - - - - -
7 : 15 - 14 - 13 - 12 - 19 -
8 : - - - - - - - - - -
9 : 24 - 23 - 22 - 21 - 20 -

  使用步长可以实现矩阵的抽样查找,但上面给出的螺旋遍历算法却不支持步长。因为它要利用访问计数退出循环,使用步长时会使矩阵中访问到的点的数目不确定,使的上述算法出现死循环。对上述算法的一个改进是使用一个逻辑变量记录遍历一轮是否有访问到点。如果没有,说明这一轮访问已经以位于矩阵之外可以退出循环。当步长为1时这种改进的算法要比前面的算法更慢,因为它要“空转”一轮。而且这种算法也不支持矩阵外的点作为基点,它会使循环提前退出。支持步长的螺旋遍历算法的示范代码如下:注意这时的VisitCount仅作为测试使用,不作为退出循环的条件。

type
    //遍历方向
    TAspect = (asLeft,  asRight, asUp, asDown);
const
    //遍历步长
    Interval = 2;
    //移动坐标差
    MoveVal : array  [asLeft..asDown] of TPoint = (
         (X : -Interval; Y : 0), //asLeft
         (X :  Interval; Y : 0), //asRight
         (X : 0; Y : -Interval), //asUp
         (X : 0; Y :  Interval)  //asDown
    );
    //矩阵大小
    RowCount = 10;
    ColCount = 10;
var
    //矩阵
    Matrix : array  [0..RowCount-1,0..ColCount-1] of Integer;
//螺旋遍历2(支持步长)
procedure MatrixOrder2(y,x : Integer);
var
    Aspect : TAspect;
    VisitCount : Integer;  //访问计数,测试用
    Count,i : Integer;
    Visit : Boolean;
begin
    VisitCount:=0; //访问计数,测试用
    Visit:=false;
    Aspect:=asUp;
    Count:=1;
    while true do
    begin
         for i:=0 to Count-1 do
         begin
             if (x>=0) and (x<ColCount) and
                (y>=0) and (y<RowCount) then
             begin
                 //访问矩阵元素
                 Matrix[y,x]:=VisitCount;
                 VisitCount:=VisitCount+1; //访问计数,测试用
                 Visit:=true;
             end;
             x:=x+MoveVal[Aspect].X;
             y:=y+MoveVal[Aspect].Y;
         end;
         case Aspect of
             asLeft : begin
                 if not Visit then break;
                 Visit:=false;
                 Aspect:=asUp;
                 Count:=Count+1;
             end;
             asRight : begin Aspect:=asDown; Count:=Count+1; end;
             asUp    : begin Aspect:=asRight; end;
             asDown  : begin Aspect:=asLeft;  end;
         end;
    end;
end;

  对于回形遍历与螺旋遍历大同小异,这里就不多说了。在本文的末尾有一个矩阵遍历示范项目的压缩包的下载。在这个压缩包中是矩阵遍历的示范程序,里面有一般遍历、螺旋遍历和回形遍历的示范代码,可以用于参考。

4 找图找色

  结合本文第一节和第二节的内容设计一个找图找色的程序应该不是问题。对于一个位图可以看成是由象素组成的矩阵,Top相当于y,Left相当于x,利用(Top,Left)可以象访问矩阵元素一样访问位图上的象素。查找过程就是对位图象素的遍历。相关的代码在BitmapData.pas文件中都有,这里就不重复了。在BitmapData.pas文件中我实现的查找过程主要还是一对一的比对,这是一种较慢的匹配算法。对于一些字符串匹配算法,在查找过程中可以在匹配失败时跳过一些字符从而加快查找的速度。在矩阵查找中也有类似的算法,但我没有找到比较好的算法,所以在实现上还是采用了一对一的比对。这就意味着查找过程的速度还有提升的可能,虽然现在的查找速度已经是可以接受的。

  另外还有一个问题:在屏幕或大图上查找一个位图,这个位图可以被称为子图。采用颜色比较算法可以允许子图出现一定的颜色偏差,这不会影响查找结果。但是这种比较算法却不允许子图出现扭曲或旋转,只要子图出现轻微的扭曲或旋转都无法查找到。如果要允许子图出现扭曲或旋转就要用到复杂的图形图象分析算法。由于图形图象分析算法太复杂,我也没有做太深的研究,所以在BitmapData.pas中我只实现了简单的子图查找。

5 BitmapData.pas的使用

  包含这个文件的压缩包的下载见本文末尾。在这个压缩包中是BitmapData.pas使用的示范程序,BitmapData.pas文件可以从压缩包中获得。在BitmapData.pas文件中我将位图数据封装成了类TBDBitmapData,以便于使用。另外我编写一系列的函数用以BGR格式颜色的构建、转换、模糊比较。注意在BitmapData.pas文件中我定义了一些常量,这些常量只是为了增加程序的可读性,修改这些常量不会修改程序支持数据的格式,只会使程序运行错误。BitmapData.pas文件的详细说明如下:

  1、function BGR(B,G,R : Byte):TBDColor;

  根据蓝(B)、绿(G)、红(R)三个通道的值生成一个BGR格式颜色。

  2、function RGBtoBGR(C : TColor):TBDColor;

  将一个RGB颜色格式转换到BGR颜色格式。

  3、function BGRtoRGB(C :TBDColor): TColor;

  将一个BGR颜色格式转换到RGB颜色格式。

  4、function BDCompareColor(C1,C2 :TBDColor; const Range : TBDColorRange): Boolean;

  根据颜色范围Range比较颜色C1和C2,返回C1和C2是否相似。C1和C2是BGR格式的颜色,Range是颜色的变化范围。TBDColorRange的定义如下:

TBDColorRange = record
    R : Integer;
    G : Integer;
    B : Integer;
end;

  其中,R表示C1和C2中红色通道最大的相差值;G表示C1和C2中绿色通道最大的相差值;B表示C1和C2中蓝色通道最大的相差值。

  示范程序,比较两个颜色:

var
    C1,C2 : TBDColor;
    Range : TBDColorRange;
begin
    Range.R:=5;
    Range.G:=5;
    Range.B:=5;
    C1:=BGR(125,125,125);
    C2:=BGR(120,120,120);
     BDCompareColor(C1,C2,Range); //成功
    C1:=BGR(125,120,125);
    C2:=BGR(120,120,120);
     BDCompareColor(C1,C2,Range); //成功
    C1:=BGR(125,200,125);
    C2:=BGR(120,120,120);
     BDCompareColor(C1,C2,Range); //失败
end;

  5、constructorTBDBitmapData.Create(const AName : String);

  新建一个TBDBitmapData对象的实例。可以为实例指定一个名字,便于以后的管理。

  6、procedure TBDBitmapData.Clear;

  清除当前TBDBitmapData对象中加载的位图数据。

  7、functionTBDBitmapData.LoadFromStream(Stream : TStream; ABackColor : TBDColor): Boolean;

  从数据流Stream中导入位图数据,返回是否成功。如果失败将设置Error成员说明情况。数据流中的数据必需是24位BMP格式文件数据。利用ABackColor可以设定图片的背景颜色,该参数可以省略,省略时表示图片不使用背景颜色。

  8、functionTBDBitmapData.SaveToStream(Stream : TStream):Boolean;

  将当前加载的位图数据导出到数据流Stream中,返回是否成功。如果失败将设置Error成员说明情况。数据将按24位BMP文件数据格式导出到数据流中。

  9、functionTBDBitmapData.LoadFromFile(const FileName : string; ABackColor : TBDColor):Boolean;

  从文件FileName中导入位图数据,返回是否成功。如果失败将设置Error成员说明情况。文件必需是24位BMP格式文件。利用ABackColor可以设定图片的背景颜色,该参数可以省略,省略时表示图片不使用背景颜色。

  10、functionTBDBitmapData.SaveToFile(const FileName : string): Boolean;

  将当前加载的位图数据导出到文件中,返回是否成功。如果失败将设置Error成员说明情况。数据按24位BMP文件数据格式导出到文件中。

  11、functionTBDBitmapData.LoadFromBitmap(Bitmap : TBitmap): Boolean;

  从一个TBitmap对象中导入数据,返回是否成功。如果失败将设置Error成员说明情况。导入时图片的背景颜色由Bitmap.Transparent和Bitmap.TransparentColor决定。

  12、functionTBDBitmapData.SaveToBitmap(Bitmap : TBitmap): Boolean;

  将当前的位图数据导出到一个TBitmap对象中,返回是否成功。如果失败将设置Error成员说明情况。导出时将根据当前的背景颜色设置Bitmap.Transparent和Bitmap.TransparentColor成员。利用LoadFromBitmap和SaveToBitmap两个函数可以实现TBDBitmapData对象和TBitmap对象的相互转换。

  13、functionTBDBitmapData.CopyFormScreen(Left,Top,AWidth,AHeight : Integer): Boolean;

  从屏幕上的指定范围中截图,并导入数据,返回是否成功。如果失败将设置Error成员说明情况。Left为截图的左边距,可省略,默认为0;Top为截图的顶边距,可省略,默认为0;AWidth为截图的宽度,可省略,默认为从Left到屏幕右边的宽度;AHeight为截图的高度,可省略,默认为从Top到屏幕底边的高度。

  14、functionTBDBitmapData.CopyFormCursor: Boolean;

  截取鼠标当前指针的图片,并导入数据,返回是否成功。如果失败将设置Error成员说明情况。如果鼠标指针是动画指针,默认截取第一帧画面。截取时会使用当前背景颜色填充背景,如果没有指定背景颜色则使用白色(RGB(255,255,255))填充。

  15、functionTBDBitmapData.Compare(Bmp : TBDBitmapData; Left,Top : Integer): Boolean;

  16、functionTBDBitmapData.Compare(Bmp : TBDBitmapData; const Range : TBDColorRange;Left,Top : Integer): Boolean;

  重载的两个函数,用于在当前位图的指定位置比较Bmp指定的位图,返回是否一致。无论比较是否一致都不会修改Error成员。第一个函数用于精确比较,第二个函数用于模糊比较。Bmp指定的位图面幅要小于等于当前位图的面幅,Bmp指定的位图不能超出当前位图,否则比较失败。Bmp为指定的位图数据;Left为比较时的左边距,可省略,默认为0;Top为比较时的顶边距,可省略,默认为0;Range为颜色变化范围。

  17、functionTBDBitmapData.FindImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean;

  18、functionTBDBitmapData.FindImage(Bmp : TBDBitmapData; const Range : TBDColorRange; varLeft,Top : Integer): Boolean;

  重载的两个函数,从当前位图中查找与Bmp一致的子图,返回是否找到。无论是否找到都不会修改Error成员。第一个函数用于精确比较,第二个函数用于模糊比较。查找时忽略Left和Top的设置,从当前位图的左上角开始按从左到右,从上到下的顺序查找。找到返回true,设置Left和Top为找到子图的位置;没找到返回false,设置Left和Top为-1。Bmp为指定的子图数据;Left为找到子图的左边距;Top为找到子图的顶边距;Range为颜色变化范围。

  示范程序,在屏幕上查找子图:

var
    Bit1,Bit2 :  TBDBitmapData;
    Left,Top : Integer;
begin
     Bit1:=TBDBitmapData.Create;
     Bit2:=TBDBitmapData.Create;
    Bit1.CopyFormScreen;
    Bit2.LoadFromFile('文件名');
    if  Bit1.FindImage(Bit2,Left,Top) then
    begin
         {已找到子图,进行相应的处理...}
    end;
    Bit1.Free;
    Bit2.Free;
end;

  19、functionTBDBitmapData.FindCenterImage(Bmp : TBDBitmapData; var Left,Top : Integer):Boolean;

  20、functionTBDBitmapData.FindCenterImage(Bmp : TBDBitmapData; const Range : TBDColorRange;var Left,Top : Integer): Boolean;

  重载的两个函数,从当前位图中查找与Bmp一致的子图,返回是否找到。无论是否找到都不会修改Error成员。第一个函数用于精确比较,第二个函数用于模糊比较。查找时以Left和Top的设置为基点,从中心向四周查找。找到返回true,设置Left和Top为找到子图的位置;没找到返回false,设置Left和Top为-1。Bmp为指定的子图数据;Left为找到子图的左边距;Top为找到子图的顶边距;Range为颜色变化范围。

  21、functionTBDBitmapData.EnumImage(Bmp : TBDBitmapData; EnumImageProc : TBDEnumImageProc;lParam : Integer): Boolean;

  22、functionTBDBitmapData.EnumImage(Bmp : TBDBitmapData; const Range : TBDColorRange;EnumImageProc : TBDEnumImageProc; lParam : Integer): Boolean;

  重载的两个函数,从当前位图中查找所有与Bmp一致的子图,即枚举位图,返回是否找到。无论是否找到都不会修改Error成员。第一个函数用于精确比较,第二个函数用于模糊比较。查找时从当前位图的左上角开始按从左到右,从上到下的顺序查找。每当查找到一个子图,就调用回调函数EnumImageProc,如果EnumImageProc返回false就停止查找,结束函数。Bmp为子图数据;EnumImageProc为回调函数;lParam为调用回调函数时发出的参数,可省略,默认为0;Range为颜色变化范围。TBDEnumImageProc的声明格式如下:

TBDEnumImageProc = function (Left,Top :  Integer; Bmp : TBDBitmapData; lParam : Integer): Boolean;

  其中,Left为找到子图的左边距;Top为找到子图的顶边距;Bmp为调用EnumImage时给出的查找子图数据;lParam为调用EnumImage时给出的设置参数。该函数的返回值表示是否继续枚举。

  23、functionTBDBitmapData.FindColor(Color : TBDColor; var Left,Top : Integer): Boolean;

  24、functionTBDBitmapData.FindColor(Color : TBDColor; const Range : TBDColorRange; varLeft,Top : Integer): Boolean;

  重载的两个函数,从当前位图中查找指定的颜色,忽略当前位图背景颜色BackColor的设置,返回是否找到。无论是否找到都不会修改Error成员。第一个函数用于精确比较,第二个函数用于模糊比较。查找时忽略Left和Top的设置,从当前位图的左上角开始按从左到右,从上到下的顺序查找。找到返回true,设置Left和Top为找到颜色的位置,没找到返回false,设置Left和Top为-1。Color为BGR格式颜色;Left为找到颜色的左边距;Top为找到颜色的顶边距;Range为颜色变化范围。

  25、functionTBDBitmapData.FindCenterColor(Color : TBDColor; var Left,Top : Integer):Boolean;

  26、functionTBDBitmapData.FindCenterColor(Color : TBDColor; const Range : TBDColorRange;var Left,Top : Integer): Boolean;

  重载的两个函数,从当前位图中查找指定的颜色,忽略当前位图背景颜色BackColor的设置,返回是否找到。无论是否找到都不会修改Error成员。第一个函数用于精确比较,第二个函数用于模糊比较。查找时以Left和Top的设置为基点,从中心向四周查找。找到返回true,设置Left和Top为找到颜色的位置,没找到返回false,设置Left和Top为-1。Color为BGR格式颜色;Left为找到颜色的左边距;Top为找到颜色的顶边距;Range为颜色变化范围。

  示范程序,在屏幕上以某点为中心向四周模糊查找颜色:

var
    Bit : TBDBitmapData;
    Range : TBDColorRange;
    Left,Top : Integer;
begin
    Bit:=TBDBitmapData.Create;
    Bit.CopyFormScreen;
    Range.R:=5;
    Range.G:=5;
    Range.B:=5;
    Left:=600;
    Top:=380;
    if  Bit.FindCenterColor(BGR(0,250,250),Range,Left,Top) then
    begin
         {已找到颜色,进行相应的处理...}
    end;
    Bit.Free;
end;

  27、functionTBDBitmapData.EnumColor(Color : TBDColor; EnumColorProc : TBDEnumColorProc;lParam : Integer): Boolean;

  28、functionTBDBitmapData.EnumColor(Color : TBDColor; const Range : TBDColorRange;EnumColorProc : TBDEnumColorProc; lParam : Integer): Boolean;

  重载的两个函数,从当前图片中查找所有指定的颜色,即枚举颜色,忽略当前位图背景颜色BackColor的设置,返回是否找到。无论是否找到都不会修改Error成员。第一个函数用于精确比较,第二个函数用于模糊比较。查找时从当前位图的左上角开始按从左到右,从上到下的顺序查找。每找到一个颜色,就调用回调函数EnumColorProc,如果EnumColorProc返回false就停止查找,结束函数。Color为BGR格式颜色;EnumColorProc为回调函数;lParam为调用回调函数时发出的参数,可省略,默认为0;Range为颜色变化范围。TBDEnumColorProc的声明格式如下:

TBDEnumColorProc = function (Left,Top :  Integer; Color : TBDColor; lParam : Integer): Boolean;

  其中,Left为找到颜色的左边距;Top为找到颜色的顶边距;Color为找到的颜色,当使用模糊查找时该颜色为实际找到的颜色;lParam为调用EnumColor时给出的设置参数。该函数的返回值表示是否继续枚举。

  29、TBDBitmapData.Error

  最近一次操作出现的错误的说明。出于性能方面的考虑,只有导入、导出、截图等操作才会修改这个成员。而查找、枚举等操作无论是否成功都不会修改这个成员。

  30、TBDBitmapData.Name

  当前位图的名称,可读写。方便位图数据的管理。

  31、TBDBitmapData.Width

  当前位图宽度,以象素为单位,只读。

  32、TBDBitmapData.Height

  当前位图高度,以象素为单位,只读。

  33、TBDBitmapData.BackColor

  当前位图的背景颜色,BGR格式的颜色,可读写。当该颜色为BD_COLORLESS时,表示该位图不使用背景颜色。

  34、TBDBitmapData.LineWidth

  对齐后每行位图数据的宽度,以字节为单位,只读。

  35、TBDBitmapData.SpareWidth

  对齐后每行位图数据填充的多余宽度,以字节为单位,只读。

  36、TBDBitmapData.Size

  位图数据的长度,以字节为单位,只读。

  37、TBDBitmapData.Bits

  位图数据缓冲区指针,只读。这个指针是只读的,但它指向的数据是可读写的。可以将这个属性看成是一个一维的字节数组,可以对缓冲区中的数据进行访问和修改。

  38、TBDBitmapData.Pixels[Left,Top :Integer]

  位图的象素颜色,BGR格式的颜色,可读写。利用这个属性可以将位图看成是一个二维的象素矩阵,可以对矩阵中的象素颜色进行访问和修改。

  示范代码,位图数据的访问:

var
    Bit : TBDBitmapData;
begin
     Bit:=TBDBitmapData.Create;
    Bit.CopyFormScreen;
    Bit.Bits[50]; //以Byte格式访问
    Bit.Pixels[10,10]; //以BGR颜色格式访问
    Bit[10,10]; //等同于Bit.Pixels[10,10];
    Bit.Free;
end;

补充1 查找两张图片上的不同

  利用TBDBitmapData对象查找两张图片上的不同,从右上角开始利用双层循环遍历两图上的所有象素点,并相互比较。不完整代码如下:

procedure TForm1.Button5Click(Sender:  TObject);
var
    Bmp1,Bmp2 :  TBDBitmapData;
    Left,Top : Integer;
    IsExit : Boolean;
begin
     Bmp1:=TBDBitmapData.Create;
     Bmp2:=TBDBitmapData.Create;
    Bmp1.LoadFromFile('文件名1');
    Bmp2.LoadFromFile('文件名2');
    //假设两张图片一样大
    IsExit:=false;
    for Top:=0 to  Bmp1.Height-1 do
    begin
         for Left:=0 to Bmp1.Width-1 do
         begin
             if Bmp1[Left,Top]<>Bmp2[Left,Top] then
             begin
                 //在(Left,Top)位置两张图片有不同
                 //相应的处理...
                 if {如果不继续查找其它不同}  then
                 begin
                     IsExit:=true; //用以退出循环
                     break;
                 end;
             end;
         end;
         if IsExit then break;
    end;
    Bmp1.Free;
    Bmp2.Free;
end;

  以上代码不完整,可以根据需要进行修改,TBDBitmapData的其它详细使用方法可以参考本文的第5节。

补充2 多点找色

  利用TBDBitmapData对象进行多点找色。先来看这样一个问题,知道一个点的颜色c0,以及该点周围若干个点的颜色c1,c2,c3,...cn,现在要查找该点的位置。首先将要查找的点称为“基点”,周围若干个点称为“参照点”。利用TBDBitmapData对象实现多点找色,从右上角开始利用双层循环遍历两图上的所有象素点进行查找,不完整代码如下:

//从屏幕上的指定范围中截图,并进行多点查找,
//基点颜色Base的格式为16进制的RGB格式字符串,
//例如'804000',其中R=$80,G=$40,B=$00,
//参照点数据可以有多个段,每个段的格式为'水平差值|垂直差值|颜色值,',
//一个段代表一个参照点,坐标差值以基点为参照,使用屏幕坐标系,可以为负数,
//颜色值的格式同基点颜色,例如'-5|-1|FF0000,12|-3|0000FF,2|8|00FF00,-9|7|FF00FF,',
//以上例子定义了4个参照点,注意:调用本函数的格式要正确,本函数没有进行
//错误格式检查,调用例子如下:
//FindMutiColor(0,0,100,100,'804000','-5|-1|FF0000,12|-3|0000FF,2|8|00FF00,-9|7|FF00FF,');
//Left:截图的左边距;
//Top:截图的顶边距;
//Width:截图的宽度;
//Height:截图的高度;
//Base:基点颜色;
//Refer:参照点数据。
procedure FindMutiColor(Left,Top,Width,Height  : Integer; const Base,Refer : String);
type
    //参照点数据
    TReferData = record
         Color : TBDColor; //颜色值
         cx,cy : Integer; //相对于基点的差值坐标
    end;
var
     Off,Len,Count,x,y,rx,ry,i : Integer;
    s : String;
    BaseColor : TBDColor;  //基点颜色值
    ReferData : array of  TReferData; //参照点数据
    Bmp : TBDBitmapData;
    Flag : Boolean;
begin
    //解析Refer文本
    Count:=0; //参照点个数
    Off:=1;
    Len:=Length(Refer);
    while Off<=Len do
    begin
         SetLength(ReferData,Count+1);
         s:=Refer[Off]; Inc(Off);
         while Refer[Off]<>'|' do
         begin
             s:=s+Refer[Off]; Inc(Off);
         end;
         Inc(Off);
         ReferData[Count].cx:=StrToIntDef(s,0);
         s:=Refer[Off]; Inc(Off);
         while Refer[Off]<>'|' do
         begin
             s:=s+Refer[Off]; Inc(Off);
         end;
         Inc(Off);
         ReferData[Count].cy:=StrToIntDef(s,0);
         s:='$'+Refer[Off]; Inc(Off);
         while Refer[Off]<>',' do
         begin
             s:=s+Refer[Off]; Inc(Off);
         end;
         Inc(Off);
         ReferData[Count].Color:=StrToIntDef(s,0);
         Inc(Count);
    end;
    //截图
     Bmp:=TBDBitmapData.Create;
     Bmp.CopyFormScreen(Left,Top,Width,Height);
    //找色
     BaseColor:=StrToIntDef('$'+Base,0);
    for y:=0 to  Bmp.Height-1 do
    begin
         for x:=0 to Bmp.Width-1 do
         begin
             if Bmp[x,y]=BaseColor then
             begin
                 //核对参照点
                Flag:=True;
                 for i:=0 to Count-1 do with ReferData[i] do
                 begin
                     rx:=x+cx; ry:=y+cy;
                     if (rx<0) or (rx>=Bmp.Width) or
                        (ry<0) or (ry>=Bmp.Height) or
                        (Bmp[rx,ry]<>Color) then
                     begin
                         Flag:=False; break;
                     end;
                 end;
                 if Flag then
                 begin
                     //在(x,y)位置找到基点,注意,这个坐标相对于图片,而不是屏幕
                     //相应的处理...
                     Form1.Memo1.Lines.Add(Format('找到:%d,%d',[x,y]));
                 end;
             end;
         end;
    end;
    //收尾
     SetLength(ReferData,0);
    Bmp.Free;
    //完成
end;

  以上代码不完整,可以根据需要进行修改,TBDBitmapData的其它详细使用方法可以参考本文的第5节。

  利用TBDBitmapData对象进行多点找色的第二版。不用Bmp[x,y]样式,而用Buf[Off]样式,提高代码效率。不完整代码如下:

//从屏幕上的指定范围中截图,并进行多点查找,
//基点颜色Base的格式为16进制的RGB格式字符串,
//例如'804000',其中R=$80,G=$40,B=$00,
//参照点数据可以有多个段,每个段的格式为'水平差值|垂直差值|颜色值,',
//一个段代表一个参照点,坐标差值以基点为参照,使用屏幕坐标系,可以为负数,
//颜色值的格式同基点颜色,例如'-5|-1|FF0000,12|-3|0000FF,2|8|00FF00,-9|7|FF00FF,',
//以上例子定义了4个参照点,注意:调用本函数的格式要正确,本函数没有进行
//错误格式检查,调用例子如下:
//FindMutiColor(0,0,100,100,'804000','-5|-1|FF0000,12|-3|0000FF,2|8|00FF00,-9|7|FF00FF,');
//Left:截图的左边距;
//Top:截图的顶边距;
//Width:截图的宽度;
//Height:截图的高度;
//Base:基点颜色;
//Refer:参照点数据。
procedure  FindMutiColor(Left,Top,Width,Height : Integer; const Base,Refer : String);
type
    //参照点数据
    TReferData = record
         Color : TBDColor; //颜色值
         cx,cy : Integer; //相对于基点的差值坐标(二维)
         cf : Integer; //相对于基点的差值索引(一维)
    end;
var
     Off,Len,Count,LineOff,w,x,y,rx,ry,i : Integer;
    s : String;
    BaseColor : TBDColor;  //基点颜色值
    ReferData : array of  TReferData; //参照点数据
    Bmp : TBDBitmapData;
    Buf : PByteAry; //指向位图数据缓冲区
    Flag : Boolean;
begin
    //解析Refer文本
    Count:=0; //参照点个数
    Off:=1;
    Len:=Length(Refer);
    while Off<=Len do
    begin
         SetLength(ReferData,Count+1);
         s:=Refer[Off]; Inc(Off);
         while Refer[Off]<>'|' do
         begin
             s:=s+Refer[Off]; Inc(Off);
         end;
         Inc(Off);
         ReferData[Count].cx:=StrToIntDef(s,0);
         s:=Refer[Off]; Inc(Off);
         while Refer[Off]<>'|' do
         begin
             s:=s+Refer[Off]; Inc(Off);
         end;
         Inc(Off);
         ReferData[Count].cy:=StrToIntDef(s,0);
         s:='$'+Refer[Off]; Inc(Off);
         while Refer[Off]<>',' do
         begin
             s:=s+Refer[Off]; Inc(Off);
         end;
         Inc(Off);
         ReferData[Count].Color:=StrToIntDef(s,0);
         Inc(Count);
    end;
    //截图
     Bmp:=TBDBitmapData.Create;
     Bmp.CopyFormScreen(Left,Top,Width,Height);
    //将二维差值坐标转换为一维差值索引
    w:=Bmp.LineWidth;
    for i:=0 to Count-1 do  with ReferData[i] do
    begin
         cf:=3*cx-w*cy;
    end;
    //找色
     BaseColor:=StrToIntDef('$'+Base,0);
    LineOff:=Bmp.Size;
    Buf:=Bmp.Bits;
    for y:=0 to  Bmp.Height-1 do
    begin
         LineOff:=LineOff-Bmp.LineWidth;
         Off:=LineOff;
         for x:=0 to Bmp.Width-1 do
         begin
             if ((PInteger(@(Buf[Off])))^ and $FFFFFF)=BaseColor then
             begin
                 //核对参照点
                 Flag:=True;
                 for i:=0 to Count-1 do with ReferData[i] do
                 begin
                     rx:=x+cx; ry:=y+cy;
                     if (rx<0) or (rx>=Bmp.Width) or
                        (ry<0) or (ry>=Bmp.Height) or
                        (((PInteger(@(Buf[Off+cf])))^  and $FFFFFF)<>Color) then
                     begin
                         Flag:=False; break;
                     end;
                 end;
                 if Flag then
                 begin
                     Form1.Memo1.Lines.Add(Format('找到:%d,%d',[x,y]));
                 end;
             end;
             Off:=Off+3;
         end;
    end;
    //收尾
     SetLength(ReferData,0);
    Bmp.Free;
    //完成
end;

  以上代码不完整,可以根据需要进行修改,TBDBitmapData的其它详细使用方法可以参考本文的第5节。

更新于2009年6月1日

  更新BitmapData.pas文件,原文件中有个小BGU,主要是截取鼠标指针的图片时没有考虑当前的背景颜色,始终为黑色。更新后截取鼠标当前指针的图片时会使用当前背景颜色填充背景,如果没有指定背景颜色则使用白色(RGB(255,255,255))填充。

更新于2011年7月10日

  更新BitmapData.pas文件,在TBDBitmapData类中增加一个CopyFormBitmap成员。这个函数可以从TBitmap对象中拷贝图片。可以指定拷贝图片的位置、大小。

相关文件下载

  矩阵遍历示范项目下载(点这里

  示范程序项目下载(点这里

  BitmapData.pas(已更新)单元文件下载(点这里

  评论这张
 
阅读(3719)| 评论(76)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017