Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I avoid a "jump" while scaling on a canvas, when the scaling center changes due to a pinching (zoom) or other gesture in a metro-style app

How can I avoid a "jump" while scaling on a canvas object, when the scaling center changes due to a pinching (zoom) or other gesture in a metro-style app

The behaviour i am trying to archive is similar to the zooming behaviour of the pre-installed win8 maps app. If you perform a pinch gesture (zooming in or out), the center of scaling is set half-way between the fingers. If you lift one of the fingers, place it on another point, you can immediately perform another zoom operation, the center of zoom changes correctly, whithout any jumps (Jump of the UI coordinates of objects in the canvas).

I am trying to implement a similar behavour on a large Canvas object (in a C# WinRT app) using a composite transform. I want to allow translation and scaling, no rotating (for now, maybe i'll add it later on):

I init like this, placing the scaling center at the screen center:

this.compositeTransform = new CompositeTransform();
this.compositeTransform.CenterX = this.mainPage.Width / 2.0;
this.compositeTransform.CenterY = this.mainPage.Height / 2.0;

this.innerCanvas.RenderTransform = compositeTransform;

Then I'll use the manipulation delta event for getting the input data

private void InnerCanvas_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{                        
    this.compositeTransform.ScaleX *= e.Delta.Scale;
    this.compositeTransform.ScaleY *= e.Delta.Scale;

    this.compositeTransform.CenterX = e.Position.X;
    this.compositeTransform.CenterY = e.Position.Y;

    this.compositeTransform.TranslateX += e.Delta.Translation.X;
    this.compositeTransform.TranslateY += e.Delta.Translation.Y;                      
}

This works correctly, as long as I perform the same gesture. The new center is calculated correctly, when I perform a pinching gesture. However, changing the zooming center, results in a sudden jump, for instance when lifting one of the fingers after the pinching gesture. Of course, I can change the center only for pinching gestures, but the problem of the jump remains. This is of course logical, since the scaling works with a new scaling center. I haven't figured out a way, how to avoid the jump. Since the scale value itself remains constant, it must be possible, to have the same appearance (unchanged coordinates) with a changed center.

My current reasoning is, that I must change the TranslateX and TranslateY coordinates somehow, in order to balance the new center point out in a way, that the current screen coordinates of the ui elements remain unchanged. Something like this (scaleTransform is a ScaleTransform, which only gets the scaling data)...

Point reverseScaleTransform =
    this.scaleTransform.Inverse.TransformPoint(new Point(e.Position.X,e.Position.Y));

this.compositeTransform.TranslateX += reverseScaleTransform.X - e.Position.X;
    this.compositeTransform.TranslateY += reverseScaleTransform.Y - e.Position.Y;

But this doen't work either. The whole thing seems to be a standard problem on tablet, but I haven't found a solution despite excessive search, maybe I use the wrong key-words.

like image 373
Sebastian Müllender Avatar asked Nov 03 '22 07:11

Sebastian Müllender


1 Answers

I finally figured out, how to solve the problem by manual transformations, not using a ScrollViewer.

    TranslateTransform tmpTranslate = new TranslateTransform();

    private void InnerCanvas_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
    {                                       
        //Transform world space into local space (origin: Scale center from the last manipulation event)            

        this.tmpTranslate.X = this.compositeTransform.CenterX;
        this.tmpTranslate.Y = this.compositeTransform.CenterY;

        Point center = this.compositeTransform.Inverse.TransformPoint(e.Position);

        Point localPoint = tmpTranslate.Inverse.TransformPoint(center);                        

        //Now scale the point in local space

        localPoint.X *= this.compositeTransform.ScaleX;
        localPoint.Y *= this.compositeTransform.ScaleY;

        //Transform local space into world space again
        Point worldPoint = tmpTranslate.TransformPoint(localPoint);

        //Take the actual scaling...
        Point distance = new Point(
            worldPoint.X - center.X,
            worldPoint.Y - center.Y);

        //...amd balance the jump of the changed scaling origin by changing the translation            

        this.compositeTransform.TranslateX += distance.X;
        this.compositeTransform.TranslateY += distance.Y;

        //Also set the scaling values themselves, especially set the new scale center...

        this.compositeTransform.ScaleX *= e.Delta.Scale;
        this.compositeTransform.ScaleY *= e.Delta.Scale;            

        this.compositeTransform.CenterX = center.X;
        this.compositeTransform.CenterY = center.Y;

        //And consider a translational shift

        this.compositeTransform.TranslateX += e.Delta.Translation.X;
        this.compositeTransform.TranslateY += e.Delta.Translation.Y;                                    
    }
like image 127
Sebastian Müllender Avatar answered Nov 14 '22 01:11

Sebastian Müllender