网摘-按键精灵屏幕找色原理分析

一、数据提取
  位图其实可以看成是一个由象素组成的矩阵,找图找色可以看成是象素值的比对。很多新手在设计这类的程序时喜欢使用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;

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

二、矩阵遍历

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

此主题相关图片如下:
(图1)

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

  螺旋遍历可以做到以一个基点为中心向四周遍历,这个基点可以不是矩阵的中心点,实际上基点可以是矩阵上的任意一点,甚至可以是矩阵外的点。注意:这里所说的“点”是指可以用(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;

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

三、找图找色

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

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

四、BitmapData.pas的使用

项目文件:
点击浏览该文件

(注:以上压缩包中的BitmapData.pas文件有个小BGU,主要是截取鼠标指针的图片时没有考虑当前的背景颜色,始终为黑色。在本贴三楼的压缩包中有更新后的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、constructor TBDBitmapData.Create(const AName : String);
  新建一个TBDBitmapData对象的实例。可以为实例指定一个名字,便于以后的管理。

6、procedure TBDBitmapData.Clear;
  清除当前TBDBitmapData对象中加载的位图数据。

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

8、function TBDBitmapData.SaveToStream(Stream : TStream):Boolean;
  将当前加载的位图数据导出到数据流Stream中,返回是否成功。如果失败将设置Error成员说明情况。数据将按24位BMP文件数据格式导出到数据流中。

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

10、function TBDBitmapData.SaveToFile(const FileName : string): Boolean;
  将当前加载的位图数据导出到文件中,返回是否成功。如果失败将设置Error成员说明情况。数据按24位BMP文件数据格式导出到文件中。

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

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

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

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

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

17、function TBDBitmapData.FindImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean;
18、function TBDBitmapData.FindImage(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为颜色变化范围。

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

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、function TBDBitmapData.FindCenterImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean;
20、function TBDBitmapData.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、function TBDBitmapData.EnumImage(Bmp : TBDBitmapData; EnumImageProc : TBDEnumImageProc; lParam : Integer): Boolean;
22、function TBDBitmapData.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、function TBDBitmapData.FindColor(Color : TBDColor; var Left,Top : Integer): Boolean;
24、function TBDBitmapData.FindColor(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为颜色变化范围。

25、function TBDBitmapData.FindCenterColor(Color : TBDColor; var Left,Top : Integer): Boolean;
26、function TBDBitmapData.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、function TBDBitmapData.EnumColor(Color : TBDColor; EnumColorProc : TBDEnumColorProc; lParam : Integer): Boolean;
28、function TBDBitmapData.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;

利用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;

以上代码不完整,可以根据需要进行修改。

原文地址:https://www.cnblogs.com/qq2806933146xiaobai/p/11177690.html

时间: 2024-10-10 14:16:31

网摘-按键精灵屏幕找色原理分析的相关文章

C#实现按键精灵的&#39;找图&#39; &#39;找色&#39; &#39;找字&#39;的功能

背景:游戏辅助功能通常使用按键精灵编写脚本,按键精灵的最大卖点就是能够找到画面中字,图,色,这对于模拟用户鼠标操作至关重要,这能找到道具,找到血量,实现自动打怪,自动补血,自动买卖道具,博主闲来无聊,看到一款按键精灵实现的辅助,于是乎想用WPF也写一款辅助工具,实现其核心的找图找色等功能.博主测试,对于背景复杂多变的画面,找不变图的成功率达到100%,找带透明的图,比如文字,能达到90%以上.默认您已经知道一个颜色值由argb构成,每个值范围都是0~255.网上发现不少人询问过该问题,几乎没有比

最近听说搞脚本挺爽的 弄了个按键精灵的找图找色

首先你需要下载个按键精灵的软件.如下http://www.anjian.com/ 下好后就打开按键精灵,首先新建一个空白脚本,然后找到抓取按钮. 随便找张图来找色 比如如上这张,用抓取按钮选择右键找到色素点,如#c10b2a 然后将该值放到颜色/图形命令去 在图形找色这里,还有找图功能,其实原理都差不多,都是通过周边像素点的色值来找到符合这个值得点,这样就能找到所选的数据. 当你找到你想得到的句柄(windows的某些你想要获取的窗口)时,就可以通过找色或找图去设置你自己所要的功能.

C#实现按键精灵的“找图”“找色”“找字”的功能

背景:游戏辅助功能通常使用按键精灵编写脚本,按键精灵的最大卖点就是能够找到画面中字,图,色,这对于模拟用户鼠标操作至关重要,这能找到道具,找到血量,实现自动打怪,自动补血,自动买卖道具,博主闲来无聊,看到一款按键精灵实现的辅助,于是乎想用WPF也写一款辅助工具,实现其核心的找图找色等功能.博主测试,对于背景复杂多变的画面,找不变图的成功率达到100%,找带透明的图,比如文字,能达到90%以上.默认您已经知道一个颜色值由argb构成,每个值范围都是0~255.网上发现不少人询问过该问题,几乎没有比

按键精灵【找图片,并打开该图或打开且关闭两段代码】

第一次学习,感觉其他语言来写太复杂,用脚本语言写这些简单功能,其实更好,不牵涉多线程这些的情况下,直接贴代码: 注意:只写了打开,并没有写关闭图片,弱需要,可以自行使用快捷键加上延时的命令,就可以不用手点,隔一段时间继续找下一张以及打开了,这有点类似与游戏内找东西. Function say() // 找与图片类似的窗口图片或图标,实现双击鼠标左键 FindPic 0,0,1024,768,"C:\Users\macbook\Desktop\腾讯视频.bmp",0.5,intX,int

原来找字也可以这样用ElseIf FindStr 手机按键精灵 跟大漠的区别

原来找字也可以这样用ElseIf FindStr(646, 1109, 776, 1261, "公告小叉", "FFFFFF-333333", 0.9, intX, intY) > 100 or intx > 0 Then         Tap intX+10, intY+10         Delay 1000 跟大漠的有什么区别?大神答:色值大漠是bgr数据模式   而手机按键精灵是rbg要转换的 大漠常用语句大全API http://xx51.

蓝丝雨零基础学习按键精灵VIP教程合集

课程目录:1.初识按键精灵2.标识符和关键字3.变量4.变量的作用域5.常量6.数据类型7.强制转换类型8.运算符与表达式19.运算符与表达式210.运算符优先级11.键盘命令12.鼠标移动13.鼠标单击14.获取鼠标位置15.得到鼠标特征码16.如果命令17.如果否则语句18.颜色判断语句19.条件选择语句20.For循环语句21.DoLoop循环语句22.WhileWend循环语句23.跳转语句和中断命令24.子程序25.参数的真正意义与使用技巧26.函数27.数组28.随机数29.冒泡算法

安卓 按键精灵 命令

运算符 变量和常量之间采用运算符连接后,称为表达式.MQ语言支持的运算符包括: 运算符 含义 + 加法运算 - 二元运算符:减法 一元运算符:负号 * 乘法运算 / 除法运算 Mod 整除取余 ^ 取幂 & 字符串连接 = 赋值 例如:当一个语句为X=Y时 等于(当用于表达式内部时) <> 不等于 > 大于 < 小于 >= 大于等于 <= 小于等于 Not 逻辑非 And 逻辑与 Or 逻辑或 基本语句 MQ的基本语句如下: 注意!以下标红为按键精灵(安卓版)新

初识按键精灵

最近一个朋友在拍沪牌,多次不中,于是到网上买了一个软件,号称可以很大程度的提高中标率,果然用了之后,两次就中.然后...,他就走上了代拍这条不归路...还让我帮他去输验证码.看后发现,其实那个软件完全就是按键精灵制作,难度肯定不大.于是有了我也来仿做一个的想法(因为久闻按键精灵的方便,但从未使用过). 首先,其主要功能为: 1.定时出价 2.识别当前的最低可成交价,从而来确定点击出价的时间 3.放大验证码 第一步,也是最烦锁的一步,就是找各个需要点击的点的从标位置,因为是在浏览器中,所以我们记录

仙境传说-按键精灵脚步研究

仙境传说是很早以前非常流行的一款网络游戏,其中的人物设置的非常可爱,尤其是一些夸张的头饰和百变的插卡系统让人回味无穷.虽然从游戏性来说仍然逃不出韩国游戏泡菜的怪圈(或者说是一款标准的泡菜游戏),但仍然是可圈可点的. 游戏中玩家要耗费大量时间进行练级,虽然现在的代理昆仑也有经验奖励的措施,比如高级经验书,高级JOB经验书,双倍经验时间区等,但是要让玩家,特别是高等级的玩家进行练级仍然是一件非常痛苦的事情.常有的事是,三转100~110的职业往往要在熔岩地图上打一个熔岩波利的怪物来进行性价比较高的练