3

I'm trying to create snipping tool like this one https://app.prntscr.com/en/index.html. I'm able to modify some codes and managed to show resize handles with selected rectangle(snip), handles work fine and i can move the selected rectangle and resize it. But when I compare it with tool mentioned above it is very slow, can someone point some problems in my code to make it fast?

The tool i'm making look like this: enter image description here

    /* 
 * Credit for this portion of Imgur Snipping Tool goes to Hans Passant from stackoverflow.com
 * http://stackoverflow.com/questions/3123776/net-equivalent-of-snipping-tool
 * 
 * Modified to work with multiple monitors.
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Free_Snipping_Tool.Classes;

namespace Free_Snipping_Tool
{
    public partial class SnippingTool2 : Form
    {
        public enum ScreenshotType
        {
            FULL, ACTIVE, DEFINED
        }
        private int oldX;
        private int oldY;
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        Rectangle topLeft = new Rectangle();
        Rectangle topMiddle = new Rectangle();
        Rectangle topRight = new Rectangle();
        Rectangle bottomLeft = new Rectangle();
        Rectangle bottomMiddle = new Rectangle();
        Rectangle bottomRight = new Rectangle();
        Rectangle leftMiddle = new Rectangle();
        Rectangle rightMiddle = new Rectangle();

        bool topLeftSelected = false;
        bool topMiddleSelected = false;
        bool topRightSelected = false;
        bool bottomLeftSelected = false;
        bool bottomMiddleSelected = false;
        bool bottomRightSelected = false;
        bool leftMiddleSelected = false;
        bool rightMiddleSelected = false;
        bool rectangleSelected = false;

        [DllImport("user32.dll")]
        private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

        [DllImport("user32.dll")]
        private static extern int GetForegroundWindow();

        SnipSizeControl lbl = new SnipSizeControl();
        floatingControlRight fcR = new floatingControlRight();
        floatingControlBottom fcB = new floatingControlBottom();

        public static Image Snip(ScreenshotType type)
        {
            switch (type)
            {
                case ScreenshotType.FULL:
                    return CreateScreenshot(0, 0, SystemInformation.VirtualScreen.Width, SystemInformation.VirtualScreen.Height);

                case ScreenshotType.DEFINED:
                    SnippingTool2 snipper = new SnippingTool2(CreateScreenshot(0, 0, SystemInformation.VirtualScreen.Width, SystemInformation.VirtualScreen.Height), new Point(SystemInformation.VirtualScreen.Left, SystemInformation.VirtualScreen.Top));
                    if (snipper.ShowDialog() == DialogResult.OK)
                    {
                        return snipper.Image;
                    }
                    break;
                case ScreenshotType.ACTIVE:
                    RECT windowRectangle;
                    GetWindowRect((System.IntPtr)GetForegroundWindow(), out windowRectangle);
                    return CreateScreenshot(windowRectangle.Left, windowRectangle.Top, windowRectangle.Right - windowRectangle.Left, windowRectangle.Bottom - windowRectangle.Top);
            }
            return null;
        }

        private static Bitmap CreateScreenshot(int left, int top, int width, int height)
        {
            Bitmap bmp = new Bitmap(width, height);
            Graphics g = Graphics.FromImage(bmp);
            g.CopyFromScreen(left, top, 0, 0, new Size(width, height));
            g.Dispose();
            return bmp;
        }


        public SnippingTool2(Image screenShot, Point startPos)
        {
            InitializeComponent();

            lbl.Text = "0x0";
            lbl.Visible = true;
            lbl.BackColor = Color.Black;
            lbl.ForeColor = Color.White;
            lbl.Visible = false;
            this.Controls.Add(lbl);

            this.BackgroundImage = screenShot;
            Size s = screenShot.Size;
            this.ShowInTaskbar = false;
            this.FormBorderStyle = FormBorderStyle.None;
            this.StartPosition = FormStartPosition.Manual;
            this.Size = s;
            this.Location = startPos;
            this.DoubleBuffered = true;
        }
        public Image Image { get; set; }

        private Rectangle rcSelect = new Rectangle();
        private Point pntStart;
        bool handleSelected = false;

        protected override void OnMouseDown(MouseEventArgs e)
        {

            if ((e.X > topLeft.X) && (e.X < topLeft.X + topLeft.Width) && (e.Y > topLeft.Y) && (e.Y < topLeft.Y + topLeft.Height))
            {
                this.Cursor = Cursors.SizeNWSE;
                topLeftSelected = true;
                handleSelected = true;
            }

            else if ((e.X > topMiddle.X) && (e.X < topMiddle.X + topMiddle.Width) && (e.Y > topMiddle.Y) && (e.Y < topMiddle.Y + topMiddle.Height))
            {
                this.Cursor = Cursors.SizeNS;
                topMiddleSelected = true;
                handleSelected = true;
            }

            else if ((e.X > topRight.X) && (e.X < topRight.X + topRight.Width) && (e.Y > topRight.Y) && (e.Y < topRight.Y + topRight.Height))
            {
                this.Cursor = Cursors.SizeNESW;
                topRightSelected = true;
                handleSelected = true;
            }

            else if ((e.X > bottomLeft.X) && (e.X < bottomLeft.X + bottomLeft.Width) && (e.Y > bottomLeft.Y) && (e.Y < bottomLeft.Y + bottomLeft.Height))
            {
                this.Cursor = Cursors.SizeNESW;

                bottomLeftSelected = true;
                handleSelected = true;

            }

            else if ((e.X > bottomMiddle.X) && (e.X < bottomMiddle.X + bottomMiddle.Width) && (e.Y > bottomMiddle.Y) && (e.Y < bottomMiddle.Y + bottomMiddle.Height))
            {
                this.Cursor = Cursors.SizeNS;
                bottomMiddleSelected = true;
                handleSelected = true;
            }

            else if ((e.X > bottomRight.X) && (e.X < bottomRight.X + bottomRight.Width) && (e.Y > bottomRight.Y) && (e.Y < bottomRight.Y + bottomRight.Height))
            {
                this.Cursor = Cursors.SizeNWSE;
                bottomRightSelected = true;
                handleSelected = true;

            }

            else if ((e.X > leftMiddle.X) && (e.X < leftMiddle.X + leftMiddle.Width) && (e.Y > leftMiddle.Y) && (e.Y < leftMiddle.Y + leftMiddle.Height))
            {
                this.Cursor = Cursors.SizeWE;
                leftMiddleSelected = true;
                handleSelected = true;
            }

            else if ((e.X > rightMiddle.X) && (e.X < rightMiddle.X + rightMiddle.Width) && (e.Y > rightMiddle.Y) && (e.Y < rightMiddle.Y + rightMiddle.Height))
            {
                this.Cursor = Cursors.SizeWE;
                rightMiddleSelected = true;
                handleSelected = true;
            }
            else if ((e.X > rcSelect.X) && (e.X < rcSelect.X + rcSelect.Width) && (e.Y > rcSelect.Y) && (e.Y < rcSelect.Y + rcSelect.Height))
            {
                this.Cursor = Cursors.SizeAll;
                rectangleSelected = true;
                handleSelected = true;
            }
            else
            {
                this.Cursor = Cursors.Cross;
                topLeftSelected = false;
                topMiddleSelected = false;
                topRightSelected = false;
                bottomLeftSelected = false;
                bottomMiddleSelected = false;
                bottomRightSelected = false;
                leftMiddleSelected = false;
                rightMiddleSelected = false;
                rectangleSelected = false;
                handleSelected = false;
            }

            // Start the snip on mouse down
            if (e.Button != MouseButtons.Left) return;
            if (handleSelected == true) return;

            pntStart = e.Location;
            rcSelect = new Rectangle(e.Location, new Size(0, 0));
            lbl.Visible = true;

            lbl.Location = new Point(e.X + 10, e.Y + 10); ;
            lbl.Value = string.Format("0x0");
            lbl.Refresh();
            fcR.Visible = false;

            oldX = e.X;
            oldY = e.Y;

            this.Refresh();
        }

        protected override CreateParams CreateParams
        {
            get
            {
                var cp = base.CreateParams;
                cp.ExStyle |= 0x02000000;    // Turn on WS_EX_COMPOSITED
                return cp;
            }
        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            // Modify the selection on mouse move

            if ((e.X > topLeft.X) && (e.X < topLeft.X + topLeft.Width) && (e.Y > topLeft.Y) && (e.Y < topLeft.Y + topLeft.Height))
            {
                this.Cursor = Cursors.SizeNWSE;
            }

            else if ((e.X > topMiddle.X) && (e.X < topMiddle.X + topMiddle.Width) && (e.Y > topMiddle.Y) && (e.Y < topMiddle.Y + topMiddle.Height))
            {
                this.Cursor = Cursors.SizeNS;
            }

            else if ((e.X > topRight.X) && (e.X < topRight.X + topRight.Width) && (e.Y > topRight.Y) && (e.Y < topRight.Y + topRight.Height))
            {
                this.Cursor = Cursors.SizeNESW;
            }

            else if ((e.X > bottomLeft.X) && (e.X < bottomLeft.X + bottomLeft.Width) && (e.Y > bottomLeft.Y) && (e.Y < bottomLeft.Y + bottomLeft.Height))
            {
                this.Cursor = Cursors.SizeNESW;
            }

            else if ((e.X > bottomMiddle.X) && (e.X < bottomMiddle.X + bottomMiddle.Width) && (e.Y > bottomMiddle.Y) && (e.Y < bottomMiddle.Y + bottomMiddle.Height))
            {
                this.Cursor = Cursors.SizeNS;
            }

            else if ((e.X > bottomRight.X) && (e.X < bottomRight.X + bottomRight.Width) && (e.Y > bottomRight.Y) && (e.Y < bottomRight.Y + bottomRight.Height))
            {
                this.Cursor = Cursors.SizeNWSE;
            }

            else if ((e.X > leftMiddle.X) && (e.X < leftMiddle.X + leftMiddle.Width) && (e.Y > leftMiddle.Y) && (e.Y < leftMiddle.Y + leftMiddle.Height))
            {
                this.Cursor = Cursors.SizeWE;
            }

            else if ((e.X > rightMiddle.X) && (e.X < rightMiddle.X + rightMiddle.Width) && (e.Y > rightMiddle.Y) && (e.Y < rightMiddle.Y + rightMiddle.Height))
            {
                this.Cursor = Cursors.SizeWE;
            }
            else if ((e.X > rcSelect.X) && (e.X < rcSelect.X + rcSelect.Width) && (e.Y > rcSelect.Y) && (e.Y < rcSelect.Y + rcSelect.Height))
            {
                this.Cursor = Cursors.SizeAll;
            }
            else
            {
                this.Cursor = Cursors.Cross;
            }

            if (e.Button == MouseButtons.Left && handleSelected == true)
            {
                Rectangle backupRect = rcSelect;

                if (topLeftSelected == true)
                {
                    rcSelect.X += e.X - oldX;
                    rcSelect.Width -= e.X - oldX;
                    rcSelect.Y += e.Y - oldY;
                    rcSelect.Height -= e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }

                else if (topRightSelected == true)
                {
                    rcSelect.Width += e.X - oldX;
                    rcSelect.Y += e.Y - oldY;
                    rcSelect.Height -= e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }

                else if (topMiddleSelected == true)
                {

                    rcSelect.Y += e.Y - oldY;
                    rcSelect.Height -= e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();

                }

                else if (bottomLeftSelected == true)
                {
                    rcSelect.Width -= e.X - oldX;
                    rcSelect.X += e.X - oldX;
                    rcSelect.Height += e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
                else if (bottomMiddleSelected == true)
                {
                    rcSelect.Height += e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
                else if (bottomRightSelected == true)
                {
                    rcSelect.Width += e.X - oldX;
                    rcSelect.Height += e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
                else if (rightMiddleSelected == true)
                {
                    rcSelect.Width += e.X - oldX;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
                else if (leftMiddleSelected == true)
                {
                    rcSelect.X += e.X - oldX;
                    rcSelect.Width -= e.X - oldX;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
                else if (rectangleSelected == true)
                {
                    rcSelect.X = rcSelect.X + e.X - oldX;
                    rcSelect.Y = rcSelect.Y + e.Y - oldY;

                    lbl.Location = new Point(rcSelect.X, rcSelect.Y - 25);
                    lbl.Value = string.Format("{0}x{1}", rcSelect.Width, rcSelect.Height);
                    this.Refresh();
                }
            }
            else if (e.Button == MouseButtons.Left)
            {
                int x1 = Math.Min(e.X, pntStart.X);
                int y1 = Math.Min(e.Y, pntStart.Y);
                int x2 = Math.Max(e.X, pntStart.X);
                int y2 = Math.Max(e.Y, pntStart.Y);

                rcSelect = new Rectangle(x1, y1, x2 - x1, y2 - y1);

                this.Refresh();

                lbl.Location = new Point(x1, y1 - 25);
                lbl.Value = string.Format("{0}x{1}", x2 - x1, y2 - y1);
                lbl.Refresh();

            }

            oldX = e.X;
            oldY = e.Y;

        }
        protected override void OnMouseUp(MouseEventArgs e)
        {
            // Complete the snip on mouse-up
            if (rcSelect.Width <= 0 || rcSelect.Height <= 0) return;
            Image = new Bitmap(rcSelect.Width, rcSelect.Height);
            using (Graphics gr = Graphics.FromImage(Image))
            {
                gr.DrawImage(this.BackgroundImage, new Rectangle(0, 0, Image.Width, Image.Height),
                    rcSelect, GraphicsUnit.Pixel);
            }

            topLeftSelected = false;
            topMiddleSelected = false;
            topRightSelected = false;
            bottomLeftSelected = false;
            bottomMiddleSelected = false;
            bottomRightSelected = false;
            leftMiddleSelected = false;
            rightMiddleSelected = false;
            rectangleSelected = false;
            handleSelected = false;

            //DialogResult = DialogResult.OK;
        }
        protected override void OnPaint(PaintEventArgs e)
        {
            // Draw the current selection
            using (Brush br = new SolidBrush(Color.FromArgb(30, Color.Black)))
            {
                int x1 = rcSelect.X; int x2 = rcSelect.X + rcSelect.Width;
                int y1 = rcSelect.Y; int y2 = rcSelect.Y + rcSelect.Height;
                e.Graphics.FillRectangle(br, new Rectangle(0, 0, x1, this.Height));
                e.Graphics.FillRectangle(br, new Rectangle(x2, 0, this.Width - x2, this.Height));
                e.Graphics.FillRectangle(br, new Rectangle(x1, 0, x2 - x1, y1));
                e.Graphics.FillRectangle(br, new Rectangle(x1, y2, x2 - x1, this.Height - y2));
            }
            using (Pen pen = new Pen(Color.Red, 1))
            {
                e.Graphics.DrawRectangle(pen, rcSelect);
            }


            //Resize Controls

            //Top left
            topLeft = new Rectangle(rcSelect.X - 3, rcSelect.Y - 3, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, topLeft);
            }

            //Top middle
            topMiddle = new Rectangle((rcSelect.X - 3) + (rcSelect.Width) / 2, rcSelect.Y - 3, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, topMiddle);
            }

            //Top right
            topRight = new Rectangle((rcSelect.X - 3) + rcSelect.Width, rcSelect.Y - 3, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, topRight);
            }


            //Bottom left
            bottomLeft = new Rectangle(rcSelect.X - 3, (rcSelect.Y - 3) + rcSelect.Height, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, bottomLeft);
            }

            //Bottom middle
            bottomMiddle = new Rectangle((rcSelect.X - 3) + (rcSelect.Width) / 2, (rcSelect.Y - 3) + rcSelect.Height, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, bottomMiddle);
            }

            //Bottom right
            bottomRight = new Rectangle((rcSelect.X - 3) + rcSelect.Width, (rcSelect.Y - 3) + rcSelect.Height, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, bottomRight);
            }

            //Left middle
            leftMiddle = new Rectangle(rcSelect.X - 3, (rcSelect.Y - 3) + (rcSelect.Height) / 2, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, leftMiddle);
            }

            //Right middle
            rightMiddle = new Rectangle((rcSelect.X - 3) + rcSelect.Width, (rcSelect.Y - 3) + (rcSelect.Height) / 2, 6, 6);
            using (Brush br = new SolidBrush(Color.FromArgb(200, Color.Red)))
            {
                e.Graphics.FillRectangle(br, rightMiddle);
            }
        }
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            // Allow canceling the snip with the Escape key
            if (keyData == Keys.Escape) this.DialogResult = DialogResult.Cancel;
            return base.ProcessCmdKey(ref msg, keyData);
        }
    }
}

Edit: Project file: https://www.dropbox.com/s/k1afggj6inye4kp/Free%20Snipipng%20Tool-project.rar?dl=0

m.qayyum
  • 401
  • 2
  • 15
  • 44
  • Possibly due to the refresh on mousemove with multiple ifs which would redraw the window very frequently right? Seems like a lot of overhead. – Jacob H May 22 '18 at 14:07
  • Yeah can't think of how i can change the logic from here. – m.qayyum May 22 '18 at 14:58
  • Can you please suggest how I can improve these ifs+refresh logic? – m.qayyum May 22 '18 at 19:27
  • Sorry I don't know how! I'm pretty average with .net. I'm sure someone has a good solution for you. It possibly involves multi-threading and calling refresh() at certain time intervals? Maybe only refresh() on mouseup event? I'm really not sure what's good here. – Jacob H May 22 '18 at 19:35
  • Ah understand, Thanks these are some good points. – m.qayyum May 22 '18 at 19:48
  • 1
    The code you provide doesn't compile. Can you provide a full project? – Simon Mourier May 25 '18 at 04:53
  • @SimonMourier I have put a link to project. – m.qayyum May 25 '18 at 11:33
  • 1
    I've made a small change, basically replace Refresh by invalidating just the areas that need it (the old rectangle and the new rectangle, in fact it could even be improved not redrawing the white part of the inner rectangle, but you get the idea). You can try it here: https://gist.github.com/smourier/8d0e73a7bff39698bbd681bdd51efae5 – Simon Mourier May 25 '18 at 14:10
  • Thanks @SimonMourier this is much improved. I would like to know more about not redrawing the inner part of inner rectangle, can you direct me to any link which explains similar it would be great. Also please put this as an answer so i can accept. – m.qayyum May 25 '18 at 20:47

1 Answers1

1

I will cite the official Windows documentation. Although the doc is about Native API, Winforms uses the same underlying technology:

An application invalidates a portion of a window and sets the update region by using the InvalidateRect or InvalidateRgn function. These functions add the specified rectangle or region to the update region, combining the rectangle or region with anything the system or the application may have previously added to the update region.

The InvalidateRect and InvalidateRgn functions do not generate WM_PAINT messages. Instead, the system accumulates the changes made by these functions and its own changes. By accumulating changes, a window processes all changes at once instead of updating bits and pieces one step at a time.

Calling .NET Refresh() is equivalent of calling InvalidateAll() + Update(). InvalidateAll marks the whole screen as invalid, and Update() forces the process of redrawing what's invalid, so the whole screen. You can optimize your program if you just invalidate "manually" what you know has changed.

That's what I did in my modified sample. Instead of calling Refresh(), I added a new oldRcRect variable to be able to invalidate old state and new state, and a RefreshOnMove() method like this (and I replaced most Refresh calls by a RefreshOnMove call) :

    private void RefreshOnMove()
    {
        // invalidate the old rect (+ size of red box)
        var rc = oldRcSelect;
        rc.Inflate(3, 3);
        Invalidate(rc);

        // invalidate the new rect (+ size of red box)
        // note you can almost omit this second one, but if you move the mouse really fast, you'll see some red box not fully displayed
        // but the benefit is small, something like a 3 x width/height rectangle
        rc = rcSelect;
        rc.Inflate(3, 3);
        Invalidate(rc);

        // each time you call invalidate, you just accumulate a change
        // to the change region, nothing actually changes on the screen

        // now, ask Windows to process the combination of changes
        Update();
    }

PS: about my comment about inner region, I just mean it may be possible to avoid invalidating the content of the white box each time as well, but it's more complex.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298