/** * 将NestPath列表转换成父子关系的树 * @param list * @param idstart * @return */ public static int toTree(List <NestPath> list, int idstart) { List <NestPath> parents = new List <NestPath>(); int id = idstart; /** * 找出所有的内回环 */ for (int i = 0; i < list.Count; i++) { NestPath p = list[i]; bool isChild = false; for (int j = 0; j < list.Count; j++) { if (j == i) { continue; } if (GeometryUtil.pointInPolygon(p.getSegments()[0], list[j]) == true) { list[j].getChildren().Add(p); p.setParent(list[j]); isChild = true; break; } } if (!isChild) { parents.Add(p); } } /** * 将内环从list列表中去除 */ for (int i = 0; i < list.Count; i++) { if (parents.IndexOf(list[i]) < 0) { list.RemoveAt(i); i--; } } for (int i = 0; i < parents.Count; i++) { parents[i].setId(id); id++; } for (int i = 0; i < parents.Count; i++) { if (parents[i].getChildren().Count > 0) { id = toTree(parents[i].getChildren(), id); } } return(id); }
/** * 坐标转换,与clipper库交互必须坐标转换 * @param polygon * @return */ public static Path scaleUp2ClipperCoordinates(NestPath polygon) { Path p = new Path(); foreach (Segment s in polygon.getSegments()) { ClipperCoor cc = CommonUtil.toClipperCoor(s.x, s.y); p.Add(new IntPoint(cc.getX(), cc.getY())); } return(p); }
public static Path NestPath2Path(NestPath nestPath) { Path path = new Path(); foreach (Segment s in nestPath.getSegments()) { ClipperCoor coor = CommonUtil.toClipperCoor(s.getX(), s.getY()); var lp = new IntPoint(coor.getX(), coor.getY()); path.Add(lp); } return(path); }
public static List <NestPath> polygonOffset(NestPath polygon, double offset) { List <NestPath> result = new List <NestPath>(); if (offset == 0 || GeometryUtil.almostEqual(offset, 0)) { /** * return EmptyResult */ return(result); } Path p = new Path(); foreach (Segment s in polygon.getSegments()) { ClipperCoor cc = toClipperCoor(s.getX(), s.getY()); p.Add(new IntPoint(cc.getX(), cc.getY())); } int miterLimit = 2; ClipperOffset co = new ClipperOffset(miterLimit, Config.CURVE_TOLERANCE * Config.CLIIPER_SCALE); co.AddPath(p, JoinType.jtRound, EndType.etClosedPolygon); Paths newpaths = new Paths(); co.Execute(ref newpaths, offset * Config.CLIIPER_SCALE); /** * 这里的length是1的话就是我们想要的 */ for (int i = 0; i < newpaths.Count; i++) { result.Add(CommonUtil.clipperToNestPath(newpaths[i])); } if (offset > 0) { NestPath from = result[0]; if (GeometryUtil.polygonArea(from) > 0) { from.reverse(); } from.add(from.get(0)); from.getSegments().RemoveAt(0); } return(result); }
public static List <String> svgGenerator(List <NestPath> list, List <List <Placement> > applied, double binwidth, double binHeight) { List <String> strings = new List <String>(); int x = 10; int y = 0; foreach (List <Placement> binlist in applied) { String s = " <g transform=\"translate(" + x + " " + y + ")\">" + "\n"; s += " <rect x=\"0\" y=\"0\" width=\"" + binwidth + "\" height=\"" + binHeight + "\" fill=\"none\" stroke=\"#010101\" stroke-width=\"1\" />\n"; foreach (Placement placement in binlist) { int bid = placement.bid; NestPath nestPath = getNestPathByBid(bid, list); double ox = placement.translate.x; double oy = placement.translate.y; double rotate = placement.rotate; s += "<g transform=\"translate(" + ox + x + " " + oy + y + ") rotate(" + rotate + ")\"> \n"; s += "<path d=\""; for (int i = 0; i < nestPath.getSegments().Count; i++) { if (i == 0) { s += "M"; } else { s += "L"; } Segment segment = nestPath.get(i); s += segment.x + " " + segment.y + " "; } s += "Z\" fill=\"#8498d1\" stroke=\"#010101\" stroke-width=\"1\" />" + " \n"; s += "</g> \n"; } s += "</g> \n"; y += (int)(binHeight + 50); strings.Add(s); } return(strings); }
//图像偏置算法 public static void offsetTree(List <NestPath> t, double offset) { for (int i = 0; i < t.Count; i++) { List <NestPath> offsetPaths = polygonOffset(t[i], offset); if (offsetPaths.Count == 1) { t[i].clear(); NestPath from = offsetPaths[0]; foreach (Segment s in from.getSegments()) { t[i].add(s); } } if (t[i].getChildren().Count > 0) { offsetTree(t[i].getChildren(), -offset); } } }
public static List <NestPath> transferSvgIntoPolygons(string xmlFilePath) { List <NestPath> nestPaths = new List <NestPath>(); XDocument document = XDocument.Load(xmlFilePath); List <XElement> elementList = document.Root.DescendantNodes().OfType <XElement>().ToList(); //对于测试库的数据,需要做一下筛选,如果是自己弄得测试数据,这句可以不要 var elements = elementList.Where(p => p.Name == "polygon"); int count = 0; int index = 0; foreach (XElement element in elements) { count++; //对于测试库的数据要加上这一句 var elementFirstNode = (XElement)element.FirstNode; var rotation = int.Parse(element.Attributes((XName)"nVertices").ToList()[0].Value.ToString()); switch (elementFirstNode.Name.ToString()) { case "polyline": case "polygon": { String datalist = element.Attributes((XName)"points").ToList()[0].Value.ToString(); NestPath polygon = new NestPath(); polygon.setElement(element.ToString()); foreach (String s in datalist.Split(' ')) { var temp = s.Trim(); if (temp.IndexOf(",") == -1) { continue; } String[] value = s.Split(','); double x = Double.Parse(value[0]); double y = Double.Parse(value[1]); polygon.add(x, y); //点的坐标 } polygon.bid = count; //多边形的序号 polygon.setRotation(4); //旋转角度,值设为4时代表角度可以旋转90、180、270,该值一般为360的倍数 nestPaths.Add(polygon); break; } case "lines": { index++; var piesCount = int.Parse(elementFirstNode.Attributes((XName)"count").ToList()[0].Value.ToString()); var dataList = elementFirstNode.DescendantNodes().OfType <XElement>().ToList(); NestPath polygon = new NestPath(); string point = null; foreach (var data in dataList) { if (data.Name == "segment") { //为了保证在排样前各个图形的坐标不重合,所以后面增加2000 * index var x0 = Double.Parse(data.Attributes((XName)"x0").ToList()[0].Value.ToString()) + 20 * index; var y0 = Double.Parse(data.Attributes((XName)"y0").ToList()[0].Value.ToString()) + 20 * index; var x1 = Double.Parse(data.Attributes((XName)"x1").ToList()[0].Value.ToString()) + 20 * index; var y1 = Double.Parse(data.Attributes((XName)"y1").ToList()[0].Value.ToString()) + 20 * index; point += x0 + "," + y0 + " "; polygon.add(x0, y0); polygon.add(x1, y1); } } var listTemp = polygon.getSegments(); var newList = new List <Segment>(); for (int i = 0; i < listTemp.Count; i++) { if (i % 2 == 0) { newList.Add(listTemp[i]); } } polygon.setSegments(newList); polygon.bid = count; //多边形的序号 polygon.setRotation(rotation); //旋转角度,值设为4时代表角度可以旋转90、180、270,该值一般为360的倍数 var elementTemp = " <polygon fill=\"none\" stroke=\"#010101\" stroke-miterlimit=\"10\" points= \"" + point + " \"></polygon>"; for (int i = 0; i < piesCount; i++) { polygon.setElement(elementTemp); nestPaths.Add(polygon); } break; } case "rect": { double width = Double.Parse(element.Attributes((XName)"width").ToList()[0].Value.ToString()); double height = Double.Parse(element.Attributes((XName)"height").ToList()[0].Value.ToString()); double x = Double.Parse(element.Attributes((XName)"x").ToList()[0].Value.ToString()); double y = Double.Parse(element.Attributes((XName)"y").ToList()[0].Value.ToString()); NestPath rect = new NestPath(); rect.setElement(element.ToString()); rect.add(x, y); rect.add(x + width, y); rect.add(x + width, y + height); rect.add(x, y + height); rect.bid = count; rect.setRotation(4); nestPaths.Add(rect); break; } case "circle": //对于圆形,给转换成坐标形式 { double cx = Double.Parse(element.Attributes((XName)"cx").ToList()[0].Value.ToString()); double cy = Double.Parse(element.Attributes((XName)"cy").ToList()[0].Value.ToString()); double radius = Double.Parse(element.Attributes((XName)"r").ToList()[0].Value.ToString()); // num is the smallest number of segments required to approximate the circle to the given tolerance var num = Math.Ceiling((2 * Math.PI) / Math.Acos(1 - (ToleranceConfig.tolerance / radius))); if (num < 3) { num = 3; } NestPath circle = new NestPath(); circle.setElement(element.ToString()); circle.bid = count; circle.setRotation(4); for (var i = 0; i < num; i++) { var theta = i * ((2 * Math.PI) / num); double x = radius * Math.Cos(theta) + cx; double y = radius * Math.Sin(theta) + cy; circle.add(x, y); } nestPaths.Add(circle); break; } case "ellipse": //对于椭圆,给转换成坐标形式 { // same as circle case. There is probably a way to reduce points but for convenience we will just flatten the equivalent circular polygon var rx = Double.Parse(element.Attributes((XName)"rx").ToList()[0].Value.ToString()); var ry = Double.Parse(element.Attributes((XName)"ry").ToList()[0].Value.ToString()); var maxradius = Math.Max(rx, ry); var cx = Double.Parse(element.Attributes((XName)"cx").ToList()[0].Value.ToString()); var cy = Double.Parse(element.Attributes((XName)"cy").ToList()[0].Value.ToString()); var num = Math.Ceiling((2 * Math.PI) / Math.Acos(1 - (ToleranceConfig.tolerance / maxradius))); if (num < 3) { num = 3; } NestPath ellipse = new NestPath(); ellipse.setElement(element.ToString()); ellipse.bid = count; ellipse.setRotation(4); for (var i = 0; i < num; i++) { var theta = i * ((2 * Math.PI) / num); double x = maxradius * Math.Cos(theta) + cx; double y = maxradius * Math.Sin(theta) + cy; ellipse.add(x, y); } nestPaths.Add(ellipse); break; } case "path": //对于带弧形的多边形,给转换成坐标形式 { var path = element.Attributes((XName)"d").ToList()[0].Value.ToString(); var pathNumbers = transferPathToNumber(path); string pathNumberType = "MLHVCSQTA"; NestPath pathPloy = new NestPath(); pathPloy.setElement(element.ToString()); pathPloy.bid = count; pathPloy.setRotation(4); double x, y, x0, y0, x1, y1, x2, y2, prevx, prevy, prevx1, prevy1, prevx2, prevy2; x = y = x0 = y0 = x1 = y1 = x2 = y2 = prevx = prevy = prevx1 = prevy1 = prevx2 = prevy2 = 0; for (var i = 0; i < pathNumbers.Count; i++) { var s = pathNumbers[i].Numbers; ////对应C#中的pathNumber的Numbers var command = pathNumbers[i].Type; //对应C#中的pathNumber的path type prevx = x; prevy = y; prevx1 = x1; prevy1 = y1; prevx2 = x2; prevy2 = y2; if (pathNumberType.Contains(command)) { switch (command) { case "M": case "L": case "T": { x = s[0]; y = s[1]; break; } case "H": { x = s[0]; break; } case "V": { y = s[0]; break; } case "Q": { x1 = s[0]; y1 = s[1]; x = s[2]; y = s[3]; break; } case "S": { x2 = s[0]; y2 = s[1]; x = s[2]; y = s[3]; break; } case "C": { x1 = s[0]; y1 = s[1]; x2 = s[2]; y2 = s[3]; x = s[4]; y = s[5]; break; } } } else { switch (command) { case "m": case "l": case "t": { x += s[0]; y += s[1]; break; } case "h": { x += s[0]; break; } case "v": { y += s[0]; break; } case "q": { x1 = x + s[0]; y1 = y + s[1]; x += s[2]; y += s[3]; break; } case "s": { x2 = x + s[0]; y2 = y + s[1]; x += s[2]; y += s[3]; break; } case "c": { x1 = x + s[0]; y1 = y + s[1]; x2 = x + s[2]; y2 = y + s[3]; x += s[4]; y += s[5]; break; } } } switch (command) { // linear line types case "m": case "M": case "l": case "L": case "h": case "H": case "v": case "V": pathPloy.add(x, y); break; // Quadratic Beziers case "t": case "T": { // implicit control point var tPathNumberType = "QqTt"; if (i > 0 && tPathNumberType.Contains(pathNumbers[i - 1].Type)) { x1 = prevx + (prevx - prevx1); y1 = prevy + (prevy - prevy1); } else { x1 = prevx; y1 = prevy; } break; } case "q": case "Q": { var pointlist = GeometryUtil.QuadraticBezierLinearize(new Segment(x: prevx, y: prevy), new Segment(x: x, y: y), new Segment(x: x1, y: y1), ToleranceConfig.tolerance); pointlist.Remove(pointlist[0]); // firstpoint would already be in the poly for (var j = 0; j < pointlist.Count; j++) { pathPloy.add(pointlist[j].x, pointlist[j].y); } break; } case "s": case "S": { var sPathNumberType = "CcSs"; if (i > 0 && sPathNumberType.Contains(pathNumbers[i - 1].Type)) { x1 = prevx + (prevx - prevx2); y1 = prevy + (prevy - prevy2); } else { x1 = prevx; y1 = prevy; } break; } case "c": case "C": { var pointlist = GeometryUtil.CubicBezierLinearize(new Segment(x: prevx, y: prevy), new Segment(x: x, y: y), new Segment(x: x1, y: y1), new Segment(x: x2, y: y2), ToleranceConfig.tolerance); pointlist.Remove(pointlist[0]); // firstpoint would already be in the poly for (var j = 0; j < pointlist.Count; j++) { pathPloy.add(pointlist[j].x, pointlist[j].y); } break; } case "a": case "A": { //var pointlist = GeometryUtil.Arc.linearize({ x: prevx, y: prevy}, { x: x, y: y}, s.r1, s.r2, s.angle, s.largeArcFlag,s.sweepFlag, this.conf.tolerance //pointlist.shift(); //for (var j = 0; j < pointlist.length; j++) //{ // var point = { }; // point.x = pointlist[j].x; // point.y = pointlist[j].y; // poly.push(point); //} break; } case "z": case "Z": { x = x0; y = y0; break; } } // Record the start of a subpath if (command == "M" || command == "m") { x0 = x; y0 = y; } } // 判断最后一个点是不是和第一个点一样,如果一样,就去除最后一个点 while (pathPloy.getSegments().Count > 0 && GeometryUtil.almostEqual(pathPloy.getSegments()[0].x, pathPloy.getSegments()[pathPloy.getSegments().Count - 1].x, ToleranceConfig.toleranceSvg) && GeometryUtil.almostEqual(pathPloy.getSegments()[0].y, pathPloy.getSegments()[pathPloy.getSegments().Count - 1].y, ToleranceConfig.toleranceSvg)) { pathPloy.getSegments().RemoveAt(pathPloy.getSegments().Count - 1); } nestPaths.Add(pathPloy); break; } } } return(nestPaths); }