// Converting tracedata to an SVG string, paths are drawn according to a Z-index // the optional lcpr and qcpr are linear and quadratic control point radiuses public static string ToSvgString(this TracedImage image, SvgRendering options) { // SVG start var scaledWidth = (int)(image.Width * options.Scale); var scaledHeight = (int)(image.Height * options.Scale); var viewBoxOrViewPort = options.Viewbox ? $"viewBox=\"0 0 {scaledWidth} {scaledHeight}\"" : $"width=\"{scaledWidth}\" height=\"{scaledHeight}\""; var stringBuilder = new StringBuilder($"<svg {viewBoxOrViewPort} version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" >"); // Creating Z-index // Only selecting the first segment of each path for sorting. // Sorting Z-index is not required, TreeMap is sorted automatically return(image.Layers .SelectMany(cs => cs.Value.Paths.Select(p => { var firstSegmentStart = p.Segments.First().Start; var label = firstSegmentStart.Y * scaledWidth + firstSegmentStart.X; return new ZPosition { Label = label, Color = cs.Key, Path = p }; })).OrderBy(z => z.Label) .Aggregate(stringBuilder, (sb, z) => { var scaledSegments = z.Path.Segments.Select(s => s.Scale(options.Scale)).ToList(); return AppendSegments(sb, scaledSegments, z.Color); }).Append("</svg>").ToString()); }
//////////////////////////////////////////////////////////// // Tracing ImageData, then returning PaddedPaletteImage with tracedata in layers private static TracedImage PaddedPaletteImageToTraceData(Bitmap image, Tracing tracing, SvgRendering rendering) { // Selective Gaussian blur preprocessing //if (options.Blur.BlurRadius > 0) //{ // // TODO: This seems to not work currently. // imgd = Blur(imgd, options.Blur.BlurRadius, options.Blur.BlurDelta); //} // 1. Color quantization var colors = image.ChangeFormat(PixelFormat.Format32bppArgb).ToColorReferences(); var colorGroups = ColorGrouping.Convert(colors, image.Width, image.Height, Palette); // 2. Layer separation and edge detection var rawLayers = Layering.Convert(colorGroups, image.Width, image.Height, Palette); // 3. Batch pathscan var pathPointLayers = rawLayers.ToDictionary(cl => cl.Key, cl => new Layer <PathPointPath> { Paths = Pathing.Scan(cl.Value, tracing.PathOmit).ToList() }); // 4. Batch interpollation var interpolationPointLayers = pathPointLayers.ToDictionary(cp => cp.Key, cp => Interpolation.Convert(cp.Value)); // 5. Batch tracing var sequenceLayers = interpolationPointLayers.ToDictionary(ci => ci.Key, ci => new Layer <SequencePath> { Paths = ci.Value.Paths.Select(path => new SequencePath { Path = path, Sequences = Sequencing.Create(path.Points.Select(p => p.Direction).ToList()).ToList() }).ToList() }); var segmentLayers = sequenceLayers.ToDictionary(ci => ci.Key, ci => new Layer <SegmentPath> { Paths = ci.Value.Paths.Select(path => new SegmentPath { Segments = path.Sequences.Select(s => Segmentation.Fit(path.Path.Points, s, tracing, rendering)).SelectMany(s => s).ToList() }).ToList() }); return(new TracedImage(segmentLayers, image.Width, image.Height)); }
// 5.4. Fit a quadratic spline through this point, measure errors on every point in the sequence // helpers and projecting to get control point public static Segment Fit(IReadOnlyList <InterpolationPoint> path, double threshold, SequenceIndices sequence, int sequenceLength, ref int errorIndex, SvgRendering rendering) { var startPoint = path[sequence.Start]; var endPoint = path[sequence.End]; var fitPoint = path[errorIndex]; Func <int, double> pseudoIndexCalc = i => (i - sequence.Start) / (double)sequenceLength; var midPoint = CreateSplinePoint(pseudoIndexCalc(errorIndex), startPoint, endPoint, fitPoint, true); // Check every point var isSpline = Fit(i => path[i], i => CreateSplinePoint(pseudoIndexCalc(i), startPoint, midPoint, endPoint), threshold, sequence.Start + 1, i => i != sequence.End, i => (i + 1) % path.Count, ref errorIndex); return(isSpline ? new SplineSegment { Start = startPoint, Mid = midPoint, End = endPoint, Radius = rendering.QCpr, RoundDecimalPlaces = rendering.RoundCoords } : null); }
// 5.2. Fit a straight line on the sequence public static Segment Fit(IReadOnlyList <InterpolationPoint> path, double threshold, SequenceIndices sequence, int sequenceLength, out int errorIndex, SvgRendering rendering) { var startPoint = path[sequence.Start]; var endPoint = path[sequence.End]; var partialPoint = CreateLinePoint(sequenceLength, startPoint, endPoint, true); var pathLength = path.Count; Func <int, double> pseudoIndexCalc = i => { // I don't know what 'pl' as a variable name means. Is it related to path length? var pl = i - sequence.Start; pl += pl < 0 ? pathLength : 0; return(pl); }; errorIndex = sequence.Start; var isLine = Fit(i => path[i], i => CreateLinePoint(pseudoIndexCalc(i), startPoint, partialPoint), threshold, (sequence.Start + 1) % pathLength, i => i != sequence.End, i => (i + 1) % pathLength, ref errorIndex); return(isLine ? new LineSegment { Start = startPoint, End = endPoint, Radius = rendering.LCpr, RoundDecimalPlaces = rendering.RoundCoords } : null); }
// 5.2. - 5.6. recursively fitting a straight or quadratic line segment on this sequence of path nodes, // called from tracepath() // Returns a segment (a list of those doubles is a segment). public static IEnumerable <Segment> Fit(IReadOnlyList <InterpolationPoint> path, SequenceIndices sequence, Tracing tracing, SvgRendering rendering) { var pathLength = path.Count; // return if invalid sequence.End // TODO: When would this ever happen? if ((sequence.End > pathLength) || (sequence.End < 0)) { yield break; } // TODO: This is actually the number of line segments in the sequence. Not the number of points in the sequence. var sequenceLength = sequence.End - sequence.Start; sequenceLength += sequenceLength < 0 ? pathLength : 0; int errorIndex; var lineResult = LineSegment.Fit(path, tracing.LTres, sequence, sequenceLength, out errorIndex, rendering); if (lineResult != null) { yield return(lineResult); yield break; } // 5.3. If the straight line fails (an error>ltreshold), find the point with the biggest error var fitIndex = errorIndex; var splineResult = SplineSegment.Fit(path, tracing.QTres, sequence, sequenceLength, ref errorIndex, rendering); if (splineResult != null) { yield return(splineResult); yield break; } // 5.5. If the spline fails (an error>qtreshold), find the point with the biggest error, var splitIndex = (fitIndex + errorIndex) / 2; // 5.6. Split sequence and recursively apply 5.2. - 5.6. to startpoint-splitpoint and splitpoint-endpoint sequences foreach (var segmentPart in Fit(path, new SequenceIndices { Start = sequence.Start, End = splitIndex }, tracing, rendering)) { yield return(segmentPart); } foreach (var segmentPart in Fit(path, new SequenceIndices { Start = splitIndex, End = sequence.End }, tracing, rendering)) { yield return(segmentPart); } }