Search code examples
c#coordinatesnormalization

Unable to sync picturebox locations using normalized coordinates in C#


I have a simple client program that displays a picturebox on a form. When the user left clicks on the picturebox and keeps the button held down, they can drag the picturebox to a new location on the form. Once the left mouse button is released, the program will calculate a percentage value of the X and Y coordinates and send them to the server. The goal of doing this is to create a consistent (normalized) coordinate system between all of the clients connected to the server.

The goal of creating a consistent coordinate system between all of the clients is so that if a picturebox is moved across one client, all of the other clients will see it move to the exact same position. For example, if two pictureboxes were moved close together and one of the clients made their form size much larger, I would still want the pictureboxes to be displayed next to each other.

Once a client gets the normalized coordinates from the server, it needs to be able to translate the normalized coordinates into the correct location on its form so that the picturebox location on both ends synchronizes and shows in the exact same location.

The solution I came up with works perfectly if both clients have the exact same form size. Otherwise, when one form is a different size than the other, the pictureboxes are slightly off and don't synchronize like I want them to. For example, assume client A has a smaller form and client B has a much larger form. If client A moved their picturebox near the bottom left corner, but left an inch of space between its left and bottom side, then client B would nearly be in that position, but it would have much more space to its left and bottom. This also means that if the two form sizes were the same and two pictureboxes were moved close together and then one of the clients increased their form size, they would move away from the picturebox they were supposed to be near. This is the problem I have not been able to overcome.

In order to try and make my problem easier to understand, here are some screenshots to help illustrate the problem:

I have condensed this problem into the smallest possible example here: https://pastebin.com/1igRRtKJ which will make understanding and hopefully solving the problem much easier. For those who do not wish to go off-site, I will provide snippets of the relevant code below.

Here is how I am moving the client's picturebox around the screen:

        private void PictureBox_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                prevX = e.X;
                prevY = e.Y;
            }
        }

        private void PictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                int newX = pictureBox.Location.X + (int)(e.X - prevX);
                int newY = pictureBox.Location.Y + (int)(e.Y - prevY);

                newX = Math.Max(0, Math.Min(ClientSize.Width - pictureBox.Width, newX));
                newY = Math.Max(0, Math.Min(ClientSize.Height - pictureBox.Height, newY));

                pictureBox.Location = new Point(newX, newY);

                this.Text = $"({newX}, {newY})";
            }
        }
        private void PictureBox_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {

                SendPercentageCoordinates();
            }
        }

I have tried to create a consistent coordinate system between all of the clients connected to the server by performing this calculation every time the picturebox is moved on the screen:

      private void SendPercentageCoordinates()
      {
          int actualX = pictureBox.Location.X;
          int actualY = pictureBox.Location.Y;

          double percentageX = (double)actualX / (ClientSize.Width - pictureBox.Width);
          double percentageY = (double)actualY / (ClientSize.Height - pictureBox.Height);

          string message = $"{pictureBoxId}|{percentageX}|{percentageY}";
          byte[] messageBytes = Encoding.ASCII.GetBytes(message);

          clientStream.Write(messageBytes, 0, messageBytes.Length);
      }

I have tried to translate the normalized coordinates sent by the server into the correct screen coordinates by using this method:

        private void UpdatePictureBoxPosition(double percentageX, double percentageY)
        {
            int clientWindowWidth = ClientSize.Width;
            int clientWindowHeight = ClientSize.Height;

            // Calculate the adjusted coordinates based on the current form size
            int adjustedX = (int)(percentageX * (clientWindowWidth - pictureBox.Width));
            int adjustedY = (int)(percentageY * (clientWindowHeight - pictureBox.Height));

            pictureBox.Location = new Point(adjustedX, adjustedY);
        }

I expected this code to correctly synchronize the pictureboxes for all clients connected to the server regardless of the differences in the screen sizes. However, as you can see in the screenshots I have provided, this is not the case.

The four corners of the screen normalize to:

Top left: (0,0) Bottom left: (0,1) Top right: (1, 0) Bottom right: (1, 1)

The ultimate goal is to be able to allow the clients to move their picturebox near other picture boxes and have them always appear to be next to each other with no added spacing between them, even if the form sizes are wildly different.

Edit: I added an extra screenshot, removed some incorrect details and removed some bits that were not really helpful and just made the post more difficult to understand.


Solution

  • The idea of creating a consistent coordinate system between all of the clients was definitely the right idea and my original implementation was correct. The problem was that the images also needed to be scaled relative to the screen size in order to maintain their ratios. Additionally, the program needed to maintain its aspect ratio so that the scaled images would have the correct position and distance for all screen sizes.

    With this information in mind, I designed the program to have its initial dimensions be a 16:9 ratio. I still allow users to resize the form, but only from one of the four corners, so that they are forced to update both the height and width when manually resizing the form. Doing all of this ensured that the program was always in a 16:9 ratio (or at least close enough that the distance and positions of images were still really accurate).

    So, in summary, if you read the original question and want to achieve the same results, you can think of this as a three step process:

    1. Create a consistent coordinate system between clients
    2. Scale your images in relation to the screen size
    3. Force your program to maintain the supported aspect ratio

    Here is a simplified version of the code for each of the three steps so you should be able to follow along and get this working pretty easily.

    Normalize coordinates before sending to other clients:

    double actualX = pb.Location.X;
    double actualY = pb.Location.Y;
    
    double percentageX = actualX / (ClientRectangle.Width - pb.Width);
    double percentageY = actualY / (ClientRectangle.Height - pb.Height);
    

    Translate the normalized coordinates to screen coordinates:

    double adjustedX = percentageX * (ClientRectangle.Width - pb.Width);
    double adjustedY = percentageY * (ClientRectangle.Height - pb.Height);
    

    Scale images (pictureboxes in my case):

    // For the reference size I used the default program height/width
    double scaleFactor = Math.Min(this.Width / referenceWidth, this.Height / referenceHeight);
    
    // Scale from the original image size
    pb.Width = originalWidth;
    pb.Height = originalHeight;
    
    // This will work for both scaling up and down
    pb.Width = (int)Math.Round(pb.Width * scaleFactor);
    pb.Height = (int)Math.Round(pb.Height * scaleFactor);
    

    Maintain aspect ratio:

    This isn't something I technically enforced with code. I just recommend designing your program to be in a 16:9 aspect ratio by default and then attempt to enforce this the best you can. I am sure there is a good way to do this, but I just took the easy way out and did as I described above.

    This is why the problem was so tricky for me to figure out, because it was not just one single thing causing the problem. It was a combination of them all. So, my original question only really took care of 1/3 of the problem, which is why I just couldn't get it to work the way I wanted. Once I completed the other two steps, things work exactly how I wanted them to.