c# WinForm制作圖片編輯工具(圖像拖動(dòng)、縮放、旋轉(zhuǎn)、摳圖)
閑暇之余,開(kāi)發(fā)一個(gè)圖片編輯小程序。程序主要特點(diǎn)就是可方便的對(duì)多個(gè)圖像編輯,實(shí)現(xiàn)了一些基本的操作。本文主要介紹一下程序的功能、設(shè)計(jì)思路。
執(zhí)行程序 下載地址:
https://pan.baidu.com/s/1cszsgjKN9ecWZ9sm1hDAdQ
1 功能介紹
程序主界面
點(diǎn)擊打開(kāi)圖片,可選擇多個(gè)圖片文件。圖片縮略圖左側(cè)顯示,雙擊左側(cè)圖片,添加到編輯區(qū)。
圖片編輯區(qū)分為:紙張區(qū)域和打印區(qū)域。圖片只能在打印區(qū)編輯。當(dāng)選中這兩個(gè)區(qū),可調(diào)整各個(gè)區(qū)的大小。
主要功能點(diǎn):
1 拖動(dòng):選中圖片后,可以任意拖動(dòng)圖片。
2 縮放:可對(duì)圖片左右上下實(shí)現(xiàn)縮放??梢枣i定顯示比例縮放。
3 旋轉(zhuǎn),可以選擇旋轉(zhuǎn)基點(diǎn)再旋轉(zhuǎn)。如果不選擇旋轉(zhuǎn)基點(diǎn),以對(duì)角為基點(diǎn)旋轉(zhuǎn)。
4 摳圖
5 其他一些操作
當(dāng)有多個(gè)圖片相互覆蓋時(shí),可以調(diào)整圖層。
選中一個(gè)圖片后,可以對(duì)圖片的位置、大小、旋轉(zhuǎn)角度調(diào)整。
選擇保存,會(huì)將編輯的圖片保存為文件。
2 處理思路
圖片編輯信息 每個(gè)圖像都有對(duì)應(yīng)的變量記錄該圖像的詳細(xì),比如位置、尺寸、旋轉(zhuǎn)角度、剪切區(qū)域。見(jiàn)下面代碼:
public class ImageProperty { public string Name { get; set; } public Image EditImage { get; set; } //原始圖片 public int ActualWidth => EditImage.Width; //實(shí)際尺寸 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); //相對(duì)于打印區(qū)的位置 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; //旋轉(zhuǎn)角度 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; } //旋轉(zhuǎn)基點(diǎn) public Point RotatioBasePointValue => RotatioBasePoint.Value; public bool HasRotatioBasePoint => (RotatioBasePoint != null && RotatioBasePoint.HasValue); }
圖片旋轉(zhuǎn)
對(duì)正常的圖片移動(dòng)、縮放并不難。只要調(diào)整圖像的長(zhǎng)寬、位置就行,基本就是加法減法計(jì)算。如果圖片有旋轉(zhuǎn),計(jì)算起來(lái)就麻煩。比如判斷鼠標(biāo)是否點(diǎn)擊了圖片、鼠標(biāo)縮放等,實(shí)現(xiàn)這些操作都麻煩。
比如判斷鼠標(biāo)是否點(diǎn)擊了圖片,如果一個(gè)圖片是斜的(旋轉(zhuǎn)后的),如何處理?我的思路是旋轉(zhuǎn):將圖片和鼠標(biāo)所在的點(diǎn)都反向旋轉(zhuǎn);此后,判斷邏輯就和常規(guī)方法一樣了。旋轉(zhuǎn)函數(shù)如下:
/// <summary> /// pointMove相對(duì)于removeAt,以一定角度旋轉(zhuǎn) /// </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) { //鼠標(biāo)位置 反向旋轉(zhuǎn), pt = DrawHelper.RotationAt(pt, Location, RotateAngle, false); //下面就是 和正常判斷邏輯一樣 EN_LinePart result = MouseMove_HitTest_Corner(pt); if (result != EN_LinePart.無(wú)) return result; }
畫(huà)圖:
對(duì)圖片相關(guān)參數(shù)修改后,需要調(diào)用refresh,強(qiáng)制重畫(huà)。調(diào)用GDI+。根據(jù)圖片在列表的順序調(diào)用(也就是根據(jù)圖層)。調(diào)用時(shí),根據(jù)設(shè)定顯示區(qū)域,旋轉(zhuǎn)角度等,做變換后再畫(huà)。
void DrawWithRotation(Graphics g, bool saveToFile) { //設(shè)置質(zhì)量 ImageHelper.SetHighQuality(g); //置背景色 if (!saveToFile) g.Clear(BackgroundColor); ImageEditInfo selectImage = null; foreach (ImageEditInfo imageInfo in ImageGroup.ListImageToDraw) { //畫(huà)圖片 if (imageInfo.IsSelect) { Debug.Assert(selectImage == null); selectImage = imageInfo; } g.TranslateTransform(imageInfo.Location.X, imageInfo.Location.Y); g.RotateTransform(imageInfo.RotateAngle); //是否需要畫(huà) 摳圖 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); //畫(huà)旋轉(zhuǎn)基點(diǎn) 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()); } //畫(huà)摳圖線(xiàn) 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(); } //畫(huà)選中狀態(tài) if (!saveToFile && selectImage != null) { DrawSelectImageWithRotation(g, selectImage); } }
后記:
一般來(lái)講,圖像的處理屬于比較難的操作。需要有空間想象能力,相應(yīng)的幾何數(shù)學(xué)基礎(chǔ)。不過(guò),如果掌握好了圖像操作,對(duì)了解控件原理很有幫助。當(dāng)遇到難以實(shí)現(xiàn)的界面,gdi+就是最后的手段;winform也是微軟過(guò)時(shí)的技術(shù)了,使用winform作圖效率很難提高;為了響應(yīng)的事件,不停重畫(huà),效率很低。WPF對(duì)圖像的操作又進(jìn)了一步,wpf屬于“保持模型”,就是你告訴操作系統(tǒng)你要畫(huà)什么就行了,只需要告訴一次。而對(duì)于winform,操作系統(tǒng)不停的告訴你,你需要重畫(huà)了。這就導(dǎo)致winform畫(huà)圖效率比較低,但是省了內(nèi)存。
以上就是c# WinForm制作圖片編輯工具(圖像拖動(dòng)、縮放、旋轉(zhuǎn)、摳圖)的詳細(xì)內(nèi)容,更多關(guān)于c# WinForm圖片編輯的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- C#使用opencv截取旋轉(zhuǎn)矩形區(qū)域圖像的實(shí)現(xiàn)示例
- C# 使用 GDI+ 實(shí)現(xiàn)添加中心旋轉(zhuǎn)(任意角度)的文字
- 利用C#代碼實(shí)現(xiàn)圖片旋轉(zhuǎn)360度
- C#實(shí)現(xiàn)計(jì)算一個(gè)點(diǎn)圍繞另一個(gè)點(diǎn)旋轉(zhuǎn)指定弧度后坐標(biāo)值的方法
- C#中圖片旋轉(zhuǎn)和翻轉(zhuǎn)(RotateFlipType)用法分析
- C#控制圖像旋轉(zhuǎn)和翻轉(zhuǎn)的方法
- C# VTK 移動(dòng)旋轉(zhuǎn)交互功能實(shí)現(xiàn)
相關(guān)文章
WCF實(shí)現(xiàn)的計(jì)算器功能實(shí)例
這篇文章主要介紹了WCF實(shí)現(xiàn)的計(jì)算器功能,結(jié)合具體實(shí)例形式較為詳細(xì)的分析了WCF實(shí)現(xiàn)計(jì)算器功能的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-06-06C#實(shí)現(xiàn)鐘表程序設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)鐘表程序設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06