【问题标题】:how to undo the paint operation using c#如何使用c#撤消绘制操作
【发布时间】:2011-06-28 08:43:29
【问题描述】:

我在图片框中加载了一个图像。我通过 mouseclick 事件对图像执行绘画操作。当鼠标在该点单击时,它会绘制一个黑色的小矩形区域。现在我想实现撤消此操作。当我单击按钮时,应撤消最后一次绘制操作。这是我的绘制操作代码..

      private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
    {

            rect.Width = 0;
            rect.Height = 0;
            pictureBox1.Invalidate();


            int radius = 10; //Set the number of pixel you want to use here
            //Calculate the numbers based on radius
            int x0 = Math.Max(e.X - (radius / 2), 0),
                y0 = Math.Max(e.Y - (radius / 2), 0),
                x1 = Math.Min(e.X + (radius / 2), pictureBox1.Width),
                y1 = Math.Min(e.Y + (radius / 2), pictureBox1.Height);
            Bitmap bm = pictureBox1.Image as Bitmap; //Get the bitmap (assuming it is stored that way)
            for (int ix = x0; ix < x1; ix++)
            {
                for (int iy = y0; iy < y1; iy++)
                {
                    bm.SetPixel(ix, iy, Color.Black); //Change the pixel color, maybe should be relative to bitmap
                }
            }
            pictureBox1.Refresh(); //Force refresh
          }

请任何人帮助我如何撤消上一次执行的操作。

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    因为您正在使用内存中的光栅图像,所以您不能只撤消该操作。可以有多种解决方案:

    1. 将原始图像保存在内存中,并为每个操作保存绘图参数:绘制的内容、位置、颜色。当您需要撤消操作时,您只需从头到尾重复所有操作(您可能还有一个存储中间图像的控制点)
    2. 每次操作后保留图像的快照 - 这将非常消耗内存。撤消时 - 恢复列表中的上一张图片。
    3. 保留更改的像素 - 在每次操作中分析前一个图像和新图像并保持像素更改。您可以通过将这些像素复制回去来恢复到以前的状态。

    【讨论】:

    • Google 搜索“C# 图像到字节数组”、“C# 存储图像”等。这将让您大致了解如何将图像作为字节数组处理(用于第 2 个和第 3 个字节)。
    • 非常感谢它对我的帮助很大!
    【解决方案2】:

    执行此操作的一种方法是将bitmap 存储为上次操作之前的状态,然后将bitmap 重新绘制到picturebox 上。这可能不是最有效的,因为位图可能会变得非常大,具体取决于它们的大小,但这是最简单和最快的方法之一。

    另一种更有效的方法是以某种方式记录前后位图之间的差异,例如已更改的像素及其之前的颜色,并将这些像素回滚到其原始颜色。这样只会保存已更改的像素,但编码更复杂。

    【讨论】:

    • 你能帮我写一点代码吗!!第一个似乎很简单!
    【解决方案3】:

    您可以声明一个私有 Image 字段并将图像状态保存到其中以供撤消使用,使用Memento Design Pattern 保存和加载对象“图像”状态是最佳实践,好好利用它。

    然而,除了一个撤消/重做操作之外,更好的解决方案是实施多撤消/重做策略,如下所示:

    • 声明两个堆栈,一个用于撤消,另一个用于重做:
    • 撤消时,从撤消堆栈中弹出操作“图像”或“图像状态”并将其推送到重做堆栈。
    • 重做时,从重做堆栈中弹出动作“图像”或“图像状态”并将其推送到撤消堆栈。

    例子:

    private Stack<Image> _undoStack = new Stack<Image>();
    private Stack<Image> _redoStack = new Stack<Image>();
    
    private readonly object _undoRedoLocker = new object();
    
    private void Undo()
    {
        lock (_undoRedoLocker)
        {
            if (_undoStack.Count > 0)
            {
                _redoStack.Push(_undoStack.Pop());
    
                //OnUndo();
                pictureBox1.Image = _redoStack.Peek();
                pictureBox1.Refresh();
            }
        }
    }
    
    private void Redo()
    {
        lock (_undoRedoLocker)
        {
            if (_redoStack.Count > 0)
            {
                _undoStack.Push(_redoStack.Pop());
    
                //OnRedo();
                pictureBox1.Image = _undoStack.Peek();
                pictureBox1.Refresh();
            }
        } 
    }
    
    //And whenever image need to be modified, add it to the undo stack first and then modify it
    private void UpdateImageData(Action updateImage)
    {
        lock (_undoRedoLocker)
        {
            _undoStack.Push(pictureBox1.Image);//image);
    
            try
            { 
                //manipulate the image here.
                updateImage();
            }
            catch
            {
                _undoStack.Pop();//because of exception remove the last added frame from stack
                throw;
            }
        }
    }
    
    private void pictureBox1_MouseClick(object sender, EventArgs e)
    {
        UpdateImageData(delegate()
        {
            rect.Width = 0;
            rect.Height = 0;
            pictureBox1.Invalidate();
    
            int radius = 10; //Set the number of pixel you want to use here
            //Calculate the numbers based on radius
            int x0 = Math.Max(e.X - (radius / 2), 0),
                y0 = Math.Max(e.Y - (radius / 2), 0),
                x1 = Math.Min(e.X + (radius / 2), pictureBox1.Width),
                y1 = Math.Min(e.Y + (radius / 2), pictureBox1.Height);
            Bitmap bm = pictureBox1.Image as Bitmap; //Get the bitmap (assuming it is stored that way)
            for (int ix = x0; ix < x1; ix++)
            {
                for (int iy = y0; iy < y1; iy++)
                {
                    bm.SetPixel(ix, iy, Color.Black); //Change the pixel color, maybe should be relative to bitmap
                }
            }
            pictureBox1.Refresh(); //Force refresh
        }
    }
    
    • 每当用户想要撤消表单上的“例如按 Ctrl+Z”时,只需调用 Undo();
    • 每当用户想要在表单上重做时,只需调用 Redo();

    注意:我没有测试上面的代码,但它应该可以工作,如果你发现任何问题发表评论

    【讨论】:

    • 我没有得到这个符号 (()=> 你使用了 UpdateImageData()。请说清楚。按下按钮时如何调用 Undo()。你能回答我这两件事吗..
    • @raghu: 将()=&gt; 符号替换为(delegate() 我们在这里创建一个新的动作委托并将其主体放在{ } 中,这个符号'()=>' 被称为lambda,阅读this
    猜你喜欢
    • 2023-03-23
    • 2012-01-17
    • 1970-01-01
    • 1970-01-01
    • 2018-08-23
    • 2012-10-03
    • 1970-01-01
    • 2018-09-29
    • 2018-01-30
    相关资源
    最近更新 更多