Delphi图像处理 -- 获取窗口或设备的图像数据

阅读提示:

    《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图像处理 -- 文章索引》。

 

你可能感兴趣的:(Delphi图像处理 -- 获取窗口或设备的图像数据)