阅读提示:
《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》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]
这里可访问《Delphi图像处理 -- 文章索引》。