123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- //----------------------------------------------------------------------------
- // Anti-Grain Geometry - Version 2.4
- // Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
- //
- // C# port by: Lars Brubaker
- // larsbrubaker@gmail.com
- // Copyright (C) 2007
- //
- // Permission to copy, use, modify, sell and distribute this software
- // is granted provided this copyright notice appears in all copies.
- // This software is provided "as is" without express or implied
- // warranty, and with no claim as to its suitability for any purpose.
- //
- //----------------------------------------------------------------------------
- // Contact: mcseem@antigrain.com
- // mcseemagg@yahoo.com
- // http://www.antigrain.com
- //----------------------------------------------------------------------------
- using MatterHackers.Agg.Font;
- using MatterHackers.Agg.Image;
- using MatterHackers.Agg.Platform;
- using MatterHackers.Agg.Transform;
- using MatterHackers.Agg.VertexSource;
- using MatterHackers.VectorMath;
- using System;
- using System.Collections.Generic;
- using Typography.OpenFont;
- namespace MatterHackers.Agg
- {
- public class ColoredVertexSource
- {
- public IVertexSource VertexSource { get; set; }
- public Color Color { get; set; }
- public ColoredVertexSource(IVertexSource vertexSource, Color color)
- {
- VertexSource = vertexSource;
- Color = color;
- }
- }
- public interface IStyleHandler
- {
- Color color(int style);
- void GenerateSpan(Color[] span, int spanIndex, int x, int y, int len, int style);
- bool IsSolid(int style);
- };
- public abstract class Graphics2D
- {
- protected Stack<Affine> affineTransformStack = new Stack<Affine>();
- protected IImageByte destImageByte;
- protected IImageFloat destImageFloat;
- protected ScanlineRasterizer rasterizer;
- protected Stroke StrockedText;
- private const int cover_full = 255;
- public Graphics2D()
- {
- affineTransformStack.Push(Affine.NewIdentity());
- }
- public Graphics2D(IImageByte destImage, ScanlineRasterizer rasterizer)
- : this()
- {
- Initialize(destImage, rasterizer);
- }
- public enum Alignment
- {
- Left,
- Center,
- Right
- }
- public enum TransformQuality
- {
- Fastest,
- Best
- }
- public IImageByte DestImage
- {
- get
- {
- return destImageByte;
- }
- }
- public IImageFloat DestImageFloat
- {
- get
- {
- return destImageFloat;
- }
- }
- public double DeviceScale { get; set; } = 1;
- public abstract int Height { get; }
- public TransformQuality ImageRenderQuality { get; set; } = TransformQuality.Fastest;
- public ScanlineRasterizer Rasterizer
- {
- get { return rasterizer; }
- }
- public abstract IScanlineCache ScanlineCache
- {
- get;
- set;
- }
- public int TransformStackCount
- {
- get { return affineTransformStack.Count; }
- }
- public abstract int Width { get; }
- public static void AssertDebugNotDefined()
- {
- #if DEBUG
- throw new Exception("DEBUG is defined and should not be!");
- #endif
- }
- public static double GetScallingBaseOnMaxSize(ImageBuffer image, Vector2 maxSize, out Vector2 size)
- {
- double ratio = 1;
- size = new Vector2(image.Width, image.Height);
- if (size.X > maxSize.X)
- {
- size.X = maxSize.X;
- ratio = size.X / image.Width;
- size.Y = image.Height * ratio;
- }
- if (size.Y > maxSize.Y)
- {
- size.Y = maxSize.Y;
- ratio = size.Y / image.Height;
- size.X = image.Width * ratio;
- }
- return ratio;
- }
- public void Circle(Vector2 origin, double radius, Color color)
- {
- Circle(origin.X, origin.Y, radius, color);
- }
- public void Circle(double x, double y, double radius, Color color)
- {
- Ellipse elipse = new Ellipse(x, y, radius, radius);
- Render(elipse, color);
- }
- public abstract void Clear(IColorType color);
- /// <summary>
- /// Draws an arc representing a portion of an ellipse specified by a Rectangle structure.
- /// </summary>
- /// <param name="color">The color to draw in.</param>
- /// <param name="rect">Structure that defines the boundaries of the ellipse.</param>
- /// <param name="startAngle">Angle in degrees measured clockwise from the x-axis to the starting point of the arc.</param>
- /// <param name="sweepAngle">Angle in degrees measured clockwise from the startAngle parameter to ending point of the arc.</param>
- public void DrawArc(Color color, RectangleDouble rect, int startAngle, int sweepAngle)
- {
- throw new NotImplementedException();
- }
- public void DrawLine(Color color, Vector2 start, Vector2 end)
- {
- Line(start, end, color);
- }
- public void DrawString(string text,
- Vector2 position,
- double pointSize = 12,
- Justification justification = Justification.Left,
- Baseline baseline = Baseline.Text,
- Color color = default,
- bool drawFromHintedCach = false,
- Color backgroundColor = default,
- bool bold = false,
- TypeFace typeface = null)
- {
- DrawString(text, position.X, position.Y, pointSize, justification, baseline, color, drawFromHintedCach, backgroundColor, bold,typeface);
- }
- /// <summary>
- /// Draws a string on a typeface printer object with various optional styling parameters.
- /// </summary>
- /// <param name="text">The string text to be drawn.</param>
- /// <param name="x">The x-coordinate where the string starts.</param>
- /// <param name="y">The y-coordinate where the string starts.</param>
- /// <param name="pointSize">The size of the point in pixels. Default is 12.</param>
- /// <param name="justification">Defines the justification of the string, i.e., the alignment of the text. It can be left, right, or center. Default is 'Left'.</param>
- /// <param name="baseline">Defines the baseline alignment of the text, i.e., the vertical alignment of the text. It can be 'Text', 'Ideographic', etc. Default is 'Text'.</param>
- /// <param name="color">Defines the color of the text. Default is 'Black' if not specified.</param>
- /// <param name="drawFromHintedCach">A boolean flag to indicate if the rendered string should be drawn from hinted cache. Default is 'false'.</param>
- /// <param name="backgroundColor">Defines the background color of the text. No background color is applied if not specified.</param>
- /// <param name="bold">A boolean flag to indicate if the text should be bold. Default is 'false'.</param>
- /// <returns>Returns a TypeFacePrinter object that holds the rendered string and drawing settings.</returns>
- /// <example>
- /// TypeFacePrinter printer = DrawString("Hello World", 50, 50, 14, Justification.Center, Baseline.Text, Color.Red, true, Color.White, true);
- /// </example>
- /// <remarks>
- /// If the 'color' parameter's alpha value is zero, the function will interpret it as the color black.
- /// If the 'backgroundColor' parameter's alpha value is not zero, a rectangle of that color will be drawn as a background behind the string.
- /// </remarks>
- public TypeFacePrinter DrawString(string text,
- double x,
- double y,
- double pointSize = 12,
- Justification justification = Justification.Left,
- Baseline baseline = Baseline.Text,
- Color color = default,
- bool drawFromHintedCach = false,
- Color backgroundColor = default,
- bool bold = false,
- TypeFace typeface = null)
- {
- TypeFacePrinter stringPrinter = typeface == null ? new TypeFacePrinter(text, pointSize, new Vector2(x, y), justification, baseline, bold):new TypeFacePrinter(text,new StyledTypeFace(typeface,pointSize),new Vector2(x,y),justification,baseline);
- if (color.Alpha0To255 == 0)
- {
- color = Color.Black;
- }
- if (backgroundColor.Alpha0To255 != 0)
- {
- FillRectangle(stringPrinter.LocalBounds, backgroundColor);
- }
- stringPrinter.DrawFromHintedCache = drawFromHintedCach;
- stringPrinter.Render(this, color);
- return stringPrinter;
- }
- public void DrawString(TypeFacePrinter stringPrinter,Color color = default,Color backgroundColor = default)
- {
- if(stringPrinter ==null) throw new ArgumentNullException(nameof(stringPrinter));
- if (color.Alpha0To255 == 0)
- {
- color = Color.Black;
- }
- if (backgroundColor.Alpha0To255 != 0)
- {
- FillRectangle(stringPrinter.LocalBounds, backgroundColor);
- }
- stringPrinter.Render(this, color);
- }
- public void FillRectangle(RectangleDouble rect, IColorType fillColor)
- {
- FillRectangle(rect.Left, rect.Bottom, rect.Right, rect.Top, fillColor);
- }
- public void FillRectangle(RectangleInt rect, IColorType fillColor)
- {
- FillRectangle(rect.Left, rect.Bottom, rect.Right, rect.Top, fillColor);
- }
- public void FillRectangle(Vector2 leftBottom, Vector2 rightTop, IColorType fillColor)
- {
- FillRectangle(leftBottom.X, leftBottom.Y, rightTop.X, rightTop.Y, fillColor);
- }
- public abstract void FillRectangle(double left, double bottom, double right, double top, IColorType fillColor);
- public abstract RectangleDouble GetClippingRect();
- public Affine GetTransform()
- {
- return affineTransformStack.Peek();
- }
- public void Initialize(IImageByte destImage, ScanlineRasterizer rasterizer)
- {
- destImageByte = destImage;
- destImageFloat = null;
- this.rasterizer = rasterizer;
- }
- public void Initialize(IImageFloat destImage, ScanlineRasterizer rasterizer)
- {
- destImageByte = null;
- destImageFloat = destImage;
- this.rasterizer = rasterizer;
- }
- /// <summary>
- /// Render a line
- /// </summary>
- /// <param name="start">start position</param>
- /// <param name="end">end position</param>
- /// <param name="color">line color</param>
- /// <param name="strokeWidth">The width in pixels, -1 will render 1 pixel scaled to device units</param>
- public void Line(Vector2 start, Vector2 end, Color color, double strokeWidth = -1)
- {
- if (strokeWidth == -1)
- {
- strokeWidth = 1 * DeviceScale;
- }
- Line(start.X, start.Y, end.X, end.Y, color, strokeWidth);
- }
-
- public IVertexSource GetLine(double x1, double y1, double x2, double y2, double strokeWidth = -1)
- {
- if (strokeWidth == -1)
- {
- strokeWidth = 1 * DeviceScale;
- }
- var lineToDraw = new VertexStorage();
- lineToDraw.Clear();
- lineToDraw.MoveTo(x1, y1);
- lineToDraw.LineTo(x2, y2);
- return new Stroke(lineToDraw, strokeWidth);
- }
- /// <summary>
- /// Render a line
- /// </summary>
- /// <param name="x1">x start</param>
- /// <param name="y1">y start</param>
- /// <param name="x2">x end</param>
- /// <param name="y2">y end</param>
- /// <param name="color">color of the line</param>
- /// <param name="strokeWidth">The width in pixels, -1 will render 1 pixel scaled to device units</param>
- public virtual void Line(double x1, double y1, double x2, double y2, Color color, double strokeWidth = -1)
- {
- this.Render(GetLine(x1, y1, x2, y2, strokeWidth), color);
- }
- public Affine PopTransform()
- {
- if (affineTransformStack.Count == 1)
- {
- throw new System.Exception("You cannot remove the last transform from the stack.");
- }
- return affineTransformStack.Pop();
- }
- public void PushTransform()
- {
- if (affineTransformStack.Count > 1000)
- {
- throw new System.Exception("You seem to be leaking transforms. You should be popping some of them at some point.");
- }
- affineTransformStack.Push(affineTransformStack.Peek());
- }
- public abstract void Rectangle(double left, double bottom, double right, double top, Color color, double strokeWidth = -1);
- public void Rectangle(RectangleDouble rect, Color color, double strokeWidth = -1)
- {
- if (strokeWidth == -1)
- {
- strokeWidth = 1 * DeviceScale;
- }
- Rectangle(rect.Left, rect.Bottom, rect.Right, rect.Top, color, strokeWidth);
- }
- public void Rectangle(RectangleInt rect, Color color)
- {
- Rectangle(rect.Left, rect.Bottom, rect.Right, rect.Top, color);
- }
- public abstract void Render(IVertexSource vertexSource, IColorType colorType);
- public void Render(IImageByte imageSource, Point2D position)
- {
- Render(imageSource, position.x, position.y);
- }
- public void Render(IImageByte imageSource, Vector2 position)
- {
- Render(imageSource, position.X, position.Y);
- }
- public void Render(IImageByte imageSource, Vector2 position, double width, double height)
- {
- Render(imageSource, position.X, position.Y, width, height);
- }
- public void Render(IImageByte imageSource, double x, double y)
- {
- Render(imageSource, x, y, 0, 1, 1);
- }
- public void Render(IImageByte imageSource, double x, double y, double width, double height)
- {
- Render(imageSource, x, y, 0, width / imageSource.Width, height / imageSource.Height);
- }
- public abstract void Render(IImageByte imageSource,
- double x,
- double y,
- double angleRadians,
- double scaleX,
- double scaleY);
- public abstract void Render(IImageFloat imageSource,
- double x,
- double y,
- double angleRadians,
- double scaleX,
- double scaleY);
- public void Render(IVertexSource vertexSource, double x, double y, IColorType color)
- {
- Render(new VertexSourceApplyTransform(vertexSource, Affine.NewTranslation(x, y)), color);
- }
- public void Render(IVertexSource vertexSource, Vector2 position, IColorType color)
- {
- Render(new VertexSourceApplyTransform(vertexSource, Affine.NewTranslation(position.X, position.Y)), color);
- }
- public void RenderMaxSize(ImageBuffer image, Vector2 position, Vector2 maxSize)
- {
- var zero = Vector2.Zero;
- RenderMaxSize(image, position, maxSize, ref zero, out _);
- }
- public void RenderMaxSize(ImageBuffer image, Vector2 position, Vector2 maxSize, ref Vector2 origin)
- {
- RenderMaxSize(image, position, maxSize, ref origin, out _);
- }
- /// <summary>
- /// Renders the given image at the given position scaling down if bigger than maxSize
- /// </summary>
- /// <param name="image">The image to render</param>
- /// <param name="position">The postion to render it at</param>
- /// <param name="maxSize">The max size to allow it to render to. Will be scaled down to fit.</param>
- /// <param name="origin">The postion in the sourc to hold at the 'positon'</param>
- /// <param name="size"></param>
- public void RenderMaxSize(ImageBuffer image, Vector2 position, Vector2 maxSize, ref Vector2 origin, out Vector2 size)
- {
- var ratio = GetScallingBaseOnMaxSize(image, maxSize, out size);
- origin *= ratio;
- if (size.X != image.Width)
- {
- this.Render(image.CreateScaledImage(size.X / image.Width), position.X - origin.X, position.Y - origin.Y, size.X, size.Y);
- }
- else
- {
- this.Render(image, position - origin);
- }
- }
- public void RenderInRect(string text,
- double pointSize,
- RectangleDouble fitRect,
- out RectangleDouble renderedBounds,
- double xPositionRatio = 0,
- double yPositionRatio = 0,
- double debugBoundsWidth = 0)
- {
- RenderInRect(text, AggContext.DefaultFont, pointSize, fitRect, out renderedBounds, xPositionRatio, yPositionRatio, debugBoundsWidth);
- }
- public void RenderInRect(string text,
- TypeFace font,
- double pointSize,
- RectangleDouble fitRect,
- out RectangleDouble renderedBounds,
- double xPositionRatio = 0,
- double yPositionRatio = 0,
- double debugBoundsWidth = 0)
- {
- var styledTypeFace = new StyledTypeFace(font, pointSize * 300 / 72);
- var typeFacePrinter = new TypeFacePrinter(text, styledTypeFace);
- RenderInRect(new ColoredVertexSource[] { new ColoredVertexSource(typeFacePrinter, Color.Black) }, fitRect, out renderedBounds, xPositionRatio, yPositionRatio, debugBoundsWidth);
- }
- /// <summary>
- /// Renders the given vector source making scaled to fit the given rect. Scalling will remain proportional.
- /// If the vector source is smaller in one dimension it will be offset based on the position ratio
- /// </summary>
- /// <param name="source">The vector source to render</param>
- /// <param name="fitRect">The rect to scale to fit within</param>
- /// <param name="xPositionRatio">The ratio of the width to offset in x if not fully utilized</param>
- /// <param name="yPositionRatio">The ratio of the height to offset in y if not fully utilized</param>
- /// <param name="debugShowBounds">Render an outline of the total rectangle</param>
- public void RenderInRect(IEnumerable<ColoredVertexSource> source,
- RectangleDouble fitRect,
- out RectangleDouble renderedBounds,
- double xPositionRatio = 0,
- double yPositionRatio = 0,
- double debugBoundsWidth = 0)
- {
- renderedBounds = RectangleDouble.ZeroIntersection;
- xPositionRatio = Math.Max(0, Math.Min(1, xPositionRatio));
- yPositionRatio = Math.Max(0, Math.Min(1, yPositionRatio));
- RectangleDouble totalBounds = RectangleDouble.ZeroIntersection;
- foreach (var colorVertices in source)
- {
- var bounds = colorVertices.VertexSource.GetBounds();
- totalBounds.ExpandToInclude(bounds);
- }
-
- foreach (var colorVertices in source)
- {
- double scale;
- if (totalBounds.Width > fitRect.Width
- || totalBounds.Height > fitRect.Height)
- {
- // we need to scale down
- scale = Math.Min(fitRect.Width / totalBounds.Width, fitRect.Height / totalBounds.Height);
- }
- else
- {
- // we need to scale up
- scale = Math.Min(fitRect.Width / totalBounds.Width, fitRect.Height / totalBounds.Height);
- }
- // zero out the offset
- var transform = Affine.NewTranslation(-totalBounds.Left, -totalBounds.Bottom);
- // scale
- transform *= Affine.NewScaling(scale);
- // offset to the fit rect
- transform *= Affine.NewTranslation(fitRect.Left, fitRect.Bottom);
- // do we need to move it to account for position ratios
- var scaledBounds = totalBounds * scale;
- transform *= Affine.NewTranslation((fitRect.Width - scaledBounds.Width) * xPositionRatio, (fitRect.Height - scaledBounds.Height) * yPositionRatio);
- var flattened = new FlattenCurves(new VertexSourceApplyTransform(colorVertices.VertexSource, transform));
- renderedBounds.ExpandToInclude(flattened.GetBounds());
- this.Render(flattened, colorVertices.Color);
- }
- if (debugBoundsWidth > 0)
- {
- this.Rectangle(fitRect, Color.Red, debugBoundsWidth);
- }
- }
- public void RenderScale(IImageByte image, double x, double y, double sizeX)
- {
- var ratio = sizeX / image.Width;
- var sizeY = image.Height * ratio;
- this.Render(image, x, y, sizeX, sizeY);
- }
- public abstract void SetClippingRect(RectangleDouble rect_d);
- public void SetTransform(Affine value)
- {
- affineTransformStack.Pop();
- affineTransformStack.Push(value);
- }
- }
- public static class ColoredVertexSourceExtensions
- {
- public static RectangleDouble GetBounds(this IEnumerable<ColoredVertexSource> source)
- {
- RectangleDouble totalBounds = RectangleDouble.ZeroIntersection;
- foreach (var colorVertices in source)
- {
- var bounds = colorVertices.VertexSource.GetBounds();
- totalBounds.ExpandToInclude(bounds);
- }
- return totalBounds;
- }
- }
- }
|