GDI Fancy Exploration Based on C# Four: Image Zoom and Coordinate Transformation

1. Two lengthy sentences

When playing with GDI, you cannot avoid images after all. Maybe at the beginning, I could try to assign the bitmap directly to the Image of the pictureBox to load the image, but it is a dead thing after all. Often when loading images, they basically have the function of zooming in and out, and also require graphics drawing. How can we rely on controls to load images by themselves when playing with GDI? How far can that go?

2. Image zoom in and out

1. Principle of image enlargement and reduction

Before I played with GDI, images were magical to me. The zooming in and out of images should be made by gods. After playing GDI, I found that I still have to believe in science, everything has its principles. Just like a cartoon, it was initially updated one by one to form a video. Zooming in and out of the image also updates the image continuously, nothing more.

2.GDI drawing image and control Image assignment

Drawing in GDI uses the DrawImage method to draw the specified area of the specified image to the specified area of the canvas without memory copying.

Control Image assignment is actually assignment. When a bitmap object is assigned to the Image property, there is a memory copy.

It’s just a drawing, not much difference, nothing to delve into.

If we want to zoom in or out, it means that the image is constantly changing and we have to draw an unknown number of times. The efficiency of Image assignment is not that good, and GDI drawing is required.

2. Coordinate transformation

Drawing requires coordinates, and the image size and control size are not the same. There must be coordinate conversion to ensure the image display ratio. What needs to be done here is to calculate the image size that needs to be intercepted according to the size of the control for each change. This process is like taking a small window, placing it on a drawing, and using this small window to capture an area on the image. Referring to the effect of Windows Photo Viewer, the image is initially displayed proportionally in the center of the control. When zooming in, the empty area is first filled and then zoomed in based on the mouse position. Based on these characteristics, it is necessary to write a class specifically for calculating coordinates. It’s roughly as follows:

 public class Coordinate
    {
        public double ImageWidth; //Image width
        public double ImageHeight; //Image height
        public double ControlWidth; //Control width
        public double ControlHeight; //Control height
        public double Scale = 1.3; //Each scaling ratio
        private bool IsWidthFill;
        public Rect ImgRect; //Intercepted image area
        public Rect conRect; //area displayed to the control
        private int N = 0; //count
        private int matchNum = 0;

        public void Init(double imgWidth, double imgHeight, double controlWidth, double controlHeight)
        {
            ImageWidth = imgWidth;
            ImageHeight = imgHeight;
            ControlWidth = controlWidth;
            ControlHeight = controlHeight;
            IsWidthFill = true;
            matchNum = (int)(Math.Log(ImageWidth * controlHeight / controlWidth / ImageHeight, 1.3)) + 1;
            conRect = new Rect();
            conRect.x = 0;
            conRect.width = controlWidth;
            conRect.height = ImageHeight / ImageWidth * controlWidth;
            conRect.y = 0.5 * (controlHeight - conRect.height);
            if (imgWidth / imgHeight < controlWidth / controlHeight)
            {
                IsWidthFill = false;
                matchNum = (int)(Math.Log(ImageHeight * controlWidth / controlHeight / ImageWidth, 1.3)) + 1;
                conRect.y = 0;
                conRect.height = controlHeight;
                conRect.width = ImageWidth / ImageHeight * controlHeight;
                conRect.x = 0.5 * (controlWidth - conRect.width);
            }
            N = 0;
            ImgRect = new Rect(0, 0, imgWidth, imgHeight);
        }
        /// <summary>
        /// Zoom in
        /// </summary>
        public void ZoomIn(Point2d srcpos)
        {
            if (N > 19)
                return;
            Scale = 1.3;
            N + + ;
            CalculateImageRect(srcpos);
        }
        /// <summary>
        /// Zoom out
        /// </summary>
        public void ZoomOut(Point2d srcpos)
        {
            Scale = 1.0 / 1.3;
            N--;
            N = Math.Max(0, N);
            CalculateImageRect(srcpos);
        }
        /// <summary>
        /// move
        /// </summary>
        /// <param name="offset"></param>
        public void Move(Point2d offset)
        {
            double resolution = ImgRect.width / conRect.width;
            if (!IsWidthFill)
                resolution = ImgRect.height / conRect.height;
            double imgRectx = ImgRect.x - offset.x * resolution;
            double imgRecty = ImgRect.y - offset.y * resolution;
            if (Math.Abs(imgRectx - 0.5 * ImageWidth) <= 0.5 * ImageWidth & amp; & amp; Math.Abs(imgRectx + ImgRect.width - 0.5 * ImageWidth) <= 0.5 * ImageWidth)
                ImgRect.x = imgRectx;
            if (Math.Abs(imgRecty - 0.5 * ImageHeight) <= 0.5 * ImageHeight & amp; & amp; Math.Abs(imgRecty + ImgRect.height - 0.5 * ImageHeight) <= 0.5 * ImageHeight)
                ImgRect.y = imgRecty;
        }
        /// <summary>
        /// Calculate the image size based on the specified point
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        private void CalculateImageRect(Point2d srcpos)
        {
            if(N==0)
            {
                conRect.x = 0;
                conRect.width = ControlWidth;
                conRect.height = ImageHeight / ImageWidth * ControlWidth;
                conRect.y = 0.5 * (ControlHeight - conRect.height);
                if (!IsWidthFill)
                {
                    conRect.y = 0;
                    conRect.height = ControlHeight;
                    conRect.width = ImageWidth / ImageHeight * ControlHeight;
                    conRect.x = 0.5 * (ControlWidth - conRect.width);
                }
                ImgRect = new Rect(0, 0, ImageWidth, ImageHeight);
                return;
            }
            Point2d imgPos = ScreenToImage(srcpos);

            if (N == matchNum)
            {
                if(IsWidthFill)
                {
                    ImgRect.width = ImageHeight / ControlHeight * ControlWidth;
                    ImgRect.height = ImageHeight;
                    ImgRect.x = 0.5 * (ImageWidth - ImgRect.width);
                    ImgRect.y = 0;
                }
                else
                {
                    ImgRect.width = ImageWidth;
                    ImgRect.height = ImageWidth / ControlWidth * ControlHeight;
                    ImgRect.x = 0;
                    ImgRect.y = 0.5 * (ImageHeight - ImgRect.height);
                }
                conRect = new Rect(0, 0, ControlWidth, ControlHeight);
            }
            if (N < matchNum)
            {
                if(IsWidthFill)
                {
                    ImgRect.width /= Scale;
                    ImgRect.height = ImageHeight;
                    ImgRect.x = 0.5 * (ImageWidth - ImgRect.width);
                    ImgRect.y = 0;
                    conRect.x = 0;
                    conRect.width = ControlWidth;
                    conRect.height = ImgRect.height / ImgRect.width * ControlWidth;
                    conRect.y = 0.5 * (ControlHeight - conRect.height);
                }
                else
                {
                    ImgRect.width = ImageWidth;
                    ImgRect.height /= Scale;
                    ImgRect.x = 0;
                    ImgRect.y = 0.5 * (ImageHeight - ImgRect.height);
                    conRect.y = 0;
                    conRect.height = ControlHeight;
                    conRect.width = ImgRect.width / ImgRect.height * ControlHeight;
                    conRect.x = 0.5 * (ControlWidth - conRect.width);
                }
            }
            if (N > matchNum)
            {
                ImgRect.width /= Scale;
                ImgRect.height /= Scale;
                conRect = new Rect(0, 0, ControlWidth, ControlHeight);
                ImgRect.x = imgPos.x - (srcpos.x - conRect.x) / conRect.width * ImgRect.width;
                ImgRect.y = imgPos.y - (srcpos.y - conRect.y) / conRect.height * ImgRect.height;
            }

            ImgRect.x = Math.Min(Math.Max(0, ImgRect.x), ImageWidth - ImgRect.width);
            ImgRect.y = Math.Min(Math.Max(0, ImgRect.y), ImageHeight - ImgRect.height);
        }
        /// <summary>
        /// Convert canvas coordinates to image coordinates
        /// </summary>
        /// <param name="srcPos"></param>
        /// <returns></returns>
        public Point2d ScreenToImage(Point2d srcPos)
        {
            Point2d imgPos = new Point2d();
            imgPos.x = (srcPos.x - conRect.x) / conRect.width * ImgRect.width + ImgRect.x;
            imgPos.y = (srcPos.y - conRect.y) / conRect.height * ImgRect.height + ImgRect.y;
            return imgPos;
        }
        /// <summary>
        /// Convert image coordinates to canvas coordinates
        /// </summary>
        /// <param name="imgPos"></param>
        /// <returns></returns>
        public Point2d ImageToScreen(Point2d imgPos)
        {
            Point2d srcPos = new Point2d();
            srcPos.x = (imgPos.x - ImgRect.x) / ImgRect.width * conRect.width + conRect.x;
            srcPos.y = (imgPos.y - ImgRect.y) / ImgRect.height * conRect.height + conRect.y;
            return srcPos;
        }
    }

3. Ending

The above coordinate transformation can be associated with world coordinates as needed and can be applied to most industrial automation projects. This article is just my personal exploration notes. If there are any deficiencies, I hope you can point them out so that improvements can be made.