File: misc\GDI\WindowsGraphics.cs
Project: ndp\fx\src\System.Drawing.csproj (System.Drawing)
//------------------------------------------------------------------------------
// <copyright file="WindowsGraphics.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
 
//#define GDI_FINALIZATION_WATCH
 
// THIS PARTIAL CLASS CONTAINS THE BASE METHODS FOR CREATING AND DISPOSING A WINDOWSGRAPHICS AS WELL
// GETTING, DISPOSING AND WORKING WITH A DC.
 
#if Microsoft_NAMESPACE
namespace System.Windows.Forms.Internal
#elif DRAWING_NAMESPACE
namespace System.Drawing.Internal
#else
namespace System.Experimental.Gdi
#endif
{
    using System;
    using System.Internal;
    using System.Runtime.InteropServices;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;    
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Runtime.Versioning;
 
    /// <include file='doc\WindowsGraphics.uex' path='docs/doc[@for="WindowsGraphics"]/*' />
    /// <devdoc>
    ///     WindowsGraphics is a library for rendering text and drawing using GDI; it was
    ///     created to address performance and compatibility issues found in GDI+ Graphics
    ///     class.
    ///     
    ///     Note: WindowsGraphics is a stateful component, DC properties are persisted from 
    ///     method calls, as opposed to Graphics (GDI+) which performs attomic operations and 
    ///     always restores the hdc.
    ///     The underlying hdc is always saved and restored on dispose so external HDCs won't
    ///     be modified by WindowsGraphics.  So we don't need to restore previous objects into 
    ///     the dc in method calls.
    ///     See VSWhidbey 300692 & 445469 for some background.
    ///</devdoc>
#if Microsoft_PUBLIC_GRAPHICS_LIBRARY
    public
#else
    internal
#endif
    sealed partial class WindowsGraphics : MarshalByRefObject, IDisposable, IDeviceContext
    {
        // Wrapper around the window dc this object refers to.
        // Note: this dc is only disposed when owned (created) by the WindowsGraphics.
        DeviceContext dc;
        bool disposeDc;
        Graphics graphics; // cached when initialized FromGraphics to be able to call g.ReleaseHdc from Dispose.
 
#if GDI_FINALIZATION_WATCH
        private string AllocationSite = DbgUtil.StackTrace;
#endif
 
        // Construction/destruction API
 
        /// <include file='doc\WindowsGraphics.uex' path='docs/doc[@for="WindowsGraphics.WindowsGraphics"]/*' />
        public WindowsGraphics( DeviceContext dc )
        {
            Debug.Assert( dc != null, "null dc!");         
            this.dc = dc;
            this.dc.SaveHdc();
            //this.disposeDc = false; // the dc is not owned by this object.
        }
 
        /// <devdoc>
        ///     Creates a WindowsGraphics from a memory DeviceContext object compatible with the primary screen device.
        ///     This object is suitable for performing text measuring but not for drawing into it because it does 
        ///     not have a backup bitmap.
        /// </devdoc>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public static WindowsGraphics CreateMeasurementWindowsGraphics()
        {
            DeviceContext dc = DeviceContext.FromCompatibleDC(IntPtr.Zero);
            WindowsGraphics wg = new WindowsGraphics(dc);
            wg.disposeDc = true; // we create it, we dispose it.
            
            return wg;
        }
 
        /// <devdoc>
        ///     Creates a WindowsGraphics from a memory DeviceContext object compatible with the a screen device.
        ///     This object is suitable for performing text measuring but not for drawing into it because it does 
        ///     not have a backup bitmap.
        /// </devdoc>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public static WindowsGraphics CreateMeasurementWindowsGraphics(IntPtr screenDC)
        {
            DeviceContext dc = DeviceContext.FromCompatibleDC(screenDC);
            WindowsGraphics wg = new WindowsGraphics(dc);
            wg.disposeDc = true; // we create it, we dispose it.
            
            return wg;
        }
 
        /// <include file='doc\WindowsGraphics.uex' path='docs/doc[@for="WindowsGraphics.FromHwnd"]/*' />
        public static WindowsGraphics FromHwnd(IntPtr hWnd)
        { 
            DeviceContext dc = DeviceContext.FromHwnd( hWnd );
            WindowsGraphics wg = new WindowsGraphics( dc );
            wg.disposeDc = true; // we create it, we dispose it.
             
            return wg;    
        }
 
        /// <include file='doc\WindowsGraphics.uex' path='docs/doc[@for="WindowsGraphics.FromHwnd"]/*' />
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public static WindowsGraphics FromHdc(IntPtr hDc)
        {
            Debug.Assert( hDc != IntPtr.Zero, "null hDc" );
            
            DeviceContext dc = DeviceContext.FromHdc(hDc);
            WindowsGraphics wg = new WindowsGraphics( dc ); 
            wg.disposeDc = true; // we create it, we dispose it.
 
            return wg;
        }
 
        /// <devdoc>
        ///     Creates a WindowsGraphics object from a Graphics object.  Clipping and coordinate transforms
        ///     are preserved.
        ///     
        ///     Notes: 
        ///     - The passed Graphics object cannot be used until the WindowsGraphics is disposed
        ///     since it borrows the hdc from the Graphics object locking it.
        ///     - Changes to the hdc using the WindowsGraphics object are not preserved into the Graphics object;
        ///     the hdc is returned to the Graphics object intact.
        ///     
        ///     Some background about how Graphics uses the internal hdc when created from an existing one
        ///     (mail from GillesK from GDI+ team):
        ///     User has an HDC with a particular state:
        ///     Graphics object gets created based on that HDC. We query the HDC for its state and apply it to the Graphics. 
        ///     At this stage, we do a SaveHDC and clear everything out of it.
        ///     User calls GetHdc. We restore the HDC to the state it was in and give it to the user.
        ///     User calls ReleaseHdc, we save the current state of the HDC and clear everything 
        ///     (so that the graphics state gets applied next time we use it).
        ///     Next time the user calls GetHdc we give him back the state after the second ReleaseHdc. 
        ///     (But the state changes between the GetHdc and ReleaseHdc are not applied to the Graphics).
        ///     Please note that this only applies the HDC created graphics, for Bitmap derived graphics, GetHdc creates a new DIBSection and 
        ///     things get a lot more complicated.
        /// </devdoc>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public static WindowsGraphics FromGraphics(Graphics g)
        {
            ApplyGraphicsProperties properties = ApplyGraphicsProperties.All;
            return WindowsGraphics.FromGraphics(g, properties);
        }
 
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public static WindowsGraphics FromGraphics(Graphics g, ApplyGraphicsProperties properties)
        {
            Debug.Assert( g != null, "null Graphics object." );
            //Debug.Assert( properties != ApplyGraphicsProperties.None, "Consider using other WindowsGraphics constructor if not preserving Graphics properties." );
 
            WindowsRegion wr = null;
            float[] elements = null;
 
            Region clipRgn = null;
            Matrix worldTransf = null;
 
            if ((properties & ApplyGraphicsProperties.TranslateTransform) != 0 || (properties & ApplyGraphicsProperties.Clipping) != 0)
            {
                object[] data = g.GetContextInfo() as object[];
 
                if( data != null && data.Length == 2 )
                {
                    clipRgn = data[0] as Region;
                    worldTransf = data[1] as Matrix;
                }
 
                if( worldTransf != null )
                {
                    if ((properties & ApplyGraphicsProperties.TranslateTransform) != 0) 
                    {
                        elements = worldTransf.Elements;
                    }
                    worldTransf.Dispose();
                }
 
                if( clipRgn != null )
                {
                    if ((properties & ApplyGraphicsProperties.Clipping) != 0)
                    {
                        // We have to create the WindowsRegion and dipose the Region object before locking the Graphics object,
                        // in case of an unlikely exception before releasing the WindowsRegion, the finalizer will do it for us.
                        // (no try-finally block since this method is used frequently - perf).
                        // See VSWhidbey#383762
                        // If the Graphics.Clip has not been set (Region.IsInfinite) we don't need to apply it to the DC.
                        if (!clipRgn.IsInfinite(g)) 
                        {
                            wr = WindowsRegion.FromRegion(clipRgn, g); // WindowsRegion will take ownership of the hRegion.
                        }
                    }
                    clipRgn.Dispose(); // Disposing the Region object doesn't destroy the hRegion.
                }
            }
 
            WindowsGraphics wg = WindowsGraphics.FromHdc( g.GetHdc() ); // This locks the Graphics object.
            wg.graphics = g;
 
            // Apply transform and clip
            if (wr != null)
            {
                using (wr)
                {
                    // If the Graphics object was created from a native DC the actual clipping region is the intersection
                    // beteween the original DC clip region and the GDI+ one - for display Graphics it is the same as 
                    // Graphics.VisibleClipBounds. See VSW#490404.
                    wg.DeviceContext.IntersectClip(wr);
 
                }
            }
 
            if (elements != null)
            {
                // elements (XFORM) = [eM11, eM12, eM21, eM22, eDx, eDy], eDx/eDy specify the translation offset.
                wg.DeviceContext.TranslateTransform((int)elements[4], (int)elements[5]);
            }
 
            return wg;
        }
 
        /// <include file='doc\WindowsGraphics.uex' path='docs/doc[@for="WindowsGraphics.Finalize"]/*' />
        ~WindowsGraphics()
        { 
            Dispose(false);
        }
 
        public DeviceContext DeviceContext 
        {
            get 
            {
                return this.dc;
            }
        }
 
 
        /// <include file='doc\WindowsGraphics.uex' path='docs/doc[@for="WindowsGraphics.Dispose"]/*' />
        // Okay to suppress. From Microsoft:
        //"WindowsGraphics object does not own the Graphics object.  For instance in a control’s Paint event we pass the 
        //GraphicsContainer object to TextRenderer, which uses WindowsGraphics; 
        //if the Graphics object is disposed then further painting will be broken."
        [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")]
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")]
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
        internal void Dispose(bool disposing)
        {
            if (this.dc != null )
            {
                DbgUtil.AssertFinalization(this, disposing);
 
                try
                {
                    // Restore original dc.
                    this.dc.RestoreHdc();
 
                    if( this.disposeDc)
                    {
                        this.dc.Dispose(disposing);
                    }
 
                    if( this.graphics != null )    // if created from a Graphics object...
                    {
                        this.graphics.ReleaseHdcInternal(this.dc.Hdc);
                        this.graphics = null;
                    }
                                
                }
                catch(Exception ex )
                {
                    if( ClientUtils.IsSecurityOrCriticalException( ex ) )
                    {
                        throw; // rethrow the original exception.
                    }
                    Debug.Fail("Exception thrown during disposing: \r\n" + ex.ToString());
                }
                finally {
                    this.dc = null;
                }
            }
        }
 
        /// <include file='doc\WindowsGraphics.uex' path='docs/doc[@for="WindowsGraphics.GetHdc"]/*' />
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public IntPtr GetHdc()
        {
            return this.dc.Hdc;
        }
 
        /// <include file='doc\WindowsGraphics.uex' path='docs/doc[@for="WindowsGraphics.ReleaseHdc"]/*' />
        public void ReleaseHdc()
        {
            this.dc.Dispose();
        }
    }
}