在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
前言
最近有好几个朋友都在问我找图找色的问题,奇怪?于是乎写了一个专门用于找图找色的单元文件“BitmapData.pas”。在这个单元文件中我实现了从文件中导入位图、屏幕截图、鼠标指针截图、在图片上查找子图、在图片上查找颜色等功能。在查找过程中可以设定颜色变化范围、可以从左到右从上到下查找、也可以从指定点向四周查找。关于这个文件的下载和使用,可以参考本文的第四节。下面详细说说这些功能的实现。 一、数据提取 位图其实可以看成是一个由象素组成的矩阵,找图找色可以看成是象素值的比对。很多新手在设计这类的程序时喜欢使用TBitmap.Canvas.Pixels属性,这个属性其实是对API函数GetPixel的封装,这个函数执行速度是很慢的,主要用来对位图象素进行偶尔的访问。而比对过程中需要对象素进行频繁的访问,造成程序运行缓慢。另外一种方法是使用TBitmap.ScanLine属性,利用它可以直接访问位图的数据。但是这些数据和当前位图的格式有关,主要是色深方面的问题,不同的色深会有不同格式的数据。另外比对过程中也需要对该属性进行频繁的调用。由于比对过程完全是数据的比较,不需要进行绘制操作。所以可以一次性将位图的数据提取出来放置到一个缓冲区中再进行比对,这样程序的性能会更高,也便于查找算法的实现。这时可以调用API函数GetDIBits获得设备无关位图的RGB数据,其实ScanLine属性也是调用这个函数实现的。GetDIBits函数格式声明如下: function GetDIBits( 其中TBitmapInfo结构的格式如下: tagBITMAPINFO = packed record 在上述结构中主要使用bmiHeader成员,TBitmapInfoHeader结构的格式如下: tagBITMAPINFOHEADER = packed record 在上面两个结构中,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 接着假设有一个位图,高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); 对于标准的24位BMP位图文件,其中的位图数据也是以上述格式保存的。有的24位BMP文件并不标准,所以文件最好使用Windows自带的画图程序保存。以下的示范代码实现从标准的24位BMP格式文件中导入位图数据到缓冲区中返回: procedure LoadFile(const FileName : string; var Bits : PByteAry; var Size : Integer); 综上所述,当位图数据提取到一个缓冲区中,找图找色就是对这个缓冲区中的数据进行访问的过程。而这个缓冲区可以作为一个矩阵来进行访问。只要对矩阵进行遍历就可以实现找图找色的算法。 二、矩阵遍历 矩阵遍历是一个数据结构方面的问题。假设有一个矩阵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 const //矩阵大小 var //螺旋遍历(不支持步长) //访问矩阵元素 VisitCount:=VisitCount+1; 这里还有一个步长的问题,所谓步长就是指在遍历的时候跳过一些点,只平均访问矩阵中的某些点。例如以下数据就是步长为2以(3,2)为基点的螺旋遍历后的矩阵,其中“-”表示遍历时没有访问到的点。 输出矩阵: 使用步长可以实现矩阵的抽样查找,但上面给出的螺旋遍历算法却不支持步长。因为它要利用访问计数退出循环,使用步长时会使矩阵中访问到的点的数目不确定,使的上述算法出现死循环。对上述算法的一个改进是使用一个逻辑变量记录遍历一轮是否有访问到点。如果没有,说明这一轮访问已经以位于矩阵之外可以退出循环。当步长为1时这种改进的算法要比前面的算法更慢,因为它要“空转”一轮。而且这种算法也不支持矩阵外的点作为基点,它会使循环提前退出。支持步长的螺旋遍历算法的示范代码如下:注意这时的VisitCount仅作为测试使用,不作为退出循环的条件。 type const //移动坐标差 //矩阵大小 var //螺旋遍历2(支持步长) //访问矩阵元素 Visit:=true; 对于回形遍历与螺旋遍历大同小异,这里就不多说了。在下面的压缩包中是矩阵遍历的示范程序,里面有一般遍历、螺旋遍历和回形遍历的示范代码,可以用于参考。 项目文件: 三、找图找色 结合本文第一节和第二节的内容设计一个找图找色的程序应该不是问题。对于一个位图可以看成是由象素组成的矩阵,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; 2、function RGBtoBGR(C : TColor): TBDColor; 3、function BGRtoRGB(C : TBDColor): TColor; 4、function BDCompareColor(C1,C2 : TBDColor; const Range : TBDColorRange): Boolean; TBDColorRange = record 其中,R表示C1和C2中红色通道最大的相差值;G表示C1和C2中绿色通道最大的相差值;B表示C1和C2中蓝色通道最大的相差值。 示范程序,比较两个颜色: var C1:=BGR(125,125,125); C1:=BGR(125,120,125); C1:=BGR(125,200,125); 5、constructor TBDBitmapData.Create(const AName : String); 6、procedure TBDBitmapData.Clear; 7、function TBDBitmapData.LoadFromStream(Stream : TStream; ABackColor : TBDColor): Boolean; 8、function TBDBitmapData.SaveToStream(Stream : TStream):Boolean; 9、function TBDBitmapData.LoadFromFile(const FileName : string; ABackColor : TBDColor): Boolean; 10、function TBDBitmapData.SaveToFile(const FileName : string): Boolean; 11、function TBDBitmapData.LoadFromBitmap(Bitmap : TBitmap): Boolean; 12、function TBDBitmapData.SaveToBitmap(Bitmap : TBitmap): Boolean; 13、function TBDBitmapData.CopyFormScreen(Left,Top,AWidth,AHeight : Integer): Boolean; 14、function TBDBitmapData.CopyFormCursor: Boolean; 15、function TBDBitmapData.Compare(Bmp : TBDBitmapData; Left,Top : Integer): Boolean; 17、function TBDBitmapData.FindImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean; 示范程序,在屏幕上查找子图: var Bit1.CopyFormScreen; if Bit1.FindImage(Bit2,Left,Top) then Bit1.Free; 19、function TBDBitmapData.FindCenterImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean; 21、function TBDBitmapData.EnumImage(Bmp : TBDBitmapData; EnumImageProc : TBDEnumImageProc; lParam : Integer): Boolean; 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; 25、function TBDBitmapData.FindCenterColor(Color : TBDColor; var Left,Top : Integer): Boolean; 示范程序,在屏幕上以某点为中心向四周模糊查找颜色: var Range.R:=5; Left:=600; if Bit.FindCenterColor(BGR(0,250,250),Range,Left,Top) then Bit.Free; 27、function TBDBitmapData.EnumColor(Color : TBDColor; EnumColorProc : TBDEnumColorProc; lParam : Integer): Boolean; 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 34、TBDBitmapData.LineWidth 35、TBDBitmapData.SpareWidth 36、TBDBitmapData.Size 37、TBDBitmapData.Bits 38、TBDBitmapData.Pixels[Left,Top : Integer] 示范代码,位图数据的访问: var Bit.Bits[50]; //以Byte格式访问 Bit.Pixels[10,10]; //以BGR颜色格式访问 Bit[10,10]; //等同于Bit.Pixels[10,10]; Bit.Free; 文件下载:https://files.cnblogs.com/rogee/BitMap%5bNOBUG%5d.zip 1 unit BitmapData; 2 3 // 4 //位图数据处理,主要用于位图的找图找色 5 //作者:yeye55 2009年5月31日 6 // 7 //版权 2009,由 yeye55 拥有,保留所有权利。 8 //本文件中的代码是免费程序,无需任何授权或许可即可用于个人和商业目的。使用者一切后果自负。 9 // 10 //如果你转载了本文件中的代码,请注明代码出处和代码作者; 11 //如果你修改了本文件中的代码,请注明修改位置和修改作者。 12 // 13 //本文件最早在http://www.programbbs.com/bbs/上发布 14 // 15 16 interface 17 18 uses 19 Windows, Classes, SysUtils, Graphics; 20 21 const 22 BD_COLORLESS = -1; //无色 23 BD_BITCOUNT = 24; //图象位数 24 BD_BYTECOUNT = BD_BITCOUNT shr 3; //每象素占用字节数 25 BD_LINEWIDTH = 32; //每行数据对齐宽度(位) 26 27 type 28 //字节数组 29 TByteAry = array [0..0] of Byte; 30 PByteAry = ^TByteAry; 31 32 //颜色变化范围,R、G、B三个通道的绝对差值 33 TBDColorRange = record 34 R : Integer; 35 G : Integer; 36 B : Integer; 37 end; 38 39 TBDColor = Integer; //BGR格式颜色 40 41 //转换函数 42 function BGR(B,G,R : Byte): TBDColor; 43 function RGBtoBGR(C : TColor): TBDColor; 44 function BGRtoRGB(C : TBDColor): TColor; 45 //比较颜色 46 function BDCompareColor(C1,C2 : TBDColor; const Range : TBDColorRange): Boolean; 47 48 type 49 TBDBitmapData = class; //位图数据 50 51 //枚举子图回调函数,查找多个子图时回调,返回是否继续枚举, 52 //Left:找到子图的左边距; 53 //Top:找到子图的顶边距; 54 //Bmp:找到子图数据; 55 //lParam:调用时设置的参数。 56 TBDEnumImageProc = function (Left,Top : Integer; Bmp : TBDBitmapData; lParam : Integer): Boolean; 57 58 //枚举颜色回调函数,查找多个颜色时回调,返回是否继续枚举, 59 //Left:找到颜色的左边距; 60 //Top:找到颜色的顶边距; 61 //Color:找到的颜色; 62 //lParam:调用时设置的参数。 63 TBDEnumColorProc = function (Left,Top : Integer; Color : TBDColor; lParam : Integer): Boolean; 64 65 //位图数据 66 TBDBitmapData = class 67 private 68 FName : String; //位图名称 69 FWidth : Integer; //位图宽度(象素) 70 FHeight : Integer; //位图高度(象素) 71 FBackColor : TBDColor; //背景颜色(BGR格式) 72 FLineWidth : Integer; //对齐后每行数据宽度(字节) 73 FSpareWidth : Integer; //对齐后每行数据多余宽度(字节) 74 FSize : Integer; //位图数据长度 75 FBufSize : Integer; //缓冲区实际长度 76 FBits : PByteAry; //位图数据缓冲区 77 function InitData(AWidth,AHeight : Integer): Boolean; 78 function GetPixels(Left,Top : Integer): TBDColor; 79 procedure SetPixels(Left,Top : Integer; Value : TBDColor); 80 public 81 Error : String; 82 constructor Create(const AName : String = ''); 83 destructor Destroy; override; 84 procedure Clear; 85 function LoadFromStream(Stream : TStream; ABackColor : TBDColor = BD_COLORLESS): Boolean; 86 function SaveToStream(Stream : TStream):Boolean; 87 function LoadFromFile(const FileName : string; ABackColor : TBDColor = BD_COLORLESS): Boolean; 88 function SaveToFile(const FileName : string): Boolean; 89 function LoadFromBitmap(Bitmap : TBitmap): Boolean; 90 function SaveToBitmap(Bitmap : TBitmap): Boolean; 91 function CopyFormScreen(Left : Integer = -1; Top : Integer = -1; AWidth : Integer = -1; AHeight : Integer = -1): Boolean; 92 function CopyFormCursor: Boolean; 93 function Compare(Bmp : TBDBitmapData; Left : Integer = 0; Top : Integer = 0): Boolean; overload; 94 function Compare(Bmp : TBDBitmapData; const Range : TBDColorRange; Left : Integer = 0; Top : Integer = 0): Boolean; overload; 95 function FindImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean; overload; 96 function FindImage(Bmp : TBDBitmapData; const Range : TBDColorRange; var Left,Top : Integer): Boolean; overload; 97 function FindCenterImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean; overload; 98 function FindCenterImage(Bmp : TBDBitmapData; const Range : TBDColorRange; var Left,Top : Integer): Boolean; overload; 99 function EnumImage(Bmp : TBDBitmapData; EnumImageProc : TBDEnumImageProc; lParam : Integer = 0): Boolean; overload; 100 function EnumImage(Bmp : TBDBitmapData; const Range : TBDColorRange; EnumImageProc : TBDEnumImageProc; lParam : Integer = 0): Boolean; overload; 101 function FindColor(Color : TBDColor; var Left,Top : Integer): Boolean; overload; 102 function FindColor(Color : TBDColor; const Range : TBDColorRange; var Left,Top : Integer): Boolean; overload; 103 function FindCenterColor(Color : TBDColor; var Left,Top : Integer): Boolean; overload; 104 function FindCenterColor(Color : TBDColor; const Range : TBDColorRange; var Left,Top : Integer): Boolean; overload; 105 function EnumColor(Color : TBDColor; EnumColorProc : TBDEnumColorProc; lParam : Integer = 0): Boolean; overload; 106 function EnumColor(Color : TBDColor; const Range : TBDColorRange; EnumColorProc : TBDEnumColorProc; lParam : Integer = 0): Boolean; overload; 107 property Name : String read FName write FName; //位图名称 108 property Width : Integer read FWidth; //位图宽度(象素) 109 property Height : Integer read FHeight; //位图高度(象素) 110 property BackColor : TBDColor read FBackColor write FBackColor; //背景颜色(BGR格式) 111 property LineWidth : Integer read FLineWidth; //对齐后每行数据宽度(字节) 112 property SpareWidth : Integer read FSpareWidth; //对齐后每行数据多余宽度(字节) 113 property Size : Integer read FSize; //位图数据长度 114 property Bits : PByteAry read FBits; //位图数据缓冲区 115 property Pixels[Left,Top : Integer] : TBDColor read GetPixels write SetPixels; default; 116 end; 117 118 implementation 119 120 type 121 //矩阵遍历方向 122 TAspect = (asLeft, asRight, asUp, asDown); 123 124 const 125 //移动坐标差,用于矩阵遍历 126 MoveVal : array [asLeft..asDown] of TPoint = ( 127 (X : -1; Y : 0), //asLeft 128 (X : 1; Y : 0), //asRight 129 (X : 0; Y : -1), //asUp 130 (X : 0; Y : 1) //asDown 131 ); 132 133 var 134 ScreenWidth : Integer; 135 ScreenHeight : Integer; 136 IconWidth : Integer; 137 IconHeight : Integer; 138 139 //根据B、G、R三个通道的值生成一个BGR格式颜色。 140 function BGR(B,G,R : Byte): TBDColor; 141 begin 142 result:=(B or (G shl 8) or (R shl 16)); 143 end; 144 145 //RGB颜色格式转换到BGR颜色格式。 146 function RGBtoBGR(C : TColor): TBDColor; 147 begin 148 result:=((C and $FF0000) shr 16) or (C and $00FF00) 全部评论
专题导读
热门推荐
热门话题
阅读排行榜
|
请发表评论