Hi,

There's is a bug in the way mono manage the unmanaged matrix in libgdiplus.

What I wanted to do is to save the transform matrix used in a Graphics
object, save it in a global variable to reuse it later in other
Graphics objects. It's just for optimization purpose.

However Mono's System.Drawing.Drawing2D.Matrix doesn't behave like
Microsoft's one.

With Microsoft I can take the transform matrix from a matrix, even
Dispose() it, then give it to another Graphics object, and it still
works. I know it's tricky to Dispose() there, but I wanted to do
futher research.

In Mono, Matrix.Dipose() (see source code
http://svn.myrealbox.com/viewcvs/trunk/mcs/class/System.Drawing/System.Drawing.Drawing2D/Matrix.cs?rev=31166&view=auto
 just always call GdipDeleteMatrix in libgdiplus
(http://svn.myrealbox.com/viewcvs/trunk/libgdiplus/src/matrix.c?rev=39075&view=auto),
which in turn calls cairo_matrix_destroy
(http://svn.myrealbox.com/viewcvs/trunk/libgdiplus/cairo/src/cairo_matrix.c?rev=39488&view=auto),
which simply do a free().

So your implementation is logic, but isn't compatible with Microsoft's one.

I would recommand you to call GdipDeleteMatrix in the destructor of
System.Drawing.Drawing2D.Matrix, and leave Dipose() empty. That way as
long as the managed Matrix lives, the unmanaged one will also.

The error message I got with my code was "mono in free(): error: chunk
is already free" under FreeBSD 5.3-RELEASE, and libgdiplus-devel,
mono-devel (1.1.4) from the BSD# project
(http://forge.novell.com/modules/xfmod/project/?bsd-sharp).

Regards,
Laurent Debacker.
using System;
using System.Drawing;
using System.Collections;
using System.Collections.Specialized;

namespace GUI
{
        public class Map
        {
                private const float AI_SIZE = 10.0f;

                private RectangleF bounds;
                private Hashtable nodes;
                private Hashtable segments;
                private ListDictionary ais;
                private ListDictionary roads;

                private bool mapComplete;
                private bool needRedraw;
                private Image map;
                private Image buffer;
                private Brush grassBrush;
                private Brush roadBrush;
                private float ratio;
private System.Drawing.Drawing2D.Matrix transform;
                public event EventHandler LayoutChanged;
                public event EventHandler Update;

                public RectangleF Bounds
                {
                        get { return bounds; }
                }

                public ICollection Roads
                {
                        get { return roads.Values; }
                }

                public ICollection AIs
                {
                        get { return ais.Values; }
                }

                public Map()
                {
                        nodes = new Hashtable();
                        segments = new Hashtable();
                        ais = new ListDictionary();
                        roads = new ListDictionary();
                        bounds = RectangleF.Empty;

                        mapComplete = false;
                        needRedraw = true;
                        map = null;

                        string path = this.GetType().Assembly.Location;
                        int i = path.LastIndexOf("/");
                        if(i == -1) path.LastIndexOf(@"\");
                        path = path.Substring(0, i + 1);

                        Image grassImg = new Bitmap(path + "grass.png");
                        grassBrush = new TextureBrush(grassImg);
                        Image roadImg = new Bitmap(path + "road.png");
                        roadBrush = new TextureBrush(roadImg);
                }

                public void SetSegment(SegmentInfo Si)
                {
                        if(segments.Contains(Si.ID)) return;

                        Road r;
                        Segment s;
                        long key = Si.Source.ID < Si.Destination.ID ? 
((long)Si.Source.ID << 32) + Si.Destination.ID : ((long)Si.Destination.ID << 
32) + Si.Source.ID;
                        if((r = (Road)roads[key]) == null) roads[key] = r = new 
Road(Si.Source, Si.Destination);
                        segments.Add(Si.ID, s = new Segment(Si));
                        r.AddSegment(s);
                        if(mapComplete) OnLayoutChanged(null);
                }

                public void SetNode(NodeInfo Ni)
                {
                        if(!nodes.Contains(Ni.ID))
                        {
                                //if(!bounds.IsEmpty) MONO FIX
                                if(nodes.Count != 0)
                                {
                                        if(Ni.X < bounds.Left) bounds.X = Ni.X;
                                        else if(Ni.X > bounds.Right) 
bounds.Width = Ni.X - bounds.X;
                                        if(Ni.Y < bounds.Top) bounds.Y = Ni.Y;
                                        else if(Ni.Y > bounds.Bottom) 
bounds.Height = Ni.Y - bounds.Y;
                                }
                                else
                                        bounds = new RectangleF(Ni.X, Ni.Y, 
0.0f, 0.0f);

                                nodes.Add(Ni.ID, Ni);
                        }
                        if(mapComplete) OnLayoutChanged(null);
                }

                public NodeInfo GetNodeByID(int ID)
                {
                        return (NodeInfo)nodes[ID];
                }

                public void MapComplete()
                {
                        mapComplete = true;
                        needRedraw = true;
                        OnLayoutChanged(null);
                }

                public void MoveAI(int ID, int Segment, float Position)
                {
                        AI ai = (AI)ais[ID];
                        if(ai == null) ais[ID] = ai = new AI(ID);
                        ai.Segment = (Segment)segments[Segment];
                        if(ai.Segment == null) throw new Exception("AI in 
unknown segment.");
                        ai.Position = Position;
                        OnUpdate(null);
                }

                public void RemoveAI(int ID)
                {
                        ais.Remove(ID);
                        OnUpdate(null);
                }

                public void Render(Graphics G, Rectangle Area)
                {
                        Rectangle drawingArea = new Rectangle(10, 10, 
Area.Width - 20, Area.Height - 20);

                        if(map == null || map.Width != Area.Width || map.Height 
!= Area.Height)
                        {
                                if(map != null) map.Dispose();
                                map = new Bitmap(Area.Width, Area.Height);

                                Graphics g = Graphics.FromImage(map);
                                g.FillRectangle(grassBrush, 0, 0, Area.Width, 
Area.Height);

                                if(mapComplete)
                                {
                                        ratio = drawingArea.Width / 
Bounds.Width;
                                        float ratio2 = drawingArea.Height / 
Bounds.Height;
                                        ratio = ratio > ratio2 ? ratio2 : ratio;

                                        g.TranslateTransform(drawingArea.X, 
drawingArea.Y);
                                        g.ScaleTransform(ratio, ratio);
                                        g.TranslateTransform(-Bounds.X, 
-Bounds.Y);
                                        transform = g.Transform;
g.Transform.Dispose();  /* Even with this it works under MS .NET */

                                        Pen roadLinePen = new 
Pen(Color.DarkKhaki, 3.0f);
                                        roadLinePen.DashStyle = 
System.Drawing.Drawing2D.DashStyle.Custom;
                                        roadLinePen.DashPattern = new float[] { 
16.0f, 8.0f };

                                        foreach(Road r in Roads)
                                        {
                                                g.FillPolygon(roadBrush, 
r.Vertexes);
                                                foreach(PointF[] pts in r.Lines)
                                                {
                                                        g.DrawLine(roadLinePen, 
pts[0], pts[1]);
                                                }
                                        }
                                }

                                g.Dispose();
                        }

                        if(needRedraw || buffer.Width != Area.Width || 
buffer.Height != Area.Height)
                        {
                                if(buffer == null || buffer.Width != Area.Width 
|| buffer.Height != Area.Height)
                                {
                                        if(buffer != null) buffer.Dispose();
                                        buffer = new Bitmap(Area.Width, 
Area.Height);
                                }

                                Graphics g = Graphics.FromImage(buffer);
                                g.DrawImageUnscaled(map, 0, 0);

                                if(mapComplete)
                                {
                                        g.Transform = transform;        /* MONO 
FIX "mono in free(): error: chunk is already free" */
                                        /*g.TranslateTransform(drawingArea.X, 
drawingArea.Y);
                                        g.ScaleTransform(ratio, ratio);
                                        g.TranslateTransform(-Bounds.X, 
-Bounds.Y);*/

                                        Brush whiteBrush = new 
SolidBrush(Color.White);
                                        foreach(AI ai in AIs)
                                        {
                                                Segment s = ai.Segment;
                                                float p = ai.Position / 
s.Length;
                                                PointF center = new 
PointF(s.Source.X + (s.Destination.X - s.Source.X) * p, s.Source.Y + 
(s.Destination.Y - s.Source.Y) * p);
                                                g.FillEllipse(whiteBrush, 
center.X - AI_SIZE/2, center.Y - AI_SIZE/2, AI_SIZE, AI_SIZE);
                                        }
                                }

                                g.Dispose();
                                needRedraw = false;
                        }

                        G.DrawImageUnscaled(buffer, Area.X, Area.Y);
                }

                protected void OnLayoutChanged(EventArgs e)
                {
                        map = null;
                        if(LayoutChanged != null) LayoutChanged(this, e);
                        OnUpdate(e);
                }

                protected void OnUpdate(EventArgs e)
                {
                        needRedraw = true;
                        if(Update != null) Update(this, e);
                }
        }
}

Reply via email to