Reputation: 441
I have a image loaded in picturebox.I perform a paint operation on the image, by the mouseclick event.It paints a small rectangular area with black color, when the mouse is clicked at that point.now I want to implement the undo operation for this .When i click a button the last paint operation should be undone.Here is my code for paint operation..
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
}
Any one please help me how i can undo the last operation performed.
Upvotes: 1
Views: 5914
Reputation: 16162
You can declare a private Image field and save image state to it for use in undo, using Memento Design Pattern to save and load object "image" state is the best practice, take a loot at it.
However instead of one undo/redo operation a better solution is to implement multi-undo/redo strategy as the following:
Example:
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
}
}
Note: I didn't test the above code but it should be working probably, if you find any issue leave a comment
Upvotes: 1
Reputation: 9936
One way to do this is to store the bitmap
as it was before the last operation, and simply redraw that bitmap
onto the picturebox
. This may not be the most efficient as the bitmaps may get quite large, depending on their size but is one of the simplest and quickest ways.
Another, more efficient, way would be to record the difference between the before and after bitmaps somehow, such as the pixels that have changed and their before colour, and rollback these pixels to their original colour. This then only saves the pixels that have changed, but is more complicated to code.
Upvotes: 2
Reputation: 1455
Because you're working with raster image in memory you cannot just undo the operation. There can be multiple solutions to this:
Upvotes: 6