基于C#的GDI花式探索四:图像放大缩小与坐标变换

发布于:2023-10-25 ⋅ 阅读:(139) ⋅ 点赞:(0)

一、啰嗦两句

玩GDI,终究是绕不开图像的。或许最初的时候,我可以尝试将位图直接赋给pictureBox的Image来实现图像加载,可终究是个死物。往往加载出图像,基本都具有放大缩小的功能,还需要图形绘制,与GDI一起玩,怎能依赖控件自己加载图像呢?那样能走多远?

二、图像放大缩小

1.图像放大缩小的原理

没玩GDI之前,图像对我而言都是神奇的,图像放大缩小,应该是神们做出来的。玩了GDI之后,发现还是得相信科学,任何东西都有它的原理。就像动画片,最初也是一张一张的更新,形成视频。图像放大缩小也是不断更新图像,仅此而已。

2.GDI绘制图像与控件Image赋值

GDI中绘图使用DrawImage方法,将指定图像的指定区域绘制到画布的指定区域,无内存拷贝。

控件Image赋值,其实就是赋值,将位图对象赋给Image属性,是存在内存拷贝的。

只是一次绘图,差别不大,没什么可深究的。

如果我们要做放大缩小,意味着图像不断在变化,要做不知道次数的绘制,Image赋值的效率就不是那么好了,还得是GDI绘图啊。

二、坐标变换

绘图都需要坐标的,而图像尺寸与控件尺寸都不是一样的,必然要有坐标转换来保证图像显示比例。这里需要做的是,每次变化按控件尺寸计算出需要截取的图像尺寸。这个过程就像拿一个小窗口,放到一张图纸上,通过这个小窗口去截取图像上的区域。参考Windows照片查看器的效果,图片初始按比例在控件居中显示,放大先填满空的区域,再根据鼠标位置进行放大。基于这些特征,需要写一个专门计算坐标的类。其大致如下:

    public class Coordinate
    {
        public double ImageWidth; //图像宽度
        public double ImageHeight; //图像高度
        public double ControlWidth; //控件宽度
        public double ControlHeight; //控件高度
        public double Scale = 1.3; //每次缩放比例
        private bool IsWidthFill;
        public Rect ImgRect;  //截取的图像区域
        public Rect conRect;  //显示到控件的区域
        private int N = 0;  //计数
        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>
        /// 放大
        /// </summary>
        public void ZoomIn(Point2d srcpos)
        {
            if (N > 19)
                return;
            Scale = 1.3;
            N++;
            CalculateImageRect(srcpos);
        }
        /// <summary>
        /// 缩小
        /// </summary>
        public void ZoomOut(Point2d srcpos)
        {
            Scale = 1.0 / 1.3;
            N--;
            N = Math.Max(0, N);
            CalculateImageRect(srcpos);
        }
        /// <summary>
        /// 移动
        /// </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 && Math.Abs(imgRectx + ImgRect.width - 0.5 * ImageWidth) <= 0.5 * ImageWidth)
                ImgRect.x = imgRectx;
            if (Math.Abs(imgRecty - 0.5 * ImageHeight) <= 0.5 * ImageHeight && Math.Abs(imgRecty + ImgRect.height - 0.5 * ImageHeight) <= 0.5 * ImageHeight)
                ImgRect.y = imgRecty;
        }
        /// <summary>
        /// 基于指定点计算图片尺寸
        /// </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>
        /// 画布坐标转图像坐标
        /// </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>
        /// 图像坐标转画布坐标
        /// </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;
        }
    }

三、结尾

上面坐标变换可根据需要再关联世界坐标,可应用于大多数工业自动化项目。此文仅为我个人探索笔记,如有欠缺,希望能提出,好做改进。

本文含有隐藏内容,请 开通VIP 后查看