#region PDFsharp - A .NET library for processing PDF // // Authors: // Stefan Lange // // Copyright (c) 2005-2017 empira Software GmbH, Cologne Area (Germany) // // http://www.pdfsharp.com // http://sourceforge.net/projects/pdfsharp // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #endregion using System; using System.Diagnostics; using System.Globalization; using System.Runtime.InteropServices; #if GDI using System.Drawing; using System.Drawing.Drawing2D; #endif #if WPF using System.Windows; using System.Windows.Media; #endif #if !EDF_CORE using PdfSharp.Internal; #else using PdfSharp.Internal; #endif // ReSharper disable RedundantNameQualifier #if !EDF_CORE namespace PdfSharp.Drawing #else namespace Edf.Drawing #endif { /// /// Represents a 3-by-3 matrix that represents an affine 2D transformation. /// [DebuggerDisplay("{DebuggerDisplay}")] [Serializable, StructLayout(LayoutKind.Sequential)] //, TypeConverter(typeof(MatrixConverter)), ValueSerializer(typeof(MatrixValueSerializer))] public struct XMatrix : IFormattable { [Flags] internal enum XMatrixTypes { Identity = 0, Translation = 1, Scaling = 2, Unknown = 4 } /// /// Initializes a new instance of the XMatrix struct. /// public XMatrix(double m11, double m12, double m21, double m22, double offsetX, double offsetY) { _m11 = m11; _m12 = m12; _m21 = m21; _m22 = m22; _offsetX = offsetX; _offsetY = offsetY; _type = XMatrixTypes.Unknown; //_padding = 0; DeriveMatrixType(); } /// /// Gets the identity matrix. /// public static XMatrix Identity { get { return s_identity; } } /// /// Sets this matrix into an identity matrix. /// public void SetIdentity() { _type = XMatrixTypes.Identity; } /// /// Gets a value indicating whether this matrix instance is the identity matrix. /// public bool IsIdentity { get { // ReSharper disable CompareOfFloatsByEqualityOperator if (_type == XMatrixTypes.Identity) return true; if (_m11 == 1.0 && _m12 == 0 && _m21 == 0 && _m22 == 1.0 && _offsetX == 0 && _offsetY == 0) { _type = XMatrixTypes.Identity; return true; } return false; // ReSharper restore CompareOfFloatsByEqualityOperator } } ///// ///// Gets an array of double values that represents the elements of this matrix. ///// //[Obsolete("Use GetElements().")] //public double[] Elements //{ // get { return GetElements(); } //} /// /// Gets an array of double values that represents the elements of this matrix. /// public double[] GetElements() { if (_type == XMatrixTypes.Identity) return new double[] { 1, 0, 0, 1, 0, 0 }; return new double[] { _m11, _m12, _m21, _m22, _offsetX, _offsetY }; } /// /// Multiplies two matrices. /// public static XMatrix operator *(XMatrix trans1, XMatrix trans2) { MatrixHelper.MultiplyMatrix(ref trans1, ref trans2); return trans1; } /// /// Multiplies two matrices. /// public static XMatrix Multiply(XMatrix trans1, XMatrix trans2) { MatrixHelper.MultiplyMatrix(ref trans1, ref trans2); return trans1; } /// /// Appends the specified matrix to this matrix. /// public void Append(XMatrix matrix) { this *= matrix; } /// /// Prepends the specified matrix to this matrix. /// public void Prepend(XMatrix matrix) { this = matrix * this; } /// /// Appends the specified matrix to this matrix. /// [Obsolete("Use Append.")] public void Multiply(XMatrix matrix) { Append(matrix); } /// /// Prepends the specified matrix to this matrix. /// [Obsolete("Use Prepend.")] public void MultiplyPrepend(XMatrix matrix) { Prepend(matrix); } /// /// Multiplies this matrix with the specified matrix. /// public void Multiply(XMatrix matrix, XMatrixOrder order) { if (_type == XMatrixTypes.Identity) this = CreateIdentity(); // Must use properties, the fields can be invalid if the matrix is identity matrix. double t11 = M11; double t12 = M12; double t21 = M21; double t22 = M22; double tdx = OffsetX; double tdy = OffsetY; if (order == XMatrixOrder.Append) { _m11 = t11 * matrix.M11 + t12 * matrix.M21; _m12 = t11 * matrix.M12 + t12 * matrix.M22; _m21 = t21 * matrix.M11 + t22 * matrix.M21; _m22 = t21 * matrix.M12 + t22 * matrix.M22; _offsetX = tdx * matrix.M11 + tdy * matrix.M21 + matrix.OffsetX; _offsetY = tdx * matrix.M12 + tdy * matrix.M22 + matrix.OffsetY; } else { _m11 = t11 * matrix.M11 + t21 * matrix.M12; _m12 = t12 * matrix.M11 + t22 * matrix.M12; _m21 = t11 * matrix.M21 + t21 * matrix.M22; _m22 = t12 * matrix.M21 + t22 * matrix.M22; _offsetX = t11 * matrix.OffsetX + t21 * matrix.OffsetY + tdx; _offsetY = t12 * matrix.OffsetX + t22 * matrix.OffsetY + tdy; } DeriveMatrixType(); } /// /// Appends a translation of the specified offsets to this matrix. /// [Obsolete("Use TranslateAppend or TranslatePrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)] public void Translate(double offsetX, double offsetY) { throw new InvalidOperationException("Temporarily out of order."); //if (_type == XMatrixTypes.Identity) //{ // SetMatrix(1.0, 0, 0, 1.0, offsetX, offsetY, XMatrixTypes.Translation); //} //else if (_type == XMatrixTypes.Unknown) //{ // _offsetX += offsetX; // _offsetY += offsetY; //} //else //{ // _offsetX += offsetX; // _offsetY += offsetY; // _type |= XMatrixTypes.Translation; //} } /// /// Appends a translation of the specified offsets to this matrix. /// public void TranslateAppend(double offsetX, double offsetY) // TODO: will become default { if (_type == XMatrixTypes.Identity) { SetMatrix(1, 0, 0, 1, offsetX, offsetY, XMatrixTypes.Translation); } else if (_type == XMatrixTypes.Unknown) { _offsetX += offsetX; _offsetY += offsetY; } else { _offsetX += offsetX; _offsetY += offsetY; _type |= XMatrixTypes.Translation; } } /// /// Prepends a translation of the specified offsets to this matrix. /// public void TranslatePrepend(double offsetX, double offsetY) { this = CreateTranslation(offsetX, offsetY) * this; } /// /// Translates the matrix with the specified offsets. /// public void Translate(double offsetX, double offsetY, XMatrixOrder order) { if (_type == XMatrixTypes.Identity) this = CreateIdentity(); if (order == XMatrixOrder.Append) { _offsetX += offsetX; _offsetY += offsetY; } else { _offsetX += offsetX * _m11 + offsetY * _m21; _offsetY += offsetX * _m12 + offsetY * _m22; } DeriveMatrixType(); } /// /// Appends the specified scale vector to this matrix. /// [Obsolete("Use ScaleAppend or ScalePrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)] public void Scale(double scaleX, double scaleY) { this = CreateScaling(scaleX, scaleY) * this; } /// /// Appends the specified scale vector to this matrix. /// public void ScaleAppend(double scaleX, double scaleY) // TODO: will become default { this *= CreateScaling(scaleX, scaleY); } /// /// Prepends the specified scale vector to this matrix. /// public void ScalePrepend(double scaleX, double scaleY) { this = CreateScaling(scaleX, scaleY) * this; } /// /// Scales the matrix with the specified scalars. /// public void Scale(double scaleX, double scaleY, XMatrixOrder order) { if (_type == XMatrixTypes.Identity) this = CreateIdentity(); if (order == XMatrixOrder.Append) { _m11 *= scaleX; _m12 *= scaleY; _m21 *= scaleX; _m22 *= scaleY; _offsetX *= scaleX; _offsetY *= scaleY; } else { _m11 *= scaleX; _m12 *= scaleX; _m21 *= scaleY; _m22 *= scaleY; } DeriveMatrixType(); } /// /// Scales the matrix with the specified scalar. /// [Obsolete("Use ScaleAppend or ScalePrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)] // ReSharper disable InconsistentNaming public void Scale(double scaleXY) // ReSharper restore InconsistentNaming { throw new InvalidOperationException("Temporarily out of order."); //Scale(scaleXY, scaleXY, XMatrixOrder.Prepend); } /// /// Appends the specified scale vector to this matrix. /// // ReSharper disable InconsistentNaming public void ScaleAppend(double scaleXY) // ReSharper restore InconsistentNaming { Scale(scaleXY, scaleXY, XMatrixOrder.Append); } /// /// Prepends the specified scale vector to this matrix. /// // ReSharper disable InconsistentNaming public void ScalePrepend(double scaleXY) // ReSharper restore InconsistentNaming { Scale(scaleXY, scaleXY, XMatrixOrder.Prepend); } /// /// Scales the matrix with the specified scalar. /// // ReSharper disable InconsistentNaming public void Scale(double scaleXY, XMatrixOrder order) // ReSharper restore InconsistentNaming { Scale(scaleXY, scaleXY, order); } /// /// Function is obsolete. /// [Obsolete("Use ScaleAtAppend or ScaleAtPrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)] public void ScaleAt(double scaleX, double scaleY, double centerX, double centerY) { throw new InvalidOperationException("Temporarily out of order."); //this *= CreateScaling(scaleX, scaleY, centerX, centerY); } /// /// Apppends the specified scale about the specified point of this matrix. /// public void ScaleAtAppend(double scaleX, double scaleY, double centerX, double centerY) // TODO: will become default { this *= CreateScaling(scaleX, scaleY, centerX, centerY); } /// /// Prepends the specified scale about the specified point of this matrix. /// public void ScaleAtPrepend(double scaleX, double scaleY, double centerX, double centerY) { this = CreateScaling(scaleX, scaleY, centerX, centerY) * this; } /// /// Function is obsolete. /// [Obsolete("Use RotateAppend or RotatePrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)] public void Rotate(double angle) { throw new InvalidOperationException("Temporarily out of order."); //angle = angle % 360.0; //this *= CreateRotationRadians(angle * Const.Deg2Rad); } /// /// Appends a rotation of the specified angle to this matrix. /// public void RotateAppend(double angle) // TODO: will become default Rotate { angle = angle % 360.0; this *= CreateRotationRadians(angle * Const.Deg2Rad); } /// /// Prepends a rotation of the specified angle to this matrix. /// public void RotatePrepend(double angle) { angle = angle % 360.0; this = CreateRotationRadians(angle * Const.Deg2Rad) * this; } /// /// Rotates the matrix with the specified angle. /// public void Rotate(double angle, XMatrixOrder order) { if (_type == XMatrixTypes.Identity) this = CreateIdentity(); angle = angle * Const.Deg2Rad; double cos = Math.Cos(angle); double sin = Math.Sin(angle); if (order == XMatrixOrder.Append) { double t11 = _m11; double t12 = _m12; double t21 = _m21; double t22 = _m22; double tdx = _offsetX; double tdy = _offsetY; _m11 = t11 * cos - t12 * sin; _m12 = t11 * sin + t12 * cos; _m21 = t21 * cos - t22 * sin; _m22 = t21 * sin + t22 * cos; _offsetX = tdx * cos - tdy * sin; _offsetY = tdx * sin + tdy * cos; } else { double t11 = _m11; double t12 = _m12; double t21 = _m21; double t22 = _m22; _m11 = t11 * cos + t21 * sin; _m12 = t12 * cos + t22 * sin; _m21 = -t11 * sin + t21 * cos; _m22 = -t12 * sin + t22 * cos; } DeriveMatrixType(); } /// /// Function is obsolete. /// [Obsolete("Use RotateAtAppend or RotateAtPrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)] public void RotateAt(double angle, double centerX, double centerY) { throw new InvalidOperationException("Temporarily out of order."); //angle = angle % 360.0; //this *= CreateRotationRadians(angle * Const.Deg2Rad, centerX, centerY); } /// /// Appends a rotation of the specified angle at the specified point to this matrix. /// public void RotateAtAppend(double angle, double centerX, double centerY) // TODO: will become default { angle = angle % 360.0; this *= CreateRotationRadians(angle * Const.Deg2Rad, centerX, centerY); } /// /// Prepends a rotation of the specified angle at the specified point to this matrix. /// public void RotateAtPrepend(double angle, double centerX, double centerY) { angle = angle % 360.0; this = CreateRotationRadians(angle * Const.Deg2Rad, centerX, centerY) * this; } /// /// Rotates the matrix with the specified angle at the specified point. /// [Obsolete("Use RotateAtAppend or RotateAtPrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)] public void RotateAt(double angle, XPoint point) { throw new InvalidOperationException("Temporarily out of order."); //RotateAt(angle, point, XMatrixOrder.Prepend); } /// /// Appends a rotation of the specified angle at the specified point to this matrix. /// public void RotateAtAppend(double angle, XPoint point) { RotateAt(angle, point, XMatrixOrder.Append); } /// /// Prepends a rotation of the specified angle at the specified point to this matrix. /// public void RotateAtPrepend(double angle, XPoint point) { RotateAt(angle, point, XMatrixOrder.Prepend); } /// /// Rotates the matrix with the specified angle at the specified point. /// public void RotateAt(double angle, XPoint point, XMatrixOrder order) { if (order == XMatrixOrder.Append) { angle = angle % 360.0; this *= CreateRotationRadians(angle * Const.Deg2Rad, point.X, point.Y); //Translate(point.X, point.Y, order); //Rotate(angle, order); //Translate(-point.X, -point.Y, order); } else { angle = angle % 360.0; this = CreateRotationRadians(angle * Const.Deg2Rad, point.X, point.Y) * this; } DeriveMatrixType(); } /// /// Function is obsolete. /// [Obsolete("Use ShearAppend or ShearPrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)] public void Shear(double shearX, double shearY) { throw new InvalidOperationException("Temporarily out of order."); //Shear(shearX, shearY, XMatrixOrder.Prepend); } /// /// Appends a skew of the specified degrees in the x and y dimensions to this matrix. /// public void ShearAppend(double shearX, double shearY) // TODO: will become default { Shear(shearX, shearY, XMatrixOrder.Append); } /// /// Prepends a skew of the specified degrees in the x and y dimensions to this matrix. /// public void ShearPrepend(double shearX, double shearY) { Shear(shearX, shearY, XMatrixOrder.Prepend); } /// /// Shears the matrix with the specified scalars. /// public void Shear(double shearX, double shearY, XMatrixOrder order) { if (_type == XMatrixTypes.Identity) this = CreateIdentity(); double t11 = _m11; double t12 = _m12; double t21 = _m21; double t22 = _m22; double tdx = _offsetX; double tdy = _offsetY; if (order == XMatrixOrder.Append) { _m11 += shearX * t12; _m12 += shearY * t11; _m21 += shearX * t22; _m22 += shearY * t21; _offsetX += shearX * tdy; _offsetY += shearY * tdx; } else { _m11 += shearY * t21; _m12 += shearY * t22; _m21 += shearX * t11; _m22 += shearX * t12; } DeriveMatrixType(); } /// /// Function is obsolete. /// [Obsolete("Use SkewAppend or SkewPrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)] public void Skew(double skewX, double skewY) { throw new InvalidOperationException("Temporarily out of order."); //skewX = skewX % 360.0; //skewY = skewY % 360.0; //this *= CreateSkewRadians(skewX * Const.Deg2Rad, skewY * Const.Deg2Rad); } /// /// Appends a skew of the specified degrees in the x and y dimensions to this matrix. /// public void SkewAppend(double skewX, double skewY) { skewX = skewX % 360.0; skewY = skewY % 360.0; this *= CreateSkewRadians(skewX * Const.Deg2Rad, skewY * Const.Deg2Rad); } /// /// Prepends a skew of the specified degrees in the x and y dimensions to this matrix. /// public void SkewPrepend(double skewX, double skewY) { skewX = skewX % 360.0; skewY = skewY % 360.0; this = CreateSkewRadians(skewX * Const.Deg2Rad, skewY * Const.Deg2Rad) * this; } /// /// Transforms the specified point by this matrix and returns the result. /// public XPoint Transform(XPoint point) { double x = point.X; double y = point.Y; MultiplyPoint(ref x, ref y); return new XPoint(x, y); } /// /// Transforms the specified points by this matrix. /// public void Transform(XPoint[] points) { if (points != null) { int count = points.Length; for (int idx = 0; idx < count; idx++) { double x = points[idx].X; double y = points[idx].Y; MultiplyPoint(ref x, ref y); points[idx].X = x; points[idx].Y = y; } } } /// /// Multiplies all points of the specified array with the this matrix. /// public void TransformPoints(XPoint[] points) { if (points == null) throw new ArgumentNullException("points"); if (IsIdentity) return; int count = points.Length; for (int idx = 0; idx < count; idx++) { double x = points[idx].X; double y = points[idx].Y; points[idx].X = x * _m11 + y * _m21 + _offsetX; points[idx].Y = x * _m12 + y * _m22 + _offsetY; } } #if GDI /// /// Multiplies all points of the specified array with the this matrix. /// public void TransformPoints(System.Drawing.Point[] points) { if (points == null) throw new ArgumentNullException("points"); if (IsIdentity) return; int count = points.Length; for (int idx = 0; idx < count; idx++) { double x = points[idx].X; double y = points[idx].Y; points[idx].X = (int)(x * _m11 + y * _m21 + _offsetX); points[idx].Y = (int)(x * _m12 + y * _m22 + _offsetY); } } #endif #if WPF /// /// Transforms an array of points. /// public void TransformPoints(System.Windows.Point[] points) { if (points == null) throw new ArgumentNullException("points"); if (IsIdentity) return; int count = points.Length; for (int idx = 0; idx < count; idx++) { double x = points[idx].X; double y = points[idx].Y; points[idx].X = (int)(x * _m11 + y * _m21 + _offsetX); points[idx].Y = (int)(x * _m12 + y * _m22 + _offsetY); } } #endif /// /// Transforms the specified vector by this Matrix and returns the result. /// public XVector Transform(XVector vector) { double x = vector.X; double y = vector.Y; MultiplyVector(ref x, ref y); return new XVector(x, y); } /// /// Transforms the specified vectors by this matrix. /// public void Transform(XVector[] vectors) { if (vectors != null) { int count = vectors.Length; for (int idx = 0; idx < count; idx++) { double x = vectors[idx].X; double y = vectors[idx].Y; MultiplyVector(ref x, ref y); vectors[idx].X = x; vectors[idx].Y = y; } } } #if GDI /// /// Multiplies all vectors of the specified array with the this matrix. The translation elements /// of this matrix (third row) are ignored. /// public void TransformVectors(PointF[] points) { if (points == null) throw new ArgumentNullException("points"); if (IsIdentity) return; int count = points.Length; for (int idx = 0; idx < count; idx++) { double x = points[idx].X; double y = points[idx].Y; points[idx].X = (float)(x * _m11 + y * _m21 + _offsetX); points[idx].Y = (float)(x * _m12 + y * _m22 + _offsetY); } } #endif /// /// Gets the determinant of this matrix. /// public double Determinant { get { switch (_type) { case XMatrixTypes.Identity: case XMatrixTypes.Translation: return 1.0; case XMatrixTypes.Scaling: case XMatrixTypes.Scaling | XMatrixTypes.Translation: return _m11 * _m22; } return (_m11 * _m22) - (_m12 * _m21); } } /// /// Gets a value that indicates whether this matrix is invertible. /// public bool HasInverse { get { return !DoubleUtil.IsZero(Determinant); } } /// /// Inverts the matrix. /// public void Invert() { double determinant = Determinant; if (DoubleUtil.IsZero(determinant)) throw new InvalidOperationException("NotInvertible"); //SR.Get(SRID.Transform_NotInvertible, new object[0])); switch (_type) { case XMatrixTypes.Identity: break; case XMatrixTypes.Translation: _offsetX = -_offsetX; _offsetY = -_offsetY; return; case XMatrixTypes.Scaling: _m11 = 1.0 / _m11; _m22 = 1.0 / _m22; return; case XMatrixTypes.Scaling | XMatrixTypes.Translation: _m11 = 1.0 / _m11; _m22 = 1.0 / _m22; _offsetX = -_offsetX * _m11; _offsetY = -_offsetY * _m22; return; default: { double detInvers = 1.0 / determinant; SetMatrix(_m22 * detInvers, -_m12 * detInvers, -_m21 * detInvers, _m11 * detInvers, (_m21 * _offsetY - _offsetX * _m22) * detInvers, (_offsetX * _m12 - _m11 * _offsetY) * detInvers, XMatrixTypes.Unknown); break; } } } /// /// Gets or sets the value of the first row and first column of this matrix. /// public double M11 { get { if (_type == XMatrixTypes.Identity) return 1.0; return _m11; } set { if (_type == XMatrixTypes.Identity) SetMatrix(value, 0, 0, 1, 0, 0, XMatrixTypes.Scaling); else { _m11 = value; if (_type != XMatrixTypes.Unknown) _type |= XMatrixTypes.Scaling; } } } /// /// Gets or sets the value of the first row and second column of this matrix. /// public double M12 { get { if (_type == XMatrixTypes.Identity) return 0; return _m12; } set { if (_type == XMatrixTypes.Identity) SetMatrix(1, value, 0, 1, 0, 0, XMatrixTypes.Unknown); else { _m12 = value; _type = XMatrixTypes.Unknown; } } } /// /// Gets or sets the value of the second row and first column of this matrix. /// public double M21 { get { if (_type == XMatrixTypes.Identity) return 0; return _m21; } set { if (_type == XMatrixTypes.Identity) SetMatrix(1, 0, value, 1, 0, 0, XMatrixTypes.Unknown); else { _m21 = value; _type = XMatrixTypes.Unknown; } } } /// /// Gets or sets the value of the second row and second column of this matrix. /// public double M22 { get { if (_type == XMatrixTypes.Identity) return 1.0; return _m22; } set { if (_type == XMatrixTypes.Identity) SetMatrix(1, 0, 0, value, 0, 0, XMatrixTypes.Scaling); else { _m22 = value; if (_type != XMatrixTypes.Unknown) _type |= XMatrixTypes.Scaling; } } } /// /// Gets or sets the value of the third row and first column of this matrix. /// public double OffsetX { get { if (_type == XMatrixTypes.Identity) return 0; return _offsetX; } set { if (_type == XMatrixTypes.Identity) SetMatrix(1, 0, 0, 1, value, 0, XMatrixTypes.Translation); else { _offsetX = value; if (_type != XMatrixTypes.Unknown) _type |= XMatrixTypes.Translation; } } } /// /// Gets or sets the value of the third row and second column of this matrix. /// public double OffsetY { get { if (_type == XMatrixTypes.Identity) return 0; return _offsetY; } set { if (_type == XMatrixTypes.Identity) SetMatrix(1, 0, 0, 1, 0, value, XMatrixTypes.Translation); else { _offsetY = value; if (_type != XMatrixTypes.Unknown) _type |= XMatrixTypes.Translation; } } } #if GDI //#if UseGdiObjects /// /// Converts this matrix to a System.Drawing.Drawing2D.Matrix object. /// public System.Drawing.Drawing2D.Matrix ToGdiMatrix() { if (IsIdentity) return new System.Drawing.Drawing2D.Matrix(); return new System.Drawing.Drawing2D.Matrix((float)_m11, (float)_m12, (float)_m21, (float)_m22, (float)_offsetX, (float)_offsetY); } //#endif #endif #if WPF /// Converts this matrix to a System.Windows.Media.Matrix object. /// /// public System.Windows.Media.Matrix ToWpfMatrix() { return (System.Windows.Media.Matrix)this; //return new System.Windows.Media.Matrix(_m11, _m12, _m21, _m22, _offsetX, _offsetY); } #endif #if GDI /// /// Explicitly converts a XMatrix to a Matrix. /// public static explicit operator System.Drawing.Drawing2D.Matrix(XMatrix matrix) { if (matrix.IsIdentity) return new System.Drawing.Drawing2D.Matrix(); return new System.Drawing.Drawing2D.Matrix( (float)matrix._m11, (float)matrix._m12, (float)matrix._m21, (float)matrix._m22, (float)matrix._offsetX, (float)matrix._offsetY); } #endif #if WPF /// /// Explicitly converts an XMatrix to a Matrix. /// public static explicit operator System.Windows.Media.Matrix(XMatrix matrix) { if (matrix.IsIdentity) return new System.Windows.Media.Matrix(); return new System.Windows.Media.Matrix( matrix._m11, matrix._m12, matrix._m21, matrix._m22, matrix._offsetX, matrix._offsetY); } #endif #if GDI /// /// Implicitly converts a Matrix to an XMatrix. /// public static implicit operator XMatrix(System.Drawing.Drawing2D.Matrix matrix) { float[] elements = matrix.Elements; return new XMatrix(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]); } #endif #if WPF /// /// Implicitly converts a Matrix to an XMatrix. /// public static implicit operator XMatrix(System.Windows.Media.Matrix matrix) { return new XMatrix(matrix.M11, matrix.M12, matrix.M21, matrix.M22, matrix.OffsetX, matrix.OffsetY); } #endif /// /// Determines whether the two matrices are equal. /// public static bool operator ==(XMatrix matrix1, XMatrix matrix2) { // ReSharper disable CompareOfFloatsByEqualityOperator if (matrix1.IsDistinguishedIdentity || matrix2.IsDistinguishedIdentity) return (matrix1.IsIdentity == matrix2.IsIdentity); return matrix1.M11 == matrix2.M11 && matrix1.M12 == matrix2.M12 && matrix1.M21 == matrix2.M21 && matrix1.M22 == matrix2.M22 && matrix1.OffsetX == matrix2.OffsetX && matrix1.OffsetY == matrix2.OffsetY; // ReSharper restore CompareOfFloatsByEqualityOperator } /// /// Determines whether the two matrices are not equal. /// public static bool operator !=(XMatrix matrix1, XMatrix matrix2) { return !(matrix1 == matrix2); } /// /// Determines whether the two matrices are equal. /// public static bool Equals(XMatrix matrix1, XMatrix matrix2) { if (matrix1.IsDistinguishedIdentity || matrix2.IsDistinguishedIdentity) return matrix1.IsIdentity == matrix2.IsIdentity; return matrix1.M11.Equals(matrix2.M11) && matrix1.M12.Equals(matrix2.M12) && matrix1.M21.Equals(matrix2.M21) && matrix1.M22.Equals(matrix2.M22) && matrix1.OffsetX.Equals(matrix2.OffsetX) && matrix1.OffsetY.Equals(matrix2.OffsetY); } /// /// Determines whether this matrix is equal to the specified object. /// public override bool Equals(object o) { if (!(o is XMatrix)) return false; return Equals(this, (XMatrix)o); } /// /// Determines whether this matrix is equal to the specified matrix. /// public bool Equals(XMatrix value) { return Equals(this, value); } /// /// Returns the hash code for this instance. /// public override int GetHashCode() { if (IsDistinguishedIdentity) return 0; return M11.GetHashCode() ^ M12.GetHashCode() ^ M21.GetHashCode() ^ M22.GetHashCode() ^ OffsetX.GetHashCode() ^ OffsetY.GetHashCode(); } /// /// Parses a matrix from a string. /// public static XMatrix Parse(string source) { IFormatProvider cultureInfo = CultureInfo.InvariantCulture; //.GetCultureInfo("en-us"); TokenizerHelper helper = new TokenizerHelper(source, cultureInfo); string str = helper.NextTokenRequired(); XMatrix identity = str == "Identity" ? Identity : new XMatrix( Convert.ToDouble(str, cultureInfo), Convert.ToDouble(helper.NextTokenRequired(), cultureInfo), Convert.ToDouble(helper.NextTokenRequired(), cultureInfo), Convert.ToDouble(helper.NextTokenRequired(), cultureInfo), Convert.ToDouble(helper.NextTokenRequired(), cultureInfo), Convert.ToDouble(helper.NextTokenRequired(), cultureInfo)); helper.LastTokenRequired(); return identity; } /// /// Converts this XMatrix to a human readable string. /// public override string ToString() { return ConvertToString(null, null); } /// /// Converts this XMatrix to a human readable string. /// public string ToString(IFormatProvider provider) { return ConvertToString(null, provider); } /// /// Converts this XMatrix to a human readable string. /// string IFormattable.ToString(string format, IFormatProvider provider) { return ConvertToString(format, provider); } internal string ConvertToString(string format, IFormatProvider provider) { if (IsIdentity) return "Identity"; char numericListSeparator = TokenizerHelper.GetNumericListSeparator(provider); provider = provider ?? CultureInfo.InvariantCulture; // ReSharper disable FormatStringProblem return string.Format(provider, "{1:" + format + "}{0}{2:" + format + "}{0}{3:" + format + "}{0}{4:" + format + "}{0}{5:" + format + "}{0}{6:" + format + "}", new object[] { numericListSeparator, _m11, _m12, _m21, _m22, _offsetX, _offsetY }); // ReSharper restore FormatStringProblem } internal void MultiplyVector(ref double x, ref double y) { switch (_type) { case XMatrixTypes.Identity: case XMatrixTypes.Translation: return; case XMatrixTypes.Scaling: case XMatrixTypes.Scaling | XMatrixTypes.Translation: x *= _m11; y *= _m22; return; } double d1 = y * _m21; double d2 = x * _m12; x *= _m11; x += d1; y *= _m22; y += d2; } internal void MultiplyPoint(ref double x, ref double y) { switch (_type) { case XMatrixTypes.Identity: return; case XMatrixTypes.Translation: x += _offsetX; y += _offsetY; return; case XMatrixTypes.Scaling: x *= _m11; y *= _m22; return; case (XMatrixTypes.Scaling | XMatrixTypes.Translation): x *= _m11; x += _offsetX; y *= _m22; y += _offsetY; return; } double d1 = (y * _m21) + _offsetX; double d2 = (x * _m12) + _offsetY; x *= _m11; x += d1; y *= _m22; y += d2; } internal static XMatrix CreateTranslation(double offsetX, double offsetY) { XMatrix matrix = new XMatrix(); matrix.SetMatrix(1, 0, 0, 1, offsetX, offsetY, XMatrixTypes.Translation); return matrix; } internal static XMatrix CreateRotationRadians(double angle) { return CreateRotationRadians(angle, 0, 0); } internal static XMatrix CreateRotationRadians(double angle, double centerX, double centerY) { XMatrix matrix = new XMatrix(); double sin = Math.Sin(angle); double cos = Math.Cos(angle); double offsetX = (centerX * (1.0 - cos)) + (centerY * sin); double offsetY = (centerY * (1.0 - cos)) - (centerX * sin); matrix.SetMatrix(cos, sin, -sin, cos, offsetX, offsetY, XMatrixTypes.Unknown); return matrix; } internal static XMatrix CreateScaling(double scaleX, double scaleY) { XMatrix matrix = new XMatrix(); matrix.SetMatrix(scaleX, 0, 0, scaleY, 0, 0, XMatrixTypes.Scaling); return matrix; } internal static XMatrix CreateScaling(double scaleX, double scaleY, double centerX, double centerY) { XMatrix matrix = new XMatrix(); matrix.SetMatrix(scaleX, 0, 0, scaleY, centerX - scaleX * centerX, centerY - scaleY * centerY, XMatrixTypes.Scaling | XMatrixTypes.Translation); return matrix; } internal static XMatrix CreateSkewRadians(double skewX, double skewY, double centerX, double centerY) { XMatrix matrix = new XMatrix(); matrix.Append(CreateTranslation(-centerX, -centerY)); matrix.Append(new XMatrix(1, Math.Tan(skewY), Math.Tan(skewX), 1, 0, 0)); matrix.Append(CreateTranslation(centerX, centerY)); return matrix; } internal static XMatrix CreateSkewRadians(double skewX, double skewY) { XMatrix matrix = new XMatrix(); matrix.SetMatrix(1, Math.Tan(skewY), Math.Tan(skewX), 1, 0, 0, XMatrixTypes.Unknown); return matrix; } static XMatrix CreateIdentity() { XMatrix matrix = new XMatrix(); matrix.SetMatrix(1, 0, 0, 1, 0, 0, XMatrixTypes.Identity); return matrix; } /// /// Sets the matrix. /// void SetMatrix(double m11, double m12, double m21, double m22, double offsetX, double offsetY, XMatrixTypes type) { _m11 = m11; _m12 = m12; _m21 = m21; _m22 = m22; _offsetX = offsetX; _offsetY = offsetY; _type = type; } void DeriveMatrixType() { // ReSharper disable CompareOfFloatsByEqualityOperator _type = XMatrixTypes.Identity; if (_m12 != 0 || _m21 != 0) { _type = XMatrixTypes.Unknown; } else { if (_m11 != 1 || _m22 != 1) _type = XMatrixTypes.Scaling; if (_offsetX != 0 || _offsetY != 0) _type |= XMatrixTypes.Translation; if ((_type & (XMatrixTypes.Scaling | XMatrixTypes.Translation)) == XMatrixTypes.Identity) _type = XMatrixTypes.Identity; } // ReSharper restore CompareOfFloatsByEqualityOperator } bool IsDistinguishedIdentity { get { return (_type == XMatrixTypes.Identity); } } // Keep the fields private and force using the properties. // This prevents using m11 and m22 by mistake when the matrix is identity. double _m11; double _m12; double _m21; double _m22; double _offsetX; double _offsetY; XMatrixTypes _type; static readonly XMatrix s_identity = CreateIdentity(); /// /// Internal matrix helper. /// internal static class MatrixHelper { // Fast mutiplication taking matrix type into account. Reflectored from WPF. internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2) { XMatrixTypes type1 = matrix1._type; XMatrixTypes type2 = matrix2._type; if (type2 != XMatrixTypes.Identity) { if (type1 == XMatrixTypes.Identity) matrix1 = matrix2; else if (type2 == XMatrixTypes.Translation) { matrix1._offsetX += matrix2._offsetX; matrix1._offsetY += matrix2._offsetY; if (type1 != XMatrixTypes.Unknown) matrix1._type |= XMatrixTypes.Translation; } else if (type1 == XMatrixTypes.Translation) { double num = matrix1._offsetX; double num2 = matrix1._offsetY; matrix1 = matrix2; matrix1._offsetX = num * matrix2._m11 + num2 * matrix2._m21 + matrix2._offsetX; matrix1._offsetY = num * matrix2._m12 + num2 * matrix2._m22 + matrix2._offsetY; if (type2 == XMatrixTypes.Unknown) matrix1._type = XMatrixTypes.Unknown; else matrix1._type = XMatrixTypes.Scaling | XMatrixTypes.Translation; } else { switch ((((int)type1) << 4) | (int)type2) { case 0x22: matrix1._m11 *= matrix2._m11; matrix1._m22 *= matrix2._m22; return; case 0x23: matrix1._m11 *= matrix2._m11; matrix1._m22 *= matrix2._m22; matrix1._offsetX = matrix2._offsetX; matrix1._offsetY = matrix2._offsetY; matrix1._type = XMatrixTypes.Scaling | XMatrixTypes.Translation; return; case 0x24: case 0x34: case 0x42: case 0x43: case 0x44: matrix1 = new XMatrix( matrix1._m11 * matrix2._m11 + matrix1._m12 * matrix2._m21, matrix1._m11 * matrix2._m12 + matrix1._m12 * matrix2._m22, matrix1._m21 * matrix2._m11 + matrix1._m22 * matrix2._m21, matrix1._m21 * matrix2._m12 + matrix1._m22 * matrix2._m22, matrix1._offsetX * matrix2._m11 + matrix1._offsetY * matrix2._m21 + matrix2._offsetX, matrix1._offsetX * matrix2._m12 + matrix1._offsetY * matrix2._m22 + matrix2._offsetY); return; case 50: matrix1._m11 *= matrix2._m11; matrix1._m22 *= matrix2._m22; matrix1._offsetX *= matrix2._m11; matrix1._offsetY *= matrix2._m22; return; case 0x33: matrix1._m11 *= matrix2._m11; matrix1._m22 *= matrix2._m22; matrix1._offsetX = matrix2._m11 * matrix1._offsetX + matrix2._offsetX; matrix1._offsetY = matrix2._m22 * matrix1._offsetY + matrix2._offsetY; return; } } } } internal static void PrependOffset(ref XMatrix matrix, double offsetX, double offsetY) { if (matrix._type == XMatrixTypes.Identity) { matrix = new XMatrix(1, 0, 0, 1, offsetX, offsetY); matrix._type = XMatrixTypes.Translation; } else { matrix._offsetX += (matrix._m11 * offsetX) + (matrix._m21 * offsetY); matrix._offsetY += (matrix._m12 * offsetX) + (matrix._m22 * offsetY); if (matrix._type != XMatrixTypes.Unknown) matrix._type |= XMatrixTypes.Translation; } } internal static void TransformRect(ref XRect rect, ref XMatrix matrix) { if (!rect.IsEmpty) { XMatrixTypes type = matrix._type; if (type != XMatrixTypes.Identity) { if ((type & XMatrixTypes.Scaling) != XMatrixTypes.Identity) { rect.X *= matrix._m11; rect.Y *= matrix._m22; rect.Width *= matrix._m11; rect.Height *= matrix._m22; if (rect.Width < 0) { rect.X += rect.Width; rect.Width = -rect.Width; } if (rect.Height < 0) { rect.Y += rect.Height; rect.Height = -rect.Height; } } if ((type & XMatrixTypes.Translation) != XMatrixTypes.Identity) { rect.X += matrix._offsetX; rect.Y += matrix._offsetY; } if (type == XMatrixTypes.Unknown) { XPoint point1 = matrix.Transform(rect.TopLeft); XPoint point2 = matrix.Transform(rect.TopRight); XPoint point3 = matrix.Transform(rect.BottomRight); XPoint point4 = matrix.Transform(rect.BottomLeft); rect.X = Math.Min(Math.Min(point1.X, point2.X), Math.Min(point3.X, point4.X)); rect.Y = Math.Min(Math.Min(point1.Y, point2.Y), Math.Min(point3.Y, point4.Y)); rect.Width = Math.Max(Math.Max(point1.X, point2.X), Math.Max(point3.X, point4.X)) - rect.X; rect.Height = Math.Max(Math.Max(point1.Y, point2.Y), Math.Max(point3.Y, point4.Y)) - rect.Y; } } } } } /// /// Gets the DebuggerDisplayAttribute text. /// /// The debugger display. // ReSharper disable UnusedMember.Local string DebuggerDisplay // ReSharper restore UnusedMember.Local { get { if (IsIdentity) return "matrix=(Identity)"; const string format = Config.SignificantFigures7; // Calculate the angle in degrees. XPoint point = new XMatrix(_m11, _m12, _m21, _m22, 0, 0).Transform(new XPoint(1, 0)); double φ = Math.Atan2(point.Y, point.X) / Const.Deg2Rad; return String.Format(CultureInfo.InvariantCulture, "matrix=({0:" + format + "}, {1:" + format + "}, {2:" + format + "}, {3:" + format + "}, {4:" + format + "}, {5:" + format + "}), φ={6:0.0#########}°", _m11, _m12, _m21, _m22, _offsetX, _offsetY, φ); } } } }