阅读提示:
《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。
《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。
尽可能保持二者内容一致,可相互对照。
本文代码必须包括文章《Delphi图像处理 -- 数据类型及公用过程》中的ImageData.pas单元。
CSDN论坛中,经常看到有关截屏的贴。所谓截屏,指的是获取屏幕,或者屏幕上某个窗口上的信息,并将其转换为图像的操作。为此,也写了几个Windows下的“截屏”函数:
function GetBitmapInfoHeader(const Data: TImageData): TBitmapInfoHeader;
begin
Result.biSize := Sizeof(TBitmapInfoHeader);
Result.biWidth := Data.Width;
Result.biHeight := Data.Height;
Result.biPlanes := 1;
Result.biBitCount := (Data.PixelFormat shr 8) and $ff;
Result.biCompression := BI_RGB;
end;
procedure GetDCImageData(DC: HDC; x, y: Integer; var Data: TImageData; pbi: TBitmapInfo);
var
saveBitmap, Bitmap: HBITMAP;
memDC: HDC;
begin
Bitmap := CreateCompatibleBitmap(DC, Data.Width, Data.Height);
try
memDC := CreateCompatibleDC(DC);
saveBitmap := SelectObject(memDC, Bitmap);
try
BitBlt(memDC, 0, 0, Data.Width, Data.Height, DC, x, y, SRCCOPY);
finally
SelectObject(memDC, saveBitmap);
DeleteDC(memDC);
end;
GetDIBits(DC, bitmap, 0, Data.Height, Data.Scan0, pbi, DIB_RGB_COLORS);
finally
DeleteObject(Bitmap);
end;
end;
function GetImageDataFromDC(DC: HDC; Rect: TRect): TImageData;
procedure FillAlpha;
asm
mov eax, Result
mov edx, [eax].TImageData.Scan0
mov ecx, [eax].TImageData.Width
imul ecx, [eax].TImageData.Height
mov eax, 0ff000000h
@@Loop:
or [edx], eax
add edx, 4
loop @@Loop
end;
var
r: TRect;
pbi: TBitmapInfo;
begin
FillChar(Result, Sizeof(TImageData), 0);
if GetClipBox(DC, r) <= NULLREGION then
Exit;
if not IntersectRect(r, r, Rect) then Exit;
Result := NewImageData(r.Right - r.Left, r.Bottom - r.Top);
Result.AlphaFlag := False;
pbi.bmiHeader := GetBitmapInfoHeader(Result);
GetDCImageData(DC, r.Left, r.Top, Result, pbi);
FillAlpha;
_InvertScan0(Result);
end;
function GetHandleImageData(Handle: HWnd; Rect: TRect): TImageData;
var
DC: HDC;
begin
DC := GetDC(Handle);
try
Result := GetImageDataFromDC(DC, Rect);
finally
ReleaseDC(Handle, DC);
end;
end;
function GetCanvasImageData(Canvas: TCanvas; Rect: TRect): TImageData;
begin
Result := GetImageDataFromDC(Canvas.Handle, Rect);
end;
function GetGpGraphicsImageData(g: TGpGraphics; Rect: TRect): TImageData;
var
DC: HDC;
begin
DC := g.GetHDC;
try
Result := GetImageDataFromDC(DC, Rect);
finally
g.ReleaseHDC(DC);
end;
end;
上面的代码提供了3个“截屏”方法,分别适合Delphi画布类TCanvas、窗后句柄和GDI+画布类TGpGraphics。
先简单介绍一下本文方法的实现原理:
3个“截屏”方法都是调用GetImageDataFromDC函数来完成的。
首先是通过Windows API CreateCompatibleBitmap建立一个与设备上下文DC兼容的位图句柄,然后调用Bitblt将有关设备的图像数据拷贝到位图中;
其次,需要把位图中的图像数据拷贝到TImageData类型的内存缓冲区。因为无论是显示屏、窗口还是其它设备,其分辨率是不尽相同的,就拿显示屏的显示质量来说,有设置为32位的,也有设置为16位的,甚至256色或更低质量的,要把它们转换为与之对应像素格式的图像,显然是很麻烦的,必须把它们转换为一种统一像素格式的图像,以便进一步分析处理,好在Windows提供了这方面的API,即GetDIBits函数,可以很好的解决这个问题。内部过程GetHBitmapData就是调用API GetDIBits来完成该项工作的。
由于Delphi图像处理系列,都是采用统一的32位图像像素格式,而GeiDIBits函数没有填充32位像素格式的Alpha分量的功能,这项功能就由内部过程FillAlpha来完成了。
获取的图像数据扫描线是Windows位图格式,即扫描线首地址是图像的最后一行,所以,必须用_InvertScan0过程把它翻转过来:让图像数据扫描线首地址指向图像的第一行,同时设置扫描线间距为负数。
截屏图像数据都必须用FreeImageData释放内存。
理论上,只要能提供以上3种类型的窗口或设备,都可将其转换为图像数据,但实际并非如此:
一、很多设备是只写的,如打印机设备,并不能反过来提供图像数据(注:我身边没有打印机设备,没做测试,只是根据经验如此认为)。即使是我们认为应该能获取图像的设备上下文句柄,也往往不能达到愿望,如GDI+的TGpBitmap,可以把一个TGpGraphics类型与之关联,按理说,通过TGpGraphics.GetDC获取的设备上下文,应该能获取TGpBitmap的图像数据,可是,我试过多次,得到的只是一个黑色填充的图像;
二、对于显示屏显示的各种窗口,也有可能得不到希望的图像数据,因为在窗口的全部或者部分被遮挡后,Windows系统是不会绘制被挡住的窗口(或者部分窗口),我们只能正确获取设备当前裁剪区域的图像,其余部分不是黑色,就是杂乱无章的,截取它们的图像数据毫无意义。所以,在内部过程GetImageDataFromDC中,获取的只是DC当前裁剪区域(用API GetClipBox计算)的图像数据。
简单应用举例,获取当前窗口的图像数据,并向右移动50后,显示在当前窗口中,也可把Canvas改为Handle:
procedure TForm1.Button4Click(Sender: TObject);
var
data: TImageData;
begin
data := GetCanvasImageData(Canvas, ClientRect);
ImageDraw(Canvas.Handle, 50, 0, data);
FreeImageData(data);
end;
代码中的ImageDraw过程见《Delphi图像处理 -- 图像显示》。
《Delphi图像处理》系列使用GDI+单元下载地址和说明见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com
这里可访问《Delphi图像处理 -- 文章索引》。