views:

403

answers:

2

I'm doing a surface app where I need to scale my scene (zoom) while the user scales with their fingers (i.e pinching)

Currently I have it working ok, but the issue is that I need to zoom in on the center point between the users fingers.

I have the point, but the maths behind the translation is hard to grasp.

When I apply a ScaleTransform to my Canvas scene, it zooms in on the top left of the canvas, i need it to zoom in on the center point of my pinch gesture (which, again, I do have).

How would the maths for the translation work to keep the zoom to appear to zoom in on the center point of the gesture?

Edit:

This is basically what I have:

void Affine2DManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
{
    //apply zoom and translate
    if (e.ScaleDelta != 1)
        ApplyZoom(e);
    else if (e.Delta.Length != 0)
        ApplyTranslation(e);
}

private void ApplyZoom(Affine2DOperationDeltaEventArgs e)
{
    //scale
    var newScale = _zoomTransform.ScaleX * e.ScaleDelta;
    newScale = GetCappedZoomLevel(newScale);

    _zoomTransform.ScaleX = newScale;
    _zoomTransform.ScaleY = newScale;
}

private void ApplyTranslation(Affine2DOperationDeltaEventArgs e)
{
    var xDiff = e.ManipulationOrigin.X - _screenStartPoint.X;
    var yDiff = e.ManipulationOrigin.Y - _screenStartPoint.Y;

    var translateX = xDiff + _startOffset.X;
    var translateY = yDiff + _startOffset.Y;

    //bounds testing to limit translation
    var rect = new Rect(0.0, 0.0, ZoomCanvas.RenderSize.Width, ZoomCanvas.RenderSize.Height);
    Rect bounds = ZoomCanvas.TransformToAncestor(MainViewportCanvas).TransformBounds(rect);

    if (CanTranslateX(translateX, bounds))
        _translateTransform.X = translateX;

    if (CanTranslateY(translateY, bounds))
        _translateTransform.Y = translateY;
}

Pretty basic really, but it works to a point...

_zoomTransform is a ScaleTransform, and _translateTransform is a TranslateTransform

MainViewport is a canvas that contains ZoomCanvas which is the canvas that I apply the transforms to.

+1  A: 

I had to do exactly this myself. Basically, a Surface control that can host arbitrary content and allow the user to pan and zoom (using gestures). The handler for my maniplation processor's Affine2DManipulationDelta is shown below. Hopefully it's fairly self-explanatory and gets you where you need to be.

private void OnManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
{
    Debug.Assert(_scrollViewer != null);

    if (CanScale && _scrollViewer != null && e.ScaleDelta != 1)
    {
        var scrollViewerContent = _scrollViewer.Content as UIElement;

        //can't do anything if the content isn't present and isn't a UIElement
        if (scrollViewerContent != null)
        {
            var newScale = Scale * e.ScaleDelta;

            newScale = Math.Max(MinScale, newScale);
            newScale = Math.Min(MaxScale, newScale);

            var origin = e.ManipulationOrigin;
            var pointInContent = _scrollViewer.TranslatePoint(origin, scrollViewerContent);

            var deltaScale = newScale - Scale;
            //width and height changes across the whole image
            var deltaWidth = deltaScale * _scrollViewer.ExtentWidth;
            var deltaHeight = deltaScale * _scrollViewer.ExtentHeight;
            //width and height changes relative to the point in the scroll viewer's content
            deltaWidth = (pointInContent.X / _scrollViewer.ExtentWidth) * deltaWidth;
            deltaHeight = (pointInContent.Y / _scrollViewer.ExtentHeight) * deltaHeight;

            _offset = Vector.Add(_offset, new Vector(deltaWidth, deltaHeight));
            var centerPoint = new Point(origin.X + deltaWidth, origin.Y + deltaHeight);
            centerPoint.Offset(_offset.X, _offset.Y);

            Scale = newScale;

            HorizontalOffset = _scrollViewer.HorizontalOffset + deltaWidth;
            VerticalOffset = _scrollViewer.VerticalOffset + deltaHeight;
        }
    }
    else if (CanPan && e.Delta.Length != 0)
    {
        var newHorizontalOffset = _scrollViewer.HorizontalOffset + (e.Delta.X * -1);
        var newVerticalOffset = _scrollViewer.VerticalOffset + (e.Delta.Y * -1);
        newHorizontalOffset = Math.Max(0, newHorizontalOffset);
        newVerticalOffset = Math.Max(0, newVerticalOffset);
        HorizontalOffset = newHorizontalOffset;
        VerticalOffset = newVerticalOffset;
    }
}

HTH,
Kent

Kent Boogaart
Just a few questions, is `Scale`, `CanPan` and `CanScale` just custom properties? Or have you extended a `ScrollViewer`? Would using scrollviewer be easier that using X and Y translations?
Mark
A: 

Here is my implementation that ended up working just fine

private void ApplyZoom(Affine2DOperationDeltaEventArgs e)
    {
        //scale
        var newScale = _zoomTransform.ScaleX * e.ScaleDelta;
        newScale = GetCappedZoomLevel(newScale);
        var pinchPoint = e.ManipulationOrigin;
        DoZoom(newScale, _transformGroup.Inverse.Transform(pinchPoint), pinchPoint);
    }

    private void DoZoom(double newScale, Point pinchPosition, Point physicalPosition)
    {
        _translateTransform.X = -1 * (pinchPosition.X * newScale - physicalPosition.X);
        _translateTransform.Y = -1 * (pinchPosition.Y * newScale - physicalPosition.Y);
        _zoomTransform.ScaleX = newScale;
        _zoomTransform.ScaleY = newScale;
    }
Mark