WPF: How to apply a GeneralTransform to a Geometry data and return the new geometry?

14,258

Solution 1

You could try and use Geometry.Combine. It applies a transform during the combine. One catch is that Combine only works if your Geometry has area, so single lines will not work.

Here is a sample that worked for me.

PathGeometry geometry = new PathGeometry();
geometry.Figures.Add(new PathFigure(new Point(10, 10), new PathSegment[] { new LineSegment(new Point(10, 20), true), new LineSegment(new Point(20, 20), true) }, true));
ScaleTransform transform = new ScaleTransform(2, 2);
PathGeometry geometryTransformed = Geometry.Combine(geometry, geometry, GeometryCombineMode.Intersect, transform);

Solution 2

I've found a solution with which arbitrary tranform can be applied to a path geometry, thanks to Todd White's answer:

Basically Geometry.Combine is used to combine the desired geometry with Geometry.Empty using Union, and the desired transform is given. The resulting geometry is transformed with the given transform.

PathGeometry geometryTransformed = Geometry.Combine(Geometry.Empty, geometry, GeometryCombineMode.Union, transform);

Solution 3

This is what I found you can do to get a transformed geometry with all of the figure information intact:

var geometry = new PathGeometry();
geometry.Figures.Add(new PathFigure(new Point(10, 10), new PathSegment[] { new LineSegment(new Point(10, 20), true), new LineSegment(new Point(20, 20), true) }, true));
geometry.Transform = new ScaleTransform(2, 2);

var transformedGeometry = new PathGeometry ();
// this copies the transformed figures one by one into the new geometry
transformedGeometry.AddGeometry (geometry); 

Solution 4

I didn't use accepted answer since it was returning geometry in format different from the original one, so I used this:

Geometry inputGeometry = new PathGeometry();
var inputGeometryClone = inputGeometry.Clone(); // we need a clone since in order to
                                                // apply a Transform and geometry might be readonly
inputGeometryClone.Transform = new TranslateTransform(); // applying some transform to it
var result = inputGeometryClone.GetFlattenedPathGeometry();

Solution 5

None of the quick solutions based on Geometry.Combine works in the case of path made of a single LineElement. So I solved the problem the hard way, like this (But I am also limited to PathGeometry):

public static class GeometryHelper
{
public static PointCollection TransformPoints(PointCollection pc, Transform t)
{
  PointCollection tp = new PointCollection(pc.Count);
  foreach (Point p in pc)
    tp.Add(t.Transform(p));
  return tp;
}
public static PathGeometry TransformedGeometry(PathGeometry g, Transform t)
{
  Matrix m = t.Value;
  double scaleX = Math.Sqrt(m.M11 * m.M11 + m.M21 * m.M21);
  double scaleY = (m.M11 * m.M22 - m.M12 * m.M21) / scaleX;
  PathGeometry ng = g.Clone();
  foreach (PathFigure f in ng.Figures)
  {
    f.StartPoint = t.Transform(f.StartPoint);
    foreach (PathSegment s in f.Segments)
    {
      if (s is LineSegment)
        (s as LineSegment).Point = t.Transform((s as LineSegment).Point);
      else if (s is PolyLineSegment)
        (s as PolyLineSegment).Points = TransformPoints((s as PolyLineSegment).Points, t);
      else if (s is BezierSegment)
      {
        (s as BezierSegment).Point1 = t.Transform((s as BezierSegment).Point1);
        (s as BezierSegment).Point2 = t.Transform((s as BezierSegment).Point2);
        (s as BezierSegment).Point3 = t.Transform((s as BezierSegment).Point3);
      }
      else if (s is PolyBezierSegment)
        (s as PolyBezierSegment).Points = TransformPoints((s as PolyBezierSegment).Points, t);
      else if (s is QuadraticBezierSegment)
      {
        (s as QuadraticBezierSegment).Point1 = t.Transform((s as QuadraticBezierSegment).Point1);
        (s as QuadraticBezierSegment).Point2 = t.Transform((s as QuadraticBezierSegment).Point2);
      }
      else if (s is PolyQuadraticBezierSegment)
        (s as PolyQuadraticBezierSegment).Points = TransformPoints((s as PolyQuadraticBezierSegment).Points, t);
      else if (s is ArcSegment)
      {
        ArcSegment a = s as ArcSegment;
        a.Point = t.Transform(a.Point);
        a.Size = new Size(a.Size.Width * scaleX, a.Size.Height * scaleY); // NEVER TRIED
      }
    }
  }
  return ng;
}
}
Share:
14,258
Pop Catalin
Author by

Pop Catalin

Think more - code less, or think less - code more?

Updated on June 06, 2022

Comments

  • Pop Catalin
    Pop Catalin almost 2 years

    Having some Geometry data and a Transform how can the transform be applied to the Geometry to get a new Geometry with it's data transformed ?

    Ex: I Have a Path object that has it's Path.Data set to a PathGeometry object, I want to tranform the points of the PathGeometry object in place using a transform, and not apply a transform to the PathGeometry that will be used at render time.

    P.S. I know that the Transform class has a method Point Transform.Transform(Point p) that can be used to transform a Point but...is there a way to transform a arbitrary geometry at once?

    Edit: See my repply for a currently found solution

  • Pop Catalin
    Pop Catalin over 15 years
    you can transform anything, even sizes and angles, besides, I know that you can transform points individually, and also my geometry is not frozen. I wanted to know if there's a framework way to apply transformations to an geometry object as a whole.
  • Goran
    Goran over 13 years
    The only problem is that the combined geometry is not the same as the original geometry. Differences aren't big but might be important.
  • Goran
    Goran over 13 years
    Not sure why this was down voted as it seems the only correct answer.
  • ManIkWeet
    ManIkWeet over 5 years
    This appears to be the correct way to do it, as no StandardFlatteningTolerance appears to be applied.
  • Jürgen Böhm
    Jürgen Böhm about 2 years
    The code for ArcSegment seems to be very dubious, as the rotation angle is left unmodified. But if you rotate a true ellipse the major axis rotates with it, so the rotation angle must change.
  • Jürgen Böhm
    Jürgen Böhm about 2 years
    It seems that even Microsoft shuns the complications of transforming an ellipse ArcSegment into another such. Instead they approximate it by Bezier curves and transform these: referencesource.microsoft.com/#PresentationCore/Core/CSharp/‌​… Look for 'AddToFigure'.