0
votes

I have a rectangle on a canvas that the user can resize, move and so on to make a selection. I also have an image the size of the screen behind the canvas (basically a screenshot).

I'd like to translate the selection (the rectangle) in the canvas to a 1:1 selection in the image (I want the image directly behind the rectangle) given I have the rectangle's Canvas.Top, Canvas.Left, Width, Height.

<Grid Name="MainGrid" SnapsToDevicePixels="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <Image x:Name="MainImage" Stretch="None" RenderOptions.BitmapScalingMode="HighQuality"/>
    <Border Background="Black" Opacity="0.4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
    <Canvas Name="MainCanvas" Width="{Binding Source={x:Static SystemParameters.PrimaryScreenWidth}}" Height="{Binding Source={x:Static SystemParameters.PrimaryScreenHeight}}" Background="Transparent">
        <ContentControl Name="SelectionRect" />
        </ContentControl>
    </Canvas>
</Grid>

I tried doing this: (MainImage is the image under the canvas)

Rect rect = new Rect(Canvas.GetLeft(SelectionRect), Canvas.GetTop(SelectionRect), SelectionRect.Width, SelectionRect.Height);
Rect from_rect = SelectionRect.TransformToVisual(this).TransformBounds(rect);

BitmapSource cropped_bitmap = new CroppedBitmap(MainImage.Source as BitmapSource, 
            new Int32Rect((int)from_rect.X, (int)from_rect.Y, (int)from_rect.Width, (int)from_rect.Height));
SelectionRectImageSource = cropped_bitmap;

But the image I get (SelectionRectImageSource) is a moved aside version of the actual pixels behind the selection rectangle. So basically, I don't understand how these transformations work and how I should use them if at all.

Example: Example

Thanks a lot! Dolev.

1

1 Answers

2
votes

Looks like you need to correct for the DPI difference between the image (usually 72dpi) and the presentation source (usually 96dpi). Additionally, your first Rect should not be offset by Canvas.Left and Canvas.Top; TransformToVisual will take care of the relative offset for you.

var source = (BitmapSource)MainImage.Source;

var selectionRect = new Rect(SelectionRect.RenderSize);

var sourceRect = SelectionRect.TransformToVisual(MainImage)
                              .TransformBounds(selectionRect);

var xMultiplier = source.PixelWidth / MainImage.ActualWidth;
var yMultiplier = source.PixelHeight / MainImage.ActualHeight;

sourceRect.Scale(xMultiplier, yMultiplier);

var croppedBitmap = new CroppedBitmap(
    source,
    new Int32Rect(
        (int)sourceRect.X,
        (int)sourceRect.Y,
        (int)sourceRect.Width,
        (int)sourceRect.Height));

SelectionRectImageSource= croppedBitmap;

Depending on where this code resides, you may also need to transform the selection rectangle to MainImage instead of this (as I did).

Also, in case MainImage.Source is smaller than the actual MainImage control, you should probably set the horizontal and vertical alignments of MainImage to Left and Top, respectively, less your translated rectangle end up outside the bounds of the source image. You'll need to clamp the selection rectangle to the dimensions of MainImage too.