1

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

现在,我被告知在开罗执行此类更新的方法是使用开罗上下文.Save();.Restore()方法来处理状态。问题是,除了创建它的事件处理程序之外,我找不到一种方法来继续在事件处理程序中引用相同的上下文,因此我在画线后恢复状态的所有尝试都没有成果。

在下面的最小(但仍然是一种巨大的,抱歉)工作代码示例中,非解决方案是在每个 Draw 上创建一个新的上下文。这给出了如下结果: 绘画应用程序的当前状态

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

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 甚至可以这样工作吗?如果没有,我怎样才能让这条线不断地重新渲染?

4

2 回答 2

2

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

截屏: 工作应用程序

源代码:

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();
    }
}
于 2013-04-19T21:51:26.770 回答
0

删除事物的一种老派(大约 xlib)方法是将它们异或到屏幕上。然后再次执行完全相同的操作以擦除它们。我记得在位图图形中这样做。Cairo好像有xor,我没试过。https://www.cairographics.org/operators/

我在 xlib 中有一个示例,位于https://www.raspberrypi.org/forums/viewtopic.php?p=1317849 您写入屏幕的所有坐标和值都需要保存在缓冲区中,这样您就可以完全做到同样的东西再次抹去。

于 2019-01-20T13:47:32.690 回答