File: SkiaCanvasState.cs
Web Access
Project: src\src\Graphics\src\Graphics.Skia\Graphics.Skia.csproj (Microsoft.Maui.Graphics.Skia)
using SkiaSharp;
using Color = SkiaSharp.SKColor;
 
namespace Microsoft.Maui.Graphics.Skia
{
	public class SkiaCanvasState : CanvasState, IBlurrableCanvas
	{
		public float Alpha = 1;
		private SKPaint _fillPaint;
		private SKPaint _strokePaint;
		private IFont _font;
		private SKPaint _fontPaint;
		private float _fontSize = 10f;
		private float _scaleX = 1;
		private float _scaleY = 1;
		private bool _typefaceInvalid;
		private bool _isBlurred;
		private float _blurRadius;
		private SKMaskFilter _blurFilter;
		private SKImageFilter _shadowFilter;
		private bool _shadowed;
		private SKColor _shadowColor;
		private float _shadowX;
		private float _shadowY;
		private float _shadowBlur;
 
		private Color _strokeColor = Colors.Black;
		private Color _fillColor = Colors.White;
		private Color _fontColor = Colors.Black;
 
		public SkiaCanvasState()
		{
		}
 
		public SkiaCanvasState(SkiaCanvasState prototype) : base(prototype)
		{
			_strokeColor = prototype._strokeColor;
			_fillColor = prototype._fillColor;
			_fontColor = prototype._fontColor;
 
			_fontPaint = prototype.FontPaint.CreateCopy();
			_fillPaint = prototype.FillPaint.CreateCopy();
			_strokePaint = prototype.StrokePaint.CreateCopy();
			_font = prototype._font;
			_fontSize = prototype._fontSize;
			Alpha = prototype.Alpha;
			_scaleX = prototype._scaleX;
			_scaleY = prototype._scaleY;
			_typefaceInvalid = false;
 
			_isBlurred = prototype._isBlurred;
			_blurRadius = prototype._blurRadius;
 
			_shadowed = prototype._shadowed;
			//_shadowFilter = prototype._shadowFilter;  // There is no reason the copy really needs to know about this.
			_shadowColor = prototype._shadowColor;
			_shadowX = prototype._shadowX;
			_shadowY = prototype._shadowY;
			_shadowBlur = prototype._shadowBlur;
		}
 
		public Color StrokeColor
		{
			get => _strokeColor;
			set => _strokeColor = value;
		}
 
		public Color FillColor
		{
			get => _fillColor;
			set
			{
				FillPaint.Shader = null;
				_fillColor = value;
			}
		}
 
		public Color FontColor
		{
			get => _fontColor;
			set
			{
				_fontColor = value;
				if (value != null)
					FontPaint.Color = _fontColor.AsSKColor();
				else
					FontPaint.Color = SKColors.Black;
			}
		}
 
		public LineCap StrokeLineCap
		{
			set
			{
				if (value == LineCap.Butt)
					StrokePaint.StrokeCap = SKStrokeCap.Butt;
				else if (value == LineCap.Round)
					StrokePaint.StrokeCap = SKStrokeCap.Round;
				else if (value == LineCap.Square)
					StrokePaint.StrokeCap = SKStrokeCap.Square;
			}
		}
 
		public LineJoin StrokeLineJoin
		{
			set
			{
				if (value == LineJoin.Miter)
					StrokePaint.StrokeJoin = SKStrokeJoin.Miter;
				else if (value == LineJoin.Round)
					StrokePaint.StrokeJoin = SKStrokeJoin.Round;
				else if (value == LineJoin.Bevel)
					StrokePaint.StrokeJoin = SKStrokeJoin.Bevel;
			}
		}
 
		public float MiterLimit
		{
			set => StrokePaint.StrokeMiter = value;
		}
 
		public void SetStrokeDashPattern(float[] pattern, float strokeDashOffset, float strokeSize)
		{
			if (pattern == null || pattern.Length == 0 || strokeSize == 0)
			{
				StrokePaint.PathEffect = null;
			}
			else
			{
				float scaledStrokeSize = strokeSize * ScaleX;
 
				if (scaledStrokeSize > 1 || scaledStrokeSize < 1)
				{
					var scaledPattern = new float[pattern.Length];
					for (var i = 0; i < pattern.Length; i++)
						scaledPattern[i] = pattern[i] * scaledStrokeSize;
					StrokePaint.PathEffect = SKPathEffect.CreateDash(scaledPattern, 0);
				}
				else
				{
					StrokePaint.PathEffect = SKPathEffect.CreateDash(pattern, 0);
				}
			}
		}
 
		public bool AntiAlias
		{
			set => StrokePaint.IsAntialias = value;
		}
 
		public bool IsBlurred => _isBlurred;
 
		public float BlurRadius => _blurRadius;
 
		public void SetBlur(float radius)
		{
			if (radius != _blurRadius)
			{
				if (_blurFilter != null)
				{
					_blurFilter.Dispose();
					_blurFilter = null;
				}
 
				if (radius > 0)
				{
					_isBlurred = true;
					_blurRadius = radius;
					_blurFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, _blurRadius);
 
					if (_fillPaint != null)
						_fillPaint.MaskFilter = _blurFilter;
					if (_strokePaint != null)
						_strokePaint.MaskFilter = _blurFilter;
					if (_fontPaint != null)
						_fontPaint.MaskFilter = _blurFilter;
				}
				else
				{
					_isBlurred = false;
					_blurRadius = 0;
 
					if (_fillPaint != null)
						_fillPaint.MaskFilter = null;
					if (_strokePaint != null)
						_strokePaint.MaskFilter = null;
					if (_fontPaint != null)
						_fontPaint.MaskFilter = null;
				}
			}
		}
 
		public float PlatformStrokeSize
		{
			set => StrokePaint.StrokeWidth = value * _scaleX;
		}
 
		public float FontSize
		{
			set
			{
				_fontSize = value;
				FontPaint.TextSize = _fontSize * _scaleX;
			}
		}
 
		public IFont Font
		{
			set
			{
				if (!ReferenceEquals(_font, value) && (_font is null || !_font.Equals(value)))
				{
					_font = value;
					_typefaceInvalid = true;
				}
			}
 
			get => _font;
		}
 
		public SKPaint FontPaint
		{
			get
			{
				if (_fontPaint == null)
				{
					_fontPaint = new SKPaint
					{
						Color = SKColors.Black,
						IsAntialias = true,
						Typeface = SKTypeface.Default,
					};
				}
 
				if (_typefaceInvalid)
				{
					_fontPaint.Typeface = _font?.ToSKTypeface() ?? SKTypeface.Default;
					_typefaceInvalid = false;
				}
 
				return _fontPaint;
			}
 
			set => _fontPaint = value;
		}
 
		public SKPaint FillPaint
		{
			private get
			{
				return _fillPaint
					   ?? (_fillPaint = new SKPaint
					   {
						   Color = SKColors.White,
						   IsStroke = false,
						   IsAntialias = true
					   });
			}
 
			set { _fillPaint = value; }
		}
 
		public SKPaint StrokePaint
		{
			private get
			{
				if (_strokePaint == null)
				{
					var paint = new SKPaint
					{
						Color = SKColors.Black,
						StrokeWidth = 1,
						StrokeMiter = CanvasDefaults.DefaultMiterLimit,
						IsStroke = true,
						IsAntialias = true
					};
 
					_strokePaint = paint;
 
					return paint;
				}
 
				return _strokePaint;
			}
 
			set { _strokePaint = value; }
		}
 
		public SKPaint StrokePaintWithAlpha
		{
			get
			{
				var paint = StrokePaint;
 
				var r = (byte)(_strokeColor.Red * 255f);
				var g = (byte)(_strokeColor.Green * 255f);
				var b = (byte)(_strokeColor.Blue * 255f);
				var a = (byte)(_strokeColor.Alpha * 255f * Alpha);
 
				paint.Color = new SKColor(r, g, b, a);
				return paint;
			}
		}
 
		public SKPaint FillPaintWithAlpha
		{
			get
			{
				var paint = FillPaint;
 
				var r = (byte)(_fillColor.Red * 255f);
				var g = (byte)(_fillColor.Green * 255f);
				var b = (byte)(_fillColor.Blue * 255f);
				var a = (byte)(_fillColor.Alpha * 255f * Alpha);
 
				paint.Color = new SKColor(r, g, b, a);
				return paint;
			}
		}
 
		public void SetFillPaintShader(SKShader shader)
		{
			FillPaint.Shader = shader;
		}
 
		public void SetFillPaintFilterBitmap(bool value)
		{
			// todo: restore this
			//FillPaint.FilterBitmap = value;
		}
 
		public float ScaledStrokeSize => StrokeSize * _scaleX;
 
		public float ScaledFontSize => _fontSize * _scaleX;
 
		public new float ScaleX => _scaleX;
 
		public new float ScaleY => _scaleY;
 
		#region IDisposable Members
 
		public override void Dispose()
		{
			_fontPaint?.Dispose();
			_fontPaint = null;
 
			_strokePaint?.Dispose();
			_strokePaint = null;
 
			_fillPaint?.Dispose();
			_fillPaint = null;
 
			_shadowFilter?.Dispose();
			_shadowFilter = null;
 
			_blurFilter?.Dispose();
			_blurFilter = null;
 
			base.Dispose();
		}
 
		#endregion
 
		public void SetShadow(float blur, float sx, float sy, SKColor color)
		{
			_shadowFilter = SKImageFilter.CreateDropShadow(sx, sy, blur, blur, color);
			FillPaint.ImageFilter = _shadowFilter;
			StrokePaint.ImageFilter = _shadowFilter;
			FontPaint.ImageFilter = _shadowFilter;
 
			_shadowed = true;
			_shadowBlur = blur;
			_shadowX = sx;
			_shadowY = sy;
			_shadowColor = color;
		}
 
		public SKPaint GetShadowPaint(float sx, float sy)
		{
			if (_shadowed)
			{
				var shadowPaint = new SKPaint
				{
					Color = SKColors.Black,
					IsStroke = false,
					IsAntialias = true
				};
 
				// todo: implement me
				shadowPaint.ImageFilter = _shadowFilter;
				//shadowPaint.SetShadowLayer(shadowBlur, shadowX * sx, shadowY * sy, shadowColor);
				//shadowPaint.Alpha = (int) (Alpha*255f);
				return shadowPaint;
			}
 
			return null;
		}
 
		public SKPaint GetImagePaint(float sx, float sy)
		{
			var imagePaint = new SKPaint
			{
				Color = SKColors.Black,
				IsStroke = false,
				IsAntialias = true
			};
 
			if (Alpha < 1)
			{
				var color = new SKColor(255, 255, 255, (byte)(Alpha * 255f));
				imagePaint.ColorFilter = SKColorFilter.CreateBlendMode(color, SKBlendMode.Modulate);
			}
 
			if (_isBlurred)
				imagePaint.MaskFilter = _blurFilter;
 
			return imagePaint;
		}
 
		public void SetScale(float sx, float sy)
		{
			_scaleX = _scaleX * sx;
			_scaleY = _scaleY * sy;
 
			StrokePaint.StrokeWidth = StrokeSize * _scaleX;
			FontPaint.TextSize = _fontSize * _scaleX;
		}
 
		public void Reset(SKPaint fontPaint, SKPaint fillPaint, SKPaint strokePaint)
		{
			_fontPaint?.Dispose();
			_fontPaint = fontPaint.CreateCopy();
 
			_fillPaint?.Dispose();
			_fillPaint = fillPaint.CreateCopy();
 
			_strokePaint?.Dispose();
			_strokePaint = strokePaint.CreateCopy();
 
			_shadowFilter?.Dispose();
			_shadowFilter = null;
 
			_blurFilter?.Dispose();
			_blurFilter = null;
 
			_font = null;
			_fontSize = 10f;
			Alpha = 1;
			_scaleX = 1;
			_scaleY = 1;
		}
	}
}