File: src\Graphics\src\Graphics\Platforms\Windows\PlatformCanvasState.cs
Web Access
Project: src\src\Graphics\src\Graphics.Win2D\Graphics.Win2D.csproj (Microsoft.Maui.Graphics.Win2D.WinUI.Desktop)
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Geometry;
using System;
using System.Numerics;
using WRect = Windows.Foundation.Rect;
#if NETFX_CORE
using WColors = global::Windows.UI.Colors;
#else
using WColors = global::Microsoft.UI.Colors;
#endif
 
#if MAUI_GRAPHICS_WIN2D
namespace Microsoft.Maui.Graphics.Win2D
#else
namespace Microsoft.Maui.Graphics.Platform
#endif
{
	/// <summary>
	/// A Windows platform implementation of <see cref="CanvasState"/>.
	/// </summary>
#if MAUI_GRAPHICS_WIN2D
	public class W2DCanvasState
#else
	public class PlatformCanvasState
#endif
		: CanvasState
	{
		private static readonly float[] _emptyFloatArray = Array.Empty<float>();
 
		private readonly PlatformCanvas _owner;
		private readonly PlatformCanvasState _parentState;
 
		private float _alpha = 1;
		private float[] _dashes;
		private float _dashOffset;
 
		private ICanvasBrush _fillBrush;
		private bool _fillBrushValid;
		private CanvasSolidColorBrush _fontBrush;
		private bool _fontBrushValid;
		private float _fontSize;
		private Vector2 _linearGradientStartPoint;
		private Vector2 _linearGradientEndPoint;
		private Vector2 _radialGradientCenter;
		private float _radialGradientRadius;
		//private GradientStopCollection _gradientStopCollection;
		private CanvasGeometry _layerBounds;
		private CanvasGeometry _layerClipBounds;
		private CanvasGeometry _layerMask;
		private CanvasActiveLayer _layer;
		private bool _needsStrokeStyle;
		private float _scale;
 
		private global::Windows.UI.Color _shadowColor;
		private bool _shadowColorValid;
		private Color _sourceFillColor;
		private Paint _sourceFillpaint;
 
		private Color _sourceFontColor;
		private Color _sourceShadowColor;
		private Color _sourceStrokeColor;
		private CanvasSolidColorBrush _strokeBrush;
		private bool _strokeBrushValid;
		private CanvasStrokeStyle _strokeStyle;
		private float _miterLimit;
		private CanvasCapStyle _lineCap;
		private CanvasLineJoin _lineJoin;
		//private CanvasStrokeStyleProperties strokeStyleProperties;
 
		private int _layerCount = 0;
		private readonly float _dpi = 96;
 
		public IFont Font { get; set; }
 
		public float BlurRadius { get; private set; }
		public bool IsShadowed { get; private set; }
		public bool IsBlurred { get; private set; }
		public Vector2 ShadowOffset { get; private set; }
		public float ShadowBlur { get; set; }
		public Matrix3x2 Matrix { get; private set; }
 
#if MAUI_GRAPHICS_WIN2D
		public W2DCanvasState(
#else
		public PlatformCanvasState(
#endif
			PlatformCanvas owner)
		{
			_owner = owner;
			_parentState = null;
			SetToDefaults();
		}
 
#if MAUI_GRAPHICS_WIN2D
		public W2DCanvasState(
#else
		public PlatformCanvasState(
#endif
			PlatformCanvasState prototype) : base(prototype)
		{
			_dpi = prototype.Dpi;
			_owner = prototype._owner;
			_parentState = prototype;
 
			_strokeBrush = prototype._strokeBrush;
			_fillBrush = prototype._fillBrush;
			_fontBrush = prototype._fontBrush;
			_shadowColor = prototype._shadowColor;
 
			_sourceStrokeColor = prototype._sourceStrokeColor;
			_sourceFillpaint = prototype._sourceFillpaint;
			_sourceFillColor = prototype._sourceFillColor;
			_sourceFontColor = prototype._sourceFontColor;
			_sourceShadowColor = prototype._sourceShadowColor;
 
			_strokeBrushValid = prototype._strokeBrushValid;
			_fillBrushValid = prototype._fillBrushValid;
			_fontBrushValid = prototype._fontBrushValid;
			_shadowColorValid = prototype._shadowColorValid;
 
			_dashes = prototype._dashes;
			_dashOffset = prototype._dashOffset;
			_strokeStyle = prototype._strokeStyle;
			_lineJoin = prototype._lineJoin;
			_lineCap = prototype._lineCap;
			_miterLimit = prototype._miterLimit;
			_needsStrokeStyle = prototype._needsStrokeStyle;
 
			IsShadowed = prototype.IsShadowed;
			ShadowOffset = prototype.ShadowOffset;
			ShadowBlur = prototype.ShadowBlur;
 
			Matrix = new Matrix3x2(prototype.Matrix.M11, prototype.Matrix.M12, prototype.Matrix.M21, prototype.Matrix.M22, prototype.Matrix.M31, prototype.Matrix.M32);
 
			Font = prototype.Font;
			FontSize = prototype.FontSize;
 
			_alpha = prototype._alpha;
			_scale = prototype._scale;
 
			IsBlurred = prototype.IsBlurred;
			BlurRadius = prototype.BlurRadius;
		}
 
		public void SetToDefaults()
		{
			_sourceStrokeColor = Colors.Black;
			_strokeBrushValid = false;
			_needsStrokeStyle = false;
			_strokeStyle = null;
 
			_dashes = null;
			_dashOffset = 1;
			_miterLimit = CanvasDefaults.DefaultMiterLimit;
			_lineCap = CanvasCapStyle.Flat;
			_lineJoin = CanvasLineJoin.Miter;
 
			_sourceFillpaint = Colors.White.AsPaint();
			_fillBrushValid = false;
 
			Matrix = Matrix3x2.Identity;
 
			IsShadowed = false;
			_sourceShadowColor = CanvasDefaults.DefaultShadowColor;
 
			Font = null;
			FontSize = 12;
 
			_sourceFontColor = Colors.Black;
			_fontBrushValid = false;
 
			_alpha = 1;
			_scale = 1;
 
			IsBlurred = false;
			BlurRadius = 0;
		}
 
		public float FontSize
		{
			get => _fontSize;
			set => _fontSize = value;
		}
 
		public float Dpi => _dpi;
 
		public float Alpha
		{
			get => _alpha;
 
			set
			{
				if (_alpha != value)
				{
					_alpha = value;
					InvalidateBrushes();
				}
			}
		}
 
		public Color StrokeColor
		{
			set
			{
				var finalValue = value ?? Colors.Black;
 
				if (!finalValue.Equals(_sourceStrokeColor))
				{
					_sourceStrokeColor = finalValue;
					_strokeBrushValid = false;
				}
			}
		}
 
		public float MiterLimit
		{
			set
			{
				_miterLimit = value;
				InvalidateStrokeStyle();
				_needsStrokeStyle = true;
			}
		}
 
		public LineCap StrokeLineCap
		{
			set
			{
				switch (value)
				{
					case LineCap.Butt:
						_lineCap = CanvasCapStyle.Flat;
						break;
					case LineCap.Round:
						_lineCap = CanvasCapStyle.Round;
						break;
					default:
						_lineCap = CanvasCapStyle.Square;
						break;
				}
 
				InvalidateStrokeStyle();
				_needsStrokeStyle = true;
			}
		}
 
		public LineJoin StrokeLineJoin
		{
			set
			{
				switch (value)
				{
					case LineJoin.Bevel:
						_lineJoin = CanvasLineJoin.Bevel;
						break;
					case LineJoin.Round:
						_lineJoin = CanvasLineJoin.Round;
						break;
					default:
						_lineJoin = CanvasLineJoin.Miter;
						break;
				}
 
				InvalidateStrokeStyle();
				_needsStrokeStyle = true;
			}
		}
 
		public void SetStrokeDashPattern(float[] pattern, float strokeDashOffset, float strokeSize)
		{
			if (pattern == null || pattern.Length == 0)
			{
				if (_needsStrokeStyle == false)
					return;
				_dashes = null;
			}
			else
			{
				_dashes = pattern;
			}
 
			_dashOffset = strokeDashOffset;
 
			InvalidateStrokeStyle();
			_needsStrokeStyle = true;
		}
 
		public Color FillColor
		{
			set
			{
				ReleaseFillBrush();
				_fillBrushValid = false;
				_sourceFillColor = value;
				_sourceFillpaint = null;
			}
		}
 
		public void SetLinearGradient(Paint aPaint, Vector2 startPoint, Vector2 endPoint)
		{
			ReleaseFillBrush();
			_fillBrushValid = false;
			_sourceFillColor = null;
			_sourceFillpaint = aPaint;
			_linearGradientStartPoint = startPoint;
			_linearGradientEndPoint = endPoint;
		}
 
		public void SetRadialGradient(Paint aPaint, Vector2 center, float radius)
		{
			ReleaseFillBrush();
			_fillBrushValid = false;
			_sourceFillColor = null;
			_sourceFillpaint = aPaint;
			_radialGradientCenter = center;
			_radialGradientRadius = radius;
		}
 
		public void SetBitmapBrush(CanvasImageBrush bitmapBrush)
		{
			_fillBrush = bitmapBrush;
			_fillBrushValid = true;
		}
 
		public ICanvasBrush PlatformFillBrush
		{
			get
			{
				if (_fillBrush == null || !_fillBrushValid)
				{
					if (_sourceFillColor != null)
					{
						_fillBrush = new CanvasSolidColorBrush(_owner.Session, _sourceFillColor.AsColor(_alpha));
						_fillBrushValid = true;
					}
					else if (_sourceFillpaint != null)
					{
						if (_sourceFillpaint is LinearGradientPaint linearGradientPaint)
						{
							var gradientStops = new CanvasGradientStop[linearGradientPaint.GradientStops.Length];
							for (int i = 0; i < linearGradientPaint.GradientStops.Length; i++)
							{
								gradientStops[i] = new CanvasGradientStop()
								{
									Position = linearGradientPaint.GradientStops[i].Offset,
									Color = linearGradientPaint.GradientStops[i].Color.AsColor(Colors.White, _alpha)
								};
							}
 
							_fillBrush = new CanvasLinearGradientBrush(_owner.Session, gradientStops);
							((CanvasLinearGradientBrush)_fillBrush).StartPoint = _linearGradientStartPoint;
							((CanvasLinearGradientBrush)_fillBrush).EndPoint = _linearGradientEndPoint;
						}
						else if (_sourceFillpaint is RadialGradientPaint radialGradientPaint)
						{
							//float radius = GeometryUtil.GetDistance(_gradientPoint1.X, _gradientPoint1.Y, _gradientPoint2.X, _gradientPoint2.Y);
 
							var gradientStops = new CanvasGradientStop[radialGradientPaint.GradientStops.Length];
							for (int i = 0; i < radialGradientPaint.GradientStops.Length; i++)
							{
								gradientStops[i] = new CanvasGradientStop
								{
									Position = radialGradientPaint.GradientStops[i].Offset,
									Color = radialGradientPaint.GradientStops[i].Color.AsColor(Colors.White, _alpha)
								};
							}
							_fillBrush = new CanvasRadialGradientBrush(_owner.Session, gradientStops);
							((CanvasRadialGradientBrush)_fillBrush).Center = _radialGradientCenter;
							((CanvasRadialGradientBrush)_fillBrush).RadiusX = _radialGradientRadius;
							((CanvasRadialGradientBrush)_fillBrush).RadiusY = _radialGradientRadius;
						}
						_fillBrushValid = true;
					}
					else
					{
						_fillBrush = new CanvasSolidColorBrush(_owner.Session, WColors.White);
						_fillBrushValid = true;
					}
				}
 
				return _fillBrush;
			}
		}
 
		public Color FontColor
		{
			set
			{
				var finalValue = value ?? Colors.Black;
 
				if (!finalValue.Equals(_sourceFontColor))
				{
					_sourceFontColor = finalValue;
					_fontBrushValid = false;
				}
			}
		}
 
		public void SetShadow(SizeF offset, float blur, Color color)
		{
			if (blur > 0)
			{
				IsShadowed = true;
				ShadowOffset = new Vector2(offset.Width, offset.Height);
				ShadowBlur = blur;
				_sourceShadowColor = color;
				_shadowColorValid = false;
			}
			else
			{
				IsShadowed = false;
			}
		}
 
		public global::Windows.UI.Color ShadowColor
		{
			get
			{
				if (!_shadowColorValid)
				{
					_shadowColor = _sourceShadowColor?.AsColor(_alpha) ?? CanvasDefaults.DefaultShadowColor.AsColor(_alpha);
				}
 
				return _shadowColor;
			}
		}
 
		public void SetBlur(float aRadius)
		{
			if (aRadius > 0)
			{
				IsBlurred = true;
				BlurRadius = aRadius;
			}
			else
			{
				IsBlurred = false;
				BlurRadius = 0;
			}
		}
 
		public float ActualScale => _scale;
 
		public float ActualShadowBlur => ShadowBlur * Math.Abs(_scale);
 
		public Matrix3x2 AppendTranslate(float tx, float ty)
		{
			//Matrix = Matrix * Matrix3x2.Translation(tx, ty);
			Matrix = Matrix.Translate(tx, ty);
			return Matrix;
		}
 
		public Matrix3x2 AppendConcatenateTransform(Matrix3x2 transform)
		{
			return Matrix = Matrix3x2.Multiply(Matrix, transform);
		}
 
		public Matrix3x2 AppendScale(float tx, float ty)
		{
			_scale *= tx;
			Matrix = Matrix.Scale(tx, ty);
			return Matrix;
		}
 
		public Matrix3x2 AppendRotate(float aAngle)
		{
			float radians = GeometryUtil.DegreesToRadians(aAngle);
			Matrix = Matrix.Rotate(radians);
			return Matrix;
		}
 
		public Matrix3x2 AppendRotate(float aAngle, float x, float y)
		{
			float radians = GeometryUtil.DegreesToRadians(aAngle);
			Matrix = Matrix.Translate(x, y);
			Matrix = Matrix.Rotate(radians);
			Matrix = Matrix.Translate(-x, -y);
			return Matrix;
		}
 
		public void ClipPath(PathF path, WindingMode windingMode)
		{
			if (_layerMask != null)
				throw new Exception("Only one clip operation currently supported.");
 
 
			/* Unmerged change from project 'Microsoft.Maui.Graphics.Win2D.WinUI.Desktop'
			Before:
						var layerRect = new Rect(0, 0, _owner.CanvasSize.Width, _owner.CanvasSize.Height);
			After:
						var layerRect = new global::Windows.Foundation.Rect(0, 0, _owner.CanvasSize.Width, _owner.CanvasSize.Height);
			*/
			var layerRect = new WRect(0, 0, _owner.CanvasSize.Width, _owner.CanvasSize.Height);
			_layerBounds = CanvasGeometry.CreateRectangle(_owner.Session, layerRect);
			var clipGeometry = path.AsPath(_owner.Session, windingMode == WindingMode.NonZero ? CanvasFilledRegionDetermination.Winding : CanvasFilledRegionDetermination.Alternate);
 
			_layerMask = _layerBounds.CombineWith(clipGeometry, Matrix3x2.Identity, CanvasGeometryCombine.Intersect);
 
			_layer = _owner.Session.CreateLayer(1, _layerMask);
			_layerCount++;
		}
 
		public void ClipRectangle(float x, float y, float width, float height)
		{
			var path = new PathF();
			path.AppendRectangle(x, y, width, height);
			ClipPath(path, WindingMode.NonZero);
		}
 
		public void SubtractFromClip(float x, float y, float width, float height)
		{
			if (_layerMask != null)
				throw new Exception("Only one subtraction currently supported.");
 
 
			/* Unmerged change from project 'Microsoft.Maui.Graphics.Win2D.WinUI.Desktop'
			Before:
						var layerRect = new Rect(0, 0, _owner.CanvasSize.Width, _owner.CanvasSize.Height);
			After:
						var layerRect = new global::Windows.Foundation.Rect(0, 0, _owner.CanvasSize.Width, _owner.CanvasSize.Height);
			*/
			var layerRect = new WRect(0, 0, _owner.CanvasSize.Width, _owner.CanvasSize.Height);
			_layerBounds = CanvasGeometry.CreateRectangle(_owner.Session, layerRect);
 
 
			/* Unmerged change from project 'Microsoft.Maui.Graphics.Win2D.WinUI.Desktop'
			Before:
						var boundsToSubtract = new Rect(x, y, width, height);
			After:
						var boundsToSubtract = new global::Windows.Foundation.Rect(x, y, width, height);
			*/
			var boundsToSubtract = new WRect(x, y, width, height);
			_layerClipBounds = CanvasGeometry.CreateRectangle(_owner.Session, boundsToSubtract);
 
			_layerMask = _layerBounds.CombineWith(_layerClipBounds, Matrix3x2.Identity, CanvasGeometryCombine.Exclude);
 
			_layer = _owner.Session.CreateLayer(1, _layerMask);
			_layerCount++;
		}
		public void SaveRenderTargetState()
		{
 
		}
 
		public void RestoreRenderTargetState()
		{
			_owner.Session.Transform = Matrix;
			//needsStrokeStyle = true;
		}
 
		public ICanvasBrush PlatformFontBrush
		{
			get
			{
				if (_fontBrush == null || (!_fontBrushValid && _parentState != null && _fontBrush == _parentState._fontBrush))
					_fontBrush = new CanvasSolidColorBrush(_owner.Session, _sourceFontColor.AsColor(Colors.Black, _alpha));
				else if (!_fontBrushValid)
					_fontBrush.Color = _sourceFontColor.AsColor(Colors.Black, _alpha);
 
				return _fontBrush;
			}
		}
 
		public ICanvasBrush PlatformStrokeBrush
		{
			get
			{
				if (_strokeBrush == null || (!_strokeBrushValid && _parentState != null && _strokeBrush == _parentState._strokeBrush))
				{
					_strokeBrush = new CanvasSolidColorBrush(_owner.Session, _sourceStrokeColor.AsColor(Colors.Black, _alpha));
					_strokeBrushValid = true;
				}
				else if (!_strokeBrushValid)
				{
					_strokeBrush.Color = _sourceStrokeColor.AsColor(Colors.Black, _alpha);
					_strokeBrushValid = true;
				}
 
				return _strokeBrush;
			}
		}
 
		public CanvasStrokeStyle PlatformStrokeStyle
		{
			get
			{
				if (_needsStrokeStyle)
				{
					if (_strokeStyle == null)
					{
						_strokeStyle = new CanvasStrokeStyle();
					}
 
					if (_dashes != null)
					{
						_strokeStyle.CustomDashStyle = _dashes;
						_strokeStyle.DashCap = _lineCap;
					}
					else
					{
						_strokeStyle.CustomDashStyle = _emptyFloatArray;
					}
 
					_strokeStyle.DashOffset = _dashOffset;
					_strokeStyle.MiterLimit = _miterLimit;
					_strokeStyle.StartCap = _lineCap;
					_strokeStyle.EndCap = _lineCap;
					_strokeStyle.LineJoin = _lineJoin;
 
 
					return _strokeStyle;
				}
 
				return _strokeStyle;
			}
		}
 
		private void InvalidateBrushes()
		{
			_strokeBrushValid = false;
			_fillBrushValid = false;
			_shadowColorValid = false;
			_fontBrushValid = false;
		}
 
		private void ReleaseFillBrush()
		{
			if (_fillBrush != null)
			{
				if (_parentState == null || _fillBrush != _parentState._fillBrush)
				{
					_fillBrush.Dispose();
				}
				_fillBrush = null;
			}
		}
 
		private void InvalidateStrokeStyle()
		{
			if (_strokeStyle != null)
			{
				if (_parentState == null || _strokeStyle != _parentState._strokeStyle)
				{
					_strokeStyle.Dispose();
				}
				_strokeStyle = null;
			}
		}
 
		public override void Dispose()
		{
			base.Dispose();
 
			if (_layer != null)
			{
				_layer.Dispose();
				_layer = null;
			}
 
			if (_layerMask != null)
			{
				_layerMask.Dispose();
				_layerMask = null;
			}
 
			if (_layerBounds != null)
			{
				_layerBounds.Dispose();
				_layerBounds = null;
			}
 
			if (_layerClipBounds != null)
			{
				_layerClipBounds.Dispose();
				_layerClipBounds = null;
			}
		}
	}
}