Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Problems.GrammaticalOptimization/SharpVectorRenderingGdi/Gdi/GdiGraphicsRenderer.cs @ 12762

Last change on this file since 12762 was 12762, checked in by aballeit, 9 years ago

#2283 GUI updates, Tree-chart, MCTS Version 2 (prune leaves)

File size: 29.8 KB
Line 
1using System;
2using System.IO;
3using System.Net;
4using System.Xml;
5using System.Reflection;
6using System.Windows.Forms;
7using System.Collections.Generic;
8using System.Drawing;
9using System.Drawing.Drawing2D;
10
11using SharpVectors.Xml;
12using SharpVectors.Dom;
13using SharpVectors.Dom.Css;
14using SharpVectors.Dom.Svg;
15using SharpVectors.Dom.Events;
16
17namespace SharpVectors.Renderers.Gdi
18{
19    /// <summary>
20    /// Renders a Svg image to GDI+
21    /// </summary>
22    public sealed class GdiGraphicsRenderer : ISvgRenderer, IDisposable
23    {
24        #region Private Fields
25
26        /// <summary>
27        /// A counter that tracks the next hit color.
28        /// </summary>
29        private int counter;
30
31        /// <summary>
32        /// Maps a 'hit color' to a graphics node.
33        /// </summary>
34        /// <remarks>
35        /// The 'hit color' is an integer identifier that identifies the
36        /// graphics node that drew it.  When 'hit colors' are drawn onto
37        /// a bitmap (ie. <see cref="idMapRaster">idMapRaster</see> the 'hit color'
38        /// of each pixel with the help of <see cref="graphicsNodes"
39        /// >graphicsNodes</see> can identify for a given x, y coordinate the
40        /// relevant graphics node a mouse event should be dispatched to.
41        /// </remarks>
42        private Dictionary<Color, SvgElement> graphicsNodes = new Dictionary<Color, SvgElement>();
43
44        /// <summary>
45        /// The bitmap containing the rendered Svg image.
46        /// </summary>
47        private Bitmap rasterImage;
48
49        /// <summary>
50        /// A secondary back-buffer used for invalidation repaints. The invalidRect will
51        /// be bitblt to the rasterImage front buffer
52        /// </summary>
53        private Bitmap invalidatedRasterImage;
54
55        /// <summary>
56        /// A bitmap image that consists of 'hit color' instead of visual
57        /// color.  A 'hit color' is an integer identifier that identifies
58        /// the graphics node that drew it.  A 'hit color' can therefore
59        /// identify the graphics node that corresponds an x-y coordinates.
60        /// </summary>
61        private Bitmap idMapRaster;
62
63        /// <summary>
64        /// The renderer's <see cref="GraphicsWrapper">GraphicsWrapper</see>
65        /// object.
66        /// </summary>
67        private GdiGraphicsWrapper graphics;
68
69        /// <summary>
70        /// The renderer's back color.
71        /// </summary>
72        private Color backColor;
73
74        /// <summary>
75        /// The renderer's <see cref="SvgWindow">SvgWindow</see> object.
76        /// </summary>
77        private ISvgWindow window;
78
79        /// <summary>
80        ///
81        /// </summary>
82        private float currentDownX;
83        private float currentDownY;
84        private IEventTarget currentTarget;
85        private IEventTarget currentDownTarget;
86
87        private GdiRenderingHelper _svgRenderer;
88
89        private SvgRectF invalidRect = SvgRectF.Empty;
90
91        #endregion
92
93        #region Constructors and Destructor
94
95        /// <summary>
96        /// Initializes a new instance of the GdiRenderer class.
97        /// </summary>
98        public GdiGraphicsRenderer()
99        {
100            counter      = 0;
101            _svgRenderer = new GdiRenderingHelper(this);
102
103            backColor = Color.White;
104        }
105
106        ~GdiGraphicsRenderer()
107        {
108            this.Dispose(false);
109        }
110
111        #endregion
112
113        #region Public Properties
114
115        /// <summary>
116        /// Gets a bitmap image of the a rendered Svg document.
117        /// </summary>
118        public Bitmap RasterImage
119        {
120            get
121            {
122                return rasterImage;
123            }
124        }
125
126        /// <summary>
127        /// Gets the image map of the rendered Svg document. This
128        /// is a picture of how the renderer will map the (x,y) positions
129        /// of mouse events to objects. You can display this raster
130        /// to help in debugging of hit testing.
131        /// </summary>
132        public Bitmap IdMapRaster
133        {
134            get
135            {
136                return idMapRaster;
137            }
138        }
139
140        /// <summary>
141        /// Gets or sets the <see cref="Window">Window</see> of the
142        /// renderer.
143        /// </summary>
144        /// <value>
145        /// The <see cref="Window">Window</see> of the renderer.
146        /// </value>
147        public ISvgWindow Window
148        {
149            get
150            {
151                return window;
152            }
153            set
154            {
155                window = value;
156            }
157        }
158
159        /// <summary>
160        /// Gets or sets the back color of the renderer.
161        /// </summary>
162        /// <value>
163        /// The back color of the renderer.
164        /// </value>
165        public Color BackColor
166        {
167            get
168            {
169                return backColor;
170            }
171            set
172            {
173                backColor = value;
174            }
175        }
176
177        /// <summary>
178        /// Gets or sets the <see cref="GraphicsWrapper">GraphicsWrapper</see>
179        /// object associated with this renderer.
180        /// </summary>
181        /// <value>
182        /// The <see cref="GraphicsWrapper">GraphicsWrapper</see> object
183        /// associated with this renderer.
184        /// </value>
185        public GdiGraphicsWrapper GraphicsWrapper
186        {
187            get
188            {
189                return graphics;
190            }
191            set
192            {
193                graphics = value;
194            }
195        }
196
197        /// <summary>
198        /// Gets or sets the <see cref="Graphics">Graphics</see> object
199        /// associated with this renderer.
200        /// </summary>
201        /// <value>
202        /// The <see cref="Graphics">Graphics</see> object associated
203        /// with this renderer.
204        /// </value>
205        public Graphics Graphics
206        {
207            get
208            {
209                return graphics.Graphics;
210            }
211            set
212            {
213                graphics.Graphics = value;
214            }
215        }
216
217        #endregion
218
219        #region Public Methods
220
221        public void InvalidateRect(SvgRectF rect)
222        {
223            if (invalidRect == SvgRectF.Empty)
224                invalidRect = rect;
225            else
226                invalidRect.Intersect(rect);
227        }
228
229        /// <summary>
230        /// Renders the <see cref="SvgElement">SvgElement</see>.
231        /// </summary>
232        /// <param name="node">
233        /// The <see cref="SvgElement">SvgElement</see> node to be
234        /// rendered
235        /// </param>
236        /// <returns>
237        /// The bitmap on which the rendering was performed.
238        /// </returns>
239        public void Render(ISvgElement node)
240        {
241            SvgRectF updatedRect;
242            if (invalidRect != SvgRectF.Empty)
243                updatedRect = new SvgRectF(invalidRect.X, invalidRect.Y,
244                    invalidRect.Width, invalidRect.Height);
245            else
246                updatedRect = SvgRectF.Empty;
247
248            RendererBeforeRender();
249
250            if (graphics != null && graphics.Graphics != null)
251            {
252                _svgRenderer.Render(node);
253            }
254           
255            RendererAfterRender();
256
257            if (onRender != null)
258                OnRender(updatedRect);
259        }
260
261        /// <summary>
262        /// Renders the <see cref="SvgDocument">SvgDocument</see>.
263        /// </summary>
264        /// <param name="node">
265        /// The <see cref="SvgDocument">SvgDocument</see> node to be
266        /// rendered
267        /// </param>
268        /// <returns>
269        /// The bitmap on which the rendering was performed.
270        /// </returns>
271        public void Render(ISvgDocument node)
272        {
273            SvgRectF updatedRect;
274            if (invalidRect != SvgRectF.Empty)
275                updatedRect = new SvgRectF(invalidRect.X, invalidRect.Y,
276                    invalidRect.Width, invalidRect.Height);
277            else
278                updatedRect = SvgRectF.Empty;
279
280            RendererBeforeRender();
281
282            if (graphics != null && graphics.Graphics != null)
283            {
284                _svgRenderer.Render(node);
285            }
286
287            RendererAfterRender();
288
289            if (onRender != null)
290                OnRender(updatedRect);
291        }
292
293        public void RenderChildren(ISvgElement node)
294        {
295            _svgRenderer.RenderChildren(node);
296        }
297
298        public void ClearMap()
299        {
300            graphicsNodes.Clear();
301        }
302
303        /// <summary>
304        /// The invalidated region
305        /// </summary>   
306        public SvgRectF InvalidRect
307        {
308            get
309            {
310                return invalidRect;
311            }
312            set
313            {
314                invalidRect = value;
315            }
316        }
317
318        public ISvgRect GetRenderedBounds(ISvgElement element, float margin)
319        {
320            SvgTransformableElement transElement = element as SvgTransformableElement;
321            if (transElement != null)
322            {
323                SvgRectF rect = this.GetElementBounds(transElement, margin);
324
325                return new SvgRect(rect.X, rect.Y, rect.Width, rect.Height);
326            }
327
328            return null;
329        }
330
331        #endregion
332
333        #region Event handlers
334
335        private RenderEvent onRender;
336        public RenderEvent OnRender
337        {
338            get
339            {
340                return onRender;
341            }
342            set
343            {
344                onRender = value;
345            }
346        }
347
348        /// <summary>
349        /// Processes mouse events.
350        /// </summary>
351        /// <param name="type">
352        /// A string describing the type of mouse event that occured.
353        /// </param>
354        /// <param name="e">
355        /// The <see cref="MouseEventArgs">MouseEventArgs</see> that contains
356        /// the event data.
357        /// </param>
358        public void OnMouseEvent(string type, MouseEventArgs e)
359        {
360            if (idMapRaster != null)
361            {
362                try
363                {
364                    Color pixel = idMapRaster.GetPixel(e.X, e.Y);
365                    SvgElement grElement = GetElementFromColor(pixel);
366                    if (grElement != null)
367                    {
368                        IEventTarget target;
369                        if (grElement.ElementInstance != null)
370                            target = grElement.ElementInstance as IEventTarget;
371                        else
372                            target = grElement as IEventTarget;
373
374                        if (target != null)
375                        {
376                            switch (type)
377                            {
378                                case "mousemove":
379                                    {
380                                        if (currentTarget == target)
381                                        {
382                                            target.DispatchEvent(new MouseEvent(
383                                                EventType.MouseMove, true, false,
384                                                null, // todo: put view here
385                                                0, // todo: put detail here
386                                                e.X, e.Y, e.X, e.Y,
387                                                false, false, false, false,
388                                                0, null, false));
389                                        }
390                                        else
391                                        {
392                                            if (currentTarget != null)
393                                            {
394                                                currentTarget.DispatchEvent(new MouseEvent(
395                                                    EventType.MouseOut, true, false,
396                                                    null, // todo: put view here
397                                                    0, // todo: put detail here
398                                                    e.X, e.Y, e.X, e.Y,
399                                                    false, false, false, false,
400                                                    0, null, false));
401                                            }
402
403                                            target.DispatchEvent(new MouseEvent(
404                                                EventType.MouseOver, true, false,
405                                                null, // todo: put view here
406                                                0, // todo: put detail here
407                                                e.X, e.Y, e.X, e.Y,
408                                                false, false, false, false,
409                                                0, null, false));
410                                        }
411                                        break;
412                                    }
413                                case "mousedown":
414                                    target.DispatchEvent(new MouseEvent(
415                                        EventType.MouseDown, true, false,
416                                        null, // todo: put view here
417                                        0, // todo: put detail here
418                                        e.X, e.Y, e.X, e.Y,
419                                        false, false, false, false,
420                                        0, null, false));
421                                    currentDownTarget = target;
422                                    currentDownX = e.X;
423                                    currentDownY = e.Y;
424                                    break;
425                                case "mouseup":
426                                    target.DispatchEvent(new MouseEvent(
427                                        EventType.MouseUp, true, false,
428                                        null, // todo: put view here
429                                        0, // todo: put detail here
430                                        e.X, e.Y, e.X, e.Y,
431                                        false, false, false, false,
432                                        0, null, false));
433                                    if (/*currentDownTarget == target &&*/ Math.Abs(currentDownX - e.X) < 5 && Math.Abs(currentDownY - e.Y) < 5)
434                                    {
435                                        target.DispatchEvent(new MouseEvent(
436                                          EventType.Click, true, false,
437                                          null, // todo: put view here
438                                          0, // todo: put detail here
439                                          e.X, e.Y, e.X, e.Y,
440                                          false, false, false, false,
441                                          0, null, false));
442                                    }
443                                    currentDownTarget = null;
444                                    currentDownX = 0;
445                                    currentDownY = 0;
446                                    break;
447                            }
448                            currentTarget = target;
449                        }
450                        else
451                        {
452
453                            // jr patch
454                            if (currentTarget != null && type == "mousemove")
455                            {
456                                currentTarget.DispatchEvent(new MouseEvent(
457                                  EventType.MouseOut, true, false,
458                                  null, // todo: put view here
459                                  0, // todo: put detail here
460                                  e.X, e.Y, e.X, e.Y,
461                                  false, false, false, false,
462                                  0, null, false));
463                            }
464                            currentTarget = null;
465                        }
466                    }
467                    else
468                    {
469                        // jr patch
470                        if (currentTarget != null && type == "mousemove")
471                        {
472                            currentTarget.DispatchEvent(new MouseEvent(
473                              EventType.MouseOut, true, false,
474                              null, // todo: put view here
475                              0, // todo: put detail here
476                              e.X, e.Y, e.X, e.Y,
477                              false, false, false, false,
478                              0, null, false));
479                        }
480                        currentTarget = null;
481                    }
482                }
483                catch
484                {
485                }
486            }
487        }
488
489        #endregion
490
491        #region Miscellaneous Methods
492
493        /// <summary>
494        /// Allocate a hit color for the specified graphics node.
495        /// </summary>
496        /// <param name="grNode">
497        /// The <see cref="GraphicsNode">GraphicsNode</see> object for which to
498        /// allocate a new hit color.
499        /// </param>
500        /// <returns>
501        /// The hit color for the <see cref="GraphicsNode">GraphicsNode</see>
502        /// object.
503        /// </returns>
504        internal Color GetNextColor(SvgElement element)
505        {
506            //  TODO: [newhoggy] It looks like there is a potential memory leak here.
507            //  We only ever add to the graphicsNodes map, never remove
508            //  from it, so it will grow every time this function is called.
509
510            // The counter is used to generate IDs in the range [0,2^24-1]
511            // The 24 bits of the counter are interpreted as follows:
512            // [red 7 bits | green 7 bits | blue 7 bits |shuffle term 3 bits]
513            // The shuffle term is used to define how the remaining high
514            // bit is set on each color. The colors are generated in the
515            // range [0,127] (7 bits) instead of [0,255]. Then the shuffle term
516            // is used to adjust them into the range [0,255].
517            // This algorithm has the feature that consecutive ids generate
518            // visually distinct colors.
519            int id = counter++; // Zero should be the first color.
520            int shuffleTerm = id & 7;
521            int r = 0x7f & (id >> 17);
522            int g = 0x7f & (id >> 10);
523            int b = 0x7f & (id >> 3);
524
525            switch (shuffleTerm)
526            {
527                case 0: break;
528                case 1: b |= 0x80; break;
529                case 2: g |= 0x80; break;
530                case 3: g |= 0x80; b |= 0x80; break;
531                case 4: r |= 0x80; break;
532                case 5: r |= 0x80; b |= 0x80; break;
533                case 6: r |= 0x80; g |= 0x80; break;
534                case 7: r |= 0x80; g |= 0x80; b |= 0x80; break;
535            }
536
537            Color color = Color.FromArgb(r, g, b);
538
539            graphicsNodes.Add(color, element);
540
541            return color;
542        }
543
544        internal void RemoveColor(Color color, SvgElement element)
545        {
546            if (!color.IsEmpty)
547            {
548                graphicsNodes[color] = null;
549                graphicsNodes.Remove(color);
550            }
551        }
552
553        /// <summary>
554        /// Gets the <see cref="GraphicsNode">GraphicsNode</see> object that
555        /// corresponds to the given hit color.
556        /// </summary>
557        /// <param name="color">
558        /// The hit color for which to get the corresponding
559        /// <see cref="GraphicsNode">GraphicsNode</see> object.
560        /// </param>
561        /// <remarks>
562        /// Returns <c>null</c> if a corresponding
563        /// <see cref="GraphicsNode">GraphicsNode</see> object cannot be
564        /// found for the given hit color.
565        /// </remarks>
566        /// <returns>
567        /// The <see cref="GraphicsNode">GraphicsNode</see> object that
568        /// corresponds to the given hit color
569        /// </returns>
570        private SvgElement GetElementFromColor(Color color)
571        {
572            if (color.A == 0)
573            {
574                return null;
575            }
576            else
577            {
578                if (graphicsNodes.ContainsKey(color))
579                {
580                    return graphicsNodes[color];
581                }
582
583                return null;
584            }
585        }
586
587        /// <summary>
588        /// TODO: This method is not used.
589        /// </summary>
590        /// <param name="color">
591        /// </param>
592        /// <returns>
593        /// </returns>
594        private static int ColorToId(Color color)
595        {
596            int r = color.R;
597            int g = color.G;
598            int b = color.B;
599            int shuffleTerm = 0;
600
601            if (0 != (r & 0x80))
602            {
603                shuffleTerm |= 4;
604                r &= 0x7f;
605            }
606
607            if (0 != (g & 0x80))
608            {
609                shuffleTerm |= 2;
610                g &= 0x7f;
611            }
612
613            if (0 != (b & 0x80))
614            {
615                shuffleTerm |= 1;
616                b &= 0x7f;
617            }
618
619            return (r << 17) + (g << 10) + (b << 3) + shuffleTerm;
620        }
621
622        private SvgRectF GetElementBounds(SvgTransformableElement element, float margin)
623        {
624            SvgRenderingHint hint = element.RenderingHint;
625            if (hint == SvgRenderingHint.Shape || hint == SvgRenderingHint.Text)
626            {
627                GraphicsPath gp = GdiRendering.CreatePath(element);
628                ISvgMatrix svgMatrix = element.GetScreenCTM();
629
630                Matrix matrix = new Matrix((float)svgMatrix.A, (float)svgMatrix.B, (float)svgMatrix.C,
631                      (float)svgMatrix.D, (float)svgMatrix.E, (float)svgMatrix.F);
632                SvgRectF bounds = SvgConverter.ToRect(gp.GetBounds(matrix));
633                bounds = SvgRectF.Inflate(bounds, margin, margin);
634
635                return bounds;
636            }
637
638            SvgUseElement useElement = element as SvgUseElement;
639            if (useElement != null)
640            {
641                SvgTransformableElement refEl = useElement.ReferencedElement as SvgTransformableElement;
642                if (refEl == null)
643                    return SvgRectF.Empty;
644
645                XmlElement refElParent = (XmlElement)refEl.ParentNode;
646                element.OwnerDocument.Static = true;
647                useElement.CopyToReferencedElement(refEl);
648                element.AppendChild(refEl);
649
650                SvgRectF bbox = this.GetElementBounds(refEl, margin);
651
652                element.RemoveChild(refEl);
653                useElement.RestoreReferencedElement(refEl);
654                refElParent.AppendChild(refEl);
655                element.OwnerDocument.Static = false;
656
657                return bbox;
658            }
659
660            SvgRectF union = SvgRectF.Empty;
661            SvgTransformableElement transformChild;
662            foreach (XmlNode childNode in element.ChildNodes)
663            {
664                if (childNode is SvgDefsElement)
665                    continue;
666                if (childNode is ISvgTransformable)
667                {
668                    transformChild = (SvgTransformableElement)childNode;
669                    SvgRectF bbox = this.GetElementBounds(transformChild, margin);
670                    if (bbox != SvgRectF.Empty)
671                    {
672                        if (union == SvgRectF.Empty)
673                            union = bbox;
674                        else
675                            union = SvgRectF.Union(union, bbox);
676                    }
677                }
678            }
679
680            return union;
681        }
682
683        #endregion
684
685        #region Private Methods
686
687        /// <summary>
688        /// BeforeRender - Make sure we have a Graphics object to render to.
689        /// If we don't have one, then create one to match the SvgWindow's
690        /// physical dimensions.
691        /// </summary>
692        private void RendererBeforeRender()
693        {
694            // Testing for null here allows "advanced" developers to create their own Graphics object for rendering
695            if (graphics == null)
696            {
697                // Get the current SVGWindow's width and height
698                int innerWidth  = (int)window.InnerWidth;
699                int innerHeight = (int)window.InnerHeight;
700
701                // Make sure we have an actual area to render to
702                if (innerWidth > 0 && innerHeight > 0)
703                {
704                    // See if we already have a rasterImage that matches the current SVGWindow dimensions
705                    if (rasterImage == null || rasterImage.Width != innerWidth || rasterImage.Height != innerHeight)
706                    {
707                        // Nope, so create one
708                        if (rasterImage != null)
709                        {
710                            rasterImage.Dispose();
711                            rasterImage = null;
712                        }
713                        rasterImage = new Bitmap(innerWidth, innerHeight);
714                    }
715
716                    // Maybe we are only repainting an invalidated section
717                    if (invalidRect != SvgRectF.Empty)
718                    {
719                        // TODO: Worry about pan...
720                        if (invalidRect.X < 0)
721                            invalidRect.X = 0;
722                        if (invalidRect.Y < 0)
723                            invalidRect.Y = 0;
724                        if (invalidRect.Right > innerWidth)
725                            invalidRect.Width = innerWidth - invalidRect.X;
726                        if (invalidRect.Bottom > innerHeight)
727                            invalidRect.Height = innerHeight - invalidRect.Y;
728
729                        if (invalidatedRasterImage == null || invalidatedRasterImage.Width < invalidRect.Right ||
730                            invalidatedRasterImage.Height < invalidRect.Bottom)
731                        {
732                            // Nope, so create one
733                            if (invalidatedRasterImage != null)
734                            {
735                                invalidatedRasterImage.Dispose();
736                                invalidatedRasterImage = null;
737                            }
738                            invalidatedRasterImage = new Bitmap((int)invalidRect.Right, (int)invalidRect.Bottom);
739                        }
740                        // Make a GraphicsWrapper object from the regionRasterImage and clear it to the background color
741                        graphics = GdiGraphicsWrapper.FromImage(invalidatedRasterImage, false);
742
743                        graphics.Clear(backColor);
744                    }
745                    else
746                    {
747                        // Make a GraphicsWrapper object from the rasterImage and clear it to the background color
748                        graphics = GdiGraphicsWrapper.FromImage(rasterImage, false);
749                        graphics.Clear(backColor);
750                    }
751                }
752            }
753        }
754
755        /// <summary>
756        /// AfterRender - Dispose of Graphics object created for rendering.
757        /// </summary>
758        private void RendererAfterRender()
759        {
760            if (graphics != null)
761            {
762                // Check if we only invalidated a rect
763                if (invalidRect != SvgRectF.Empty)
764                {
765                    // We actually drew everything on invalidatedRasterImage and now we
766                    // need to copy that to rasterImage
767                    Graphics tempGraphics = Graphics.FromImage(rasterImage);
768                    tempGraphics.DrawImage(invalidatedRasterImage, invalidRect.X, invalidRect.Y,
769                      GdiConverter.ToRectangle(invalidRect), GraphicsUnit.Pixel);
770                    tempGraphics.Dispose();
771                    tempGraphics = null;
772
773                    // If we currently have an idMapRaster here, then we need to create
774                    // a temporary graphics object to draw the invalidated portion from
775                    // our main graphics window onto it.
776                    if (idMapRaster != null)
777                    {
778                        tempGraphics = Graphics.FromImage(idMapRaster);
779                        tempGraphics.DrawImage(graphics.IdMapRaster, invalidRect.X, invalidRect.Y,
780                          GdiConverter.ToRectangle(invalidRect), GraphicsUnit.Pixel);
781                        tempGraphics.Dispose();
782                        tempGraphics = null;
783                    }
784                    else
785                    {
786                        idMapRaster = graphics.IdMapRaster;
787                    }
788                    // We have updated the invalid region
789                    invalidRect = SvgRectF.Empty;
790                }
791                else
792                {
793                    if (idMapRaster != null && idMapRaster != graphics.IdMapRaster)
794                        idMapRaster.Dispose();
795                    idMapRaster = graphics.IdMapRaster;
796                }
797
798                graphics.Dispose();
799                graphics = null;
800            }
801        }
802
803        #endregion
804
805        #region IDisposable Members
806
807        public void Dispose()
808        {
809            this.Dispose(true);
810            GC.SuppressFinalize(this);
811        }
812
813        private void Dispose(bool disposing)
814        {
815            if (idMapRaster != null)
816                idMapRaster.Dispose();
817            if (invalidatedRasterImage != null)
818                invalidatedRasterImage.Dispose();
819            if (rasterImage != null)
820                rasterImage.Dispose();
821            if (graphics != null)
822                graphics.Dispose();
823        }
824
825        #endregion
826    }
827}
Note: See TracBrowser for help on using the repository browser.