none
Scaling Adorners according to image RRS feed

  • Question

  • Hi,

    I have some shape adorners which adorn an image. The image is placed in an InkCanvas. If the image is resized I want the adorners to resize so that they remain in the same place relative to the image (they scale so that they appear to stay on the same place as they originally were). Does anyone know how I might approach this?

    Cheers,

    Nilu
    Thursday, November 19, 2009 12:54 AM

Answers

  • Ah! finally figured it out. :D

    At the moment what I am doing is that recognizing strokes and placing the recognized strokes as shapes on an adorner.  Obviously if the user selects the image and re-sizes it the adorners have to re-size as well. I wasn't able to figure it out on OnRender() method unfortunately. Because scaling transformed the position of the shapes as well as the size.

    So this is what I did:

    InkCanvas.SelectionResizing event -
    1. Here I only allow the user to resize the image in such a way that the image would always retain the same aspect ratio.
    2. Once the image has resized to the aspect ratio, I call UpdateScale(ScaleX, ScaleY) method in my adorner
    3. Finally I cancel the resizing as it wouldn't retain the aspect ratio (it is important to note that here the image is actually resized. But, not to the size the user specifies. It resizes such that it retains it's original aspect ratio
    InkCanvas.SelectionResizing

    Here in the InkCanvas.SelectionChanging I have saved all the images which are selected in a List called _selectedImages. You also have to retain the original width and height of the image - I have done this when the Image is inserted into the canvas.

    void _canvas_SelectionResizing(object sender, InkCanvasSelectionEditingEventArgs e)
            {
                if (_selectedImages.Count == 0)
                    return;
    
                foreach (Image item in _selectedImages)
                {
                    // we use the aspect ratio of the old size.
                    double oldAspectRatio = item.Width / item.Height;
    
                    double changeY = Math.Abs(e.NewRectangle.Height - e.OldRectangle.Height);
                    double changeX = Math.Abs(e.NewRectangle.Width - e.OldRectangle.Width);
    
                    if (changeX > 0) // width is modified
                    {
                        double height = e.NewRectangle.Width / oldAspectRatio;
    
                        // oldImageHeight and oldImageWidth are stored when the image is inserted
                        //double oldImageHeight = item.Height, oldImageWidth = item.Width;
    
                        item.Height = height;
                        item.Width = e.NewRectangle.Width;
    
                        // these should be the same
    
    
                        double heightScale = item.Height / oldImageHeight;
                        double widthScale = item.Width / oldImageWidth;
    
                        UpdateScale(widthScale, heightScale);
    
    
    
    
                    }
                    else if (changeY > 0)
                    {
                        double width = e.NewRectangle.Height * oldAspectRatio;
                        double oldImageHeight = item.Height, oldImageWidth = item.Width;
    
                        item.Height = e.NewRectangle.Height;
                        item.Width = width;
    
                        double heightScale = item.Height / oldImageHeight;
                        double widthScale = item.Width / oldImageWidth;
    
                        UpdateScale(widthScale, heightScale);
                    } 
                    // we only resize the image to maintain the aspect ratio
                    // - so cancel the "normal" resizing
                    e.Cancel = true;
               }
            }


     UpdateScale(ScaleX,ScaleY) method -
    1. There are three shape classes I have to consider when scaling: Ellipse, Rectangle and Polygon. The difference is that for Ellipse and Rectangle the renderTransform contains initialized offsetX and offsetY where as the Polygon only specifies the points. (Realizing this took me ages!! :( )
    2. So when rendering the Ellipse and Rectangle I scale the size of the Shape in the LayoutTransform (this doesn't move it - rather it scales it in place) and on the RenderTransform I translate it to the scaled position (which can be calculated using the offsetX and offsetY values of the original renderTransform).
    3. For the Polygon we only need to scale the size and retain the same position as the position in this case is contained in the points of the polygon.

    UpdateScale(ScaleX,ScaleY)

    Here I have save the original LayoutTransform and the RenderTransform in _shapeLayoutTransforms and _shapeRenderTransforms respectively. These values are used to transform the shapes to the new scale

     public void UpdateScale(double newXScale, double newYScale)
            {
                _scaleX = newXScale;
                _scaleY = newYScale;
    
                foreach (Shape item in _shapes)
                {
                    if (item is Rectangle || item is Ellipse)
                    {
                        // what we need to do: scale the size in layout transform
                        // scale the position in render transform
    
                        // scale the size in layout transform
                        TransformGroup group1 = new TransformGroup();
                        Transform originalLayoutTransform = _shapeLayoutTransforms[item];
    
                        group1.Children.Add(originalLayoutTransform);
                        group1.Children.Add(new TranslateTransform(-originalLayoutTransform.Value.OffsetX,
                            -originalLayoutTransform.Value.OffsetY)); // move back the transform to origin
                        // scale it
                        group1.Children.Add(new ScaleTransform(_scaleX, _scaleY));
                        item.LayoutTransform = group1;
    
                        // scale the position in render transform
                        item.RenderTransformOrigin = new Point(0.5, 0.5);
                        TransformGroup group2 = new TransformGroup();
                        double xOffset = _shapeRenderTransforms[item].Value.OffsetX * _scaleX;
                        double yOffset = _shapeRenderTransforms[item].Value.OffsetY * _scaleY;
    
                        //move to the scaled position
                        group2.Children.Add(new TranslateTransform(xOffset, yOffset)); 
                        item.RenderTransform = group2;
    
                    }
                    else if (item is Polygon)
                    {
                        Trace.WriteLine(item.GetType().ToString());
                        TransformGroup group = new TransformGroup();
                        group.Children.Add(_shapeLayoutTransforms[item]);
                        group.Children.Add(new ScaleTransform(_scaleX, _scaleY));
                        item.LayoutTransform = group;
                    }
                }
    
                // used to update the adorner
                Update();
            }


    Hope this helps.

    Cheers,

    Nilu
    • Marked as answer by nilz Thursday, November 19, 2009 10:20 PM
    Thursday, November 19, 2009 10:10 PM

All replies

  • Hi,

    when you set your adorner, you usually receive the size of the element then you can resize your adorner depending
    on the size of your object.

    OnRender() you can get the size of your element by

    Rect

     

    adornedElementRect = new Rect(this.AdornedElement.DesiredSize);

    then you can use DrawingContext() to set your adorner.


    Hope this helps.


    Reply,

    Gio


    vb.net GUI
    Thursday, November 19, 2009 2:35 AM
  • Hi Gio,

    Thanks for the reply! I will try it out.

    Cheers,

    Nilu
    Thursday, November 19, 2009 3:02 AM
  • Ah! finally figured it out. :D

    At the moment what I am doing is that recognizing strokes and placing the recognized strokes as shapes on an adorner.  Obviously if the user selects the image and re-sizes it the adorners have to re-size as well. I wasn't able to figure it out on OnRender() method unfortunately. Because scaling transformed the position of the shapes as well as the size.

    So this is what I did:

    InkCanvas.SelectionResizing event -
    1. Here I only allow the user to resize the image in such a way that the image would always retain the same aspect ratio.
    2. Once the image has resized to the aspect ratio, I call UpdateScale(ScaleX, ScaleY) method in my adorner
    3. Finally I cancel the resizing as it wouldn't retain the aspect ratio (it is important to note that here the image is actually resized. But, not to the size the user specifies. It resizes such that it retains it's original aspect ratio
    InkCanvas.SelectionResizing

    Here in the InkCanvas.SelectionChanging I have saved all the images which are selected in a List called _selectedImages. You also have to retain the original width and height of the image - I have done this when the Image is inserted into the canvas.

    void _canvas_SelectionResizing(object sender, InkCanvasSelectionEditingEventArgs e)
            {
                if (_selectedImages.Count == 0)
                    return;
    
                foreach (Image item in _selectedImages)
                {
                    // we use the aspect ratio of the old size.
                    double oldAspectRatio = item.Width / item.Height;
    
                    double changeY = Math.Abs(e.NewRectangle.Height - e.OldRectangle.Height);
                    double changeX = Math.Abs(e.NewRectangle.Width - e.OldRectangle.Width);
    
                    if (changeX > 0) // width is modified
                    {
                        double height = e.NewRectangle.Width / oldAspectRatio;
    
                        // oldImageHeight and oldImageWidth are stored when the image is inserted
                        //double oldImageHeight = item.Height, oldImageWidth = item.Width;
    
                        item.Height = height;
                        item.Width = e.NewRectangle.Width;
    
                        // these should be the same
    
    
                        double heightScale = item.Height / oldImageHeight;
                        double widthScale = item.Width / oldImageWidth;
    
                        UpdateScale(widthScale, heightScale);
    
    
    
    
                    }
                    else if (changeY > 0)
                    {
                        double width = e.NewRectangle.Height * oldAspectRatio;
                        double oldImageHeight = item.Height, oldImageWidth = item.Width;
    
                        item.Height = e.NewRectangle.Height;
                        item.Width = width;
    
                        double heightScale = item.Height / oldImageHeight;
                        double widthScale = item.Width / oldImageWidth;
    
                        UpdateScale(widthScale, heightScale);
                    } 
                    // we only resize the image to maintain the aspect ratio
                    // - so cancel the "normal" resizing
                    e.Cancel = true;
               }
            }


     UpdateScale(ScaleX,ScaleY) method -
    1. There are three shape classes I have to consider when scaling: Ellipse, Rectangle and Polygon. The difference is that for Ellipse and Rectangle the renderTransform contains initialized offsetX and offsetY where as the Polygon only specifies the points. (Realizing this took me ages!! :( )
    2. So when rendering the Ellipse and Rectangle I scale the size of the Shape in the LayoutTransform (this doesn't move it - rather it scales it in place) and on the RenderTransform I translate it to the scaled position (which can be calculated using the offsetX and offsetY values of the original renderTransform).
    3. For the Polygon we only need to scale the size and retain the same position as the position in this case is contained in the points of the polygon.

    UpdateScale(ScaleX,ScaleY)

    Here I have save the original LayoutTransform and the RenderTransform in _shapeLayoutTransforms and _shapeRenderTransforms respectively. These values are used to transform the shapes to the new scale

     public void UpdateScale(double newXScale, double newYScale)
            {
                _scaleX = newXScale;
                _scaleY = newYScale;
    
                foreach (Shape item in _shapes)
                {
                    if (item is Rectangle || item is Ellipse)
                    {
                        // what we need to do: scale the size in layout transform
                        // scale the position in render transform
    
                        // scale the size in layout transform
                        TransformGroup group1 = new TransformGroup();
                        Transform originalLayoutTransform = _shapeLayoutTransforms[item];
    
                        group1.Children.Add(originalLayoutTransform);
                        group1.Children.Add(new TranslateTransform(-originalLayoutTransform.Value.OffsetX,
                            -originalLayoutTransform.Value.OffsetY)); // move back the transform to origin
                        // scale it
                        group1.Children.Add(new ScaleTransform(_scaleX, _scaleY));
                        item.LayoutTransform = group1;
    
                        // scale the position in render transform
                        item.RenderTransformOrigin = new Point(0.5, 0.5);
                        TransformGroup group2 = new TransformGroup();
                        double xOffset = _shapeRenderTransforms[item].Value.OffsetX * _scaleX;
                        double yOffset = _shapeRenderTransforms[item].Value.OffsetY * _scaleY;
    
                        //move to the scaled position
                        group2.Children.Add(new TranslateTransform(xOffset, yOffset)); 
                        item.RenderTransform = group2;
    
                    }
                    else if (item is Polygon)
                    {
                        Trace.WriteLine(item.GetType().ToString());
                        TransformGroup group = new TransformGroup();
                        group.Children.Add(_shapeLayoutTransforms[item]);
                        group.Children.Add(new ScaleTransform(_scaleX, _scaleY));
                        item.LayoutTransform = group;
                    }
                }
    
                // used to update the adorner
                Update();
            }


    Hope this helps.

    Cheers,

    Nilu
    • Marked as answer by nilz Thursday, November 19, 2009 10:20 PM
    Thursday, November 19, 2009 10:10 PM