using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Browser; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace Html5Canvas { // Implements the CanvasRenderingContext2D interface [ScriptableType] public class CanvasRenderingContext2D { private HTMLCanvasElement _canvasElement; private Canvas _root; private Canvas _canvas; private Brush _fill; private Brush _stroke; private PathGeometry _pathGeometry; private bool _hasSubPaths; private TransformGroup _transformGroup = new TransformGroup(); private Stack _drawingStates = new Stack(); public CanvasRenderingContext2D(HTMLCanvasElement canvasElement, Canvas root) { // Initialize _canvasElement = canvasElement; _root = root; _canvas = new Canvas { Width = _root.Width, Height = _root.Height, RenderTransform = _transformGroup }; _root.Children.Add(_canvas); globalAlpha = 1.0; fillStyle = "#000000"; strokeStyle = "#000000"; lineWidth = 1; lineCap = "butt"; lineJoin = "miter"; miterLimit = 10; beginPath(); // Handle size change by updating children ((FrameworkElement)(Application.Current.RootVisual)).SizeChanged += delegate(object sender, SizeChangedEventArgs e) { _root.Width = e.NewSize.Width; _root.Height = e.NewSize.Height; foreach (var child in _root.Children.OfType()) { child.Width = _root.Width; child.Height = _root.Height; } }; } public void save() { // Save state on stack _drawingStates.Push(new DrawingState { strokeStyle = strokeStyle, fillStyle = fillStyle, globalAlpha = globalAlpha, lineWidth = lineWidth, lineCap = lineCap, lineJoin = lineJoin, miterLimit = miterLimit, transformGroup = _transformGroup, }); } public void restore() { // Restore state from stack if (_drawingStates.Any()) { var drawingState = _drawingStates.Pop(); strokeStyle = drawingState.strokeStyle; fillStyle = drawingState.fillStyle; globalAlpha = drawingState.globalAlpha; lineWidth = drawingState.lineWidth; lineCap = drawingState.lineCap; lineJoin = drawingState.lineJoin; miterLimit = drawingState.miterLimit; // Create a new Canvas for the transform _transformGroup = drawingState.transformGroup; _canvas = new Canvas { Width = _root.Width, Height = _root.Height, RenderTransform = _transformGroup }; _root.Children.Add(_canvas); } } public void scale(double x, double y) { // Create new Canvas for the new combined transform _transformGroup = Utility.CloneTransformGroup(_transformGroup); _transformGroup.Children.Insert(0, new ScaleTransform { ScaleX = x, ScaleY = y }); _canvas = new Canvas { Width = _root.Width, Height = _root.Height, RenderTransform = _transformGroup }; _root.Children.Add(_canvas); } public void rotate(double angle) { // Create new Canvas for the new combined transform _transformGroup = Utility.CloneTransformGroup(_transformGroup); _transformGroup.Children.Insert(0, new RotateTransform { Angle = (angle / (2 * Math.PI)) * 360 }); _canvas = new Canvas { Width = _root.Width, Height = _root.Height, RenderTransform = _transformGroup }; _root.Children.Add(_canvas); } public void translate(double x, double y) { // Create new Canvas for the new combined transform _transformGroup = Utility.CloneTransformGroup(_transformGroup); _transformGroup.Children.Insert(0, new TranslateTransform { X = x, Y = y }); _canvas = new Canvas { Width = _root.Width, Height = _root.Height, RenderTransform = _transformGroup }; _root.Children.Add(_canvas); } public double globalAlpha { get { return _globalAlpha; } set { if ((0.0 <= value) && (value <= 1.0)) { _globalAlpha = value; } } } private double _globalAlpha; public object strokeStyle { get { return _strokeStyle; } set { var stringValue = value as string; var gradientValue = value as CanvasGradient; var patternValue = value as CanvasPattern; if (null != stringValue) { // Parse string style Brush brush; string style; if (Utility.ParseStyle(stringValue, out brush, out style)) { _stroke = brush; _strokeStyle = style; } } else if (null != gradientValue) { // Apply gradient style _stroke = gradientValue.GradientBrush; _strokeStyle = value; } else if (null != patternValue) { // Apply pattern style _stroke = patternValue.ImageBrush; _strokeStyle = value; } } } private object _strokeStyle; public object fillStyle { get { return _fillStyle; } set { var stringValue = value as string; var gradientValue = value as CanvasGradient; var patternValue = value as CanvasPattern; if (null != stringValue) { // Parse string style Brush brush; string style; if (Utility.ParseStyle(stringValue, out brush, out style)) { _fill = brush; _fillStyle = style; } } else if (null != gradientValue) { // Apply gradient style _fill = gradientValue.GradientBrush; _fillStyle = value; } else if (null != patternValue) { // Apply pattern style _fill = patternValue.ImageBrush; _fillStyle = value; } } } private object _fillStyle; public CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1) { // Translate coordinates var brush = new LinearGradientBrush { StartPoint = new Point(x0, y0), EndPoint = new Point(x1, y1), MappingMode = BrushMappingMode.Absolute }; return new CanvasGradient { GradientBrush = brush }; } public CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1) { // Translate coordinates var brush = new RadialGradientBrush { GradientOrigin = new Point(x0, y0), Center = new Point(x1, y1), RadiusX = r1, RadiusY = r1, MappingMode = BrushMappingMode.Absolute }; var rMin = Math.Min(r0, r1); var rMax = Math.Max(r0, r1); return new CanvasGradient { GradientBrush = brush, OffsetMinimum = rMin / rMax, OffsetMultiplier = (rMax - rMin) / rMax }; } public CanvasPattern createPattern(HtmlElement image, string repetition) { var brush = new ImageBrush { ImageSource = new BitmapImage { UriSource = new Uri(HtmlPage.Document.DocumentUri, image.GetAttribute("src")) }, Stretch = Stretch.None }; return new CanvasPattern { ImageBrush = brush }; } public double lineWidth { get { return _lineWidth; } set { if ((0.0 < value) && !double.IsInfinity(value)) { _lineWidth = value; } } } private double _lineWidth; public string lineCap { get { return _lineCap.ToString(); } set { try { _lineCap = (CapTypes)Enum.Parse(typeof(CapTypes), value, true); } catch (ArgumentException) { // Ignore bogus value } } } private CapTypes _lineCap; public string lineJoin { get { return _lineJoin.ToString(); } set { try { _lineJoin = (JoinTypes)Enum.Parse(typeof(JoinTypes), value, true); } catch (ArgumentException) { // Ignore bogus value } } } private JoinTypes _lineJoin; public double miterLimit { get { return _miterLimit; } set { if ((0.0 < value) && !double.IsInfinity(value)) { _miterLimit = value; } } } private double _miterLimit; public void clearRect(double x, double y, double w, double h) { if ((0 < w) && (0 < h)) { if ((x <= 0) && (y <= 0) && (_root.Width <= w) && (_root.Height <= h)) { // Optimize common scenario of clearing the entire canvas _root.Children.Clear(); } else { // Apply the necessary exclusive clip to all children foreach (var canvas in _root.Children.OfType()) { var group = new GeometryGroup(); group.Children.Add(new RectangleGeometry { Rect = new Rect(0, 0, _root.Width, _root.Height) }); group.Children.Add(new RectangleGeometry { Rect = new Rect(x, y, w, h) }); canvas.Clip = group; } } // Create a new canvas to draw on _canvas = new Canvas { Width = _root.Width, Height = _root.Height, RenderTransform = _transformGroup }; _root.Children.Add(_canvas); } } public void fillRect(double x, double y, double w, double h) { if ((0 < w) && (0 < h)) { var rect = new Rectangle { Width = w, Height = h, Fill = Utility.CloneAndMapGradientBrush(_fill, x, y), Opacity = globalAlpha }; Canvas.SetLeft(rect, x); Canvas.SetTop(rect, y); _canvas.Children.Add(rect); } } public void strokeRect(double x, double y, double w, double h) { var rect = new Rectangle { Width = w, Height = h, Stroke = Utility.CloneAndMapGradientBrush(_stroke, x, y), StrokeThickness = lineWidth, StrokeLineJoin = JoinTypeToPenLineJoin(_lineJoin), StrokeMiterLimit = _miterLimit, Opacity = globalAlpha }; Canvas.SetLeft(rect, x); Canvas.SetTop(rect, y); _canvas.Children.Add(rect); } public void beginPath() { _pathGeometry = new PathGeometry(); _pathGeometry.Figures.Add(new PathFigure { StartPoint = new Point() }); _hasSubPaths = false; } public void closePath() { if (_hasSubPaths) { var figure = _pathGeometry.Figures.Last(); figure.IsClosed = true; figure = new PathFigure { StartPoint = figure.StartPoint }; } } public void moveTo(double x, double y) { var figure = new PathFigure { StartPoint = new Point(x, y) }; _pathGeometry.Figures.Add(figure); _hasSubPaths = true; } public void lineTo(double x, double y) { if (!_hasSubPaths) { moveTo(x, y); } var figure = _pathGeometry.Figures.Last(); figure.Segments.Add(new LineSegment { Point = new Point(x, y) }); } public void quadraticCurveTo(double cpx, double cpy, double x, double y) { if (!_hasSubPaths) { moveTo(x, y); } var figure = _pathGeometry.Figures.Last(); figure.Segments.Add(new QuadraticBezierSegment { Point1 = new Point(cpx, cpy), Point2 = new Point(x, y) }); } public void bezierCurveTo(double cp1x, double cp1y, double cp2x, double cp2y, double x, double y) { if (!_hasSubPaths) { moveTo(x, y); } var figure = _pathGeometry.Figures.Last(); figure.Segments.Add(new BezierSegment { Point1 = new Point(cp1x, cp1y), Point2 = new Point(cp2x, cp2y), Point3 = new Point(x, y) }); } public void arc(double x, double y, double radius, double startAngle, double endAngle, bool anticlockwise) { var figure = _pathGeometry.Figures.Last(); var start = new Point(x + (radius * Math.Cos(startAngle)), y + (radius * Math.Sin(startAngle))); if (_hasSubPaths) { // Draw line from last point of path to start of arc figure.Segments.Add(new LineSegment { Point = start }); } else { // Start at start point figure.StartPoint = start; _hasSubPaths = true; } if (2 * Math.PI <= Math.Abs(endAngle - startAngle)) { // Need to draw complete circles as two half-circles arc(x, y, radius, startAngle, Math.PI, anticlockwise); startAngle = Math.PI; } // Normalize angles start = new Point(x + (radius * Math.Cos(startAngle)), y + (radius * Math.Sin(startAngle))); var end = new Point(x + (radius * Math.Cos(endAngle)), y + (radius * Math.Sin(endAngle))); if (anticlockwise) { if (startAngle < endAngle) { startAngle += 2 * Math.PI; } } else { if (endAngle < startAngle) { endAngle += 2 * Math.PI; } } // Add arc figure.Segments.Add(new ArcSegment { Point = end, Size = new Size(radius, radius), IsLargeArc = Math.PI < Math.Abs(endAngle - startAngle), SweepDirection = anticlockwise ? SweepDirection.Counterclockwise : SweepDirection.Clockwise }); } public void rect(double x, double y, double w, double h) { moveTo(x, y); lineTo(x + w, y); lineTo(x + w, y + h); lineTo(x, y + h); lineTo(x, y); moveTo(x, y); } public void fill() { _canvas.Children.Add(new Path { Data = Utility.ClonePathGeometry(_pathGeometry), Fill = _fill, Opacity = globalAlpha }); } public void stroke() { _canvas.Children.Add(new Path { Data = Utility.ClonePathGeometry(_pathGeometry), Stroke = _stroke, StrokeThickness = lineWidth, Opacity = globalAlpha, StrokeStartLineCap = CapTypeToPenLineCap(_lineCap), StrokeEndLineCap = CapTypeToPenLineCap(_lineCap), StrokeLineJoin = JoinTypeToPenLineJoin(_lineJoin), StrokeMiterLimit = miterLimit }); } public void drawImage(HtmlElement image, double dx, double dy) { var imageElement = new Image { Source = new BitmapImage(new Uri(HtmlPage.Document.DocumentUri, image.GetAttribute("src"))), Opacity = globalAlpha }; Canvas.SetLeft(imageElement, dx); Canvas.SetTop(imageElement, dy); _canvas.Children.Add(imageElement); } public void drawImage(HtmlElement image, double dx, double dy, double dw, double dh) { var imageElement = new Image { Source = new BitmapImage(new Uri(HtmlPage.Document.DocumentUri, image.GetAttribute("src"))), Width = dw, Height = dh, Stretch = Stretch.Fill, Opacity = globalAlpha }; Canvas.SetLeft(imageElement, dx); Canvas.SetTop(imageElement, dy); _canvas.Children.Add(imageElement); } public void drawImage(HtmlElement image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh) { var imageElement = new Image { Stretch = Stretch.Fill, Opacity = globalAlpha }; imageElement.Clip = new RectangleGeometry(); _canvas.Children.Add(imageElement); imageElement.ImageOpened += delegate { // Calculate bounds to apply specified bounds var xm = dw / sw; var ym = dh / sh; Canvas.SetLeft(imageElement, dx - (sx * xm)); Canvas.SetTop(imageElement, dy - (sy * ym)); imageElement.Width = imageElement.ActualWidth * xm; imageElement.Height = imageElement.ActualHeight * ym; imageElement.Clip = new RectangleGeometry { Rect = new Rect(sx * xm, sy * ym, dw, dh) }; }; imageElement.Source = new BitmapImage(new Uri(HtmlPage.Document.DocumentUri, image.GetAttribute("src"))); } private enum CapTypes { butt, round, square }; private PenLineCap CapTypeToPenLineCap(CapTypes capType) { switch (capType) { case CapTypes.butt: return PenLineCap.Flat; case CapTypes.round: return PenLineCap.Round; case CapTypes.square: return PenLineCap.Square; default: throw new NotSupportedException(); } } private enum JoinTypes { round, bevel, miter }; private PenLineJoin JoinTypeToPenLineJoin(JoinTypes joinType) { switch (joinType) { case JoinTypes.bevel: return PenLineJoin.Bevel; case JoinTypes.miter: return PenLineJoin.Miter; case JoinTypes.round: return PenLineJoin.Round; default: throw new NotSupportedException(); } } // Represents a drawing state private class DrawingState { public object strokeStyle; public object fillStyle; public double globalAlpha; public double lineWidth; public string lineCap; public string lineJoin; public double miterLimit; public TransformGroup transformGroup; } } }