Графический редактор, история редактирования изображения

Нужно сделать графический редактор типа "Paint". Сделал всё, кроме отмены последнего действия. Отмена действия реализована как помещение битмапа в стек, и при нажатии "Отмена" последний битмап удаляется. Проблема в том, что в 1 случае "Отмена" не работает и битмап не обновляется, во 2 случае "Отмена" работает, но битмап не переприсваивается, и после продолжения рисования битмап ставится с неотменёнными действиями. Как решить? (Отмена последнего действия обязательно должна быть в другом классе)

public partial class Form1 : Form
{
    Brush b = new Brush();
    public Bitmap bitmap;
    Graphics gr;
    Point py;
    Rectangle rectangle;
    Pen p = new Pen(Color.Black, 1);
    Pen erase = new Pen(Color.White, 1);
    bool isPressed = false;
    int Tool;
    public Form1()
    {
        InitializeComponent();
        rectangle = Screen.PrimaryScreen.Bounds;
        bitmap = new Bitmap(rectangle.Width, rectangle.Height);
        gr = Graphics.FromImage(bitmap);
        p.StartCap = System.Drawing.Drawing2D.LineCap.Round;
        p.EndCap = System.Drawing.Drawing2D.LineCap.Round;
        pictureBox1.Image = bitmap;
        b.SaveDraw(pictureBox1);
        Tool = 1;
    }

    private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
    {
        isPressed = true;
        b.StartDraw(e.Location);
    }
    private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
    {
        py = e.Location;
        if (isPressed)
        {
            if (Tool == 1)
            {
                b.Draw(gr, pictureBox1, bitmap, p, e.Location);
            }
            if (Tool == 2)
            {
                b.Draw(gr, pictureBox1, bitmap, erase, e.Location);
            }
        }
        pictureBox1.Refresh();
    }
    private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
    {
        b.SaveDraw(pictureBox1);
        isPressed = false;
        if (Tool == 5)
        {
            b.DrawEllipse(gr, pictureBox1, bitmap, p, e.Location);
        }
        if (Tool == 4)
        {
            b.DrawRectangle(gr, pictureBox1, bitmap, p, e.Location);
        }
        if (Tool == 3)
        {
            b.DrawLine(gr, pictureBox1, bitmap, p, e.Location);
        }
    }
    private void Clear_Click(object sender, EventArgs e)
    {
        b.Cancel(pictureBox1, bitmap);
    }
}

Класс, где не переопределяется битмап

class Brush
{
    Point fp;
    Stack<Bitmap> bmp = new Stack<Bitmap>();
    Stack<Bitmap> bmp2 = new Stack<Bitmap>();
    Bitmap bpa;
    public void StartDraw(Point fp)
    {
        this.fp = fp;
    }
    public void SaveDraw(PictureBox pb)
    {
        bpa = new Bitmap(pb.Image);
        bmp.Push(bpa);
    }
    public void Draw(Graphics gr, PictureBox pb, Bitmap bp, Pen p, Point sp)
    {
        gr.DrawLine(p, this.fp, sp);
        this.fp = sp;
        pb.Image = bp;
    }
    public void Cancel(PictureBox pb, Bitmap bp)
    {
        if (bmp.Count > 1)
        {
            bmp2.Push(bmp.Pop());
            pb.Image = bmp.Peek();
            bp = bpa;
        }
    }
}

Класс, где не обновляется битмап

class Brush
{
    Point fp;
    Stack<Bitmap> bmp = new Stack<Bitmap>();
    Stack<Bitmap> bmp2 = new Stack<Bitmap>();
    Bitmap bpa;
    public void StartDraw(Point fp)
    {
        this.fp = fp;
    }
    public void SaveDraw(PictureBox pb)
    {
        bmp.Push((Bitmap)pb.Image);
    }
    public void Draw(Graphics gr, PictureBox pb, Bitmap bp, Pen p, Point sp)
    {
        gr.DrawLine(p, this.fp, sp);
        this.fp = sp;
        pb.Image = bp;
    }
    public void Cancel(PictureBox pb, Bitmap bp)
    {
        if (bmp.Count > 1)
        {
            bmp2.Push(bmp.Pop());
            pb.Image = bmp.Peek();
        }
    }
}

Ответы (1 шт):

Автор решения: aepot

Я не смог разобраться в вашем коде, уж простите.

Набросал короткий пример. Здесь нет классов для рисования, вы можете сделать их сами, а есть только класс для управления историей редактирования.

public class BitmapHistory
{
    private readonly PictureBox _pictureBox;
    private readonly Stack<Bitmap> _undoHistory;
    private readonly Stack<Bitmap> _redoHistory;

    public bool CanUndo => _undoHistory.Count > 0;
    public bool CanRedo => _redoHistory.Count > 0;

    public BitmapHistory(PictureBox pictureBox)
    {
        _pictureBox = pictureBox;
        _undoHistory = new Stack<Bitmap>();
        _redoHistory = new Stack<Bitmap>();
        Reset();
    }

    public void Reset()
    {
        _undoHistory.Clear();
        _redoHistory.Clear();
        Bitmap bmp = new Bitmap(_pictureBox.Width, _pictureBox.Height, PixelFormat.Format24bppRgb);
        using Graphics g = Graphics.FromImage(bmp);
        g.FillRectangle(Brushes.White, 0, 0, bmp.Width, bmp.Height);
        _pictureBox.Image = bmp;
    }

    public void Save()
    {
        Bitmap clone = (Bitmap)_pictureBox.Image.Clone();
        _undoHistory.Push(clone);
        _redoHistory.Clear();
    }

    public void Undo()
    {
        if (CanUndo)
        {
            _redoHistory.Push((Bitmap)_pictureBox.Image);
            _pictureBox.Image = _undoHistory.Pop();
        }
    }

    public void Redo()
    {
        if (CanRedo)
        {
            _undoHistory.Push((Bitmap)_pictureBox.Image);
            _pictureBox.Image = _redoHistory.Pop();
        }
    }
}

Очень просто, передаете ему в конструктор PictureBox, далее он все сделает сам. Перед каждым рисованием нужно выполнить Save(). Чтобы отменить изменения, нужно вызвать Undo(), чтобы отменить отменение изменений :), нужно вызвать Redo().

Вот простая форма для демонстрации: 2 кнопки и картинка.

public partial class Form1 : Form
{
    private BitmapHistory _history;
    private Random _rnd;
    private Pen _pen;

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        _history = new BitmapHistory(pictureBox1);
        _rnd = new Random();
        _pen = new Pen(Color.Black, 1);
        UpdateButtons();
    }

    private void UpdateButtons()
    {
        button1.Enabled = _history.CanUndo;
        button2.Enabled = _history.CanRedo;
    }

    private void pictureBox1_Click(object sender, EventArgs e)
    {
        _history.Save();
        using Graphics g = Graphics.FromImage(pictureBox1.Image);
        g.DrawEllipse(_pen, _rnd.Next(0, pictureBox1.Width), _rnd.Next(0, pictureBox1.Height), 10, 10);
        pictureBox1.Invalidate();
        UpdateButtons();
    }

    private void UndoButton_Click(object sender, EventArgs e)
    {
        _history.Undo();
        UpdateButtons();
    }

    private void RedoButton_Click(object sender, EventArgs e)
    {
        _history.Redo();
        UpdateButtons();
    }
}

Скриншот ничего полезного не покажет, но пусть будет

введите сюда описание изображения

→ Ссылка