File: Base\System\Windows\Media\Matrix.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//------------------------------------------------------------------------------
//  Microsoft Avalon
//  Copyright (c) Microsoft Corporation, 2001, 2002
//
//  File: Matrix.cs
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Reflection;
using MS.Internal;
using System.Text;
using System.Collections;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using SR=MS.Internal.WindowsBase.SR;
using SRID=MS.Internal.WindowsBase.SRID;
 
// IMPORTANT
//
// Rules for using matrix types.
// 
//    internal enum MatrixTypes
//    {
//        TRANSFORM_IS_IDENTITY    = 0,
//        TRANSFORM_IS_TRANSLATION = 1,
//        TRANSFORM_IS_SCALING     = 2,
//        TRANSFORM_IS_UNKNOWN     = 4
//    }
//
// 1. Matrix type must be one of 0, 1, 2, 4, or 3 (for scale and translation)
// 2. Matrix types are true but not exact!  (E.G. A scale or identity transform could be marked as unknown or scale+translate.)
// 3. Therefore read-only operations can ignore the type with one exception
//      EXCEPTION: A matrix tagged identity might have any coefficients instead of 1,0,0,1,0,0
//                 This is the (now) classic no default constructor for structs issue
// 4. Matrix._type must be maintained by mutation operations
// 5. MS.Internal.MatrixUtil uses unsafe code to access the private members of Matrix including _type.
//
// In Jan 2005 the matrix types were changed from being EXACT (i.e. a
// scale matrix is always tagged as a scale and not something more
// general.)  This resulted in about a 2% speed up in matrix
// multiplication.
//
// The special cases for matrix multiplication speed up scale*scale
// and translation*translation by 30% compared to a single "no-branch"
// multiplication algorithm.  Matrix multiplication of two unknown
// matrices is slowed by 20% compared to the no-branch algorithm.
//
// windows/wcp/DevTest/Drts/MediaApi/MediaPerf.cs includes the
// simple test of matrix multiplication speed used for these results.
 
namespace System.Windows.Media
{
    ///<summary>
    /// Matrix
    ///</summary>
    public partial struct Matrix: IFormattable
    {
        // the transform is identity by default
        // Actually fill in the fields - some (internal) code uses the fields directly for perf.
        private static Matrix s_identity = CreateIdentity();
 
#region Constructor
 
        /// <summary>
        /// Creates a matrix of the form
        ///             / m11, m12, 0 \
        ///             | m21, m22, 0 |
        ///             \ offsetX, offsetY, 1 /
        /// </summary>
        public Matrix(double m11, double m12,
                      double m21, double m22,
                      double offsetX, double offsetY)
        {
            this._m11 = m11;
            this._m12 = m12;
            this._m21 = m21;
            this._m22 = m22;
            this._offsetX = offsetX;
            this._offsetY = offsetY;
            _type = MatrixTypes.TRANSFORM_IS_UNKNOWN;
            _padding = 0;
 
            // We will detect EXACT identity, scale, translation or
            // scale+translation and use special case algorithms.
            DeriveMatrixType();
        }
 
#endregion Constructor
 
#region Identity
 
        /// <summary>
        /// Identity
        /// </summary>
        public static Matrix Identity
        {
            get
            {
                return s_identity;
            }
        }
 
        /// <summary>
        /// Sets the matrix to identity.
        /// </summary>
        public void SetIdentity()
        {
            _type = MatrixTypes.TRANSFORM_IS_IDENTITY;
        }
 
        /// <summary>
        /// Tests whether or not a given transform is an identity transform
        /// </summary>
        public bool IsIdentity
        {
            get
            {
                return (_type == MatrixTypes.TRANSFORM_IS_IDENTITY ||
                        (_m11 == 1 && _m12 == 0 && _m21 == 0 && _m22 == 1 && _offsetX == 0 && _offsetY == 0));
            }
        }
 
#endregion Identity
 
#region Operators
        /// <summary>
        /// Multiplies two transformations.
        /// </summary>
        public static Matrix operator *(Matrix trans1, Matrix trans2)
        {
            MatrixUtil.MultiplyMatrix(ref trans1, ref trans2);
            trans1.Debug_CheckType();
            return trans1;
        }
 
        /// <summary>
        /// Multiply
        /// </summary>
        public static Matrix Multiply(Matrix trans1, Matrix trans2)
        {
            MatrixUtil.MultiplyMatrix(ref trans1, ref trans2);
            trans1.Debug_CheckType();
            return trans1;
        }
 
#endregion Operators
 
#region Combine Methods
 
        /// <summary>
        /// Append - "this" becomes this * matrix, the same as this *= matrix.
        /// </summary>
        /// <param name="matrix"> The Matrix to append to this Matrix </param>
        public void Append(Matrix matrix)
        {
            this *= matrix;
        }
 
        /// <summary>
        /// Prepend - "this" becomes matrix * this, the same as this = matrix * this.
        /// </summary>
        /// <param name="matrix"> The Matrix to prepend to this Matrix </param>
        public void Prepend(Matrix matrix)
        {
            this = matrix * this;
        }
 
        /// <summary>
        /// Rotates this matrix about the origin
        /// </summary>
        /// <param name='angle'>The angle to rotate specifed in degrees</param>
        public void Rotate(double angle)
        {
            angle %= 360.0; // Doing the modulo before converting to radians reduces total error
            this *= CreateRotationRadians(angle * (Math.PI/180.0));
        }
 
        /// <summary>
        /// Prepends a rotation about the origin to "this"
        /// </summary>
        /// <param name='angle'>The angle to rotate specifed in degrees</param>
        public void RotatePrepend(double angle)
        {
            angle %= 360.0; // Doing the modulo before converting to radians reduces total error
            this = CreateRotationRadians(angle * (Math.PI/180.0)) * this;
        }
 
        /// <summary>
        /// Rotates this matrix about the given point
        /// </summary>
        /// <param name='angle'>The angle to rotate specifed in degrees</param>
        /// <param name='centerX'>The centerX of rotation</param>
        /// <param name='centerY'>The centerY of rotation</param>
        public void RotateAt(double angle, double centerX, double centerY)
        {
            angle %= 360.0; // Doing the modulo before converting to radians reduces total error
            this *= CreateRotationRadians(angle * (Math.PI/180.0), centerX, centerY);
        }
 
        /// <summary>
        /// Prepends a rotation about the given point to "this"
        /// </summary>
        /// <param name='angle'>The angle to rotate specifed in degrees</param>
        /// <param name='centerX'>The centerX of rotation</param>
        /// <param name='centerY'>The centerY of rotation</param>
        public void RotateAtPrepend(double angle, double centerX, double centerY)
        {
            angle %= 360.0; // Doing the modulo before converting to radians reduces total error
            this = CreateRotationRadians(angle * (Math.PI/180.0), centerX, centerY) * this;
        }
 
        /// <summary>
        /// Scales this matrix around the origin
        /// </summary>
        /// <param name='scaleX'>The scale factor in the x dimension</param>
        /// <param name='scaleY'>The scale factor in the y dimension</param>
        public void Scale(double scaleX, double scaleY)
        {
            this *= CreateScaling(scaleX, scaleY);
        }
 
        /// <summary>
        /// Prepends a scale around the origin to "this"
        /// </summary>
        /// <param name='scaleX'>The scale factor in the x dimension</param>
        /// <param name='scaleY'>The scale factor in the y dimension</param>
        public void ScalePrepend(double scaleX, double scaleY)
        {
            this = CreateScaling(scaleX, scaleY) * this;
        }
 
        /// <summary>
        /// Scales this matrix around the center provided
        /// </summary>
        /// <param name='scaleX'>The scale factor in the x dimension</param>
        /// <param name='scaleY'>The scale factor in the y dimension</param>
        /// <param name="centerX">The centerX about which to scale</param>
        /// <param name="centerY">The centerY about which to scale</param>
        public void ScaleAt(double scaleX, double scaleY, double centerX, double centerY)
        {
            this *= CreateScaling(scaleX, scaleY, centerX, centerY);
        }
 
        /// <summary>
        /// Prepends a scale around the center provided to "this"
        /// </summary>
        /// <param name='scaleX'>The scale factor in the x dimension</param>
        /// <param name='scaleY'>The scale factor in the y dimension</param>
        /// <param name="centerX">The centerX about which to scale</param>
        /// <param name="centerY">The centerY about which to scale</param>
        public void ScaleAtPrepend(double scaleX, double scaleY, double centerX, double centerY)
        {
            this = CreateScaling(scaleX, scaleY, centerX, centerY) * this;
        }
 
        /// <summary>
        /// Skews this matrix
        /// </summary>
        /// <param name='skewX'>The skew angle in the x dimension in degrees</param>
        /// <param name='skewY'>The skew angle in the y dimension in degrees</param>
        public void Skew(double skewX, double skewY)
        {
            skewX %= 360;
            skewY %= 360;
            this *= CreateSkewRadians(skewX * (Math.PI/180.0),
                                      skewY * (Math.PI/180.0));
        }
 
        /// <summary>
        /// Prepends a skew to this matrix
        /// </summary>
        /// <param name='skewX'>The skew angle in the x dimension in degrees</param>
        /// <param name='skewY'>The skew angle in the y dimension in degrees</param>
        public void SkewPrepend(double skewX, double skewY)
        {
            skewX %= 360;
            skewY %= 360;
            this = CreateSkewRadians(skewX * (Math.PI/180.0),
                                     skewY * (Math.PI/180.0)) * this;
        }
 
        /// <summary>
        /// Translates this matrix
        /// </summary>
        /// <param name='offsetX'>The offset in the x dimension</param>
        /// <param name='offsetY'>The offset in the y dimension</param>
        public void Translate(double offsetX, double offsetY)
        {
            //
            // / a b 0 \   / 1 0 0 \    / a      b       0 \
            // | c d 0 | * | 0 1 0 | = |  c      d       0 |
            // \ e f 1 /   \ x y 1 /    \ e+x    f+y     1 /
            //
            // (where e = _offsetX and f == _offsetY)
            //
 
            if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
            {
                // Values would be incorrect if matrix was created using default constructor.
                // or if SetIdentity was called on a matrix which had values.
                //
                SetMatrix(1, 0,
                          0, 1,
                          offsetX, offsetY,
                          MatrixTypes.TRANSFORM_IS_TRANSLATION);
            }
            else if (_type == MatrixTypes.TRANSFORM_IS_UNKNOWN)
            {
                _offsetX += offsetX;
                _offsetY += offsetY;
            }
            else
            {
                _offsetX += offsetX;
                _offsetY += offsetY;
 
                // If matrix wasn't unknown we added a translation
                _type |= MatrixTypes.TRANSFORM_IS_TRANSLATION;
            }
 
            Debug_CheckType();
        }
 
        /// <summary>
        /// Prepends a translation to this matrix
        /// </summary>
        /// <param name='offsetX'>The offset in the x dimension</param>
        /// <param name='offsetY'>The offset in the y dimension</param>
        public void TranslatePrepend(double offsetX, double offsetY)
        {
            this = CreateTranslation(offsetX, offsetY) * this;
        }
 
#endregion Set Methods
 
#region Transformation Services
 
        /// <summary>
        /// Transform - returns the result of transforming the point by this matrix
        /// </summary>
        /// <returns>
        /// The transformed point
        /// </returns>
        /// <param name="point"> The Point to transform </param>
        public Point Transform(Point point)
        {
            Point newPoint = point;
            MultiplyPoint(ref newPoint._x, ref newPoint._y);
            return newPoint;
        }
 
        /// <summary>
        /// Transform - Transforms each point in the array by this matrix
        /// </summary>
        /// <param name="points"> The Point array to transform </param>
        public void Transform(Point[] points)
        {
            if (points != null)
            {
                for (int i = 0; i < points.Length; i++)
                {
                    MultiplyPoint(ref points[i]._x, ref points[i]._y);
                }
            }
        }
 
        /// <summary>
        /// Transform - returns the result of transforming the Vector by this matrix.
        /// </summary>
        /// <returns>
        /// The transformed vector
        /// </returns>
        /// <param name="vector"> The Vector to transform </param>
        public Vector Transform(Vector vector)
        {
            Vector newVector = vector;
            MultiplyVector(ref newVector._x, ref newVector._y);
            return newVector;
        }
 
        /// <summary>
        /// Transform - Transforms each Vector in the array by this matrix.
        /// </summary>
        /// <param name="vectors"> The Vector array to transform </param>
        public void Transform(Vector[] vectors)
        {
            if (vectors != null)
            {
                for (int i = 0; i < vectors.Length; i++)
                {
                    MultiplyVector(ref vectors[i]._x, ref vectors[i]._y);
                }
            }
        }
 
#endregion Transformation Services
 
#region Inversion
 
        /// <summary>
        /// The determinant of this matrix
        /// </summary>
        public double Determinant
        {
            get
            {
                switch (_type)
                {
                case MatrixTypes.TRANSFORM_IS_IDENTITY:
                case MatrixTypes.TRANSFORM_IS_TRANSLATION:
                    return 1.0;
                case MatrixTypes.TRANSFORM_IS_SCALING:
                case MatrixTypes.TRANSFORM_IS_SCALING | MatrixTypes.TRANSFORM_IS_TRANSLATION:
                    return(_m11  * _m22);
                default:
                    return(_m11  * _m22) - (_m12 * _m21);
                }
            }
        }
 
        /// <summary>
        /// HasInverse Property - returns true if this matrix is invertable, false otherwise.
        /// </summary>
        public bool HasInverse
        {
            get
            {
                return !DoubleUtil.IsZero(Determinant);
            }
        }
 
        /// <summary>
        /// Replaces matrix with the inverse of the transformation.  This will throw an InvalidOperationException
        /// if !HasInverse
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// This will throw an InvalidOperationException if the matrix is non-invertable
        /// </exception>
        public void Invert()
        {
            double determinant = Determinant;
 
            if (DoubleUtil.IsZero(determinant))
            {
                throw new System.InvalidOperationException(SR.Get(SRID.Transform_NotInvertible));
            }
 
            // Inversion does not change the type of a matrix.
            switch (_type)
            {
            case MatrixTypes.TRANSFORM_IS_IDENTITY:
                break;
            case MatrixTypes.TRANSFORM_IS_SCALING:
                {
                    _m11 = 1.0 / _m11;
                    _m22 = 1.0 / _m22;
                }
                break;
            case MatrixTypes.TRANSFORM_IS_TRANSLATION:
                _offsetX = -_offsetX;
                _offsetY = -_offsetY;
                break;
            case MatrixTypes.TRANSFORM_IS_SCALING | MatrixTypes.TRANSFORM_IS_TRANSLATION:
                {
                    _m11 = 1.0 / _m11;
                    _m22 = 1.0 / _m22;
                    _offsetX = -_offsetX * _m11;
                    _offsetY = -_offsetY * _m22;
                }
                break;
            default:
                {
                    double invdet = 1.0/determinant;
                    SetMatrix(_m22 * invdet,
                              -_m12 * invdet,
                              -_m21 * invdet,
                              _m11 * invdet,
                              (_m21 * _offsetY - _offsetX * _m22) * invdet,
                              (_offsetX * _m12 - _m11 * _offsetY) * invdet,
                              MatrixTypes.TRANSFORM_IS_UNKNOWN);
                }
                break;
            }
        }
 
#endregion Inversion
 
#region Public Properties
 
        /// <summary>
        /// M11
        /// </summary>
        public double M11
        {
            get
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    return 1.0;
                }
                else
                {
                    return _m11;
                }
            }
            set
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    SetMatrix(value, 0,
                              0, 1,
                              0, 0,
                              MatrixTypes.TRANSFORM_IS_SCALING);
                }
                else
                {
                    _m11 = value;
                    if (_type != MatrixTypes.TRANSFORM_IS_UNKNOWN)
                    {
                        _type |= MatrixTypes.TRANSFORM_IS_SCALING;
                    }
                }
            }
        }
 
        /// <summary>
        /// M12
        /// </summary>
        public double M12
        {
            get
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    return 0;
                }
                else
                {
                    return _m12;
                }
            }
            set
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    SetMatrix(1, value,
                              0, 1,
                              0, 0,
                              MatrixTypes.TRANSFORM_IS_UNKNOWN);
                }
                else
                {
                    _m12 = value;
                    _type = MatrixTypes.TRANSFORM_IS_UNKNOWN;
                }
            }
        }
 
        /// <summary>
        /// M22
        /// </summary>
        public double M21
        {
            get
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    return 0;
                }
                else
                {
                    return _m21;
                }
            }
            set
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    SetMatrix(1, 0,
                              value, 1,
                              0, 0,
                              MatrixTypes.TRANSFORM_IS_UNKNOWN);
                }
                else
                {
                    _m21 = value;
                    _type = MatrixTypes.TRANSFORM_IS_UNKNOWN;
                }
            }
        }
 
        /// <summary>
        /// M22
        /// </summary>
        public double M22
        {
            get
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    return 1.0;
                }
                else
                {
                    return _m22;
                }
            }
            set
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    SetMatrix(1, 0,
                              0, value,
                              0, 0,
                              MatrixTypes.TRANSFORM_IS_SCALING);
                }
                else
                {
                    _m22 = value;
                    if (_type != MatrixTypes.TRANSFORM_IS_UNKNOWN)
                    {
                        _type |= MatrixTypes.TRANSFORM_IS_SCALING;
                    }
                }
            }
        }
 
        /// <summary>
        /// OffsetX
        /// </summary>
        public double OffsetX
        {
            get
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    return 0;
                }
                else
                {
                    return _offsetX;
                }
            }
            set
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    SetMatrix(1, 0,
                              0, 1,
                              value, 0,
                              MatrixTypes.TRANSFORM_IS_TRANSLATION);
                }
                else
                {
                    _offsetX = value;
                    if (_type != MatrixTypes.TRANSFORM_IS_UNKNOWN)
                    {
                        _type |= MatrixTypes.TRANSFORM_IS_TRANSLATION;
                    }
                }
            }
        }
 
        /// <summary>
        /// OffsetY
        /// </summary>
        public double OffsetY
        {
            get
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    return 0;
                }
                else
                {
                    return _offsetY;
                }
            }
            set
            {
                if (_type == MatrixTypes.TRANSFORM_IS_IDENTITY)
                {
                    SetMatrix(1, 0,
                              0, 1,
                              0, value,
                              MatrixTypes.TRANSFORM_IS_TRANSLATION);
                }
                else
                {
                    _offsetY = value;
                    if (_type != MatrixTypes.TRANSFORM_IS_UNKNOWN)
                    {
                        _type |= MatrixTypes.TRANSFORM_IS_TRANSLATION;
                    }
                }
            }
        }
 
        #endregion Public Properties
 
        #region Internal Methods
        /// <summary>
        /// MultiplyVector
        /// </summary>
        internal void MultiplyVector(ref double x, ref double y)
        {
            switch (_type)
            {
            case MatrixTypes.TRANSFORM_IS_IDENTITY:
            case MatrixTypes.TRANSFORM_IS_TRANSLATION:
                return;
            case MatrixTypes.TRANSFORM_IS_SCALING:
            case MatrixTypes.TRANSFORM_IS_SCALING | MatrixTypes.TRANSFORM_IS_TRANSLATION:
                x *= _m11;
                y *= _m22;
                break;
            default:
                double xadd = y * _m21;
                double yadd = x * _m12;
                x *= _m11;
                x += xadd;
                y *= _m22;
                y += yadd;
                break;
            }
        }
 
        /// <summary>
        /// MultiplyPoint
        /// </summary>
        internal void MultiplyPoint(ref double x, ref double y)
        {
            switch (_type)
            {
            case MatrixTypes.TRANSFORM_IS_IDENTITY:
                return;
            case MatrixTypes.TRANSFORM_IS_TRANSLATION:
                x += _offsetX;
                y += _offsetY;
                return;
            case MatrixTypes.TRANSFORM_IS_SCALING:
                x *= _m11;
                y *= _m22;
                return;
            case MatrixTypes.TRANSFORM_IS_SCALING | MatrixTypes.TRANSFORM_IS_TRANSLATION:
                x *= _m11;
                x += _offsetX;
                y *= _m22;
                y += _offsetY;
                break;
            default:
                double xadd = y * _m21 + _offsetX;
                double yadd = x * _m12 + _offsetY;
                x *= _m11;
                x += xadd;
                y *= _m22;
                y += yadd;
                break;
            }
        }
 
        /// <summary>
        /// Creates a rotation transformation about the given point
        /// </summary>
        /// <param name='angle'>The angle to rotate specifed in radians</param>
        internal static Matrix CreateRotationRadians(double angle)
        {
            return CreateRotationRadians(angle, /* centerX = */ 0, /* centerY = */ 0);
        }
 
        /// <summary>
        /// Creates a rotation transformation about the given point
        /// </summary>
        /// <param name='angle'>The angle to rotate specifed in radians</param>
        /// <param name='centerX'>The centerX of rotation</param>
        /// <param name='centerY'>The centerY of rotation</param>
        internal static Matrix CreateRotationRadians(double angle, double centerX, double centerY)
        {
            Matrix matrix = new Matrix();
 
            double sin = Math.Sin(angle);
            double cos = Math.Cos(angle);
            double dx    = (centerX * (1.0 - cos)) + (centerY * sin);
            double dy    = (centerY * (1.0 - cos)) - (centerX * sin);
 
            matrix.SetMatrix( cos, sin,
                              -sin, cos,
                              dx,    dy,
                              MatrixTypes.TRANSFORM_IS_UNKNOWN);
 
            return matrix;
        }
 
        /// <summary>
        /// Creates a scaling transform around the given point
        /// </summary>
        /// <param name='scaleX'>The scale factor in the x dimension</param>
        /// <param name='scaleY'>The scale factor in the y dimension</param>
        /// <param name='centerX'>The centerX of scaling</param>
        /// <param name='centerY'>The centerY of scaling</param>
        internal static Matrix CreateScaling(double scaleX, double scaleY, double centerX, double centerY)
        {
            Matrix matrix = new Matrix();
 
            matrix.SetMatrix(scaleX,  0,
                             0, scaleY,
                             centerX - scaleX*centerX, centerY - scaleY*centerY,
                             MatrixTypes.TRANSFORM_IS_SCALING | MatrixTypes.TRANSFORM_IS_TRANSLATION);
 
            return matrix;
        }
 
        /// <summary>
        /// Creates a scaling transform around the origin
        /// </summary>
        /// <param name='scaleX'>The scale factor in the x dimension</param>
        /// <param name='scaleY'>The scale factor in the y dimension</param>
        internal static Matrix CreateScaling(double scaleX, double scaleY)
        {
            Matrix matrix = new Matrix();
            matrix.SetMatrix(scaleX,  0,
                             0, scaleY,
                             0, 0,
                             MatrixTypes.TRANSFORM_IS_SCALING);
            return matrix;
        }
 
        /// <summary>
        /// Creates a skew transform
        /// </summary>
        /// <param name='skewX'>The skew angle in the x dimension in degrees</param>
        /// <param name='skewY'>The skew angle in the y dimension in degrees</param>
        internal static Matrix CreateSkewRadians(double skewX, double skewY)
        {
            Matrix matrix = new Matrix();
 
            matrix.SetMatrix(1.0,  Math.Tan(skewY),
                             Math.Tan(skewX), 1.0,
                             0.0, 0.0,
                             MatrixTypes.TRANSFORM_IS_UNKNOWN);
 
            return matrix;
        }
 
        /// <summary>
        /// Sets the transformation to the given translation specified by the offset vector.
        /// </summary>
        /// <param name='offsetX'>The offset in X</param>
        /// <param name='offsetY'>The offset in Y</param>
        internal static Matrix CreateTranslation(double offsetX, double offsetY)
        {
            Matrix matrix = new Matrix();
 
            matrix.SetMatrix(1, 0,
                             0, 1,
                             offsetX, offsetY,
                             MatrixTypes.TRANSFORM_IS_TRANSLATION);
 
            return matrix;
        }
 
#endregion Internal Methods
 
#region Private Methods
        /// <summary>
        /// Sets the transformation to the identity.
        /// </summary>
        private static Matrix CreateIdentity()
        {
            Matrix matrix = new Matrix();
            matrix.SetMatrix(1, 0,
                             0, 1,
                             0, 0,
                             MatrixTypes.TRANSFORM_IS_IDENTITY);
            return matrix;
        }
 
        ///<summary>
        /// Sets the transform to
        ///             / m11, m12, 0 \
        ///             | m21, m22, 0 |
        ///             \ offsetX, offsetY, 1 /
        /// where offsetX, offsetY is the translation.
        ///</summary>
        private void SetMatrix(double m11, double m12,
                               double m21, double m22,
                               double offsetX, double offsetY,
                               MatrixTypes type)
        {
            this._m11 = m11;
            this._m12 = m12;
            this._m21 = m21;
            this._m22 = m22;
            this._offsetX = offsetX;
            this._offsetY = offsetY;
            this._type = type;
        }
 
        /// <summary>
        /// Set the type of the matrix based on its current contents
        /// </summary>
        private void DeriveMatrixType()
        {
            _type = 0;
 
            // Now classify our matrix.
            if (!(_m21 == 0 && _m12 == 0))
            {
                _type = MatrixTypes.TRANSFORM_IS_UNKNOWN;
                return;
            }
 
            if (!(_m11 == 1 && _m22 == 1))
            {
                _type = MatrixTypes.TRANSFORM_IS_SCALING;
            }
 
            if (!(_offsetX == 0 && _offsetY == 0))
            {
                _type |= MatrixTypes.TRANSFORM_IS_TRANSLATION;
            }
 
            if (0 == (_type & (MatrixTypes.TRANSFORM_IS_TRANSLATION | MatrixTypes.TRANSFORM_IS_SCALING)))
            {
                // We have an identity matrix.
                _type = MatrixTypes.TRANSFORM_IS_IDENTITY;
            }
            return;
        }
 
        /// <summary>
        /// Asserts that the matrix tag is one of the valid options and
        /// that coefficients are correct.   
        /// </summary>
        [Conditional("DEBUG")]
        private void Debug_CheckType()
        {
            switch(_type)
            {
            case MatrixTypes.TRANSFORM_IS_IDENTITY:
                return;
            case MatrixTypes.TRANSFORM_IS_UNKNOWN:
                return;
            case MatrixTypes.TRANSFORM_IS_SCALING:
                Debug.Assert(_m21 == 0);
                Debug.Assert(_m12 == 0);
                Debug.Assert(_offsetX == 0);
                Debug.Assert(_offsetY == 0);
                return;
            case MatrixTypes.TRANSFORM_IS_TRANSLATION:
                Debug.Assert(_m21 == 0);
                Debug.Assert(_m12 == 0);
                Debug.Assert(_m11 == 1);
                Debug.Assert(_m22 == 1);
                return;
            case MatrixTypes.TRANSFORM_IS_SCALING|MatrixTypes.TRANSFORM_IS_TRANSLATION:
                Debug.Assert(_m21 == 0);
                Debug.Assert(_m12 == 0);
                return;
            default:
                Debug.Assert(false);
                return;
            }
        }
 
#endregion Private Methods
    
#region Private Properties and Fields
 
        /// <summary>
        /// Efficient but conservative test for identity.  Returns
        /// true if the the matrix is identity.  If it returns false
        /// the matrix may still be identity.
        /// </summary>
        private bool IsDistinguishedIdentity
        {
            get
            {
                return _type == MatrixTypes.TRANSFORM_IS_IDENTITY;
            }
        }
 
        // The hash code for a matrix is the xor of its element's hashes.
        // Since the identity matrix has 2 1's and 4 0's its hash is 0.
        private const int c_identityHashCode = 0;
    
#endregion Private Properties and Fields
 
        internal double _m11;
        internal double _m12;
        internal double _m21;
        internal double _m22;
        internal double _offsetX;
        internal double _offsetY;
        internal MatrixTypes _type;
 
// This field is only used by unmanaged code which isn't detected by the compiler.
#pragma warning disable 0414
        // Matrix in blt'd to unmanaged code, so this is padding 
        // to align structure.
        //
        // ToDo: Microsoft, Validate that this blt will work on 64-bit
        //
        internal Int32 _padding;
#pragma warning restore 0414
    }
}