1. 程式人生 > >WinForm版影象編輯小程式(實現影象拖動、縮放、旋轉、摳圖)

WinForm版影象編輯小程式(實現影象拖動、縮放、旋轉、摳圖)

WinForm版影象編輯小程式(實現影象拖動、縮放、旋轉、摳圖)

閒暇之餘,開發一個圖片編輯小程式。程式主要特點就是可方便的對多個影象編輯,實現了一些基本的操作。本文主要介紹一下程式的功能、設計思路。

執行程式 下載地址:

百度網盤。https://pan.baidu.com/s/1cszsgjKN9ecWZ9sm1hDAdQ 

CSDN       https://download.csdn.net/download/qq_29939347/10831160

1功能介紹

程式主介面

點選開啟圖片,可選擇多個圖片檔案。圖片縮圖左側顯示,雙擊左側圖片,新增到編輯區。

圖片編輯區分為:紙張區域和列印區域。圖片只能在列印區編輯。當選中這兩個區,可調整各個區的大小。

 主要功能點:

1 拖動:選中圖片後,可以任意拖動圖片。

2 縮放:可對圖片左右上下實現縮放。可以鎖定顯示比例縮放。

3 旋轉,可以選擇旋轉基點再旋轉。如果不選擇旋轉基點,以對角為基點旋轉。

4 摳圖

 

5 其他一些操作

當有多個圖片相互覆蓋時,可以調整圖層。

選中一個圖片後,可以對圖片的位置、大小、旋轉角度調整。

選擇儲存,會將編輯的圖片儲存為檔案。

 2 處理思路

  圖片編輯資訊

 每個影象都有對應的變數記錄該影象的詳細,比如位置、尺寸、旋轉角度、剪下區域。見下面程式碼:

 

    public class ImageProperty
    {
        public string Name { get; set; }
        public Image EditImage { get; set; } //原始圖片

        public int ActualWidth => EditImage.Width; //實際尺寸
        public int ActualHeight => EditImage.Height;

        public bool ShowImageTip { get; set; } = true;

        public bool LockSizeRate { get; set; } //比例是否鎖定
        public Size DrawSize { get; set; } //顯示尺寸
        public object Tag { get; set; }
    }

    public class ImageEditInfo
    {
        public ImageProperty ImageProperty { get; set; }

        public Point Location { get; set; } = new Point(0, 0); //相對於列印區的位置
        public Point LocationTopRight => new Point(Location.X + Width, Location.Y);
        public Point LocationBottomRight => new Point(Location.X + Width, Location.Y + Height);
        public Point LocationBottomLeft => new Point(Location.X, Location.Y + Height);

        public int RightX => Location.X + Width;
        public int ButtomY => Location.Y + Height;

        public Size DrawSize
        {
            get { return ImageProperty.DrawSize; }
            set { ImageProperty.DrawSize = value; }
        }

        public Image Image => ImageProperty.EditImage;

        public float RotateAngle { get; set; } = 0; //旋轉角度

        public bool IsSelect { get; set; }

        public bool LockSizeRate  //顯示比例是否鎖定
        {
            get
            {
                return ImageProperty.LockSizeRate;
            }
            set
            {
                ImageProperty.LockSizeRate = value;
            }
        }

        public int Width
        {
            get
            {
                return DrawSize.Width;
            }
            set
            {
                ImageProperty.DrawSize = new Size(value, DrawSize.Height);
            }
        }

        public int Height
        {
            get
            {
                return DrawSize.Height;
            }
            set
            {
                ImageProperty.DrawSize = new Size(DrawSize.Width, value);
            }
        }

        public bool ShowImageTip
        {
            get { return ImageProperty.ShowImageTip; }
            set { ImageProperty.ShowImageTip = value; }
        } 

        public Point? RotatioBasePoint { get; set; } //旋轉基點

        public Point RotatioBasePointValue => RotatioBasePoint.Value;

        public bool HasRotatioBasePoint => (RotatioBasePoint != null && RotatioBasePoint.HasValue);
}

 

圖片旋轉 對正常的圖片移動、縮放並不難。只要調整影象的長寬、位置就行,基本就是加法減法計算。如果圖片有旋轉,計算起來就麻煩。比如判斷滑鼠是否點選了圖片、滑鼠縮放等,實現這些操作都麻煩。

比如判斷滑鼠是否點選了圖片,如果一個圖片是斜的(旋轉後的),如何處理?我的思路是旋轉:將圖片和滑鼠所在的點都反向旋轉;此後,判斷邏輯就和常規方法一樣了。旋轉函式如下:

 

 /// <summary>
        /// pointMove相對於removeAt,以一定角度旋轉
        /// </summary>
        /// <param name="pointMove"></param>
        /// <param name="removeAt"></param>
        /// <param name="rotateAngle"></param>
        /// <param name="clockwise"></param>
        /// <returns></returns>
        public static Point RotationAt(Point pointMove, Point removeAt, double rotateAngle, bool clockwise)
        {
            if (rotateAngle == 0)
                return pointMove;

            lock (matrix)
            {
                matrix.Reset();
                matrix.Rotate((float)(clockwise ? rotateAngle : -rotateAngle));

                Point pt2 = new Point(pointMove.X - removeAt.X, pointMove.Y - removeAt.Y);
                Point[] pts = new Point[] { new Point(pt2.X, pt2.Y) };
                matrix.TransformPoints(pts);

                Point result = new Point(pts[0].X + removeAt.X, pts[0].Y + removeAt.Y);
                return result;
            }
        }

 internal EN_LinePart MouseMove_HitTest(Point pt)
        {
            //滑鼠位置 反向旋轉,
            pt = DrawHelper.RotationAt(pt, Location, RotateAngle, false);

            //下面就是 和正常判斷邏輯一樣
            EN_LinePart result = MouseMove_HitTest_Corner(pt);
            if (result != EN_LinePart.無)
                return result;
}

 

畫圖:對圖片相關引數修改後,需要呼叫refresh,強制重畫。呼叫GDI+。根據圖片在列表的順序呼叫(也就是根據圖層)。呼叫時,根據設定顯示區域,旋轉角度等,做變換後再畫。

 

        void DrawWithRotation(Graphics g, bool saveToFile)
        {
            //設定質量
            ImageHelper.SetHighQuality(g);

            //置背景色
            if (!saveToFile)
                g.Clear(BackgroundColor);

            ImageEditInfo selectImage = null;
            foreach (ImageEditInfo imageInfo in ImageGroup.ListImageToDraw)
            {
                //畫圖片
                if (imageInfo.IsSelect)
                {
                    Debug.Assert(selectImage == null);
                    selectImage = imageInfo;
                }

                g.TranslateTransform(imageInfo.Location.X, imageInfo.Location.Y);
                g.RotateTransform(imageInfo.RotateAngle);

                //是否需要畫 摳圖
                Image imageToDraw = imageInfo.Image;
                if (imageInfo.CutStat == ImageCutStat.have_cut
                    && imageInfo.CutPoints.Count > 2)
                {
                    Bitmap bitmap = imageToDraw as Bitmap;
                    System.Windows.Point[] points = imageInfo.CutPoints.Select(o => new System.Windows.Point(o.X,o.Y)).ToArray();
                    Bitmap cutBitmap = ImageCutout.GetImage(bitmap, points);
                    imageToDraw = cutBitmap;
                }

                g.DrawImage(imageToDraw,
                      new Rectangle(0, 0, imageInfo.DrawSize.Width, imageInfo.DrawSize.Height),
                      new Rectangle(0, 0, imageInfo.Image.Width, imageInfo.Image.Height),
                      GraphicsUnit.Pixel);

                //畫旋轉基點
                if (!saveToFile && imageInfo.HasRotatioBasePoint)
                {
                    Point pt = imageInfo.RotatioBasePointValue;
                    g.FillEllipse(RotatioBaseBrush, pt.X - RotatioBaseRadius, pt.Y - RotatioBaseRadius, RotatioBaseRadius * 2, RotatioBaseRadius * 2);
                }

                //顯示資訊
                if (!saveToFile && imageInfo.ShowImageTip)
                {                
                    ImageProperty ImageProperty = imageInfo.ImageProperty;
                    string info = string.Format($"({imageInfo.Location.X},{imageInfo.Location.Y}) ({ImageProperty.ActualWidth}X{ImageProperty.ActualHeight}--{imageInfo.DrawSize.Width}X{imageInfo.DrawSize.Height}) (∠{imageInfo.RotateAngle.ToString("0.00")})");

                    SizeF sizeF = g.MeasureString(info, _drawProperty.TxtFont);
                    g.FillRectangle(_drawProperty.TxtBackgroundBrush,
                        new RectangleF(new Point(), sizeF));

                    g.DrawString(info, _drawProperty.TxtFont, _drawProperty.TxtBrush, new Point());
                }

                //畫摳圖線
                if(!saveToFile
                    && imageInfo.CutStat == ImageCutStat.in_cuting
                    && imageInfo.CutPoints.Count>1)
                {
                    for(int i=1;i< imageInfo.CutPoints.Count;i++ )
                    {
                        g.DrawLine(SelectBorderPen, imageInfo.ToDestImage(imageInfo.CutPoints[i-1]),
                            imageInfo.ToDestImage(imageInfo.CutPoints[i]));
                    }

                    if(imageInfo.CutPoints.Count > 2)
                    {
                        g.DrawLine(SelectBorderPen, imageInfo.ToDestImage(imageInfo.CutPoints.First()),
                            imageInfo.ToDestImage(imageInfo.CutPoints.Last()));
                    }
                }

                g.ResetTransform();
            }

            //畫選中狀態 
            if (!saveToFile  && selectImage != null)
            {
                DrawSelectImageWithRotation(g, selectImage);
            }
        }

 

後記:一般來講,影象的處理屬於比較難的操作。需要有空間想象能力,相應的幾何數學基礎。不過,如果掌握好了影象操作,對了解控制元件原理很有幫助。當遇到難以實現的介面,gdi+就是最後的手段;winform也是微軟過時的技術了,使用winform作圖效率很難提高;為了響應的事件,不停重畫,效率很低。WPF對影象的操作又進了一步,wpf屬於“保持模型”,就是你告訴作業系統你要畫什麼就行了,只需要告訴一次。而對於winform,作業系統不停的告訴你,你需要重畫了。這就導致winform畫圖效率比較低,但是省了記憶體。