WinForm版图像编辑小程序(实现图像拖动、缩放、旋转、抠图)
闲暇之余,开发一个图片编辑小程序。程序主要特点就是可方便的对多个图像编辑,实现了一些基本的操作。本文主要介绍一下程序的功能、设计思路。
执行程序 下载地址:
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(, ); //相对于打印区的位置
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; } = ; //旋转角度 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 == )
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[].X + removeAt.X, pts[].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 > )
{
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(, , imageInfo.DrawSize.Width, imageInfo.DrawSize.Height),
new Rectangle(, , 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 * , RotatioBaseRadius * );
} //显示信息
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>)
{
for(int i=;i< imageInfo.CutPoints.Count;i++ )
{
g.DrawLine(SelectBorderPen, imageInfo.ToDestImage(imageInfo.CutPoints[i-]),
imageInfo.ToDestImage(imageInfo.CutPoints[i]));
} if(imageInfo.CutPoints.Count > )
{
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画图效率比较低,但是省了内存。
WinForm版图像编辑小程序(实现图像拖动、缩放、旋转、抠图)的更多相关文章
- 1个多商户、多平台版 微信小程序(多商户、多平台版),影城行业、影业连锁 多商户、多平台版微信小程序。(基于多平台版,支持在业务上 可给 每个单独影城 分发定制单独的小程序版本)
1个 影城行业 微信小程序(多商户.多平台版), 影业连锁 多商户.多平台版微信小程序.(基于多平台版,支持在业务上 可给 每个单独影城 分发定制单独的小程序版本) 资讯QQ: 876635409 ...
- WordPress版微信小程序3.5版发布
最近花时间对WordPress版微信小程序做了一些完善和调整,修复不少程序的问题.一个程序的完善是持续和渐进的,没有最好,只有更完善.虽然会采纳一些用户的建议和意见,但我会从一个产品角度去考虑,哪些功 ...
- WordPress版微信小程序3.2版发布
WordPress版微信小程序(下称开源版)距离上次更新已经过去大半年了,在此期间,我开发新的专业版本-微慕小程序(下称微慕版),同时开源版的用户越来越多,截止到2018年11月26日,在github ...
- WordPress版微信小程序3.1.5版的新功能
产品的完善是无止境,每过段时间就会发现产品的新问题,使用的人越多,提的需求也会越多,我听得最多的一句话就是:如果加上某某功能就完美了.其实,完美是不存在的,每个人的视角不一样,完美的定义也是不一样的. ...
- WordPress版微信小程序3.0版发布
距离WordPress版微信小程序上一个版本的发布过去了一个月了.在此间,我的工作有些变化,加上正在开发新版本,目前开源版的完善和升级稍稍有些滞后. 虽然这个版本是3.0版,期间有个过渡的2.8版,不 ...
- WordPress版微信小程序2.6版发布
WordPress版微信小程序的完善和升级的工作一直都在进行中,我争取保证一个月可以出一个版本,希望通过一点点的改进,让这个开源产品日趋完美. 同时,pro版WordPress微信小程序也在紧锣密鼓的 ...
- WordPress版微信小程序安装使用说明
昨天在群里,有刚刚使用WordPress版微信小程序朋友,在问安装过程中的问题,这些问题是经常被问到,这至少说明两个问题: 1.我开发的程序安装和使用不够简易,无法通过简单的配置就可以使用,特别是如果 ...
- WordPress版微信小程序2.4版发布
自从发布2017年9月16日WordPress版微信小程序2.2.8版本后,这个一个多月来,WordPress版微信小程序,在经过一些比较小的更新后,今天发布阶段性的版本:2.4版 .这版本主要是功能 ...
- 优秀WordPress版微信小程序推荐(二)
随着使用WordPress版微信小程序的用户越来越多,其中涌现不少优秀的小程序,无论UI设计还是功能上都远远超过我开源的程序.这次是推荐第二批优秀Wordpress版微信小程序,希望有更多的小程序的爱 ...
随机推荐
- JVM思考-ClassLoader.loadClasshe和Class.forName区别
JVM思考-ClassLoader.loadClasshe和Class.forName区别 目录:JVM总括:目录 见博客第四节:JVM总括四-类加载过程.双亲委派模型.对象实例化过程
- etcd-v2第三集
简单说下golang的etcd接口例子.etcd api有v2(http+json)和v3(grpc)两个版本,目前大家都用v2,所以... v2: https://github.com/coreos ...
- NFS服务简介与配置
NFS简介 NFS特点 NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源 在NFS的应用中, ...
- django学习install apps注册错了的影响
今天在学习例子的时候 不注意吧settings.py里面的INSTALL APPS 的APP应用名称写错了 应该是blog 写成了myblog 结果导致python manage.py makemi ...
- Codeforces 1077C Good Array 坑 C
Codeforces 1077C Good Array https://vjudge.net/problem/CodeForces-1077C 题目: Let's call an array good ...
- 【部署问题】解决Nginx: [error] open() "/usr/local/Nginx/logs/Nginx.pid" failed(2:No such file or directory)
问题:环境问题 解决方法: /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf 使用nginx -c的参数指定nginx.c ...
- Ubuntu 利用 crontab 和 notify-send 定时发送桌面通知,提示该休息啦
[经测试,每隔多少分钟执行并不像自己想象的一样] 比如:每隔50分钟执行一次提醒 */50 * * * * export DISPLAY=:0.0; notify-send -i /home ...
- Shell脚本学习-数组
跟着RUNOOB网站的教程学习的笔记 Shell数组 数组中可以存放多个值,Bash Shell只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与PHP类似). 与大部分编程语言类似,数 ...
- python open()函数的模式选择
python open()函数打开文件的模式详解 使用python处理文件时,避免不了要用到open()函数.我们今天主要讨论mode参数的区分. fd = open('文件名(路径)’, mode= ...
- Forward团队-爬虫豆瓣top250项目-模块测试
项目托管平台地址:https://github.com/xyhcq/top250 模块测试:爬虫对信息的处理部分 测试方法: 实际运行一下代码: 可以看见,信息都已经爬取出来了 其他补充说明: 原本系 ...