我正在尝试使用Cairo和GTK#来创建一个简单的绘画应用程序。给我带来麻烦的功能是在两点之间画一条线。单击绘图区域后,将出现一条线,并按照光标直到释放鼠标按钮,此时绘图区域将更新,并且可以绘制另一条线。它应该是MS Paint等程序的熟悉功能。
现在,I am told在开罗执行此类更新的方法是使用开罗上下文的.Save();
和.Restore()
方法来处理状态。问题是我找不到继续在创建它的事件处理程序中引用相同Context的方法,所以我在绘制一行之后恢复状态的所有尝试都没那么富有成效。
在下面的最小(但仍然是那种令人遗憾的)工作代码示例中,非解决方案是在每个Draw上创建一个新的Context。这给出了如下结果:
笔(左侧)表现得很好。这条线显然没有,因为在移动鼠标后无法恢复到之前的状态,因此它们都保留在屏幕上以制作图示的扇形图案。
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甚至可以这样工作吗?如果没有,我怎样才能让线连续重新渲染?
答案 0 :(得分: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();
}
}
答案 1 :(得分:0)
一种古老的删除方式(大约xlib)是将它们在屏幕上异或。然后再次执行完全相同的操作以擦除它们。我记得在位图图形中这样做。开罗似乎有异或,我没有尝试。 [2]
我有一个在https://www.cairographics.org/operators/的xlib中执行此操作的示例,您将写入屏幕的所有坐标和值都需要保存在缓冲区中,以便可以再次执行完全相同的操作来擦除。