[Mono-winforms-list] Matrix memory management in GDI+ bug

Laurent Debacker Laurent Debacker <debackerl@gmail.com>
Sat, 12 Mar 2005 11:18:22 +0100

Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 7bit
Content-Disposition: inline


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
 just always call GdipDeleteMatrix in libgdiplus
which in turn calls cairo_matrix_destroy
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

Laurent Debacker.

Content-Type: text/plain; name="Map.cs"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment; filename="Map.cs"

using System;
using System.Drawing;
using System.Collections;
using System.Collections.Specialized;

namespace GUI
=09public class Map
=09=09private const float AI_SIZE =3D 10.0f;

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

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

=09=09public RectangleF Bounds
=09=09=09get { return bounds; }

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

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

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

=09=09=09mapComplete =3D false;
=09=09=09needRedraw =3D true;
=09=09=09map =3D null;

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

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

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

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

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

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

=09=09public NodeInfo GetNodeByID(int ID)
=09=09=09return (NodeInfo)nodes[ID];

=09=09public void MapComplete()
=09=09=09mapComplete =3D true;
=09=09=09needRedraw =3D true;

=09=09public void MoveAI(int ID, int Segment, float Position)
=09=09=09AI ai =3D (AI)ais[ID];
=09=09=09if(ai =3D=3D null) ais[ID] =3D ai =3D new AI(ID);
=09=09=09ai.Segment =3D (Segment)segments[Segment];
=09=09=09if(ai.Segment =3D=3D null) throw new Exception("AI in unknown segm=
=09=09=09ai.Position =3D Position;

=09=09public void RemoveAI(int ID)

=09=09public void Render(Graphics G, Rectangle Area)
=09=09=09Rectangle drawingArea =3D new Rectangle(10, 10, Area.Width - 20, A=
rea.Height - 20);

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

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

=09=09=09=09=09ratio =3D drawingArea.Width / Bounds.Width;
=09=09=09=09=09float ratio2 =3D drawingArea.Height / Bounds.Height;
=09=09=09=09=09ratio =3D ratio > ratio2 ? ratio2 : ratio;

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

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

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


=09=09=09if(needRedraw || buffer.Width !=3D Area.Width || buffer.Height !=
=3D Area.Height)
=09=09=09=09if(buffer =3D=3D null || buffer.Width !=3D Area.Width || buffer=
.Height !=3D Area.Height)
=09=09=09=09=09if(buffer !=3D null) buffer.Dispose();
=09=09=09=09=09buffer =3D new Bitmap(Area.Width, Area.Height);

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

=09=09=09=09=09g.Transform =3D transform;=09/* MONO FIX "mono in free(): er=
ror: chunk is already free" */
=09=09=09=09=09/*g.TranslateTransform(drawingArea.X, drawingArea.Y);
=09=09=09=09=09g.ScaleTransform(ratio, ratio);
=09=09=09=09=09g.TranslateTransform(-Bounds.X, -Bounds.Y);*/

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

=09=09=09=09needRedraw =3D false;

=09=09=09G.DrawImageUnscaled(buffer, Area.X, Area.Y);

=09=09protected void OnLayoutChanged(EventArgs e)
=09=09=09map =3D null;
=09=09=09if(LayoutChanged !=3D null) LayoutChanged(this, e);

=09=09protected void OnUpdate(EventArgs e)
=09=09=09needRedraw =3D true;
=09=09=09if(Update !=3D null) Update(this, e);