Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF shape geometry scaling without affecting stroke

Tags:

wpf

I am wondering if someone has managed to override the default behaviour of the WPF shape rendering when applying a scaletransform to the shape. The default behaviour transforms the entire shape drawing, including strokes, but I would like to only scale the geometry. The difficulty is that my shapes reside in a visual hierarchy with render transforms applied on different levels (sort of like a 2D scene graph but a WPF visual tree), this I cannot change(!). I have read in different places that it might be possible to create a custom shape to cancel out the transform for the render transform and put it on the geometry instead. At the moment I have something like:

public sealed class MyPath : Shape
{
    // This class has a Data property of type Geometry just like the normal Path class

    protected override Geometry DefiningGeometry
    {
        get
        {
            Geometry data = Data;

            if (data == null)
            {
                data = Geometry.Empty;
            }

            return data;
        }
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        Transform tr = RenderedGeometry.Transform;
        Geometry geomToDraw = RenderedGeometry.Clone();
        geomToDraw.Transform = new MatrixTransform(tr.Value * tr.Value);
        Matrix trInv = tr.Value; trInv.Invert();
        drawingContext.PushTransform(new MatrixTransform(trInv));
        drawingContext.DrawGeometry(Brushes.Transparent, new Pen() { Brush = Brushes.Black, Thickness = 1 }, geomToDraw);
    }
}

As is clearly evident, I am quite new to this and the above code is probably completely messed up. I was trying to transfer the matrix to the geometry without changing the final resulting geometry transform, hence the tr.Value*tr.Value and trInv. But it does not work as I want it to. I know this transfer transform technique works in theory because I tried it out with constant transforms (testing to set Geometry.Transform to scale x with 4 and pushing a transform to scale x with 0.25 worked fine but the resulting shape drawing did not seem to apply stretch=fill, which I rely upon). So there must be something that I am missing with the render transforms.

The test scenario that is not working is this:

  • I apply a render scale transform with scaleX=4 and scaleY=1 in xaml.
  • The built-in Path class scales the entire drawing so that strokes 4 times wider in the x direction than in the y direction.
  • I want MyPath to scale the geometry only, not the strokes. <- THIS IS NOT WORKING!
    • What happens is: The geometry gets scaled correctly, the strokes get scaled by 4 in the x direction and by slightly less than 4 in the y direction. What is wrong? I have a feeling that I should not be working solely with RenderedGeometry.Transform but what should I use instead? I need to incorporate the render transform and the stretch=fill on the shape. My render transforms hierarchy may contain a mix of scales, rotations and translations so the solution must be general enough to handle any transform, not just axis-aligned scaling.

Note: I know it is bad to create the geometry in OnRender but I want to get it working before spending time cleaning it up.

By the way, I read this post:

Invariant stroke thickness of Path regardless of the scale

The problem as stated before is that I do have to take render transforms into consideration and I am not sure of how to adapt that solution to work with them.

like image 818
thehan83 Avatar asked Nov 06 '12 22:11

thehan83


1 Answers

If I understand the question correctly you want to cancel out the effect of the render transformation on the pen but not on the geometry.

This could be accomplished by getting the transformation of the control relative to the item from which you want to cancel the transform an using its inverse to cancel out the effect on the pen. (so for example if you have the hierarchy P1/P2/P3/UrShape, and P1,P2,P3 all have transforms on them and you want all of them to not affect your pen, you will need to obtain the transform of P1 relative to UrShape). Then you could reapply the transform to just your shape.

var brush = new SolidColorBrush(Colors.Red);
var pen = new Pen(brush, 5);
//Instead of the direct parent you could walk up the visual tree to the root from where you want to cancel the transform

var rootTransform = (MatrixTransform)this.TransformToAncestor((Visual)this.Parent);

var inverserRootTransform = (MatrixTransform)rootTransform.Inverse;

//We cancel out the transformation from the parent 
drawingContext.PushTransform(inverserRootTransform);

var renderGeometry = this.Geometry.Clone();
// We apply the parent transform to the shape only, and group it with the original transform that was present on the shape
// we do this to honor any transformation that was set on the shape.
renderGeometry.Transform = new TransformGroup()
{
   Children =
   {
      rootTransform,
      this.Geometry.Transform
   }
};

//We draw the shape, the pen size will have the correct size since we canceled out the transform from the parent
// but the shape now has the transformation directly on it.
drawingContext.DrawGeometry(brush, pen, renderGeometry);
drawingContext.Pop();
like image 167
Titian Cernicova-Dragomir Avatar answered Nov 01 '22 03:11

Titian Cernicova-Dragomir