畫圖板程式

來源:互聯網
上載者:User

 

最近做一些影像處理相關的東西 還是有那麼一點點心得分享下

第一部分:

做一個畫圖板程式類windows裡的mspaint
首先是想到怎麼把準系統實現 鉛筆阿 畫線阿 畫圓 畫框阿 啥的,為了突出咱的不同 咱做一個對圖形進行拖動 調整大小的功能 。
要對圖形重定義 那麼要對映像儲存中繼資料以便以後調整 說得忽悠點就是序列化啥的 磚家們經常這樣講
畫線 鉛筆 畫圓 畫框 這些在.net的graphic裡調用都一句話的事 然後在onpaint裡進行重繪就可以了。
明白原理自己去實現也不是不可以 以前我寫過一篇在winfrom裡畫直線的文章 只是我們沒有必要再去造輪子。

我們先弄一個工具列 整個工具轉場效果 先拖個menustrip控制項 然後加5個功能表項目:


寫menustrip的itemclicked事件的代碼, 注意不是功能表項目的click事件:

//工具轉場效果private void menuStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e){    for (int i = 0; i < menuStrip1.Items.Count; i++)    {        menuStrip1.Items[i].BackColor = Color.FromName("Control");    }    e.ClickedItem.BackColor = Color.Green;}

 然後再聲明一個枚舉類型 枚舉5種工具,單擊功能表項目的時候進行切換:

private void 畫線ToolStripMenuItem_Click(object sender, EventArgs e){    type = toolType.line;}private void 鉛筆ToolStripMenuItem_Click(object sender, EventArgs e){    type = toolType.pencil;}toolType type;enum toolType{    line, pencil,rec}

 然後是畫的過程,咱不能直接在表單上畫 咱得儲存中繼資料阿,線段嗎暫時用 Ilist<Point> 就可以 鉛筆用graphicPath 矩形就用 Ilist<Rectangle>
畫的時候根據滑鼠 按下->拖動->釋放 把圖形添加到後台。onpaint事件的時候把圖形重現。
下面是一些代碼 我說過畫線畫框那些在.net裡都是一句話調用的事,鉛筆工具稍微複雜點 在最後面有說明:

private void Form1_Paint(object sender, PaintEventArgs e){    //for (int i = 0; i < line.Count; i += 1)    //{    //    if (i >= line.Count - 1)    //        break;    //    e.Graphics.DrawLine(Pens.Red, line[i], line[i + 1]);    //}    for (int i = 0; i < lines.Count; i += 2)    {        if (i >= lines.Count - 1)            break;        e.Graphics.DrawLine(Pens.Red, lines[i], lines[i + 1]);    }    foreach (Rectangle rec in recs)    {        e.Graphics.DrawRectangle(Pens.Green, rec);    }}private void Form1_MouseMove(object sender, MouseEventArgs e){    //line.Add(e.Location);    Invalidate();}IList<Point> lines = new List<Point>();IList<Rectangle> recs = new List<Rectangle>();private void Form1_MouseDown(object sender, MouseEventArgs e){    startMouse = e.Location;}Point startMouse;private void Form1_MouseUp(object sender, MouseEventArgs e){    switch (type)    {        case toolType.line: {            lines.Add(startMouse);            lines.Add(e.Location);            break;        }        case toolType.rec:            {                recs.Add(new Rectangle(startMouse, new Size                    (e.Location.X - startMouse.X, e.Location.Y - startMouse.Y)));                break;            }    }    Invalidate();}

 並且矩形這裡也只對從左上起點到右下結束的這種情況作了處理 其他的情況沒考慮,在完整代碼裡功能是實現了的。
還有就是怎麼沒有“即時效果” 就是畫線斷跟畫框的拖拽效果,在onpaint最後加上這兩句就可以了:

if (!mouseDown)    return;if (type == toolType.line)    e.Graphics.DrawLine(Pens.Red, startMouse, tmpMouse);if (type == toolType.rec)    e.Graphics.DrawRectangle(Pens.Green, new Rectangle(startMouse, new Size                    (tmpMouse.X - startMouse.X, tmpMouse.Y - startMouse.Y)));

 當然得加一個mouseDown的bool變數 讓他在滑鼠按下時為true 彈起時為false,再加一個tmpMouse的 Point變數  在滑鼠移動時給他賦值。
以上所有源碼下載 猛擊此處
噹噹噹噹 現在一個畫圖板出來了吧基本上可以用了吧 挖哈哈 (¯▽¯;)


對了拖動功能呢?這個嘛其實很簡單, 如果當前是對象選擇工具(最後一個按鈕) 在滑鼠按下時把那個座標拿到所有對象裡去檢測 如果有符合的 選定那個圖形。
檢測的方式嘛 自己去搞咯, 看某個點是否在一條線斷上我那個 畫直線 的文章裡有 第二部分的源碼裡也有。檢測矩形就更簡單啦
滑鼠移動的時候看當前是否檢測並選擇了對象 如果有 拖動就是啦,拖動的過程 這還不簡單 更改當前對象的座標就是啦 point.offset 根據當前滑鼠位置改 不要告訴我你不會哈。
這些所有的在第二部分的源碼裡都有。

第二部分:

程式不是完了嗎,為什麼還要有第二部分,看完就知道了 並且後面的更精彩 嘿嘿,並且第二部分所有代碼都是重新寫的。
上面那樣依然不是好的結構 為了更系統些包括以後功能的擴充 程式結構的清晰 ,我們得用物件導向的方式去設計它 把 資料模型 代碼邏輯分離, 功能的分類 定義好
想象一下上中學幾何課的時候在黑板上畫圖,比如這是一個系統。它有黑板(Board類)這個對象對不 它是所有圖形的承載體 包括背景色 粉筆 當前正在畫的圖形 等,
圖形是預定好的 有三角形 矩形 各種圖形(Circle line pencil square)
但是他們都有相同的特性比如在黑板上的位置 線條的顏色 填充顏色 他們有共同的基類(Shape) 這點我們可以通過繼承來實現:

來複習下最基本的繼承以及virtual方法實現:

class Program{    static void Main(string[] args)    {        IList<father> fathers = new List< father>();        fathers.Add(new son());        fathers.Add(new son());        fathers[0].method();        fathers[0].method2();    }}class father{    public virtual void method()    {        Console.WriteLine("father's method");    }    public virtual void method2()    {        Console.WriteLine("father's method2");    }}class son : father {    public override void method()    {        Console.WriteLine("son's method");        //base.method();    }}

輸出:
son's method
father's method2
說到底virtual跟override就是物件導向的精華所在, 子類實現了調用子類的 子類未實現調用父類的。 包括VC++的MFC到處都用到了這種模式。
原來從C轉到C++的也根本沒得這麼多亂七八糟的物件導向設計的理論,只是有這麼一個概念而已。

這是表現圖形及方法實現部分代碼,這麼做的目的就是為了對資料模型進行定義 便於擴充,比如我以後沒有畫矩形的需求了 可以把矩形那個類刪掉
以後想畫五角星了 可以增加一個五角星的類 繼承Shape。比如每種圖形都有相同的背景色。可以在Shape 定義showBackground()供每個繼承的對象調用。
而不用每個都重新實現這個方法。

#region 圖形基類public  class Shape{    public Rectangle region;//圖形的大小及位置    public int lineAlpha=100;//透明度    public int layer=1;//層疊位置    public Pen line = new Pen(Color.Red, 1);//線段屬性    public Color bgColor = Color.Lavender;//背景色    public int bgColorAlpha = 50;    public Point mouseStart = Point.Empty;//滑鼠起點    public Point mouseEnd = Point.Empty;//滑鼠終點    public bool selected = false;    public Shape() { }    public Shape(Color _lineColor, Point _mouseStart)    {        line.Color = _lineColor; this.region = new Rectangle(); region.Location = _mouseStart; region.Width = 0; region.Height = 0;     }    //繪製(其實就是資料更新)    public virtual void draw(Point p){}    public virtual void move(int x, int y)//根據位移量移動    {        region.Location.Offset(x, y);    }    public virtual bool select(Point p)    {        return false;    }                public virtual void show(Graphics handle)//顯示    {        handle.DrawRectangle(line, this.region);    }}#endregion#region 線段public class Line : Shape{        //通過兩點初始化一條線段    public Line(Point _start, Point _end)    {        this.mouseStart = _start; this.mouseEnd = _end;        region.X = mouseEnd.X > mouseStart.X ? mouseStart.X : mouseStart.X - (mouseStart.X - mouseEnd.X);        region.Y = mouseEnd.Y > mouseStart.Y ? mouseStart.Y : mouseStart.Y - (mouseStart.Y - mouseEnd.Y);        region.Width = Math.Abs(mouseStart.X - mouseEnd.X);        region.Height = Math.Abs(mouseStart.Y - mouseEnd.Y);    }    public Line() { }    //通過一點初始化一條線段(起點跟終點同一個座標不能稱之為線段 準確的說應該是點)    public Line(Point _start)    {        line.Color = Color.Red;        this.mouseStart = _start; this.mouseEnd = Point.Empty;        region.X = mouseStart.X;        region.Y = mouseStart.Y;        region.Width = 0;        region.Height = 0;    }    //繪製(對兩個點進行資料更新)    public override void draw(Point p)    {        if (mouseStart == Point.Empty)        { this.region = new Rectangle(); region.Location = p; region.Width = 0; region.Height = 0; }        else //if (mouseEnd == Point.Empty)        {            mouseEnd = p;            region.X = mouseEnd.X > mouseStart.X ? mouseStart.X : mouseStart.X - (mouseStart.X - mouseEnd.X);            region.Y = mouseEnd.Y > mouseStart.Y ? mouseStart.Y : mouseStart.Y - (mouseStart.Y - mouseEnd.Y);            region.Width = Math.Abs(mouseStart.X - mouseEnd.X);            region.Height = Math.Abs(mouseStart.Y - mouseEnd.Y);        }    }    //對當前執行個體進行顯示(根據起始座標畫一條直線)    public override void show(Graphics handle)    {        handle.DrawLine(line, mouseStart, mouseEnd);    }    //移動    public override void move(int x, int y)    {        mouseStart.Offset(x, y);        mouseEnd.Offset(x, y);        region.Location.Offset(x, y);    }         public override bool select(Point mouse)    {        Util u = new Util();        if (u.linedetector(mouse, mouseStart, mouseEnd))        {            selected = true;            return true;        }        selected = false;        return false;    }}#endregion

 
跟電腦相關的東西 特別是程式開發,有些東西是要搞清楚原理的 原理弄懂了 什麼都好辦 ,不要只浮於表面的一些啥工具啊技術的 發現培訓機構出來的都有點這種。

最菜的時候弄鉛筆工具 我想用滑鼠跟隨的方式不斷drawRectangle 這樣就可以把滑鼠筆跡畫出來了,最後發現滑鼠是跟不上的 畫出來斷斷續續的,
我弄這個之前也沒有看過其他人的代碼,想想也確實應該這樣實現啊。
原來的鉛筆工具代碼。。。

private void Form1_MouseMove(object sender, MouseEventArgs e){    Graphics g = Graphics.FromHwnd(this.Handle);    g.DrawRectangle(Pens.Red, new Rectangle(e.Location, new Size(2, 2)));}

 
這個看上去有點像橡皮擦哈
百度上搜的東西都是廢的。始終找不到原因 到底是滑鼠沒有捕捉到那個點麼,事實上滑鼠的每一個移動的座標系統底層都是會捕捉到的 只是畫的速度跟不上,
滑鼠移動慢點還好 你說移動快了刷刷的 不斷的drawRectangle多費系統資源啊 怎麼可能跟得上 可能它也是根據CPU時間片來的唄 所以有些就漏掉了唄。
可能有些原理性的東西想想大概也就明白了  但是你得去琢磨啊 否則做什麼程式開發,現在做程式設計的早沒有以前C語言時候那些人的認真跟謹慎了
一天費勁心思去研究那些數學演算法 譚大師的《C程式設計》是本好書 有難度但是不是太難 只要認真看都還是看得懂的。
後來知道了有graphicsPath這個東西做什麼用的聽名字就知道啊 然後在滑鼠移動的時候不斷的graphicsPath.addLine 把滑鼠移動的路徑用graphicsPath連起來
果然一試就成功 就跟Windows裡的畫圖板一模一樣 我想他那裡面也是採用這種方式吧。現在鉛筆工具代碼 注意沒有用到graphicpath

private void Form1_Paint(object sender, PaintEventArgs e){    for (int i = 0; i < line.Count; i += 1)    {        if (i >= line.Count - 1)            break;        e.Graphics.DrawLine(Pens.Red, line[i], line[i + 1]);    }}private void Form1_MouseMove(object sender, MouseEventArgs e){    line.Add(e.Location);    Invalidate();}IList<Point> line = new List<Point>();

 

如果迴圈裡換成 i+=2 則變成虛線,並且值越大虛得越凶 有意思吧 (¯▽¯;)  。實際上多邊形也可以用這種方式來做 數組裡兩個一組兩個一組的point值 明白賽。

別忘了加上雙緩衝代碼

this.SetStyle(ControlStyles.UserPaint, true);this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 禁止擦除背景.this.SetStyle(ControlStyles.DoubleBuffer, true);

對映像指定部分進行清除 達到橡皮擦的效果想起來好像很複雜 其實你反過來想不就得了吧,橡皮擦是一種畫筆 一種特殊的畫筆 背景色的畫筆。

還後onPaint的概念自MFC裡就有的並不是winfrom裡才有 這是Windows表單的一個重要概念 必須弄明白。
本來參數e裡就有e.graphics  以前看到網上在滑鼠移動時graphics g=graphics.fromhandle(this.handle)  就一直這樣用 結果就是老是一閃一閃的,
這次在onpaint直接用e.graphics就不閃了。可見onpaint函數是直接接管底層的windwos重繪訊息 有特別作用的 是不一樣的。有.net了 有winform了 方便了
所有的都是基於託管平台編程了 我們不用去處理底層windows訊息了 於是我們不明白原理了。

還有就是做這種繪圖或者即時性效果的代碼裡邊 效率很重要,因為要不斷的重繪不斷的重繪可能每秒鐘那段代碼都被運行無數次了。
代碼需要最佳化 包括各種條件判斷 這樣可以提高效率 一條 確保在真正需要的時候代碼才會被執行 學校裡那些爛代碼就不要整到裡面了 都是從爛代碼過來的唄。
程式的設計也是很重要的 本人從不在winform裡整些亂七八糟的代碼 winfrom裡只負責調用  哪個類繼承哪個類  程式結構整整潔潔的 多好。

又講了這麼多沒用的

最後是畫圖板最終程式源碼 猛擊此處 怕有的朋友運行不起是在.net2.0模式下編譯的,這裡就是個學習的地方 別藏著掖著 整個東西出來還不給源碼給別人研究 多煩啊
如果有用了咱的代碼的請留個名兒哈。

程式還有諸多未完善的地方比如橢圓地區的判定 變形 等 望改進。橢圓的拖動功能暫未實現 其他的都OK。
還有拖動時圖形抓取的容差是5個像素 針對線段  抓取拖動的過程沒有任何顏色跟游標的變化 。同鞋們注意咯 免得說俺功能沒實現哈。

其實畫圖板不難 真諦不難 就算你要做成向量的 可進行後處理的 可拖動的 變形的 多圖層的 就算做到photoshop那樣 也不是什麼很困難的事 大家可以看到在《圖片裁剪效果》的文章裡alpha透明效果圓角矩形等等我都已經實現了。這個實際上不算影像處理哈 ,要做專門的圖象處理需要有專門的理論 演算法  高數 平面幾何等等 本人承認在這方面還欠缺 還需努力學習 加油。

又是熬夜寫這個鬼東東 靠 五點多了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.