前几篇我已经向大家介绍了如何使用GDI+来绘图,并做了一个截图的实例,这篇我向大家介绍下如何来做一个类似windows画图的工具.
个人认为如果想做一个功能强大的绘图工具,那么单纯掌握GDI还远远不够,我的目前也只能做一个比较简单的绘图工具了.不足之处,欢迎大家讨论!
先来看一下最终效果吧:

主要实现功能:画直线,矩形,橡皮,圆形,切换颜色,打开图片,保存图片,清除图片,手动调节画布大小;软件刚启动时,为一张空白画布,我们可以直接在画布上绘画,也可以通过菜单中的“打开”,导入一张图片,然后我们就可以在这张图片上进行绘制。
平台:VS2005 WINFORM
由于代码过多,在这里只简要介绍下制作步骤,提供大家工程下载.
1.对整个界面进行布局.
2.实现绘图工具的功能
3.实现颜色拾取的功能,这里我们直接拿上次写的自定义控件来用.
4.实现菜单功能
5.实现手动调节画布大小的功能
6.测试 
实现绘图工具的功能
为了让代码藕合度小点,稍许用了些设计模式,因为不是很会,所以代码还是有点乱乱的,嘿嘿!关于绘图工具的这些功能块全部写在了DrawTools这个类里.那么在主窗体中,只需要调用这个类来完成绘制就行了,而不需要过多的涉及到具体的绘图代码。绘图工具这个类提供的主要工具就是:铅笔、橡皮、直线、矩形、圆形、实心矩形、实心圆形。关于这些功能块的代码,并不难,只要大家对认真看过前几篇内容,那应该都看得懂。
这里要注意以下几点:
1.如何防止记录不必要的绘图过程中的痕迹?
这个问题在第三篇中有提到过,大家不妨先去看看那一篇。为了让代码看起来可读性高点,我设置了两个Image变量,finishingImg用来保存绘图过程中的痕迹,orginalImg用来保存已完成的绘图过程和初始时的背景图片。
2.这个类如何与主窗体进行通信?
当然如果直接将这些功能块写在主窗体中自然没有这个问题。但是那样代码会显得很混杂,如果只是工具代码出现问题就需要改整个项目。我在这里通过定义方法和属性,让主窗体通过给属性赋值将画板画布以及颜色什么的信息传给这个工具类,然后通过调用相应的工具方法来使用这些工具。
3.关键属性
要想让这些工具能正常使用,必须传递给他以下几样东西:目标画板(也就是picturebox),绘图颜色,原始画布。
实现菜单功能
  
这里就需要我们对文件的操作有一点了解,大家可以去查一下相关资料。
难点主要就是“打开”这个菜单项的实现
我们要实现将打开后的图片在修改后重新保存就必须让文件在打开后就能关闭,否则就会因为文件打开而无法覆盖原文件。就会导致编译时弹出“GDI  一般性错误”。所以根据网上其它朋友的做法就是先将打开的图片通过GDI+将图片画到另一个画布上,然后及时关闭打开的图片和用来绘制该图片的画板。

复制代码代码如下:
private void openPic_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();//实例化文件打开对话框
            ofd.Filter = "JPG|*.jpg|Bmp|*.bmp|所有文件|*.*";//设置对话框打开文件的括展名
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                Bitmap bmpformfile = new Bitmap(ofd.FileName);//获取打开的文件
                panel2.AutoScrollPosition = new Point(0,0);//将滚动条复位
                pbImg.Size = bmpformfile.Size;//调整绘图区大小为图片大小
                reSize.Location = new Point(bmpformfile.Width, bmpformfile.Height);//reSize为我用来实现手动调节画布大小用的
                //因为我们初始时的空白画布大小有限,"打开"操作可能引起画板大小改变,所以要将画板重新传入工具类
                dt.DrawTools_Graphics = pbImg.CreateGraphics();
                Bitmap bmp = new Bitmap(pbImg.Width, pbImg.Height);
                Graphics g = Graphics.FromImage(bmp);
                g.FillRectangle(new SolidBrush(pbImg.BackColor), new Rectangle(0, 0, pbImg.Width, pbImg.Height));//不使用这句话,那么这个bmp的背景就是透明的
                g.DrawImage(bmpformfile, 0, 0,bmpformfile.Width,bmpformfile.Height);//将图片画到画板上
                g.Dispose();//释放画板所占资源
                //不直接使用pbImg.Image = Image.FormFile(ofd.FileName)是因为这样会让图片一直处于打开状态,也就无法保存修改后的图片
                bmpformfile.Dispose();//释放图片所占资源
                g = pbImg.CreateGraphics();
                g.DrawImage(bmp, 0, 0);
                g.Dispose();
                dt.OrginalImg = bmp;
                bmp.Dispose();
                sFileName = ofd.FileName;//储存打开的图片文件的详细路径,用来稍后能覆盖这个文件
                ofd.Dispose();
            }
        }

清除图像其实就是用白色填充整个画布
其它的都比较简单,这就不具体讲了。
实现手动调节画布大小
网上有人说使用API,但是个人觉得还是使用其它控件帮忙比较简单,至少我们还看得懂。
思路:放置一个picturebox1(尺寸为5*5),将它固定在主画板的右下角,然后改变鼠标进入时的Cursor为箭头形状,设置鼠标按下移动时的事件,让该picturebox1 跟随鼠标移动。当鼠标松开时,将主画板的右下角坐标调整为picturebox1的坐标。
下面来看下代码:
其中的reSize就是我们用来帮忙的picturebox控件 

复制代码代码如下:
private bool bReSize = false;//是否改变画布大小
        private void reSize_MouseDown(object sender, MouseEventArgs e)
        {
            bReSize = true;//当鼠标按下时,说明要开始调节大小
        }
        private void reSize_MouseMove(object sender, MouseEventArgs e)
        {
            if (bReSize)
            {
                reSize.Location = new Point(reSize.Location.X + e.X, reSize.Location.Y + e.Y);
            }
        }
        private void reSize_MouseUp(object sender, MouseEventArgs e)
        {
            bReSize = false;//大小改变结束
            //调节大小可能造成画板大小超过屏幕区域,所以事先要设置autoScroll为true.
            //但是滚动条的出现反而增加了我们的难度,因为滚动条上下移动并不会自动帮我们调整图片的坐标。
            //这是因为GDI绘图的坐标系不只一个,好像有三个,没有仔细了解,一个是屏幕坐标,一个是客户区坐标,还个是文档坐标。
            //滚动条的上下移动改变的是文档的坐标,但是客户区坐标不变,而location属性就属于客户区坐标,所以我们直接计算会出现错误
            //这时我们就需要知道文档坐标与客户区坐标的偏移量,这就是AutoScrollPostion可以提供的
            pbImg.Size = new Size(reSize.Location.X - (this.panel2.AutoScrollPosition.X), reSize.Location.Y - (this.panel2.AutoScrollPosition.Y));
            dt.DrawTools_Graphics = pbImg.CreateGraphics();//因为画板的大小被改变所以必须重新赋值
            //另外画布也被改变所以也要重新赋值
            Bitmap bmp = new Bitmap(pbImg.Width, pbImg.Height);
            Graphics g = Graphics.FromImage(bmp);
            g.FillRectangle(new SolidBrush(Color.White), 0, 0, pbImg.Width, pbImg.Height);
            g.DrawImage(dt.OrginalImg, 0, 0);
            g.Dispose();
            g = pbImg.CreateGraphics();
            g.DrawImage(bmp, 0, 0);
            g.Dispose();
            dt.OrginalImg = bmp;
            bmp.Dispose();
        }

效果如下图(仔细看白色区域的右下角):
  
此时就可以通过拖动那个小方块来调节图片大小了。
这样,主要的问题差不多已经解决了,但还是有不足这处,欢迎大家提出宝贵的意见。

GDI4的更多相关文章

随机推荐

  1. [蓝桥杯2016初赛]卡片换位 BFS

    题目描述 你玩过华容道的游戏吗?这是个类似的,但更简单的游戏.看下面 3 x 2 的格子 +---+---+---+ | A | * | * | +---+---+---+ | B | | * | + ...

  2. Web - 实用组件

    1, vue-awesome-swiper  基于 Swiper4.适用于 Vue 的轮播组件,支持服务端渲染和单页应用. 2,  http://www.spritecow.com/     雪碧图背 ...

  3. 收藏 40 2 CPD (广告合作方式)

    CPD,Cost per day的缩写,意思是按天收费,是一种广告合作方式. 在实际的广告合作中根据行业不同还包括Cost per Download的缩写含义,意思是依据实际下载量收费.   “CPD ...

  4. CSS3-多列(column-count等)

    CSS3 多列属性 属性 描述 column-count 指定元素应该被分割的列数. column-fill 指定如何填充列 column-gap 指定列与列之间的间隙 column-rule 所有 ...

  5. 「ZJOI2013」K大数查询

    「ZJOI2013」K大数查询 传送门 整体二分,修改的时候用线段树代替树状数组即可. 参考代码: #include <cstdio> #define rg register #defin ...

  6. 题解 JSOI2010 找零钱的洁癖

    题解 JSOI2010 找零钱的洁癖 题面 BZOJ 个人体会 van全没有思路... 只能去看题解... 还是个bfs+贪心 不管怎样竟然乱搞过了... 听M_sea小姐姐说她有更正经的做法(线性规 ...

  7. [Write-up]Mr-Robot

    关于 下载地址 目标:找到3个Key 哔哩哔哩视频. 信息收集 用的是Host-only,所以网卡是vmnet1,IP一直是192.168.7.1/24 nmap -T4 192.168.7.1/24 ...

  8. LeetCode刷题--21.合并两个有序链表(简单)

    题目描述 将两个有序链表合并为一个新的有序链表并返回.新链表是通过拼接给定的两个链表的所有节点组成的. 示例: 输入:1 -> 2 -> 4 ,1 -> 3 -> 4 输出:1 ...

  9. Python Web 框架原理

    Web Socket 所谓 Web 服务,本质上就是用户使用一个 socket 客户端(浏览器)去访问一个 socket 服务端. 下面是一个最基础的基于 socket 的 Python Web 服务 ...

  10. 如何:使用 as 和 is 运算符安全地进行强制转换(C# 编程指南)

    如何:使用 as 和 is 运算符安全地进行强制转换(C# 编程指南) 由于对象是多态的,因此基类类型的变量可以保存派生类型.若要访问派生类型的方法,需要将值强制转换回该派生类型.不过,在这些情况下, ...