How do I get a Rectangle drawn on an adorner to scale with the Image element its bound to when the window size is changed?

1.2k Views Asked by At

I'm using a rectangle drawn on an adorner to mark a region of interest on an image. The issue is that if I resize the window, the rectangle doesn't change size.

I'm new to WPF, so I've done a bunch of research, googling what I can with multiple different search terms. I actually just learned adorners that way, and I've gotten this far on that, but I've hit a wall on how to finish this last piece. I know that my problem is based in the size of the rectangle, but I don't know what to capture/look for to adjust it, since wpf resizes the actual image object on window resize, so there's no scale factor to look at.

Here's the XAML for the application I'm testing things in.

<Window x:Class="TestingAdorners.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestingAdorners"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
        <Grid ClipToBounds="True">
            <AdornerDecorator>
                <Image Name="Btn" Source="nyan.png" Stretch="Uniform"/>
            </AdornerDecorator>
        </Grid>
</Window>

The adorner class:

    class RoiAdorner : Adorner
    {
        public Rect rectangle = new Rect();
        public RoiAdorner(UIElement adornedElement) : base(adornedElement)
        {
            rectangle.Height = 30;
            rectangle.Width = 100;
            IsHitTestVisible = false;

        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            Pen pen = new Pen(Brushes.Green, 5);

            drawingContext.DrawRectangle(null, pen, rectangle);

        }
    }

And the Xaml.cs

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            AdornerLayer.GetAdornerLayer(Btn).Add(new RoiAdorner(Btn));

        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
        }
    }

The desired result is that the rectangle scales with the image object so that it always covers the same region of the image. The problem is I don't know how to capture a scale factor to scale it up and down as the window resizes.

Update: After thinking through Frenchy's suggestion I realized the answer is simply: "Normalize your coordinates"

2

There are 2 best solutions below

3
Frenchy On BEST ANSWER

you just adapt your render method like this:

class RoiAdorner : Adorner
{
    public double factorX = 0d;
    public double factorY = 0d;
    public Rect rectangle = new Rect();
    public RoiAdorner(UIElement adornedElement) : base(adornedElement)
    {

        rectangle.Height = 30;
        rectangle.Width = 100;
        IsHitTestVisible = false;

    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        if (factorY == 0)
            factorY =  rectangle.Height / AdornedElement.DesiredSize.Height;
        if (factorX == 0)
            factorX = rectangle.Width / AdornedElement.DesiredSize.Width;
        var r = new Rect(new Size(AdornedElement.DesiredSize.Width * factorX, AdornedElement.DesiredSize.Height * factorY));
        //Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
        drawingContext.DrawRectangle(null, new Pen(Brushes.Red, 5), r);

    }

this.AdornedElement.DesiredSize gives you the size of image.

1
Andy On

The approach I would use is to render the picture and rectangle to the same thing. Then that one thing is stretched, scaled or whatever.

One way to do this would be to use a DrawingImage. Drawing methods are extremely efficient if rather low level.

    <Grid ClipToBounds="True">
        <AdornerDecorator>
            <Image Name="img" Stretch="Uniform">
                <Image.Source>
                    <DrawingImage PresentationOptions:Freeze="True">
                        <DrawingImage.Drawing>
                            <DrawingGroup>
                                <ImageDrawing Rect="0,0,595,446" ImageSource="DSC00025.jpg"/>
                                <GeometryDrawing Brush="Green">
                                    <GeometryDrawing.Geometry>
                                        <RectangleGeometry Rect="0,0,100,30"  />
                                    </GeometryDrawing.Geometry>
                                </GeometryDrawing>
                            </DrawingGroup>
                        </DrawingImage.Drawing>
                    </DrawingImage>
                </Image.Source>
            </Image>
        </AdornerDecorator>
    </Grid>

Another is with a visualbrush. Controls inherit from visual - this is somewhat higher level coding.

    <Grid ClipToBounds="True">
        <AdornerDecorator>
            <Rectangle Name="rec">
            <Rectangle.Fill>
                <VisualBrush Stretch="Uniform">
                    <VisualBrush.Visual>
                        <Grid Height="446" Width="595">
                            <Image Source="DSC00025.jpg" Stretch="Fill"/>
                            <Rectangle Height="30" Width="100" Fill="Green"
                                       VerticalAlignment="Top" HorizontalAlignment="Left"/>
                        </Grid>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Rectangle.Fill>
            </Rectangle>
        </AdornerDecorator>
    </Grid>

Note that both of these are quick and dirty illustrations to give you the idea. The image I picked at random off my hard drive is sized 446 * 595. You could calculate sizes or bind or stretch as suits your requirement best.