如何使用c#撤消绘制操作

时间:2011-06-28 08:43:30

标签: c# .net

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

      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
          }

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

3 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

您可以声明一个私有的Image字段并将图像状态保存到它以供撤消使用,使用Memento Design Pattern来保存和加载对象“image”状态是最好的做法,抓住它就可以了。

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

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

示例:

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();

注意:我没有测试上面的代码,但它可能正常工作,如果你发现任何问题留下评论