简单的绘画应用:在鼠标移动时引用Cairo Context

时间:2013-04-12 02:21:38

标签: c# mono gtk# cairo

我正在尝试使用Cairo和GTK#来创建一个简单的绘画应用程序。给我带来麻烦的功能是在两点之间画一条线。单击绘图区域后,将出现一条线,并按照光标直到释放鼠标按钮,此时绘图区域将更新,并且可以绘制另一条线。它应该是MS Paint等程序的熟悉功能。

现在,I am told在开罗执行此类更新的方法是使用开罗上下文的.Save();.Restore()方法来处理状态。问题是我找不到继续在创建它的事件处理程序中引用相同Context的方法,所以我在绘制一行之后恢复状态的所有尝试都没那么富有成效。

在下面的最小(但仍然是那种令人遗憾的)工作代码示例中,非解决方案是在每个Draw上创建一个新的Context。这给出了如下结果:  Current state of the Painting application

笔(左侧)表现得很好。这条线显然没有,因为在移动鼠标后无法恢复到之前的状态,因此它们都保留在屏幕上以制作图示的扇形图案。

using Gtk;
using Cairo;
using System;

public class Paint : Gtk.Window
{

    bool isDrawing = false;
    bool isDrawingLine = false;
    bool isDrawingWithPen = false;

    double Point_l1x;
    double Point_l1y;
    double Point_l2x;
    double Point_l2y;

    double Point_p1x;
    double Point_p1y;

    Button line;
    Button pen;

    static DrawingArea area;
    Cairo.Context ctx;

    void OnDrawingAreaExposed (object source, ExposeEventArgs args)
    {   
        DrawingArea area = (DrawingArea) source;
        ctx = Gdk.CairoHelper.Create (area.GdkWindow);

        ((IDisposable) ctx.Target).Dispose();
        ((IDisposable) ctx).Dispose ();
    }

    public void DrawImage ()
    {
        //Shouldn't this be referencing an external context?
        using (Cairo.Context ctx = Gdk.CairoHelper.Create (area.GdkWindow))
        {
            ctx.Color = new Cairo.Color (0, 0, 0);

            if(isDrawingLine)
            {
                ctx.MoveTo (new PointD (Point_l1x, Point_l1y));
                ctx.LineTo (new PointD (Point_l2x, Point_l2y));
                ctx.Stroke ();
            }

            else if(isDrawingWithPen)
            {
                ctx.Rectangle(Point_p1x, Point_p1y, 1, 1);
                ctx.Stroke();
            }
        }
    }

    public void LineClicked(object sender, EventArgs args)
    {
        isDrawingLine = true;
        isDrawingWithPen = false;
    }

    public void PenClicked(object sender, EventArgs args)
    {
        isDrawingLine = false;
        isDrawingWithPen = true;
    }

    void OnMousePress (object source, ButtonPressEventArgs args)
    {
        isDrawing = true;
        if (isDrawingLine)
        {
            Point_l1x = args.Event.X;
            Point_l1y = args.Event.Y;
        }
        else if (isDrawingWithPen)
        {
            Point_p1x = args.Event.X;
            Point_p1y = args.Event.Y;
        }
    }

    void OnMouseRelease (object source, ButtonReleaseEventArgs args)
    {
        isDrawing = false;
        DrawImage ();
    }

    void OnMouseMotion (object source, MotionNotifyEventArgs args)
    {
        if (!isDrawing)
        {
            return;
        }

        if (isDrawingLine)
        {
            Point_l2x = args.Event.X;
            Point_l2y = args.Event.Y;
        }
        else if (isDrawingWithPen)
        {
            Point_p1x = args.Event.X;
            Point_p1y = args.Event.Y;
        }
        DrawImage();
    }

    public Paint () : base("Painting application")
    {   
        area = new DrawingArea ();
        area.ExposeEvent += OnDrawingAreaExposed;

        area.AddEvents (
                (int)Gdk.EventMask.PointerMotionMask
                | (int)Gdk.EventMask.ButtonPressMask
                | (int)Gdk.EventMask.ButtonReleaseMask);

        area.ButtonPressEvent += OnMousePress;
        area.ButtonReleaseEvent += OnMouseRelease;
        area.MotionNotifyEvent += OnMouseMotion;

        DeleteEvent += delegate { Application.Quit(); };

        SetDefaultSize(500, 500);
        SetPosition(WindowPosition.Center);

        VBox vbox = new VBox();
        vbox.Add(area);
        HBox hbox = new HBox();

        line = new Button("Line");
        pen = new Button("Pen");
        hbox.Add(line);
        hbox.Add(pen);

        Alignment halign = new Alignment(1, 0, 0, 0);
        halign.Add(hbox);

        vbox.Add(hbox);
        vbox.PackStart(halign, false, false, 3);

        line.Clicked += LineClicked;
        pen.Clicked += PenClicked;

        Add(vbox);
        ShowAll();
    }

    public static void Main()
    {
        Application.Init();
        new Paint();
        Application.Run();
    }
}

如果我修改DrawImage方法来引用OnDrawingAreaExposed中定义的Context,整个事情就会崩溃,提供一个我无法理解的堆栈跟踪:

Stacktrace:

at (wrapper managed-to-native) Cairo.NativeMethods.cairo_set_source_rgba (intptr,double,double,double,double) <0xffffffff>
at Cairo.Context.set_Color (Cairo.Color) <0x0002b>
at Paint.DrawImage () <0x000a3>
at Paint.OnMouseMotion (object,Gtk.MotionNotifyEventArgs) <0x001af>
at (wrapper runtime-invoke) <Module>.runtime_invoke_void__this___object_object (object,intptr,intptr,intptr) <0xffffffff>
at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&) <0xffffffff>
at System.Reflection.MonoMethod.Invoke (object,System.Reflection.BindingFlags,System.Reflection.Binder,object[],System.Globalization.CultureInfo) <0x0018b>
at System.Reflection.MethodBase.Invoke (object,object[]) <0x0002a>
at System.Delegate.DynamicInvokeImpl (object[]) <0x001a3>
at System.MulticastDelegate.DynamicInvokeImpl (object[]) <0x0003b>
at System.Delegate.DynamicInvoke (object[]) <0x00018>
at GLib.Signal.ClosureInvokedCB (object,GLib.ClosureInvokedArgs) <0x0014f>
at GLib.SignalClosure.Invoke (GLib.ClosureInvokedArgs) <0x0002f>
at GLib.SignalClosure.MarshalCallback (intptr,intptr,uint,intptr,intptr,intptr) <0x0050b>
at (wrapper native-to-managed) GLib.SignalClosure.MarshalCallback (intptr,intptr,uint,intptr,intptr,intptr) <0xffffffff>
at (wrapper managed-to-native) Gtk.Application.gtk_main () <0xffffffff>
at Gtk.Application.Run () <0x0000b>
at Paint.Main () <0x00027>
at (wrapper runtime-invoke) object.runtime_invoke_void (object,intptr,intptr,intptr) <0xffffffff>

Native stacktrace:

        mono() [0x4961e9]
        mono() [0x4e6d1f]
        mono() [0x41dcb7]
        /lib/x86_64-linux-gnu/libpthread.so.0(+0xfcb0) [0x7f3fd2f07cb0]
        /usr/lib/x86_64-linux-gnu/libcairo.so.2(cairo_set_source_rgba+0x1) [0x7f3fcaaccc71]
        [0x41a28dbd]

我是否在正确的轨道上,尝试引用该上下文? Cairo Contexts甚至可以这样工作吗?如果没有,我怎样才能让线连续重新渲染?

2 个答案:

答案 0 :(得分:2)

未来的访问者可能有兴趣知道我最终总结了一个解决方案。注意使用委托。结果如下:

截屏: The working application

源代码:

using Gtk;
using Cairo;
using System;

public class Paint : Gtk.Window
{
    delegate void DrawShape(Cairo.Context ctx, PointD start, PointD end);

    ImageSurface surface;
    DrawingArea area;
    DrawShape Painter;
    PointD Start, End;

    bool isDrawing;
    bool isDrawingPoint;

    Button line;
    Button pen;

    public Paint() : base("Painting application")
    {
        surface = new ImageSurface(Format.Argb32, 500, 500);
        area = new DrawingArea();

        area.AddEvents(
            (int)Gdk.EventMask.PointerMotionMask
            |(int)Gdk.EventMask.ButtonPressMask
            |(int)Gdk.EventMask.ButtonReleaseMask);

        area.ExposeEvent += OnDrawingAreaExposed;
        area.ButtonPressEvent += OnMousePress;
        area.ButtonReleaseEvent += OnMouseRelease;
        area.MotionNotifyEvent += OnMouseMotion;
        DeleteEvent += delegate { Application.Quit(); };

        Painter = new DrawShape(DrawLine);

        Start = new PointD(0.0, 0.0);
        End = new PointD(500.0, 500.0);
        isDrawing = false;
        isDrawingPoint = false;

        SetDefaultSize(500, 500);
        SetPosition(WindowPosition.Center);

        VBox vbox = new VBox();
        vbox.Add(area);
        HBox hbox = new HBox();

        line = new Button("Line");
        pen = new Button("Pen");
        hbox.Add(line);
        hbox.Add(pen);

        Alignment halign = new Alignment(1, 0, 0, 0);
        halign.Add(hbox);

        vbox.Add(hbox);
        vbox.PackStart(halign, false, false, 3);

        line.Clicked += LineClicked;
        pen.Clicked += PenClicked;

        Add(vbox);

        Add(area);
        ShowAll();
    }

    void OnDrawingAreaExposed(object source, ExposeEventArgs args)
    {
        Cairo.Context ctx;

        using (ctx = Gdk.CairoHelper.Create(area.GdkWindow))
        {
            ctx.Source = new SurfacePattern(surface);
            ctx.Paint();
        }

        if (isDrawing)
        {
            using (ctx = Gdk.CairoHelper.Create(area.GdkWindow))
            {
                Painter(ctx, Start, End);
            }
        }
    }

    void OnMousePress(object source, ButtonPressEventArgs args)
    {
        Start.X = args.Event.X;
        Start.Y = args.Event.Y;

        End.X = args.Event.X;
        End.Y = args.Event.Y;

        isDrawing = true;
        area.QueueDraw();
    }

    void OnMouseRelease(object source, ButtonReleaseEventArgs args)
    {
        End.X = args.Event.X;
        End.Y = args.Event.Y;

        isDrawing = false;

        using (Context ctx = new Context(surface))
        {
            Painter(ctx, Start, End);
        }

        area.QueueDraw();
    }

    void OnMouseMotion(object source, MotionNotifyEventArgs args)
    {
        if (isDrawing)
        {
            End.X = args.Event.X;
            End.Y = args.Event.Y;

            if(isDrawingPoint)
            {
                using (Context ctx = new Context(surface))
                {
                    Painter(ctx, Start, End);
                }
            }
            area.QueueDraw();
        }
    }

    void LineClicked(object sender, EventArgs args)
    {
        isDrawingPoint = false;
        Painter = new DrawShape(DrawLine);
    }

    void PenClicked(object sender, EventArgs args)
    {
        isDrawingPoint = true;
        Painter = new DrawShape(DrawPoint);
    }

    void DrawLine(Cairo.Context ctx, PointD start, PointD end)
    {
        ctx.MoveTo(start);
        ctx.LineTo(end);
        ctx.Stroke();
    }

    void DrawPoint(Cairo.Context ctx, PointD start, PointD end)
    {
        ctx.Rectangle(end, 1, 1);
        ctx.Stroke();
    }

    public static void Main()
    {
        Application.Init();
        new Paint();
        Application.Run();
    }
}

答案 1 :(得分:0)

一种古老的删除方式(大约xlib)是将它们在屏幕上异或。然后再次执行完全相同的操作以擦除它们。我记得在位图图形中这样做。开罗似乎有异或,我没有尝试。 [2]

我有一个在https://www.cairographics.org/operators/的xlib中执行此操作的示例,您将写入屏幕的所有坐标和值都需要保存在缓冲区中,以便可以再次执行完全相同的操作来擦除。