Kestami
Kestami

Reputation: 2073

Imitating MSpaints drawing methods

For graphics exercises and some self-improvement sorta stuff, i've decided to basically just mess around and try and recreate some of the functionality of paint within a winform. I've got a lot of the standard ones to work, for example paint can, drawing dots around the cursor, free hand draw and what not, however i'm a little puzzled as to how paint does the mid-drawing animations. For example;

To draw a simple line i can simply get mouse coordinates on MouseUp and MouseDown events, and use the graphics class to draw a line between the two.

However, on MSpaint whilst drawing a line, you get almost a "preview" of the line after you click the first point, and whilst dragging it to the second point the line follows your cursor, but i'm a little stuck as to how this would be done? Does it involve constant redrawing of the line and the graphics device? It'd be great if someone could give me some hints / inner knowledge, i've had a search around the internet but can't REALLY find anything of use..

Upvotes: 1

Views: 1001

Answers (2)

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236308

And very modern via ControlPaint.DrawReversibleLine method :)

Point? startPoint;
Point? endPoint;

private void Form_MouseDown(object sender, MouseEventArgs e)
{
    startPoint = PointToScreen(e.Location);
}

private void Form_MouseMove(object sender, MouseEventArgs e)
{
    if (!startPoint.HasValue)
        return;

    if (endPoint.HasValue)
        ControlPaint.DrawReversibleLine(startPoint.Value, endPoint.Value, Color.White);

    endPoint = PointToScreen(e.Location);
    ControlPaint.DrawReversibleLine(startPoint.Value, endPoint.Value, Color.White);
}

private void Form_MouseUp(object sender, MouseEventArgs e)
{
    startPoint = null;
    endPoint = null;
}

Upvotes: 2

Dai
Dai

Reputation: 155578

Bitmap/raster software makes use of two memory buffers: one is the current "persisted" canvas that contains the pixels that the user has modified explicitly, the second is the framebuffer on the graphics card which is used to display the canvas on-screen.

Making the bitmap document appear on-screen is done by simply copying the raw bytes of the in-memory bitmap document to the framebuffer (if the framebuffer has a different byte-format or color-depth than the in-memory bitmap then you need to perform a conversion. GDI can do this for you if necessary, but let's just assume everything is 32-bit ARGB).

In WinForms, the framebuffer is exposed by the Graphics argument passed into your Control.OnPaint override.

You can implement these "preview" effects using one of two approaches:

Modern

The first approach is used today, and has been for the past 17 years or so (since Windows 95). The in-memory bitmap is copied to the framebuffer whenever the screen needs to be updated, such as a single mouse movement (of even 1px). The preview effect (such as the line the user would be painting once they release the mouse button) is then drawn on-top. The process is repeated as soon as the user's mouse moves again, so to update the preview.

You'd have something like this:

public class PaintingCanvas : Control {

    private Bitmap _canvas = new Bitmap();

    private Boolean _inOp; // are we in a mouse operation?
    private Point _opStart; // where the current mouse operation started
    private Point _opEnd; // where it ends

    public override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        g.DrawImage( _canvas ); // draw the current state

        if( _inOp ) {
            // assuming the only operation is to draw a line
            g.DrawLine( _opStart, _opEnd );
        }
    }

    protected override OnMouseDown(Point p) {
        _inOp = true;
        _opStart = _opEnd = p;
    }
    protected override OnMouseMove(Point p) {
        _opEnd = p;
        this.Invalidate(); // trigger repainting
    }
    protected override OnMouseUp(Point p) {
        using( Graphics g = Graphics.FromImage( _bitmap ) ) {
            g.DrawLine( _opStart, _opEnd ); // a permanent line
        }
        _inOp = false;
    }
}

1980s Flashblack

In ye olden days (think: 1980s), copying the bitmap from memory to the framebuffer was slow, so a surprisingly good hack was using XOR painting. The program assumes ownership of the framebuffer (so no overlapping windows would cause it need to be copied from memory). The preview line is drawn by performing an XOR of all of the pixels that the line would cover. This is fast because XORing the pixels means that their original colour can be restored without needing to recopy the pixels from memory. This trick was used in computers for many kinds of selection, highlight, or preview effect until recently.

highlightOrPreviewColor = originalPixelColor XOR (2^bpp - 1)
originalPixelColor      = highlightOrPreviewColor XOR (2^bpp - 1)

Upvotes: 1

Related Questions