2

I am using C# to develop a tool for capturing a piece of a screen and placing it into a PicBox, I am already able to get the screenshot but I am having trouble in one point. The shot that I take is being taken from the start of the screen, I want to give the coordinates(X,Y) from where the shot should start. Here is my code, any sugestions?

#region DLLIMPORTS

    enum RasterOperation : uint { SRC_COPY = 0x00CC0020 }

    [DllImport("coredll.dll")]
    static extern int BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, RasterOperation rasterOperation);

    [DllImport("coredll.dll")]
    private static extern IntPtr GetDC(IntPtr hwnd);

    [DllImport("coredll.dll")]
    private static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);

    #endregion

    public PrtScreenFrm()
    {
        InitializeComponent();
    }
    private void MakeLayout()
    {

        this.imageOriginalPicBox.Image = PrtScreenProjectCF.Properties.Resources.testImage;
        this.imageOriginalPicBox.SizeMode = PictureBoxSizeMode.StretchImage;

    }

    private void PrtScreenFrm_Load(object sender, EventArgs e)
    {

        this.MakeLayout();

    }

    private void TakeScreenShot(Point _start, Point _end)
    {

        Rectangle boundsOfShot = new Rectangle((int)_start.X, (int)_start.Y, (int)(_end.X - _start.X), (int)(_end.Y - _start.Y));

        IntPtr hdc = GetDC(IntPtr.Zero);
        Bitmap shotTook = new Bitmap(boundsOfShot.Width, boundsOfShot.Height, PixelFormat.Format16bppRgb565);

        using (Graphics draw = Graphics.FromImage(shotTook))
        {

            IntPtr dstHdc = draw.GetHdc();
            BitBlt(dstHdc, 0, 0, boundsOfShot.Width, boundsOfShot.Height, hdc, 0, 0,
            RasterOperation.SRC_COPY);
            draw.ReleaseHdc(dstHdc);
        }

        imagePrintedPicBox.Image = shotTook;
        imagePrintedPicBox.SizeMode = PictureBoxSizeMode.StretchImage;

        ReleaseDC(IntPtr.Zero, hdc);

    }

    private void takeShotMenuItem_Click(object sender, EventArgs e)
    {

        Point start = new Point(3, 3);    //here I am forcing the rectangle
        Point end = new Point(237, 133);  //to start where the picBox starts 
        TakeScreenShot(start, end);       //but it doesn't work

    }

Thanks a lot, I am probably missing something silly, give me a light please.

  • `CopyFromScreen` is a method of `Graphics` and is easier to use. The `nXDest` and `nYDest` should actually be 0,0 to put the copied area on the very corner of the destination image `shotTook`. It is the `nXSrc` and `nYSrc` that control where in the screen you want the captured area to begin. – SimpleVar Oct 24 '15 at 13:39
  • You are actually putting 0's into all of those parameters when you call `BitBlt` – SimpleVar Oct 24 '15 at 13:45
  • @YoryeNathan I tryed with the ".CopyFromScreen()" but I don't know why it isn't recognized. I think it is because it is compact framework (maybe). – Marcelo Vinícius de Paula Oct 24 '15 at 14:54

2 Answers2

4

The problem is that your BitBlt is specifying (0,0) as the source X-Y position. Changing this line:

BitBlt(dstHdc, 0, 0, boundsOfShot.Width, boundsOfShot.Height, hdc, 
       0, 0, RasterOperation.SRC_COPY);

to

BitBlt(dstHdc, 0, 0, boundsOfShot.Width, boundsOfShot.Height, hdc, 
       _start.X, _start.Y, RasterOperation.SRC_COPY);

Fixes the issue with the code.

If you need the screen location of one of your controls, which is different than the Location property, you can use:

Point _start = this.imageOriginalPicBox.PointToScreen(this.imageOriginalPicBox.Location);

The solution above works for the compact framework, but its a little bit over-complicated if you are using the full framework. For completeness' sake, here's two better ways to go about it.

As pointed out by Yorye Nathan in comments, a simpler approach is to use the CopyFromScreen method of the Graphics class. This removes all of the p/Invokes, and reduces the capture code to:

Rectangle boundsOfShot = new Rectangle(_start.X, _start.Y,
                                       _end.X - _start.X, _end.Y - _start.Y);

Bitmap shotTook = new Bitmap(boundsOfShot.Width, boundsOfShot.Height,
                             PixelFormat.Format16bppRgb565);

using (Graphics draw = Graphics.FromImage(shotTook))
{
    draw.CopyFromScreen(_start, new Point(0, 0), boundsOfShot.Size);
}

Alternatively, if you're trying to capture a bitmap of one of your own controls:

Bitmap b = new Bitmap(this.imageOriginalPicBox.Width, this.imageOriginalPicBox.Height);
this.imageOriginalPicBox.DrawToBitmap(b, 
      new Rectangle(new Point(0, 0), this.imageOriginalPicBox.Size));    
Community
  • 1
  • 1
theB
  • 6,450
  • 1
  • 28
  • 38
  • Yes, that what I thought on the begginning, but I tryed it and the result is the same. Maybe I didn't explained well my problem, the matter is that the rectangle gets the points (0,0) from the screen and I wanted to get the ImageOriginalPicBox origin location. Anyway, thanks a lot, at least now I know my thoughts weren't wrong. – Marcelo Vinícius de Paula Oct 24 '15 at 15:01
  • @Marcelo - Added some information about how to get the location of a control in screen coordinates. – theB Oct 24 '15 at 15:38
1

I got it, theB's answer was almost correct, the matter is that I had to use what he said plus use the .PointToScreen(Point.Empty) while passing the points to the constructor. So, instead of:

private void takeShotMenuItem_Click(object sender, EventArgs e)
{

    Point start = new Point(3, 3);   
    Point end = new Point(237, 133);  
    TakeScreenShot(start, end);       

}

I needed to call the method as:

private void takeShotMenuItem_Click(object sender, EventArgs e)
    {

        Point start = imageOriginalPicBox.PointToScreen(Point.Empty);
        Point end = new Point(imageOriginalPicBox.PointToScreen(Point.Empty).X + imageOriginalPicBox.Width, imageOriginalPicBox.PointToScreen(Point.Empty).Y + imageOriginalPicBox.Height);
        TakeScreenShot(start, end);

    }

And now it works fine. Thanks for the help guys. You make coding so much easier.