Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Capture Image of TWinControl instances

twincontrol windows api graphic bitblt capture image

  • Please log in to reply
2 replies to this topic

#1 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 29 September 2012 - 06:51 AM

Capture Image of TWinControl instances

A couple hours ago I stumbled into a question asking how to capture the image of the content of a TForm or TScrollBox. Actually both can be generalized to TWinControl, since both are descendants of TWinControl. So instead of showing how to capture the image of TForm or TScrollBox specifically, this tutorial will generalize to show you how to capture the image of any TWinControl instances.

Had you read my tutorial about capturing the screen in this tutorial: How to Capture Screen with Delphi code, you will see that the technique that we will use is basically the same. I.e. we are going to get the canvas of the TWinControl then draw the content of the canvas to our TBitmap. We can then manipulate the TBitmap according to the mission at hand. Note that in this case the canvas is the windows' version of canvas, i.e. Device Context (DC). Not VCL's TCanvas. Although we can consider that they are the same.


Getting Device Context of TWinControl

To get Device Context of our TWinControl we can use Windows API GetWindowDC. We feed GetWindowDC with the window handle which device context we need. As you might already know, all TWinControl instances have Handle property with type of HWND.


Capture Content of Device Context to TBitmap

Now that we have the device context, it's time to capture its content to our TBitmap. What you need to realize first that each TBitmap actually has its own Device Context through its Canvas property. Remember that TCanvas has Handle property which type is HDC. HDC is actually handle to windows device context.

To copy the content of a Device Context to another one we can use Windows API BitBlt. For example, if we want to copy the content of Device Context vDC to a TBitmap vBmp we can use code like the following.

  BitBlt(vBmp.Canvas.Handle, 0, 0, vBmp.Width, vBmp.Height, vControlDC, 0, 0, SRCCOPY);

And that's it! Since now we have the content of the source Device Context handy in our TBitmap, we can easily use the TBitmap to further manipulate the captured image.


Capture TWinControl Image Implementation

Now that we know what we need to do, it is time to implement it into working code. We will implement this function into a procedure. This procedure will take two parameters. The first is the instance of TWinControl which image we want to capture. The second one is an instance of TGraphic to hold the captured image. This way we know that the caller is responsible to create and destroy the TGraphic.

Let's name this procedure CaptureWinCtrlImage, and here is the implementation.

procedure CaptureWinCtrlImage(AWinControl: TWinControl; AImg: TGraphic);
var
  vBmp: Graphics.TBitmap;
  vControlDC: HDC;
begin
  // if the TWinControl has not been initialized, raise error
  if not AWinControl.HandleAllocated then
    raise Exception.Create('The control''s window handle has not been allocated');

  // Get Device Context of the TWinControl
  vControlDC := GetWindowDC(AWinControl.Handle);
  try

    // create our temporary TBitmap
    vBmp := Graphics.TBitmap.Create;
    try
      vBmp.PixelFormat := pf24bit;

      // adjust the temporary TBitmap dimension to match
      // the TWinControl's
      vBmp.Height := AWinControl.Height;
      vBmp.Width  := AWinControl.Width;

      // draw the content of the TWinControl to the temporary bitmap
      BitBlt(vBmp.Canvas.Handle, 0, 0, vBmp.Width, vBmp.Height, vControlDC, 0, 0, SRCCOPY);

      // copy the image in the temporary bitmap to the given graphic instance
      AImg.Assign(vBmp);
    finally
      vBmp.Free;
    end;

  finally
    // release the Device Context
    ReleaseDC(AWinControl.Handle, vControlDC);
  end;
end;


Demo Project

Let's create a demo project to see that our CaptureWinCtrlImage routine works as expected. In this demo project we will try to capture the image of the main form and also try to capture the image of a TScrollBox contained in the main form.


Setup the Demo Project's GUI

  • Create new Delphi project. In some new versions of Delphi you may need to choose New VCL forms Application.
  • Save the project and its main form. For our demo purpose, just keep their default names (Project1 and Form1).
  • Add some controls like shown below.

    Form1_Design00.png
  • Add new form to the project. It will automatically named into Form2. Save it. Remove it from the auto-create form list.
  • Add a TScrollBox into Form2. Adjust it to nearly cover all the client area of Form2. It will be automatically named ScrollBox1.
  • Drop a TImage into ScrollBox1. It will be automatically named Image1. Set its AutoSize property to True.
  • Drop a TBitBtn to Form2. It will automatically named BitBtn1. Set its Caption to "Close", and its ModalResult to mrCancel.
  • Form2 should looks something like shown below.

    Form2_Design00.png


Coding the Demo Project

  • Fors open Form2, then add a public class procedure named ShowImage to it. Use the following code for the implementation.

    class procedure TForm2.ShowImage(AOwner: TComponent; AImg: TGraphic);
    begin
      with Self.Create(AOwner) do
      try
        Image1.Picture.Graphic := AImg;
        ShowModal;
      finally
        Free;
      end;
    end;
    
  • Let's handle the Get Image button of Form1. When clicking this button, we want the user to be presented with the current image of Form1. The image, of course, will be shown in Form2.

    • Double click Get Image button to generate its onclick event handler skeleton code. Implement the event handler using the following code.
          procedure TForm1.BitBtn1Click(Sender: TObject);
          var
            vImg: Graphics.TBitmap;
          begin
            vImg := Graphics.TBitmap.Create;
            try
              CaptureWinCtrlImage(Self, vImg);
              TForm2.ShowImage(Self, vImg);
            finally
              vImg.Free;
            end;
          end;
      
  • The Close button in Form1. When this button is clicked, we want the demo program to close. So double click it and use the following code for its onclick event handler.
    procedure TForm1.BitBtn2Click(Sender: TObject);
    begin
      Close;
    end;
    
  • The Load image to scroll box button in Form1. Here we want to allow user to select an image file then load the content to TImage in the ScrollBox. Double click this button and use the following code for its onclick event handler.

    procedure TForm1.BitBtn3Click(Sender: TObject);
    begin
      if OpenPictureDialog1.Execute then
      begin
        Edit2.Text := OpenPictureDialog1.FileName;
        Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);
      end;
    end;
    
  • The Show image of the scrollbox in Form1. When clicking this button, we want to present the user with current image of the scroll box. Like with the Get Image button, the image will be shown using Form2. So, double click this button and use the following code.

    procedure TForm1.BitBtn4Click(Sender: TObject);
    var
      vImg: Graphics.TBitmap;
    begin
      vImg := Graphics.TBitmap.Create;
      try
        CaptureWinCtrlImage(ScrollBox1, vImg);
        TForm2.ShowImage(Self, vImg);
      finally
        vImg.Free;
      end;
    end;
    


Running the Demo

I believe we have covered every key things. Now we are ready to test run the demo project. Go compile and run the demo by pressing F9 key in Delphi IDE.

Initially you will get display like the following.

Form1_Run01.png

You can change the content of editable controls (like the edit controls) and then click the Get Image. After that Form2 will be shown and it will contain the current Form1 image. You might get something like the following.

Form2_Run01.png

There! Now you can see that our capturing codes is working correctly. You can proceed to test capturing the scroll box only.


Full source code of the demo project is attached below. Feel free to use or improve it.

Attached File  Capture Form Image Demo.zip   238.58KB   672 downloads
  • 1

#2 Healwave

Healwave

    CC Lurker

  • Just Joined
  • Pip
  • 2 posts
  • Programming Language:Delphi/Object Pascal

Posted 30 September 2012 - 04:55 AM

It works like a charm, but i have a question: When i do "vimg.SaveToFile('c:\scrollbox.bmp');" , on the "get Image" button, it saves the content of the scrollbox but only of what we are seeing at the moment! I have more objects than the ones i see. I have to scroll to the right and click again get image to get the rest of the scrollbox, but it will override the old file etc etc blablabla .. so that's not what i need.

Do you know any way to save all the content of it into a image?

What i'm getting at the moment:
http://imageshack.us...7/76817185.png/

What i need:
http://imageshack.us...27/actualy.png/ + http://imageshack.us...6/84706051.png/ + http://imageshack.us...1/26601520.png/


Thanks For your time :) Healwave
  • 0

#3 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 30 September 2012 - 05:39 AM

Hi Healwave, welcome to CodeCall!

Yes, that is correct and actually the intended behavior in respect to effectivity. Mostly we don't want to "draw" something that is not visible. That would be a waste of time and resource.

Therefore I believe you have to improve the code to create somthing that captures parts of the scroll box content until you get the whole area, and then "stitch" them together. It may sounds difficult at first. But actually it's not. Simply follow the following pseudocode.
 

procedure CaptureScrollBox;
var
  x, y: Integer;
begin
  X := -ScrollBoxWidth;
  Y := -ScrollBoxHeight;
  repeat

    Y := Y + ScrollBoxHeight;
    repeat
      X := X + ScrollBoxWidth;
      ScrollBox.ScrollTo(X, Y);
      CaptureImage;
    until (X+ScrollBoxWidth >= ScrollBoxClientWidth);

  until (Y+ScrollBoxHeight >= ScrollBoxClientHeight)
        and (X+ScrollBoxWidth >= ScrollBoxClientWidth);

  StitchCapturedImages;
end;


 

-- Edit
Another option is to briefly resize the scrollbox to make all its content to be visible, capture, then resize it back to original size. You may want to do this in the background, by parenting the ScrollBox to another control with sufficient size.


Edited by LuthfiHakim, 10 February 2013 - 09:29 AM.

  • 0





Also tagged with one or more of these keywords: twincontrol, windows api, graphic, bitblt, capture image

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download