using System; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.Net; using Svg.Transforms; namespace Svg { /// /// Represents and SVG image /// [SvgElement("image")] public class SvgImage : SvgVisualElement { /// /// Initializes a new instance of the class. /// public SvgImage() { Width = new SvgUnit(0.0f); Height = new SvgUnit(0.0f); } private GraphicsPath _path; /// /// Gets an representing the top left point of the rectangle. /// public SvgPoint Location { get { return new SvgPoint(X, Y); } } /// /// Gets or sets the aspect of the viewport. /// /// [SvgAttribute("preserveAspectRatio")] public SvgAspectRatio AspectRatio { get { return this.Attributes.GetAttribute("preserveAspectRatio"); } set { this.Attributes["preserveAspectRatio"] = value; } } [SvgAttribute("x")] public virtual SvgUnit X { get { return this.Attributes.GetAttribute("x"); } set { this.Attributes["x"] = value; } } [SvgAttribute("y")] public virtual SvgUnit Y { get { return this.Attributes.GetAttribute("y"); } set { this.Attributes["y"] = value; } } [SvgAttribute("width")] public virtual SvgUnit Width { get { return this.Attributes.GetAttribute("width"); } set { this.Attributes["width"] = value; } } [SvgAttribute("height")] public virtual SvgUnit Height { get { return this.Attributes.GetAttribute("height"); } set { this.Attributes["height"] = value; } } [SvgAttribute("href", SvgAttributeAttribute.XLinkNamespace)] public virtual string Href { get { return this.Attributes.GetAttribute("href"); } set { this.Attributes["href"] = value; } } /// /// Gets the bounds of the element. /// /// The bounds. public override RectangleF Bounds { get { return new RectangleF(this.Location.ToDeviceValue(null, this), new SizeF(this.Width.ToDeviceValue(null, UnitRenderingType.Horizontal, this), this.Height.ToDeviceValue(null, UnitRenderingType.Vertical, this))); } } /// /// Gets the for this element. /// public override GraphicsPath Path(ISvgRenderer renderer) { if (_path == null) { // Same size of rectangle can suffice to provide bounds of the image var rectangle = new RectangleF(Location.ToDeviceValue(renderer, this), SvgUnit.GetDeviceSize(Width, Height, renderer, this)); _path = new GraphicsPath(); _path.StartFigure(); _path.AddRectangle(rectangle); _path.CloseFigure(); } return _path; } /// /// Renders the and contents to the specified object. /// protected override void Render(ISvgRenderer renderer) { if (!Visible || !Displayable) return; if (Width.Value > 0.0f && Height.Value > 0.0f && this.Href != null) { var img = GetImage(this.Href); if (img != null) { RectangleF srcRect; var bmp = img as Image; var svg = img as SvgFragment; if (bmp != null) { srcRect = new RectangleF(0, 0, bmp.Width, bmp.Height); } else if (svg != null) { srcRect = new RectangleF(new PointF(0, 0), svg.GetDimensions()); } else { return; } var destClip = new RectangleF(this.Location.ToDeviceValue(renderer, this), new SizeF(Width.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this), Height.ToDeviceValue(renderer, UnitRenderingType.Vertical, this))); RectangleF destRect = destClip; this.PushTransforms(renderer); renderer.SetClip(new Region(destClip), CombineMode.Intersect); this.SetClip(renderer); if (AspectRatio != null && AspectRatio.Align != SvgPreserveAspectRatio.none) { var fScaleX = destClip.Width / srcRect.Width; var fScaleY = destClip.Height / srcRect.Height; var xOffset = 0.0f; var yOffset = 0.0f; if (AspectRatio.Slice) { fScaleX = Math.Max(fScaleX, fScaleY); fScaleY = Math.Max(fScaleX, fScaleY); } else { fScaleX = Math.Min(fScaleX, fScaleY); fScaleY = Math.Min(fScaleX, fScaleY); } switch (AspectRatio.Align) { case SvgPreserveAspectRatio.xMinYMin: break; case SvgPreserveAspectRatio.xMidYMin: xOffset = (destClip.Width - srcRect.Width * fScaleX) / 2; break; case SvgPreserveAspectRatio.xMaxYMin: xOffset = (destClip.Width - srcRect.Width * fScaleX); break; case SvgPreserveAspectRatio.xMinYMid: yOffset = (destClip.Height - srcRect.Height * fScaleY) / 2; break; case SvgPreserveAspectRatio.xMidYMid: xOffset = (destClip.Width - srcRect.Width * fScaleX) / 2; yOffset = (destClip.Height - srcRect.Height * fScaleY) / 2; break; case SvgPreserveAspectRatio.xMaxYMid: xOffset = (destClip.Width - srcRect.Width * fScaleX); yOffset = (destClip.Height - srcRect.Height * fScaleY) / 2; break; case SvgPreserveAspectRatio.xMinYMax: yOffset = (destClip.Height - srcRect.Height * fScaleY); break; case SvgPreserveAspectRatio.xMidYMax: xOffset = (destClip.Width - srcRect.Width * fScaleX) / 2; yOffset = (destClip.Height - srcRect.Height * fScaleY); break; case SvgPreserveAspectRatio.xMaxYMax: xOffset = (destClip.Width - srcRect.Width * fScaleX); yOffset = (destClip.Height - srcRect.Height * fScaleY); break; } destRect = new RectangleF(destClip.X + xOffset, destClip.Y + yOffset, srcRect.Width * fScaleX, srcRect.Height * fScaleY); } if (bmp != null) { renderer.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel); bmp.Dispose(); } else if (svg != null) { var currOffset = new PointF(renderer.Transform.OffsetX, renderer.Transform.OffsetY); renderer.TranslateTransform(-currOffset.X, -currOffset.Y); renderer.ScaleTransform(destRect.Width / srcRect.Width, destRect.Height / srcRect.Height); renderer.TranslateTransform(currOffset.X + destRect.X, currOffset.Y + destRect.Y); renderer.SetBoundable(new GenericBoundable(srcRect)); svg.RenderElement(renderer); renderer.PopBoundable(); } this.ResetClip(renderer); this.PopTransforms(renderer); } // TODO: cache images... will need a shared context for this // TODO: support preserveAspectRatio, etc } } public object GetImage() { return this.GetImage(this.Href); } public object GetImage(string uriString) { try { var uri = new Uri(uriString.Substring(0, 65519)); //Uri MaxLength is 65519 (https://msdn.microsoft.com/en-us/library/z6c2z492.aspx) // handle data/uri embedded images (http://en.wikipedia.org/wiki/Data_URI_scheme) if (uri.IsAbsoluteUri && uri.Scheme == "data") { int dataIdx = uriString.IndexOf(",") + 1; if (dataIdx <= 0 || dataIdx + 1 > uriString.Length) throw new Exception("Invalid data URI"); // we're assuming base64, as ascii encoding would be *highly* unsusual for images // also assuming it's png or jpeg mimetype byte[] imageBytes = Convert.FromBase64String(uriString.Substring(dataIdx)); using (var stream = new MemoryStream(imageBytes)) { return Image.FromStream(stream); } } if (!uri.IsAbsoluteUri) { uri = new Uri(OwnerDocument.BaseUri, uri); } // should work with http: and file: protocol urls var httpRequest = WebRequest.Create(uri); using (WebResponse webResponse = httpRequest.GetResponse()) { using (var stream = webResponse.GetResponseStream()) { if (stream.CanSeek) { stream.Position = 0; } if (uri.LocalPath.EndsWith(".svg", StringComparison.InvariantCultureIgnoreCase)) { var doc = SvgDocument.Open(stream); doc.BaseUri = uri; return doc; } else { return Bitmap.FromStream(stream); } } } } catch (Exception ex) { Trace.TraceError("Error loading image: '{0}', error: {1} ", uriString, ex.Message); return null; } } protected static MemoryStream BufferToMemoryStream(Stream input) { byte[] buffer = new byte[4 * 1024]; int len; MemoryStream ms = new MemoryStream(); while ((len = input.Read(buffer, 0, buffer.Length)) > 0) { ms.Write(buffer, 0, len); } ms.Seek(0, SeekOrigin.Begin); return ms; } public override SvgElement DeepCopy() { return DeepCopy(); } public override SvgElement DeepCopy() { var newObj = base.DeepCopy() as SvgImage; newObj.Height = this.Height; newObj.Width = this.Width; newObj.X = this.X; newObj.Y = this.Y; newObj.Href = this.Href; return newObj; } } }