Intro
I noticed an issue while implementing clipping (see this).
It looks like UIElement.Clip still render invisible parts
Rendering relatively small geometry (lines to only fill 1920x1200 area ~ 2000 vertical lines) take a lot of time. When using Clip and moving that geometry offscreen (so that clipping should remove significant part of it) it is still take same time (around 1 sec).
Ok, I found what using Geometry.Combine will do a clip (render time is reduced proportionally to removed after clipping geometry). Perfect!
Problem
Geometry.Combine doesn't work with non-closed geometry properly. It produce closed geometry. And it looks ugly, connecting first and last point:
Question
How can I perform clipping (reducing amount of geometry to be rendered) for non-closed figures?
Edit
Here is geometry before (small peace of shown on picture)
{M0;50L0;50L1;53,1395259764657L2;56,2666616782152L3;59,3690657292862L4;62,4344943582427L5;65,4508497187474L6;68,4062276342339L7;71,2889645782536L8; ...
and after
{F1M54,9999923706055;34,5491371154785L53,9999885559082;37,5655174255371 53,0000114440918;40,6309471130371 52,0000076293945;43,7333335876465 ...
Notice change at beginning, was M 0;50 L ..., become F 1 M 55;34 L ...
F1 means NonZero filling
Rule that determines whether a point is in the fill region of the path by drawing a ray from that point to infinity in any direction and then examining the places where a segment of the shape crosses the ray. Starting with a count of zero, add one each time a segment crosses the ray from left to right and subtract one each time a path segment crosses the ray from right to left. After counting the crossings, if the result is zero then the point is outside the path. Otherwise, it is inside.
And I have absolutely no clue what that means. But maybe it is important?
Edit
I should have been looking at the end of strings. There is z at the end of Path.Data, which means figure is closed.
Strangely enough, trying to remove z (by using Geometry.ToString()/Geometry.Parse() combo) doesn't works. After some investigation I found what Combine produces physically enclosing figures (commands L x;y, where x;y is the leftmost point). And the worst thing is what it's not always the last point, so simply removing last L x;y before parsing doesn't works either. =(
Edit
Sample to demonstrate problem:
Xaml:
<Path x:Name="path" Stroke="Red"/>
Code:
var geometry1 = new RectangleGeometry(new Rect(100, 100, 100, 100));
var geometry2 = new PathGeometry(new[] { new PathFigure(new Point(0,0), new[] {
new LineSegment(new Point(300, 300), true),
new LineSegment(new Point(300, 0), true),
}, false) });
//path.Data = geometry1;
//path.Data = geometry2;
//path.Data = Geometry.Combine(geometry1, geometry2, GeometryCombineMode.Intersect, null);
Pictures of geometry1 and geometry2:
Resulting Combine:
As you can see 2 lines become 3 after clipping, debugging proves it:
{F1M100;100L200;100 200;200 100;100z}
Notice, it's not only z, but also 100;100 point at the end, connecting starting point.
I attempted to implement a clipping solutions for non closed geometry based on this line intersection algorithm
Code
public static PathGeometry ClipGeometry(PathGeometry geom, Rect clipRect)
{
PathGeometry clipped = new PathGeometry();
foreach (var fig in geom.Figures)
{
PathSegmentCollection segments = new PathSegmentCollection();
Point lastPoint = fig.StartPoint;
foreach (LineSegment seg in fig.Segments)
{
List<Point> points;
if (LineIntersectsRect(lastPoint, seg.Point, clipRect, out points))
{
LineSegment newSeg = new LineSegment(points[1], true);
PathFigure newFig = new PathFigure(points[0], new[] { newSeg }, false);
clipped.Figures.Add(newFig);
}
lastPoint = seg.Point;
}
}
return clipped;
}
static bool LineIntersectsRect(Point lineStart, Point lineEnd, Rect rect, out List<Point> points)
{
points = new List<Point>();
if (rect.Contains(lineStart) && rect.Contains(lineEnd))
{
points.Add(lineStart);
points.Add(lineEnd);
return true;
}
Point outPoint;
if (Intersects(lineStart, lineEnd, rect.TopLeft, rect.TopRight, out outPoint))
{
points.Add(outPoint);
}
if (Intersects(lineStart, lineEnd, rect.BottomLeft, rect.BottomRight, out outPoint))
{
points.Add(outPoint);
}
if (Intersects(lineStart, lineEnd, rect.TopLeft, rect.BottomLeft, out outPoint))
{
points.Add(outPoint);
}
if (Intersects(lineStart, lineEnd, rect.TopRight, rect.BottomRight, out outPoint))
{
points.Add(outPoint);
}
if (points.Count == 1)
{
if (rect.Contains(lineStart))
points.Add(lineStart);
else
points.Add(lineEnd);
}
return points.Count > 0;
}
static bool Intersects(Point a1, Point a2, Point b1, Point b2, out Point intersection)
{
intersection = new Point(0, 0);
Vector b = a2 - a1;
Vector d = b2 - b1;
double bDotDPerp = b.X * d.Y - b.Y * d.X;
if (bDotDPerp == 0)
return false;
Vector c = b1 - a1;
double t = (c.X * d.Y - c.Y * d.X) / bDotDPerp;
if (t < 0 || t > 1)
return false;
double u = (c.X * b.Y - c.Y * b.X) / bDotDPerp;
if (u < 0 || u > 1)
return false;
intersection = a1 + t * b;
return true;
}
currently solution works for line based geometry, other types perhaps need to be included if needed.
test xaml
<UniformGrid Columns="2"
Margin="250,250,0,0">
<Grid>
<Path x:Name="pathClip"
Fill="#22ff0000" />
<Path x:Name="path"
Stroke="Black" />
</Grid>
<Path x:Name="path2"
Margin="100,0,0,0"
Stroke="Black" />
</UniformGrid>
test code 1
void test()
{
var geometry = new PathGeometry(new[] { new PathFigure(new Point(0,0), new[] {
new LineSegment(new Point(300, 300), true),
new LineSegment(new Point(300, 0), true),
}, false) });
Rect clipRect= new Rect(10, 10, 180, 180);
path.Data = ClipGeometry(geometry, clipRect);
path2.Data = geometry;
pathClip.Data = new RectangleGeometry(clipRect);
}
result
test code 2
void test()
{
var radius = 1.0;
var figures = new List<LineSegment>();
for (int i = 0; i < 2000; i++, radius += 0.1)
{
var segment = new LineSegment(new Point(radius * Math.Sin(i), radius * Math.Cos(i)), true);
segment.Freeze();
figures.Add(segment);
}
var geometry = new PathGeometry(new[] { new PathFigure(figures[0].Point, figures, false) });
Rect clipRect= new Rect(10, 10, 180, 180);
path.Data = ClipGeometry(geometry, clipRect);
path2.Data = geometry;
pathClip.Data = new RectangleGeometry(clipRect);
}
result
give it a try and see how close it is.
If i am right your mainquestion is:"How do i improve the performance of drawing many shapes?"
To get this working you have to understand Geometry Math.
Geometric objects can only merged/combined if they connect or overlap. And there is a big difference between path-geometry and shape-geometry.
As example if two circle overlap you can combine them in WPF
to get the overlapping region: Intersect
to get the difference: Xor
to get the combined surface: Union
to get the difference of only one shape: Exclude
For path-geometry it's a little different, because a path has no surface a path cannot Intersect | Xor | Union | Exclude another path or shape.
But WPF thinks you just forgot to close the path and is doing that for you, which result in the given result in your question.
so to achieve a performanceboost you have to filter all the geometry first for shapes and paths.
foreach(Shape geometryObj in ControlsOrWhatEver)
{
if(geometryObj is Line || geometryObj is Path || geometryObj is Polypath)
{
pathList.Add(geometryObj);
}
else
{
shapeList.Add(geometryObj);
}
}
for the shapeList you can use Geometry.Combine, but for the pathList you have to do some other work. You have to check if the connect at some point, doesnt matter if beginningPoint, endPoint or somwhere inbetween.
If you have done that you can Merge not Combine the path by yourself like:
public Polyline mergePaths(Shape line1, Shape line2)
{
if(!checkLineType(line1) || !checkLineType(line2))
{
return null;
}
if(hitTest(line1, line2))
{
//here you have to do some math to determine the overlapping points
//on these points you can do something like this:
foreach(Point p in Overlapping Points)
{
//add the first line until p then add line2 and go on to add lin 1 until another p
}
}
else
{
return null;
}
}
Related
Using CvInvoke.Canny and CvInvoke.FindContours I'm trying to find the rectangle containing the item name (Schematic: Maple Desk). This rectangle is shown in the image below:
I'm able to find a lot of rectangles but I'm not able to get this one. Tried a lot of different thresholds for Canny but to no effect. The following image shows all rectangles I currently get:
Any ideas how to tackle this? Do I need to use other thresholds or another approach? I already experimented using grayscale and blurring but that didn't give better result. I added my current source below and the original image I'm using is this:
public Mat ProcessImage(Mat img)
{
UMat filter = new UMat();
UMat cannyEdges = new UMat();
Mat rectangleImage = new Mat(img.Size, DepthType.Cv8U, 3);
//Convert the image to grayscale and filter out the noise
//CvInvoke.CvtColor(img, filter, ColorConversion.Bgr2Gray);
//Remove noise
//CvInvoke.GaussianBlur(filter, filter, new System.Drawing.Size(3, 3), 1);
// Canny and edge detection
double cannyThreshold = 1.0; //180.0
double cannyThresholdLinking = 1.0; //120.0
//CvInvoke.Canny(filter, cannyEdges, cannyThreshold, cannyThresholdLinking);
CvInvoke.Canny(img, cannyEdges, cannyThreshold, cannyThresholdLinking);
// Find rectangles
List<RotatedRect> rectangleList = new List<RotatedRect>();
using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
{
CvInvoke.FindContours(cannyEdges, contours, null, RetrType.List, ChainApproxMethod.ChainApproxSimple);
int count = contours.Size;
for (int i = 0; i < count; i++)
{
using (VectorOfPoint contour = contours[i])
using (VectorOfPoint approxContour = new VectorOfPoint())
{
CvInvoke.ApproxPolyDP(contour, approxContour, CvInvoke.ArcLength(contour, true) * 0.05, true);
// Only consider contours with area greater than 250
if (CvInvoke.ContourArea(approxContour, false) > 250)
{
// The contour has 4 vertices.
if (approxContour.Size == 4)
{
// Determine if all the angles in the contour are within [80, 100] degree
bool isRectangle = true;
System.Drawing.Point[] pts = approxContour.ToArray();
LineSegment2D[] edges = Emgu.CV.PointCollection.PolyLine(pts, true);
for (int j = 0; j < edges.Length; j++)
{
double angle = Math.Abs(edges[(j + 1) % edges.Length].GetExteriorAngleDegree(edges[j]));
if (angle < 80 || angle > 100)
{
isRectangle = false;
break;
}
}
if (isRectangle) rectangleList.Add(CvInvoke.MinAreaRect(approxContour));
}
}
}
}
}
// Draw rectangles
foreach (RotatedRect rectangle in rectangleList)
{
CvInvoke.Polylines(rectangleImage, Array.ConvertAll(rectangle.GetVertices(), System.Drawing.Point.Round), true,
new Bgr(Color.DarkOrange).MCvScalar, 2);
}
//Drawing a light gray frame around the image
CvInvoke.Rectangle(rectangleImage,
new Rectangle(System.Drawing.Point.Empty,
new System.Drawing.Size(rectangleImage.Width - 1, rectangleImage.Height - 1)),
new MCvScalar(120, 120, 120));
//Draw the labels
CvInvoke.PutText(rectangleImage, "Rectangles", new System.Drawing.Point(20, 20),
FontFace.HersheyDuplex, 0.5, new MCvScalar(120, 120, 120));
Mat result = new Mat();
CvInvoke.VConcat(new Mat[] { img, rectangleImage }, result);
return result;
}
Edit 1:
After some more fine tuning with the following thresholds for Canny
cannyThreshold 100
cannyThresholdLinking 400
And using a minimum size for all ContourAreas of 10000 I can get the following result:
Edit 2:
For those interested, solved using the current detection, no changes were needed. Used the 2 detected rectangles in the screenshot above to get the location of the missing rectangle containing the item name.
Result can be found here:
https://github.com/josdemmers/NewWorldCompanion
For those interested, solved using the current detection, no changes were needed. Used the 2 detected rectangles in the screenshot above to get the location of the missing rectangle containing the item name.
Result can be found here: https://github.com/josdemmers/NewWorldCompanion
I have grid dimension 2000x2000. I have some points stored on grid and I want to connect some of them, but connection need to be closest path from one to another.
I have :
private static readonly Dictionary<double,Point> ExistingPoints = new Dictionary<double, Point>();
private static Point[,] gridMatrix = new Point[200, 200];
where ExistingPoints is dictionary of points that are placed on grid and gridMatrix contains same points from dictionary, but his row and column are x and y from point divided by 10 so it can be represented on grid.
So how can I find closest path from one point to another?
EDIT
Path can only go on grid, meaning it can go on lines only, it can't be straight line, so when I need to go up/ down/ left/ right it need to be on 90 degrees
Okey as we said in the comments I would do this:
List<Point> path = new List<Point>();
Point startingPoint = new Point(1000, 1000);
Point targetPoint = new Point(500, 500);
Point currentPos = startingPoint;
//Level in Y
while (currentPos.Y != targetPoint.Y)
{
path.Add(new Point(currentPos.X, currentPos.Y);
currentPos.Y += (currentPos.Y > targetPoint.Y ? -1 : 1);
}
//Level in X
while(currentPos.X != targetPoint.X)
{
path.Add(new Point(currentPos.X, currentPos.Y);
currentPos.X += (currentPos.X > targetPoint.X ? -1 : 1);
}
//Draw the lines in steps
for (int i = 0; i < path.Count()-1; i++)
{
DrawLineBetween(path[i], path[i + 1]);
}
DrawLineBetween(path.Last(), targetPoint);
I did not test the code but it compiles and should work in theory. The Method DrawLineBetweenstill has to be implemented of course but I guess you have something in place here.
You could, of course, combine the X and Y leveling in one loop and then go in a "stair" pattern.
is there any example for plotting volumetric slice in Ilnumerics use community version. This is an example I got from matlab website:
Volumetric slice image example of matlab
I have array X, Y, Z as posistions and V (velocity) as value for color plotting. All I have done is use Ilpoints to plot that V in position X, Y, Z not , a surfaces. Here are My Code and the result,
ILArray<float> plotXY = ILMath.zeros<float>(3, XcoordinateXY.Length);
plotXY["0;:"] = ILMath.tosingle(SurfaceXY[":;:;1"]);
plotXY["1;:"] = ILMath.tosingle(SurfaceXY[":;:;2"]);
plotXY["2;:"] = ILMath.tosingle(SurfaceXY[":;:;3"]);
ILArray<float> ColorMap = ILMath.tosingle(SurfaceXY[":;:;0"]);
var ilsurfaceplotXY = new ILPoints()
{
/*Wireframe = { Color = Color.FromArgb(50, Color.LightGray) },
Colormap = new ILColormap(dataXY),
Children = { new ILColorbar() }*/
Positions = plotXY,
Colors = cm.Map(ColorMap).T,
Color = null
};
Here are code for displaying:
var scene = new ILScene();
scene.Add(
new ILPlotCube
{
TwoDMode = false,
Axes =
{
XAxis =
{
Label = { Text = "UTM X (Km)" },
GridMajor =
{
DashStyle = DashStyle.Dashed,
Color = Color.DarkGray,
Width = 1
}
},
YAxis =
{
Label = { Text = "UTM Y (Km)" },
GridMajor =
{
DashStyle = DashStyle.Dashed,
Color = Color.DarkGray,
Width = 1
}
},
ZAxis =
{
Label = { Text = "DEPTH (Km)" },
GridMajor =
{
DashStyle = DashStyle.Dashed,
Color = Color.DarkGray,
Width = 1
}
}
},
Children = { ilsurfaceplotXY, ilsurfaceplotXZ, ilsurfaceplotYZ },
}
);
this.ilPanel1.Scene = scene;
this.ilPanel1.Scene.Configure();
this.ilPanel1.Refresh();
And here is an image result.
Result Image
I'm sorry the image is in the link.
Regarding the visualization this can be done with regular surfaces, imagesc plots, or the new fast surface in the Drawing2 toolbox. They all allow to provide X,Y, and Z values as well as a color for each grid point or tile.
Regarding the computation of the points: it seems that you just pick points from the available set. It would be much better to interpolate between these points. The Interpolation Toolbox provides functions for the interpolation of gridded and scattered data. (In your case the data seem to be gridded ?). This allows to have slices in arbitrary orientation / angles. The interpolation toolbox interpolates the positions of the slice grid points as well as the values for the colors.
From an online example:
The setup of the horizontal slices is done as follows:
ILArray<float> C;
for (int i = 0; i < m_nrSlices; i += m_nrSlices / 4) {
C = m_V[":",":", i];
pc1.Add(new ILSurface(grid + i, C, colormap: Colormaps.Bone)
{
Wireframe = { Visible = false },
});
}
Here, m_V is your 3D dataset, handled as 3D array. pc is the plot cube. The surfaces are simply added to the plot cube. The points of the red interpolated area are dynamically computed as the user moves the red balls:
// Points on the cutting area are considered scattered points, because the area is not (necessarily) plain. However, V
// is a grid. interp3s interpolates the scattered points very efficiently.
// Note how the shape of the coordinate arrays Xn, Yn and Zn is not important. interp3s takes their elements in sequential order.
// The output is a vector of interpolated values. (We gonna reshape it below.)
ILArray < float> Z = Interpolation.interp3s(m_V, m_x, m_x, m_x, m_Xn, m_Yn, Zn, method: InterpolationMethod.cubic);
// let's plot! We get a reference to the fast surface
var fsurf = ilPanel1.Scene.First<ILFastSurface>("dynslice");
if (fsurf != null) {
// first time setup only: provide the full coordinates of X and V. Here it is sufficient to provide grid vectors.
if (fsurf.Cols == 0) {
fsurf.Update(X: m_xn * res, Y: m_xn * res, Z: Zn * res, C: ILMath.reshape(Z, Zn.S), colormap: Colormaps.Hot);
} else {
// the grid was configured already and did not change. we save some recomputing by ommiting the X and Y coordinates, prevent from reshaping buffers.
fsurf.Update(Z: Zn * res, C: ILMath.reshape(Z, Zn.S), colormap: Colormaps.Hot);
}
}
fsurf.Configure();
ilPanel1.Refresh();
To go into the details is out of scope for SO. You can download the example and run it on your machine. You will need a recent version of ILNumerics though.
EDIT: Axis aligned slices as in the plot you provided are only a subdomain, of course. Generating them works in the very same way:
Trying to draw a polar type chart using GeometryContext in C#. I have input of direction as an xRange (start & stop) which is in degrees. I'm converting this to Radians. All good. The yRange is cut in & cut out wind speed currently in m/s as a double. I'm trying to acheive a simplified version of the image below without the axis labels etc.
For each object to be charted I am returning an XY range:
public IEnumerable<Styled2DRange> Query()
{
SolidColorBrush brush = new SolidColorBrush(Colors.Maroon);
brush.Freeze();
Pen linePen = new Pen(brush, 3);
linePen.Freeze();
SolidColorBrush fillBrush = new SolidColorBrush(Colors.Maroon);
fillBrush.Freeze();
foreach (var range in this.Charts)
{
Range xRange = new Range(ConvertToRadians(range.StartDirection), ConvertToRadians(range.EndDirection));
Range yRange = new Range(range.CutInWindSpeed, range.CutOutWindSpeed);
yield return new 2DRange()
{
Range = new XYRange()
{
XRange = xRange,
YRange = yRange
},
Line = linePen,
Fill = fillBrush
};
}
yield break;
}
This method is called from my override of Onrender. Obviously my points to be drawn by the StreamGeometryContext don't make sense as the Range.Y values are just wind speeds in m/s:
protected override void OnRender(DrawingContext dc)
{
Point origin = new Point(0, 0);
double maxR = 0;
SweepDirection outerSweep = SweepDirection.Clockwise;
SweepDirection innerSweep = SweepDirection.Counterclockwise;
outerSweep = SweepDirection.Counterclockwise;
innerSweep = SweepDirection.Clockwise;
foreach (Styled2DRange range in Query())
{
maxR = Math.Max(maxR, range.Range.YRange.End);
Point outerScreenPointBefore = new Point(range.Range.XRange.Start, range.Range.YRange.End);
Point outerScreenPointAfter = new Point(range.Range.XRange.End, range.Range.YRange.End);
Point innerScreenPointBefore = new Point(range.Range.XRange.Start, range.Range.YRange.Start);
Point innerScreenPointAfter = new Point(range.Range.XRange.End, range.Range.YRange.Start);
StreamGeometry sectorGeometry = new StreamGeometry();
sectorGeometry.FillRule = FillRule.Nonzero;
using (StreamGeometryContext geometryContext = sectorGeometry.Open())
{
geometryContext.BeginFigure(innerScreenPointBefore, true, true);
geometryContext.LineTo(outerScreenPointBefore, true, false);
double outerCircleRadius = Math.Sqrt(Math.Pow(outerScreenPointBefore.X - origin.X, 2) + Math.Pow(outerScreenPointBefore.Y - origin.Y, 2));
geometryContext.ArcTo(outerScreenPointAfter, new Size(outerCircleRadius, outerCircleRadius), 0, false, outerSweep, true, false);
geometryContext.LineTo(innerScreenPointAfter, true, false);
double innerCircleRadius = Math.Sqrt(Math.Pow(innerScreenPointBefore.X - origin.X, 2) + Math.Pow(innerScreenPointBefore.Y - origin.Y, 2));
geometryContext.ArcTo(innerScreenPointBefore, new Size(innerCircleRadius, innerCircleRadius), 0, false, innerSweep, true, false);
}
sectorGeometry.Freeze();
dc.DrawGeometry(range.Fill, range.Line, sectorGeometry);
}
}
So how do I reference the wind speed to create an actual point within the bounds of the drawing?
The four points of your chart sector are lying on two concentric circles, where the radius of the inner circle is given by the start wind speed, and that of the outer circle by the end wind speed. The position of the points on each circle is directly given by the wind directions in radians.
Provided that you have the variables startDirection and endDirection for the wind directions and startSpeed and endSpeed for the wind speeds, a sector would be constructed like this:
var pStart = new Point(Math.Sin(startDirection), -Math.Cos(startDirection));
var pEnd = new Point(Math.Sin(endDirection), -Math.Cos(endDirection));
var isLargeArc = Math.Abs(endDirection - startDirection) > Math.PI;
var geometry = new StreamGeometry();
using (var sgc = geometry.Open())
{
sgc.BeginFigure( // start point on inner circle
new Point(startSpeed * pStart.X, startSpeed * pStart.Y),
true, true);
sgc.ArcTo( // end point on inner circle
new Point(startSpeed * pEnd.X, startSpeed * pEnd.Y),
new Size(startSpeed, startSpeed), // radius of inner circle
0d, isLargeArc, SweepDirection.Clockwise, true, true);
sgc.LineTo( // end point on outer circle
new Point(endSpeed * pEnd.X, endSpeed * pEnd.Y),
true, true);
sgc.ArcTo( // start point on outer circle
new Point(endSpeed * pStart.X, endSpeed * pStart.Y),
new Size(endSpeed, endSpeed), // radius of outer circle
0d, isLargeArc, SweepDirection.Counterclockwise, true, true);
}
Hi I have a path from GetFlattenedPathGeometry where i can iterate through the figures and segments to get the points to add to a PointCollection.
I then multiply each point.x/y by a scale factor to get a full scaled version of the original path data. (not using scaletransform as it doesn't suit my requirements).
If i use something like:
public static PathGeometry GetPathGeometry(PointCollection polygonCorners)
{
List<PathSegment> pathSegments = new List<PathSegment> { new PolyLineSegment(polygonCorners, true) };
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(new PathFigure(polygonCorners[0], pathSegments, true));
return pathGeometry;
}
It returns a new path geometry but doesn't handle ellipses with excluded path geometry in that the path is just one continuous line.
Is there a way to convert the PointCollection to Path.Data (eg: with the "M" "L" and such) for me to re-use Geometry.Parse(the new string)?
Here is the code i'm using to get the flattenedgeometry pointcollection:
PathGeometry g = path.Data.GetFlattenedPathGeometry();
foreach (var f in g.Figures)
{
foreach (var s in f.Segments)
{
if (s is PolyLineSegment)
{
foreach (var pt in ((PolyLineSegment) s).Points)
{
strGeom += pt.ToString();
Point ptn = new Point(pt.X * ScaleX, pt.Y * ScaleY);
pcol.Add(ptn);
}
}
}
}
< Edit Images >
Here is the original path with rectangles and ellipses subtracted from the geometry.
And here is what is looks like re-creating from the code.
If i use the original GetFlattenedPathGeometry, it looks like the original but i need to scale the points to a new resolution.
Hope this makes it clearer.
You could simply call ToString on the PathGeometry to get the whole path data string at once:
var sourceGeometry = path.Data.GetFlattenedPathGeometry();
var geometryString = sourceGeometry.ToString(CultureInfo.InvariantCulture);
var targetGeometry = Geometry.Parse(geometryString);
And why can't you just apply a ScaleTransform to the whole geometry before calling GetFlattenedPathGeometry? The following works perfectly for me (with two EllipseGeometries in an excluding CombinedGeometry):
var pathGeometry = path.Data.Clone();
pathGeometry.Transform = new ScaleTransform(0.5, 0.5);
var scaledGeometry = pathGeometry.GetFlattenedPathGeometry();
EDIT: From what you write in your question and comments, I'm guessing that all you actually want to do is to add or combine geometries with different scaling factors. If that is true, your flattened geometry approach is by far to complicated, as you could easily do that with the following two methods:
private PathGeometry AddGeometries(
Geometry geometry1, Geometry geometry2, double scale)
{
geometry2 = geometry2.Clone();
geometry2.Transform = new ScaleTransform(scale, scale);
var pathGeometry = PathGeometry.CreateFromGeometry(geometry1);
pathGeometry.AddGeometry(geometry2);
return pathGeometry;
}
private PathGeometry CombineGeometries(
Geometry geometry1, Geometry geometry2, GeometryCombineMode mode, double scale)
{
geometry2 = geometry2.Clone();
geometry2.Transform = new ScaleTransform(scale, scale);
return Geometry.Combine(geometry1, geometry2, mode, null);
}
Given a Path with some geometry in its Data property, you may now add (or combine) an arbitray other geometry with a scaling factor with a call like this:
Geometry newGeometry1 = ...
double scale1 = ...
path.Data = AddGeometries(path.Data, newGeometry1, scale1);
Geometry newGeometry2 = ...
double scale2 = ...
path.Data = CombineGeometries(path.Data, newGeometry2,
GeometryCombineMode.Exclude, scale2);
Found the answer by perseverance.
The code to get each point of flattenedpathgeometry and add a scale to each point and recreate the same flattenedpathgeometry with the new points. hope it helps someone. And thanks Clemens. Appreciate your efforts.
path.Data = Geometry.Parse(CurrentObject.Geometry1);
PathGeometry g = path.Data.GetFlattenedPathGeometry();
PathGeometry g = path.Data.GetFlattenedPathGeometry();
foreach (var f in g.Figures)
{
Point pt1 = f.StartPoint;
pt1.X = pt1.X * ScaleX;
pt1.Y = pt1.Y * ScaleY;
strGeom += "M" + pt1.ToString();
foreach (var s in f.Segments)
if (s is PolyLineSegment)
{
count = 0;
foreach (var pt in ((PolyLineSegment)s).Points)
{
int scount = ((PolyLineSegment)s).Points.Count;
if (count == 0)
{
Point pts = new Point(pt.X * ScaleX, pt.Y * ScaleY);
strGeom += "L" + pts.ToString();
}
else if (count < scount)
{
Point pts = new Point(pt.X * ScaleX, pt.Y * ScaleY);
strGeom += " " + pts.ToString();
}
else if (count == scount)
{
Point pts = new Point(pt.X * ScaleX, pt.Y * ScaleY);
strGeom += " " + pts.ToString() + "Z";
}
count++;
}
}
}
path.Data = Geometry.Parse(strGeom);
Here's an image of the paths sent from a remote session: 1366x768 scales to 1920x1080