MySQL带参数的存储过程小例子 - IT徐胖子的专栏 - 博客频道 - CSDN.NET

mikel阅读(984)

 

来源: MySQL带参数的存储过程小例子 – IT徐胖子的专栏 – 博客频道 – CSDN.NET

存储过程P_GET_CLASS_NAME是根据输入的班级号判断班级名称

存储过程P_INSERT_STUDENT是接收输入的学生信息,最终将信息插入学生表。

  1. DROP PROCEDURE IF EXISTS `P_GET_CLASS_NAME`;
  2. CREATE PROCEDURE P_GET_CLASS_NAME(IN ID int,OUT NAME VARCHAR(50))
  3. BEGIN
  4.     IF(ID = 1) THEN
  5.           SET NAME = ‘一班’;
  6.     END IF;
  7.     IF(ID = 2) THEN
  8.           SET NAME = ‘二班’;
  9.     END IF;
  10. END;
  11. DROP PROCEDURE IF EXISTS `P_INSERT_STUDENT`;
  12. CREATE PROCEDURE P_INSERT_STUDENT(IN ID INT,IN NAME VARCHAR(10),IN CLASSNO INT,IN BIRTH DATETIME)
  13. BEGIN
  14.      SET @ID = ID;
  15.      SET @NAME = NAME;
  16.      SET @CLASSNO = CLASSNO;
  17.      SET @BIRTH = BIRTH;
  18.      SET @CLASSNAME = NULL;
  19.      CALL P_GET_CLASS_NAME(@CLASSNO,@CLASSNAME);
  20.      SET @insertSQL = CONCAT(‘INSERT INTO TBL_STUDENT VALUES(?,?,?,?)’);
  21.      PREPARE stmtinsert FROM @insertSQL;
  22.      EXECUTE stmtinsert USING @ID,@NAME,@CLASSNAME,@BIRTH;
  23.      DEALLOCATE PREPARE stmtinsert;
  24. END;
  25. CALL P_INSERT_STUDENT(1,‘xy’,1,‘2012-10-01 10:20:01’);

 

在第二个存储过程中

①利用SET声明了参数,调用了第一个存储过程

②在第一个存储过程中的NAME参数是输出参数,所以@CLASSNAME这个参数在调用完第一个过程后就被附值

③最终利用CONCAT拼接SQL语句并传入参数执行SQL语句

 

CALL P_INSERT_STUDENT(1,’xy’,1,’2012-10-01 10:20:01′);调用存储过程

 

MySQL存储过程详解  mysql 存储过程_王者佳暮_新浪博客

mikel阅读(847)

MySQL存储过程详解  mySQL 存储过程_王者佳暮_新浪博客,王者佳暮,

来源: MySQL存储过程详解  mysql 存储过程_王者佳暮_新浪博客

mySQL存储过程详解

  1.     存储过程简介

 

我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。

一个存储过程是一个可编程的函数,它在数据库中创建并保存。它可以有SQL语句和一些特殊的控制结构组成。当希望在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过程是非常有用的。数据库中的存储过程可以看做是对编程中面向对象方法的模拟。它允许控制数据的访问方式。

存储过程通常有以下优点:

(1).存储过程增强了SQL语言的功能和灵活性。存储过程可以用流控制语句编写,有很强的灵活性,可以完成复杂的判断和较复杂的运算。

(2).存储过程允许标准组件是编程。存储过程被创建后,可以在程序中被多次调用,而不必重新编写该存储过程的SQL语句。而且数据库专业人员可以随时对存储过程进行修改,对应用程序源代码毫无影响。

(3).存储过程能实现较快的执行速度。如果某一操作包含大量的Transaction-SQL代码或分别被多次执行,那么存储过程要比批处理的执行速度快很多。因为存储过程是预编译的。在首次运行一个存储过程时查询,优化器对其进行分析优化,并且给出最终被存储在系统表中的执行计划。而批处理的Transaction-SQL语句在每次运行时都要进行编译和优化,速度相对要慢一些。

(4).存储过程能过减少网络流量。针对同一个数据库对象的操作(如查询、修改),如果这一操作所涉及的Transaction-SQL语句被组织程存储过程,那么当在客户计算机上调用该存储过程时,网络中传送的只是该调用语句,从而大大增加了网络流量并降低了网络负载。

(5).存储过程可被作为一种安全机制来充分利用。系统管理员通过执行某一存储过程的权限进行限制,能够实现对相应的数据的访问权限的限制,避免了非授权用户对数据的访问,保证了数据的安全。

 

  1.     关于MySQL的存储过程

存储过程是数据库存储的一个重要的功能,但是MySQL在5.0以前并不支持存储过程,这使得MySQL在应用上大打折扣。好在MySQL 5.0终于开始已经支持存储过程,这样即可以大大提高数据库的处理速度,同时也可以提高数据库编程的灵活性。

  1.     MySQL存储过程的创建

 

(1). 格式

MySQL存储过程创建的格式:CREATE PROCEDURE 过程名 ([过程参数[,…]])
[特性 …] 过程体

这里先举个例子:

  1. mysql> DELIMITER //
  2. mysql> CREATE PROCEDURE proc1(OUT s int)
  3.     -> BEGIN
  4.     -> SELECT COUNT(*) INTO s FROM user;
  5.     -> END
  6.     -> //
  7. mysql> DELIMITER ;

注:

(1)这里需要注意的是DELIMITER //和DELIMITER ;两句,DELIMITER是分割符的意思,因为MySQL默认以”;”为分隔符,如果我们没有声明分割符,那么编译器会把存储过程当成SQL语句进行处理,则存储过程的编译过程会报错,所以要事先用DELIMITER关键字申明当前段分隔符,这样MySQL才会将”;”当做存储过程中的代码,不会执行这些代码,用完了之后要把分隔符还原。

(2)存储过程根据需要可能会有输入、输出、输入输出参数,这里有一个输出参数s,类型是int型,如果有多个参数用”,”分割开。

(3)过程体的开始与结束使用BEGIN与END进行标识。

这样,我们的一个MySQL存储过程就完成了,是不是很容易呢?看不懂也没关系,接下来,我们详细的讲解。

 

(2). 声明分割符

 

其实,关于声明分割符,上面的注解已经写得很清楚,不需要多说,只是稍微要注意一点的是:如果是用MySQL的Administrator管理工具时,可以直接创建,不再需要声明。

 

(3). 参数

MySQL存储过程的参数用在存储过程的定义,共有三种参数类型,IN,OUT,INOUT,形式如:

CREATE PROCEDURE([[IN |OUT |INOUT ] 参数名 数据类形…])

IN 输入参数:表示该参数的值必须在调用存储过程时指定,在存储过程中修改该参数的值不能被返回,为默认值

OUT 输出参数:该值可在存储过程内部被改变,并可返回

INOUT 输入输出参数:调用时指定,并且可被改变和返回

Ⅰ. IN参数例子

创建:

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE demo_in_parameter(IN p_in int)
  3. -> BEGIN
  4. -> SELECT p_in;
  5. -> SET p_in=2;
  6. -> SELECT p_in;
  7. -> END;
  8. -> //
  9. mysql > DELIMITER ;

执行结果:

  1. mysql > SET @p_in=1;
  2. mysql > CALL demo_in_parameter(@p_in);
  3. +——+
  4. | p_in |
  5. +——+
  6. |   1  |
  7. +——+
  8. +——+
  9. | p_in |
  10. +——+
  11. |   2  |
  12. +——+
  13. mysql> SELECT @p_in;
  14. +——-+
  15. | @p_in |
  16. +——-+
  17. |  1    |
  18. +——-+

以上可以看出,p_in虽然在存储过程中被修改,但并不影响@p_id的值

 

Ⅱ.OUT参数例子

创建:

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE demo_out_parameter(OUT p_out int)
  3. -> BEGIN
  4. -> SELECT p_out;
  5. -> SET p_out=2;
  6. -> SELECT p_out;
  7. -> END;
  8. -> //
  9. mysql > DELIMITER ;

执行结果:

  1. mysql > SET @p_out=1;
  2. mysql > CALL sp_demo_out_parameter(@p_out);
  3. +——-+
  4. | p_out |
  5. +——-+
  6. | NULL  |
  7. +——-+
  8. +——-+
  9. | p_out |
  10. +——-+
  11. |   2   |
  12. +——-+
  13. mysql> SELECT @p_out;
  14. +——-+
  15. | p_out |
  16. +——-+
  17. |   2   |
  18. +——-+

Ⅲ. INOUT参数例子

创建:

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE demo_inout_parameter(INOUT p_inout int)
  3. -> BEGIN
  4. -> SELECT p_inout;
  5. -> SET p_inout=2;
  6. -> SELECT p_inout;
  7. -> END;
  8. -> //
  9. mysql > DELIMITER ;

 

 

执行结果:

  1. mysql > SET @p_inout=1;
  2. mysql > CALL demo_inout_parameter(@p_inout) ;
  3. +———+
  4. | p_inout |
  5. +———+
  6. |    1    |
  7. +———+
  8. +———+
  9. | p_inout |
  10. +———+
  11. |    2    |
  12. +———+
  13. mysql > SELECT @p_inout;
  14. +———-+
  15. | @p_inout |
  16. +———-+
  17. |    2     |
  18. +———-+

(4). 变量

Ⅰ. 变量定义

DECLARE variable_name [,variable_name…] datatype [DEFAULT value];

其中,datatype为MySQL的数据类型,如:int, float, date, varchar(length)

例如:

  1. DECLARE l_int int unsigned default 4000000;
  2. DECLARE l_numeric number(8,2) DEFAULT 9.95;
  3. DECLARE l_date date DEFAULT ‘1999-12-31’;
  4. DECLARE l_datetime datetime DEFAULT ‘1999-12-31 23:59:59’;
  5. DECLARE l_varchar varchar(255) DEFAULT ‘This will not be padded’;

 

 

Ⅱ. 变量赋值

SET 变量名 = 表达式值 [,variable_name = expression …]

 

Ⅲ. 用户变量

 

ⅰ. 在MySQL客户端使用用户变量

  1. mysql > SELECT ‘Hello World’ into @x;
  2. mysql > SELECT @x;
  3. +————-+
  4. |   @x        |
  5. +————-+
  6. Hello World |
  7. +————-+
  8. mysql > SET @y=’Goodbye Cruel World’;
  9. mysql > SELECT @y;
  10. +———————+
  11. |     @y              |
  12. +———————+
  13. | Goodbye Cruel World |
  14. +———————+
  15. mysql > SET @z=1+2+3;
  16. mysql > SELECT @z;
  17. +——+
  18. | @z   |
  19. +——+
  20. |  6   |
  21. +——+

ⅱ. 在存储过程中使用用户变量

  1. mysql > CREATE PROCEDURE GreetWorld( ) SELECT CONCAT(@greeting,’ World’);
  2. mysql > SET @greeting=’Hello’;
  3. mysql > CALL GreetWorld( );
  4. +—————————-+
  5. | CONCAT(@greeting,’ World’) |
  6. +—————————-+
  7. |  Hello World               |
  8. +—————————-+

 

ⅲ. 在存储过程间传递全局范围的用户变量

  1. mysql> CREATE PROCEDURE p1()   SET @last_procedure=’p1′;
  2. mysql> CREATE PROCEDURE p2() SELECT CONCAT(‘Last procedure was ‘,@last_proc);
  3. mysql> CALL p1( );
  4. mysql> CALL p2( );
  5. +———————————————–+
  6. | CONCAT(‘Last procedure was ‘,@last_proc  |
  7. +———————————————–+
  8. | Last procedure was p1                         |
  9. +———————————————–+

 

 

注意:

①用户变量名一般以@开头

②滥用用户变量会导致程序难以理解及管理

 

(5). 注释

 

MySQL存储过程可使用两种风格的注释

双模杠:–

该风格一般用于单行注释

c风格: 一般用于多行注释

例如:

 

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE proc1 –name存储过程名
  3. -> (IN parameter1 INTEGER)
  4. -> BEGIN
  5. -> DECLARE variable1 CHAR(10);
  6. -> IF parameter1 = 17 THEN
  7. -> SET variable1 = ‘birds’;
  8. -> ELSE
  9. -> SET variable1 = ‘beasts’;
  10. -> END IF;
  11. -> INSERT INTO table1 VALUES (variable1);
  12. -> END
  13. -> //
  14. mysql > DELIMITER ;

 

  1.     MySQL存储过程的调用

用call和你过程名以及一个括号,括号里面根据需要,加入参数,参数包括输入参数、输出参数、输入输出参数。具体的调用方法可以参看上面的例子。

  1.     MySQL存储过程的查询

我们像知道一个数据库下面有那些表,我们一般采用show tables;进行查看。那么我们要查看某个数据库下面的存储过程,是否也可以采用呢?答案是,我们可以查看某个数据库下面的存储过程,但是是令一钟方式。

我们可以用

select name from mysql.proc where db=’数据库名’;

或者

select routine_name from information_schema.routines where routine_schema=’数据库名’;

或者

show procedure status where db=’数据库名’;

进行查询。

如果我们想知道,某个存储过程的详细,那我们又该怎么做呢?是不是也可以像操作表一样用describe 表名进行查看呢?

答案是:我们可以查看存储过程的详细,但是需要用另一种方法:

SHOW CREATE PROCEDURE 数据库.存储过程名;

就可以查看当前存储过程的详细。

 

  1.     MySQL存储过程的修改

ALTER PROCEDURE

更改用CREATE PROCEDURE 建立的预先指定的存储过程,其不会影响相关存储过程或存储功能。

 

  1.     MySQL存储过程的删除

删除一个存储过程比较简单,和删除表一样:

DROP PROCEDURE

从MySQL的表格中删除一个或多个存储过程。

 

  1.     MySQL存储过程的控制语句

(1). 变量作用域

内部的变量在其作用域范围内享有更高的优先权,当执行到end。变量时,内部变量消失,此时已经在其作用域外,变量不再可见了,应为在存储
过程外再也不能找到这个申明的变量,但是你可以通过out参数或者将其值指派
给会话变量来保存其值。

 

 

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE proc3()
  3.      -> begin
  4.      -> declare x1 varchar(5) default ‘outer’;
  5.      -> begin
  6.      -> declare x1 varchar(5) default ‘inner’;
  7.      -> select x1;
  8.      -> end;
  9.      -> select x1;
  10.      -> end;
  11.      -> //
  12. mysql > DELIMITER ;

 

 (2). 条件语句

. if-then -else语句

 

 

 

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE proc2(IN parameter int)
  3.      -> begin
  4.      -> declare var int;
  5.      -> set var=parameter+1;
  6.      -> if var=0 then
  7.      -> insert into t values(17);
  8.      -> end if;
  9.      -> if parameter=0 then
  10.      -> update t set s1=s1+1;
  11.      -> else
  12.      -> update t set s1=s1+2;
  13.      -> end if;
  14.      -> end;
  15.      -> //
  16. mysql > DELIMITER ;

. case语句:

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE proc3 (in parameter int)
  3.      -> begin
  4.      -> declare var int;
  5.      -> set var=parameter+1;
  6.      -> case var
  7.      -> when 0 then
  8.      -> insert into t values(17);
  9.      -> when 1 then
  10.      -> insert into t values(18);
  11.      -> else
  12.      -> insert into t values(19);
  13.      -> end case;
  14.      -> end;
  15.      -> //
  16. mysql > DELIMITER ;

 

(3). 循环语句

. while ···· end while

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE proc4()
  3.      -> begin
  4.      -> declare var int;
  5.      -> set var=0;
  6.      -> while var<6 do
  7.      -> insert into t values(var);
  8.      -> set var=var+1;
  9.      -> end while;
  10.      -> end;
  11.      -> //
  12. mysql > DELIMITER ;

 

 

. repeat···· end repeat

它在执行操作后检查结果,而while则是执行前进行检查。

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE proc5 ()
  3.      -> begin
  4.      -> declare v int;
  5.      -> set v=0;
  6.      -> repeat
  7.      -> insert into t values(v);
  8.      -> set v=v+1;
  9.      -> until v>=5
  10.      -> end repeat;
  11.      -> end;
  12.      -> //
  13. mysql > DELIMITER ;

 
. loop ·····end loop:

loop循环不需要初始条件,这点和while 循环相似,同时和repeat循环一样不需要结束条件, leave语句的意义是离开循环。

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE proc6 ()
  3.      -> begin
  4.      -> declare v int;
  5.      -> set v=0;
  6.      -> LOOP_LABLE:loop
  7.      -> insert into t values(v);
  8.      -> set v=v+1;
  9.      -> if v >=5 then
  10.      -> leave LOOP_LABLE;
  11.      -> end if;
  12.      -> end loop;
  13.      -> end;
  14.      -> //
  15. mysql > DELIMITER ;

 

 

. LABLES 标号:

标号可以用在begin repeat while 或者loop 语句前,语句标号只能在合法的语句前面使用。可以跳出循环,使运行指令达到复合语句的最后一步。

 

(4). ITERATE迭代

. ITERATE:

通过引用复合语句的标号,来从新开始复合语句

  1. mysql > DELIMITER //
  2. mysql > CREATE PROCEDURE proc10 ()
  3.      -> begin
  4.      -> declare v int;
  5.      -> set v=0;
  6.      -> LOOP_LABLE:loop
  7.      -> if v=3 then
  8.      -> set v=v+1;
  9.      -> ITERATE LOOP_LABLE;
  10.      -> end if;
  11.      -> insert into t values(v);
  12.      -> set v=v+1;
  13.      -> if v>=5 then
  14.      -> leave LOOP_LABLE;
  15.      -> end if;
  16.      -> end loop;
  17.      -> end;
  18.      -> //
  19. mysql > DELIMITER ;

 

 

  1.     MySQL存储过程的基本函数

 

(1).字符串类

CHARSET(str) //返回字串字符集
CONCAT (string2 [,… ]) //连接字串
INSTR (string ,substring ) //返回substring首次在string中出现的位置,不存在返回0
LCASE (string2 ) //转换成小写
LEFT (string2 ,length ) //从string2中的左边起取length个字符
LENGTH (string ) //string长度
LOAD_FILE (file_name ) //从文件读取内容
LOCATE (substring , string [,start_position ] ) 同INSTR,但可指定开始位置
LPAD (string2 ,length ,pad ) //重复用pad加在string开头,直到字串长度为length
LTRIM (string2 ) //去除前端空格
REPEAT (string2 ,count ) //重复count次
REPLACE (str ,search_str ,replace_str ) //在str中用replace_str替换search_str
RPAD (string2 ,length ,pad) //在str后用pad补充,直到长度为length
RTRIM (string2 ) //去除后端空格
STRCMP (string1 ,string2 ) //逐字符比较两字串大小,
SUBSTRING (str , position [,length ]) //从str的position开始,取length个字符,
注:mysql中处理字符串时,默认第一个字符下标为1,即参数position必须大于等于1

 

  1. mysql> select substring(‘abcd’,0,2);
  2. +———————–+
  3. | substring(‘abcd’,0,2) |
  4. +———————–+
  5. |                       |
  6. +———————–+
  7. 1 row in set (0.00 sec)
  8. mysql> select substring(‘abcd’,1,2);
  9. +———————–+
  10. | substring(‘abcd’,1,2) |
  11. +———————–+
  12. |     ab                |
  13. +———————–+
  14. 1 row in set (0.02 sec)

TRIM([[BOTH|LEADING|TRAILING] [padding] FROM]string2) //去除指定位置的指定字符
UCASE (string2 ) //转换成大写
RIGHT(string2,length) //取string2最后length个字符
SPACE(count) //生成count个空格

(2).数学类

ABS (number2 ) //绝对值
BIN (decimal_number ) //十进制转二进制
CEILING (number2 ) //向上取整
CONV(number2,from_base,to_base) //进制转换
FLOOR (number2 ) //向下取整
FORMAT (number,decimal_places ) //保留小数位数
HEX (DecimalNumber ) //转十六进制
注:HEX()中可传入字符串,则返回其ASC-11码,如HEX(‘DEF’)返回4142143
也可以传入十进制整数,返回其十六进制编码,如HEX(25)返回19
LEAST (number , number2 [,..]) //求最小值
MOD (numerator ,denominator ) //求余
POWER (number ,power ) //求指数
RAND([seed]) //随机数
ROUND (number [,decimals ]) //四舍五入,decimals为小数位数]

注:返回类型并非均为整数,如:
(1)默认变为整形值

  1. mysql> select round(1.23);
  2. +————-+
  3. | round(1.23) |
  4. +————-+
  5. |           1 |
  6. +————-+
  7. 1 row in set (0.00 sec)
  8. mysql> select round(1.56);
  9. +————-+
  10. | round(1.56) |
  11. +————-+
  12. |           2 |
  13. +————-+
  14. 1 row in set (0.00 sec)

(2)可以设定小数位数,返回浮点型数据

  1. mysql> select round(1.567,2);
  2. +—————-+
  3. | round(1.567,2) |
  4. +—————-+
  5. |           1.57 |
  6. +—————-+
  7. 1 row in set (0.00 sec)

SIGN (number2 ) //

 

(3).日期时间类

ADDTIME (date2 ,time_interval ) //将time_interval加到date2
CONVERT_TZ (datetime2 ,fromTZ ,toTZ ) //转换时区
CURRENT_DATE ( ) //当前日期
CURRENT_TIME ( ) //当前时间
CURRENT_TIMESTAMP ( ) //当前时间戳
DATE (datetime ) //返回datetime的日期部分
DATE_ADD (date2 , INTERVAL d_value d_type ) //在date2中加上日期或时间
DATE_FORMAT (datetime ,FormatCodes ) //使用formatcodes格式显示datetime
DATE_SUB (date2 , INTERVAL d_value d_type ) //在date2上减去一个时间
DATEDIFF (date1 ,date2 ) //两个日期差
DAY (date ) //返回日期的天
DAYNAME (date ) //英文星期
DAYOFWEEK (date ) //星期(1-7) ,1为星期天
DAYOFYEAR (date ) //一年中的第几天
EXTRACT (interval_name FROM date ) //从date中提取日期的指定部分
MAKEDATE (year ,day ) //给出年及年中的第几天,生成日期串
MAKETIME (hour ,minute ,second ) //生成时间串
MONTHNAME (date ) //英文月份名
NOW ( ) //当前时间
SEC_TO_TIME (seconds ) //秒数转成时间
STR_TO_DATE (string ,format ) //字串转成时间,以format格式显示
TIMEDIFF (datetime1 ,datetime2 ) //两个时间差
TIME_TO_SEC (time ) //时间转秒数]
WEEK (date_time [,start_of_week ]) //第几周
YEAR (datetime ) //年份
DAYOFMONTH(datetime) //月的第几天
HOUR(datetime) //小时
LAST_DAY(date) //date的月的最后日期
MICROSECOND(datetime) //微秒
MONTH(datetime) //月
MINUTE(datetime) //分返回符号,正负或0
SQRT(number2) //开平方

键盘鼠标模拟全知道 - 北极星 - North Star - 博客园

mikel阅读(1112)

来源: 键盘鼠标模拟全知道 – 北极星 – North Star – 博客园

本文目录如下

一、基于windows 消息机制的鼠标键盘模拟

 (一)、应用程序级模拟

 (二)、系统级模拟

        1用API函数keybd_event 模拟键盘事件

        2、 SendInput函数模拟全局键盘鼠标事件

        3、用全局钩子模拟键盘消息

二、驱动级模拟

*******************************************************************************************

一、基于windows 消息机制的鼠标键盘模拟

我们怎样才能用Delphi来写一个程序,用来代替人们按键的方法呢?那就让我们来先了解一下windows中响应键盘事件的机制。

当用户按下键盘上的一个键时,键盘内的芯片会检测到这个动作,并把这个信号传送到计算机。如何区别是哪一个键被按下了呢?键盘上的所有按键都有一个编 码,称作键盘扫描码。当你按下一个键时,这个键的扫描码就被传给系统。扫描码是跟具体的硬件相关的,同一个键,在不同键盘上的扫描码有可能不同。键盘控制 器就是将这个扫描码传给计算机,然后交给键盘驱动程序。键盘驱动程序会完成相关的工作,并把这个扫描码转换为键盘虚拟码。什么是虚拟码呢?因为扫描码与硬 件相关,不具有通用性,为了统一键盘上所有键的编码,于是就提出了虚拟码概念。无论什么键盘,同一个按键的虚拟码总是相同的,这样程序就可以识别了。简单 点说,虚拟码就是我们经常可以看到的像VK_A,VK_B这样的常数,比如键A的虚拟码是65,写成16进制就是&H41,注意,人们经常用16 进制来表示虚拟码。当键盘驱动程序把扫描码转换为虚拟码后,会把这个键盘操作的扫描码和虚拟码还有其它信息一起传递给操作系统。然后操作系统则会把这些信 息封装在一个消息中,并把这个键盘消息插入到消息列队。最后,要是不出意外的话,这个键盘消息最终会被送到当前的活动窗口那里,活动窗口所在的应用程序接 收到这个消息后,就知道键盘上哪个键被按下,也就可以决定该作出什么响应给用户了。

这个过程可以简单的如下表示:

 

用户按下键盘上的一个键 >>>>> 键盘控制器就把这个键的扫描码传给计算机,然后交给键盘驱动程序  >>>>> 键盘驱动程序会把这个扫描码转换为键盘虚拟码(VK_A,VK_B这样的常数,比如键A的虚拟码是65,写成16进制就是&H41)传给操作系统  >>>>>  操操作系统则会把这些信息封装在一个消息中,并把这个键盘消息插入到消息列队  >>>>> 键盘消息被发送到当前活动窗口

 

明白了这个过程,我们就可以编程实现在其中的某个环节来模拟键盘操作了。在Delphi中,有多种方法可以实现键盘模拟,我们就介绍几种比较典型的。

(一)、应用程序级模拟(只针对某个程序,我称之为局部模拟)

windows提供了几个这样的API函数可以实现直接向目标程序发送消息的功能,常用的有SendMessage和PostMessage,它们 的区别是PostMessage函数直接把消息仍给目标程序就不管了,而SendMessage把消息发出去后,还要等待目标程序返回些什么东西才好。这 里要注意的是,模拟键盘消息一定要用PostMessage函数才好,用SendMessage是不正确的(因为模拟键盘消息是不需要返回值的,不然目标 程序会没反应),切记切记!

PostMessage函数的delphi声明如下:

PostMessage(

hWnd: HWND;                      {目标程序上某个控件的句柄}

Msg: UINT;                         {消息的类型}

wParam: WPARAM;                  {32位指定附加的消息特定的信息}

lParam: LPARAM                    {32位指定附加的消息特定的信息}

): BOOL;

参数hwnd 是你要发送消息的目标程序上某个控件的句柄,参数Msg 是消息的类型,表示你要发送什么样的消息,最后wParam 和lParam这两个参数是随消息附加的数据,具体内容要由消息决定。

 

 

再来看看Msg 这个参数,要模拟按键就靠这个了。

键盘消息常用的有如下几个:

WM_KEYDOWN      表示一个普通键被按下

WM_KEYUP          表示一个普通键被释放

 

WM_SYSKEYDOWN   表示一个系统键被按下,比如Alt键

WM_SYSKEYUP       表示一个系统键被释放,比如Alt键

 

如果你确定要发送以上几个键盘消息,那么再来看看如何确定键盘消息中的wParam 和lParam 这两个参数。在一个键盘消息中,wParam 参数的含义较简单,它表示你要发送的键盘事件的按键虚拟码,比如你要对目标程序模拟按下A键,那么wParam 参数的值就设为VK_A ,至于lParam 这个参数就比较复杂了,因为它包含了多个信息,一般可以把它设为0。即

 

PostMessage (MyHwnd, WM_KEYDOWN, key, 0);

 

但是如果你想要你的模拟更真实一些,那么建议你还是设置一下这个参数。那么我们就详细了解一下lParam 吧。

lParam 是一个32 bit的参数,它在内存中占4个字节,写成二进制就是

00000000 00000000 00000000 00000000

一共是32位,我们从右向左数,假设最右边那位为第0位(注意是从0而不是从1开始计数),最左边的就是第31位。那么该参数的

 

0-15位表示键的发送次数等扩展信息,

16-23位为按键的扫描码,

24-31位表示是按下键还是释放键。

 

大家一般习惯写成16进制的,那么就应该是

&H00 00 00 00 ,

第0-15位一般为&H0001,如果是按下键,那么24-31位为&H00,释放键则为&HC0,

那么16-23位的扫描码怎么会得呢?这需要用到一个API函数MapVirtualKey,这个函数可以将虚拟码转换为扫描码,或将扫描码转换为虚拟码,还可以把虚拟码转换为对应字符的ASCII码。它的delphi 声明如下:

 

MapVirtualKey(

uCode: UINT;                        {key code, scan code or virtual key}

uMapType: UINT                     {flags for translation mode}

): UINT;                             {returns translated key code}

 

参数uCode 表示待转换的码,参数uMapType 表示从什么转换为什么,如果是虚拟码转扫描码,则uMapType 设置为0,如果是虚拟扫描码转虚拟码,则wMapType 设置为1,如果是虚拟码转ASCII码,则uMapType 设置为2.相信有了这些,我们就可以构造键盘事件的lParam参数了。

 

下面给出一个构造lParam参数的函数:

 

function VKB_param(VirtualKey:Integer;flag:Integer):Integer; //函数名

var

s,Firstbyte,Secondbyte:String;

S_code:Integer;

Begin

if flag=1 then  //按下键

begin

Firstbyte :=’00’

end

else         //弹起键

begin

Firstbyte :=’C0′

end;

S_code:= MapVirtualKey(VirtualKey, 0);

Secondbyte:=’00’+inttostr(s_code);

Secondbyte:=copy(Secondbyte,Length(Secondbyte)-1,2);

s:=’$’+Firstbyte + Secondbyte + ‘0001’;

Result:=strtoint(s);

End;

 

使用按键的方法:

说明:key为键值,如1键[不是数字键的1]的值是$31,flag传递的是按键状态,1是按下,0是弹起。

 

lparam := VKB_param(key, 1);      {按下键}

PostMessage (MyHwnd, WM_KEYDOWN, key, lParam);

lParam := VKB_param(key, 0);      {松开键}

PostMessage (MyHwnd, WM_KEYUP, key, lParam);

 

var

hwnd, lparam:Cardinal;

begin

hwnd:=FindWindowEx(FindWindow(nil,’无标题 – 记事本’),0,’edit’,nil);

lparam := VKB_param(97, 1);      {按下键}

PostMessage (hwnd,WM_KEYDOWN,vk_F3,lparam) ; //按下F3键

//PostMessage (hwnd,WM_CHAR,97,lparam);        //输入字符A (edit控件接收字符)

lParam := VKB_param(key, 0);      {松开键}

PostMessage (hwnd,WM_KEYUP,vk_F3,lparam);   //释放F3键

end;

 

模拟鼠标点击

Var

P1:Tpoint;

Lparam:integer;

begin

GetCursorPos(P1); // 获取屏幕坐标

   P1.X:= P1.X+100;

   P1.Y:=P1.Y+100;

lparam:=p1.X+ p1.Y shl 16;

sendmessage(h,messages.WM_LBUTTONDOWN ,0,lparam);// 按下鼠标左键

sendmessage(h,messages.WM_LBUTTONUP  ,0, lparam); //抬起鼠标左键

End;

 

(二)、系统级模拟(针对所有程序的窗口,我称之为全局模拟)

 

模拟全局键盘消息常见的可以有以下一些方法:

 

1用API函数keybd_event,这个函数可以用来模拟一个键盘事件

 

keybd_event(

bVk: Byte;                       {virtual-key code}

bScan: Byte;                      {scan-code}

dwFlags: DWORD;                {option flags}

dwExtraInfo: DWORD             {additional information about the key}

);                               {this procedure does not return a value}

 

keybd_event(VK_A, 0, 0, 0)   ‘按下A键

keybd_event (VK_A, 0, KEYEVENTF_KEYUP, 0)    ‘释放A键

 

注意有时候按键的速度不要太快,否则会出问题,可以用API函数Sleep来进行延时,delphi声明如下:

Sleep(

dwMilliseconds: DWORD                {specifies the number of milliseconds to pause}

);

 

参数dwMilliseconds表示延时的时间,以毫秒为单位。

那么如果要模拟按下功能键怎么做呢?比如要按下Ctrl+C实现拷贝这个功能,可以这样:

keybd_event (VK_Ctrl, 0, 0, 0 );   //按下Ctrl键

keybd_event (VK_C, 0, 0, 0);       //按下C键

Sleep(500 );          //延时500毫秒

keybd_event (VK_C, 0, KEYEVENTF_KEYUP, 0 );   //释放C键

keybd_event (VK_Ctrl, 0, KEYEVENTF_KEYUP, 0 );   //释放Ctrl键

 

好了,现在你可以试试是不是可以骗过目标程序了,这个函数对大部分的窗口程序都有效,可是仍然有一部分游戏对它产生的键盘事件熟视无睹,这时候,你 就要用上bScan这个参数了。一般的,bScan都传0,但是如果目标程序是一些DirectX游戏,那么你就需要正确使用这个参数传入扫描码,用了它 可以产生正确的硬件事件消息,以被游戏识别。这样的话,就可以写成这样:

keybd_event (VK_A, MapVirtualKey(VK_A, 0), 0, 0);   //按下A键

keybd_event (VK_A, MapVirtualKey(VK_A, 0), KEYEVENTF_KEYUP, 0 );   //释放A键

以上就是用keybd_event函数来模拟键盘事件。

模拟按下鼠标左键

mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);//鼠标左键按下mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);//鼠标左键弹起

 

2、 SendInput函数也可以模拟全局键盘鼠标事件

 

SendInput可以直接把一条消息插入到消息队列中,算是比较底层的了。

SendInput的参数其实很简单,在Windows.pas就有函数的声明如下:

function SendInput (

cInputs: UINT;                   pInputs中记录数组的元素数目

var pInputs: TInput;               TInput类型记录数组的第1个元素

cbSize: Integer                   定义TInput的大小,一般为SizeOf(TInput)

): UINT; stdcall;

 

cInputs:定义pInputs中记录数组的元素数目。pInputs:TInput类型记录数组的第1个元素。每个元素代表插人到系统消 息队列的键盘或鼠标事件。cbSize:定义TInput的大小,一般为SizeOf(TInput)。函数返回成功插入系统消息队列中事件的数目,失败 返回0。调用SendInput关键的就是要搞清楚它的几个记录结构的意思,在Windows.pas中对TInput的声明如下:

 

tagINPUT = packed record

Itype: DWORD;

case Integer of

0: (mi: TMouseInput);

1: (ki: TKeybdInput);

2: (hi: THardwareInput);

end;

TInput = tagINPUT;

 

其中mi、ki、hi是3个共用型的记录结构,Itype指出记录结构中所使用的类型,它有3个值。INPUT_MOUSE:表示使用mi记录结构,忽略ki和hi;INPUT_KEYBOARD:表示使用ki记录结构,忽略mi和hi。

 

(1)、键盘模拟

TKeybdInput记录结构的声明如下:

 

tagKEYBDINPUT = packed record

wVk: WORD;                   是将要操作的按键的虚键码

wScan: WORD;                  是安全码,一般不用

dwFlags: DWORD;              指定键盘所进行的操作,为0时表示按下某键,KEYEVENTF_KEYUP表示放开某键

time: DWORD;

dwExtraInfo: DWORD;  是扩展信息,可以使用API函数GetMessageExtraInfo的返回值

end;

TKeybdInput = tagKEYBDINPUT;

 

其中wVk是将要操作的按键的虚键码。wScan是安全码,一般不用。dwFlags指定键盘所进行的操作,为0时表示按下某 键,KEYEVENTF_KEYUP表示放开某键。time是时间戳,可以使用API函数GetTickCount的返回值。dwExtraInfo是扩 展信息,可以使用API函数GetMessageExtraInfo的返回值。例如击键“A”的程序如下:

 

procedure KeyPressA;

var

Inputs : array [0..1] of TInput;

begin

Inputs[0].Itype:=INPUT_KEYBOARD;

with Inputs[0].ki do

begin

wVk:=VK_A;

wScan:=0;

dwFlags:=0;

time:=GetTickCount;

dwExtraInfo:=GetMessageExtraInfo;

end;

Inputs[1].Itype:=INPUT_KEYBOARD;

with Inputs[1].ki do

begin

wVk:=VK_A;

wScan:=0;

dwFlags:=KEYEVENTF_KEYUP;

time:=GetTickCount;

dwExtraInfo:=GetMessageExtraInfo;

end;

SendInput(2,Inputs[0],SizeOf(TInput));

end;

 

注意:在Windows.pas单元中并没有字母和数字的虚键码的声明,在我写的SIMouseKeyboard.pas单元文件中对所有的虚键码进行了重新声明,包含了字母、数字和标点符号。

 

(2)、鼠标模拟

 

TMouseInput记录结构的声明如下:

 

tagMOUSEINPUT = packed record

dx: Longint;

dy: Longint;

mouseData: DWORD;

dwFlags: DWORD;

time: DWORD;

dwExtraInfo: DWORD;

end;

TMouseInput = tagMOUSEINPUT;

 

其中dx、dy是鼠标移动时的坐标差(不是象素单位),在鼠标移动时有效。mouseData是鼠标滚轮滚动值,在滚动鼠标滚轮时有效。当 mouseData小于0时向下滚动,当mouseData大于0时向上滚动,mouseData的绝对值一般设为120。dwFlags指定鼠标所进行 的操作,例如,MOUSEEVENTF_MOVE表示移动鼠标,MOUSEEVENTF_LEFTDOWN表示按下鼠标左 键,MOUSEEVENTF_LEFTUP表示放开鼠标左键。time是时间戳,可以使用API函数GetTickCount的返回值。 dwExtraInfo是扩展信息,可以使用API函数GetMessageExtraInfo的返回值。例如单击鼠标左键的程序如下:

 

procedure MouseClick;

var

Inputs : array [0..1] of TInput;

begin

Inputs[0].Itype:=INPUT_MOUSE;

with Inputs[0].mi do

begin

dx:=0;

dy:=0;

mouseData:=0;

dwFlags:=MOUSEEVENTF_LEFTDOWN;

time:=GetTickCount;

dwExtraInfo:=GetMessageExtraInfo;

end;

Inputs[1].Itype:=INPUT_MOUSE;

with Inputs[1].mi do

begin

dx:=0;

dy:=0;

mouseData:=0;

dwFlags:=MOUSEEVENTF_LEFTUP;

time:=GetTickCount;

dwExtraInfo:=GetMessageExtraInfo;

end;

SendInput(2,Inputs[0],SizeOf(TInput));

end;

 

鼠标的移动总是很麻烦,上面的dx、dy不是以象素为单位的,而是以鼠标设备移动量为单位的,它们之间的比值受鼠标移动速度设置的影响。具体的 解决方法我已经在《Delphi下利用WinIo模拟鼠标键盘详解》一文中进行了讨论,这里不再重复。dwFlags可以设置一个 MOUSEEVENTF_ABSOLUTE标志,这使得可以用另外一种方法移动鼠标。当dwFlags设置了MOUSEEVENTF_ABSOLUTE标 志,dx、dy为屏幕坐标值,表示将鼠标移动到dx,dy的位置。但是这个坐标值也不是以象素为单位的。这个值的范围是0到65535($FFFF),当 dx等于0、dy等于0时表示屏幕的最左上角,当dx等于65535、dy等于65535时表示屏幕的最右下角,相当于将屏幕的宽和高分别65536等 分。API函数GetSystemMetrics(SM_CXSCREEN)可以返回屏幕的宽度,函数 GetSystemMetrics(SM_CYSCREEN)可以返回屏幕的高度,利用屏幕的宽度和高度就可以将象素坐标换算成相应的dx、dy。注意: 这种换算最多会出现1象素的误差。例如:将鼠标指针移动到屏幕150,120坐标处的程序如下:

 

procedure MouseMove;

var

Input : TInput;

begin

Input.Itype:=INPUT_MOUSE;

with Input.mi do

begin

dx:=($FFFF div (GetSystemMetrics(SM_CXSCREEN)-1)) * 150;

dy:=($FFFF div (GetSystemMetrics(SM_CYSCREEN)-1)) * 120;

mouseData:=0;

dwFlags:=MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE;

time:=GetTickCount;

dwExtraInfo:=GetMessageExtraInfo;

end;

SendInput(1,Input,SizeOf(TInput));

end;

 

(3)、SendInput与WInIo的对比

 

在《Delphi下利用WinIo模拟鼠标键盘详解》一文中我已经说了WinIo的很多缺点,SendInput几乎没有这些缺点。 SendInput的模拟要比WinIo简单的多。事件是被直接插入到系统消息队列的,所以它的速度比WinIo要快。系统也会保证数据的完整性,不会出 现数据包混乱的情况。利用“绝对移动”可以将鼠标指针移动到准确的位置,同鼠标的配置隔离不会出现兼容性的问题。SendInput的缺点也是最要命的, 它会被一些程序屏蔽。所以说在SendInput与WInIo都可以使用的情况下优先考虑SendInput。另外SendInput与WInIo可以接 合使用,一些程序对鼠标左键单击敏感,可以使用WinIo模拟鼠标左键单击,其它操作由SendInput模拟。

 

3、用全局钩子也可以模拟键盘消息。如果你对windows中消息钩子的用 法已经有所了解,那么你可以通过设置一个全局HOOK来模拟键盘消息,比如,你可以用WH_JOURNALPLAYBACK这个钩子来模拟按键。 WH_JOURNALPLAYBACK是一个系统级的全局钩子,它和WH_JOURNALRECORD的功能是相对的,常用它们来记录并回放键盘鼠标操 作。WH_JOURNALRECORD钩子用来将键盘鼠标的操作忠实地记录下来,记录下来的信息可以保存到文件中,而 WH_JOURNALPLAYBACK则可以重现这些操作。当然亦可以单独使用WH_JOURNALPLAYBACK来模拟键盘操作。

 

SetWindowsHookEx函数,它可以用来安装消息钩子:

SetWindowsHookEx(

idHook: Integer;                                {hook type flag}

lpfn: TFNHookProc;                        {a pointer to the hook function}

hmod: HINST;                                {a handle to the module containing the hook function}

dwThreadId: DWORD                        {the identifier of the associated thread}

): HHOOK;

先安装WH_JOURNALPLAYBACK这个钩子,然后你需要自己写一个钩子函数,在系统调用它时,把你要模拟的事件传递给钩子参数 lParam所指向的EVENTMSG区域,就可以达到模拟按键的效果。不过用这个钩子模拟键盘事件有一个副作用,就是它会锁定真实的鼠标键盘,不过如果 你就是想在模拟的时候不会受真实键盘操作的干扰,那么用用它倒是个不错的主意。

在不需要监视系统消息时需要调用提供UnHookWindowsHookEx来解除对消息的监视。       下面来建立程序,在Delphi中建立一个工程,在Form1上添加3个按钮用于程序操作。另外再添加一个按钮控件和一个Edit控件用于验证操 作。

下面是Form1的全部代码

 

unit Unit1;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls;

 

type

TForm1 = class(TForm)

Button1: TButton;

Button2: TButton;

Button3: TButton;

Edit1: TEdit;

Button4: TButton;

procedure FormCreate(Sender: TObject);

procedure Button1Click(Sender: TObject);

procedure Button2Click(Sender: TObject);

procedure Button3Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

 

var

Form1: TForm1;

 

EventArr:array[0..1000]of EVENTMSG;

EventLog:Integer;

PlayLog:Integer;

hHook,hPlay:Integer;

recOK:Integer;

canPlay:Integer;

bDelay:Bool;

implementation

 

{$R *.DFM}

Function PlayProc(iCode:Integer;wParam:wParam;lParam:lParam):LRESULT;stdcall;

begin

canPlay:=1;

Result:=0;

 

if iCode < 0 then     // 必须将消息传递到消息链的下一个接受单元

 

Result := CallNextHookEx(hPlay,iCode,wParam,lParam)

else if iCode = HC_SYSMODALON then

canPlay:=0

else if iCode = HC_SYSMODALOFF then

canPlay:=1

else if ((canPlay =1 )and(iCode=HC_GETNEXT)) then begin

if bDelay then begin

bDelay:=False;

Result:=50;

end;

pEventMSG(lParam)^:=EventArr[PlayLog];

end

else if ((canPlay = 1)and(iCode = HC_SKIP))then begin

bDelay := True;

PlayLog:=PlayLog+1;

end;

if PlayLog>=EventLog then begin

UNHookWindowsHookEx(hPlay);

end;

end;

 

function HookProc(iCode:Integer;wParam:wParam;lParam:lParam):LRESULT;stdcall;

begin

recOK:=1;

Result:=0;

 

if iCode < 0 then

Result := CallNextHookEx(hHook,iCode,wParam,lParam)

else if iCode = HC_SYSMODALON then

recOK:=0

else if iCode = HC_SYSMODALOFF then

recOK:=1

else if ((recOK>0) and (iCode = HC_ACTION)) then begin

EventArr[EventLog]:=pEventMSG(lParam)^;

EventLog:=EventLog+1;

 

if EventLog>=1000 then begin

UnHookWindowsHookEx(hHook);

end;

end;

end;

 

procedure TForm1.FormCreate(Sender: TObject);

begin

Button1.Caption:= ‘纪录’;

Button2.Caption:=  ‘停止’;

Button3.Caption:= ‘回放’;

Button4.Caption:=  ‘范例’;

Button2.Enabled:=False;

Button3.Enabled:=False;

end;

 

procedure TForm1.Button1Click(Sender: TObject);

begin

EventLog:=0;    //   建立键盘鼠标操作消息纪录链

 

hHook:=SetwindowsHookEx(WH_JOURNALRECORD,HookProc,HInstance,0);

Button2.Enabled:=True;

Button1.Enabled:=False;

end;

 

procedure TForm1.Button2Click(Sender: TObject);

begin

UnHookWindowsHookEx(hHook);

hHook:=0;

 

Button1.Enabled:=True;

Button2.Enabled:=False;

Button3.Enabled:=True;

end;

 

procedure TForm1.Button3Click(Sender: TObject);

begin

PlayLog:=0;  //建立键盘鼠标操作消息纪录回放链

 

hPlay:=SetwindowsHookEx(WH_JOURNALPLAYBACK,PlayProc,

HInstance,0);

 

Button3.Enabled:=False;

end;

end.

 

二、驱动级模拟 (绕过了windows消息)

有一些使用DirectX接口的游戏程序,它们在读取键盘操作时绕过了windows的消息机制,而使用DirectInput.这是因为有些游戏 对实时性控制的要求比较高,比如赛车游戏,要求以最快速度响应键盘输入。而windows消息由于是队列形式的,消息在传递时会有不少延迟,有时1秒钟也 就传递十几条消息,这个速度达不到游戏的要求。而DirectInput则绕过了windows消息,直接与键盘驱动程序打交道,效率当然提高了不少。因 此也就造成,对这样的程序无论用PostMessage或者是keybd_event都不会有反应,因为这些函数都在较高层。对于这样的程序,只好用直接 读写键盘端口的方法来模拟硬件事件了。要用这个方法来模拟键盘,需要先了解一下键盘编程的相关知识。

 

在DOS时代,当用户按下或者放开一个键时,就会产生一个键盘中断(如果键盘中断是允许的),这样程序会跳转到BIOS中的键盘中断处理程序去执 行。打开windows的设备管理器,可以查看到键盘控制器由两个端口控制。其中&H60是数据端口,可以读出键盘数据,而&H64是控 制端口,用来发出控制信号。也就是,从&H60号端口可以读此键盘的按键信息,当从这个端口读取一个字节,该字节的低7位就是按键的扫描码,而高 1位则表示是按下键还是释放键。当按下键时,最高位为0,称为通码,当释放键时,最高位为1,称为断码。既然从这个端口读数据可以获得按键信息,那么向这 个端口写入数据就可以模拟按键了!用过QbASIC4.5的朋友可能知道,QB中有个OUT命令可以向指定端口写入数据,而INP函数可以读取指定端口的 数据。那我们先看看如果用QB该怎么写代码:

假如你想模拟按下一个键,这个键的扫描码为&H50,那就这样

OUT &H64,&HD2    ‘把数据&HD2发送到&H64端口。这是一个KBC指令,表示将要向键盘写入数据

OUT &H60,&H50    ‘把扫描码&H50发送到&H60端口,表示模拟按下扫描码为&H50的这个键

那么要释放这个键呢?像这样,发送该键的断码:

OUT &H64,&HD2    ‘把数据&HD2发送到&H64端口。这是一个KBC指令,表示将要向键盘写入数据

OUT &H60,(&H50 or &H80)    ‘把扫描码&H50与数据&H80进行或运算,可以把它的高位置1,得到断码,表示释放这个键

 

好了,现在的问题就是在delphi中如何向端口写入数据了。因为在windows中,普通应用程序是无权操作端口的,于是我们就需要一个驱动程序 来帮助我们实现。在这里我们可以使用一个组件WINIO来完成读写端口操作。什么是WINIO?WINIO是一个全免费的、无需注册的、含源程序的 WINDOWS2000端口操作驱动程序组件(可以到http://www.internals.com/上去下载)。它不仅可以操作端口,还可以操作内 存;不仅能在VB下用,还可以在DELPHI、VC等其它环境下使用,性能特别优异。下载该组件,解压缩后可以看到几个文件夹,其中Release文件夹 下的3个文件就是我们需要的,这3个文件是WinIo.sys(用于win xp下的驱动程序),WINIO.VXD(用于win 98下的驱动程序),WinIo.dll(封装函数的动态链接库),我们只需要调用WinIo.dll中的函数,然后WinIo.dll就会安装并调用驱 动程序来完成相应的功能。值得一提的是这个组件完全是绿色的,无需安装,你只需要把这3个文件复制到与你的程序相同的文件夹下就可以使用了。用法很简单, 先用里面的InitializeWinIo函数安装驱动程序,然后就可以用GetPortVal来读取端口或者用SetPortVal来写入端口了。好, 让我们来做一个驱动级的键盘模拟吧。先把winio的3个文件拷贝到你的程序的文件夹下。

下面给出使用WINIO模拟按键的单元和使用方法:

{****************************************************************************}

unit MNwinio;

 

interface

 

const

KBC_KEY_CMD = $64; //键盘命令端口

KBC_KEY_DATA = $60; //键盘数据端口

 

implementation

 

function InitializeWinIo: Boolean; stdcall; external ‘WinIo.dll’ name ‘InitializeWinIo’;

{

本函数初始化WioIO函数库。

必须在调用所有其它功能函数之前调用本函数。

如果函数调用成功,返回值为非零值。

如果调用失败,则返回值为0。

 

procedure TForm1.FormActivate(Sender: TObject); //通常在程序启动时调用

begin

if InitializeWinIo = False then

begin

Messagebox(handle, ‘初始化失败!’, ‘提示’, MB_OK + MB_IconError)

end;

end;

}

 

function InstallWinIoDriver(pszWinIoDriverPath: PString; IsDemandLoaded: boolean

= false): Boolean; stdcall; external ‘WinIo.dll’ name ‘InstallWinIoDriver’;

 

function RemoveWinIoDriver: Boolean; stdcall; external ‘WinIo.dll’ name

‘RemoveWinIoDriver’;

 

function GetPortVal(PortAddr: Word; PortVal: PDWord; bSize: Byte): Boolean;

stdcall; external ‘WinIo.dll’ name ‘GetPortVal’;

 

function SetPortVal(PortAddr: Word; PortVal: DWord; bSize: Byte): Boolean;

stdcall; external ‘WinIo.dll’ name ‘SetPortVal’;

 

function GetPhysLong(PhysAddr: PByte; PhysVal: PDWord): Boolean; stdcall;

external ‘WinIo.dll’ name ‘GetPhysLong’;

 

function SetPhysLong(PhysAddr: PByte; PhysVal: DWord): Boolean; stdcall; external

‘WinIo.dll’ name ‘SetPhysLong’;

 

function MapPhysToLin(PhysAddr: PByte; PhysSize: DWord; PhysMemHandle: PHandle):

PByte; stdcall; external ‘WinIo.dll’ name ‘MapPhysToLin’;

 

function UnMapPhysicalMemory(PhysMemHandle: THandle; LinAddr: PByte): Boolean;

stdcall; external ‘WinIo.dll’ name ‘UnmapPhysicalMemory’;

 

procedure ShutdownWinIo; stdcall; external ‘WinIo.dll’ name ‘ShutdownWinIo’;

{ 本函数在内存中清除WinIO库

本函数必须在中止应用函数之前或者不再需要WinIO库时调用

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); //通常在程序关闭时调用

begin

ShutdownWinIo;

end;

}

{**********以上为WINIO.dll中API函数的调用***************}

 

procedure KBCWait4IBE; //等待键盘缓冲区为空

var

dwVal: DWord;

begin

repeat

GetPortVal($64, @dwVal, 1);

{这句表示从&H64端口读取一个字节并把读出的数据放到变量dwVal中. GetPortVal函数的用法是 GetPortVal (端口号,存放读出数据的变量地址,读入的长度}

until (dwVal and $2) = 0;

 

{上面的是一个根据KBC规范写的过程,它的作用是在向键盘端口写入数据前等待一段时间,后面将会用到。}

end;

 

procedure MyKeyDown(vKeyCoad: Integer);    // 这个用来模拟按下键,参数vKeyCoad传入按键的虚拟码

var

btScancode: DWord;

begin

btScancode := MapVirtualKey(vKeyCoad, 0);

KBCWait4IBE;                           // 发送数据前应该先等待键盘缓冲区为空

SetPortVal($64, $D2, 1);               // 发送键盘写入命令

{SetPortVal函数用于向端口写入数据,它的用法是:SetPortVal(端口号,欲写入的数据,写入数据的长度)}

KBCWait4IBE;

SetPortVal($60, btScancode, 1);   //写入按键信息,按下键

end;

procedure MyKeyUp(vKeyCoad: Integer);     //这个用来模拟释放键,参数vKeyCoad传入按键的虚拟码

var

btScancode: DWord;

begin

btScancode := MapVirtualKey(vKeyCoad, 0);

KBCWait4IBE;

SetPortVal($64, $D2, 1);

KBCWait4IBE;

SetPortVal($64, (btScancode or $80), 1);

end;

end.

 

{

使用方法:

如模拟按键 1

MyKeyDown($31);

Sleep(50);

MyKeyUp($31);

}

使用delphi+intraweb进行微信开发5—准备实现微信API,先从获取AccessToken开始 - Delphi力量 - 博客园

mikel阅读(1803)

来源: 使用delphi+intraweb进行微信开发5—准备实现微信API,先从获取AccessToken开始 – Delphi力量 – 博客园

在前4讲中我们已经使iw开发的应用成功和微信进行了对接,再接下来的章节中我们开始逐一尝试和实现微信的各个API,开始前先来点准备工作
  1. 首先需要明确的是,微信的API都是通过https调用实现的,分为post方法调用和get方法调用。不需要上传数据的采用get方法(例如获取AccessToken),而需要向微信服务器提交数据的采用post方法(例如创建菜单)。
  2. 微信方法调用均需传递AccessToken(URL参数方式),这个AccessToken不是我们微信接入时使用的Token,这个AccessToken专门用于微信API调用,AccessToken有过期时间,而且每天有请求次数限制,据说是为了防止不良的程序调用导致微信服务器出现异常。因此在这种情况下则必须在获取AccessToken后进行保存,在即将过期前再重新获取。
好吧,让我们开始:首先定义post和get方法。这里我们采用Indy实现,不需要再安装什么第三方组件了,也没有大量并发的要求(咱们这是客户端程序),简单易用最重要。

 

/// <summary>
/// 向指定URL发起Get请求
/// </summary>
/// <param name=”http”>TIdHTTP</param>
/// <param name=”URL”>指定URL</param>
/// <param name=”Max”>Get请求失败最大重试次数</param>
/// <returns>返回腾讯服务器响应(string类型的json格式数据)</returns>
function GetMethod(http: TIdHTTP; URL: String; Max: Integer): String;

var
RespDataTStringStream;
begin
RespData := TStringStream.Create(TEncoding.UTF8);
try
try
http.Get(URLRespData);
http.Request.Referer := URL;
Result := RespData.DataString;
except
Dec(Max);
if Max then
begin
Result := ;
exit;
end;
Result := GetMethod(httpURLMax);
end;
finally
FreeAndNil(RespData);
end;
end;

/// <summary>
/// 向指定URL提交数据(Post)
/// </summary>
/// <param name=”http”>TIdHTTP</param>
/// <param name=”URL”>指定URL</param>
/// <param name=”Data”>要提交的数据(UTF8String)</param>
/// <param name=”Max”>Post请求失败最大重试次数</param>
/// <returns>返回腾讯服务器响应(string类型的json格式数据)</returns>
function PostMethod(http: TIdHTTP; URL: String; Data: UTF8String;
Max: Integer): String;

var
PostDataRespDataTStringStream;
begin
RespData := TStringStream.Create();
PostData := TStringStream.Create(Data);
try
try
if http nil then
exit;
http.Post(URLPostDataRespData);
Result := RespData.DataString;
http.Request.Referer := URL;
except
Dec(Max);
if Max then
begin
Result := ;
exit;
end;
Result := PostMethod(httpURLDataMax);
end;
finally
http.Disconnect;
FreeAndNil(RespData);
FreeAndNil(PostData);
end;
end;

有了上面两个方法我们就可以开始测试微信API了。

♥ 不过你有没有注意到,微信请求是https, 不是http啊,所以似乎还需要让Indy支持ssl传输才行啊,这当然没有问题,上Indy官网下载SSL支持DLL即可,分为64位和32位版本不要 搞错,下载后和编译好的IW程序放置在同一目录下即可(说实在的下载网站我给忘了,大家可以百度一下,如果找不到给我留言,我把我下载的发出来)。

 

接下来研究下如何获取这个AccessToken

 

由于AccessToken在API调用中都需要使用,因此先来获取AccessToken吧,关于AccessToken的解释请看微信文档:http://mp.weixin.qq.com/wiki/14/9f9c82c1af308e3b14ba9b973f99a8ba.html

使用的微信命令URL是:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

采用Get方法。

如你所见:

1、URL调用需传递【APPID】和【APPSECRET】两个参数,返回结果为Json格式字符串。

2、调用成功的情况下,微信会返回下述JSON数据包给公众号:

{"access_token":"ACCESS_TOKEN","expires_in":7200}
参数 说明
access_token 获取到的凭证
expires_in 凭证有效时间,单位:秒

3、错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):

{"errcode":40013,"errmsg":"invalid appid"}

♥ 好吧,好像还得找个Json解析的组件,嗯嗯,够麻烦的,推荐使用第三方组件“SuperObject”进行Json格式解析,不过你要非得自行进行Json字符串编解码也行,嗯,可能会累点,相信我,最好找个封装完善的Json组件,工欲善其事必先利其器,否则创建菜单什么的时候有你受的。

 

嗯,Json结果解析判断什么的我就不说了,再说说AccessToken的缓存问题。

 

简单说:“为了保密appsecrect,第三方需要一个access_token获取和刷新的中控服务器。而其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则会造成access_token覆盖而影响业务;”这个是微信文档原话,所以让我们看看怎么缓存这个东东。

先声明一个TAccessToken记录,然后利用一个全局公开的变量,再加上一个泛型容器,如此的简单:

/// <summary>
/// AccessToken记录,包含AccessToken值和过期时间
/// </summary>
TAccessToken record
AccessTokenstring;
AccessTokenExpiresDtTDateTime;
end;

var
gAccessTokenLstTDictionary<stringTAccessToken>;
gCSTCriticalSection;

♥  声明成记录类型的好处是,可以当做简单变量来使用,要是声明成指针或者类,哦哦,创建再释放,太麻烦了。微信返回的json结果中是这个AccessToken还有多少秒过期,我们换算成时间类型会比较好用一些。

♥ 泛型我喜欢,让Hash表成为强类型的。

♥  如上gAccessTokenLst用来存储获取的AccessToken,key就是微信号的APPID,估计这个不会重复吧!上面还声明了一个临界区对象gCS,大约我不说你也能猜到,既然是全局的那就得上锁,防止写入混乱。

♥  上面那两个变量gAccessTokenLst和gCS我是在单元的initialization部分实例化的,并在finalization进行了释放,在这两个地方处理全局变量的好处是:运行时只执行一次。是初始化全局变量的绝佳地点。

 

万事具备了,最终获取AccessToken的函数代码如下:

 

procedure TWxSdkImp.GetAccessToken(const appidappsecretstring;
var AccessTokenstringconst GetNewBoolean = false);
var
URLstring;
JSONObjectISuperObject;
tempsKeystring;
recAccessTokenTAccessToken;
begin
if (appid or (appsecret then
raise Exception.Create(‘TWxSdkImp.GetAccessToken执行出错,参数应用ID或者应用秘钥不能为空!’);

recAccessToken.AccessToken := ;
sKey := appid;
if gAccessTokenLst.ContainsKey(sKeythen
recAccessToken := gAccessTokenLst.Items[sKey];

// 如果要求重新获取AccessToken 或者 尚未获取AccessToken 或者 已经获取了但是离过期不足30秒
gCS.Enter;
try
if GetNew or (recAccessToken.AccessToken or (SecondSpan(recAccessToken.AccessTokenExpiresDtNow30then
begin
URL := Format(WxCmdUrl_GetAccessToken[appidappsecret]);
temp := GetMethod(httpURL3);
JSONObject := ParseJson(temp[‘”access_token”‘‘”errcode”‘]);
if Pos(‘”access_token”‘tempthen
begin
recAccessToken.AccessToken := JSONObject[‘access_token’].AsString;
recAccessToken.AccessTokenExpiresDt := IncSecond(Now, JSONObject[‘expires_in’].AsInteger);         if gAccessTokenLst.ContainsKey(sKey) then           gAccessTokenLst.Remove(sKey);         gAccessTokenLst.Add(sKey, recAccessToken);       end else         raise Exception.Create(‘TWxSdkImp.GetAccessToken执行出错,服务器返回错误代 码:’ + JSONObject[‘errcode’].AsString + ‘,错误信 息:’ + JSONObject[‘errmsg’].AsString + ‘!’);     end;   finally     gCS.Leave;   end;   AccessToken := recAccessToken.AccessToken; end;

需要获取AccessToken时,

var accessToken: string; 

GetAccessToken(‘YourAppID’, ‘YourAppSecret’, accessToken);

输出的accessToken字符串就是我们想要的结果。

 

♥  呵呵,如果想要用Delphi+iw进行微信开发的,看到这里应该算是有方向了吧,加油!希望早日看到各位的大作。

更新大数据的表结构的缓和做法 - 王森 - 博客园

mikel阅读(989)

来源: 更新大数据的表结构的缓和做法 – 王森 – 博客园

场景

前些天遇到一个问题,要往线上数据库中数据量比较大的表格里添加新的字段,以及赋上默认值,
执行的时间比较长,如果直接在原表格的基础上直接执行SQL,害怕会将表格甚至是数据库弄成死锁。
和团队兄弟聊了聊找到了一种办法,不知道的也可以借鉴一下。

解决办法

根据源表格创建一个临时表格

CREATE TABLE t_bdcards_temp LIKE t_bdcards;

给临时表格添加一个字段

ALTER TABLE t_bdcards_temp
ADD COLUMN bookId BIGINT (80) NULL
DEFAULT NULL
COMMENT ‘对应的书籍id’ AFTER user_id;

将源表格的数据插入到临时表格中

INSERT INTO t_bdcards_temp (
card_num,
STATUS,
ctime,
user_id,
bookId
) SELECT
card_num,
STATUS,
ctime,
user_id,
100031
FROM
t_bdcards;

验证数据是否正确

SELECT
count(id)
FROM
t_bdcards_temp;

SELECT
count(id)
FROM
t_bdcards;

将源表名修改成另外一个名词

RENAME TABLE t_bdcards TO t_bdcards_blank;

将临时表格修改成源表格的名称

RENAME TABLE t_bdcards_temp TO t_bdcards;

thinkphp 使用U方法自动生成URL超链接

mikel阅读(1104)

ThinkPHP U方法

U 方法是 ThinkPHP 内置的一个快捷方法,可以根据系统 URL 模式配置动态的生成智能的 URL 地址。

由于 ThinkPHP 支持各种不同的 URL 模式,另外还有分组模式,因此当环境发生变化时,有时候可能会改变 URL 模式,而 U 方法正是解决不同配置情况下的 URL 统一问题。除了动态生成 URL 以自适应系统配置外,U 方法还有一个好处是可以自动加上伪静态后缀。

U方法语法

U 方法的定义规则如下:

U('[项目://][路由@][分组名-模块/]操作?参数1=值1[&参数N=值N]')
// 或者:
U('[项目://][路由@][分组名-模块/]操作',array('参数1'=>'值1' [,'参数N'=>'值N']))

U方法实例

假设当前为 Index 模块的 index 操作,生成当前模块的 list 操作 URL:

U('list?cat_id=1&status=1')

生成的 URL 为相对链接:index.php/Index/list/cat_id/1/status/1

模板中使用 U 方法

U 方法实际属于 ThinkPHP 系统函数,可以直接使用,更多可参见《ThinkPHP 模板中使用函数》。通常 U 方法用于模板生成超链接:

<a href="{:U('list?cat_id=1&status=1')}">超链接字符</a>

生成 URL 后实际的 html 代码为:

<a href="index.php/Index/list/cat_id/1/status/1">超链接字符</a>

U 方法中使用变量,需要用 . 连接符:

<a href="{:U('list?cat_id='.$catList['cat_id'].'&status=1')}">超链接字符</a>

各种 URL 模式下的对比

U 方法根据 URL 模式的不同而自适应的生成不同的 URL 格式,如上面例子在各模式下生成的 URL 对比如下:

  • 普通兼容模式:index.php?m=Index&a=list&cat_id=1&status=1
  • PATHINFO 模式:index.php/Index/list/cat_id/1/status/1
  • REWRITE 模式:Index/list/cat_id/1/status/1
  • REWRITE 模式,伪静态后缀为 .html:Index/list/cat_id/1/status/1.html
  • REWRITE 模式,使用 – 分隔符与 .html 后缀:Index-list-cat_id-1-status-1.html

常见 U 方法使用例子

// 当前模块 list 操作
U('list?cat_id=1&status=1')
// 其他模块操作
U('Blog/read?id=1')        // 生成Blog模块的read操作,并且id为1的URL地址
// 其他分组
U('Admin-User/view?uid=1')    // 生成Admin分组的User模块的view操作的URL地址

U 方法也可以在操作中使用,与使用普通函数用法一致(不需要 $this->)。

PHP获取系统当前时间

mikel阅读(1301)

PHP获取系统当前时间
2009-07-17 09:13

使用函式 date() 实现                                         转自zjriso空间

<?php echo $showtime=date(“Y-m-d H:i:s”);?>

显示的格式: 年-月-日 小时:分钟:秒

相关时间参数:

a – “am” 或是 “pm”
A – “AM” 或是 “PM”
d – 几日,二位数字,若不足二位则前面补零; 如: “01” 至 “31”
D – 星期几,三个英文字母; 如: “Fri”
F – 月份,英文全名; 如: “January”
h – 12 小时制的小时; 如: “01” 至 “12”
H – 24 小时制的小时; 如: “00” 至 “23”
g – 12 小时制的小时,不足二位不补零; 如: “1” 至 12″
G – 24 小时制的小时,不足二位不补零; 如: “0” 至 “23”
i – 分钟; 如: “00” 至 “59”
j – 几日,二位数字,若不足二位不补零; 如: “1” 至 “31”
l – 星期几,英文全名; 如: “Friday”
m – 月份,二位数字,若不足二位则在前面补零; 如: “01” 至 “12”
n – 月份,二位数字,若不足二位则不补零; 如: “1” 至 “12”
M – 月份,三个英文字母; 如: “Jan”
s – 秒; 如: “00” 至 “59”
S – 字尾加英文序数,二个英文字母; 如: “th”,”nd”
t – 指定月份的天数; 如: “28” 至 “31”
U – 总秒数
w – 数字型的星期几,如: “0” (星期日) 至 “6” (星期六)
Y – 年,四位数字; 如: “1999”
y – 年,二位数字; 如: “99”
z – 一年中的第几天; 如: “0” 至 “365”

可以自由设定显示的内容,连接符号或是显示位置,例如 date(“m-d H”) 或者date(“dmY”);?>等php中的日

期处理

加入时间:2004-12-18 17:35:22 大小:12 KB 阅读次数:1405PHP中的日期处理

转贴:xiaxia   日期:2004-05-26   人气:9
我正打算用PHP编写一种帮助处理系统。我发现我必须知道处理完最后一位客户的问题后已经过去了多长

时间?当我过去用ASP时解决这个问题相当简单,ASP有相应的函数DateDiff可以给出两个日期间间隔多少

月、多少天和多少秒。当我搜寻完PHP手册后我发现PHP并没有类似的函数。

本文包含以下内容:
1、 得到目前的日期和时间-我们有多少种方式?
2、 改变日期显示的方式-日期和时间的显示形式
3、 转换现在的日期为Unix的时间戳值
4、 改变日期
a. 增加时间
b. 减去时间
c. 找出两日期之间的间隔
5、 为PHP添加DateAdd函数
6、 为PHP添加DateDiff函数

**得到目前的日期和时间

在Unix中,时间的表示方式为计算从1970年1月1日零时起所过去的秒数,这称为UNIX 时间戳(Unix

Epoch)。
如果我们有这样一段的代码:
?
echo time();
?
将返回值958905820
而此时的时间为2000年5月21日12时43分。
你也许会说这相当不错。当这对我毫无帮助,或者只有一点帮助。在PHP中,对日期处理的函数都必须用

到由time()返回的时间戳值。同时,由于PHP在Unix和Windows系统中均使用同样的时间戳值,这就允许你

不需要修改代码即可在不同的系统间移植。另外的一个好处是time()函数返回的是一个整数,你可以将其

作为整数字段或文本字段存入数据库,而不必使用特别的日期/时间字段。
你已经基本了解了Unix的时间戳值,现在让我们来展示它的实际用途。

改变日期显示的方式-日期和时间的显示形式

PHP提供两个办法来将Unix的时间戳值转换成为有用的数据。第一个是date()函数。这个函数有两个参数

-第一个字符串用于设定你所希望返回的格式,第二个为Unix的时间戳值。
格式化字符串通过一些简单的特殊格式化字符来显示你所希望看到的格式的日期和时间。假设你希望日期

以这样的格式显示“18h01 Sunday 21 May”。
我们需要对字符串中的每一部分使用一个特殊格式化字符,你可以从PHP手册中日期和时间函数库中找到

。这样的特殊格式化字符数量不少,他们所表示的类似于星期几、月的英文名、用2位或4位数表示的年份

,是否是上午(AM)或下午(PM)以及其他。对于这个例子我们需要的特殊字符为:
‘H’ -24 小时制的小时
‘i’- 分钟
‘l’- 星期几的英文全名
‘d’- 本月的第几日
‘F’- 月份的英文全名
因此我们的格式化字符串为”Hhi l d F”, PHP代码为:
?
echo date (“Hhi l d F” ,time());
?
当我们执行这段代码,我们发现我们所得到的结果为:
180609 Sunday 21 May
这样的结果看起来有些奇怪。让我们再查一下PHP手册,原来’h’所代表的是12 小时制的小时数。这再

次证明了一句真理:“计算机只做你所告诉它该做的,而不是你想要它做的”。我们有两个选择。第一个

是在h前使用转义字符“”:
echo date (“Hhi l d F”, time());
我们得到这样的结果:
18h12 Sunday 21 May
这正是我们所要的。但如果我们在一个十分复杂的句子中需要包含日期和时间,我们是否需要对每个字符

使用转义字符?
答案当然是不。我们使用另一个函数strftime()。
strftime()有两个好处。第一个好处我们并不在本文讨论范围内-如果你使用setlocale()函数,你可以

通过strftime得到相应语言的月份的名称。另外的一个好处是你可以将特别的日期和时间的格式化字符包

含在你的字符串中。这同时也意味着无论你是否要学习date()函数的所有特殊格式化字符,你都必须学习

一整套完全不同的格式化字符。
strftime()工作的方式和date()没有什么不同,除了特殊格式化字符的前面必须添加一个百分号%。如果

用strftime()函数,前面例子的代码如下:
?
echo strftime (“%Hh%M %A %d %b” ,time());
?
结果为:
18h24 Sunday 21 May
这也许看起来将简化繁,但考虑一下如果你所需要的显示的为”Today is Sunday 21 May 2000. The time

is somewhere close to 18h24.” 我想使用date()函数无疑令人感到厌烦。
在开始的时候,我提及我们有两种方式可以从Unix时间戳值中得到有用的数据。我们刚刚了解了date()和

strftime()。另一个getdate()。这个函数只需要Unix 的时间戳值作为参数,而函数的返回值为日期和时

间的数组。
下面是一个例子:
?
$date_time_array = getdate (time());
echo $date_time_array[ “weekday”];
?
返回的结果为:
Sunday
除了”weekday”,该数组的其他部分为:
“seconds” –秒
“minutes” –分
“hours” –小时
“mday” – 本月的第几天
“wday” -本周的第几天(数字)
“mon” -月(数字)
“year” –年
“yday” – r本年的第几天(数字)
“month” -月份全名
我们现在可以得到容易辨认的日期和时间。那么其他呢?

**转换现在的日期为Unix的时间戳值

通常你必须处理一些日期或时间格式的数据。打开M$的一个Access数据库,所有的日期都以YYYY/MM/DD的

格式存储,加入目前的日前即为2000/05/27。Mktime()函数可以将一个时间转换成Unix的时间戳值。
函数的格式为:int mktime(int hour, int minute, int second, int month, int day, int year, int

[is_dst] );
从左往右你必须提供小时、分、秒、月、天和年。最后一个参数用于指定你是否处于夏令时,此参数是可

选的,所以我们将忽略它。
代码如下:
?
echo mktime (0, 0,0 ,5, 27,2000 );
?
由于不知道小时、分和秒同时这些参数必须填写,我将其设置为0。设置为0意味着时间为午夜。
?
$access_date = “2000/05/27”;
//explode()函数用一个字符串作为分界来分解另一个字符串。这个例子$access_date通过字符串”/”来

分解
$date_elements = explode(“/” ,$access_date);
// 此时
// $date_elements[0] = 2000
// $date_elements[1] = 5
// $date_elements[2] = 27
echo mktime (0, 0,0 ,$date_elements [1], $date_elements[ 2],$date_elements [0]);
?
我们看一个比从Access数据库单纯获得日期更复杂的情况,我们得到一个以下格式的日期和时间:

2000/05/27 02:40:21 PM
?
// 来自Access的字符串
$date_time_string = “2000/05/27 02:40:21 PM”;
// 将字符串分解成3部分-日期、时间和上午/下午
$dt_elements = explode(” ” ,$date_time_string);
// 分解日期
$date_elements = explode(“/” ,$dt_elements[ 0]);
// 分解时间
$time_elements = explode(“:” ,$dt_elements[ 1]);
// 如果是下午,我们将时间增加12小时以便得到24小时制的时间
if ($dt_elements [2]== “PM”) { $time_elements[ 0]+=12;}
// 输出结果
echo mktime ($time_elements [0], $time_elements[ 1], $time_elements[ 2], $date_elements[1],

$date_elements[2], $date_elements[0]);
?

**修改日期

有时我们需要知道6小时以后是什么时间,35天前的日期或者从你最后一次玩Quake3后已过去多少秒。我

们已经知道如何用mktime()函数从单独的日期和时间中获得Unix的时间戳值。如果我们需要的并非目前日

期和时间的Unix时间戳值,我们该咋办?下面是一些练习可以帮助说明我们后面所要做的。
正如前面所见,mktime()使用以下参数:小时、分、秒、月、天和年。想想第二节,getdate()函数可以

为我们获得这些参数。
?
// 将目前的时间戳值放入一数组内
$timestamp = time();
echo $timestamp;
echo “p”;
$date_time_array = getdate( $timestamp);
// 用mktime()函数重新产生Unix时间戳值
$timestamp = mktime($date_time_array [“hours”], $date_time_array[“minutes”

],$date_time_array[ “seconds”],$date_time_array [“mon”], $date_time_array[“mday”

],$date_time_array[ “year”]);
echo $timestamp;
?
看起来有一些令人感到迷惑。我将用一些变量来使上面的程序看起来更容易了解。
?
// 将目前的时间戳值放入一数组内
$timestamp = time();
echo $timestamp;
echo “p”;
$date_time_array = getdate( $timestamp);
$hours = $date_time_array[ “hours”];
$minutes = $date_time_array[“minutes”];
$seconds = $date_time_array[ “seconds”];
$month = $date_time_array[“mon”];
$day = $date_time_array[“mday”];
$year = $date_time_array[“year”];
// 用mktime()函数重新产生Unix时间戳值
$timestamp = mktime($hours ,$minutes, $seconds,$month ,$day,$year);
echo $timestamp;
?
现在我们将由getdate()所产生的时间戳值放入相对应的名称变量中,所以代码变得相对容易阅读和理解

。现在如果我们需要在目前的时间上加上19个小时,我们用$hours+19代替mktime()函数中的$hours。

mktime()将自动为我们将时间转到第二天。
?
// 将目前的时间戳值放入一数组内
$timestamp = time();
echo strftime( “%Hh%M %A %d %b”,$timestamp);
echo “p”;
$date_time_array = getdate($timestamp);
$hours = $date_time_array[“hours”];
$minutes = $date_time_array[“minutes”];
$seconds = $date_time_array[“seconds”];
$month = $date_time_array[“mon”];
$day = $date_time_array[“mday”];
$year = $date_time_array[“year”];
// 用mktime()函数重新产生Unix时间戳值
// 增加19小时
$timestamp = mktime($hours + 19, $minutes,$seconds ,$month, $day,$year);
echo strftime( “%Hh%M %A %d %b”,$timestamp);
echo “br~E after adding 19 hours”;
?
运行后得到:
14h58 Saturday 03 Jun
09h58 Sunday 04 Jun
~E after adding 19 hours
减少时间也是同样的-你只需要减少相应变量的值即可。
得到两个不同时间值的差同样也是非常简单。你所需要做的只是将两个时间值转换为Unix的时间戳值,然

后两者相减即可。两者之差即为两个时间所相隔的秒数。另外一些算法可以很快地将秒转为天、小时、分

和秒。

**为PHP添加DateAdd函数

正如在文章一开始我所说的-写本文的原因是因为我在PHP中找不到类似ASP的DateDiff函数。在介绍完

PHP是如何处理日期和时间,让我们将ASP中常用的两个函数移植到PHP。第一个函数是DateAdd。
根据Vbscript的文档,DateAdd(interval,number,date)函数的定义为“返回已添加指定时间间隔的日期

。”
Inetrval为表示要添加的时间间隔字符串表达式,例如分或天;number为表示要添加的时间间隔的个数的

数值表达式;Date表示日期。
Interval(时间间隔字符串表达式)可以是以下任意值:
yyyy year年
q Quarter季度
m Month月
y Day of year一年的数
d Day天
w Weekday一周的天数
ww Week of year周
h Hour小时
n Minute分
s Second秒
w、y和d的作用是完全一样的,即在目前的日期上加一天,q加3个月,ww加7天。
?
function DateAdd ($interval, $number, $date) {
$date_time_array = getdate($date);
$hours = $date_time_array[“hours”];
$minutes = $date_time_array[“minutes”];
$seconds = $date_time_array[“seconds”];
$month = $date_time_array[“mon”];
$day = $date_time_array[“mday”];
$year = $date_time_array[“year”];
switch ($interval) {
case “yyyy”: $year +=$number; break;
case “q”: $month +=($number*3); break;
case “m”: $month +=$number; break;
case “y”:
case “d”:
case “w”: $day+=$number; break;
case “ww”: $day+=($number*7); break;
case “h”: $hours+=$number; break;
case “n”: $minutes+=$number; break;
case “s”: $seconds+=$number; break;
}
$timestamp = mktime($hours ,$minutes, $seconds,$month ,$day, $year);
return $timestamp;}
?
我们可以将上面的代码保存为dateadd.inc文件,然后运行以下代码:
?
include(‘dateadd.inc’);
$temptime = time();
echo strftime( “%Hh%M %A %d %b”,$temptime);
$temptime = DateAdd(“n” ,50,$temptime);
echo “p”;
echo strftime( “%Hh%M %A %d %b”,$temptime);
?
我们将得到:
15h41 Saturday 03 Jun
16h31 Saturday 03 Jun
为PHP添加DateDiff函数
现在DateAdd已经完成,那么DateDiff呢?
根据文档,DateDiff(interval,date1,date2)函数的定义为“返回两个日期之间的时间间隔”。
Intervals参数的用法与DateAdd函数中的相同。出于避免过于复杂的考虑,我们决定忽略Vbscript中

DateDiff函数中其它复杂的参数,即其两个可选的参数变量[firstdayofweek[, firstweekofyear]](它

们用于决定星期中第一天是星期天还是星期一和一年中第一周的常数。而且我们只允许intervals有以下

五个值:”w”(周)、”d”(天)、”h”(小时)、”n”(分钟) 和”s”(秒)。

Let’s see what we can come up with: 下面的代码是我们所需要的:
?
Function DateDiff ($interval, $date1,$date2) {
// 得到两日期之间间隔的秒数
$timedifference = $date2 – $date1;
switch ($interval) {
case “w”: $retval = bcdiv($timedifference ,604800); break;
case “d”: $retval = bcdiv( $timedifference,86400); break;
case “h”: $retval = bcdiv ($timedifference,3600); break;
case “n”: $retval = bcdiv( $timedifference,60); break;
case “s”: $retval = $timedifference; break;
}
return $retval;}
?
将上面的代码存为datediff.inc文件,然后运行下面的代码:
?
include(‘datediff.inc’);
include(‘dateadd.inc’);
$currenttime = time();
echo “Current time: “. strftime(“%Hh%M %A %d %b” ,$currenttime).”br”;
$newtime = DateAdd (“n”,50 ,$currenttime);
echo “Time plus 50 minutes: “. strftime(“%Hh%M %A %d %b” ,$newtime).”br”;
$temptime = DateDiff (“n”,$currenttime ,$newtime);
echo “Interval between two times: “.$temptime;
?
如果一切顺利,你可以看到以下结果:
Current time: 16h23 Saturday 03 Jun
Time plus 50 minutes: 17h13 Saturday 03 Jun
Interval between two times: 50
如果你在Unix机器上运行PHP,你必须编译PHP支持BC高精度函数。你必须从以下地址

http://www.php.net/extra/number4.tar.gz下载BC库,然后将其解压到PHP4的根目录下,重新编译PHP,

编译时要加上–enable-bcmath的选项。(详细说明见PHP4中README.BCMATH)。PHP4的Windows版本则不

需要做任何修补即可直接使用BC高精度函数。
现在你已经得到处理日期和时间的函数,剩下的就是如何将其运用到你的PHP程序中。
php时间显示例

加入时间:2004-12-18 17:22:53 大小:1 KB 阅读次数:1564
第一种:简单(供学习用)
<?PHP
$today=date(“Y-m-d G:i:s”);
echo “<center>$today</center>”;
?>

第二种:
/*
Format Time
*/
Function formatTime($time,$type=”1″){
switch($type){
case 1;#2002-06-0418:58 Tuesday
return date(“Y.m.d H:i”,$time).”<font color=blue>”.date(” l”,$time).”</font>”;
case 2;#June 2002
return date(“M Y”,$time);
case 3;#2002-06-04 18:58
return date(“Y.m.d H:i”,$time);
case 4;#06-04 AM
return date(“m-d A”,$time);
case 5;#06-04 18:58
return date(“m.d H:i”,$time);
}
}不知道对于两个以字符串格式存储的日期得比较会有什么结果,例如 ‘2007-07-12′ ‘2007-06-27′

,这种日期最好用什么方法来比较呢?就大小而论

leehao July 17th, 2006 1:33 am

/*日期比较*/
$Date_1=”2007-07-12″;
$Date_2=”2007-06-27″;

/*
首先用explode这个函数来拆分字符串
explode(“这引号里面是条件来的哦,改成:就会用:来拆分字符串的,可以应用到很多方面去的”,后面要拆

分的字符串)
*/

$Date_explode_1=explode(“-“,$Date_1);
$Date_explode_2=explode(“-“,$Date_2);

/*
拆分后的字符串,我们通过使用mktime函数来计算时间,
必须说明一下的是mktime计算出来的时间是用秒来算的哦,
后面就简单啦,具体可以看手册,里面详细得很
*/

$Day_1=mktime(0,0,0,$Date_explode_1[1],$Date_explode_1[2],$Date_explode_1[0]);
$Day_2=mktime(0,0,0,$Date_explode_2[1],$Date_explode_2[2],$Date_explode_2[0]);

$Days=round(($Day_1-$Day_2)/3600/24);

echo “您要的结果是 $Days 天哦”;
?>
请问如何得到90天以后的日期

加入时间:2004-12-18 18:09:53 大小:1 KB 阅读次数:158
//获取今天的时间信息
$Y=date(Y);
$m=date(m);
$d=date(d);
//$m为月,$d为天,$Y为年,”+”号表示之后,用”-“号可以得到多少天前的日期,”Y年m月d日”,只是显示的格

式,你可以改成”Y-m-d”,
$out_date1=date( “Y年m月d日”, mktime(0,0,0,$m,$d+7,$Y) );//一周后
$out_date2=date( “Y年m月d日”, mktime(0,0,0,$m,$d+14,$Y) );//二周后
$out_date3=date( “Y年m月d日”, mktime(0,0,0,$m+1,$d,$Y) );//一个月后
$out_date4=date( “Y年m月d日”, mktime(0,0,0,$m+2,$d,$Y) );//二个月后
$out_date5=date( “Y年m月d日”, mktime(0,0,0,$m+3,$d,$Y) );//三个月后
$out_date6=date( “Y年m月d日”, mktime(0,0,0,$m+6,$d,$Y) );//六个月后
$out_date7=date( “Y年m月d日”, mktime(0,0,0,$m,$d,$Y+1) );//一年后
echo date(“Ymd”, time() + 90*86400);
echo date(“Y-m-d”,strtotime(“2004-10-26 +90day”));

date(“Y-m-d”,strtotime(“+90 day”));

[转载]delphi之精确找图 - devlyn - 博客园

mikel阅读(1590)

来源: [转载]delphi之精确找图 – devlyn – 博客园

以前一直以为找图比较难,后来看了AutoHotkey的源码,原来也就是笨方法。

以下是精确的找图。因为已经很快,没再做优化处理。注意我这里去掉了透明处理,需要的自己加上吧。

//精确判断,在大图里的(x,y)位置上是不是小图?

function BmpCmp(bmpBig,bmp:TBitmap;x,y:integer):boolean;

var

i,j:integer;

row1, row2:pRGBTripArray;

p1,p2:TRGBTriple;

begin

result:=true;

for j:=0 to bmp.Height-1 do

begin

row1:=bmpBig.ScanLine[y+j];

row2:=bmp.ScanLine[j];

for i:= 0 to bmp.Width-1 do

begin

p1:=row1[x+i];

p2:=row2[i];

if (p1.rgbtBlue<>p2.rgbtBlue) or (p1.rgbtGreen<>p2.rgbtGreen)

or (p1.rgbtRed<>p2.rgbtRed) then

begin

result:=false;

exit;

end;

end;

end;

end;

 

以下是精确找图, 调用精确判断

// 精确找图,在大图里的(x1,y1)和(x2,y2)中找出小图来?

// 当返回true时,以下变量存放找到的位置

//    bmpFindX:integer;

//    bmpFindY:integer;

function BmpFind(bmpBig,bmp:TBitmap;x1,y1,x2,y2:integer):Boolean;

var

i,j,x,y:integer;

row, row1, row2:pRGBTripArray;

p0,p,p1,p2:TRGBTriple;

begin

if x1+y1+x2+y2=0 then

begin

x1:=0;

y1:=0;

x2:=bmpBig.Width-1;

y2:=bmpBig.Height-1;

end;

row:=bmp.ScanLine[0];

p0:=row[0];

for y:=y1 to y2-1 do

begin

row:=bmpBig.ScanLine[y];

for x:=x1 to x2-1 do

begin

p:=row[x];

if (bmp.Width<=x2-x) and (bmp.Height<=y2-y)

and (p.rgbtBlue=p0.rgbtBlue) and (p.rgbtGreen=p0.rgbtGreen)

and (p.rgbtRed=p0.rgbtRed) then

begin

if BmpCmp(bmpBig,bmp,x,y) then

begin

result:=true;

bmpFindX:=x;

bmpFindY:=y;

exit;

end;

end; // end if

end; // end for x

end; // end for y

result:=false;

end;

从网上找来的Delphi屏幕找图单元

mikel阅读(1730)

从网上找来的Delphi屏幕找图单元_淡淡的忧愁_新浪博客,淡淡的忧愁,

unit BitmapData;

//
//位图数据处理,主要用于位图的找图找色
//作者:yeye55 2009年5月29日
//
//版权 2009,由 yeye55 拥有,保留所有权利。
//本文件中的代码是免费程序,无需任何授权或许可即可用于个人和商业目的。使用者一切后果自负。
//
//如果你转载了本文件中的代码,请注明代码出处和代码作者;
//如果你修改了本文件中的代码,请注明修改位置和修改作者。
//
//本文件最早在http://www.programbbs.com/bbs/上发布
//

interface

uses
Windows, Classes, SysUtils, Graphics;

const
BD_COLORLESS = -1; //无色
BD_BITCOUNT = 24; //图象位数
BD_BYTECOUNT = BD_BITCOUNT shr 3; //每象素占用字节数
BD_LINEWIDTH = 32; //每行数据对齐宽度(位)

type
//字节数组
TByteAry = array [0..0] of Byte;
PByteAry = ^TByteAry;

//颜色变化范围,R、G、B三个通道的绝对差值
TBDColorRange = record
R : Integer;
G : Integer;
B : Integer;
end;

TBDColor = Integer; //BGR格式颜色

//转换函数
function BGR(B,G,R : Byte): TBDColor;
function RGBtoBGR(C : TColor): TBDColor;
function BGRtoRGB(C : TBDColor): TColor;
//比较颜色
function BDCompareColor(C1,C2 : TBDColor; const Range : TBDColorRange): Boolean;

type
TBDBitmapData = class; //位图数据

//枚举子图回调函数,查找多个子图时回调,返回是否继续枚举,
//Left:找到子图的左边距;
//Top:找到子图的顶边距;
//Bmp:找到子图数据;
//lParam:调用时设置的参数。
TBDEnumImageProc = function (Left,Top : Integer; Bmp : TBDBitmapData; lParam : Integer): Boolean;

//枚举颜色回调函数,查找多个颜色时回调,返回是否继续枚举,
//Left:找到颜色的左边距;
//Top:找到颜色的顶边距;
//Color:找到的颜色;
//lParam:调用时设置的参数。
TBDEnumColorProc = function (Left,Top : Integer; Color : TBDColor; lParam : Integer): Boolean;

//位图数据
TBDBitmapData = class
private
FName : String; //位图名称
FWidth : Integer; //位图宽度(象素)
FHeight : Integer; //位图高度(象素)
FBackColor : TBDColor; //背景颜色(BGR格式)
FLineWidth : Integer; //对齐后每行数据宽度(字节)
FSpareWidth : Integer; //对齐后每行数据多余宽度(字节)
FSize : Integer; //位图数据长度
FBufSize : Integer; //缓冲区实际长度
FBits : PByteAry; //位图数据缓冲区
function InitData(AWidth,AHeight : Integer): Boolean;
function GetPixels(Left,Top : Integer): TBDColor;
procedure SetPixels(Left,Top : Integer; Value : TBDColor);
public
Error : String;
constructor Create(const AName : String = ”);
destructor Destroy; override;
procedure Clear;
function LoadFromStream(Stream : TStream; ABackColor : TBDColor = BD_COLORLESS): Boolean;
function SaveToStream(Stream : TStream):Boolean;
function LoadFromFile(const FileName : string; ABackColor : TBDColor = BD_COLORLESS): Boolean;
function SaveToFile(const FileName : string): Boolean;
function LoadFromBitmap(Bitmap : TBitmap): Boolean;
function SaveToBitmap(Bitmap : TBitmap): Boolean;
function CopyFormScreen(Left : Integer = -1; Top : Integer = -1; AWidth : Integer = -1; AHeight : Integer = -1): Boolean;
function CopyFormCursor: Boolean;
function Compare(Bmp : TBDBitmapData; Left : Integer = 0; Top : Integer = 0): Boolean; overload;
function Compare(Bmp : TBDBitmapData; const Range : TBDColorRange; Left : Integer = 0; Top : Integer = 0): Boolean; overload;
function FindImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean; overload;
function FindImage(Bmp : TBDBitmapData; const Range : TBDColorRange; var Left,Top : Integer): Boolean; overload;
function FindCenterImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean; overload;
function FindCenterImage(Bmp : TBDBitmapData; const Range : TBDColorRange; var Left,Top : Integer): Boolean; overload;
function EnumImage(Bmp : TBDBitmapData; EnumImageProc : TBDEnumImageProc; lParam : Integer = 0): Boolean; overload;
function EnumImage(Bmp : TBDBitmapData; const Range : TBDColorRange; EnumImageProc : TBDEnumImageProc; lParam : Integer = 0): Boolean; overload;
function FindColor(Color : TBDColor; var Left,Top : Integer): Boolean; overload;
function FindColor(Color : TBDColor; const Range : TBDColorRange; var Left,Top : Integer): Boolean; overload;
function FindCenterColor(Color : TBDColor; var Left,Top : Integer): Boolean; overload;
function FindCenterColor(Color : TBDColor; const Range : TBDColorRange; var Left,Top : Integer): Boolean; overload;
function EnumColor(Color : TBDColor; EnumColorProc : TBDEnumColorProc; lParam : Integer = 0): Boolean; overload;
function EnumColor(Color : TBDColor; const Range : TBDColorRange; EnumColorProc : TBDEnumColorProc; lParam : Integer = 0): Boolean; overload;
property Name : String read FName write FName; //位图名称
property Width : Integer read FWidth; //位图宽度(象素)
property Height : Integer read FHeight; //位图高度(象素)
property BackColor : TBDColor read FBackColor write FBackColor; //背景颜色(BGR格式)
property LineWidth : Integer read FLineWidth; //对齐后每行数据宽度(字节)
property SpareWidth : Integer read FSpareWidth; //对齐后每行数据多余宽度(字节)
property Size : Integer read FSize; //位图数据长度
property Bits : PByteAry read FBits; //位图数据缓冲区
property Pixels[Left,Top : Integer] : TBDColor read GetPixels write SetPixels; default;
end;

implementation

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
);

var
ScreenWidth : Integer;
ScreenHeight : Integer;
IconWidth : Integer;
IconHeight : Integer;

//根据B、G、R三个通道的值生成一个BGR格式颜色。
function BGR(B,G,R : Byte): TBDColor;
begin
result:=(B or (G shl 8) or (R shl 16));
end;

//RGB颜色格式转换到BGR颜色格式。
function RGBtoBGR(C : TColor): TBDColor;
begin
result:=((C and $FF0000) shr 16) or (C and $00FF00) or ((C and $0000FF) shl 16);
end;

//BGR颜色格式转换到RGB颜色格式。
function BGRtoRGB(C : TBDColor): TColor;
begin
result:=((C and $FF0000) shr 16) or (C and $00FF00) or ((C and $0000FF) shl 16);
end;

//根据颜色范围Range比较颜色C1和C2,返回C1和C2是否相似,
//C1,C2:BGR格式颜色;
//Range:为颜色变化范围。
function BDCompareColor(C1,C2 : TBDColor; const Range : TBDColorRange): Boolean;
var
C : Integer;
begin
result:=false;
//B
C:=(C1 and $FF)-(C2 and $FF);
if (C>Range.B) or (C< -Range.B) then exit; //G C:=((C1 and $FF00) shr 8)-((C2 and $FF00) shr 8); if (C>Range.G) or (C< -Range.G) then exit; //R C:=((C1 and $FF0000) shr 16)-((C2 and $FF0000) shr 16); if (C>Range.R) or (C< -Range.R) then exit; // result:=true; end; {TBDBitmapData} //位图数据 constructor TBDBitmapData.Create(const AName : String); begin self.FName:=AName; self.FWidth:=0; self.FHeight:=0; self.FBackColor:=BD_COLORLESS; self.FLineWidth:=0; self.FSize:=0; self.FBufSize:=0; self.FBits:=nil; self.Error:=''; end; destructor TBDBitmapData.Destroy; begin self.Clear; end; //根据当前的AWidth和AHeight初始化数据,分配内存,返回是否成功, //如果失败将设置self.Error说明情况, //AWidth:位图的宽度; //AHeight:位图的高度。 function TBDBitmapData.InitData(AWidth,AHeight : Integer): Boolean; var Align : Integer; begin self.Error:=''; result:=true; if (self.FWidth=AWidth) and (self.FHeight=AHeight) then exit; //计算对齐后的每行数据宽度 self.FWidth:=AWidth; self.FHeight:=AHeight; Align:=BD_LINEWIDTH-1; self.FLineWidth:=(((self.FWidth*BD_BITCOUNT)+Align) and ($7FFFFFFF-Align)) shr 3; self.FSpareWidth:=self.FLineWidth-(self.FWidth*BD_BYTECOUNT); self.FSize:=self.FLineWidth*self.FHeight; //分配内存 if self.FSize<=self.FBufSize then exit; if self.FBits<>nil then FreeMem(self.FBits);
try
GetMem(self.FBits,self.FSize);
except
on EOutOfMemory do begin
self.FSize:=0;
self.FBufSize:=0;
self.FBits:=nil;
self.Error:=’内存不足!’;
result:=false;
exit;
end;
end;
self.FBufSize:=self.FSize;
end;

//获取指定位置象素的颜色值,
//Left:象素的左边距;
//Top:象素的顶边距。
function TBDBitmapData.GetPixels(Left,Top : Integer): TBDColor;
begin
if (Left<0) or (Left>=self.FWidth) or
(Top<0) or (Top>=self.FHeight) then
begin
result:=0;
exit;
end;
result:=((PInteger(@(self.FBits[
((self.FHeight-Top-1)*self.FLineWidth)+(Left*BD_BYTECOUNT)
])))^ and $FFFFFF);
end;

//设置指定位置象素的颜色值,
//Left:象素的左边距;
//Top:象素的顶边距;
//Value:BGR格式颜色。
procedure TBDBitmapData.SetPixels(Left,Top : Integer; Value : TBDColor);
var
Off : Integer;
begin
if (Left<0) or (Left>=self.FWidth) or
(Top<0) or (Top>=self.FHeight) then exit;
Off:=((self.FHeight-Top-1)*self.FLineWidth)+(Left*BD_BYTECOUNT);
//B
self.FBits[Off]:=Byte(Value and $FF);
//G
self.FBits[Off+1]:=Byte((Value and $FF00) shr 8);
//R
self.FBits[Off+2]:=Byte((Value and $FF0000) shr 16);
end;

//清除当前的位图数据。
procedure TBDBitmapData.Clear;
begin
self.FWidth:=0;
self.FHeight:=0;
self.FBackColor:=BD_COLORLESS;
self.FLineWidth:=0;
self.FSize:=0;
self.FBufSize:=0;
if self.FBits<>nil then
begin
FreeMem(self.FBits);
self.FBits:=nil;
end;
self.Error:=”;
end;

//从数据流中导入位图数据,返回是否成功,
//如果失败将设置self.Error说明情况,
//数据流中的数据必需是24位BMP格式文件数据,
//Stream:数据流;
//ABackColor:位图的背景颜色,可省略。
function TBDBitmapData.LoadFromStream(Stream : TStream; ABackColor : TBDColor): Boolean;
var
FileHeader : TBitmapFileHeader;
InfoHeader : TBitmapInfoHeader;
begin
if Stream=nil then
begin
self.Error:=’没有指定数据流!’;
result:=false;
exit;
end;
//读取文件头
Stream.Read(FileHeader,SizeOf(TBitmapFileHeader));
Stream.Read(InfoHeader,SizeOf(TBitmapInfoHeader));
with FileHeader,InfoHeader do
begin
//确定位图格式
if (bfType<>$4D42) or (biSize<>SizeOf(TBitmapInfoHeader)) or
(biBitCount<>BD_BITCOUNT) or (biCompression<>BI_RGB) then
begin
self.Error:=’错误的数据格式!’;
result:=false;
exit;
end;
//数据初始化
self.FBackColor:=ABackColor;
if not self.InitData(biWidth,biHeight) then
begin
result:=false;
exit;
end;
end;
//读入数据
result:=Stream.Read((self.FBits)^,self.FSize)=self.FSize;
if result then self.Error:=”
else self.Error:=’读取的数据不完整!’;
end;

//将当前的位图数据导出到数据流中,返回是否成功,
//如果失败将设置self.Error说明情况,
//数据按24位BMP文件数据格式导出到数据流中,
//Stream:数据流。
function TBDBitmapData.SaveToStream(Stream : TStream):Boolean;
var
FileHeader : TBitmapFileHeader;
InfoHeader : TBitmapInfoHeader;
HeaderLen,n : Integer;
begin
if Stream=nil then
begin
self.Error:=’没有指定数据流!’;
result:=false;
exit;
end;
//初始化文件头
HeaderLen:=SizeOf(TBitmapFileHeader)+SizeOf(TBitmapInfoHeader);
with FileHeader,InfoHeader do
begin
bfType:=$4D42;
bfSize:=self.FSize+HeaderLen;
bfReserved1:=0;
bfReserved2:=0;
bfOffBits:=HeaderLen;
biSize:=SizeOf(TBitmapInfoHeader);
biWidth:=self.FWidth;
biHeight:=self.FHeight;
biPlanes:=1;
biBitCount:=BD_BITCOUNT;
biCompression:=BI_RGB;
biSizeImage:=self.FSize;
biXPelsPerMeter:=$EC4;
biYPelsPerMeter:=$EC4;
biClrUsed:=0;
biClrImportant:=0;
end;
//写入数据
n:=0;
n:=n+Stream.Write(FileHeader,SizeOf(TBitmapFileHeader));
n:=n+Stream.Write(InfoHeader,SizeOf(TBitmapInfoHeader));
n:=n+Stream.Write((self.FBits)^,self.FSize);
result:=n=(self.FSize+HeaderLen);
if result then self.Error:=”
else self.Error:=’写入的数据不完整!’;
end;

//从文件中导入位图数据,返回是否成功,
//如果失败将设置self.Error说明情况,
//文件必需是24位BMP格式文件,
//FileName:BMP文件名;
//ABackColor:位图的背景颜色,可省略。
function TBDBitmapData.LoadFromFile(const FileName : string; ABackColor : TBDColor): Boolean;
var
Stream : TFileStream;
begin
Stream:=TFileStream.Create(FileName,fmOpenRead);
result:=self.LoadFromStream(Stream,ABackColor);
Stream.Free;
end;

//将当前的位图数据导出到文件中,返回是否成功,
//如果失败将设置self.Error说明情况,
//数据按24位BMP文件数据格式导出到文件中,
//FileName:BMP文件名。
function TBDBitmapData.SaveToFile(const FileName : string): Boolean;
var
Stream : TFileStream;
begin
Stream:=TFileStream.Create(FileName,fmCreate);
result:=self.SaveToStream(Stream);
Stream.Free;
end;

//从一个TBitmap对象中导入数据,返回是否成功,位图的背景颜色由
//TBitmap.Transparent和TBitmap.TransparentColor决定,
//如果失败将设置self.Error说明情况,
//Bitmap:TBitmap对象。
function TBDBitmapData.LoadFromBitmap(Bitmap : TBitmap): Boolean;
var
Stream : TMemoryStream;
ABackColor : TBDColor;
begin
if Bitmap=nil then
begin
self.Error:=’没有指定位图!’;
result:=false;
exit;
end;
if Bitmap.Transparent then
ABackColor:=RGBtoBGR(Bitmap.TransparentColor)
else
ABackColor:=BD_COLORLESS;
Stream:=TMemoryStream.Create;
Bitmap.SaveToStream(Stream);
Stream.Position:=0;
result:=self.LoadFromStream(Stream,ABackColor);
Stream.Free;
end;

//将当前的位图数据导出到一个TBitmap对象中,返回是否成功,根据当前
//的背景颜色设置TBitmap.Transparent和TBitmap.TransparentColor成员,
//如果失败将设置self.Error说明情况,
//Bitmap:TBitmap对象。
function TBDBitmapData.SaveToBitmap(Bitmap : TBitmap): Boolean;
var
Stream : TMemoryStream;
begin
if Bitmap=nil then
begin
self.Error:=’没有指定位图!’;
result:=false;
exit;
end;
Stream:=TMemoryStream.Create;
result:=self.SaveToStream(Stream);
if not result then
begin
Stream.Free;
exit;
end;
Stream.Position:=0;
Bitmap.LoadFromStream(Stream);
if self.FBackColor<>BD_COLORLESS then
begin
Bitmap.TransparentColor:=BGRtoRGB(self.FBackColor);
Bitmap.Transparent:=true;
end
else Bitmap.Transparent:=false;
Stream.Free;
end;

//从屏幕上的指定范围中截图,并导入数据,返回是否成功,
//如果失败将设置self.Error说明情况,
//Left:截图的左边距,可省略;
//Top:截图的顶边距,可省略;
//AWidth:截图的宽度,可省略;
//AHeight:截图的高度,可省略。
function TBDBitmapData.CopyFormScreen(Left,Top,AWidth,AHeight : Integer): Boolean;
var
Wnd : HWND;
DC,MemDC : HDC;
Bitmap,OldBitmap : HBITMAP;
BitInfo : TBitmapInfo;
begin
//参数调整
if (Left<0) or (Left>=ScreenWidth) then Left:=0;
if (Top<0) or (Top>=ScreenHeight) then Top:=0;
if AWidth< =0 then AWidth:=ScreenWidth-Left; if AHeight<=0 then AHeight:=ScreenHeight-Top; //数据初始化 self.FBackColor:=BD_COLORLESS; if not self.InitData(AWidth,AHeight) then begin result:=false; exit; end; //截图 Wnd:=GetDesktopWindow(); DC:=GetWindowDC(Wnd); MemDC:=CreateCompatibleDC(DC); Bitmap:=CreateCompatibleBitmap(DC,self.FWidth,self.FHeight); OldBitmap:=SelectObject(MemDC,Bitmap); result:=BitBlt(MemDC,0,0,self.FWidth,self.FHeight,DC,Left,Top,SRCCOPY); Bitmap:=SelectObject(MemDC,OldBitmap); if not result then begin DeleteDC(MemDC); DeleteObject(Bitmap); ReleaseDC(Wnd,DC); self.Error:='截图失败!'; exit; end; //位图信息初始化 with BitInfo.bmiHeader do begin biSize:=SizeOf(TBitmapInfoHeader); biWidth:=self.FWidth; biHeight:=self.FHeight; biPlanes:=1; biBitCount:=BD_BITCOUNT; biCompression:=BI_RGB; biSizeImage:=0; biXPelsPerMeter:=0; biYPelsPerMeter:=0; biClrUsed:=0; biClrImportant:=0; end; //提取数据 result:=GetDIBits(DC,Bitmap,0,self.FHeight,Pointer(self.FBits),BitInfo,DIB_RGB_COLORS)<>0;
if result then self.Error:=”
else self.Error:=’提取数据失败!’;
DeleteDC(MemDC);
DeleteObject(Bitmap);
ReleaseDC(Wnd,DC);
end;

//截取鼠标指针的位图,并导入数据,返回是否成功,
//如果失败将设置self.Error说明情况,
//如果鼠标指针是动画指针,默认截取第一帧画面。
function TBDBitmapData.CopyFormCursor: Boolean;
var
Wnd : HWND;
DC,MemDC : HDC;
Bitmap,OldBitmap : HBITMAP;
CurInfo : TCursorInfo;
BitInfo : TBitmapInfo;
begin
//数据初始化
self.FBackColor:=BD_COLORLESS;
self.InitData(IconWidth,IconHeight);
//获取鼠标指针信息
FillChar(CurInfo,SizeOf(TCursorInfo),0);
CurInfo.cbSize:=SizeOf(TCursorInfo);
if not GetCursorInfo(CurInfo) then
begin
self.Error:=’获取鼠标指针信息失败!’;
result:=false;
exit;
end;
//截取鼠标指针位图
Wnd:=GetDesktopWindow();
DC:=GetWindowDC(Wnd);
MemDC:=CreateCompatibleDC(DC);
Bitmap:=CreateCompatibleBitmap(DC,self.FWidth,self.FHeight);
OldBitmap:=SelectObject(MemDC,Bitmap);
result:=DrawIconEx(MemDC,0,0,CurInfo.hCursor,0,0,0,0,DI_IMAGE);
Bitmap:=SelectObject(MemDC,OldBitmap);
if not result then
begin
DeleteDC(MemDC);
DeleteObject(Bitmap);
ReleaseDC(Wnd,DC);
self.Error:=’截取鼠标指针位图失败!’;
exit;
end;
//位图信息初始化
with BitInfo.bmiHeader do
begin
biSize:=SizeOf(TBitmapInfoHeader);
biWidth:=self.FWidth;
biHeight:=self.FHeight;
biPlanes:=1;
biBitCount:=BD_BITCOUNT;
biCompression:=BI_RGB;
biSizeImage:=0;
biXPelsPerMeter:=0;
biYPelsPerMeter:=0;
biClrUsed:=0;
biClrImportant:=0;
end;
//提取数据
result:=GetDIBits(DC,Bitmap,0,self.FHeight,Pointer(self.FBits),BitInfo,DIB_RGB_COLORS)<>0;
if result then self.Error:=”
else self.Error:=’提取数据失败!’;
DeleteDC(MemDC);
DeleteObject(Bitmap);
ReleaseDC(Wnd,DC);
end;

//在当前位图的指定位置比较Bmp位图,返回是否一致,
//无论是否一致都不会修改self.Error,
//Bmp位图面幅要小于等于当前位图的面幅,Bmp位图不能超出当前位图,
//Bmp:位图数据;
//Left:比较时的左边距,可省略;
//Top:比较时的顶边距,可省略。
function TBDBitmapData.Compare(Bmp : TBDBitmapData; Left,Top : Integer): Boolean;
var
x,y,Off1,Off2 : Integer;
c1,c2 : TBDColor;
begin
if ((Left+Bmp.FWidth)>self.FWidth) or
((Top+Bmp.FHeight)>self.FHeight) then
begin
result:=false;
exit;
end;
Off1:=((self.FHeight-Bmp.FHeight-Top)*self.FLineWidth)+(Left*BD_BYTECOUNT);
Off2:=0;
result:=true;
for y:=0 to Bmp.FHeight-1 do
begin
for x:=0 to Bmp.FWidth-1 do
begin
c1:=((PInteger(@(self.FBits[Off1])))^ and $FFFFFF);
c2:=((PInteger(@(Bmp.FBits[Off2])))^ and $FFFFFF);
if (c1<>self.FBackColor) and (c2<>Bmp.FBackColor) and
(c1<>c2) then
begin
result:=false;
break;
end;
Off1:=Off1+3;
Off2:=Off2+3;
end;
if not result then break;
Off1:=Off1+(self.FLineWidth-Bmp.FLineWidth)+Bmp.FSpareWidth;
Off2:=Off2+Bmp.FSpareWidth;
end;
end;

//在当前位图的指定位置模糊比较Bmp位图,返回是否一致,
//无论是否一致都不会修改self.Error,
//Bmp位图面幅要小于等于当前位图的面幅,Bmp位图不能超出当前位图,
//Bmp:位图数据;
//Range:为颜色变化范围
//Left:比较时的左边距,可省略;
//Top:比较时的顶边距,可省略。
function TBDBitmapData.Compare(Bmp : TBDBitmapData; const Range : TBDColorRange; Left,Top : Integer): Boolean;
var
x,y,Off1,Off2 : Integer;
c1,c2 : TBDColor;
begin
if ((Left+Bmp.FWidth)>self.FWidth) or
((Top+Bmp.FHeight)>self.FHeight) then
begin
result:=false;
exit;
end;
Off1:=((self.FHeight-Bmp.FHeight-Top)*self.FLineWidth)+(Left*BD_BYTECOUNT);
Off2:=0;
result:=true;
for y:=0 to Bmp.FHeight-1 do
begin
for x:=0 to Bmp.FWidth-1 do
begin
c1:=((PInteger(@(self.FBits[Off1])))^ and $FFFFFF);
c2:=((PInteger(@(Bmp.FBits[Off2])))^ and $FFFFFF);
if (c1<>self.FBackColor) and (c2<>Bmp.FBackColor) and
(not BDCompareColor(c1,c2,Range)) then
begin
result:=false;
break;
end;
Off1:=Off1+3;
Off2:=Off2+3;
end;
if not result then break;
Off1:=Off1+(self.FLineWidth-Bmp.FLineWidth)+Bmp.FSpareWidth;
Off2:=Off2+Bmp.FSpareWidth;
end;
end;

//从当前位图中查找与Bmp一致的子图,返回是否找到,
//无论是否找到都不会修改self.Error,
//按从左到右,从上到下的顺序查找,
//找到返回true,设置Left和Top为找到子图的位置,
//没找到返回false,设置Left和Top为-1。
//Bmp:子图数据;
//Left:找到子图的左边距;
//Top:找到子图的顶边距。
function TBDBitmapData.FindImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean;
var
x,y : Integer;
begin
result:=false; x:=0;
for y:=0 to self.FHeight-Bmp.FHeight-1 do
begin
for x:=0 to self.FWidth-Bmp.FWidth-1 do
begin
if self.Compare(Bmp,x,y) then
begin
result:=true;
break;
end;
end;
if result then break;
end;
if result then
begin
Left:=x; Top:=y;
end
else
begin
Left:=-1; Top:=-1;
end;
end;

//从当前位图中模糊查找与Bmp一致的子图,返回是否找到,
//无论是否找到都不会修改self.Error,
//按从左到右,从上到下的顺序查找,
//找到返回true,设置Left和Top为找到的位置,
//没找到返回false,设置Left和Top为-1。
//Bmp:子图数据;
//Range:为颜色变化范围;
//Left:找到子图的左边距;
//Top:找到子图的顶边距。
function TBDBitmapData.FindImage(Bmp : TBDBitmapData; const Range : TBDColorRange; var Left,Top : Integer): Boolean;
var
x,y : Integer;
begin
result:=false; x:=0;
for y:=0 to self.FHeight-Bmp.FHeight-1 do
begin
for x:=0 to self.FWidth-Bmp.FWidth-1 do
begin
if self.Compare(Bmp,Range,x,y) then
begin
result:=true;
break;
end;
end;
if result then break;
end;
if result then
begin
Left:=x; Top:=y;
end
else
begin
Left:=-1; Top:=-1;
end;
end;

//从当前位图中查找与Bmp一致的子图,返回是否找到,
//无论是否找到都不会修改self.Error,
//以(Left,Top)为基点,从中心向四周查找,
//找到返回true,设置Left和Top为找到子图的位置,
//没找到返回false,设置Left和Top为-1。
//Bmp:子图数据;
//Left:找到子图的左边距;
//Top:找到子图的顶边距。
function TBDBitmapData.FindCenterImage(Bmp : TBDBitmapData; var Left,Top : Integer): Boolean;
var
Aspect : TAspect;
VisitCount,Count,i : Integer;
begin
result:=false;
VisitCount:=0;
Aspect:=asUp;
Count:=1;
while VisitCount< (self.FWidth*self.FHeight) do begin for i:=0 to Count-1 do begin if (Left>=0) and (Left=0) and (Top=0) and (Left=0) and (Top=0) and (Left=0) and (Top=0) and (Left=0) and (Top

(转)Delphi下实现全屏快速找图找色

mikel阅读(1069)

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

前言

 

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

 

一、数据提取

 

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

 

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