开发平台:VS 2008,Windows xp sp2
效果截图:
图1 软件界面图
图2 差异结果图
图片说明:第一幅图展示的是我所实现的这个软件的最终效果图,通过菜单栏的按键可以选择两幅有差异的图片(要求是相同大小,24位或以上颜色,目的是模仿系统屏幕截图),然后对图片进行比较后,就会截取出差异部分,并将该图片保存到E盘根目录,也就是上面的差异结果图。
最近项目中要实现屏幕传输和控制功能,其中主要的就是屏幕传输功能。“不就是屏幕传输嘛,简单呀~~建立个线程,隔一定时间截取一下屏幕的图片,然后发送给对方不就成了。”这是我最初的想法,我也按照这个办法做了,当看到屏幕从一台电脑成功传到了另一台电脑时(局域网内),我有点高兴,心里暗想“原来远程控制也不过如此”。当然,事情的发展并不如预期中那样完美,事实上,我所实现的远程控制几乎没有任何价值,因为延迟太大,一副屏幕的截图从截取到发送到再显示出来,整个过程至少要耗费2秒多的时间。这是什么概念呢?我们可以比较一下,标准的卡通片是24fps,也就是每秒24帧,这样才会流畅;如果你玩过极品飞车,会发现极品飞车一般的帧数会保持在40以上(之所以说一般,是因为帧数和具体的显卡有关)。而现在我所实现的这个帧数是0.5,极品飞车的帧数是我的80倍,当你玩极品飞车的时候,画面每2秒才刷新一次,你就会知道我的实现有多烂了。不过这些帧数都是对流畅性要求极高的环境来说的,对于远程控制这种每秒画面变化不大的情况,就没必要这么高要求了,只要能保持在6帧每秒就几乎可以达到流畅的感受了。
屏幕传输主要分为“截图->压缩->传输->解压->显示”这几个步骤,传输、压缩、解压及显示几乎很难有明显的性能提升,因此主要能改进的就是“截图”这个过程。单纯的全屏截图,我尝试过最快每秒也就7、8帧,无法实现更高的速度。(我截屏利用的GDI+方式,而并非是用DirectX来做,DirectX更能发挥显卡的性能,但是我没有尝试过。)7、8帧的速度是不是可以了呢?这是不够的,因为这只是单纯的截图,并没有加上网络传输、压缩解压等,因此必须要有更快的速度。而且,此种方式,每秒需要传输的数据量是相当惊人的。拿我的电脑1280*800的分辨率来说,按24位颜色质量存储的话,一张图需要2.9M,按每秒7帧来算需要20.3M。每秒需要传输20.3M,这对于现有的网络带宽来说是承受不了的。
翻阅了很多网上达人的文章,发现通常用于屏幕传输的方法主要是传输前后两幅图片的不同之处。而查找不同之处的算法又主要有两类:隔行查找和分块比较。翻阅了很多,主要是以Delphi下的实现为主,尤其以DG老大为代表的实现方式。由于本人从未用过Delphi,对C++也有多年未用了,因此在研究DG老大的代码(DGScreenSpy_0.2c)上着实花了不少时间,现在也只能算是懂了个基本。在我自己的实现方式,并非只是简单的对DG老大代码的翻译,根据我自己的理解,我的算法思路如下:
首先对两张图片的数据按照从上到下从左到右的方式进行扫描,考虑到在实际的环境下,一个图标或一个窗口肯定会横向和纵向跨好几个像素,因此用了两个参数来分别表示步进值。扫描的过程中用一个矩形区域来表示当前变化的范围,这个范围在整个扫描过程中会随时进行调整以适应变化的区域。
下面举个例子来说明区域的设置:从第0行开始扫描,没有发现该行内的像素不同,加上步进的数值后(假如是3),扫描接着从第4(3+1)行开始,在该行的扫描中发现第2个像素到第10个像素(基数为0)都不同,于是设置矩形的Top边为4,Left为2,Right为11,Bottom为5。接着扫描第8行,发现这一行中第0个像素到第8个像素不一样,则调整矩形的区域(Top:4,Left:0,Right:11,Bottom:9),依次类推直到扫描到一行全都相同或扫描完最后一行时,这个矩形的区域则正式确定。这样该区域的图形就是最小的变化范围。
这是大概的思路,具体的大家可以参考我的代码。按照这种方式,可以获取变化的最小区域,如果变化不多,则图片数据量明显变少,如果变化很多或干脆整个屏幕都不一样,那图片数据量仍然会很大,因此还需要进一步对图片数据进行压缩或采取降低图片颜色质量的方式,QQ的远程屏幕传输就降低了图片的质量,估计其图片质量为16位每像素。
为了专门研究图片差异的获取,所以并没有实现其他诸如压缩、传输的功能。其中对像素的比较用的是BitmapData.Scan0来获取像素指针的方式进行比较,因为这样比较速度最快。我在测试时发现,我的这种实现方式(以我设置的步进值为基准)一次比较需要0.15秒左右(包括了存储差异部分到本地文件的时间,大约耗时0.04秒),速度上仍然有待提升。改变步进值可以适当的调节速度,但也会影响到变化区域的准确性。
本着合作共赢的想法,提供我的代码供大家参考,希望大家在看了我的思路和代码后,能提出宝贵的意见,我会根据您的意见思路对代码进行不断改进,每一次重大改进我都会把代码发布出来。希望能早日完成一款Dot Net下的远程控制佳作。以下是核心代码:
FindDifferences
public
void
FindDifferences()
{
if
(_oldBmp.GetHashCode()
==
_newBmp.GetHashCode())
{
_status
=
-
1
;
return
;
}
else
if
(_oldBmp.Width
!=
_newBmp.Width
||
_oldBmp.Height
!=
_newBmp.Height)
{
_status
=
1
;
return
;
}
else
{
//
行扫描
BitmapData bdOldBmp
=
_oldBmp.LockBits(
new
Rectangle(
0
,
0
, _oldBmp.Width, _oldBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
BitmapData bdNewBmp
=
_newBmp.LockBits(
new
Rectangle(
0
,
0
, _newBmp.Width, _newBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int
rectTop
=
-
1
, rectLeft
=
-
1
, rectRight
=
-
1
, rectBottom
=
-
1
;
int
vStep
=
VERTICAL_STEP;
int
hStep
=
HORIZONTAL_STEP;
int
diffCapacity
=
0
;
//
容差
bool
sameLine
=
false
;
//
表示是否是同一行内的扫描
int
differenceNumOneLine
=
0
;
unsafe
{
byte
*
pointToOldBmp
=
(
byte
*
)bdOldBmp.Scan0.ToPointer();
byte
*
pointToNewBmp
=
(
byte
*
)bdNewBmp.Scan0.ToPointer();
int
offset
=
bdNewBmp.Stride
-
_newBmp.Width
*
3
;
logger.Debug(
"
启动时间:
"
+
DateTime.Now.ToString(
"
mm-ss-ffff
"
));
for
(
int
y
=
0
; y
<
_newBmp.Height; y
=
y
+
1
+
vStep)
{
sameLine
=
false
;
differenceNumOneLine
=
0
;
for
(
int
x
=
0
; x
<
_newBmp.Width; x
=
x
+
1
+
hStep)
{
if
(Math.Abs(pointToOldBmp[
0
]
-
pointToNewBmp[
0
])
>
diffCapacity
||
Math.Abs(pointToOldBmp[
1
]
-
pointToNewBmp[
1
])
>
diffCapacity
||
Math.Abs(pointToOldBmp[
2
]
-
pointToNewBmp[
2
])
>
diffCapacity)
{
differenceNumOneLine
++
;
if
(sameLine)
{
//
如果是同一行,则当前这个点只用于给right赋值
rectRight
=
Math.Max(x
+
1
, rectRight);
}
else
{
//
如果不是同一行,则当前这个点是这行扫描时发现的第一个点,则用于给left赋值
if
(rectLeft
==
-
1
)
{
//
rectLeft为-1的时候,rectRight肯定也是-1,-1说明是第一次扫描
rectLeft
=
x;
rectRight
=
x
+
1
;
}
else
{
rectLeft
=
Math.Min(x, rectLeft);
}
rectTop
=
rectTop
==
-
1
?
y : Math.Min(y, rectTop);
sameLine
=
true
;
}
}
if
(x
+
hStep
+
1
>=
_newBmp.Width)
{
hStep
=
_newBmp.Width
-
x
-
1
;
}
pointToNewBmp
+=
3
*
(hStep
+
1
);
pointToOldBmp
+=
3
*
(hStep
+
1
);
}
//
给rectBottom赋值
if
(y
+
vStep
+
1
>=
_newBmp.Height)
{
rectBottom
=
_newBmp.Height;
}
else
if
(differenceNumOneLine
>
0
)
{
rectBottom
=
y
+
1
;
}
if
((differenceNumOneLine
==
0
||
y
+
vStep
+
1
>=
_newBmp.Height)
&&
rectBottom
-
rectTop
>
0
&&
rectRight
-
rectLeft
>
0
)
{
if
(handler
!=
null
)
{
handler(_newBmp,
new
Rectangle(rectLeft, rectTop, rectRight
-
rectLeft, rectBottom
-
rectTop));
}
rectTop
=
rectRight
=
rectLeft
=
rectBottom
=
-
1
;
}
vStep
=
differenceNumOneLine
==
0
?
1
: VERTICAL_STEP;
//
补齐偏移量
pointToNewBmp
+=
offset;
pointToOldBmp
+=
offset;
//
调整指针隔行
pointToOldBmp
+=
vStep
*
bdOldBmp.Stride;
pointToNewBmp
+=
vStep
*
bdNewBmp.Stride;
}
}
_oldBmp.UnlockBits(bdOldBmp);
_newBmp.UnlockBits(bdNewBmp);
logger.Debug(
"
结束时间:
"
+
DateTime.Now.ToString(
"
mm-ss-ffff
"
));
}
}
项目打包下载:http://files.cnblogs.com/stg609/Pic.rar
参考:http://iamgyg.blog.163.com/